Overview
The NIC has two buffers, each divided into pools, one pool per traffic class:- The egress buffer holds what the NIC sends. Space for each data packet is reserved before it goes to the wire, and released when the packet has been serialized onto the link, so, with the prefetch buffer off, the default, the egress pools bound the data the NIC has fetched and not yet sent. Its size is the
EgressBufferManagerTotalBufferSize, 256 KB by default. - The ingress buffer holds what the NIC has received and not yet handed on: data until its write to host memory is accepted by the PCIe link, control packets until the NIC has processed them. Its size is the
IngressBufferManagerTotalBufferSize, 4 MB by default.
- The egress buffer never drops data. When the next data packet cannot reserve space, the NIC stops generating data until the pool drains, so a full transmit pool holds the sender back rather than losing data. The data class needs a transmit pool large enough for one packet (Egress buffer).
- The ingress buffer drops what does not fit. A received packet whose pool cannot hold it is dropped. PFC, when enabled for a class, pauses the rack switch before that happens, provided the pool’s headroom absorbs what is already on its way (Sizing the headroom).
- At the default PCIe settings, the ingress buffer stays nearly empty. The PCIe link writes received data to the host faster than the 400 Gbps network link delivers it, so the receive pools never back up far enough for the receiver penalty or PFC to engage (PCIe host interface).
Key concepts
Traffic classes
With the applications’ default traffic classes, data uses class 0 and control class 1. Control means ACKs, NACKs, base-RTT probes, and trimmed packets. Data retransmissions are data. A packet uses the same class in the egress buffer of the NIC that sends it and in the ingress buffer of the NIC that receives it, except a data packet that a switch has trimmed, which the receiving NIC charges to the control class. The class decides only which pool a packet uses inside the NIC. The codepoint a packet carries on the wire is set by its role, separately from its class (Packet marking). Each buffer’sPoolAllocationVector must give classes 0 and 1 a non-zero share: an ingress or egress PoolAllocationVector with fewer than two non-zero entries stops the simulation at start (Configuration rules that stop a simulation).
With the default StrictPriority egress scheduling, the higher class goes first, so the NIC’s ACKs and NACKs leave ahead of its own waiting data (Transmit scheduling).
Buffer pools
Each buffer is divided into pools when the simulation starts, in the same way for both. The non-zero entries of the buffer’sPoolAllocationVector come first: they start at class 0 and run without a 0 between them. [0.9, 0.1, 0, 0, 0, 0, 0, 0] and [0.5, 0.3, 0.2, 0, 0, 0, 0, 0] follow this rule; [0, 0.5, 0.5, 0, 0, 0, 0, 0] does not. With the entries in that shape, each class from 0 through the last non-zero entry gets a pool:
PFCEnableVector and WRRQueueWeights, use the same index. The pools are sized independently: entries that sum to less than 1 leave the rest of the buffer unused, and entries that sum to more than 1 give pools that together exceed TotalBufferSize. The two buffers have their own PoolAllocationVector, so a class can have a different share of each.
Every byte is accounted for in the pool of the packet’s traffic class. PFC frames are not charged to any pool.
Egress buffer
Each data packet reserves its size on the wire in the egress pool of its class: its payload plus 106 bytes, 4,202 bytes for a full packet at the defaultRdmaDataMSS (Packets on the wire). When the reservation is made depends on the prefetch buffer (Prefetch buffer):
PrefetchBufferEnablefalse(default): when the payload fetch over PCIe is issued, before the payload has arrived from the host.PrefetchBufferEnabletrue: when a prefetched payload is committed to the wire.
- When the pool has room, the data packet goes ahead: its payload is fetched, or it is committed to the wire.
- When the pool has no room, the NIC stops generating data packets. Generation restarts at the latest when that pool drains to half its size or less.
- Data is never dropped here. A congested or paused class holds the sender back through its pool rather than losing data.
RdmaDataMSS + 106 bytes (4,202 bytes at the default RdmaDataMSS). If it cannot, the NIC stops sending data to every destination. The default pool, 230,400 bytes, meets this. The egress pool of the control class must hold at least one ACK, 78 bytes; the default pool, 25,600 bytes, meets this.
Without the prefetch buffer, the egress pool of the data class is therefore the most data the NIC has fetched from the host and not yet sent: 54 full packets at the defaults (Worked example: the default configuration). A larger pool lets more data wait in the NIC, for example while PFC pauses the class; a smaller one makes the NIC fetch closer to the moment it sends.
Transmit scheduling
Each time the network port can send, it takes the next frame in this order:- PFC frames first, from a dedicated queue ahead of all traffic classes. This queue is never paused and is not charged to a pool, so a pause or resume frame waits only for the frame already being transmitted and for any PFC frames queued ahead of it.
- One traffic class, chosen by the
EgressBufferManagerQueueSchedulingTypeamong the classes that have an egress pool, skipping any class whose queue is empty and any class the rack switch has paused with PFC (Receiving a pause).
With the default
StrictPriority and the default classes, ACKs and NACKs are sent ahead of waiting data. WRRQueueWeights applies only with WeightedRoundRobin: with the default egress weights [1, 5, 0, 0, 0, 0, 0, 0], class 1 sends at most five packets for each packet of class 0. Since class 1 holds only control packets by default, data is still sent whenever no control packet is waiting.
Receive buffer
Each packet the NIC receives is charged to the ingress pool of its traffic class at its size without the Ethernet header and frame check sequence: 4,184 bytes for a full data packet at the defaultRdmaDataMSS. A packet that arrives when its pool cannot hold it is dropped and counted in the nd-stats rx_drops column (Records).
How long a packet stays charged depends on what it is:
- Data stays charged until the NIC has handed it to the host interface, that is, until the PCIe transmit buffer accepts the write of its payload to host memory (Receiving a packet).
- ACKs, NACKs, probes, and trimmed packets are released as soon as the NIC has processed them.
- PFC frames are never charged.
- The ACK service time. The receiver holds each ACK for the time its data-pool backlog, plus the packet, takes to drain at its NIC side’s PCIe rate, and reports that time in the ACK so the sender’s RTT sample leaves it out (Acknowledgements).
- The receiver penalty. Above
ReceiverCongestionLowThresholdpercent of the pool, the receiver’s ACKs tell its senders to shrink their windows (Receiver penalty). - PFC. At the Xoff threshold, the NIC pauses the rack switch, for the classes PFC is enabled on (PFC).
QueueSchedulingType and WRRQueueWeights don’t change that order. The order in which traffic classes leave the NIC is set by the egress buffer’s scheduling (Transmit scheduling).
PFC
The NIC takes part in priority flow control in both directions: it pauses the rack switch when a receive pool fills, and it holds a class’s transmission when the switch pauses it.PFCEnableVector governs only the first, and it is all 0 by default, so a default NIC never sends a pause frame. It always honors the pause frames it receives. PFC is per traffic class, and its thresholds are fractions of each class’s own receive pool.
The NIC’s own pauses depend on its receive pool filling, and that happens only when its PCIe link to the host writes received data more slowly than the network delivers it. At the default PCIe settings it does not, so even with PFCEnableVector enabled, a NIC at the default PCIe settings does not send PFC. With a slower host link, the same backlog raises the receiver penalty first, at 25% of the data pool by default, well below the 80% Xoff threshold (Worked example: PCIe and network rates).
Sending a pause
For each traffic class whosePFCEnableVector entry is 1, the NIC checks every packet of that class it receives. When no pause is outstanding for the class and the pool’s occupancy, with the arriving packet added, is at or above the class’s Xoff threshold, the NIC sends one PFC pause frame for the class, requesting QuantaValue[c] quanta. The frame goes out through the dedicated queue ahead of all traffic. The arriving packet is still admitted if the pool has room for it.
The thresholds are fractions of the class’s receive pool, not of TotalBufferSize:
DataRate or the rack switch’s downlink DataRate if that is lower, and Delay is the NIC’s own TransmissionMedium Delay, the propagation delay of its link (TransmissionMedium).
Resuming
Once it has sent a pause for a class, the NIC resumes the switch in one of two ways, whichever comes first:- At the refresh. Every half pause time after a pause frame, the NIC checks the class’s pool. If occupancy is still at or above Xoff, it sends the pause again and checks again half a pause time later. Otherwise it sends XON (a frame with quanta 0), even if occupancy is still above Xon.
- On a release at or below Xon. Each time received bytes of the class are released, the NIC checks occupancy. When it is at or below Xon, it sends XON at once and stops refreshing.
Receiving a pause
A PFC frame received from the rack switch pauses or resumes the traffic class it names, for every class, whateverPFCEnableVector says. A frame with a non-zero quanta holds that class’s transmission for the time the frame asks for, computed at the NIC’s link rate, plus the link’s Delay; a fresh pause frame restarts that time in full. A frame with quanta 0, or the end of the pause time, resumes the class and restarts transmission at once.
While a class is paused, the scheduler skips it and its packets wait in its egress queue. Its egress pool keeps filling as packets already reserved join the queue, and data generation stops once the next packet cannot be reserved (Egress buffer). A received PFC frame is consumed by the NIC and never charged to a pool. How the rack switch decides to pause its ports is on Shared buffer manager.
Sizing the headroom
After the NIC sends a pause, the switch keeps sending until the pause reaches it, and what is already on the link still arrives. All of it is charged to the receive pool above Xoff, so the headroom,(1 − PfcStaticXoffThreshold[c]) × pool, has to hold it or packets of the class are dropped. rx_drops that coincide with pause frames point to too little headroom.
At the defaults, if PFC is enabled on class 0, its headroom is large next to the link:
PCIe host interface
The NIC and its host are joined by a PCIe link, with a protocol stack at each end: lanes, and transaction-layer buffers on the NIC side and on the host side (Host interface). The link carries the NIC’s payload fetches for the data it sends (Fetching the payload over PCIe) and the writes that deliver received data to host memory. Link rate. Each direction runs at the sending side’sLaneCount × LaneBps, and the two directions run independently. The NIC side’s lanes carry the NIC-to-host direction: the read requests for payload the NIC sends, and the writes of the data it receives. The host side’s lanes carry the host-to-NIC direction: the payload the NIC fetches. The defaults, 31.52 Gbps per lane on 16 lanes at both ends, give 504.32 Gbps in each direction; 31.52 Gbps is the PCIe 5.0 lane rate. The lane rate alone sets the generation the link represents: the PCIeGen3.0 container that holds these attributes in the configuration does not.
Transfers. PCIe moves data in transfers of at most 512 bytes of payload, and each transfer adds 34 bytes of PCIe protocol overhead on the link. The host answers each read and accepts each write at once: host memory latency is not modeled, and the PCIe link has no propagation delay, so what a transfer costs is its time on the link.
Buffers. Each end of the link has transaction-layer buffers for transfer headers and for payload, on its transmit side and its receive side, sized by RXHeaderBufferSize, TXHeaderBufferSize, RXDataBufferSize, and TXDataBufferSize. A transfer waits in its transmit buffer until it can go on the link, and is sent only when the receiving end has buffer space for it. On the NIC side, received data is released from the NIC’s ingress pool as soon as its write is accepted into the PCIe transmit buffer (Receive buffer), so the PCIe buffers set how much received data can wait for the link outside the NIC’s ingress pools.
Worked example: PCIe and network rates
At the defaults,RdmaDataMSS 4096, 16 lanes of 31.52 Gbps, and a 400 Gbps network link:
- The ACK service time grows, with the backlog the receiver reports in each ACK.
- The receiver penalty starts at 25% of the pool,
ReceiverCongestionLowThreshold, and slows the receiver’s senders (Receiver penalty). - PFC pauses the rack switch at 80% of the pool, if PFC is enabled for the class.
- Packets are dropped when the pool cannot hold them.
Worked example: the default configuration
A NIC with every attribute at its default: link rate 400 Gbps,Delay 100 ns, RdmaDataMSS 4096.
Egress pools.
Packet counts are full data packets of 4,184 bytes, rounded down. The receiver penalty reads only the data class’s pool. PFC is enabled on no class by default; the Xoff and Xon columns apply once
PFCEnableVector enables a class.
Pause time, for a pause the NIC requests with the default QuantaValue:
Records
These records show the buffers and PFC (Records):nd-stats and ecn-pfc-stats are written by default. NDStatsReportInterval and NetworkBufferStatsReportingInterval (each a double in seconds, default 0.001) and EnableNetworkBufferStatsReporting (a bool, default false) are global simulation parameters, not attributes of this model. They sit in the configuration’s SimulationParameters and are changed with PATCH /api/v1/configurations/{config_id}/parameters.