InfiniBand の QP ステートの遷移を理解する

作成日:2014.04.12
修正日:2014.05.11

このページでは InfiniBand Verbs プログラムで必須な Queue Pair(QP) のステートと、その遷移について解説する。

以下は関連ページ。


更新履歴
(2014.04.12) 作成。
(2014.05.11) API にリンクを貼る


目次

1. はじめに

TCP の学習を進めてゆくと必ず TCP コネクション・ステート・ダイアグラムに行き着くことになる。 例えば下のようなグラフが TCP コネクション・ステート・ダイアグラムである。 TCP のソケットは内部でグラフ中のようなステート持っており、これを切り変えることで通信を確立する。

Fig 1: A Simplified TCP State Diagram From Wikipedia
QPステート

一方でただ TCP をソケットで使うだけであれば、TCP ステートに関する細かい理解は不要である。 socket()bind()listen()accept()close() のようなシステムコールさえ理解していれば、TCP を使うには十分である。 UDP を使う場合には、ステート・ダイアグラムもシステムコールもさらに簡単になる。

では InfiniBand はどうだろう?

InfiniBand のローレベルな API である InfiniBand Verbs (IB Verbs) を使う場合、TCP コネクション・ステート・ダイアグラムに相当する QP ステート・ダイアグラムを意識しながら接続を行うしかない。 また TCP に相当する RC サービスタイプであっても、UDP に相当する UD サービスタイプであっても、QP ステート・ダイアグラムは同程度の複雑さになっている。 そのためチュートリアルのページで解説しているように、IB Verbs のプログラムは呪術的なコードが連なることになる。

RDMA Communication Manager(CM) は IB Verbs の QP の接続に関する部分をラッピングし、TCP コネクション・ステート・ダイアグラムに対するソケットシステムコールレベルまで InfiniBand を使い易くしてくれる。 実際にはそっちを使った方が楽だ。

2. QP ステートの種類

QP には ResetInitReady To Receive(RTR)Ready To Send(RTS)Send Queue Drain(SQD)Send Queue Error(SQE)Error の 7 つの QP ステートが存在する。

QP ステートの遷移は ibv_modify_qp() で行う。

QP のステートを知るためには IBV_QP_STATE を指定して ibv_query_qp() を呼び出すと qp_state に返ってくる。

2.1 Reset

Reset ステートは ibv_create_qp() で作成された直後の状態で、サービスタイプ(RC/RD/UC/UD など)、QP 番号、CQ との連結、SQ、RQ の最大値などが決まっている他には何も決まっていない状態である。

仕様上、ユーザが ibv_modify_qp() を使うことで任意のステートからこのステートに遷移することができるとある。 ただし一部の実機では Error ステート以外からは ibv_modfiy_qp() で遷移しようとしても失敗することがある。 この理由は後で詳しく述べる。

2.2 Init

Init ステートは Reset ステートの次の状態である。 ibv_modify_qp() でユーザが切り変えることでこのステートに遷移する。

このステートに遷移する際にポートとの紐付けが行われる点が最も重要である。 ここで設定するポートが QP の入力先であり出力先となる。

また Init ステートから Receive Queue(RQ) に対して ibv_post_recv() で Receive Work Request(WR) を登録できるようになる。 この段階ではまだ受信は起きないが、Init ステートの段階から Receive WR を登録することで、次の RTR ステートに遷移した瞬間から漏れなくパケットを受信できるようにするという腹であろう。

2.3 Ready To Receive (RTR)

RTR ステートは Init ステートの次の状態である。 受信しかしない場合は、このステートで留まることも可能である。 ibv_modify_qp() でユーザが切り変えることでこのステートに遷移する。

このステートからパケットの受信が開始される。 ただし送信はまだである。

