Skip to content
VLSI Mentor

Ethernet · Module 23

Ethernet for AI Training Clusters

A collective runs at its slowest member, so the same 32 flows deliver 63.9% as independent traffic and 28.4% as a ring all-reduce — and one uplink stalls 1 024 accelerators.

Chapter 23.1 §10 derived that 32 large flows over 32 equal-cost paths reach 28.4% of an uplink group. A collective is the traffic pattern that makes that number binding rather than pessimistic.

The same 32 flows on the same 32 uplinksAggregate delivered
independent greedy flows — each takes what it can63.9% of the 3.2 Tb/s group
a collective — every participant held to the same rate28.4%
ratio2.25×

Row one is the model most people carry and row two is the one Chapter 23.1 §10 actually computed. Independent flows fill whatever uplink they land on, so the group delivers 100 Gb/s times the number of non-empty uplinks — and with 32 balls in 32 bins, 20.4 of the 32 uplinks are non-empty. A collective cannot do that: a participant that finishes early has nowhere to go, because the next step needs the data the slowest participant has not sent yet.

A collective's bandwidth is the minimum over its members, not the sum and not the mean. Chapter 23.1's figure was already the minimum-based one — which makes it exactly right here and conservative everywhere else.

The cost, on a real job.

A 1 024-accelerator ring all-reduce, 8 GB of gradientsValue
bytes moved per participant — 2(N−1)/N × S15.984 GB
at 100 Gb/s with no contention1.279 s
at ECMP's 28.4%4.503 s
slowdown3.52×
accelerator-seconds wasted per all-reduce3 301
over a 100 000-step run89.6 hours, 91 705 accelerator-hours

And the mechanism is a single uplink.

Flows on the busiest uplinkSlowest ring linkAll-reduceAccelerators stalled
1100.0 Gb/s1.279 s
250.0 Gb/s2.558 s1 024
425.0 Gb/s5.115 s1 024
616.7 Gb/s7.672 s1 024

One uplink out of 32 on one leaf out of 32 sets the pace of every accelerator in the job. That is what "bound to the minimum" means in hardware, and Section 8 is the derivation.

Then the result that decides the architecture, and it is not the one people expect.

CollectiveFlows leaving one leafECMP efficiency
ring all-reduce32 — one per accelerator28.4%
all-to-all32 736 — 32 × (N−1)93.8%

All-to-all is ECMP's best case and the ring is its worst, on the same fabric, in the same job, for the same reason: the ring has N flows and the all-to-all has N². Section 10 is the arithmetic and Section 12 answers which of Chapter 23.1 §10's four remedies survives.


1. Scope — The Adversarial Case for Chapter 23.1's Arithmetic

Scope: what a collective communication pattern is, why it converts a statistical inefficiency into a hard bound, and what a fabric has to add.

Not in scope: training itself. What the accelerators compute between collectives is a different discipline. This chapter treats a training step as an alternating sequence — compute, then a collective — and asks only what the collective costs.

Three facts define the whole chapter.

FactConsequence
1a collective's rate is its slowest member'sthe mean is the wrong statistic
2a ring all-reduce has N flows, not N²it is ECMP's worst case by construction
3the flows are long-lived and start togethera bad hash persists for the whole job

Fact 3 is the one that makes it unbearable rather than merely inefficient. Chapter 23.1 §9 established that ECMP pins a flow to a path for the flow's life, because re-hashing reorders it. A collective's flows live for the duration of the training run — hours or weeks — so the assignment drawn in the first millisecond is the assignment for the whole job. Two ring links that collided at start-up collide for eighty-nine hours.

And one thing this chapter is careful not to claim.

Scheduling is not the answer. Chapter 22.2 priced a time-aware schedule at 19.48% of the link for a two-window gate list, and showed that per-stream separation makes the gate-control list as long as the stream count. At 1 024 endpoints that is 131 072 flops of schedule state per port with the shadow copy — 9.3 times Chapter 19.7 §19's entire MAC receive datapath — and it would have to be recomputed whenever a job's participant set changed. Section 14 rules it out with that arithmetic rather than with an opinion.

The chapter's arc.

QuestionSection
1why is the minimum the right statistic?4 and 6
2how bad is it, on a real job?8
3why is all-to-all the opposite case?10
4which remedy survives, and what does it cost?12, 14 and 17

2. What a Collective Is, and Why It Is Not Traffic

A collective is a communication pattern with a completion condition that every participant shares. That is the whole of it, and everything else follows.

The four that matter, with their flow counts.

CollectiveWhat it computesFlowsBytes per participant
broadcastone participant's data to allN − 1, or a treeS
all-reduce (ring)a sum over all, delivered to allN2(N−1)/N × S
all-gatherevery participant's data to allN(N−1)/N × S
all-to-alla distinct message from each to eachN(N−1)(N−1)/N × S

Column three is the chapter. A ring all-reduce over 1 024 participants produces 1 024 flows; an all-to-all over the same 1 024 produces 1 047 552. Those two numbers sit at opposite ends of Chapter 23.1 §10's efficiency curve, and they are produced by the same job on the same hardware in the same second.

Column four is worth deriving once, because the factor of two surprises people.

A ring all-reduce is two passes. The first — reduce-scatter — moves each participant's chunk around the ring, accumulating; after N−1 steps every participant holds the final value of one chunk. The second — all-gather — circulates those final chunks; after another N−1 steps everyone holds all of them.

steps = 2(N − 1), bytes per step = S / N, total = 2(N−1)/N × S

At N = 1 024 and S = 8 GB that is 15.984 GB per participant, which is almost exactly 2S — and independent of N, which is the property that makes a ring the standard choice. Doubling the participant count does not increase the bytes each one moves.

What it does increase is the number of links in the chain.

Ring of 1 024
steps2 046
links in the ring1 024
a step completes whenevery one of the 1 024 links has delivered
the ring's ratethe slowest link's

Row four is the fact this chapter exists to price, and it is a property of the algorithm, not of the fabric. A ring is a chain; a chain's throughput is its weakest link's, and nothing the fabric does changes that.

And the traffic's shape is nothing like ordinary data-centre traffic.

General trafficA collective
flow countlarge and varyingfixed by the algorithm
flow sizeheavy-tailed, mostly smalluniform and enormous
start timesindependentsimultaneous, every step
durationmilliseconds to secondsthe whole job
a slow flowone user waitsevery participant waits
spare capacity elsewhereusable by other flowsusable by nobody

Every row moves in the direction that makes ECMP worse, which is why a fabric built and measured on general traffic disappoints on a training job without anything having changed about the fabric.


3. RTL 1 — The Collective Package and the Participant Set

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// collective_pkg -- a collective as arithmetic over a participant set.
//
// The one constant that matters is not a rate or a size: it is the
// REDUCTION OPERATOR used to combine the participants' rates. Every
// other chapter in this track has summed or averaged. A collective
// takes the MINIMUM, and Section 20's refused property is what happens
// when a design forgets that.
// ---------------------------------------------------------------------
package collective_pkg;

  // ---- the fabric, from Chapter 23.1 -----------------------------------
  localparam int UPLINKS_PER_LEAF = 32;
  localparam int RATE_GBPS        = 100;
  localparam int LEAVES           = 32;      // leaves this job spans
  localparam int ACC_PER_LEAF     = 32;
  localparam int PARTICIPANTS     = LEAVES * ACC_PER_LEAF;   // 1024

  // ---- the collective ---------------------------------------------------
  typedef enum logic [2:0] {
    COLL_BROADCAST  = 3'd0,
    COLL_ALLREDUCE  = 3'd1,   // ring: N flows -- ECMP's worst case
    COLL_ALLGATHER  = 3'd2,
    COLL_ALLTOALL   = 3'd3,   // N(N-1) flows -- ECMP's best case
    COLL_REDUCE     = 3'd4
  } collective_e;

  // Flows leaving ONE leaf, which is what the uplink group sees.
  function automatic int flows_per_leaf(collective_e c);
    case (c)
      COLL_ALLREDUCE, COLL_ALLGATHER: return ACC_PER_LEAF;
      COLL_ALLTOALL:                  return ACC_PER_LEAF * (PARTICIPANTS - 1);
      default:                        return ACC_PER_LEAF;
    endcase
  endfunction

  // Ring all-reduce: 2(N-1) steps of S/N bytes -- total is independent
  // of N, which is why a ring is the default choice.
  function automatic int bytes_per_participant_mb(collective_e c, int s_mb);
    case (c)
      COLL_ALLREDUCE: return (2 * (PARTICIPANTS - 1) * s_mb) / PARTICIPANTS;
      COLL_ALLGATHER,
      COLL_ALLTOALL:  return ((PARTICIPANTS - 1) * s_mb) / PARTICIPANTS;
      default:        return s_mb;
    endcase
  endfunction

  // ---- THE reduction operator ------------------------------------------
  // Every other chapter combined per-port quantities with a sum or a
  // mean. A collective does not get to.
  typedef enum logic [1:0] {
    RED_SUM  = 2'd0,   // wrong for a collective
    RED_MEAN = 2'd1,   // wrong for a collective
    RED_MIN  = 2'd2    // the only correct one
  } rate_reduction_e;

  localparam rate_reduction_e COLLECTIVE_REDUCTION = RED_MIN;

  // Chapter 23.1 Section 10's efficiency curve, as a lookup rather than a
  // simulation. Indexed by log2 of flows-per-uplink, values in per cent.
  //   1 flow/uplink -> 28.4 ; 2 -> 37.0 ; 4 -> 46.2 ; 8 -> 55.9
  //   20 -> 67.2 ; 100 -> 82.4
  localparam int EFF_PCT [6] = '{28, 37, 46, 56, 67, 82};

  function automatic int efficiency_pct(int flows, int uplinks);
    automatic int fpu = flows / uplinks;
    if (fpu <= 1)   return EFF_PCT[0];
    if (fpu <= 2)   return EFF_PCT[1];
    if (fpu <= 4)   return EFF_PCT[2];
    if (fpu <= 8)   return EFF_PCT[3];
    if (fpu <= 20)  return EFF_PCT[4];
    return EFF_PCT[5];
  endfunction

  // Section 12's remedies, as an enumeration, so a design has to name
  // which one it is relying on.
  typedef enum logic [2:0] {
    FIX_NONE         = 3'd0,
    FIX_STRIPE       = 3'd1,   // more, smaller flows
    FIX_FLOWLET      = 3'd2,   // re-hash at a gap
    FIX_LOAD_AWARE   = 3'd3,   // choose the least-loaded uplink
    FIX_SPRAY        = 3'd4,   // per-packet, plus a reorder buffer
    FIX_HIERARCHICAL = 3'd5    // reduce inside the leaf first
  } fabric_fix_e;

endpackage

Classification: a parameter package whose central declaration is a reduction operator rather than a number.

What it teaches: that COLLECTIVE_REDUCTION = RED_MIN is the only line in the package that is not arithmetic. Every earlier chapter in Modules 19 to 23 combined per-port quantities by summingChapter 19.7's counters, Chapter 23.1's bisection — or by averaging. A collective does not get to choose: its completion condition is that every participant has finished, so its rate is the minimum and every sum in the track's habits is now the wrong operator.

And it teaches that flows_per_leaf differs between collectives by a factor of a thousand. A ring all-reduce and an all-gather each produce one flow per accelerator — 32 per leaf. An all-to-all produces 32 × 1 023 = 32 736. Those land at opposite ends of Chapter 23.1 §10's curve, and a job runs both.

Deliberately simplified: EFF_PCT is a six-entry lookup where the underlying quantity is a smooth function of flows per uplink; the table rounds to whole per cent and the function is not linear between entries. bytes_per_participant_mb uses integer megabytes, so an 8 GB buffer is 8 192 and the division by 1 024 is exact by luck rather than by design. PARTICIPANTS is a compile-time constant where a real job's participant set changes when a node fails and the job restarts on a different set. And there is no notion of the ring's ordering — which accelerator is whose neighbour — even though Section 17 shows that ordering is the single most valuable thing a job scheduler controls.

Production implication: the missing ring ordering is the cheapest large win available and it belongs to the job scheduler rather than the fabric. A ring whose consecutive participants are on the same leaf never crosses the fabric for most of its links; a ring assigned in arbitrary order crosses it for all of them. With 32 accelerators per leaf and a ring ordered leaf-by-leaf, 31 of every 32 ring links stay inside a leaf switch and only 1 crosses the fabric — which is Section 17's hierarchical result arriving as a placement decision rather than an algorithm change. Give the scheduler the topology, and the fabric's hardest problem stops being posed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// slowest_member_tracker -- combine per-participant rates with the
// correct operator, and report how wrong the usual one would be.
//
// The block exists to make the gap between the mean and the minimum a
// measured quantity rather than an argument.
// ---------------------------------------------------------------------
module slowest_member_tracker
  import collective_pkg::*;
