随着大规模AI训练与推理的兴起,RDMA(远程直接内存访问)在多路径/自适应路由(AR)等场景中的应用日益广泛,这使得其对乱序(Out-of-Order, OOO)数据传输的支持成为一个关键技术热点。支持OOO通常与支持直接数据放置(DDP)相关联,但实现全面的OOO支持远不止于此。本文将以IRN(一个对RoCE v2的增强方案)为例,系统梳理在改造所有RDMA操作以支持乱序时,需要关注和解决的几个核心问题。
第一个数据包问题
这个问题已有多方讨论。其核心在于,对于某些操作(如RoCE v2的RETH头),DDP的关键信息仅存在于第一个数据包中。为了在支持DDP的同时避免网卡缓存报文,解决方案是让每个数据包都携带RETH头。此处不再赘述。
WQE匹配问题
在RDMA中,部分操作要求每个到达的数据包都必须与接收端对应的WQE(工作队列元素)进行匹配。例如:
- SEND操作:每个数据包必须被放置到对应Recv WQE缓冲区的正确偏移位置。
- WRITE-with-Immediate操作:需要将Immediate数据放入对应的WQE。
- READ Request与Atomic Request:请求包本身可能被分片,网卡需要将这些分片“重组”到一个请求WQE上下文中。
在数据包顺序到达的传统模式下,这种匹配是隐式完成的。然而,当数据包无序到达时,隐式匹配机制将失效。解决方案是为WQE分配显式的序列号,并携带在数据包头中,用于标识每个数据包所属的WQE。具体可分为两类场景:
这类操作要求接收端的Recv WQE按照其提交顺序被消耗。因此,可以为每个Recv WQE以及对应的请求WQE维护一个recv_WQE_SN(接收WQE序列号),用于指示它们的发布顺序。该值被携带在所有SEND数据包以及最后一个Write-with-Immediate数据包中,用于识别正确的目标Recv WQE。IRN方案还要求数据包携带其在WQE数据区域内的相对偏移量(offset),以实现数据的精准放置。
实施流程:
- 发送端为每个Recv WQE分配一个单调递增的
recv_WQE_SN。
- 发送端构造数据包时,在包头中携带目标
recv_WQE_SN和载荷偏移量offset。
- 接收端网卡(NIC)解析到
recv_WQE_SN后,在Recv队列中查找对应的WQE,并根据offset将数据直接放置到缓冲区的正确位置。
- 当该WQE的所有数据字节都到达后,网卡生成一个完成队列元素(CQE)。
关键点:recv_WQE_SN由接收端生成,但发送端如何知晓?这依赖于RDMA协议在保序条件下保证的语义一致性。发送端和接收端对“第N个消息对应第N个Recv WQE”有共识,发送端可用自身的发送序号推导出远端的recv_WQE_SN。
2. Read 与 Atomic
这类操作采用请求/响应模式,其接收端不涉及Recv WQE。核心挑战在于语义保序:一个RDMA READ或ATOMIC请求,必须等到所有在它之前发布的请求都处理完毕后才能执行。乱序到达的请求包可以提前被缓存,但不能乱序执行。
例如,两个READ请求R1和R2被分片。如果R2的第二个分片(P2b)先于R1的分片到达,网卡必须能够:1)识别P2b属于请求R2;2)在R1及其自身所有分片都到达前,不执行R2。
解决方案:IRN为每个本地发起的Read/Atomic请求WQE维护一个read_WQE_SN。所有请求包都携带此序列号。接收端网卡维护内部的Read WQE上下文缓冲区(slot)。乱序到达的包按其read_WQE_SN被放入正确的slot中缓存,仅当轮到该序列号(即前面的请求均已处理)时,网卡才将其提交给执行流水线,从而在包乱序到达的情况下保证请求的顺序执行。
最后一个数据包问题
对于许多RDMA操作(如Write-with-Immediate),关键信息(如Immediate数据、完成元数据)仅包含在最后一个数据包中。启用OOO后,需要跟踪并妥善处理提前到达的“最后一个包”。
IRN方案概述:
- 消息序列号(MSN):响应方维护一个MSN,当接收到写/发送消息的最后一个包,或接收到读/原子请求时,MSN递增。MSN通过ACK包返回给请求方,用于使相应的请求WQE失效。
- 包状态跟踪:响应方为每个包偏移维护一个2-bit的状态映射(bit0: 是否已到达
has_arrived;bit1: 是否为最后一个包 is_last_pkt)。
- 延迟完成处理:如果最后一个包提前到达,网卡会解析其中的
recv_WQE_SN和完成元数据,将其存入一个临时的pending_cqe_meta区域(可在主机内存),但不会立即触发CQE生成。仅当该消息的所有前置数据包都到达后,才执行MSN递增、WQE失效并生成CQE。
对Read/Atomic的作用:同样,如果READ/Atomic请求的最后一个包先到,也必须将其缓存,直到该请求之前的所有请求都完成,才能触发执行和后续的MSN更新。
旧消息的重传覆盖问题
考虑一个场景:消息A和消息B操作的内存区域存在重叠。由于网络问题,消息A的某个包延迟到达或触发重传。此时接收端可能已在处理消息B。如果启用OOO放置,这个迟到的旧包仍会被直接写入旧地址,从而可能覆盖消息B已经写入的新数据。
解决此问题主要有两种策略:
1. 应用层 Fence
- 工作请求级Fence:在提交WR时设置
IBV_SEND_FENCE标志。这会强制发送端在执行该带Fence的WR前,等待所有先前发出的有序操作完成,防止新旧请求的包在接收端产生干扰。
- 应用协议级Fence:在应用层设计协议,例如,只有在收到前一个操作的完成确认(ACK)后,才重用或释放对应的缓冲区,从根本上杜绝旧包写入已复用内存区域的可能。这涉及到对网络/系统底层操作语义的精确控制。
2. Fence Indicator(网卡/驱动层实现)
这是一种更底层的方案。网卡或驱动为每个接收缓冲区维护一个“围栏”指示器。当应用提交一个新的WQE,且其缓冲区地址与某个尚未完全完成的旧WQE缓冲区重叠时,驱动会标记新WQE的Fence Indicator。网卡在收到数据包时,会检查该标志,如果包属于旧的重叠请求,则将其丢弃或特殊处理,防止数据覆盖。
其他考量
某些应用(如早期的FaRM)严重依赖轮询特定内存位置来检测操作完成,这与OOO数据放置异步完成的特性不兼容。这类情况通常需要应用迁移到官方支持的完成机制(如Write-with-Immediate)。对于这类强依赖保序的场景,应继续使用强顺序(strongly ordered)模式。
值得一提的是,类似的能力在iWARP协议中已有原生支持。iWARP类似Falcon方案,具备MSN机制,可基于此进行WQE索引,天然更好地处理了部分乱序问题。