Problem
Dion's whole point is a cheaper orthogonalization step: select only a
fraction (default 0.25) of each matrix's rows and run Newton-Schulz on that
submatrix. Under FSDP2 row-sharding this saving is silently erased.
In megabatch_orthogonalize_async (dion/megabatch_base.py), every rank's
selected rows are padded back up to the full per-rank shard before the
all-to-all:
padded_local_size = ceil(global_comm_dim_size / world_size) # the FULL shard
# e.g. 5120-row weight, 4 ranks: selected 320 rows -> padded back to 1280
Because select_dim == shard_dim (both -2/rows), the selection dimension is
exactly the dimension the pad rule re-inflates. Result: the assembled matrix is
full size (~¾ zero rows), so comm volume and NS GEMM cost are identical to
NorMuon — Dion2's fraction buys nothing here.
Not a correctness bug (zero rows contribute nothing to UᵀU and are narrowed off
after the second all-to-all). It is a performance regression: Dion2 pays the
selection overhead (topk, gather, scatter, ~244-kernel tail) and collects none of
its compute/comm reward.
How it was identified
- GPU kernel breakdown of
nordion2_gramns vs normuon_gramns (5120/8L, bs1
traces): both spend ~63% in the same gramNS GEMMs; nordion2's NS is only
modestly cheaper (49 vs 65 ms), not the ~4x expected from fraction=0.25.
- Trace shapes confirmed it: nordion2's
all_to_all chunks and
gemm_symmetric inputs are full [*, 5120, 5120], byte-identical comm
(2500 MB / 6 calls) to normuon — the selection never shrank the matrix.
git blame + history traced the padding to the megabatch path and showed the
original Dion2 did not do this.
When it was introduced
The original Dion2 (Kwangjun Ahn) had a dedicated distributed path that
communicated and orthogonalized only the selected submatrix — the old
dion2_update_batch_async sized its all-to-all buffers to U_selected and
carried the literal comment # Only submatrix is orthogonalized!. No pad to
global size.
The regression was introduced by the megabatch refactor that moved Dion2
onto NorMuon's shared comm path, then hardened by later padding fixes:
| date |
commit |
what |
| 2025-11-13 |
5ca6fa5 Kwangjun Ahn (#15) |
original Dion2; selected-submatrix-only comm + NS |
| 2026-03-27 |
4266bd0 John Langford |
Refactor Dion2 to use megabatching — Dion2 joins the shared path (regression starts here) |
| 2026-03-27 |
e4cc9b2 John Langford |
Dion2 + NorMuon share the megabatch base class |
| 2026-05-04 |
e8e1c6a John (#74) |
pad to ceil(global/world) to fix an FSDP2 empty-shard NCCL hang (makes the full-size inflation explicit) |
| 2026-05-06 |
ff0fb99 (#75) / 4dc5728 (#77) |
precompute + harden padded_local_size |
Root cause
megabatch_orthogonalize_async is shared between full-shard callers (Muon/
NorMuon, where padding to ceil(global/world) is correct and necessary to avoid
the all-to-all hang) and selected callers (Dion2/NorDion2). The pad rule has no
notion that the selected input was already subsampled, so it re-inflates the
selection to the full shard. The original per-optimizer path avoided this by
sizing comm to k = ceil(fraction * rows) directly.
Problem
Dion's whole point is a cheaper orthogonalization step: select only a
fraction(default 0.25) of each matrix's rows and run Newton-Schulz on thatsubmatrix. Under FSDP2 row-sharding this saving is silently erased.
In
megabatch_orthogonalize_async(dion/megabatch_base.py), every rank'sselected rows are padded back up to the full per-rank shard before the
all-to-all:
Because
select_dim == shard_dim(both -2/rows), the selection dimension isexactly the dimension the pad rule re-inflates. Result: the assembled matrix is
full size (~¾ zero rows), so comm volume and NS GEMM cost are identical to
NorMuon — Dion2's
fractionbuys nothing here.Not a correctness bug (zero rows contribute nothing to UᵀU and are narrowed off
after the second all-to-all). It is a performance regression: Dion2 pays the
selection overhead (topk, gather, scatter, ~244-kernel tail) and collects none of
its compute/comm reward.
How it was identified
nordion2_gramnsvsnormuon_gramns(5120/8L, bs1traces): both spend ~63% in the same gramNS GEMMs; nordion2's NS is only
modestly cheaper (49 vs 65 ms), not the ~4x expected from
fraction=0.25.all_to_allchunks andgemm_symmetricinputs are full[*, 5120, 5120], byte-identical comm(2500 MB / 6 calls) to normuon — the selection never shrank the matrix.
git blame+ history traced the padding to the megabatch path and showed theoriginal Dion2 did not do this.
When it was introduced
The original Dion2 (Kwangjun Ahn) had a dedicated distributed path that
communicated and orthogonalized only the selected submatrix — the old
dion2_update_batch_asyncsized its all-to-all buffers toU_selectedandcarried the literal comment
# Only submatrix is orthogonalized!. No pad toglobal size.
The regression was introduced by the megabatch refactor that moved Dion2
onto NorMuon's shared comm path, then hardened by later padding fixes:
5ca6fa5Kwangjun Ahn (#15)4266bd0John Langforde4cc9b2John Langforde8e1c6aJohn (#74)ceil(global/world)to fix an FSDP2 empty-shard NCCL hang (makes the full-size inflation explicit)ff0fb99(#75) /4dc5728(#77)padded_local_sizeRoot cause
megabatch_orthogonalize_asyncis shared between full-shard callers (Muon/NorMuon, where padding to
ceil(global/world)is correct and necessary to avoidthe all-to-all hang) and selected callers (Dion2/NorDion2). The pad rule has no
notion that the selected input was already subsampled, so it re-inflates the
selection to the full shard. The original per-optimizer path avoided this by
sizing comm to
k = ceil(fraction * rows)directly.