이 튜토리얼에서는 compute set 로, 실행을 위한 작업단위 조각(compute graph의 vertex)으로 어떻게 계산 단계가 구성되는지 살펴보겠습니다. 여기에 설명된 compute set를 구성하는 가정은 PopLibs 라이브러리에서 사용하는 것과 동일한 방법입니다. Vertex에 대해 자세히 알아보려면 Poplar 및 PopLibs 사용자 가이드: 정점 이해를 참조하세요.
IPU에서 이 튜토리얼을 실행하려면, Poplar SDK 환경을 활성화해야 합니다(IPU 시스템 시작 안내서 참조).
또한 C++11 표준과 호환되는 C++ 도구 체인이 필요합니다. 이 튜토리얼의 빌드 명령은 GCC를 사용합니다.
tut3_vertices/start_here 작업 디렉터리로 사용하여 tut3.cpp 파일을 편집기에서 엽니다. 이 파일에는 튜토리얼 2와 같은 outline program이 있지만 PopLibs 라이브러리를 사용하지 않습니다. 대신 C++로 vertex에 대한 device code를 작성하겠습니다.
프로그램은 처음에 두 개의 요소를 4-element 벡터 두 개(v1 및 v2)를 Graph 에 추가합니다. 우리가 추가할 코드는 v2의 각 요소를 v1의 합계로 설정합니다. 따라서 v2[0] 은 v1 의 모든 요소의 합이 저장되고 v2[1] 에는 v1 의 모든 요소의 합이 저장됩니다.
이 operation을 구현하려면 device에서 실행할 몇 가지 코드를 작성해야 하며 대개 코드렛(codelet)이라 불립니다. 튜토리얼 디렉토리에 이를 위한 파일이 tut3_codelets.cpp 으로 제공됩니다. 작업 디렉터리에 이 파일의 복사본을 만듭니다.
// Add codelets to the graph
graph.addCodelets("tut3_codelets.cpp");
이는 명령은 호스트 프로그램이 device code를 그래프에 로드하고 컴파일하여 device에서 실행하도록 지시합니다.
tut3_codelets.cpp 내부에는 codelet 개요가 있습니다. 모든 Poplar codelet과 마찬가지로, poplar::Vertex 클래스에서 파생된 C++ 클래스이며, compute이라는 단일 멤버 함수를 가진다. 이 함수는 vertex에 의해 수행된 작업을 나타낸다. 이 compute 함수는 성공적으로 완료되면 true를 반환합니다.
숫자 집합을 가져와서 해당 숫자의 합을 작성하는 코드를 이 vertex에 추가할 것이다.
class SumVertex : public poplar::Vertex {
public:
// Fields
poplar::Input<poplar::Vector<float>> in;
poplar::Output<float> out;
in과 out이라는 이름으로 지정된 필드는 외부 tensor와 vertex의 연결을 나타낸다. 이는 연산 중인 텐서 데이터를 읽고 쓰기 위해 compute 함수 본문에서 사용됩니다.
// Compute function
bool compute() {
*out = 0;
for (const auto &v : in) {
*out += v;
}
return true;
}
destination tensor가 다른 tile에 있더라도 out field가 업데이트될 수 있다는 것을 기억해야 합니다. 이는 vertex가 data의 로컬 복사본에서 작동하기 때문입니다. 최종 결과는 계산이 완료된 후 exchange phase에서 destination tile로 전송됩니다.
이제 device code가 있으므로, 이를 실행하는 step을 구축하고 이를 control program에 추가할 수 있습니다. 이렇게 하려면 다음을 수행해야 합니다.
이에 대해서는 아래에서 더 자세히 설명합니다.
1. Compute Set 생성: v1 초기화 코드 다음에 tut3.cpp의 control program에 다음 선언을 추가합니다(string argument는 디버그 식별자임):
ComputeSet computeSet = graph.addComputeSet("computeSet");
2. Compute set에 vertex 4개를 추가: compute set 선언 코드 뒤에 다음 루프를 추가합니다. 이는 codelet에 정의된 클래스의 이름을 전달하며, 이는 각 vertex에 대한 해당 클래스의 인스턴스를 생성합니다. 각 vertex는 v2의 각 요소를 출력합니다.
for (unsigned i = 0; i < 4; ++i) {
VertexRef vtx = graph.addVertex(computeSet, "SumVertex");
}
"SumVertex" argument는 사용할 vertex 타입을 지정해야 합니다. 이 경우, graph에 로드된tut3_codelets.cpp 파일에 정의된 vertex입니다.
3. Define the connections: 방금 만든 loop 본문에 다음 코드를 추가하세요. 이는 input 및 output variable을 vertex에 연결합니다. tensor operator와 loop index를 사용하여, 각 vertex은 서로 다른 tensor element에 연결됩니다.
graph.connect(vtx["in"], v1.slice(i, 4));
graph.connect(vtx["out"], v2[i]);
4. tile mapping 설정: 동일한 loop 본문에 다음 코드를 추가하세요.
graph.setTileMapping(vtx, i);
여기서 각 vertex는 다른 tile에 매핑됩니다.
IPU 모델 시뮬레이션을 사용하여 성능을 프로파일링하려는 경우, vertex에 대한 cycle estimate를 설정할 수 있습니다. 이는 IPU에서 codelet을 실행하는 데 걸리는 cycle 수 입니다. 여기서는 cycle estimate을 20 cycles로 설정했습니다.
graph.setPerfEstimate(vtx, 20);
compute set를 생성한 후 마지막 작업은 control program을 compute set에서 실행하는 단계를 추가하는 것입니다.
// Add step to execute the compute set
prog.add(Execute(computeSet));
이제 v2 tensor가 예상 값으로 업데이트 되었음을 확인할 수 있습니다.
v2: [7.0000000 6.0000000 4.5000000 2.5000000]
popc tut3_codelets.cpp -o tut3_codelets.gp
그 후, 프로그램에서 소스 코드 대신 컴파일된 코드를 로드하여 사용할 수 있습니다.
// Add codelets to the graph
graph.addCodelets("tut3_codelets.gp");