Packet spraying and the reordering it hands to the NIC
S2·E6The sixth word on the label · Building D, Monday morning, the second pod's first full run
Builds on: Zero Touch RoCE, ZTR-RTT CC and DOCA PCC
Before you read: what do you already know?
3 quick questions. Wrong answers are fine and expected; trying first makes the lesson stick.
After this lesson you can
- Derive the UDP source port of a connected RoCEv2 QP from its two QPNs and explain why every packet of that QP takes one path.
- Quote the Cumulus adaptive routing eligibility list and syntax for the release a customer runs, in the documentation's own words.
- Evaluate whether out-of-order data placement can be promised on a RoCE fabric, given what NVIDIA documents and what it does not.
- Judge whether a rise in out_of_sequence and packet_seq_err is loss, reordering or a broken lossless design, and name the counter that decides it.
- Separate shipping, documented multipath mechanisms from MRC, which no vendor publishes a way to enable.
Episode 6 — The sixth word on the label
The label on the rack door reads MARKED, TRUSTED, PAUSED, SIGNALLED, MEASURED — one word per week of this bring-up — and the Dell SE is on his second cold coffee, row 41 of the spreadsheet rewritten to say “lossy: on purpose”.
Then the network lead opens the grid-paper notebook and reads out what the switch team did over the weekend, to help: adaptive routing, on the new leaves. Her counter is out_of_sequence, climbing on hosts that are dropping nothing. Show me the counter, she says, and then tell me who is losing packets.
Nobody is. This module rested on an assumption nobody said out loud: all sixteen packets of one RDMA Write walk the same path. On a RoCEv2 queue pair that is not a law: the driver derives one number at INIT to RTR, writes it into the address path, and every packet of that QP carries it.[2] The ECMP hash reads that field and keeps the flow on one path.[1] Adaptive routing deletes the assumption: on 5.14 the switch “forwards packets to the less loaded path on a per packet basis”, and by 5.18 the sentence reads “adaptive routing eligible packets”.[4][5]
That is why the feature exists. A hash spreads flows, not load: two elephant flows collide on one uplink while a parallel one idles. Choosing per packet by queue occupancy fixes the balance and hands the ordering problem to the endpoint.[4][12] A fabric that balances per packet sends the reordering bill to the NIC. Start with the number that pinned the flow in the first place.
1One QP, one path — where the entropy comes from
RoCEv2 is UDP encapsulation with a fixed destination port 4791 and a source port that NVIDIA describes as a field that “Can serve as an opaque flow ID to assist with ECMP (Equal-Cost Multi-Path) routing”.[1] Read the verb: the field is available for ECMP; the page does not say the NIC varies it or how a value is derived.[1] NVIDIA’s own mlx5 man page is just as permissive about who consumes it: the source port “is used to create entropy for network routers (ECMP), load balancers and 802.3ad link aggregation switching that are not aware of RoCE IB headers”.[18]
The derivation lives in two kernel inlines. rdma_calc_flow_label(lqpn, rqpn) multiplies the two 24-bit QP numbers and folds the product to 20 bits; rdma_flow_label_to_udp_sport() XOR-folds that label to 14 bits and ORs in IB_ROCE_UDP_ENCAP_VALID_PORT_MIN, 0xC000.[17] mlx5 calls them in that order: mlx5_set_path_udp_sport() takes ah->grh.flow_label and only if that is zero falls back to the QPN hash, writing the result into the address path’s udp_sport.[2] It is gated, not universal: mlx5_set_path() calls the helper only for a RoCE address handle on an RC, UC or XRC QP with a RoCEv2 source GID, and only when the modify carries IB_QP_DEST_QPN — which is the INIT to RTR transition.[2] The computed value is used for udp_sport only; the path’s own flow_label is set separately from the unmodified grh->flow_label.[2]
Two consequences matter in the field. The QPN hash is symmetric because it is built on a product: the helper’s comment says it exists so that “both the requester and responder” can “calculate the same flow_label for a given connection”.[17] RDMA-CM usually supplies a non-zero flow label first, derived from the CM port numbers as hash = (u32)sport * 31 + dport, which is not commutative.[20] And the range convention is a should, not a rule — the same comment says “All RoCE V2 drivers should use this same convention”, yet bnxt_re writes htons(0x8CD1) into its QP1 header, below 0xC000, so a capture filter on 49152 to 65535 does not catch every RoCEv2 packet.[17][21]
One QP therefore carries one source port at a time. mlx5dv_modify_qp_udp_sport() “enables modifying the configured UDP source port of a given RC/UC QP when QP is in RTS state”.[18] The provider gates it on QP type, on RTS state and on the rts2rts_qp_udp_sport capability bit, and issues it as DEVX into qpc.primary_address_path.udp_sport — one source port, so it moves a QP between paths rather than spreading one QP across paths.[3] Portable verbs have no equivalent: ibv_modify_qp() exposes only ah_attr, fixed at RTR, and alt_ah_attr for automatic path migration, gated on IBV_DEVICE_AUTO_PATH_MIG.[19] That leaves a portable application one multipath tool — create more QPs.
2Turning the switch into a sprayer — and the phrase the docs actually use
Cumulus Linux defines the feature in one sentence, and the sentence moves with the release. On 5.14: “Adaptive routing is a load balancing feature that improves network utilization for eligible IP packets by selecting forwarding paths dynamically based on the state of the switch, such as queue occupancy and port utilization.”[4] On 5.18 the same sentence reads “for adaptive routing eligible IP packets”.[5] Note “such as” — those are examples of switch state, not an enumerated mechanism, and the page publishes no register, counter or config key behind them.[4] The behaviour sentence splits the same way, and it is the one to memorise: 5.14 says “With adaptive routing, the switch forwards packets to the less loaded path on a per packet basis to best utilize the fabric resources and avoid congestion”, while 5.18 says “forwards adaptive routing eligible packets” in the same position.[4][5] Either way the basis is per packet, and that is adaptive routing’s behaviour, not the switch’s default; ordinary ECMP on the same page is per-flow hashing.[4]
Eligibility is two lists, and which list a bullet sits in is the whole argument. Under “Cumulus Linux supports adaptive routing with”, 5.14 names Spectrum-4 at 400G and 200G speeds, RoCE2 unicast traffic, VXLAN-encapsulated RoCE traffic, layer 3 interfaces, next hop router interfaces in the default VRF, and the NVIDIA Spectrum-X networking platform.[4] The restriction bullets that follow run unchanged into 5.18: adaptive routing “does not make use of resilient hashing”, is not supported “on layer 3 subinterfaces, SVIs, bonds or bond members”, and “The Spectrum-4 switch does not support adaptive routing on 800G links”.[5] From 5.16 the Spectrum-X platform appears a second time, now among the restrictions — “Adaptive routing is only supported on the NVIDIA Spectrum-X networking platform” — and the support list’s traffic bullet narrows to “Adaptive routing eligible RoCE2 unicast traffic”, so quote the release the customer runs.[5]
The syntax changed after 5.14, per command. The NVUE reference states that “In Cumulus Linux 5.14 and earlier, you specify enable on or enable off instead of state enabled or state disabled”, attached to nv set interface <interface-id> router adaptive-routing state and nv set router adaptive-routing state; both toggles were introduced in 5.1.0 and link-utilization-threshold in 5.6.0.[6] One documented expectation carries weight in a design review: enable adaptive routing “on all the ports that are part of the same ECMP route”, because mixing adaptive routing traffic with regular ECMP routes egressing ports without it means “Unexpected route hashing might occur” — the page’s own words, in a Note rather than a Warning, with no drop, reordering or performance consequence stated.[4]
3What spraying hands the receiver, and what strict RC does with it
Take the same sixteen-packet RDMA Write and make the switch choose per packet. Paths differ in queueing delay, so packets of one QP arrive interleaved. A strict reliable connection has one response to a gap in the packet sequence number: treat it as loss. The responder stops accepting from the gap onward and the requester retransmits, which is why out_of_sequence and packet_seq_err can climb on a fabric where nothing has actually been dropped — those two names come from the mlx5 driver, and the chain that connects them to per-packet forwarding is assembled in segment 6, not quoted from any page.[13] The fabric spent its cleverness balancing the load, and the transport threw the result away.
The Ultra Ethernet specification is the clearest public statement of that trade because it writes both halves as conformance language. Its reliable ordered mode, ROD, is the RC-shaped one and pays the RC-shaped price: packets “MUST be transmitted in order over the network interface using a single network path (i.e., using a single entropy value)”, normatively “UET ROD mode MUST operate on a single network path”, and out-of-order new packet sequence numbers are dropped and NACKed under GoBackN recovery.[11] Its reliable unordered mode, RUD, is the shape a multipath transport needs: it “uses selective retransmission capabilities and enables semantic processing and direct data placement out of order”, and “Packets are delivered to the semantic sublayer in the order they arrive from the network”.[11] The specification stops there — it never says that anything above the transport restores order.[11] The framing usually quoted for that shape is AWS’s, about SRD: reliable but out-of-order delivery that “leaves order restoration to the layers above it”.[23]
UEC also publishes the caution that saves an FAE from over-promising: spraying alone does not balance a fabric. “Hashing is not guaranteed to spread the packets evenly. The presence of other traffic that is not sprayed may impact available bandwidth on some paths. In addition, failures may render a path unavailable.”[11] Its conclusion is that “Improved throughput and reduced flow completion times can be achieved if the sources actively balance traffic” — which is the argument for endpoints that know something about paths, not for a switch acting alone.[11]
The switch selects “based on the state of the switch”, so it ranks paths by its own egress queue — not by the delay you set, half of which is downstream of it in this model. Watch it avoid p2 and still deliver out of order. ⚠ NVIDIA publishes no figure for reorder depth under AR.
1. Post the Write
One QP, one RDMA Write, 16 packets, consecutive PSNs. Nothing has been decided yet except that this is a single message on a reliable connection.
Out-of-order arrival on a strict RC QP: the responder drops from the gap onward and the requester retransmits. The fabric spread the load; the transport undid it.
One QP posts a 16-packet RDMA Write. The path this flow takes is chosen once, by a number computed at INIT→RTR and written into the address path as udp_sport.
Everything downstream — how many links carry this write, whether packets can arrive out of order at all — follows from that one 14-bit value. Change a QPN and the whole flow moves.
FAE angle: when a customer says "we enabled ECMP and one link is hot", the question is not about the switch. Ask how many QPs the application opens. One QP is one source port is one path, whatever the fabric is willing to do.
- RoCEv2 uses "a UDP header — Used for stateless encapsulation; destination port 4791" and a "Source Port Field – Can serve as an opaque flow ID to assist with ECMP (Equal-Cost Multi-Path) routing." The page says the field is available for ECMP; it never says how a value is derived or what controls it. DOCA — RDMA over Converged Ethernet (vendor documentation)
- `mlx5_set_path_udp_sport()` takes `ah->grh.flow_label`, and only if that is zero falls back to `rdma_calc_flow_label(lqpn, rqpn)` — a symmetric hash of the local and destination QPNs, so both ends compute the same label — then `rdma_flow_label_to_udp_sport()` XOR-folds the 20-bit label to 14 bits and ORs in 0xC000. linux v6.12 — hw/mlx5/qp.c (source code)
- ∴ It is gated, not universal: `mlx5_set_path()` calls that helper only for a RoCE AH on an RC/UC/XRC QP with a RoCEv2 GID type, when the modify carries `IB_QP_DEST_QPN` — the INIT→RTR transition, which is where the destination QPN becomes known. linux v6.12 — hw/mlx5/qp.c (source code)
- `IB_ROCE_UDP_ENCAP_VALID_PORT_MIN (0xC000)`, `IB_GRH_FLOWLABEL_MASK (0x000FFFFF)`, and the comment "All RoCE V2 drivers should use this same convention." A should, not enforcement: bnxt_re hardcodes 0x8CD1 for its QP1 path, below 0xC000. A capture filter on 49152–65535 does not catch every RoCEv2 packet. linux v6.12 — hw/mlx5/qp.c (source code)
- ∴ RDMA-CM usually supplies a non-zero flow label first — `cma_resolve_iboe_route()` derives one from the CM port numbers (`hash = (u32)sport * 31 + dport`) — so the QPN-product branch is often never taken. That hash is not commutative, unlike the QPN product. linux v6.12 — hw/mlx5/qp.c (source code)
- ∴ `mlx5dv_modify_qp_udp_sport()` "enables modifying the configured UDP source port of a given RC/UC QP when QP is in RTS state" — gated on QP type, on RTS, and on the `rts2rts_qp_udp_sport` capability bit, issued as DEVX. It writes one `primary_address_path.udp_sport`: it moves a QP between paths, it does not spread one QP across paths. rdma-core v65.0 — providers/mlx5/mlx5.c (source code)
- ∴ There is no standard verbs call to change a live QP’s path entropy: `ibv_modify_qp(3)` exposes only `ah_attr` (fixed at RTR) and `alt_ah_attr` (APM, unsupported on RoCE). A portable application has exactly one portable multipath tool — create more QPs. rdma-core — providers/mlx5/mlx5dv.h (source code)
- NVIDIA’s own framing of the per-flow failure mode: ECMP "doesn’t account for real-time congestion, so large flows may collide on one link while others are underused." NVIDIA — Giga-Scale AI and the Ethernet Evolution (vendor blog)
- `NCCL_IB_QPS_PER_CONNECTION` (since 2.10): "a number between 1 and 128, default is 1", and the stated rationale is routing entropy — it "can be useful on multi-level fabrics which need multiple queue pairs to have good routing entropy". "Can be useful", never required; no recommended value and no performance number is published. NCCL 2.31.2 — Environment Variables (vendor documentation)
packet_seq_errmovingretrans_q_cnts[] · gate MLX5_CAP_GEN(retransmission_q_counters) — PSN gaps seen by the requester. On a strict RC QP this is the counter a network lead asks for by name.out_of_sequencemovingout_of_seq_q_cnts[] · gate MLX5_CAP_GEN(out_of_seq_cnt) — Its own array, its own independent capability bit. A box can expose this one and not packet_seq_err.implied_nak_seq_errflatretrans_q_cnts[] · gate MLX5_CAP_GEN(retransmission_q_counters) — Same array as packet_seq_err. Kernel source gives the name; no NVIDIA page defines the semantics, so do not narrate it.local_ack_timeout_errflatretrans_q_cnts[] · gate MLX5_CAP_GEN(retransmission_q_counters) — Note the _err suffix — the one name in this array people mistype. In switchdev on a non-host PF it reads vport_local_ack_timeout_err.duplicate_requestflatretrans_q_cnts[] · gate MLX5_CAP_GEN(retransmission_q_counters) — Retransmitted requests the responder had already executed.req_transport_retries_exceededflatextended_err_cnts[] · gate registered with the extended set — The six retransmission names are not the whole picture; this array carries two more.roce_adp_retransnot publishedroce_accl_cnts[] · gate MLX5_CAP_GEN(roce_accl) — Adaptive Retransmission family. NVIDIA publishes no page defining what it counts — seven candidate URLs all 404. Name from source, semantics unknown.
cd /sys/class/infiniband/mlx5_0/ports/1/hw_counters/ && grep . packet_seq_err out_of_sequence rdma statistic show link mlx5_0/1 # nothing there? check for the vport_ prefix before you blame the driver: ls /sys/class/infiniband/mlx5_0/ports/1/hw_counters/ | grep -E "seq|vport_"
Best-effort on this priority: a deep path can drop, and the reliable transport is what recovers. No pause frame walks upstream, so a neighbour flow on the same priority keeps running.
The designers of MRC: MRC "disables PFC and uses Ethernet in best-effort (lossy) mode", and its target is not plain lossy Ethernet but "best-effort (i.e. lossy) networks that support packet trimming", absorbing the loss with SACK-based fast selective retransmission and out-of-order placement. That inverts the deployment model of a classic lossless RoCEv2 fabric. preprint
Ultra Ethernet, SHOULD NOT: "PFC SHOULD NOT be used anywhere in a best-effort network", scoped to UET under UET-CC, with the stated cause being UET-CC’s latency assumptions — not spraying and not reordering. SHOULD NOT, not MUST NOT: UEC is not anti-PFC in general and keeps a lossless UET mapping. UE Specification v1.0.3
AWS, 2020, at AWS scale: Dated, and scoped to AWS scale: PFC "is not feasible on large-scale networks, because it creates head-of-the-line blocking, congestion spreading, and occasional deadlocks." IEEE Micro 2020
⊘ NVIDIA: NVIDIA names the same failure mode once, hedged and generic: "RoCEv2 deployments often use Priority Flow Control (PFC) to reduce loss, but pause frames can propagate congestion, create head-of-line blocking, and potentially stall the fabric." It does not say Spectrum-X removes or disables PFC, and it never connects PFC to sprayed paths. NVIDIA blog
On the ordering of the loops: "the switch only generates Explicit Congestion Notification (ECN) marks when its adaptive routing capacity is completely exhausted and the queue continues to grow." Both conditions, joined by "and" — a mark means the queue got deep AND AR could not relieve it. It does not mean a mark stopped indicating queue depth.
The switch picks the egress port per packet from its own state — "such as queue occupancy and port utilization" — for packets that are adaptive-routing eligible. The eligibility list is short and the restrictions are specific; fail one and the same traffic quietly goes back to per-flow hashing.
nv set router adaptive-routing state enabled # 5.15+ syntax nv set interface swp1 router adaptive-routing state enabled nv config apply nv show router adaptive-routing # configuration, not counters
⚠ Model only. Packet times, per-path delays and the ECMP hash are a teaching model: the real ECMP hash function is vendor-specific and unpublished, and NVIDIA publishes no figure for adaptive-routing reorder depth. The shape is right; the numbers are not a simulation of your fabric.
Naming, once: the term is MRC — Multipath Reliable Connection. "MPRC" is used by no vendor, no specification and no repository. And inside NVIDIA’s own corpus "MRC" already means Multi-Rack Connectivity, a DOCA service, so say the words before you say the acronym.
Scope: nothing in this lab is MRC. MRC is specification-stage — one NVIDIA blog post, an OCP specification that returns HTTP 403 to every automated fetch, and IBTA Vol. 1 Release 2.1 Annex 21, members-only. No documentation page, no mlxconfig parameter, no verbs symbol, no environment variable, no counter, no firmware note, no perftest transport. Everything here ships today.
Key: ⊘ a negative finding (a probe that came back empty) · ∴ assembled from separate sources, never written as one sentence by the vendor · ⚠ not confirmed on a fetched page.
4The flag that exists, its three gates, and the chapter it is filed under
NVIDIA does document a receiver-side answer. The page is called “Out-of-order Data Placement” and its scope is stated verbatim: “This feature is only supported on: NVIDIA ConnectX-5 adapters and above; RC and XRC QPs; DC transport.”[8] The mechanism is described plainly — packets of the same QP “may traverse different network paths from source to destination. This can lead to packets arriving out of order”, and “Instead of dropping these packets and triggering retransmissions, this feature enables them to be accepted and placed directly into host memory, even if they arrive out of order.”[8] Do not call it reordering: the page’s own closing sentence is that data “is placed in host memory in the order it arrives”, and the words offset, segment, scatter, reorder, buffer and sort do not appear on it.[8] Its two benefits, “Enhancing network utilization” and “Reducing latency”, carry no percentage and no benchmark.[8]
Now the part you do not get to soften. That page sits under DOCA Programming Guide, DOCA Drivers, MLX Drivers and Technologies, InfiniBand Network, and the strings “RoCE”, “ECMP” and “Spectrum-X” appear nowhere on either version of it.[8] The sentence to say is “NVIDIA documents out-of-order data placement only for InfiniBand.” It is not “it does not apply to RoCE” — the page neither affirms nor denies that, and NVIDIA publishes no RoCE or Ethernet counterpart page in DOCA 3.5.0.[8]
The verbs surface, by contrast, is fully documented and it is opt-in. MLX5DV_QP_CREATE_OOO_DP is an mlx5 direct-verbs creation flag set in struct mlx5dv_qp_init_attr.create_flags and passed to mlx5dv_create_qp(); ibv_create_qp_ex() cannot set it.[9] Three gates ride with it — query first, both ends, no fallback — and the callout below quotes them.[9] What the flag relaxes is receive-WR consumption and arrival ordering; what it does not relax is completion ordering on either side, and RDMA Read and Atomic operations still execute in order on the responder, though RDMA Read response packets may scatter out of order on the requestor for a QP created with the flag.[9]
Diagram source (Mermaid)
flowchart LR A["Cumulus ECMP page: eligible packets go to the less loaded path on a per packet basis"] --> G["No NVIDIA page writes this chain as one sentence"] G --> B["DOCA page: packets of one QP may traverse different paths and arrive out of order - accept and place them instead of dropping"] A -.-> N1["5.14 and 5.15 never mention the NIC at all - 5.16 adds a NIC-side packet load balancer"] B -.-> N2["The words RoCE, ECMP and Spectrum-X appear nowhere on this page"]
5Why spraying and PFC argue — and what that does to last week's recommendation
The designers of MRC state the collision directly: “Spraying is hard to combine with the priority flow control (PFC) mechanism used in lossless Ethernet because a single flow reaches the last-hop switch over hundreds of paths. Further, PFC tends to create head-of-line blocking between different collectives, hurting tail latency. Thus MRC disables PFC and uses Ethernet in best-effort (lossy) mode.”[10] Read the order of that argument: disabling PFC is a consequence of spraying, not the starting point. The same paper pairs it with “fast selective retransmission, using Selective ACK (SACK) packets” and out-of-order memory placement to absorb the resulting loss, and names its target as “best-effort (i.e. lossy) networks that support packet trimming” — where trimming is optional, since the paper says MRC “can use packet trimming”.[10]
One scoping fact has to travel with that quote or the whole segment misleads. MRC “was designed to work with either hash-based ECMP forwarding or SRv6”, but the authors rejected static ECMP because “there still is not a simple mapping from EV to path”, deployed IPv6 segment routing instead, and “simply disabled” switch dynamic routing.[10] The paper’s title is “…using MRC and SRv6”: the fabric it describes is not the fabric in this episode, and nothing in it endorses spraying over ECMP.[10]
UEC says something adjacent and narrower, and the scoping matters more than the sentence: “PFC SHOULD NOT be used anywhere in a best-effort network.”[11] Three limits travel with it: it governs UET under UET-CC only, “in a best-effort network” is the condition, and the stated cause is UET-CC’s latency assumptions, not spraying or reordering.[11] UEC also warns that switch-autonomous spraying breaks an endpoint’s feedback loop — it is “RECOMMENDED that such behavior not be enabled for UET traffic if path-aware multipath spraying is used, as it prevents UET getting a clean signal as to which paths are congested”.[11]
That last point is what changes the recommendation this module already shipped. NVIDIA’s own architecture narrative demotes ECN to a second-line signal when adaptive routing is the first: “the switch only generates Explicit Congestion Notification (ECN) marks when its adaptive routing capacity is completely exhausted and the queue continues to grow”, and senders “utilize precise RTT probes combined with ECN marks to adjust their sending rates at RTT timescales”.[12] Read it as a conjunction, not a substitution: on that architecture a mark means the queue got deep and routing could not relieve it.[12]
⚠ Model only: a 4 MB TC3 pool at 400 Gb/s (50,000 B/µs). The shape is right; the numbers are not a simulation of your fabric.
1. Sender transmits
The RP offers traffic on priority 3 with an ECT codepoint set. If the packets left as Not-ECT (00) the switch is not allowed to mark them at all — it can only drop.
The sending NIC. On the host this is the r_roce_ecn_rp algorithm, enabled per priority: echo 1 > /sys/class/net/<if>/ecn/roce_rp/enable/3.
It is the only element that can lower the offered load. Marking and CNPs are worth nothing if the RP never acts: the rate stays flat and the queue keeps growing.
Tooling: Counter: rp_cnp_handled (sender). Read with rdma statistic show link mlx5_0/1, or straight from hw_counters.
FAE angle: when np_cnp_sent climbs on the receiver and rp_cnp_handled stays flat on the sender, stop tuning thresholds. The CNPs are being lost or misclassified on the way back.
- Two host algorithms: r_roce_ecn_rp (reaction point) and r_roce_ecn_np (notification point), per device/port/priority
- ⚠ np_cnp_sent = CNPs sent by the notification point after seeing CE; rp_cnp_handled = CNPs handled by the reaction point to throttle — not confirmed on a fetched NVIDIA page
- Every current NVIDIA ECN page ends the parameter section with the literal string "requested attributes: Next Slide" — the tables were lost converting slides to docs
np_ecn_marked_roce_packetsflatreceiver · hw_counters — CE seen in the RoCEv2 IP header. Flat while the queue is deep = the switch is not marking your queue.cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/np_ecn_marked_roce_packets
np_cnp_sentflatreceiver · hw_counters — CNPs the notification point put back on the wire, DSCP 48 / priority 6.cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/np_cnp_sent
rp_cnp_handledflatsender · hw_counters — CNPs the reaction point acted on. Moves on a different host from np_cnp_sent — read both.cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/rp_cnp_handled
rp_cnp_ignoredflatsender · hw_counters — CNPs that arrived and were dropped on the floor — r_roce_ecn_rp is not enabled for this priority.cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/rp_cnp_ignored
rx_prio3_pauseflatsender · ethtool -S — The switch is pausing you on priority 3. Direction identifies the victim.ethtool -S <if> | grep -E "prio3_pause"
tx_prio3_pauseflatreceiver · ethtool -S — You pausing the network. Flat here: the receiver NIC is not the bottleneck in this scenario.ethtool -S <if> | grep -E "prio3_pause"
packet_seq_errflatsender · hw_counters — The fabric dropped or reordered RoCE. On a lossless design this number should stay at zero.cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/packet_seq_err
rx_prio3_buf_discardflatreceiver · ethtool -S — Packets discarded for lack of per-host receive buffers on priority 3.ethtool -S <if> | grep -E "prio3_(buf_)?discard"
The switch marks CE instead of dropping; the receiver returns CNPs; the sender cuts its rate. No PFC anywhere, so a marking mistake becomes loss instead of a pause.
nv set qos roce mode lossy # switch: drops PFC and the lossless pool, keeps ECN echo 1 > /sys/class/net/<if>/ecn/roce_rp/enable/3 echo 1 > /sys/class/net/<if>/ecn/roce_np/enable/3
Queue is shallow; no mechanism has anything to do yet. Raise the offered load.
Cumulus RoCE (NVUE) · ECN sysfs · ZTR-RTT parameters · mlx5 counters · rdma statistic
6The counters, and the two levers an operator actually has
The names come from two adjacent, separately gated arrays in the mlx5 driver. out_of_seq_q_cnts[] holds one entry, out_of_sequence; retrans_q_cnts[] holds duplicate_request, rnr_nak_retry_err, packet_seq_err, implied_nak_seq_err and local_ack_timeout_err.[13] Each array registers only if its own capability bit is set, and the two gates are independent, so a missing packet_seq_err on a customer’s box is a capability statement rather than a broken driver.[13] In switchdev mode on a non-host-PF port the driver uses the parallel vport_* arrays with a literal vport_ prefix, reason enough for a bare grep to come back empty.[13] Nor are these six the whole retransmission picture: extended_err_cnts[] adds req_transport_retries_exceeded and req_rnr_retries_exceeded.[13]
Now the honest gap, and say it out loud to the customer. The driver exports names; it defines no semantics, and NVIDIA publishes no primary page that does — including which end of a connection each counter increments on, which is why you read both ends and record which one moved.[13] No NVIDIA page states that adaptive routing changes what out_of_sequence or packet_seq_err mean either. The chain — per-packet forwarding, out-of-order arrival, placement instead of a NAK — is assembled from separate primaries: the Cumulus ECMP page, the DOCA out-of-order page, and a blog.[4][8][12] On the switch side there is nothing to read: nv show router adaptive-routing “Shows if adaptive routing is enabled on the switch”, and the per-interface form prints enable on and link-utilization-threshold 70 under an applied column — configuration, not counters.[7]
Two levers remain, and both are host-side. The first is queue pairs: NCCL_IB_QPS_PER_CONNECTION is “a number between 1 and 128, default is 1”, and NVIDIA’s own rationale is routing entropy — it “can be useful on multi-level fabrics which need multiple queue pairs to have good routing entropy”, with no recommended value and no published performance number.[14] The page does not tell you the resulting QP count; the source does, computing nQpsPerDev * ndevs on both sides and taking the larger, so on merged or multi-rail hosts the count is the environment value times the device count.[16] The second lever is NCCL’s adaptive routing framing: NCCL_IB_ADAPTIVE_ROUTING is “Enabled (1) by default on IB networks. Disabled (0) by default on RoCE networks”, and it controls how NCCL frames sends above NCCL_IB_AR_THRESHOLD.[14] NCCL_IB_OOO_RQ is documented as a bare force-enable — “Disabled (0) by default. Set to 1 to force enable” — with no precondition stated anywhere on the page, and that omission is the trap.[14] The 2.31.2 source refuses the connection on an mlx5 device when the force-enable is set and ar == 0, returning ncclInternalError with “OOO RQ is force enabled but AR is not enabled”; on RoCE, where ar is 0 by default, the force-enable alone fails connection setup.[16]
Read order: rping smoke test → ethtool -S per-priority on BOTH hosts → the switch's PFC/ECN counters → hw_counters → a packet capture, last.
Fine at two nodes, collapses at scale
“Any two hosts hit line rate, but the 32-node job runs at a third of it.”
- 1Sender
ethtool -S enp1s0f0 | grep rx_prio3_pause
What would you expect to see? Commit your diagnosis to reveal.
- 2Receiver
ethtool -S enp1s0f0 | grep tx_prio3_pause
What would you expect to see? Commit your diagnosis to reveal.
- 3Switch
nv show interface swp1 qos roce counters
What would you expect to see? Commit your diagnosis to reveal.
- 4Sender
cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/packet_seq_err
What would you expect to see? Commit your diagnosis to reveal.
rp_cnp_handled / rp_cnp_ignored increment here. This is where the rate is reduced.
/sys/class/infiniband/<dev>/ports/<p>/hw_counters/ grep . /sys/class/infiniband/mlx5_0/ports/1/hw_counters/*
The driver's real RoCE counters: transport errors, RNR, retries, ICRC, and the DCQCN four (np_ecn_marked_roce_packets, np_cnp_sent, rp_cnp_handled, rp_cnp_ignored). Names come from the in-tree mlx5_ib driver.
Gotcha. lifespan is a cache, "in milliseconds and defaults to 10"; you may echo 0–10000 into it. Poll faster than lifespan and every read returns the same value — that is your "frozen counter".
sourceRoCE counters are read on both hosts in the same window, with timestamps. A single host's dump cannot separate “the receiver never reported” from “the sender never reacted”.
Situation: 64 new nodes, Cumulus 5.17 leaves, adaptive routing enabled on the new leaves only, no PFC, ECN on the RoCE class. out_of_sequence and packet_seq_err climbing on the hosts; job throughput down; nothing reports a drop.
- Establish what the switch is actually configured to do, in its own vocabulary. Ask for
nv show router adaptive-routingandnv show interface <interface-id> router adaptive-routingfrom every leaf and spine in the path.[7] These print configuration, not counters, so the deliverable is a list of ports with adaptive routing on and a list without it — nothing about volume.[7] - Test eligibility before testing anything else. On 5.17 the page requires the Spectrum-X platform, and its support list is Spectrum-4 at 400G and 200G, adaptive-routing-eligible RoCE2 unicast traffic, VXLAN-encapsulated RoCE traffic, layer 3 interfaces and next hop router interfaces in the default VRF; the restriction bullets exclude layer 3 subinterfaces, SVIs, bonds and bond members, and 800G links on Spectrum-4.[5] A pod cabled with bonded uplinks fails the list and the whole symptom is something else.
- Check the mixing condition, because it is the likeliest configuration fault in a partial rollout. Cumulus expects adaptive routing “on all the ports that are part of the same ECMP route”, and warns that mixing adaptive routing traffic with regular ECMP routes egressing ports without it means “Unexpected route hashing might occur”.[4] New leaves on, old leaves off, one ECMP route across both, is exactly that shape.
- Separate reordering from loss on the host. Read the two arrays and their neighbours in one window on both ends:[13]
The reading you are testing, and label it in the report as assembled rather than quoted, because no NVIDIA page defines these semantics:grep . /sys/class/infiniband/mlx5_0/ports/1/hw_counters/{out_of_sequence,packet_seq_err,implied_nak_seq_err,local_ack_timeout_err,req_transport_retries_exceeded}out_of_sequenceandpacket_seq_errclimbing whilelocal_ack_timeout_errandreq_transport_retries_exceededstay flat is consistent with a responder NAKing gaps rather than a requester missing ACKs;local_ack_timeout_errmoving too is consistent with real loss.[13] Record which end of the connection each number moved on — the driver does not tell you, and neither does NVIDIA.[13] If the counters are absent, that is a capability bit, not a broken driver.[13] - Decide the receiver-side question honestly. NVIDIA documents out-of-order data placement for ConnectX-5 and above, RC and XRC QPs and DC transport, opt-in through
MLX5DV_QP_CREATE_OOO_DP, queried first, set on both ends, with no fallback.[8][9] It documents it under the InfiniBand chapter and never uses the word RoCE on that page, so write in the report that engagement on their RoCE QPs is unverified rather than promising it.[8] - Offer two reversible moves and one non-move. Reversible: revert adaptive routing on the new leaves so the pod is uniform (
nv set router adaptive-routing state disabledon 5.15+ syntax,enable offon 5.14 and earlier), or finish the rollout so every member port of the ECMP route has it.[6][4] Host-side: raiseNCCL_IB_QPS_PER_CONNECTIONfrom its documented default of 1 to gain entropy without touching the fabric,[14] remembering that the resulting count is the value times the device count on multi-rail hosts, which the source shows and the page does not.[16] The non-move: do not setNCCL_IB_OOO_RQ=1on RoCE withoutNCCL_IB_ADAPTIVE_ROUTING=1— the documentation states no precondition, but the 2.31.2 source fails connection setup.[16] - Write the caveats in. The causal chain from per-packet forwarding to out-of-order placement is assembled from three separate pages and is written as one sentence by none of them, and no NVIDIA page states that adaptive routing changes what these counters mean.[4][8][13]
- Ask for
nv show router adaptive-routingfrom every hop. Remember it prints ____ , not ____ . - Eligibility on 5.17: Spectrum-4 at ____ and ____ speeds, ____ unicast traffic, ____-encapsulated RoCE traffic, layer ____ interfaces, next hop router interfaces in the ____ VRF. Excluded: subinterfaces, SVIs, ____ , and ____ links on Spectrum-4.
- Mixing condition: enable it on ____ the ports of the same ____ route, or the page warns of ____ route hashing.
- Host counters. Consistent with reordering:
out_of_sequenceand____climbing whilelocal_ack_timeout_errstays ____ . Consistent with real loss:____moves too. An absent counter means a ____ bit, and the reading itself must be labelled ____ because no page defines it. - Receiver side: flag
____, gates are query-first, ____ ends, and no ____ . The page is filed under ____ and never says RoCE, so the report says ____ . - Two reversible moves: revert with
nv set router adaptive-routing state ____(orenable ____on 5.14), or raise____from its default of ____ . Never setNCCL_IB_OOO_RQ=1on RoCE without ____ , which only the ____ states. - Caveat to write down: the chain is ____ from three pages and stated as one sentence by ____ .
A different Dell account. Two pods, both Cumulus 5.14, adaptive routing enabled everywhere from day one, PFC enabled on priority 3 everywhere, ECN on the same class. Symptom: at 32 nodes throughput is fine; at 64 it drops by half. rx_prio3_pause is climbing on the senders, out_of_sequence is climbing on the receivers, np_ecn_marked_roce_packets is climbing, local_ack_timeout_err is flat. The customer’s proposal is to raise the ECN marking threshold.
Write the evaluation and the change plan. Acceptance criteria: (a) state, with the documented sentence behind it, why a per-packet-forwarding fabric and a hop-by-hop pause mechanism are in tension, and whether any NVIDIA page says so; (b) explain what the flat local_ack_timeout_err rules out and what it does not; (c) argue whether raising the ECN threshold addresses the observed counters, given that ECN marks and pause frames are both present; (d) name the exact 5.14 NVUE syntax you would use for any switch-side change and its rollback; (e) give one host-side change that needs no switch work, its documented range and default, and the reason it is not a free win on a multi-rail host; (f) state which single claim in your recommendation is assembled from separate pages rather than quoted, and how you will label it in the document.
Episode 6 — The sixth word
You put the counters on the projector: out_of_sequence and packet_seq_err climbing, local_ack_timeout_err flat. Nobody is losing packets; the leaves choose per packet and strict RC calls every gap a loss.[4][5][13] The switch team finishes the rollout to every member port of the ECMP route, and the report says that out-of-order placement is documented under InfiniBand and unverified on their RoCE queue pairs.[8] The NVIDIA PM confirms MRC is announced, is in the IBTA spec as Annex 21, and has nothing published to turn it on — correctly.[15] A fabric feature nobody told the transport about is a fabric feature the transport will undo. The operator relabels the door: MARKED, TRUSTED, PAUSED, SIGNALLED, MEASURED, SPRAYED — then asks how anyone proves this with a benchmark.
Lab
Goal: on the Dell-lab BlueField-3 SuperNIC and ConnectX hosts plus one Spectrum leaf, capture the source port, baseline the counters, then make one switch-side change and revert it. Steps 5 and 6 are mutating and each names its rollback.
- Pre-flight inventory, read-only. On both hosts:
Expected:ibdev2netdev ibv_devinfo -d mlx5_0 | grep -E "transport|fw_ver|link_layer" ip -d link show <netdev> | head -20link_layer: Etherneton the RoCE ports. An InfiniBand link layer means the rest of this lab reads differently, because NCCL’s adaptive routing default flips on link layer.[14] - Baseline the counters on both hosts and save them. They are
hw_countersunder/sys/class/infiniband/, not ethtool counters:[13]
Expected: five numbers, orfor h in out_of_sequence packet_seq_err implied_nak_seq_err local_ack_timeout_err req_transport_retries_exceeded; do printf '%s %s\n' "$h" "$(cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/$h 2>/dev/null || echo ABSENT)" done | tee /tmp/roce-m2-06-counters-before.txtABSENTfor a name whose capability bit is clear. If you are on a switchdev non-host PF, look for thevport_prefixed names instead before concluding anything is missing.[13] Run it on both ends and keep the files apart — no page tells you which end a given counter increments on.[13] - Capture the entropy. Start
ib_write_bw -d mlx5_0 -q 1on one host and its client on the other, and on the server side run:
Expected: a constant UDP source port for the run, insudo timeout 5 tcpdump -i <netdev> -nn udp port 4791 -c 200xC000to0xFFFFon mlx5, against a fixed destination port 4791.[1][17] Record it. One QP, one number, one path.[2] - Repeat with eight QPs:
ib_write_bw -d mlx5_0 -q 8on both ends, same capture. Expected: several distinct source ports. This is the portable multipath tool — portable verbs expose no call that changes a live QP’s path entropy, so more QPs is what a vendor-neutral application has.[19] - Mutating, switch side. Save the running configuration first, then enable adaptive routing on the leaf’s ECMP member ports. Use the syntax for the release on the box —
state enabledon 5.15 and later,enable onon 5.14 and earlier:[6]
Rollback:nv config save # restore point nv show router adaptive-routing # record the before state nv set router adaptive-routing state enabled nv set interface swp1,swp2,swp3,swp4 router adaptive-routing state enabled nv config applynv set router adaptive-routing state disabledand the matching per-interfacestate disabled, thennv config apply, then re-runnv show router adaptive-routingand diff it against the before state from this step. Enable it on every member port of the ECMP route, not a subset — the page warns that mixing adaptive routing traffic with regular ECMP routes egressing ports without it means unexpected route hashing might occur.[4] Note that NVUE reloads switchd when applying these settings on 5.13 and later, and restarted it on 5.12 and earlier.[6] - Mutating, host side, optional. Re-run the workload under NCCL with more queue pairs per connection, one variable at a time:
Rollback: unset the variable; the documented default is 1.[14] Do not addNCCL_IB_QPS_PER_CONNECTION=4 NCCL_DEBUG=INFO <your all-reduce benchmark>NCCL_IB_OOO_RQ=1here unlessNCCL_IB_ADAPTIVE_ROUTING=1is set too: the documentation calls it a plain force-enable, but the 2.31.2 source fails connection setup on a device with adaptive routing off, which is the RoCE default.[16] - Re-read the counters with the same loop as step 2 into a second file and diff. Record what moves, on which host, and by how much — do not record a conclusion about what NVIDIA says these counters mean under adaptive routing, because no NVIDIA page says it.[4][8] The hypothesis to test, not the expected result:
out_of_sequenceandpacket_seq_errmoving withlocal_ack_timeout_errflat is consistent with reordering rather than loss, and adaptive routing does not guarantee that packets of one QP arrive out of order at all.[13] - Close out. Apply the step 5 rollback, confirm
nv show router adaptive-routingmatches the before state, unset any NCCL variables from step 6, and re-run the step 3 capture to confirm the source port behaviour is back to the baseline.[7]
Goal: derive the entropy by hand, prove it is symmetric, then read a recorded transcript. No hardware, no switch.
- Compute the source port for one QP pair. Local QPN 1057, remote QPN 2081. Follow the two inline helpers: multiply the QPNs, fold the product with
fl ^= fl >> 20thenfl ^= fl >> 40, mask to 20 bits, split the label intolow = label & 0x03FFFandhigh = label & 0xFC000, XORlowwithhigh >> 14, then OR in0xC000.[17] Expected: product 2199617, label0x19043, folded0x1045, source port0xD045= 53317. If your answer is below 49152 you skipped the OR. - Prove the symmetry. Redo step 1 with the QPNs swapped, 2081 and 1057. Expected: the same
0xD045, because the label is built from a product and multiplication is commutative — which is how both ends agree without exchanging anything, and what the helper’s own comment says it is for.[17] Then note the contrast: the RDMA-CM label ishash = (u32)sport * 31 + dport, which is not commutative.[20] - Change one QPN by one. Redo with 1057 and 2082. Expected: label
0x19460, source port0xD466= 54374 — a different number, and on a four-way ECMP set a different path.[17] One QP still gets one path; you just moved which one.[1] - Run the simulator twice. In the interactive, set entropy
single-qpwith four paths and a strict RC receiver, step through, and write down the arrival order and which counters move. Then set entropyswitch-arwith everything else unchanged and do the same. Expected: one path and in-order arrival in the first case; interleaved arrival andout_of_sequencepluspacket_seq_errin the second, with no drop anywhere.[13] - Change the receiver, not the fabric. Set the receiver to
ooo-placementwithswitch-arstill on. Write one sentence on what changed and one on what did not. Expected: arriving packets are placed in host memory in the order they arrive rather than being NAKed, and completion ordering is unchanged on both sides.[8][9] - Read this recorded transcript and name each line’s meaning:[7][6]
Expected: A and B print configuration and prove nothing about traffic; B additionally shows the per-interface link-utilization threshold; C is 5.15-and-later syntax; D is 5.14-and-earlier syntax for the same thing. Neither A nor B is a counter command.[7]A: nv show router adaptive-routing applied: enable on B: nv show interface swp1 router adaptive-routing applied: enable on link-utilization-threshold 70 C: nv set router adaptive-routing state enabled D: nv set router adaptive-routing enable on - Boundary drill. Write the two sentences you would put in a customer email: one stating exactly what NVIDIA documents about out-of-order data placement and the chapter it is filed under, one stating what is not published about MRC.[8][10] Compare against the FAE callout in segment 6.
Retrieval check
10 questions from memory. Answer before looking anything up; misses become flashcards.
Explain it to a Dell SE
A Dell customer's switch team enabled adaptive routing over the weekend and out_of_sequence started climbing on the hosts. Explain in five sentences what changed, why the transport reacts the way it does, what NVIDIA documents about the receiver side, and what you will not promise.
Sources
Facts in this lesson were checked against Cumulus Linux 5.14 and 5.18 Equal Cost Multipath Load Sharing pages, NVUE Adaptive Routing set and show references, DOCA 3.5.0 RDMA over Converged Ethernet and Out-of-order Data Placement, DOCA 3.3.0 bug-fix list, rdma-core v65.0 mlx5dv_create_qp(3), mlx5dv_modify_qp_udp_sport(3), ibv_modify_qp(3) and providers/mlx5/mlx5.c, linux v6.12 include/rdma/ib_verbs.h, hw/mlx5/qp.c, core/cma.c, hw/bnxt_re/ib_verbs.c and v6.17 hw/mlx5/counters.c, NCCL 2.31.2 environment variables and NVIDIA/nccl v2.31.2-1 net_ib/connect.cc, Ultra Ethernet Specification v1.0.3, AWS SRD (IEEE Micro 2020), NVIDIA Giga-Scale Ethernet blog, IBTA Release 2.1 overview deck - all fetched 2026-09-11. Dates are when each page was fetched.
- RDMA over Converged Ethernet - DOCA SDK Documentation v3.5.0 · fetched 2026-09-11 · DOCA 3.5.0
- linux v6.12 - drivers/infiniband/hw/mlx5/qp.c · fetched 2026-09-11
- rdma-core v65.0 - providers/mlx5/mlx5.c (mlx5dv_modify_qp_udp_sport implementation) · fetched 2026-09-11
- Equal Cost Multipath Load Sharing - Cumulus Linux 5.14 · fetched 2026-09-11
- Equal Cost Multipath Load Sharing - Cumulus Linux 5.18 · fetched 2026-09-11
- Adaptive Routing - NVUE Set and Unset Commands · fetched 2026-09-11
- Adaptive Routing - NVUE Show Commands · fetched 2026-09-11
- Out-of-order Data Placement - DOCA 3.5.0 · fetched 2026-09-11 · DOCA 3.5.0
- rdma-core v65.0 - mlx5dv_create_qp(3) man page · fetched 2026-09-11
- Resilient AI Supercomputer Networking using MRC and SRv6 (arXiv:2605.04333v1 preprint) · fetched 2026-09-11
- Ultra Ethernet Specification v1.0.3 (July 16, 2026) · fetched 2026-09-11
- Giga-Scale AI and the Ethernet Evolution - How Spectrum-X Ethernet Rewrites the Rules (NVIDIA blog, 2026-08-24) · fetched 2026-09-11
- linux v6.17 - drivers/infiniband/hw/mlx5/counters.c · fetched 2026-09-11
- Environment Variables - NCCL 2.31.2 documentation · fetched 2026-09-11
- What is new - Release 2.1, Vol. 1 and 2, General Overview (IBTA public slide deck) · fetched 2026-09-11
- NVIDIA/nccl v2.31.2-1 - src/transport/net_ib/connect.cc · fetched 2026-09-11
- linux v6.12 - include/rdma/ib_verbs.h (rdma_calc_flow_label, rdma_flow_label_to_udp_sport) · fetched 2026-09-11
- rdma-core v65.0 - mlx5dv_modify_qp_udp_sport(3) man page · fetched 2026-09-11
- rdma-core v65.0 - ibv_modify_qp(3) man page · fetched 2026-09-11
- linux v6.12 - drivers/infiniband/core/cma.c (cma_get_roce_udp_flow_label) · fetched 2026-09-11
- linux v6.12 - drivers/infiniband/hw/bnxt_re/ib_verbs.c (QP1 UDP source port) · fetched 2026-09-11
- Bug Fixes in This Version - DOCA 3.3.0 (entry 4636365, Multi-Rack Connectivity service) · fetched 2026-09-11 · DOCA 3.3.0
- A Cloud-Optimized Transport Protocol for Elastic and Scalable HPC (AWS SRD, IEEE Micro 2020) · fetched 2026-09-11