UD サービス以外のサービスタイプでは、このステートに遷移後最初にパケットを受信したタイミングで Communication Established (IBV_EVENT_COMM_EST) 非同期イベントが発生する。 通常はこの非同期イベントは無視してよいのだが、うまく使えば 4. で説明するようにコミュニケーションの確立に役立たせることができる。 InfiniBand Architecture Specification Volume 1 Release 1.2.1には UD サービスの場合はこの非同期イベントが発生させないことを推奨されており、実機はこれを守っていると考えられる。

2.4 Ready To Send (RTS)

RTS ステートは RTR ステートの次の状態である。 ibv_modify_qp() でユーザが切り変えることでこのステートに遷移する。 これが通常通信を行う定常的なステートである。

このステートからパケットの送信が開始される。 受信も可能である。

2.5 Send Queue Drain (SQD)

SQD ステートは、すでに送信中の Send WR に関しては完了を待つが、まだ送信を開始しない新しい Send WR に関しては保留し、Send WR の処理が止まるタイミングを待つという特殊なステートである。 RTS ステートから ibv_modify_qp() でユーザが切り替えることでこのステートに遷移する。

SQD ステートは送信中の Send WR が全て完了したタイミングを非同期イベントで通知する。 非同期イベントは Send Queue Drained (IBV_EVENT_SQ_DRAINED) と呼び、QP に紐付けられている。 この非同期イベントが到着した後は、SQD ステートは RTS ステートに遷移することが可能である。 RTS ステートに遷移すると、保留されていた新しい Send WR の送信が再開される。

もう一つ非同期イベントが到着した後に、SQD ステートは SQD ステートに遷移できる。 一見無駄のように見えるが、Init ステート時に決めてしまったポートなど重要なものも含めてパラメータを変更可能である。

SQD ステートは受信に関しては RTR ステートや RTS ステートと同じように可能である。

また ibv_post_send() で新しい Send WR を登録することは可能である。 ただし SQD ステートにいる間は、送信が開始されない。

2.6 Send Queeu Error (SQE)

SQD ステートは、Send Queue(SQ) の処理がエラーとなり送信不能となった状態である。 このステートは ibv_modify_qp() ではなく、QP に発生するエラー事象によってのみ遷移する。 遷移元は RTS ステートまたは SQD ステートになる。 受信に関しては RTR ステートや RTS ステートと同じように可能である。

SQE ステートに落ちると、SQ にあった Send WR は全て完了エラーとして CQ に移される。 完了エラーは Flush Error (IBV_WC_WR_FLUSH_ERR) となる。

SQE ステートからは RTS ステートに戻すことが可能である。

一つだけ例外があり、RC QP が SQE ステートになることはない。 RC QP の SQ に生じたエラーは SQE ステートではなく Error ステートに遷移する。

2.7 Error

Error ステートは、Receive Queue(RQ) の処理にエラーが発生した場合、RC QP にエラーが発生した場合、その他の原因でエラーが発生した場合に落ちるステートである。 送信も受信も止まる。 ibv_modify_qp() でユーザが切り変えることでこのステートに遷移することもある。

Error ステートに落ちると、RQ と SQ にあった Send WR は全て完了エラーとして CQ に移される。 完了エラーは Flush Error (IBV_WC_WR_FLUSH_ERR) となる。

いったん Error ステートに落ちた場合、Init ステートに遷移させるか ibv_destroy_qp() で QP を破壊するしかない。

3. QP ステートの遷移

Fig 2 は QP ステートの遷移図である。

Fig 2: QP ステートの遷移図
QPステート

次の Table 1 は各遷移において変更可能な属性を示している。 これは ibv_modify_qp() の第三引数 attr_maskIBV_QP_XXXX マクロの論理和として指定する。 Required Attributes は必ず指定する必要のある属性で、Optional Attributes で指定してもよい属性である。 Required Attributes でも Optional Attributes でもない属性は指定できない。 この表には載っていないが、IBV_QP_STATE は毎回設定する必要がある。

ちなみに Table 1 は InfiniBand Architecture Specification Volume 1 Release 1.2.1 の Table 91 QP State Transition Properties の表に似ているが、仕様と実装が乖離している部分は、実装(Linux カーネル 3.12)側を載せている。

