Vengineerの戯言

人生は短いけど、長いです。人生を楽しみましょう!

Graphcore : Poplar Tutorial (その1)

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

github.com

チュートリアルは、次の7つ。

* Tutorial 1: programs and variables
* Tutorial 2: using Poplibs
* Tutorial 3: writing vertex code
* Tutorial 4: profiling output
* Tutorial 5: a basic machine learning example
* Tutorial 6: matrix-vector multiplication
* Tutorial 7: matrix-vector multiplication optimisation

 この例題を見ることで、IPU がどんな感じに動くか、妄想できるかな?

Tutorial1の tutorials/poplar/tut1_variables/complete/tut1_ipu_model_complete.cpp を見ていきます。

int main() {
    // Create the IPU model device
    IPUModel ipuModel;
    Device device = ipuModel.createDevice();
    Target target = device.getTarget();

 ここまでは、IPUのモデル(実機ではない)、のDevice と Target を獲得しています。

    // Create the Graph object
    Graph graph(target);

    // Add variables to the graph
    Tensor v1 = graph.addVariable(FLOAT, {4}, "v1");
    Tensor v2 = graph.addVariable(FLOAT, {4}, "v2");
    Tensor v3 = graph.addVariable(FLOAT, {4, 4}, "v3");
    Tensor v4 = graph.addVariable(INT, {10}, "v4");

ここまでで、Graph を作って、Variable (v1/v2/v3/v4) を追加します。

    // Allocate v1 to reside on tile 0
    graph.setTileMapping(v1, 0);

    // Spread v2 over tiles 0..3
    for (unsigned i = 0; i < 4; ++i)
        graph.setTileMapping(v2[i], i);

    // Allocate v3, t4 to tile 0
    graph.setTileMapping(v3, 0);
    graph.setTileMapping(v4, 0);

ここまでで、各 Variable をどこの Tile (0) にマッピングするかを指定します。

    // Add a constant tensor to the graph

    Tensor c1 = graph.addConstant<float>(FLOAT, {4}, {1.0, 1.5, 2.0, 2.5});
    graph.setTileMapping(c1, 0);

 定数を Tile (0) にマッピングします。

    // Create a control program that is a sequence of steps
    program::Sequence prog;

    // Add a step to initialize v1 with the constant value in c1
    prog.add(program::Copy(c1, v1));
    // Debug print the tensor to the host console
    prog.add(program::PrintTensor("v1-debug", v1));

    // Copy the data in v1 to v2
    prog.add(program::Copy(v1, v2));
    // Debug print v2
    prog.add(program::PrintTensor("v2-debug", v2));

 シーケンシャルな Program を定義し、以下の4つのプログラムを追加します。

  • Copy(c1, v1)
  • PrintTensor("v1-debug", v1)
  • Copy(v1, v2)
  • PrintTensor("v2-debug", v2)

 この4つのプログラムはシーケンシャルに実行します。

つまり、c1 => v1 => v2 の代入になります。

    // Create host read/write handles for v3
    graph.createHostWrite("v3-write", v3);
    graph.createHostRead("v3-read", v3);

 ここで、v3 を ホストから Write/Read できるようにします。

    // Copy a slice of v1 into v3
    Tensor v1slice = v1.slice(0, 3);
    Tensor v3slice = v3.slice({1, 1}, {2, 4});
    prog.add(program::Copy(v1slice, v3slice));

 v1のスライス (0,1) を v3のスライス({1,1}, {2,4}) にコピーします。

    // Add a data stream to fill v4
    DataStream inStream = graph.addHostToDeviceFIFO("v4-input-stream", INT, 10);

ホストからデバイスへのINT型のデータを10を入力します。

    // Add program steps to copy from the stream
    prog.add(program::Copy(inStream, v4));
    prog.add(program::PrintTensor("v4-0", v4));
    prog.add(program::Copy(inStream, v4));
    prog.add(program::PrintTensor("v4-1", v4));

以下の4つのプログラムを追加します。

  • Copy(inStream, v4)
  • PrintTensor("v4-0", v4)
  • Copy(inStream, v4)
  • PrintTensor("v4-1", v4)

 この4つのプログラムはシーケンシャルに実行します。

GraphとProgramuをEngineに割当、Device にロードします。

    // Create the engine
    Engine engine(graph, prog);
    engine.load(device);

Variable v3 にデータ(3*3の0)を書き込みます。

    // Copy host data via the write handle to v3 on the device
    std::vector<float> h3(4 * 4, 0);
    engine.writeTensor("v3-write", h3.data());

 inDataを初期化し、v4-input-stream に接続します。

    // Create a buffer to hold data to be fed via the data stream
    std::vector<int> inData(10 * 3);
    for (unsigned i = 0; i < 10 * 3; ++i)
    inData[i] = i;

    // Connect the data stream
    engine.connectStream("v4-input-stream", &inData[0], &inData[10 * 3]);

プログラムを実行 (run) します。

    // Run the control program
    std::cout << "Running program\n";
    engine.run(0);
    std::cout << "Program complete\n";

実行結果の v3 を h3 にコピーし、結果を表示します。

    // Copy v3 back to the host via the read handle
    engine.readTensor("v3-read", h3.data());

    // Output the copied back values of v3
    std::cout << "\nh3 data:\n";
    for (unsigned i = 0; i < 4; ++i) {
        std::cout << " ";
        for (unsigned j = 0; j < 4; ++j) {
            std::cout << h3[i * 4 + j] << " ";
        }
        std::cout << "\n";
    }

    return 0;
}

tutorials/poplar/tut1_variables/complete/tut1_ipu_hardware_complete.cpp では、

    // Create the IPU model device
    IPUModel ipuModel;
    Device device = ipuModel.createDevice();

を DeviceManager を使って、実機のDeviceを見つけているだけで、それ以外は同じです。

    // Create the DeviceManager which is used to discover devices
    DeviceManager manager = DeviceManager::createDeviceManager();

    // Attempt to attach to a single IPU:
    Device device;
    bool success = false;
    // Loop over all single IPU devices on the host
    // Break the loop when an IPU is successfully acquired
    for (auto &hwDevice : manager.getDevices(poplar::TargetType::IPU, 1)) {
        device = std::move(hwDevice);
        std::cerr << "Trying to attach to IPU " << device.getId() << std::endl;
        if ((success = device.attach())) {
            std::cerr << "Attached to IPU " << device.getId() << std::endl;
            break;
       }
    }
    if (!success) {
        std::cerr << "Error attaching to device" << std::endl;
    return -1;
}