このセクションでは、デバイス (結果) バッファーの一部など、アクセラレータとの間の特殊なデータ転送や、単一のアクセラレータ計算で複数のファイルを同時に処理できるようにするさまざまな形式のファイル転送について説明します。
I/O サブバッファーの転送のカスタマイズ
出力バッファー全体をホストに同期し戻さない場合、同期し戻すもののサイズがホスト コードの先頭では既知でない場合があります。良い例は圧縮です。特に、圧縮アルゴリズムが複数のチャンクを同じ出力バッファーに圧縮して、出力バッファーからその圧縮されたチャンクのみを同期し戻す場合です。これには、次の例に示すように、データが利用可能になったときに同期し戻されるのとまったく同じ内容を制御する関数をレジスタに入力します。
xfilter::custom_sync_outputs([=]()
{
auto fut = xfilter::sync_output<int>(outSz, chunks, 0);
fut.get();
for (int chunk = 0; chunk < chunks; ++chunk) {
xfilter::sync_output<int>(out, outSz[chunk], chunk * chunkSz);
}
});
custom_sync_outputs()
メソッドは、どのバッファー/サブバッファーをホストに同期し戻すかを決定するコールバック関数をレジスタに入力します。このメソッドは、最初の compute()
呼び出しの前に、send_while
本文内で呼び出す必要があります。
custom_sync_outputs()
を呼び出すと、出力バッファーの自動転送戻しが無効になります。コールバック関数内では、同期し戻す内容と、同期する必要があるかどうかがユーザー定義コードにより完全に制御されます。上記の例では、outSz
バッファーを同期し戻します。このバッファーには、圧縮されたチャンクのサイズが含まれます。sync_output
API は std::future
を戻します。その future で fut.get()
を呼び出すと、コードはその同期が完了するまで待機します。次に、すべてのチャンクを正確なサイズでホストに転送し戻します。これらの呼び出しも future を戻しますが、これらの同期のために同期する必要はなくなります。システム コンパイル ランタイム層は、対応する receive_all
イテレーションを開始する前に、これらのすべての同期が完了していることを確認します。
ファイル転送モード
システム コンパイル モードでは、サポートされるプラットフォームとスタートアップの例 の file_filter_sc
に示すように、ファイルの簡単な読み出しおよび書き込みもサポートされます。これは、VPP_ACC クラスの API で説明するように、次のいずれかのモードを使用して create_bufpool()
メソッドで有効にできます。
-
vpp::p2p
モード: ファイルはピア ツー ピア (P2P) の PCIe ブリッジを介してアクセラレータに転送されます。これは、P2P 機能をサポートするプラットフォーム (たとえば、接続された smartSSD を搭載した U2 カードなど) でのみ機能します。 -
vpp::h2c
モード: ファイルは PCIe を介してホスト CPU (接続されたファイル サーバー) からカードに転送されます。これは、PCIe を介してホスト CPU に接続されるほとんどの Alveo カードの標準です。
このシンプルなファイル転送オプション (vpp::p2p
および vpp::h2c
) を使用すると、アクセラレータ デザインがプラットフォーム間でポータブルになります。データ ファイルをホストする一般的なホスト CPU (ファイル サーバーに接続) 上でソフトウェア エミュレーションを使用して、プラットフォーム用にデザインをテストするのは簡単ですが、最終的には、デザインを SmartSSD に接続された U2 カードなどの P2P をサポートする特定のプラットフォーム用にコンパイルする必要があります。これにより、デザイン ソースを変更せずに直接移植できるようになります。
次のコード例は、これをデモンストレーションしています。
auto inBP = my_acc::create_bufpool(vpp::input , P2P ? vpp::p2p : vpp::h2c);
my_acc::send_while([=]() {
if (P2P) o_flags |= O_DIRECT;
int fd = open(fnm, o_flags, s_flags);
DT* in = (DT*)xfilter::file_buf(inBP, fd, fsz);
my_acc::compute(in, ...);
...
このコードは、P2P
フラグの値に基づいて、ホストにマップされた NVMe デバイス ファイルのピア ツー ピア (P2P) 転送を実行するか、ホスト ファイルからデバイス (H2C) 転送を実行します。P2P の場合、ファイルは NVMe デバイスから直接デバイス メモリにロードされ、ホスト経由でのデータ転送は実行されません。H2C の場合、システム コンパイル ランタイム層がホストからデバイス バッファー (出力の場合はその逆) にファイルを自動的に転送します。
P2P をイネーブルにするには、上の例に示すように、O_DIRECT
フラグを指定してファイルを開く必要があります。P2P モードでは、VPP_ACC::file_buf
への呼び出しによって返されるホスト ポインターは、その計算呼び出し引数のハンドルにすぎず、読み出しや書き込みはできません。
マルチファイル バッファー
VPP_ACC クラスの API で説明するように、compute()
メソッドを呼び出す前に file_buf
メソッドを複数回呼び出したり、複数のファイルや複数のファイル セグメントを 1 つのデバイス バッファーにマップしたりできます。次のコード例は、アクセラレータが 1 回の compute()
呼び出しで同時に処理する複数のファイルの一部分を示しています。
void* VPP_ACC::file_buf(VPP_BP bp, int fd, size_t sz, off_t fos = 0, off_t bos = 0)
xfilter::send_while([=, &total_out_size]()
{
static int iter = 0;
int* in;
// collect all "chunks" input files into one "in" buffer
for (int chunk = 0; chunk < chunks; ++chunk) {
std::stringstream nm;
nm << DATA << iter << '-' << chunk << ".orig";
int ifd = open(nm.str().c_str(), rd_o_flags);
assert(ifd > 2);
in = xfilter::file_buf<int>(inBP, ifd, chunkSz, 0, chunk * chunkSz);
}
// prepare output buffer to be able to hold all chunks
int* out = xfilter::file_buf<int>(outBP, 0, chunks * chunkSz, 0);
// output buffer to provide the actual filtered size of each chunk
int* outSz = xfilter::alloc_buf<int>(outSzBP, chunks);
....
xfilter::compute(chunks, chunkSz, in, out, outSz);
....
});
このコードは、これらのファイル セグメントを保持する入力 (in
) バッファーと、処理された出力データを保持する出力 (out
) バッファーを作成します。各 send_while
イテレーションでは、file_buf()
が chunkSz
および in
バッファーに関連付けられた各ファイルの読み出しオフセット (chunk*chunkSz
) を提供します。その後の compute()
への呼び出しでは、これらのすべてのファイル セグメントが入力に書き込まれるか、出力デバイス バッファーから読み出されます。
in =
xfilter::file_buf<...>
割り当てが繰り返され、最後に戻されるポインターが in
に割り当てられます。これは、アプリケーション コードを記述する際に必ず従う必要があります。出力ファイルのカスタム転送
また、ファイル バッファーに対してカスタム同期を使用することもできます。この場合、出力バッファーを custom_sync_outputs()
のファイル記述子に同期できます。VPP_ACC クラスの API で説明するように、これは sync_output_to_file()
メソッドを呼び出すことで実行できます。次はその例です。
VPP_ACC::sync_output_to_file(void* buf, int fd, size_t byte_sz,
off_t fd_byte_offset = 0,
off_t buf_byte_offset = 0);
この場合、VPP_ACC::file_buf(bufPool, fd, sz)
への呼び出しで追加されたファイルは自動的に同期されません。そのため、file_buf
API への呼び出しは、実際にファイルを追加するためには必要はありませんが、ホスト ポインターを戻すためだけに必要となっています。VPP_ACC::file_buf(bufPool,0,0);
のようなダミー ファイルを追加するのが最適な方法です。
次に、コード例の一部分を示します。
my_acc::send_while([=]() {
DT* out = (DT*)my_acc::file_buf(outBP, 0, 0);
...
my_acc::custom_sync_outputs([=](){
...
auto fut = my_acc::sync_output_to_file(out, fd, sz, fd_offset, buf_offset);
...
});
...