サブロウ丸

Sabrou-mal サブロウ丸

主にプログラミングと数学

InfiniBand -- RC(Reliable Connection)でのpingpongサンプルコード

コード

test_rc.cpp · GitHub


概要

ここでは二つのプロセス (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
                ;
フラグ 説明
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;
}