各属性は ibv_modify_qp() の API 解説で行う。

Table 1 QP ステート遷移時に設定する属性
TransitionRequired AttributesOptional AttributesAction
Reset to Init PKEY_INDEX
PORT
QKEY (UD Olnly)
ACCESS_FLAGS (UC & RC Only)
  Receive Queue への投入が可能に
Init to RTR AV (UC & RC Only)
PATH_MTU (UC & RC Only)
DEST_QPN (UC & RC Only)
RQ_PSN (UC & RC Only)
MAX_DEST_RD_ATOMIC (RC Only)
MIN_RNR_TIMER (RC Only)
PKEY_INDEX (UD Only)
QKEY (UD Only)
ALT_PATH (UC & RC Only)
ACCESS_FLAGS (UC & RC Only)
PKEY_INDEX (UC & RC Only)
受信開始
Init to Init   PORT
PKEY_INDEX
QKEY (UD Only)
ACCESS_FLAGS (UC & RC Only)
PKEY_INDEX (UC & RC Only)
 
RTR to RTS SQ_PSN
TIMEOUT (RC Only)
RETRY_CNT (RC Only)
RNR_RETRY (RC Only)
MAX_QP_RD_ATOMIC (RC Only)
CUR_STATE
QKEY (UD Only)
ALT_PATH (UC & RC Only)
ACCESS_FLAGS (UC & RC Only)
PATH_MIG_STATE (UC & RC Only)
MIN_RNR_TIMER (RC Only)
送信開始
RTS to RTS   CUR_STATE
QKEY (UD Only)
ACCESS_FLAGS (UC & RC Only)
ALT_PATH (UC & RC Only)
PATH_MIG_STATE (UC & RC Only)
MIN_RNR_TIMER (RC Only)
 
RTR to SQD   EN_SQD_ASYNC_NOTIFY 新規の送信停止
RTR to SQE     これは ibv_modify_qp() では遷移できない
SQD to RTS   CUR_STATE
QKEY (UD Only)
ACCESS_FLAGS (UC & RC Only)
ALT_PATH (UC & RC Only)
PATH_MIG_STATE (UC & RC Only)
MIN_RNR_TIMER (RC Only)
新規の送信再開
SQD to SQD   PKEY_INDEX
QKEY (UD Only)
AV (UC & RC Only)
ALT_PATH (UC & RC Only)
ACCESS_FLAGS (UC & RC Only)
PATH_MIG_STATE (UC & RC Only)
PORT (RC Only)
TIMEOUT (RC Only)
RETRY_CNT (RC Only)
RNR_RETRY (RC Only)
MAX_QP_RD_ATOMIC (RC Only)
MAX_DEST_RD_ATOMIC (RC Only)
MIN_RNR_TIMER (RC Only)
 
SQE to RTS   CUR_STATE
QKEY (UD Only)
ACCESS_FLAGS (UC Only)
送信再開
Any to Error      
Any to Reset      

IBV_QP_CUR_STATE は注意が必要である。 ibv_modify_qp()IBV_QP_STATE で「遷移先」のステートを指定するが、IBV_QP_CUR_STATE は「遷移元」を指定することができる。 「遷移元」は現在の QP のステートなのだから指定する必要はないように思えるのだが、IBV_QP_CUR_STATE を指定することで現在の QP ステート以外から遷移したように装うことができる、と思われる。 ibv_modify_qp()IBV_QP_CUR_STATE を指定するには、Table 1. で Optional Attributes 指定されている以外に、HCA がこの機能をサポートしている必要がある。 機能がサポートされている場合、ibv_query_port() の中の port_cap_flagsIBV_DEVICE_CURR_QP_STATE_MOD の属性がセットされている。