#(
  parameter int MEMBERS = 32        // ring links leaving one leaf
)(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        sample,
  input  logic [15:0] member_gbps [MEMBERS],

  output logic [15:0] min_gbps,
  output logic [15:0] mean_gbps,
  output logic [15:0] max_gbps,
  output logic [15:0] collective_gbps,      // = min, always
  output logic [7:0]  slowest_member,
  output logic [15:0] mean_over_min_x10,
  output logic        mean_would_overstate,
  output logic [31:0] c_samples,
  output logic [31:0] c_slowest_unchanged
);
  logic [31:0] total;
  logic [7:0]  prev_slowest;

  always_comb begin
    min_gbps       = 16'hFFFF;
    max_gbps       = 16'd0;
    total          = 32'd0;
    slowest_member = 8'd0;
    for (int i = 0; i < MEMBERS; i++) begin
      total = total + 32'(member_gbps[i]);
      if (member_gbps[i] < min_gbps) begin
        min_gbps       = member_gbps[i];
        slowest_member = 8'(i);
      end
      if (member_gbps[i] > max_gbps) max_gbps = member_gbps[i];
    end
    mean_gbps = 16'(total / MEMBERS);

    // The collective's rate is the minimum. Not the sum, not the mean.
    collective_gbps = min_gbps;

    mean_over_min_x10 = (min_gbps == 0) ? 16'hFFFF
                      : (mean_gbps * 16'd10) / min_gbps;
    mean_would_overstate = (mean_gbps > min_gbps);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_samples <= '0; c_slowest_unchanged <= '0; prev_slowest <= 8'd0;
    end else if (sample) begin
      c_samples <= c_samples + 32'd1;
      // Section 1's fact 3: an ECMP assignment is pinned for the flow's
      // life, so the SAME member is slowest for the whole job. A
      // stable index is the signature of a persistent collision.
      if (slowest_member == prev_slowest)
        c_slowest_unchanged <= c_slowest_unchanged + 32'd1;
      else
        c_slowest_unchanged <= '0;
      prev_slowest <= slowest_member;
    end
  end
endmodule

Classification: a reduction with three operators computed and only one used, so the difference is reportable.

What it teaches: that mean_over_min_x10 is the number to publish and it is never 1.0. With 32 ring links whose rates are set by ECMP, the mean is 28.4 Gb/s of usable share per link on average and the minimum is what the job runs at — and quoting the mean overstates the job's rate by the imbalance factor, which Chapter 23.1 §10 put at 3.52× for 32 flows on 32 paths.

And it teaches that c_slowest_unchanged is the diagnostic that distinguishes the two failure modes. A collective that is slow because of transient congestion has a slowest member that moves between samples. One that is slow because of a pinned hash collision has the same index, sample after sample, for hours — which is Chapter 23.1 §9's stickiness made visible in one counter.

Deliberately simplified: member_gbps is an instantaneous rate where a collective's steps are discrete and the useful quantity is step completion time, not bandwidth. The combinational loop over 32 members is a 32-way comparator tree, fine at this scale and not at 1 024. c_slowest_unchanged resets to zero on any change, so a slowest member that alternates between two indices reads as unstable when it is in fact two pinned collisions. And the block sees only one leaf's members — the job's true minimum is over all 1 024, which no leaf can compute.

Production implication: that last limitation is the one that decides where this block belongs. The collective's rate is the minimum over 1 024 participants and every leaf sees 32 of them, so a per-leaf minimum is a local minimum and can be far above the global one. Reporting it as the collective's rate is precisely Chapter 22.2 §20's twelfth taxonomy group — a guarantee that is a relation between nodes, evaluated at one. The global minimum has to be reduced across leaves, which is itself a small all-reduce, and the honest implementation runs it in the job's control plane at a rate of a few hertz rather than trying to compute it in a switch.


4. The Minimum, Not the Mean: Two Models of the Same 32 Flows

Thirty two large flows are hashed onto thirty two equal cost uplinks, every link healthy and every table correct. Under the first model the flows are independent and greedy: each takes whatever its uplink will give, an uplink with any flows on it delivers a hundred gigabits per second, and an uplink with none delivers nothing. Thirty two balls in thirty two bins leaves twenty point four four bins non empty, so the group delivers two thousand and forty four gigabits per second of its three thousand two hundred, which is sixty three point nine per cent. Under the second model the flows belong to a collective, and every participant is held to the same rate because a participant that finishes a step early cannot start the next one until the slowest has sent its data. All thirty two flows therefore run at a hundred divided by the busiest uplink's flow count, which averages three point five two, giving twenty eight point four gigabits per second each and nine hundred and nine gigabits per second for the group, which is twenty eight point four per cent. The difference is a factor of two point two five on identical hardware in the same instant, and it exists because the collective cannot reach the spare capacity sitting on the empty uplinks. Chapter twenty three point one's published efficiency figure was computed under the equal rate assumption, which makes it exactly right for a collective and conservative for everything else.32 large flows32 equal-cost uplinksECMP hashuniform, independent20.44 non-emptyof 32 uplinksBusiest: 3.52flowsthe pacing uplinkIndependent:63.9%2 044 of 3 200 Gb/sCollective: 28.4%909 of 3 200 Gb/s2.25× apartsame flows, sameinstant12
Figure 1 — the same 32 flows on the same 32 uplinks, and a factor of 2.25 between two models.

Take Chapter 23.1 §10's exact set-up — 32 large flows, 32 equal-cost uplinks, every link healthy — and run it under two models.

Model A — independent greedy flows. Each flow takes whatever its uplink will give. An uplink with k flows delivers 100 Gb/s regardless of k; an uplink with zero flows delivers nothing. So the group's aggregate is 100 Gb/s times the number of non-empty uplinks.

non-empty uplinks = n(1 − (1 − 1/n)^m)

Value
32 balls in 32 bins20.44 non-empty, analytically 20.42
aggregate delivered2 044 Gb/s of 3 200
efficiency63.9%

Model B — a collective. Every participant is held to the same rate, because a participant that finishes its step early cannot start the next one: the next step needs data the slowest participant has not sent. So all 32 flows run at rate r, the busiest uplink carries max(k) × r ≤ 100, and:

r = 100 ÷ max(k), aggregate = 32 × r

Value
mean of the busiest bin3.52
per-flow rate28.4 Gb/s
aggregate delivered909 Gb/s of 3 200
efficiency28.4%

The same flows, the same fabric, the same instant — and a factor of 2.25 between the two answers.

Chapter 23.1's 28.4% was computed under the equal-rate assumption. That assumption is conservative for ordinary traffic and exactly right for a collective, which is why this chapter can use that chapter's table unmodified.

Three consequences, and the third is the one that changes how a fabric is measured.

One — spare capacity is unusable. Model A's 63.9% comes from uplinks running full while others run empty. In a collective the empty ones stay empty, because the flow that would have used them is pinned elsewhere and the flows that could use them are all throttled to the minimum. The fabric has 2 291 Gb/s of idle uplink capacity and nothing that can reach it.

Two — the mean is not merely optimistic, it is uninformative. Averaged over the 32 ring links, the per-link share is 100 Gb/s; the per-link rate is 28.4. No participant experiences the mean and no participant experiences anything above the minimum.

Three — and improving one participant can make the collective slower. If a fabric change speeds up 31 links and slows one, the mean rises and the collective's rate falls. That is not a hypothetical: a load-balancing scheme that reassigns flows to even out utilisation can move a flow onto the busiest uplink, improving the aggregate and lengthening every step. Section 20's refused property is exactly this observation written as an assertion.

One calibration, so the two models are not confused later.

Model A: independentModel B: collective
what a fast flow does with spare capacityuses itcannot — it waits
the group's aggregate63.9%28.4%
the right statisticthe sumthe minimum
which one a fabric benchmark measuresusually A
which one a training job experiencesB

Row four against row five is why a fabric that benchmarks well can disappoint on a training job without anything having changed. The benchmark ran independent streams; the job runs a chain.


5. RTL 2 — The Ring All-Reduce Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ring_allreduce_model -- 2(N-1) steps, S/N bytes each, and a step that
// completes only when its slowest link has.
//
// The block's purpose is to expose the two quantities a collective
// cares about and general traffic does not: the step's completion time,
// and which link set it.
// ---------------------------------------------------------------------
module ring_allreduce_model
  import collective_pkg::*;
#(
  parameter int MEMBERS   = 32,        // ring links leaving this leaf
  parameter int S_MB      = 8192       // gradient buffer, megabytes
)(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        start,
  input  logic [15:0] link_gbps [MEMBERS],

  output logic [31:0] step_bytes_kb,
  output logic [31:0] steps_total,
  output logic [31:0] step_time_us,
  output logic [31:0] collective_time_ms,
  output logic [31:0] ideal_time_ms,
  output logic [15:0] slowdown_x100,
  output logic [7:0]  pacing_member,
  output logic        running,
  output logic [31:0] c_steps_done
);
  localparam int N = PARTICIPANTS;

  logic [15:0] slowest;

  always_comb begin
    // 2(N-1) steps of S/N bytes. The total is 2(N-1)/N x S and is
    // independent of N, which is the ring's defining property.
    steps_total   = 32'(2 * (N - 1));
    step_bytes_kb = 32'((S_MB * 1024) / N);

    slowest = 16'hFFFF;
    pacing_member = 8'd0;
    for (int i = 0; i < MEMBERS; i++)
      if (link_gbps[i] < slowest) begin
        slowest = link_gbps[i]; pacing_member = 8'(i);
      end

    // A step completes when the SLOWEST link has delivered its chunk.
    // kB x 8 / (Gb/s) gives microseconds directly.
    step_time_us = (slowest == 0) ? 32'hFFFF_FFFF
                 : (step_bytes_kb * 8) / 32'(slowest);

    collective_time_ms = (step_time_us * steps_total) / 1000;
    ideal_time_ms      = ((step_bytes_kb * 8) / 32'(RATE_GBPS))
                         * steps_total / 1000;

    slowdown_x100 = (ideal_time_ms == 0) ? 16'd100
                  : 16'((collective_time_ms * 100) / ideal_time_ms);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)      begin running <= 1'b0; c_steps_done <= '0; end
    else if (start)  begin running <= 1'b1; c_steps_done <= '0; end
    else if (running) begin
      c_steps_done <= c_steps_done + 32'd1;
      if (c_steps_done + 1 >= steps_total) running <= 1'b0;
    end
  end
endmodule

Classification: a step-time model whose only interesting input is a minimum.

What it teaches: that step_time_us divides by slowest and never by the mean, and that this single choice is the difference between a model that predicts a training job and one that does not. Every link in the ring transfers the same S/N bytes in every step; they all start together and the step is not over until the last one finishes. The 31 fast links sit idle waiting, which is Section 4's model B in one expression.

And it teaches that steps_total is 2(N−1) while step_bytes_kb is S/N, so their product is independent of N. A ring over 1 024 participants and a ring over 64 move the same bytes per participant — 15.984 GB against 15.750 for S = 8 GB — which is why the ring is chosen and why its flow count, not its byte count, is what hurts.

Deliberately simplified: the model assumes every link's rate is constant for the whole collective, which Chapter 23.1 §9's stickiness makes nearly true and which a congestion controller makes only approximately so. There is no propagation or per-step synchronisation overhead, which at 2 046 steps and a three-hop 1.635 µs fabric latency adds 3.3 ms — small against 4.5 seconds and not zero. MEMBERS is the links leaving one leaf, not the ring's 1 024, so pacing_member is a local index. And S_MB is fixed, where real training overlaps a collective with the backward pass and issues it in buckets rather than as one transfer.

Production implication: bucketing is the optimisation that makes the model's headline number soft, and it is worth being precise about what it does and does not fix. Splitting an 8 GB gradient into 32 buckets of 256 MB lets each bucket's all-reduce start as soon as its layer's gradients are ready, overlapping communication with the backward pass — so the exposed time is the part not hidden behind compute, not the whole 4.5 seconds. What bucketing does not change is the rate: each bucket still runs at the ring's slowest link, so a 3.52× slowdown on a collective that was 40% exposed becomes a 3.52× slowdown on 40% of the step. It reduces the damage and does not address the cause.


Section 5's model divides by the minimum. This section is why no amount of fabric cleverness changes that.

The ring's completion condition, stated exactly.

Step s of a ring all-reduce cannot begin at any participant until step s−1 has completed at every participant — because the chunk a participant reduces in step s is the chunk its predecessor sent in step s−1.

That is a property of the algorithm. It is true on a perfect fabric, on an imperfect one, and on no fabric at all. The ring is a data dependency, and a data dependency does not negotiate.

Three things follow, in increasing order of how much they hurt.

One — the fast participants idle, and the idle time is exactly the imbalance.

Ring linkRateStep time for a 7.8125 MB chunkIdle waiting
alone on its uplink100.0 Gb/s0.625 ms1.576 ms
sharing with one other50.0 Gb/s1.250 ms0.951 ms
on the busiest uplink28.4 Gb/s2.201 ms0

Every step, 31 links wait for one. Over 2 046 steps, 1.576 ms of idling per step is 3.22 seconds — which is precisely Section 8's lost time, and it is spent by hardware that is otherwise perfectly healthy.

Two — the ring's rate is the minimum over all 1 024 links, not over one leaf's 32. Each leaf's 32 outbound ring links have their own local minimum; the job's rate is the minimum of the 32 local minima, which is lower again. That is the difference between a per-leaf measurement and a job-level one, and it is why Section 3's tracker is explicit that its minimum is local.

Value
minimum over one leaf's 32 links28.4 Gb/s
minimum over all 32 leaves' minimalower, and set by the unluckiest leaf
which one a switch can computethe first
which one the job runs atthe second

Three — and the ring cannot route around the slow link. A general network recovers from a congested path by moving traffic elsewhere; a ring link connects two specific participants and there is no other pair of participants that can do its work. Chapter 23.1 §5's 32 equal-cost paths are equal-cost for that ring link, and moving it is Chapter 23.1 §9's re-hash, which reorders.

The comparison that makes it concrete.

General traffic on a congested pathA ring link on a congested path
who is affectedthat flow's userevery participant
can the load move?yes, to another flow's pathno — the pair is fixed
does spare capacity help?yesno — everyone is throttled
how long does it last?the flow's lifethe job's life

Row four is the one that turns an inefficiency into an outage-shaped event. Chapter 23.1 §9 established that a flow's path is pinned for the flow's life; a collective's flows live as long as the job, so an unlucky draw in the first millisecond of a two-week run is an unlucky draw for two weeks.


7. RTL 3 — The Stall Accountant

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// stall_accountant -- convert an imbalance into the currency a training
// programme actually budgets in: accelerator-seconds.
//
// Section 6's idle time is not a fabric metric. It is hardware that
// was bought, powered and scheduled, waiting. This block prices it.
// ---------------------------------------------------------------------
module stall_accountant
  import collective_pkg::*;
#(
  parameter int ACCELERATORS = PARTICIPANTS     // 1024
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [31:0] ideal_time_ms,
  input  logic [31:0] actual_time_ms,
  input  logic [31:0] steps_per_run,
  input  logic        collective_done,

  output logic [31:0] lost_ms_per_collective,
  output logic [31:0] accel_seconds_lost,
  output logic [31:0] accel_hours_per_run,
  output logic [31:0] wall_hours_per_run,
  output logic [15:0] slowdown_x100,
  output logic [15:0] exposed_pct,
  output logic [31:0] c_collectives,
  output logic [31:0] c_accel_seconds_total
);
  always_comb begin
    lost_ms_per_collective = (actual_time_ms > ideal_time_ms)
                           ? (actual_time_ms - ideal_time_ms) : 32'd0;

    // The conversion that makes the number arguable in a budget
    // meeting: every accelerator in the job waits for the whole of it.
    accel_seconds_lost = (lost_ms_per_collective * 32'(ACCELERATORS))
                         / 1000;

    wall_hours_per_run = (lost_ms_per_collective * steps_per_run)
                         / (1000 * 3600);
    accel_hours_per_run = wall_hours_per_run * 32'(ACCELERATORS);

    slowdown_x100 = (ideal_time_ms == 0) ? 16'd100
                  : 16'((actual_time_ms * 100) / ideal_time_ms);

    // Bucketing hides part of the collective behind the backward pass.
    // What it hides is TIME, not RATE -- Section 5's production note.
    exposed_pct = 16'd100;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_collectives <= '0; c_accel_seconds_total <= '0;
    end else if (collective_done) begin
      c_collectives         <= c_collectives + 32'd1;
      c_accel_seconds_total <= c_accel_seconds_total + accel_seconds_lost;
    end
  end
endmodule

Classification: a unit conversion, and the unit is the point.

What it teaches: that accel_seconds_lost is the only figure in this chapter that a programme will act on. A fabric engineer reports 28.4% ECMP efficiency; nobody outside the network team knows what that costs. The same fact expressed as 3 301 accelerator-seconds per all-reduce, 91 705 accelerator-hours over a 100 000-step run is a number with a price attached, and it is the same measurement multiplied by a constant.

And it teaches that exposed_pct is hard-wired to 100 and should not be. A real training step overlaps the collective with the backward pass by issuing it in buckets; only the unhidden part is lost. The block leaves the field at 100 deliberately, as an honest worst case, and a design that fills it in correctly reduces every number below it proportionally without touching the slowdown.

Deliberately simplified: all arithmetic is integer milliseconds, so a collective under a millisecond reads as zero. ACCELERATORS is the whole job where a failure that affects one leaf still stalls the whole job — correct here, and wrong for a job partitioned into independent sub-jobs. steps_per_run is an input with no notion of a run that is stopped early, which most are. And c_accel_seconds_total is a 32-bit accumulator that at 3 301 per collective wraps after 1.3 million collectives, which a long run reaches.

Production implication: the 32-bit wrap is real at this scale and it is worth choosing the width deliberately rather than by habit. A 100 000-step run accumulates 3.3 × 10⁸ accelerator-seconds of loss, which fits; a fleet-level accumulator across a thousand runs does not. The general rule the track has arrived at repeatedly — Chapter 19.7 §8's saturating counters, Chapter 22.1 §18's 64-second wrap — applies here with a new twist: the quantity being accumulated is a product of two large numbers, so its width is the sum of their widths and not either one's.


A ring all reduce over one thousand and twenty four accelerators moves fifteen point nine eight four gigabytes per participant in two thousand and forty six steps, which at a hundred gigabits per second with no contention takes one point two seven nine seconds. Each of the thirty two leaves sends thirty two ring flows and equal cost multipath hashes them across its thirty two uplinks, so the busiest uplink carries k of them and each flow on it receives a hundred divided by k gigabits per second. Because a ring is a chain and a step is not complete until every link has delivered, the whole ring runs at that rate and the slowdown is exactly k: two flows give fifty gigabits and two point five five eight seconds, three give thirty three point three and three point eight three six, four give twenty five and five point one one five, six give sixteen point seven and seven point six seven two. Every one of the thousand and twenty four accelerators waits for the whole of it. The distribution of the busiest bin decides how often this happens: the probability that it is at most two is two point nine per cent, that it is three is fifty one point five, that it is four is thirty six point three, that it is five is seven point eight and that it is six or more is one point four, so eighty seven point eight per cent of randomly assigned rings run at a third or a quarter of line rate. The perfect assignment of one flow per uplink has probability one point eight times ten to the minus thirteen. The cost over a hundred thousand step run is eighty nine point six hours of wall clock and ninety one thousand seven hundred and five accelerator hours, on a fabric that is seventy one point six per cent idle throughout.1 024-member ring2 046 steps, 15.984 GBeachk = 1100 Gb/s — p = 1.8e-13k = 333.3 Gb/s — p = 51.5%k = 425.0 Gb/s — p = 36.3%Slowdown = kexactly, notapproximately1 024 stalledevery one waits91 705accel-hoursover 100 000 stepsFabric 71.6% idle73.3 Tb/s unreachable12
Figure 2 — the slowdown is exactly k, and k is 3 or 4 in 87.8% of randomly assigned rings.

The chapter's central quantitative claim, derived rather than asserted.

Set-up. A 1 024-accelerator ring all-reduce, 32 accelerators on each of 32 leaves, 32 uplinks per leaf at 100 Gb/s, an 8 GB gradient buffer. Every link in the fabric is up and every forwarding table is correct.

Each leaf sends 32 ring flows, ECMP hashes them across its 32 uplinks, and the busiest uplink carries k of them.

k on the busiest uplinkSlowest ring linkAll-reduce timeSlowdownAccelerators stalled
1100.0 Gb/s1.279 s1.00×0
250.0 Gb/s2.558 s2.00×1 024
333.3 Gb/s3.836 s3.00×1 024
425.0 Gb/s5.115 s4.00×1 024
616.7 Gb/s7.672 s6.00×1 024

The slowdown is exactly k. Not approximately, not asymptotically — the ring runs at 100/k Gb/s and takes k times as long, because every other link is waiting for that one.

And k = 1 almost never happens. Thirty-two balls in thirty-two bins gives a busiest bin of 1 with probability 1.8 × 10⁻¹³; the expected maximum is 3.52, and the distribution is what matters:

Busiest binProbability
≤ 22.9%
351.5%
436.3%
57.8%
≥ 61.4%

So 87.8% of randomly assigned rings run at a third or a quarter of line rate, and the fabric reports every link healthy throughout.

The cost, expressed three ways.

Value
all-reduce at 28.4%4.503 s against 1.279 ideal
lost per all-reduce3.224 s
accelerator-seconds per all-reduce3 301
over 10 000 steps8.96 h wall clock, 9 170 accelerator-hours
over 100 000 steps89.6 h wall clock, 91 705 accelerator-hours

Eighty-nine hours of wall clock on a run that should have taken thirty-six — and the cause is that thirty-two flows were assigned to thirty-two paths by a hash.

Two framings that make the size of this clear.

First, against the fabric's own capacity. The job is using 909 Gb/s of a 3 200 Gb/s uplink group per leaf, so 2 291 Gb/s per leaf — 73.3 Tb/s across the 32 leaves — is idle, powered, and unreachable. That is Chapter 23.1 §19's gap arriving with a workload attached.

Second, against what the accelerators cost. A 3.52× slowdown on the communication phase of a job whose accelerators are the most expensive items in the building is not a networking problem that the network team owns; it is a capital-efficiency problem that happens to have a networking cause — and Section 7's accelerator-hours is the unit that makes that legible.

The fabric is not congested. It is 71.6% idle, and the job is running at 28.4% of line rate, and both statements are true at the same instant.


9. RTL 4 — The All-to-All Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// all_to_all_model -- the same job, the same fabric, the same second,
// and the opposite end of Chapter 23.1 Section 10's efficiency curve.
//
// A ring produces N flows. An all-to-all produces N(N-1). That single
// difference moves the uplink group from 28.4% to 93.8%, and nothing
// about the fabric changed.
// ---------------------------------------------------------------------
module all_to_all_model
  import collective_pkg::*;
#(
  parameter int S_MB = 1024          // per-participant dispatch volume
)(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        start,

  output logic [31:0] flows_per_leaf_o,
  output logic [31:0] flows_per_uplink,
  output logic [15:0] efficiency_pct,
  output logic [31:0] egress_mb,
  output logic [31:0] time_ms,
  output logic [31:0] ideal_ms,
  output logic [15:0] slowdown_x100,
  output logic        is_ecmp_best_case,
  output logic [31:0] ring_flows_same_job
);
  always_comb begin
    // 32 accelerators per leaf, each sending to N-1 peers.
    flows_per_leaf_o = 32'(ACC_PER_LEAF) * 32'(PARTICIPANTS - 1);
    flows_per_uplink = flows_per_leaf_o / 32'(UPLINKS_PER_LEAF);

    efficiency_pct = 16'(efficiency_pct_fn(int'(flows_per_leaf_o),
                                           UPLINKS_PER_LEAF));

    egress_mb = 32'((S_MB * (PARTICIPANTS - 1)) / PARTICIPANTS);

    ideal_ms = (egress_mb * 8 * 1000) / (32'(RATE_GBPS) * 1000);
    time_ms  = (efficiency_pct == 0) ? 32'hFFFF_FFFF
             : (ideal_ms * 100) / 32'(efficiency_pct);

    slowdown_x100 = (ideal_ms == 0) ? 16'd100
                  : 16'((time_ms * 100) / ideal_ms);

    // With a thousand flows per uplink the imbalance is small, which
    // is the whole point of the comparison.
    is_ecmp_best_case = (flows_per_uplink > 32'd100);

    // The SAME job's ring all-reduce, for contrast.
    ring_flows_same_job = 32'(ACC_PER_LEAF);
  end

  function automatic int efficiency_pct_fn(int flows, int uplinks);
    return efficiency_pct(flows, uplinks);
  endfunction
endmodule

Classification: the same fabric model with one input changed by a factor of a thousand.

What it teaches: that flows_per_leaf_o and ring_flows_same_job are outputs of the same block for a reason. A training job that uses expert parallelism runs both collectives: an all-to-all to dispatch tokens to experts, and a ring all-reduce over the gradients. They produce 32 736 and 32 flows per leaf respectively, land at 93.8% and 28.4% efficiency, and run on the same uplinks within the same training step. Any statement about "the fabric's efficiency for AI traffic" that does not say which collective is meaningless.

And it teaches why the all-to-all is comfortable. With 1 023 flows per uplink, the balls-in-bins imbalance is 1 091 on the busiest against a mean of 1 023 — a ratio of 1.067 — because the relative fluctuation of a sum of m independent assignments falls as 1/√m. The law of large numbers is the all-to-all's load balancer, and it is free.

Deliberately simplified: the model assumes all N(N−1) flows are active simultaneously, which for a real all-to-all is true within a step and not across one. efficiency_pct comes from the package's six-entry lookup, so 32 736 flows and 3 200 flows both return 82 — the true figure at 32 736 is 93.8%, and the lookup's top bucket is too coarse. egress_mb ignores the message the participant sends to itself, correctly. And there is no notion of incast: every participant receives from N−1 peers simultaneously, which is a receive-side problem the model does not have.

Production implication: incast is the all-to-all's real hazard and it is on the other side of the link from everything in this chapter. 1 023 peers each sending to one receiver at once is Chapter 14.1's congestion at its sharpest: the receiving leaf's downlink to that accelerator is a single 100 Gb/s port with 1 023 senders aimed at it, and the buffer that absorbs the overshoot is the switch's, not the fabric's. An all-to-all's efficiency on the uplink side is 93.8% and its failure mode is entirely on the downlink, which is why a fabric tuned for all-reduce and a fabric tuned for all-to-all are tuned in different places.


10. All-to-All Is ECMP's Best Case and the Ring Is Its Worst

A training job using expert parallelism runs two collectives in the same step on the same uplinks, and they sit at opposite ends of the fabric's efficiency curve. A ring all reduce produces one flow per accelerator, which is thirty two flows leaving each leaf and one flow per uplink; the busiest uplink carries three point five two times the mean and the group delivers twenty eight point four per cent. An all to all produces N times N minus one flows, which is thirty two times one thousand and twenty three, or thirty two thousand seven hundred and thirty six per leaf, and one thousand and twenty three per uplink; the busiest carries one thousand and ninety one against a mean of one thousand and twenty three, an imbalance of one point zero six seven, and the group delivers ninety three point eight per cent. The reason is that the relative fluctuation of a bin's count falls as one over the square root of the mean occupancy, so the closed form one plus the square root of two times the natural logarithm of the bin count divided by the mean occupancy tracks the simulation within about eight per cent across three decades. The design rule that follows is that an uplink group needs about two times the natural logarithm of its size, which is roughly seven flows per uplink or two hundred and twenty two per leaf at thirty two uplinks, and a ring is structurally seven times short of that. The two collectives also fail in different places: the ring fails on the leaf's uplink group through hash collision, while the all to all fails on the receiving leaf's downlink through incast from a thousand simultaneous senders, which is a buffer problem rather than a hashing one.One training stepboth collectivesRing all-reduce32 flows per leafAll-to-all32 736 flows per leafImbalance 3.52×1 flow per uplinkImbalance 1.067×1 023 per uplink28.4%ECMP's worst case93.8%ECMP's best caseDifferent failuresitesuplink hash vsdownlink incast12
Figure 3 — one job, two collectives, and a thousandfold difference in flow count.

Two collectives, one job, one fabric, and a factor of 3.3 in efficiency between them.

Ring all-reduceAll-to-all
flows leaving one leaf3232 736
flows per uplink1.01 023
mean of the busiest uplink3.521 091
imbalance ratio3.52×1.067×
ECMP efficiency28.4%93.8%

Row four explains rows one to five in one line. The imbalance of m balls in n bins is driven by the relative fluctuation of a bin's count, which scales as 1/√(m/n). At one flow per uplink that fluctuation is of the same size as the mean; at a thousand flows per uplink it is about three per cent.

imbalance ≈ 1 + √(2 ln n ÷ (m/n))

Flows per uplinkPredicted imbalanceSimulatedEfficiency
13.633.5228.4%
42.322.1646.2%
201.591.4967.2%
1001.261.2182.4%
1 0231.081.06793.8%

The closed form tracks the simulation within about 8% across three orders of magnitude, which is enough to use it as a design rule: an uplink group needs of order 2 ln n flows per uplink to be within a few per cent of balanced, and at n = 32 that is about seven — so 222 flows per leaf, against the ring's 32.

The ring is not unlucky. It is structurally seven times short of the flow count its uplink group needs.

Which reframes the problem usefully. The question is not why is ECMP bad at all-reduce — ECMP is behaving exactly as specified. The question is why a collective with N participants produces only N flows, and the answer is that a ring is a one-dimensional decomposition of an all-reduce. A different decomposition produces a different flow count, which is Section 12's first remedy and Section 17's better one.

And the two collectives fail in different places, which matters for tuning.

Ring all-reduceAll-to-all
where it hurtsthe leaf's uplink groupthe receiving leaf's downlink
the mechanismhash collision among few flowsincast from many senders
the resourceuplink bandwidthswitch buffer
the remedymore flows, or better assignmentcongestion control, or buffer
Chapter 23.1's model predicts ityes, §10no — it is a downlink problem

Row five is worth stating plainly. Chapter 23.1's whole analysis was about an uplink group and a hash; the all-to-all's difficulty is neither. A fabric optimised entirely on that chapter's arithmetic will handle all-reduce better and all-to-all no differently, because the two failures do not share a mechanism.


11. RTL 5 — The Striping Controller

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// striping_controller -- Chapter 23.1 Section 10's first remedy, applied to
// a ring link.
//
// Split each ring link into c connections. The ring link's bandwidth is
// then the SUM of its connections' fair shares, and the collective's
// rate is the minimum over ring links. Section 12 has the measured
// curve; this block is the mechanism and its ordering hazard.
// ---------------------------------------------------------------------
module striping_controller
  import collective_pkg::*;
#(
  parameter int MEMBERS = 32,     // ring links leaving this leaf
  parameter int STRIPES = 8       // connections per ring link
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        chunk_start,
  input  logic [31:0] chunk_bytes,
  input  logic [15:0] stripe_gbps [MEMBERS*STRIPES],

  output logic [31:0] stripe_bytes,
  output logic [15:0] member_gbps [MEMBERS],
  output logic [15:0] collective_gbps,
  output logic [7:0]  pacing_member,
  output logic        reassembly_required,
  output logic [31:0] reorder_depth_bytes,
  output logic [15:0] connections_total,
  output logic [31:0] c_chunks
);
  always_comb begin
    stripe_bytes      = chunk_bytes / STRIPES;
    connections_total = 16'(MEMBERS) * 16'(STRIPES);

    collective_gbps = 16'hFFFF;
    pacing_member   = 8'd0;
    for (int m = 0; m < MEMBERS; m++) begin
      automatic logic [31:0] acc = 32'd0;
      for (int s = 0; s < STRIPES; s++)
        acc = acc + 32'(stripe_gbps[m*STRIPES + s]);
      member_gbps[m] = 16'(acc);
      if (member_gbps[m] < collective_gbps) begin
        collective_gbps = member_gbps[m];
        pacing_member   = 8'(m);
      end
    end

    // The cost that is not bandwidth: a chunk split across STRIPES
    // independent paths arrives out of order, and the receiver must
    // hold the gap. This is NOT the same as Chapter 23.1 Section 9's
    // reordering hazard -- these are separate connections carrying
    // disjoint byte ranges, so nothing is reordered WITHIN a
    // connection. What is required is reassembly, not reordering.
    reassembly_required = (STRIPES > 1);
    // Worst case: STRIPES-1 stripes complete and one lags by the
    // fabric's path skew.
    reorder_depth_bytes = stripe_bytes * 32'(STRIPES - 1);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)           c_chunks <= '0;
    else if (chunk_start) c_chunks <= c_chunks + 32'd1;
  end
endmodule

Classification: a striping controller whose most important comment is that its hazard is not the one people expect.

What it teaches: that striping a ring link across c connections is not the reordering hazard Chapter 23.1 §9 warned about. That hazard was splitting a single flow's packets across paths, which delivers one byte stream out of order. Here each connection carries a disjoint byte range, so nothing is reordered within a connection — what the receiver must do is reassemble c independent, individually ordered streams into one buffer. That is a memory problem, not a transport one, and it is why striping is the cheapest of the remedies.

And it teaches that member_gbps is a sum while collective_gbps is a minimum, in the same block. The two reductions are correct at different levels: a ring link's bandwidth is the sum of its stripes because they run in parallel; the collective's rate is the minimum over ring links because they run in series. Getting either one wrong by habit is Section 20's refused class.

Deliberately simplified: stripe_gbps is an input where the actual share depends on how many other connections landed on the same uplink — the block consumes the result of the assignment rather than modelling it. The nested loop is MEMBERS × STRIPES = 256 additions combinationally, which is a model. reorder_depth_bytes assumes at most one lagging stripe where a real skew distributes across all of them. And there is no connection-establishment cost, which at 256 connections per leaf and a job that restarts on every failure is not free.

Production implication: the connection count is what limits this remedy in practice and it multiplies faster than people expect. Striping every ring link by 8 takes a 1 024-participant job from 1 024 connections to 8 192; striping by 32 takes it to 32 768. Each connection costs state in the NIC — a send queue, a receive queue, a congestion-control context, and a reassembly buffer — and a high-rate NIC's connection cache is finite. Exceeding it causes context thrashing, whose symptom is a throughput collapse that looks exactly like the fabric problem striping was deployed to fix. Size the stripe count against the NIC's cache, not against the efficiency curve, and measure the cache's hit rate before and after.


12. Which of Chapter 23.1's Four Remedies Survives

Chapter 23.1 §10 offered four. This section runs each against a 1 024-participant ring all-reduce and reports what it delivers.

Remedy 1 — more, smaller flows (striping). Measured, with each ring link split into c connections, the ring's rate being the minimum over ring links of the sum of their stripes' shares:

Connections per ring linkRing rateEfficiencyGain
129.6 Gb/s29.6%1.00×
242.4 Gb/s42.4%1.44×
461.0 Gb/s61.0%2.06×
877.1 Gb/s77.1%2.61×
1687.9 Gb/s87.9%2.97×
3293.8 Gb/s93.8%3.17×

It survives, and it is the best return per unit of effort in the chapter. Eight connections per ring link — a library setting — takes the all-reduce from 4.503 s to 1.659 s, recovering 88% of the gap. The cost is 8 192 connections for the job and the NIC cache that must hold them.

Remedy 2 — flowlet switching. Chapter 23.1 §9 described it as re-hashing after a gap long enough to prevent reordering. A ring all-reduce has 2 046 natural gaps — one per step — so every step is a flowlet boundary and the mechanism fires constantly.

WithoutWith
expected per-step slowdown3.52×3.52×
variance across stepszero — one draw for the whole joba fresh draw every step
variance across jobslargesmall
worst case for a given jobwhatever it drewthe average

It survives in a weakened and still valuable form. It does not improve the expected rate — each step's minimum is still set by that step's maximum — but it converts a systematic penalty into a statistical one. A job that drew k = 6 no longer runs at 16.7 Gb/s for two weeks; it runs at the average of many draws. For a fleet, that removes the tail of unlucky jobs, which is worth a great deal and is not a speed-up.

Remedy 3 — load-aware path selection. This is the one the collective case rewards most.

Value
flows to place32
uplinks32
greedy least-loaded assignmentexactly one flow per uplink
resulting efficiency100%
state requiredChapter 23.1 §19's per-path load array — 512 flops, 3.6% of a MAC datapath

It survives and it is transformative, for a reason specific to collectives: the flows are long-lived, equal-sized and start simultaneously, which is precisely the condition under which a greedy least-loaded assignment is optimal rather than merely better. General traffic — short, heavy-tailed, independently arriving — is the case where load-aware selection is hard to get right. A collective hands it the easy case.

Remedy 4 — larger link groups. Chapter 23.1 §10 already noted it does not remove the hash, and at this scale it is worse than neutral: doubling a leaf's uplinks from 32 to 64 halves the flows per uplink from 1.0 to 0.5, and Section 10's curve says the imbalance rises. It does not survive.

Summary, ranked by delivered rate.

RemedyRing rateWhere it livesVerdict
load-aware path selection100 Gb/sthe switchsurvives — transformative
hierarchical reduction (Section 17)100 Gb/sthe collective librarysurvives — and it is free
striping ×877.1 Gb/sthe collective librarysurvives — best effort/return
flowlet switching28.4 Gb/s meanthe switchsurvives as variance reduction only
larger link groupsworse than 28.4the fabric's wiringdoes not survive
time-aware schedulingChapter 22.2ruled out — Section 14

Rows one and two reach the same number by completely different routes, and row two costs nothing at all, which is why Section 17 argues it should be tried first.


13. RTL 6 — Load-Aware Path Selection

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// load_aware_selector -- choose the least-loaded uplink at flow start,
// instead of hashing.
//
// Chapter 23.1 Section 9's stickiness is PRESERVED: the choice is made once,
// at flow establishment, and never revisited. What changes is only HOW
// the choice is made, so nothing is reordered and the correctness
// argument is untouched.
// ---------------------------------------------------------------------
module load_aware_selector
  import collective_pkg::*;
#(
  parameter int PATHS = UPLINKS_PER_LEAF
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        flow_start,
  input  logic [31:0] flow_hash,          // the fallback
  input  logic [15:0] declared_gbps,      // what the flow will offer
  input  logic        flow_end,
  input  logic [7:0]  ending_path,
  input  logic        load_aware_enable,

  output logic [7:0]  chosen_path,
  output logic [15:0] path_load_gbps [PATHS],
  output logic [15:0] busiest_gbps,
  output logic [15:0] lightest_gbps,
  output logic [15:0] imbalance_x10,
  output logic        choice_was_hash,
  output logic [31:0] c_flows_placed,
  output logic [31:0] c_perfect_placements,
  output logic        sticky_preserved
);
  logic [7:0] lightest_idx;

  always_comb begin
    lightest_idx  = 8'd0;
    lightest_gbps = 16'hFFFF;
    busiest_gbps  = 16'd0;
    for (int i = 0; i < PATHS; i++) begin
      if (path_load_gbps[i] < lightest_gbps) begin
        lightest_gbps = path_load_gbps[i];
        lightest_idx  = 8'(i);
      end
      if (path_load_gbps[i] > busiest_gbps) busiest_gbps = path_load_gbps[i];
    end

    choice_was_hash = !load_aware_enable;
    chosen_path     = load_aware_enable ? lightest_idx
                                        : 8'(flow_hash % PATHS);

    imbalance_x10 = (lightest_gbps == 0) ? 16'hFFFF
                  : (busiest_gbps * 16'd10) / lightest_gbps;

    // The correctness claim, made checkable: the path is decided ONCE,
    // at flow start. Chapter 23.1 Section 9's ordering requirement is
    // untouched.
    sticky_preserved = 1'b1;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_flows_placed <= '0; c_perfect_placements <= '0;
      for (int i = 0; i < PATHS; i++) path_load_gbps[i] <= '0;
    end else begin
      if (flow_start) begin
        path_load_gbps[chosen_path] <= path_load_gbps[chosen_path]
                                     + declared_gbps;
        c_flows_placed <= c_flows_placed + 32'd1;
        // A placement onto an empty path is the outcome a hash cannot
        // guarantee and a greedy choice can, while flows <= paths.
        if (path_load_gbps[chosen_path] == 16'd0)
          c_perfect_placements <= c_perfect_placements + 32'd1;
      end
      if (flow_end)
        path_load_gbps[ending_path] <= (path_load_gbps[ending_path]
                                        > declared_gbps)
                                     ? path_load_gbps[ending_path] - declared_gbps
                                     : 16'd0;
    end
  end
endmodule

Classification: a greedy least-loaded assignment that changes the choice function and nothing else.

What it teaches: that sticky_preserved is the reason this remedy is deployable and flowlet switching is contentious. Chapter 23.1 §9 established that a flow's path is pinned because re-hashing reorders it. Load-aware selection still pins the flow — it simply decides where to pin it using load rather than a hash. No ordering property changes, so the entire correctness argument from that chapter carries over unmodified.

And it teaches that c_perfect_placements counts the outcome a hash structurally cannot produce. While the flow count is at most the path count, a greedy least-loaded assignment places every flow on an empty path — 32 flows, 32 uplinks, one each, 100% efficiency, deterministically. A uniform hash produces that outcome with probability 1.8 × 10⁻¹³.

Deliberately simplified: declared_gbps assumes a flow announces its offered rate, which a collective library can and general traffic cannot. The greedy choice is made on instantaneous load with no hysteresis, so two flows starting in the same cycle both see the same lightest path. path_load_gbps decrements on flow_end with a clamp, hiding an accounting bug rather than reporting it. And the whole scheme is per-leaf: two leaves independently choosing lightest uplinks can still converge on the same spine, which this block cannot see.

Production implication: the spine-side collision is the limitation that decides how far this remedy goes, and it is Chapter 22.2 §20's twelfth taxonomy group again. Each leaf's uplink choice is locally optimal and the spine's ingress load is a sum over 64 leaves' independent choices — so a fabric can be perfectly balanced on every leaf's uplink group and congested at a spine. The remedy is the same as it has been in three previous chapters: import a number. A spine that advertises its per-port load, at a few hertz, lets each leaf's greedy choice account for the far side, and the advertisement is a few bytes per spine per second. Without it, load-aware selection fixes the leaf's half of the path and leaves the other half to chance.


14. What a Fabric Must Add, and What It Must Not

Four candidate additions, priced.

Must not — a time-aware schedule. Chapter 22.2 built one and priced it precisely, and the arithmetic does not survive the move to a cluster.

A vehicle (Chapter 22.2)A 1 024-accelerator job
endpoints to separate≈50 control streams1 024 participants
gate-control entries per port501 024
with the shadow copy for atomic install6 400 flops131 072 flops
against Chapter 19.7 §19's datapath45.2%9.25×
express reservation19.48% of the link19.48% of the link
when the participant set changesa software update, vehicle stationaryevery job start and every node failure

Row four alone rules it out9.25 times a MAC receive datapath, per port, of schedule state — and row six rules it out again for a different reason: a vehicle's schedule is installed once and a cluster's would be recomputed whenever a job started. Chapter 22.2 §13's atomic installation is a per-port mechanism; coordinating it across 96 switches at job-start latency is a distributed-consensus problem the fabric does not have.

And the express reservation is the second objection. A schedule that reserves 19.48% of every link for the scheduled class gives back less than the imbalance it removes unless the efficiency gain exceeds that — which at 28.4% it comfortably does, but only if the schedule is actually correct for the job running, and Section 18 shows the participant set is the one thing that changes most.

Should add — load-aware path selection, plus a spine-side load advertisement.

Cost
per-path load array512 flops — 3.6% of a MAC datapath
selection logica 32-way minimum, combinational
spine load advertisement64 bytes per spine, a few times a second
delivered28.4% → 100% for 32 flows on 32 uplinks

May add — per-packet spraying with a receive-side reassembly buffer. This is the strongest mechanism available and its cost is memory rather than logic.

Spine buffer depthPath skew at 100 Gb/sReassembly buffer per port
1 MB80 µs1.00 MB
10 MB800 µs10.0 MB
32 MB2 560 µs32.0 MB

The reassembly buffer must equal the fabric's worst-case path skew times the line rate, and the skew is set by the deepest buffer a packet can sit behind. So spraying's cost is a direct function of how much buffering the fabric has — a shallow-buffered fabric makes spraying cheap and a deep-buffered one makes it expensive, which is the opposite of the usual intuition that more buffer is safer.

Must add, and it costs nothing — a way for the collective library to tell the fabric what it is doing. Every remedy above is easier with one bit of information the library has and the fabric does not: these N flows belong to one collective and will all run for the job's duration. With it, load-aware selection has a declared rate to place against and spraying has a reassembly bound. Without it, the fabric is inferring a collective's structure from packet headers, which is the hardest possible way to obtain a fact that the application already knows.

The cheapest thing a fabric can add for AI traffic is not a mechanism. It is an interface through which the collective declares itself.


15. RTL 7 — Collective Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// collective_telemetry -- what a leaf knows about a collective running
// across it, and the one field it cannot compute.
//
// The uncomputable field is the JOB's minimum, which is a minimum over
// 1024 participants spread across 32 leaves. Chapter 22.2 Section 20's
// twelfth taxonomy group, third appearance: import it.
// ---------------------------------------------------------------------
module collective_telemetry
  import collective_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        snapshot,

  input  logic [15:0] i_local_min_gbps,
  input  logic [15:0] i_local_mean_gbps,
  input  logic [7:0]  i_slowest_member,
  input  logic [31:0] i_slowest_unchanged,
  input  logic [31:0] i_steps_done,
  input  logic [31:0] i_accel_seconds_lost,
  input  logic [15:0] i_flows_this_leaf,
  input  logic [15:0] i_busiest_uplink_flows,
  input  logic [15:0] i_job_min_gbps,        // IMPORTED across leaves
  input  logic        i_job_min_valid,
  input  fabric_fix_e i_active_fix,

  output logic [15:0] o_local_min_gbps,
  output logic [15:0] o_mean_over_min_x10,
  output logic [7:0]  o_slowest_member,
  output logic [15:0] o_pinned_samples_pct,
  output logic [15:0] o_imbalance_x10,
  output logic [31:0] o_accel_seconds_lost,
  output logic [15:0] o_job_min_gbps,
  output logic [2:0]  o_active_fix,
  output logic [2:0]  o_caveats,
  output logic        o_rate_claim_valid
);
  localparam int C_LOCAL_ONLY = 0;   // no job-wide minimum available
  localparam int C_PINNED     = 1;   // the same member is always slowest
  localparam int C_FEW_FLOWS  = 2;   // flows per uplink below 2 ln n

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      o_local_min_gbps <= '0; o_mean_over_min_x10 <= 16'd10;
      o_slowest_member <= '0; o_pinned_samples_pct <= '0;
      o_imbalance_x10 <= 16'd10; o_accel_seconds_lost <= '0;
      o_job_min_gbps <= '0; o_active_fix <= 3'd0; o_caveats <= '0;
    end else if (snapshot) begin
      o_local_min_gbps     <= i_local_min_gbps;
      o_slowest_member     <= i_slowest_member;
      o_accel_seconds_lost <= i_accel_seconds_lost;
      o_active_fix         <= 3'(i_active_fix);

      if (i_local_min_gbps != 0)
        o_mean_over_min_x10 <= (i_local_mean_gbps * 16'd10)
                               / i_local_min_gbps;

      if (i_steps_done != 0)
        o_pinned_samples_pct <= 16'((i_slowest_unchanged * 100)
                                    / i_steps_done);

      if (i_flows_this_leaf != 0)
        o_imbalance_x10 <= (i_busiest_uplink_flows * 16'd10
                            * 16'(UPLINKS_PER_LEAF)) / i_flows_this_leaf;

      o_job_min_gbps <= i_job_min_valid ? i_job_min_gbps : 16'd0;

      o_caveats[C_LOCAL_ONLY] <= !i_job_min_valid;
      o_caveats[C_PINNED]     <= (i_steps_done != 0) &&
                                 ((i_slowest_unchanged * 2) > i_steps_done);
      // 2 ln 32 is about 7 flows per uplink; below that the imbalance
      // is structural rather than incidental -- Section 10.
      o_caveats[C_FEW_FLOWS]  <= (i_flows_this_leaf <
                                  16'(7 * UPLINKS_PER_LEAF));
    end
  end

  // A rate figure for a collective is only meaningful as the JOB's
  // minimum, which this leaf cannot compute.
  assign o_rate_claim_valid = !o_caveats[C_LOCAL_ONLY];
endmodule

Classification: a snapshot bank whose headline field is disqualified without an imported one.

What it teaches: that o_rate_claim_valid depends on i_job_min_valid and on nothing else. Every other field here is a correct local measurement; none of them is the collective's rate, because the collective's rate is a minimum over 1 024 participants and a leaf holds 32. Publishing o_local_min_gbps as the job's rate is exactly the error Section 20 refuses, one level up from the mean-versus-minimum mistake.

And it teaches that o_pinned_samples_pct separates the two slow-collective causes in one number. Chapter 23.1 §9's stickiness means a hash collision produces the same slowest member on every sample; transient congestion moves it. A value near 100% is a pinned collision — fix it with placement; a value near 1/32 is congestion — fix it with capacity or backoff. The two have completely different remedies and identical symptoms without this field.

Deliberately simplified: no read-clear. o_imbalance_x10 recomputes from flow counts rather than from measured rates, so it is a prediction rather than an observation — useful, and not the same thing. C_FEW_FLOWS's threshold of 7 flows per uplink is Section 10's 2 ln n rounded, baked for n = 32 and wrong for any other uplink count. And o_active_fix reports which remedy is enabled without reporting whether it is working.

Production implication: the missing effectiveness check for o_active_fix is the field to add, because every remedy in Section 12 can be enabled and silently inert. Striping is inert if the library opened the connections and the NIC's cache is thrashing; load-aware selection is inert if the spine side is the bottleneck; flowlet switching is inert if the gaps are shorter than the detection threshold. Publish the remedy's own success metric alongside the flag — connections actually established, perfect placements achieved, flowlet boundaries detected — and a fix that is configured but not functioning becomes visible in the same read that reports it is on.


16. RTL 8 — The Collective Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// collective_conformance_monitor -- properties for a leaf carrying a
// collective.
//
// Properties 1 to 9 are about mechanism and hold throughout a 3.52x
// slowdown. Properties 10 to 12 are the ones that do not, and all
// three constrain a CLAIM rather than a behaviour.
// ---------------------------------------------------------------------
module collective_conformance_monitor
  import collective_pkg::*;
#(
  parameter int MEMBERS = 32
)(
  input logic        clk,
  input logic        rst_n,

  input logic [15:0] member_gbps [MEMBERS],
  input logic [15:0] min_gbps,
  input logic [15:0] mean_gbps,
  input logic [15:0] collective_gbps,
  input logic [7:0]  slowest_member,
  input logic [31:0] step_time_us,
  input logic [31:0] steps_total,
  input logic [31:0] c_steps_done,
  input logic        running,
  input logic [7:0]  chosen_path,
  input logic [7:0]  paths,
  input logic        flow_start,
  input logic        sticky_preserved,
  input logic        load_aware_enable,
  input logic [15:0] lightest_gbps,
  input logic [15:0] path_load_at_choice,
  input logic        job_min_valid,
  input logic [15:0] job_min_gbps,
  input logic        rate_claim_valid,
  input logic        claim_job_rate
);
  // 1. The collective's rate is the minimum, never the mean.
  p_cl_is_min: assert property (@(posedge clk) disable iff (!rst_n)
    collective_gbps == min_gbps);

  // 2. The minimum never exceeds the mean.
  p_cl_min_le_mean: assert property (@(posedge clk) disable iff (!rst_n)
    min_gbps <= mean_gbps);

  // 3. The slowest member's rate IS the minimum.
  p_cl_slowest_is_min: assert property (@(posedge clk) disable iff (!rst_n)
    member_gbps[slowest_member] == min_gbps);

  // 4. A step's time is set by the minimum.
  p_cl_step_time: assert property (@(posedge clk) disable iff (!rst_n)
    (min_gbps != 0) |-> (step_time_us > 32'd0));

  // 5. Steps done never exceed the total.
  p_cl_steps_bounded: assert property (@(posedge clk) disable iff (!rst_n)
    c_steps_done <= steps_total);

  // 6. Running clears when the last step completes.
  p_cl_done: assert property (@(posedge clk) disable iff (!rst_n)
    (c_steps_done >= steps_total) |=> !running);

  // 7. A chosen path is inside the path set.
  p_cl_path_in_set: assert property (@(posedge clk) disable iff (!rst_n)
    flow_start |-> (chosen_path < paths));

  // 8. The path choice is made once and never revisited.
  p_cl_sticky: assert property (@(posedge clk) disable iff (!rst_n)
    sticky_preserved);

  // 9. Load-aware selection picks a lightest path.
  p_cl_greedy: assert property (@(posedge clk) disable iff (!rst_n)
    (flow_start && load_aware_enable) |->
      (path_load_at_choice == lightest_gbps));

  // 10. A JOB rate claim requires the imported job-wide minimum.
  p_cl_claim_needs_import: assert property (@(posedge clk) disable iff (!rst_n)
    claim_job_rate |-> job_min_valid);

  // 11. And the claim must be the job's minimum, not this leaf's.
  p_cl_claim_is_job_min: assert property (@(posedge clk) disable iff (!rst_n)
    claim_job_rate |-> (job_min_gbps <= min_gbps));

  // 12. The telemetry's validity flag agrees with the import.
  p_cl_valid_flag: assert property (@(posedge clk) disable iff (!rst_n)
    rate_claim_valid == job_min_valid);
endmodule

Classification: twelve properties, nine about mechanism and three about a claim.

What it teaches: that property 11's direction is the interesting one. It asserts job_min_gbps <= min_gbpsthe job's minimum is at most this leaf's minimum — which is true by construction and is worth asserting because the tempting error is to publish the larger number. A leaf that reports its own minimum as the job's rate is reporting something that is always at least as high as the truth, and the property's inequality is what catches an implementation that has them the wrong way round.

And it teaches that properties 1 to 9 hold perfectly during every failure in this chapter. A ring running at 28.4% satisfies all nine: the minimum is computed correctly, the step time follows from it, the paths are in the set, the stickiness is preserved, and the greedy choice — when enabled — really does pick the lightest path. Nothing is broken. The collective is simply three and a half times slower than the hardware it is running on, which is Section 20's subject.

Deliberately simplified: property 3 indexes an array with a signal, which needs the index to be stable in the same cycle the array is read. Property 9 compares against path_load_at_choice, a signal the selector must export purely for the property's benefit. Property 6 uses >= on a counter that property 5 bounds, so the two overlap. And there is no property about the rate at all — nothing here says the collective should be fast, because nothing in the fabric promises it.

Production implication: that last absence is the one to raise in a specification review. A fabric vendor can satisfy all twelve properties on a fabric delivering 28.4%, and the customer's question — will my training job run at line rateis not expressible as a property of the switch. What is expressible, and what a specification should ask for, is the mechanism: does the switch support load-aware selection, does it export per-path load, does it accept a spine-side load advertisement, and does it publish o_pinned_samples_pct. Ask for the four mechanisms, not for a rate, because the rate depends on a flow count the fabric does not control.


17. Hierarchical Reduction, and the Flow Count That Cannot Collide

Five remedies were offered for a ring all reduce running at twenty eight point four per cent of its uplink group, and they deliver very different rates for very different costs. Striping each ring link into eight connections takes the ring from twenty nine point six to seventy seven point one gigabits per second, recovering eighty eight per cent of the gap for a library setting, at a cost of eight thousand one hundred and ninety two connection contexts in the network interface cards. Flowlet switching re hashes at each of the two thousand and forty six natural step boundaries and does not change the expected rate at all, but converts a systematic penalty that lasts the whole job into a statistical one redrawn every step, which removes the tail of unlucky jobs across a fleet. Load aware path selection places thirty two flows onto thirty two uplinks one each, deterministically, reaching a hundred gigabits per second for five hundred and twelve flip flops of per path load state, which is three point six per cent of a media access control receive datapath, and it preserves stickiness because the choice is still made once at flow start. Larger link groups make the imbalance worse, because doubling the uplinks halves the flows per uplink. And hierarchical reduction reduces within each leaf first, inside the leaf switch which is non blocking by construction, so only one flow per leaf crosses the fabric and one flow cannot collide with itself; it reaches a hundred gigabits per second, its inter leaf phase takes one point two four zero seconds which is slightly faster than the ideal flat ring, and it costs nothing at all because it lives in the collective library.No remedy28.4 Gb/sFlowlet switchingsame mean, lessvarianceStriping ×877.1 Gb/s — 8 192contextsMore uplinksworse — fewer flowsper pathLoad-aware100 Gb/s — 512 flopsHierarchical1 flow per leaf — freeOne flow cannotcollidewith itself12
Figure 4 — five remedies ranked, and the two that reach 100% cost 512 flops and nothing.

Section 12 ranked five remedies. This one reaches 100% and costs nothing, because it changes the algorithm rather than the fabric.

The observation is Section 10's, read backwards. A ring all-reduce is slow because it produces one flow per accelerator, which is far below the 2 ln n ≈ 7 flows per uplink an uplink group needs to balance. The obvious response is to raise the flow count — Section 12's striping. The better response is to lower it to one.

A flow count of one cannot collide with itself.

Hierarchical all-reduce, in two stages.

StageParticipantsWhere it runsCrosses the fabric?
1 — reduce-scatter within each leaf32 per leafinside the leaf switchno
2 — all-reduce across leaves32 leaves, 1 flow eachthe fabricyes, one flow per leaf
3 — all-gather within each leaf32 per leafinside the leaf switchno

Stage 2 is the only fabric traffic and it is one flow per leaf. One flow on a 32-uplink group picks one uplink and gets all 100 Gb/s of itChapter 23.1 §4's result that a single flow uses one path, arriving here as an advantage rather than a limitation.

Flat ringHierarchical
flows crossing the fabric per leaf321
flows per uplink1.00.031
collision probability on a leaf97.1% of rings see ≥3 on one uplinkzero — there is one flow
inter-leaf ring rate28.4 Gb/s100 Gb/s
inter-leaf bytes per leaf15.984 GB15.500 GB
inter-leaf time4.503 s1.240 s

Row six against Section 8's 1.279 s is the striking part: hierarchical reduction is slightly faster than the ideal flat ring, because the inter-leaf ring has 32 participants rather than 1 024, so its 2(N−1)/N factor is 1.9375 rather than 1.998.

And stages 1 and 3 are not free — they are just not on the fabric.

Value
intra-leaf bytes per participant15.500 GB
where they goleaf switch, downlink ports, 32 × 100 Gb/s
leaf switch capacity for it3.2 Tb/s of downlink, non-blocking within the switch
do they contend with stage 2?no — different ports, and the stages are sequential

A leaf switch's internal fabric is non-blocking by constructionChapter 23.1 §2's D + U = P says all 64 ports can be active — so an intra-leaf all-reduce among 32 accelerators runs at full rate with no hashing involved at all. The uplink group is not touched.

Which gives the chapter's most useful practical statement.

The fix for a collective's fabric problem is usually not in the fabric. It is to arrange the collective so that almost none of it crosses the fabric.

Three ways to obtain that arrangement, in increasing order of effort.

ApproachWho does itEffect
order the ring leaf-by-leafthe job scheduler, at placement31 of 32 ring links stay inside a leaf
use a hierarchical all-reducethe collective library1 flow per leaf crosses
bothscheduler and library1 flow per leaf, and it is the only one

Row one costs nothing and is frequently not done, because the job scheduler places work by accelerator availability and has no reason to know the network topology. Giving it the leaf assignment — a single integer per accelerator — converts a 3.52× slowdown into none, and it is a scheduling input rather than a fabric feature.

The honest limitation. Hierarchical reduction assumes the intra-leaf group is large enough to be worth reducing and that all its members belong to the same job. A job spread thinly — four accelerators on each of 256 leaves — has 4 flows per leaf crossing the fabric instead of 32, which is better and not one. The benefit is proportional to how well the job is packed, which is again the scheduler's decision.


18. What the Collective Assumes

Seven premises. Three of them are about the job rather than the fabric, which is the chapter's recurring point.

AssumptionIf it is false
every participant runs at the same rateit is true by construction — that is what makes the minimum binding
the ring's links are placed on distinct uplinksSection 8 — a 3.52× slowdown, 87.8% of the time
a flow's path is stable for the jobChapter 23.1 §9 — it is, and that is the problem
the participant set does not changea node failure restarts the job on a new set and redraws every hash
the ring's ordering matches the topologySection 17 — 31 of 32 links could have stayed inside a leaf
the NIC can hold the connection countSection 11 — striping ×32 is 32 768 connections
the fabric's two halves are both balancedSection 13 — a leaf-side greedy choice says nothing about the spine

Row one is unusual and worth dwelling on. In every other chapter of this track an assumption is something that might be false. Here it is guaranteed true by the algorithm: a ring's data dependency makes every participant run at the slowest one's rate. The assumption is not a risk — it is the mechanism, and the chapter's arithmetic follows from it rather than being threatened by it.

Row four is the operational one and it is easy to miss. A 1 024-accelerator job that loses a node restarts on a different set of 1 024, with a different ring ordering, and every flow redraws its hash. So a job's speed changes discontinuously at every restart, for no reason visible anywhere — and a job that was running at 100 Gb/s per link before a restart can run at 16.7 after one. That is a support case that looks like a hardware regression and is a fresh draw from a distribution.

Row five is the one with the largest gap between cost and benefit. The ring's ordering is chosen by the collective library, usually by rank index; the mapping from rank to leaf is chosen by the job scheduler, usually by availability. Neither knows the other's constraint, and the result is a ring whose consecutive members are scattered across leaves when they could have been adjacent. One integer per accelerator, passed from the scheduler to the library, closes it.

And one premise that is not on the list because it does not hold at all.

That the fabric can tell a collective's flows from any other traffic. It cannot. Thirty-two long-lived, equal-sized, simultaneously-started flows look exactly like thirty-two long-lived, equal-sized, simultaneously-started flows — and Section 14's conclusion is that the cheapest fix is an interface through which the collective says so.


19. The Cost, Accounted

Priced against Chapter 19.7 §19's 14 166-flop MAC receive datapath, for the sixth chapter running.

BlockFlopsShare of 14 166
slowest_member_tracker720.5%
ring_allreduce_model330.2%
stall_accountant640.5%
all_to_all_model0 — combinational
striping_controller320.2%
load_aware_selector5764.1%
collective_telemetry1471.0%
collective_conformance_monitor0 — assertions
total9246.5%

One block is 55% of the total and it is the remedy that works. load_aware_selector's 512 flops are Chapter 23.1 §19's per-path load array — 32 uplinks × 16 bits — and they convert a 28.4% fabric into a 100% one for the case this chapter is about. Four point one per cent of a MAC receive datapath, for a 3.52× improvement in a training job's communication phase.

That ratio is the best in Modules 21 to 23 and it is worth stating against the alternatives.

RemedyHardware costRing rate
nothing028.4 Gb/s
load-aware selection512 flops — 3.6%100 Gb/s
striping ×80 in the fabric; 8 192 NIC contexts77.1 Gb/s
hierarchical reduction0 anywhere100 Gb/s
per-packet spraying1–32 MB of reassembly buffer per port100 Gb/s
time-aware scheduling131 072 flops — 9.25×ruled out

Rows two and four reach the same rate at 3.6% and 0% respectively, and row six is two and a half orders of magnitude more expensive than row two for an outcome Section 14 rejects.

And the unit's limits show again, in a new place. Row five's cost is megabytes of buffer, which in flip-flops would be 8 × 10⁶ to 2.56 × 10⁸565 to 18 071 times the datapath — and is meaningless expressed that way because it is SRAM. That is the fifth time in six chapters that a mechanism's cost has escaped the flop unit, always at the same place: when the size is set by the data rather than by the design. Chapter 23.3 meets the problem at full scale and names the unit that replaces it.

The comparison that closes the section.

Value
fabric hardware to fix this512 flops per leaf — 3.6% of a MAC datapath
accelerator-hours it recovers per 100 000-step run91 705
what the same recovery costs in extra fabric3.5× the fabric (Chapter 23.1 §19)

Five hundred and twelve flip-flops against three and a half times the fabric. That is the whole engineering argument, and it is the same shape as Chapter 23.1 §19's — a cheap measurement beats expensive capacity — with the difference that here the cheap thing is in the switch rather than in the application.


20. Properties Worth Asserting, and One Worth Refusing

Six groups. Every property holds while a training job runs at 28.4% of the hardware it is using, which is the condition the refused class describes.

Group A — the reduction operator.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A1. The collective's rate is the minimum.
p_rd_is_min: assert property (@(posedge clk) disable iff (!rst_n)
  collective_gbps == min_gbps);

// A2. The minimum is at most the mean.
p_rd_min_le_mean: assert property (@(posedge clk) disable iff (!rst_n)
  min_gbps <= mean_gbps);

// A3. The minimum is at most every member.
p_rd_min_le_all: assert property (@(posedge clk) disable iff (!rst_n)
  min_gbps <= member_gbps[member_idx]);

// A4. The slowest member's rate is the minimum.
p_rd_slowest: assert property (@(posedge clk) disable iff (!rst_n)
  member_gbps[slowest_member] == min_gbps);

// A5. The declared reduction operator is MIN and nothing else.
p_rd_operator: assert property (@(posedge clk) disable iff (!rst_n)
  COLLECTIVE_REDUCTION == RED_MIN);

// A6. mean-over-min is at least 1.0.
p_rd_ratio: assert property (@(posedge clk) disable iff (!rst_n)
  mean_over_min_x10 >= 16'd10);

Group B — the ring's structure.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// B1. Steps are 2(N-1).
p_rg_steps: assert property (@(posedge clk) disable iff (!rst_n)
  steps_total == 32'(2 * (PARTICIPANTS - 1)));

// B2. A step moves S/N bytes.
p_rg_chunk: assert property (@(posedge clk) disable iff (!rst_n)
  step_bytes_kb == 32'((S_MB * 1024) / PARTICIPANTS));

// B3. Step time is the chunk divided by the MINIMUM rate.
p_rg_step_time: assert property (@(posedge clk) disable iff (!rst_n)
  (min_gbps != 0) |->
    (step_time_us == ((step_bytes_kb * 8) / 32'(min_gbps))));

// B4. Steps done never exceed the total.
p_rg_bounded: assert property (@(posedge clk) disable iff (!rst_n)
  c_steps_done <= steps_total);

// B5. The collective ends when the last step does.
p_rg_ends: assert property (@(posedge clk) disable iff (!rst_n)
  (c_steps_done >= steps_total) |=> !running);

// B6. Slowdown is at least 1.00.
p_rg_slowdown: assert property (@(posedge clk) disable iff (!rst_n)
  slowdown_x100 >= 16'd100);

Group C — flows and paths.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// C1. A ring all-reduce produces one flow per accelerator.
p_fp_ring_flows: assert property (@(posedge clk) disable iff (!rst_n)
  (coll == COLL_ALLREDUCE) |->
    (flows_per_leaf_o == 32'(ACC_PER_LEAF)));

// C2. An all-to-all produces ACC_PER_LEAF x (N-1).
p_fp_a2a_flows: assert property (@(posedge clk) disable iff (!rst_n)
  (coll == COLL_ALLTOALL) |->
    (flows_per_leaf_o == 32'(ACC_PER_LEAF) * 32'(PARTICIPANTS - 1)));

// C3. A chosen path is inside the set.
p_fp_in_set: assert property (@(posedge clk) disable iff (!rst_n)
  flow_start |-> (chosen_path < 8'(UPLINKS_PER_LEAF)));

// C4. The choice is made once per flow and never revisited.
p_fp_sticky: assert property (@(posedge clk) disable iff (!rst_n)
  sticky_preserved);

// C5. Efficiency rises monotonically with flows per uplink.
p_fp_monotone: assert property (@(posedge clk) disable iff (!rst_n)
  (flows_a > flows_b) |->
    (efficiency_pct(flows_a, UPLINKS_PER_LEAF) >=
     efficiency_pct(flows_b, UPLINKS_PER_LEAF)));

// C6. Efficiency never exceeds 100%.
p_fp_capped: assert property (@(posedge clk) disable iff (!rst_n)
  efficiency_pct(flows_a, UPLINKS_PER_LEAF) <= 100);

Group D — load-aware selection.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// D1. Greedy selection picks a lightest path.
p_la_greedy: assert property (@(posedge clk) disable iff (!rst_n)
  (flow_start && load_aware_enable) |->
    (path_load_at_choice == lightest_gbps));

// D2. With hashing, the choice is the hash modulo the path count.
p_la_hash: assert property (@(posedge clk) disable iff (!rst_n)
  (flow_start && !load_aware_enable) |->
    (chosen_path == 8'(flow_hash % UPLINKS_PER_LEAF)));

// D3. Placing a flow raises exactly one path's load.
p_la_one_path: assert property (@(posedge clk) disable iff (!rst_n)
  flow_start |=> (path_load_gbps[$past(chosen_path)] ==
                  $past(path_load_gbps[$past(chosen_path)]) +
                  $past(declared_gbps)));

// D4. While flows <= paths, greedy placement is perfect.
p_la_perfect: assert property (@(posedge clk) disable iff (!rst_n)
  (load_aware_enable && (c_flows_placed <= 32'(UPLINKS_PER_LEAF))) |->
    (c_perfect_placements == c_flows_placed));

// D5. The lightest path's load never exceeds the busiest's.
p_la_order: assert property (@(posedge clk) disable iff (!rst_n)
  lightest_gbps <= busiest_gbps);

// D6. Load never goes negative on flow end.
p_la_nonneg: assert property (@(posedge clk) disable iff (!rst_n)
  flow_end |=> (path_load_gbps[$past(ending_path)] >= 16'd0));

Group E — striping.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// E1. A ring link's rate is the SUM of its stripes.
p_st_sum: assert property (@(posedge clk) disable iff (!rst_n)
  member_gbps[m] == stripe_sum(m));

// E2. The collective's rate is still the MINIMUM over ring links.
p_st_min: assert property (@(posedge clk) disable iff (!rst_n)
  collective_gbps == min_over_members());

// E3. Stripes carry disjoint byte ranges, so nothing is reordered.
p_st_disjoint: assert property (@(posedge clk) disable iff (!rst_n)
  (STRIPES > 1) |-> reassembly_required);

// E4. The reassembly depth is (STRIPES-1) stripe-sized gaps.
p_st_depth: assert property (@(posedge clk) disable iff (!rst_n)
  reorder_depth_bytes == (stripe_bytes * 32'(STRIPES - 1)));

// E5. Connections scale with members times stripes.
p_st_conns: assert property (@(posedge clk) disable iff (!rst_n)
  connections_total == (16'(MEMBERS) * 16'(STRIPES)));

Group F — the claim.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// F1. A job-rate claim requires the imported job-wide minimum.
p_cm_needs_import: assert property (@(posedge clk) disable iff (!rst_n)
  claim_job_rate |-> job_min_valid);

// F2. And the claim is the job's minimum, never this leaf's.
p_cm_is_job_min: assert property (@(posedge clk) disable iff (!rst_n)
  claim_job_rate |-> (job_min_gbps <= min_gbps));

// F3. The telemetry flag agrees with the import.
p_cm_flag: assert property (@(posedge clk) disable iff (!rst_n)
  rate_claim_valid == job_min_valid);

// F4. A pinned slowest member is reported, not averaged away.
p_cm_pinned: assert property (@(posedge clk) disable iff (!rst_n)
  ((c_slowest_unchanged * 2) > c_steps_done) |-> caveat_pinned);

// F5. Accelerator-seconds are the loss times the participant count.
p_cm_accel: assert property (@(posedge clk) disable iff (!rst_n)
  accel_seconds_lost ==
    ((lost_ms_per_collective * 32'(PARTICIPANTS)) / 1000));

Thirty-four properties, and all thirty-four hold on a fabric delivering 28.4% to a job that paid for 100%.


21. Verification Scenarios

Fifty-eight scenarios. Group 2's need a chain rather than a stream, and group 6's need more than one leaf.

Group 1 — the reduction operator (8).

#ScenarioExpect
132 members all at 100 Gb/smin = mean = 100; ratio 1.0
231 at 100, one at 28.4min 28.4, mean 97.8, ratio 3.44
331 at 51.6, one at 1.6sum 1 601.2, min 1.6 — the refused property's case
4the same compared against 32 × 50lower sum, 31.25× higher minimum
5one member raised, another lowered, sum constantthe minimum falls; the collective slows
6collective_gbps forced to the meanp_rd_is_min fires
7min_gbps forced above mean_gbpsp_rd_min_le_mean fires
8slowest_member pointing at a non-minimump_rd_slowest fires

Group 2 — the ring, which needs a chain (10).

#ScenarioExpect
9N = 1 024, S = 8 GB, all links 100 Gb/s2 046 steps, 1.279 s, 15.984 GB per participant
10N = 64, same S15.750 GB — the byte count barely moves
11one link at 28.4 Gb/s, 31 at 1004.503 s — the whole ring runs at 28.4
12the 31 fast links' idle time per step1.576 ms each, 3.22 s over the run
13one link at 16.7 Gb/s (k = 6)7.672 s, 6.00× — slowdown equals k exactly
14link rates re-randomised every stepthe mean slowdown is unchanged; variance falls
15a link at 0 Gb/sthe collective never completes — and nothing else fails
162 046 steps of fabric latency added+3.3 ms on 4.5 s — real and negligible
17step_time_us computed from the meanthe model under-predicts by 3.44×
18c_steps_done forced past steps_totalp_rg_bounded fires

Group 3 — flow counts and efficiency (10).

#ScenarioExpect
19ring all-reduce, 1 024 participants32 flows per leaf, 1.0 per uplink
20all-to-all, same job32 736 flows per leaf, 1 023 per uplink
2132 balls in 32 bins, 200 000 trialsP(max ≤ 2) = 2.9%, P(max = 3) = 51.5%
22the same, P(max = 1)1.8 × 10⁻¹³ — a hash cannot deliver it
23efficiency at 1, 4, 20, 100, 1 023 flows/uplink28.4, 46.2, 67.2, 82.4, 93.8%
24the closed form 1 + √(2 ln n ÷ (m/n))within 8% of simulation across three decades
25the flow count for a balanced group≈7 per uplink, 222 per leaf
26a ring's 32 against that 222structurally 7× short
27all-gather instead of all-reducealso 32 flows — the same problem
28efficiency_pct above 100p_fp_capped fires

Group 4 — the remedies (12).

#ScenarioExpect
29striping ×129.6 Gb/s
30striping ×242.4 Gb/s, 1.44×
31striping ×877.1 Gb/s, 2.61× — 88% of the gap
32striping ×3293.8 Gb/s, 3.17×
33striping ×32 against NIC context capacity32 768 connections — cache thrash
34load-aware, 32 flows on 32 uplinksone per uplink, 100% — deterministic
35load-aware with 64 flows on 32 uplinkstwo per uplink, still perfectly even
36load-aware with two flows starting the same cycleboth pick the same lightest path — the known hole
37flowlet switching, gaps at every stepsame mean, far lower variance
38flowlet switching on a job that drew k = 6it escapes; without it, 6× for the whole run
39larger link groups: 64 uplinks, 32 flows0.5 flows per uplink; imbalance rises
40hierarchical: one flow per leaf100 Gb/s; collision impossible

Group 5 — the stall accounting (8).

#ScenarioExpect
414.503 s actual against 1.279 ideal3.224 s lost, 3 301 accelerator-seconds
4210 000 steps8.96 h wall, 9 170 accelerator-hours
43100 000 steps89.6 h wall, 91 705 accelerator-hours
44an ideal 100 000-step run35.5 h — the run should have taken a third
45bucketing hides 60% behind computethe loss falls to 40%; the rate is unchanged
46c_accel_seconds_total over 1.3 M collectives32-bit wrap
47actual < ideallost_ms clamps to zero, not negative
48accel_seconds_lost not equal to loss × Np_cm_accel fires

Group 6 — multi-leaf, which one leaf cannot express (10).

#ScenarioExpect
4932 leaves, each with its own local minimumthe job's rate is the minimum of the 32
50one leaf at 16.7, thirty-one at 100the job runs at 16.7 — one leaf sets it
51a leaf reporting its local minimum as the job'salways ≥ the truth — p_cm_is_job_min fires
52job_min_valid lowno rate claim permitted — F1
53the same, suppliedthe claim is permitted
54two leaves both choosing their lightest uplinkthey can still converge on the same spine
55a spine load advertisement suppliedthe leaf-side choice accounts for the far side
56a node fails and the job restartsevery hash redraws; the rate changes discontinuously
57the same job, ring ordered leaf-by-leaf31 of 32 ring links stay inside a leaf
58the same job, ring ordered by rank across leavesall 32 cross the fabric

Scenario 5 is the one to run in front of a fabric team and scenario 51 is the one that cannot pass.

The directed test random stimulus will not produce

This test requires a fabric change that improves the aggregate and slows the job, on the same hardware, in the same run.

The arrangement:

StepAction
1a 32-member ring, 32 uplinks, a balanced starting placement — 32 × 50 Gb/s
2measure: aggregate 1 600 Gb/s, minimum 50, collective rate 50
3enable a load balancer whose objective is maximum aggregate throughput
4it moves one flow onto an uplink that had spare capacity in the other direction
5measure again: aggregate 1 601.2 Gb/s, minimum 1.6, collective rate 1.6
6the refused property p_collective_bandwidth passes more comfortably than before

Random stimulus will not produce this because randomisation explores the input space and this is a change in the optimiser's objective. A constrained-random generator varying flow sizes, hashes and link rates will occasionally produce an imbalanced placement, and every one of those is a worse aggregate as well as a worse minimum — so a randomly-reached bad state is bad on both metrics and teaches nothing. The instructive state is one where the two metrics disagree in sign, and reaching it requires deliberately optimising one of them.

The oracle is in four parts and part three is the finding.

PartSignalRequired valueWhy alone it is not enough
1 — the aggregate improvesaggregate_gbps1 601.2 > 1 600this is the "success" being tested
2 — the minimum collapsesmin_over_members_gbps1.6, from 50the real outcome
3 — the refused property still passesp_collective_bandwidthholds, more comfortablythis is the class
4 — the replacement property firesp_no_regressive_balancefires on the transitionthe guard rail that works

Part 3 is the assertion that something passes, and it is the whole test. The property was written to protect the collective; the change that made the collective 31 times slower made the property more satisfied. A test suite that only ever checks whether properties fail cannot express this, which is why the oracle has to name the pass explicitly.

Part 4 is the demonstration that the replacement is not merely different but sufficient. p_no_regressive_balance is the only property in Section 20 that constrains a transition rather than a statethe aggregate may not rise while the minimum fallsand it is the only one that fires here. Run the test once with it disabled and once enabled; the first run is a 31× regression that every dashboard approves of, and the second is an assertion failure at the moment the balancer acts.


22. Debugging a Slow Collective

A procedure, ordered so that the free reads come first and the fabric comes last.

StepDoBecause
1ask which collective is slowSection 10 — all-reduce is 28.4%, all-to-all is 93.8%
2count the flows per uplinkSection 10 — below ≈7 the imbalance is structural
3read o_pinned_samples_pctnear 100% is a pinned hash; near 3% is congestion
4read the minimum, never the mean or the sumSection 20 — the sum can rise while the job slows
5check the ring's ordering against the leaf mapSection 17 — 31 of 32 links could be intra-leaf
6check whether a remedy is enabled and workingSection 15 — striping is inert if the NIC cache thrashes
7only now look at links, optics and countersChapter 21.4's space, unchanged
8check whether the job restarted recentlySection 18 — a restart redraws every hash

Step 1 first is the highest-value question in the list, because the two collectives differ by a factor of 3.3 in efficiency and have different failure sites: the all-reduce fails on a leaf's uplink group, the all-to-all fails on a receiving leaf's downlink buffer. Investigating the wrong one wastes the whole effort.

Four signatures.

SignatureCause
slowdown is an exact small integer — 2×, 3×, 4×a hash collision; the integer is k on the busiest uplink
the same member is slowest on every sample, for hoursa pinned assignment — Section 3's c_slowest_unchanged
the rate changed discontinuously after a restarta fresh hash draw, not a regression
all-to-all slow, all-reduce fineincast on the downlink, not imbalance on the uplink

Row one is the most useful diagnostic in the chapter and it is free. A collective whose slowdown is 3.00× or 4.00× rather than 2.7× or 3.4× is being paced by an uplink carrying exactly 3 or 4 flows — Section 8's table — and the integer tells you the collision's size before any capture is taken. A slowdown that is not near an integer is something else.

Row three is the support case that looks worst and is least alarming. A job that ran at full rate yesterday and at a quarter today, with no change to the fabric, no counter moving and no link event, has redrawn its hashes on a restart. It is not a regression, it is a sample, and the remedy is not to investigate the fabric but to deploy Section 12's placement.


23. Misconceptions

Six, in wrong-model / what-it-costs / corrected-model form.

Misconception 1 — "the fabric is 1:1, so a collective gets line rate."

Wrong model: non-blocking wiring delivers non-blocking throughput to any traffic.

What it costs: a factor of 3.52 on the communication phase of every training step. Chapter 23.1 §10's arithmetic: 32 flows hashed onto 32 uplinks put 3.52× the mean on the busiest, and a collective — held to a common rate by its own data dependency — runs at 100 ÷ 3.52 = 28.4 Gb/s per link.

Corrected model: the fabric's capacity is present and its reachability depends on the flow count. A ring all-reduce produces one flow per accelerator, which is about seven times fewer than the 2 ln n per uplink an uplink group needs to balance. Nothing is broken; the collective is structurally short of flows.

Misconception 2 — "the fabric is congested."

Wrong model: a job running at 28.4% must be filling the links.

What it costs: the entire diagnosis, in the most expensive direction — buying more fabric. Section 8: the job uses 909 Gb/s of a 3 200 Gb/s uplink group per leaf, so 2 291 Gb/s per leaf — 73.3 Tb/s across 32 leaves — is idle.

Corrected model: the fabric is 71.6% idle and the job is at 28.4%, simultaneously and consistently. The capacity is on uplinks the collective's flows were not hashed onto, and no participant can use it because they are all throttled to the slowest. Adding capacity adds idle uplinks.

Misconception 3 — "measure the aggregate throughput."

Wrong model: sum the members' rates and compare against the target.

What it costs: the ability to detect the failure at all, and worse. Section 20's table: 32 members at 50 Gb/s sum to 1 600 and run at 50; 31 at 51.6 plus one at 1.6 sum to 1 601.2 and run at 1.6. The higher aggregate is thirty-one times slower.

Corrected model: a collective's rate is the minimum over its participants. The aggregate is a true statement about a different quantity — capacity, not delivery — and a load balancer optimising for it can make a job slower on purpose. Assert the minimum, bound the imbalance, and forbid a change that raises the sum while lowering the minimum.

Misconception 4 — "all AI traffic looks the same to the fabric."

Wrong model: collectives are collectives; tune the fabric once.

What it costs: tuning the wrong half. A ring all-reduce produces 32 flows per leaf and an all-to-all produces 32 73628.4% against 93.8% efficiency — and a job running expert parallelism issues both, in the same training step, on the same uplinks.

Corrected model: they are opposite cases with different failure sites. The ring fails on the leaf's uplink group through hash collision; the all-to-all fails on the receiving leaf's downlink through incast, which is a buffer problem Chapter 23.1's uplink analysis does not model at all. A fabric tuned entirely on the ring improves the ring and leaves the all-to-all exactly as it was.

Misconception 5 — "add a schedule, like the vehicle did."

Wrong model: Chapter 22.2 bounded latency with a time-aware schedule; do that here.

What it costs: a design that cannot be built. At 1 024 endpoints a per-participant gate-control list is 1 024 entries per port, doubled for Chapter 22.2 §13's atomic installation — 131 072 flops, 9.25 times Chapter 19.7 §19's entire MAC receive datapath, per port. And it would be recomputed at every job start and every node failure, coordinated across 96 switches.

Corrected model: scheduling solves a problem a vehicle has — a fixed, small, known participant set with hard deadlines — that a cluster does not. What a cluster needs is placement, not separation: 512 flops of per-path load state and a greedy choice deliver 100% for 3.6% of a datapath, and hierarchical reduction delivers it for nothing.

Misconception 6 — "this is the network team's problem."

Wrong model: the fabric under-delivers, so the fabric must be fixed.

What it costs: the two cheapest remedies, both of which live outside the network. Section 12's ranking: hierarchical reduction (collective library, free, 100 Gb/s) and ring ordering that matches the leaf map (job scheduler, one integer per accelerator, 31 of 32 links stay off the fabric).

Corrected model: the fabric's hardest problem need not be posed. A ring whose consecutive members share a leaf, or a two-stage reduction that reduces inside the leaf first, sends one flow per leaf across the fabric — and one flow cannot collide with itself. The fabric fix is real and costs 512 flops; the two that cost nothing belong to the scheduler and the library, and neither team is usually in the room.


24. Interview Questions

Six, with what a strong answer contains.

1. Why does a collective get less from a fabric than independent traffic does?

Because it cannot use spare capacity. Independent greedy flows fill whatever uplink they land on, so a 32-flow group delivers 100 Gb/s × the non-empty uplinks — 20.4 of 32, or 63.9%. A collective's participants are held to a common rate by the algorithm's data dependency, so all 32 run at 100 ÷ max(k) = 28.4 Gb/s and the group delivers 28.4%. A strong answer notes that Chapter 23.1 §10's published figure was already the equal-rate one — conservative for general traffic and exactly right here.

2. How does one uplink stall a thousand accelerators?

A ring is a chain, and a step is not complete until every link has delivered. An uplink carrying k ring flows gives each 100/k Gb/s; the ring runs at that rate; the slowdown is exactly k. A strong answer gives the distribution — P(max = 3) = 51.5%, P(max = 4) = 36.3%, so 87.8% of randomly assigned rings run at a third or a quarter of line rate — and the cost: 3 301 accelerator-seconds per all-reduce, 91 705 accelerator-hours over a 100 000-step run.

3. Why is all-to-all easy and all-reduce hard on the same fabric?

Flow count. A ring produces N flows and an all-to-all produces N(N−1)32 and 32 736 per leaf for a 1 024-participant job. The balls-in-bins imbalance falls as 1/√(flows per uplink), so it is 3.52× at one flow per uplink and 1.067× at a thousand. A strong answer names the design rule — an uplink group needs about 2 ln n ≈ 7 flows per uplink to balance, which is 222 per leaf against the ring's 32 — and adds that the all-to-all's real hazard is incast on the downlink, a different resource entirely.

4. Which of the fabric's remedies actually works for a collective, and why?

Load-aware path selection, and the reason is specific to collectives. The flows are long-lived, equal-sized and start together, which is exactly the condition where a greedy least-loaded assignment is optimal: 32 flows, 32 uplinks, one each, 100%, deterministically — against a hash's probability of 1.8 × 10⁻¹³ of the same outcome. A strong answer notes that stickiness is preserved — the path is still chosen once and never revisited, so Chapter 23.1 §9's ordering argument carries over unchanged — and that the cost is 512 flops, 3.6% of a MAC receive datapath.

5. Why not schedule the fabric the way a vehicle schedules its backbone?

Two reasons, and the first is arithmetic. Chapter 22.2 §13's atomic installation needs a shadow copy, so per-participant windows at 1 024 endpoints are 131 072 flops per port — 9.25× a whole MAC receive datapath. The second is operational: a vehicle's schedule is installed once with the vehicle stationary; a cluster's would be recomputed at every job start and every node failure, coordinated across 96 switches. A strong answer adds the third: a schedule reserves 19.48% of every link, and the cluster's problem is placement, not separation.

6. You are told a training job is slow and the fabric is 71% idle. What is happening?

Both statements are true and they are the same fact. The collective's flows were hashed onto a subset of the uplinks; every participant is throttled to the slowest; the uplinks nobody landed on stay empty. A strong answer names the diagnostic sequence: which collective, how many flows per uplink, is the slowest member pinned, and is the slowdown an exact small integer — because a slowdown of 3.00× or 4.00× names k directly, and anything not near an integer is a different problem.


25. Questions and Answers


26. What's Next

The fabric's shape came from Chapter 23.1; its worst workload came from this chapter. What is inside the switch is next.

This chapter established
the right statisticthe minimum over participants, never the sum or the mean
the same flows, two models63.9% independent, 28.4% as a collective
the cost3.52×, 3 301 accelerator-seconds per all-reduce
the mechanisman uplink carrying k flows makes the slowdown exactly k
the two collectivesring 28.4%, all-to-all 93.8%, same job, same second
the remedy that survivesload-aware placement at 512 flops, and hierarchical reduction for free
the refused propertya collective's performance asserted as an aggregate

And the chapter leaves one debt. Section 19 priced its mechanisms in flip-flops and hit the unit's limit again on per-packet spraying's reassembly buffer — 1 to 32 megabytes, which as flip-flops is 565 to 18 071 times a MAC receive datapath and means nothing expressed that way. That is the fifth failure of the unit in six chapters, always where a mechanism's size is set by the data.

Chapter 23.3 goes inside a merchant switch ASIC, where the problem is not marginal but total. A modern switch's packet buffer is tens of megabytes and its forwarding tables are hundreds of thousands of entries; expressed in flip-flops those are numbers in the hundreds of millions, against a MAC datapath of fourteen thousand. The chapter's first job is to name the unit that replaces the flop — one that prices a flip-flop and an SRAM bit against each other honestly — and then to walk the pipeline from ingress port to egress queue in it: the parser, the lookup, the shared buffer with its dynamic thresholds, the scheduler, and the ingress-to-egress crossing that every one of Module 21's fault classes hides behind.

Continue learning

Standards & specifications

Governing standard
IEEE Std 802.3 (Ethernet)(opens IEEE in a new tab)

Defines the Ethernet MAC, the media-independent interfaces and the physical-layer sublayers, including framing, access control, auto-negotiation and per-rate PHY specifications. VLAN tagging, priority and time-sensitive shaping are defined by IEEE 802.1, not by 802.3.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Ethernet curriculum.