InfiniBand -- RC(Reliable Connection)でのpingpongサンプルコード
コード
概要
ここでは二つのプロセス (SenderとReceiver) を作成し、InfiniBandを介してデータを送信するコードを紹介します。
InfiniBand VerbsではQueryPair (QP)を使って、データの送受信を行う。このQPの状態をRESET → INIT → RTR → RTSに遷移させておく必要があり、このパラメータ指定がめんどくさい。 また、注意点としてデータの送信側は、送り先のプロセスが持つQPに紐づいている(ランダムに割り振られる)番号を知っておく必要があります。
QP (Queue Pair) タイプ
InfiniBandでは, RC, RD, UC, UD の4種類の通信方式がある。 よく使われるのはおそらくRC, UDの二つ。
信頼性や接続型に違いがある。
| 特徴/要素 | RC (Reliable Connection) | UD (Unreliable Datagram) |
|---|---|---|
| 信頼性 | 高い(再送や順序の保証あり) | 低い(再送や順序の保証なし) |
| 通信形態 | 接続型通信(1対1) | コネクションレス通信(1対多も可能) |
| エンドポイント管理 | QP ごとに接続を管理 | 明示的な接続管理は不要 |
| 状態管理 | 各接続の状態を管理 | 最小限の状態のみ管理 |
| リソース使用量 | 高い(接続ごとにQPを使用) | 低い(少数の QP で対応可能) |
| スケーラビリティ | 限定的(接続数に比例してリソース消費) | 高い(多くのノードと通信可能) |
| データサイズ | 大きなデータ転送が可能 | L2 MTU に制限される(通常2KB~4KB) |
| 用途 | 高信頼性通信、高性能計算 | スケーラブルな通信、通知 |
| 具体例 | RDMA (Read/Write), 高速データベース通信 | MPI, マルチキャスト |
使い分けとしては、
- 信頼性が必要な場合 → RC
- 高信頼性、高スループットが要求される環境では RC を使用。
- 例: データベース、HPC。
- スケーラビリティが重要な場合 → UD
コードの一部分を抜粋
QP state
Queue Pair の状態を次のように遷移させる。
RESET(create時) → INIT → RTR (Ready To Receive) → RTS (Ready To Send)
RTRを飛ばしてRTSにはできない。また、ReceiverはRTSに遷移せずにHCAリソースを節約することは可能ですが、遷移しておいたとしても動作します。
INIT → RTR
ibv_qp_attr attr = {};
attr.qp_state = IBV_QPS_INIT;
attr.pkey_index = 0;
attr.port_num = 1;
attr.qp_access_flags = IBV_ACCESS_LOCAL_WRITE;
if (is_sender) {
attr.qp_access_flags |= IBV_ACCESS_REMOTE_WRITE;
}
if (is_receiver) {
attr.qp_access_flags |= IBV_ACCESS_REMOTE_READ;
}
int flags = IBV_QP_STATE
| IBV_QP_PKEY_INDEX
| IBV_QP_PORT
| IBV_QP_ACCESS_FLAGS
;
- pkey_index: パーティションキー。InfiniBandファブリック内で通信を分離し、異なるパーティション間のアクセス制御を行うために使用される。
pkey_index = 0と設定することで、デバイスは最初のパーティションキーを使用する。 - qp_access_flags: アクセスフラグ
| フラグ | 説明 |
|---|---|
| IBV_ACCESS_LOCAL_WRITE | ローカルノードがローカルメモリに対して書き込み可能 |
| IBV_ACCESS_REMOTE_READ | リモートノードがリモートメモリに対して読み込み可能 |
| IBV_ACCESS_REMOTE_WRITE | リモートノードがリモートメモリに対して書き込み可能 |
今回は、Senderはリモートメモリへの書き込みだけ、Receiverはリモートメモリへの読み込みだけあれば良い。(両方指定しても良い)
RTR → RTS
接続先のHCAのlidやQPNを設定する必要がある。ahとは、Address Handleのこと。
int flags; ibv_qp_attr attr = {}; attr.dest_qp_num = dest->qpn; // queue pair number of the remote side attr.ah_attr.dlid = dest->lid; // destination LID
QPNは、Queue Pair番号のこと。これはQPを作成したときに自動で割り振られるため、通信相手のQP番号を取得しておく必要がある。socket通信などで交換すれば良い。サンプルコードではMPIを使ってQPNとHCAのLIDを交換しています。
全体の流れ
main関数を抜粋
int main(int argc, char *argv[]) { // MPI init ... // get IB device ... // initialize InfiniBand resources context ctx; init_ctx(device, &ctx); // initialize InfiniBand resources // HCAデバイスを扱うためのハンドラを設定 // post recv if (is_receiver) { post_recv(&ctx); } // connect to remote QP dest rem_dest; exch_dest(&ctx, &rem_dest, rank); // exchange LID and QP number connect_ctx(&ctx, &rem_dest); // connect to remote QP // post send if (is_sender) { snprintf(ctx.buf, BUFFER_SIZE, "Hello, world!"); post_send(&ctx); } // check completion check_completion(&ctx); if (is_receiver) { printf("%s: Receive message: %s\n", role, ctx.buf); } // cleanup ... return 0; }