Table 1 に記載していないが、HCA によっては ibv_create_qp() で指定したケーパビリティ(max_send_wrmax_recv_wrmax_send_sgemax_recv_sge)を変更できる機能をサポートしているものがある。 機能のサポートは、ibv_query_port() の中の port_cap_flagsIBV_DEVICE_RESIZE_MAX_WR の属性がセットされている。 ケーパビリティを変更する場合、ibv_modify_qp()IBV_QP_CAP を指定して変更する。

Mellanox ConnectX-3 は IBV_DEVICE_CURR_QP_STATE_MOD 機能も IBV_DEVICE_RESIZE_MAX_WR 機能もどちらもサポートされていない。

4. コミュニケーションを確立するには?

InfiniBand で QP と QP 間の通信路をコミュニケーション(Communication)と呼ぶが、チュートリアルの最後で触れたようにコミュニケーション確立はかなり面倒である。 特に RC サービスは QP の RTR ステートで通信相手の情報を設定する必要があるので厄介である。

Fig 3 は RC サービスタイプの QP のコミュニケーションを確立するためのフローの一例である。 他にも様々なやり方が考えられる。

  1. サーバーは IPoIB などでソケットを用意する。
  2. 通信路を開きたいクライアントは QP を作成した後でサーバにリクエストを投げることになる。当然リクエストには LID、QP 番号、PSN などを含める。
  3. サーバーはリクエストを受信して自身も QP を作成し、リクエスト内の情報に従って RTR ステートまで遷移させる。これでサーバは受信可能になる。
  4. サーバーはクライアントにリプライを返す。当然リプライには LID、QP 番号、PSN などを含める。
  5. クライアントはリプライを受信して、リプライ内の情報に従って RTS ステートまで 遷移させる。これでクライアントは送信可能になる。

ここまでできたところでクライアントはサーバに InfiniBand としてのオペレーションを送信する。 これは本番のメッセージでも、ダミーのメッセージでも構わない。

サーバの QP はメッセージを受信した時に、(RTR ステートの QP が最初にパケットを受信したので) Communication Established 非同期イベントを発生させる。 非同期エラー&イベントを監視しているルーチンは ibv_get_async_event() で非同期イベントを拾い上げ、Communication Established 非同期イベントだと判定できれば、それに紐付けられている QP を RTR ステートから RTS ステートへ遷移させればよい。 これでサーバの QP も送信可能となり、コミュニケーションが双方向通信可能になる。

Fig 3: RC サービスのコミュニケーションの確立フロー
RC サービスのコミュニケーションの確立フロー

この Communication Established 非同期イベントを使う方法のメリットだが、もし Communication Established 非同期イベントがなければ completion channel を使わない CQ に対してはポーリングで監視するしかない。 しかし Communication Established 非同期イベントがあることで、非同期エラーの割り込み型の監視を利用できる。

またコミュニケーションの確立の最後をソケットではなく InfiniBand の通信で終わらせるのは、QP と InfiniBand ネットワークが正常に動作していることを確認することができるというメリットがある。 Ready To Use まで IPoIB のソケットで行ってしまうと、IPoIB は通るが RC QP では通信できないといった発生する危険性がある。

5. いったん Error ステートに落ちた QP をどうすればよいか?

いったん QP が Error ステートに落ちた通信路を再度通信可能にするにはどうすればよいだろうか? 二つの方法がある。

第一の方法は ibv_destroy_qp() で QP を回収して、再度 ibv_create_qp() で作り直すという方法である。 圧倒的にこちらをお勧めする。

やむをえず第二の方法として QP を再利用することもできる。 Fig 2 を見ると Error ステートから Reset ステートを介してステートマシンを回すのだ。 だが実は Reset ステートへの遷移はかなりやっかいである。

5.1 Error ステートへの遷移で何が起きるか?

QP が Error ステートに落ちる時、送信と受信が止まり ibv_post_send() で Send Queue(SQ) に登録した Send WR と、ibv_post_recv() で Receive Queue(RQ) に登録した Receive WR が Completion Queue(CQ)に吐き出されてくる。 この動作はエラー事象の発生により QP が Error ステートに落ちる、あるいは ibv_modify_qp() を使って自発的に Error ステートに落ちた場合も同様である。

