CPU、GPU、および FPGA 間の移行
インテル® oneAPI DPC++ コンパイラーを使用した SYCL* プログラミングでは、プラットフォームは CPU、GPU、FPGA、またはその他のアクセラレーターやプロセッサーなどのデバイスに接続されたホストデバイスで構成されます。
プラットフォームに複数のデバイスが存在する場合、一部または大部分のワークをデバイスへオフロードするようにアプリケーションを設計します。oneAPI プログラミング・モデルを使用して複数のデバイスにワークを分散するには、いくつかの方法があります:
デバイスセレクターの初期化 - SYCL* ではセレクターと呼ばれるクラスが提供され、プラットフォーム内のデバイスを手動で選択するか、oneAPI ランタイムのヒューリスティックによってデバイスで利用可能な計算機能を判断してデフォルトデバイスを選択できるようになっています。
データセットの分割 - データに依存関係がない高い並列性を持つアプリケーションでは、データセットを明示的に分割して異なるデバイスで利用します。次のサンプルコードは、複数のデバイスにワークロードを分配する例です。
icpx -fsycl snippet.cppコマンドでコードをコンパイルします。int main() { int data[1024]; for (int i = 0; i < 1024; i++) data[i] = i; try { cpu_selector cpuSelector; queue cpuQueue(cpuSelector); gpu_selector gpuSelector; queue gpuQueue(gpuSelector); buffer<int, 1> buf(data, range<1>(1024)); cpuQueue.submit([&](handler& cgh) { auto ptr = buf.get_access<access::mode::read_write>(cgh); cgh.parallel_for<class divide>(range<1>(512), [=](id<1> index) { ptr[index] -= 1; }); }); gpuQueue.submit([&](handler& cgh1) { auto ptr = buf.get_access<access::mode::read_write>(cgh1); cgh1.parallel_for<class offset1>(range<1>(1024), id<1>(512), [=](id<1> index) { ptr[index] += 1; }); }); cpuQueue.wait(); gpuQueue.wait(); } catch (exception const& e) { std::cout << "SYCL exception caught: " << e.what() << '\n'; return 2; } return 0; }
デバイス間で複数のカーネルをターゲットにする - アプリケーションに複数の独立したカーネルで並列処理可能なスコープがある場合、ターゲットデバイスごとに異なるキューを使用します。SYCL* がサポートされるプラットフォームとプラットフォームごとのデバイスのリストは、
get_platforms()とplatform.get_devices()) を呼び出して取得できます。すべてのデバイスが特定されたら、デバイスごとにキューを作成し、異なるカーネルを異なるキューに配置します。次のサンプルコードは、複数の SYCL* デバイスにカーネルを配置する方法を示します。#include <stdio.h> #include <vector> #include <CL/sycl.hpp> using namespace cl::sycl; using namespace std; int main() { size_t N = 1024; vector<float> a(N, 10.0); vector<float> b(N, 10.0); vector<float> c_add(N, 0.0); vector<float> c_mul(N, 0.0); { buffer<float, 1> abuffer(a.data(), range<1>(N), { property::buffer::use_host_ptr() }); buffer<float, 1> bbuffer(b.data(), range<1>(N), { property::buffer::use_host_ptr() }); buffer<float, 1> c_addbuffer(c_add.data(), range<1>(N), { property::buffer::use_host_ptr() }); buffer<float, 1> c_mulbuffer(c_mul.data(), range<1>(N), { property::buffer::use_host_ptr() }); try { gpu_selector gpuSelector; auto queue = cl::sycl::queue(gpuSelector); queue.submit([&](cl::sycl::handler& cgh) { auto a_acc = abuffer.template get_access<access::mode::read>(cgh); auto b_acc = bbuffer.template get_access<access::mode::read>(cgh); auto c_acc_add = c_addbuffer.template get_access<access::mode::write>(cgh); cgh.parallel_for<class VectorAdd> (range<1>(N), [=](id<1> it) { //int i = it.get_global(); c_acc_add[it] = a_acc[it] + b_acc[it]; }); }); cpu_selector cpuSelector; auto queue1 = cl::sycl::queue(cpuSelector); queue1.submit([&](cl::sycl::handler& cgh) { auto a_acc = abuffer.template get_access<access::mode::read>(cgh); auto b_acc = bbuffer.template get_access<access::mode::read>(cgh); auto c_acc_mul = c_mulbuffer.template get_access<access::mode::write>(cgh); cgh.parallel_for<class VectorMul> (range<1>(N), [=](id<1> it) { c_acc_mul[it] = a_acc[it] * b_acc[it]; }); }); } catch (cl::sycl::exception e) { /* 例外がスローされた場合は、 エラーメッセージを出力し、 * 1 を返します。*/ std::cout << e.what(); return 1; } } for (int i = 0; i < 8; i++) { std::cout << c_add[i] << std::endl; std::cout << c_mul[i] << std::endl; } return 0; }