Host QoS with mlnx_qos: trust state, prio2tc, ETS
S2·E2Two identical nodes that are not identical · Building D, day two of the bring-up, four days out
Builds on: ToS, DSCP, PCP, SL: where marking gets lost
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
- Read a full mlnx_qos output block and state the trust state the DSCP-to-priority map the PFC row and the scheduling algorithm of every traffic class.
- Apply mlnx_qos to move a host from the default PCP trust to DSCP trust with a chosen dscp2prio entry and roll the change back.
- Choose between strict ets and vendor for a traffic class and justify the choice using NVIDIA's stated starvation and minimum-bandwidth semantics.
- Identify when mlnx_qos is the wrong tool - BlueField Embedded Host mode and DPU rate limiting - and name what replaces it.
Episode 2 — Two identical nodes that are not identical
Day two of the bring-up, four days out. Two PowerEdge nodes off the same order, same firmware, same cabling, and an ib_write_bw run clean one way and half rate the other. The Dell SE has spent the morning swapping optics and added a spreadsheet row for them. The night-shift operator has arrived early with a label maker and an offer to relabel the rack, which is the second-best idea in the room.
You stop the cable hunt and print the whole host QoS state at once. mlnx_qos is documented as a centralized tool that talks directly to the driver and needs no DCBX daemon, which is why it belongs first on any RoCE ticket.[1] Node A reports Priority trust state: dscp. Node B reports pcp — the default you get by not choosing, since NVIDIA states the default trust state is PCP.[1] Nothing about the fabric is wrong: the two ends of one flow disagree about which field on the frame is allowed to mean anything.
That is why trust is explicit rather than inferred. A tagged frame carries both PCP and DSCP and nothing forces them to agree; an untagged frame carries only DSCP.[1] Making it explicit is what makes it auditable in one line — and the same output carries the dscp2prio table, the PFC row and every class’s scheduler beside it.[1]
Two hosts that disagree about trust produce symptoms that look exactly like a broken fabric. The operator labels node B while you save the before state, because there is no undo. Read it block by block.
1One command, four blocks, and the line that decides everything
mlnx_qos is “a centralized tool used to configure QoS features of the local host. It communicates directly with the driver thus does not require setting up a DCBX daemon on the system.”[1] Its syntax is mlnx_qos -i <interface> [options], and run with no options it prints the host’s entire QoS state — which is why it is the first command on any RoCE ticket.[1][2]
The canonical output has four blocks. NVIDIA’s own sample, abridged:[1][8]
DCBX mode: OS controlled
Priority trust state: dscp
dscp2prio mapping:
prio:0 dscp:07,06,05,04,03,02,01,00,
prio:1 dscp:15,14,13,12,11,10,09,08,
prio:2 dscp:23,22,21,20,19,18,17,16,
prio:3 dscp:31,30,29,28,27,26,25,24,
prio:4 dscp:39,38,37,36,35,34,33,32,
prio:5 dscp:47,46,45,44,43,42,41,40,
prio:6 dscp:55,54,53,52,51,50,49,48,
prio:7 dscp:63,62,61,60,59,58,57,56,
Cable len: 7
PFC configuration:
priority 0 1 2 3 4 5 6 7
enabled 0 0 0 0 0 0 0 0
tc: 0 ratelimit: unlimited, tsa: vendorRead them in this order. DCBX mode says who owns the configuration, firmware or the OS.[1] Priority trust state says which packet field the NIC prioritizes on, and it is the line that decides whether an application’s DSCP has any effect at all — “The default trust state is PCP. Ethernet packets are prioritized based on the value of the field (PCP/DSCP).”[1] The dscp2prio mapping block is the DSCP-to-priority table. The PFC configuration row and the eight tc: lines say what can be paused and how each class is scheduled.
The default map deserves a second look because operators keep rebuilding it by hand. It groups DSCP in eights, so DSCP 24-31 already lands on priority 3 and DSCP 48-55 on priority 6, with no --dscp2prio at all.[1] That is exactly the split the switch side uses: Cumulus defaults RoCE data to DSCP 26 on switch priority 3 and CNP to DSCP 48 on switch priority 6.[5] Host and switch agree out of the box on everything except the trust state.
1. App sets a ToS
Carried by: nothing on the wire yet — this lives in the address vector
RDMA-CM apps set it per connection with rdma_set_option(..., RDMA_OPTION_ID_TOS, value); "If a TOS is not set, the default value (0) is used." The CM default can also be moved without touching code: cma_roce_tos -d <ib_device> -t <TOS>, or the RDMA-CM configfs entry. An app that calls rdma_set_option still overrides both.
ib_write_bw -T 104 -R # CM path ib_write_bw --tclass=104 # AV path, no CM # no code change at all: echo "tclass=104" > /sys/class/infiniband/mlx5_0/tc/1/traffic_class echo "tclass=16,src_ip=1.1.1.2,dst_ip=1.1.1.0/24" > /sys/class/infiniband/mlx5_0/tc/1/traffic_class echo "tclass=-1" > /sys/class/infiniband/mlx5_0/tc/1/traffic_class # delete
Failure mode #1 of three: the app never set a ToS, so the traffic is DSCP 0 / priority 0 no matter how good the switch config is. Force DSCP via .../tc/<port>/traffic_class proves it in one command — global wins over rules, negative values disable, valid range 0–255. The sibling file is ttl.
trust pcp with an untagged frame: there is no PCP on the wire, so every packet is treated as priority 0 — including the DSCP you set.
Counter that proves it: ethtool -S <netdev> | grep prio — traffic sits in prio0 while your PFC priority stays idle.
⚠ marks a number no fetched NVIDIA or IETF page states: the GRH, BTH and ICRC byte sizes (the IBTA spec is members-only) and whether a switch classifies RoCEv1 on the GRH traffic-class byte.
2The option surface, exactly as documented
Every flag below is quoted from the Ethernet QoS option table.[1] -f LIST, --pfc=LIST: “Set priority flow control for each priority. LIST is a comma separated value for each priority starting from 0 to 7. Example: 0,0,0,0,1,1,1,1 enable PFC on TC4-7”.[1] -p LIST, --prio_tc=LIST: “Maps UPs to TCs. LIST is 8 comma-separated TC numbers. Example: 0,0,0,0,1,1,1,1 maps UPs 0-3 to TC0, and UPs 4-7 to TC1”.[1] -s LIST, --tsa=LIST takes strict, ets or vendor per class.[1] -t LIST, --tcbw=LIST sets minimum guaranteed percentages that “must sum to 100”, and values on non-ETS classes “are ignored but must be present”.[1] -r LIST, --ratelimit=LIST is Gbps per class, where “For an unlimited ratelimit, set the ratelimit to 0”, with a documented tolerance of roughly 10 percent.[1] --trust=TRUST is “set priority trust state to pcp or dscp”; --dscp2prio=DSCP2PRIO takes 'set,30,2' to map DSCP 30 to priority 2 and 'del,30,2' to reset it; --cable_len=CABLE_LEN sets “cable_len for buffer’s xoff and xon thresholds”; -d DCBX chooses fw or os.[1]
Two semantics matter more than the syntax. Strict priority: “TC 7 is the highest priority, TC 0 is lowest”, strict classes also outrank all ETS classes, and “This property needs to be used with care, as it may easily cause starvation of other TCs.”[1] ETS is the opposite kind of promise — it “exploits the time periods in which the offered load of a particular Traffic Class (TC) is less than its minimum allocated bandwidth by allowing the difference to be available to other traffic classes”, so the percentages are floors, not ceilings.[1] That is the pairing the switch side uses too: CNP strict, RoCE and default traffic at 50 percent each under weighted round robin.[5] vendor is the third legal value and the one NVIDIA’s own sample output shows on every class, but the option table names it without defining its scheduling behaviour, re-checked 2026-09-07.[1][2] Read a tsa: vendor line as “driver default, not a decision anyone made”, and if the class matters, set the algorithm you intend explicitly with -s.
3Applying it: the DSCP-trust host recipe and its rollback
The host half of a DSCP-based lossless RoCE deployment is three settings: trust DSCP, map the fabric’s RoCE DSCP to its priority, and enable PFC on that priority.[1][5] A Juniper validated design that configures NVIDIA hosts uses exactly this shape, mlnx_qos -i <interface> --pfc <0/1>,<0/1>,... followed by a bare mlnx_qos -i <interface> to verify.[7]
mlnx_qos -i <if> > /tmp/qos-before.txt # this file is your rollback plan
mlnx_qos -i <if> --trust dscp
mlnx_qos -i <if> --dscp2prio set,26,3
mlnx_qos -i <if> --pfc 0,0,0,1,0,0,0,0
mlnx_qos -i <if> # diff against /tmp/qos-before.txtTwo cautions about that block. First, the exact three-line combination is widely circulated but the NVIDIA knowledge-base article carrying it as a canonical recipe could not be fetched for this course, so treat the strings as assembled from the documented option table rather than as a quoted NVIDIA recipe; the DSCP 26 to priority 3 default underneath it is confirmed.[5][1] Second, --dscp2prio set,26,3 is redundant on a stock host, because the default map already puts 24-31 on priority 3 — it is worth running anyway when a previous operator may have edited the map, and worth checking rather than assuming.[1]
Ordering has one hard rule: “Setting the Trust State mode shall be done before enabling SR-IOV in order to propagate the Trust State to the VFs.”[1] On a host that is already running VFs, changing trust on the PF does not retroactively fix them, which is why this step belongs in provisioning and not in a live-troubleshooting session. NVIDIA’s own Spectrum-X host profile encodes the same decisions as declarative fields — Trust: dscp, ToS 96, and a PFC bitmap of "00010000" — so the recipe above is the manual form of a profile the NIC Configuration Operator applies for you.[6]
- Profile doca-all is "other profiles" in the matrix → Level 2. Every component is in cycle 25 (Oct 2025 → Jul 2026 (3.2 → 3.5)) → supported until the next October GA.
- This is exactly the Spectrum-X validated stack v2.3.1 combination (Sep 2026).
- ⚠ Any FW or mode change on PowerEdge needs a full power cycle, not a warm reboot (Dell KB 000300192; NVIDIA modes page).
Matrix (policy): doca-ofed ↔ FW/BF-FW-Bundle = L1 · doca-ofed ↔ BF-Bundle = L1 · other profiles ↔ FW or BF-Bundle = L2 · DOCA-DPU ↔ BF-FW-Bundle = L2 · DOCA Services ↔ BF-Bundle/FW = L2. source ↗
⚠ = not confirmed on a fetched primary source (hover for why). Facts as of DOCA 3.5.0 (Sep 2026). Selections are saved.
4Where mlnx_qos is the wrong tool
On BlueField the tool has a documented trap: “When working in Embedded Host mode, using mlnx_qos on both the host and Arm will result with undefined behavior. Users must only use mlnx_qos from the Arm.”[4] This is not a warning about conflicting values; it is a statement that the result is undefined. On a Dell PowerEdge with a BlueField-3 in DPU mode, the QoS conversation belongs to the Arm side, and a customer’s x86 runbook that sets host QoS needs to be rewritten before it is run, not after it misbehaves.
Rate limiting on the DPU is a different subsystem entirely. DOCA documents devlink port function rate add|del|set|show with tx_max and tx_share, plus set parent for grouping functions into a shared rate group — not mlnx_qos -r.[4] Reaching for mlnx_qos to rate-limit a DPU function is a category error that will silently do nothing useful.
The third boundary is the switch. Everything in this lesson stops at the NIC: user priorities, traffic classes and PFC enables on the host. The switch has its own vocabulary — switch priority, traffic class, buffer pool — and its own defaults, and the two only meet on the wire through DSCP or PCP.[5] When a customer says “we set priority 3”, always ask which side of the wire they set it on.
Context: a Dell-lab ConnectX host, ens1f0, that a customer wants moved to a DSCP-trust lossless-RoCE posture on priority 3. Nothing here touches firmware.
- Capture the before state. This file is the rollback plan;
mlnx_qoshas no undo:[1]
Expected: aibdev2netdev mlnx_qos -i ens1f0 | tee /tmp/qos-before.txt mlnx_qos --help | tee /tmp/qos-help.txtPriority trust state:line (probablypcp, the default), the eight-linedscp2prioblock, an all-zero PFC row and eighttc:lines.[1] The help capture settles for this exact driver version whether--buffer_sizeand--prio2bufferexist, which the doc table does not answer.[2] - Confirm the fabric’s intent before changing the host. If the switch side is Cumulus at defaults, RoCE is DSCP 26 on switch priority 3 and CNP is DSCP 48 on switch priority 6.[5] Write those two numbers down; they are the contract the host must match.
- Change trust state:
Expected:mlnx_qos -i ens1f0 --trust dscpPriority trust state: dscpon the next read. If this host will later run SR-IOV, note that VFs inherit trust at creation, so this must happen before VFs are enabled.[1] - Make the DSCP mapping explicit rather than assumed:
Expected:mlnx_qos -i ens1f0 --dscp2prio set,26,3prio:3now lists 26 among its DSCP values. On a stock map it already did — the value of running it is that it survives a host where someone edited the map.[1] - Enable PFC on priority 3 only:
Expected: the PFC row now readsmlnx_qos -i ens1f0 --pfc 0,0,0,1,0,0,0,0enabled 0 0 0 1 0 0 0 0.[1] Remember that this is one endpoint of a hop-by-hop mechanism: PFC “must be enabled on all endpoints and switches in the flow path”.[9] - Verify by diff, not by eye:
Expected: exactly three changed regions — trust state, the priority-3 DSCP line if it moved, and the PFC row. Any fourth change is something you did not intend.mlnx_qos -i ens1f0 > /tmp/qos-after.txt diff /tmp/qos-before.txt /tmp/qos-after.txt - Rollback if needed: re-apply the values read from
/tmp/qos-before.txt—--trust pcp,--dscp2prio del,26,3if you added an entry that was not there, and--pfc 0,0,0,0,0,0,0,0if PFC was previously all zero.[1]
- Capture:
mlnx_qos -i ens1f0 | tee ____andmlnx_qos --help | tee ____. The help capture exists because the doc table lists only____and not--buffer_sizeor--prio2buffer. - Read the fabric contract: Cumulus defaults put RoCE on DSCP ____ / switch priority ____ and CNP on DSCP ____ / switch priority ____.
mlnx_qos -i ens1f0 --trust ____. This must happen before ____ is enabled, or the VFs keep the old state.mlnx_qos -i ens1f0 --dscp2prio ____,26,3. The inverse operation is____,26,3.mlnx_qos -i ens1f0 --pfc ____,____,____,____,____,____,____,____for priority 3 only.- Verify with
diffagainst the before file. Expected number of changed regions: ____ . - Rollback:
--trust ____,--dscp2prio ____,26,3if you added it, and the all-zero--pfcvector if that is what the before file showed.
A customer sends this from one of two hosts in a failing pair:
DCBX mode: OS controlled
Priority trust state: pcp
Cable len: 7
PFC configuration:
priority 0 1 2 3 4 5 6 7
enabled 0 0 0 1 0 0 0 0
tc: 3 ratelimit: unlimited, tsa: etsThey also mention that the RoCE IPs are on untagged interfaces, that the switch is Cumulus at defaults, and that they enabled SR-IOV last month for a Kubernetes rollout.
Write the analysis and the change plan. Acceptance criteria: (a) name the exact reason this host is not classifying RoCE into priority 3 despite PFC being enabled there; (b) explain why the untagged detail makes the current state strictly worse than a PCP-trust host on a tagged fabric; (c) give the ordered commands you would run, and state which one of them will not fully take effect on this host and why; (d) name the additional artifact you need from the second host before touching the switch; (e) state your rollback for every mutating command.
Episode 2 — Before the noon call
You save mlnx_qos -i <if> to a file, move node B to DSCP trust, confirm DSCP 26 still lands on priority 3 in the default map, enable PFC on priority 3, and diff against the saved output.[1] The reverse direction comes up to line rate and the label on node B is finally true. You flag the provisioning order too: trust must be set before SR-IOV is enabled, or the VFs keep the old state while every command on the host reports the right answer.[1] Then the network lead runs the whole pod instead of one pair — clean at eight nodes, a third slower at sixty-four — and asks whether PFC is really on every hop, which is the only place it counts.[9]
Lab
Goal: on the Dell-lab ConnectX or BlueField-3 host, capture the real option surface and perform one reversible trust change. Steps 1-2 are read-only; steps 3-5 are mutating and each names its rollback.
- Pre-flight inventory, read-only:
Expected: a full four-block output plus a per-TC listing. Record whetheribdev2netdev mlnx_qos -i <if> | tee /tmp/qos-before.txt mlnx_qos --help | tee /tmp/qos-help.txt mlnx_qos -i <if> -a | head -40--buffer_sizeand--prio2bufferappear in the help output; the documentation table does not list them, so this capture is the authoritative answer for this driver version.[2][1] - BlueField check. If the device is a BlueField-3 in Embedded Host mode, stop and move to the Arm console before any write: running
mlnx_qosfrom both sides is documented as undefined behavior.[4] On the Arm, repeat step 1 there. - Mutating - trust state. Rollback:
mlnx_qos -i <if> --trust pcpif the before file saidpcp.[1]
Expected:mlnx_qos -i <if> --trust dscp mlnx_qos -i <if> | grep -i "trust"Priority trust state: dscp. - Mutating - DSCP map entry. Rollback:
mlnx_qos -i <if> --dscp2prio del,26,3, which NVIDIA documents as resetting that DSCP back to the default priority-0 entry - so only run the rollback if the entry was not already present in your before file.[1]mlnx_qos -i <if> --dscp2prio set,26,3 mlnx_qos -i <if> | sed -n "/dscp2prio/,/Cable/p" - Mutating - PFC on priority 3. Rollback: re-apply the PFC vector from
/tmp/qos-before.txt, normally--pfc 0,0,0,0,0,0,0,0.[1]
Expected:mlnx_qos -i <if> --pfc 0,0,0,1,0,0,0,0 mlnx_qos -i <if> | sed -n "/PFC configuration/,+3p"enabled 0 0 0 1 0 0 0 0. Do not claim the path is lossless from this - PFC must be enabled on every endpoint and switch in the path.[9] - Verify and close out:
Then perform every rollback in reverse order and confirm the diff is empty. Attachmlnx_qos -i <if> > /tmp/qos-after.txt && diff /tmp/qos-before.txt /tmp/qos-after.txt/tmp/qos-help.txtto the module notes - it is the answer to an open question the vendor documentation does not resolve.[2]
Goal: become fluent reading host QoS with no mlx5 device present. Entirely read-only.
- Read the annotated block in segment 1 and answer, without scrolling back: which priority does DSCP 26 land on, is PFC enabled anywhere, and which traffic classes are ETS. Expected: priority 3 by the default 24-31 band; PFC nowhere - the row is all zeroes; and none in that sample, since the
tc:line shown istsa: vendor.[1] - Translate three option strings into English and check yourself against the option table:
--pfc 0,0,0,0,1,1,1,1,--prio_tc 0,0,0,0,1,1,1,1,--tsa vendor,strict,ets,ets,ets,ets,ets,ets.[1] Expected: PFC on priorities 4-7; user priorities 0-3 to TC0 and 4-7 to TC1; TC0 vendor, TC1 strict, the rest ETS. - Predict the failure. Given
--tcbwpercentages that sum to 100 across only the ETS classes, with the strict and vendor positions omitted, say what happens and why. Expected: rejected - the argument is a fixed eight-element vector and non-ETS values “are ignored but must be present”.[1] - Vocabulary alignment. Write a three-column table mapping host user priority, host traffic class, and switch priority for a Cumulus-default fabric.[5] Expected: DSCP 26 to host priority 3 to a host TC of your choosing, and independently DSCP 26 to switch priority 3 to switch TC3. The point of the exercise is that the two TC numbers are different namespaces that happen to agree by convention.
- If you have an NVIDIA Air or Cumulus VX instance, configure the switch half with
nv set qos roceandnv config apply, then readnv show qos roceand line the output up against your table.[5] Course caveat: free-tier Air limits were not confirmed at authoring time, so treat this step as optional.
Retrieval check
10 questions from memory. Answer before looking anything up; misses become flashcards.
Explain it to a Dell SE
Explain to a Dell SE, in five sentences, what the four blocks of mlnx_qos output mean and which single line decides whether the customer's DSCP marking has any effect on this host.
Sources
Facts in this lesson were checked against DOCA-Host Ethernet QoS page re-fetched 2026-09-07 (option table confirmed to contain neither --buffer_size nor --prio2buffer), Ethernet QoS generated PDF, MLNX_OFED 23.10 LTS QoS page, DOCA QoS Configuration page, Cumulus Linux 5.18 RoCE page. Dates are when each page was fetched.
- Ethernet QoS (DOCA-Host) - generated PDF · fetched 2026-09-07 · DOCA 3.5.0
- Ethernet QoS (DOCA-Host, current HTML) · fetched 2026-09-07 · DOCA 3.5.0
- Quality of Service (QoS) - MLNX_OFED 23.10-2.1.3.1 LTS · fetched 2026-09-07
- QoS Configuration (DOCA SDK, BlueField) · fetched 2026-09-07 · DOCA 3.5.0
- RDMA over Converged Ethernet - RoCE (Cumulus Linux 5.18) · fetched 2026-09-07
- Spectrum-X Configuration - NVIDIA NIC Configuration Operator · fetched 2026-09-07
- NVIDIA Configuration - Juniper Validated Design (AI DC with NVIDIA and WEKA) · fetched 2026-09-07
- Quality of Service (QoS) - MLNX_OFED 5.9-0.5.6.0 · fetched 2026-09-07
- RDMA over Converged Ethernet (DOCA-Host) - generated PDF · fetched 2026-09-07 · DOCA 3.5.0
The same idea elsewhere
Other lessons that cover this ground, sometimes from another course's angle.
- How a RoCE packet gets classifiedSpectrum-X course · Same ground: dscp, trust and qos
- The switch half: Cumulus NVUE and Dell Enterprise SONiCElsewhere in this course · Same ground: PFC, trust and qos
- `nv set qos roce`: the one command and its five modesSpectrum-X course · Same ground: PFC, qos and traffic