この動作は重要である。 なぜなら InfiniBand は ibv_post_send()ibv_post_recv() で登録した WR を取り外すシステムコールが存在しない。 しかし チュートリアルの方で説明したように、Send WR や Receive WR には wr_id があり、プログラムはこれを使ってバッファ管理を行ったりする。 SQ や RQ に入っている未処理の WR を CQ に吐き出すために、Error ステートへの自発的に遷移させることはよく行われる。

QP が SRQ を使っている場合は少し動作が異なる。 SRQ は独立したオブジェクトなので、QP が Error ステートになったからといって ibv_post_srq_recv() で登録した Receive WR が CQ に吐き出されるということはない。 しかし QP が処理していた最後の受信パケットがあった場合、これを CQ に格納するまでに少し時間がかかるかもしれない。 QP を Error ステートにしてから、全ての WQE が CQ に吐き出されるまで一呼吸かかるのだ。 HCA はこのタイミングをプログラムに知らせるために Last WQE Reached 非同期イベント(IBV_EVENT_QP_LAST_WQE_REACHED) を投げる。

つまり以下のようになる。

5.2 Reset ステートへの遷移で何が起きるか?

Reset ステートは ibv_create_cq() で作成された直後の初期状態である。 Reset ステートへの遷移は、この初期状態に戻ることを意味する。

そのため Reset ステートへの遷移時には SQ と RQ に積まれていた Work Request は (CQ に吐き出されることなく) 消失する。 Work Request の wr_id を使った資源管理を行っているなら、これは非常にまずいことになる。 任意のステートから Reset ステートの遷移が可能にも関わらず Fig 2 にそのパスを書かなかったのは、Error ステートを経由せずに、いきなり Reset ステートへ遷移させるのはプログラム的にありえないからである。

Fig 4: Reset ステートへの遷移時の CQ の状態
Reset ステートへの遷移時の CQ の状態

次に Reset ステートへの遷移時には、ibv_create_qp() 時に send_cqrecv_cq で指定した CQ の中からこの QP に所属する CQE だけを削除する。 これは凄いことで、CQ は複数の QP に関連付けることが可能なので、CQ の中には複数の QP に所属する CQE がマダラに入っているのだが、本来 FIFO のはずの CQ の途中から当該の CQE だけを抜いてしまうのである。

何にせよ CQE の削除はプログラムから見えないことろで勝手にやられるので、4.1 で QP を Error ステートに変えた段階で、Work Completion は全部回収しておく必要がある。

複数の QP から CQ が共有されている場合、特定の QP の CQE を全部取り出したと判断するのが困難な場合がある。 特に SRQ を使っている場合、受信したメッセージの数が分からないので、CQ に入っている CQE 数も不明なためである。

これを克服するためには、CQ からの取り出し方を工夫する必要がある。 InfiniBand Architecture Specification Volume 1 Release 1.2.1 の 10.3.1 QUEUE PAIR AND EE CONTEXT STATES には 2 種類の方法が提示されている。

  1. CQ が空になったと報告する(戻り値が 0 を返す)まで、ibv_poll_cq() を呼び出し続ける。
  2. マーカーとなる別の Work Request の結果を CQ に突っ込み、それが取り出されるまで ibv_poll_cq() を呼び出し続ける。

1. は、CQ を共有する他の QP に途切れなく通信が続いて、CQ が空になる瞬間がこない恐れがある。 そのため実用的には 2. を使うべきであろう。

これ以外に CQ の最大 CQE 数を一度に ibv_poll_cq() するという方法があるが、IB デバイスの実装によっては ibv_create_cq() の第二引数 cqe に指定した値よりも実際の CQ の最大 CQE 数が大きくとられることがあるため、確証が取れる方法ではない。

コメント

コメントを書き込む

TOP    掲示板    戻る
Written by NAKAMURA Minoru, Email: nminoru atmark nminoru dot jp, Twitter:@nminoru_jp