[BUG] sm_103 (B300): `tl.dot` chunked-recurrence kernel is non-deterministic with `num_warps∈{4,8}, num_stages∈{2,3}` — reproduces on Triton 3.7.0, i.e. NOT fixed by #9615 / #9871's fix
bugneeds reproducer
### Describe the bug
## Summary
A Triton kernel that carries an fp32 accumulator state across a sequential `for` loop of `tl.dot` updates (the standard "chunked linear-attention recurrence" pattern) produces **bitwise-different outputs on every invocation with identical inputs** on NVIDIA B300 (sm_103). Failing configs (each launched directly, no autotuner): `num_warps=4` with `num_stages=2` (BV=32 and BV=64), `num_warps=4` with `num_stages=3` (BV=32), and `num_warps=8` with `num_stages∈{2,3}` (BV=32). All `num_warps=2` and `num_warps=16` configs are bitwise deterministic. The kernel has no cross-CTA communication (each program writes a disjoint output slice), so this is a compiler/scheduling race, not a kernel logic error.
**Verified on both Triton 3.6.0 (torch 2.10/2.11 stack) and Triton 3.7.0 (torch 2.12 stack) — identical failing configs on both.** Since 3.7.0 includes PR #9615 (the fix that closed #9871 for sm_100), either that fix does not cover sm_103, or this is a related-but-distinct race.
This matches the failure class of #9871 (sm_100), but on sm_103 the envelope is **wider** than the formula verified there (`num_warps=4 AND BV<=32 AND num_stages∈{2,3} AND grid>=160`):
| | #9871 (sm_100, GB200/B200) | This report (sm_103, B300) |
|---|---|---|
| `BV=32, w4, s2/s3` | FAIL | FAIL |
| `BV=64, w4, s2` | **pass** (BV≤32 only) | **FAIL** |
| `BV=32, w8, s2/s3` | **pass** (verified formula: w4 only) | **FAIL** |
| Loop bound | dynamic (memory-loaded) required | **uniform runtime kernel arg fails** (`do_not_specialize`, no memory-loaded bound) |
| Grid threshold | ≥ ~160 CTAs | FAIL observed from 128–192 CTAs (timing-dependent; reliable at ≥ 256) |
| Status after #9615 (Triton 3.7.0) | fixed (regression test PR #10075) | **still failing (verified)** |
## Environment
| Component | Version |
|---|---|
| GPU | NVIDIA B300 SXM6 AC — sm_103 / capability (10, 3), 148 SMs |
| Driver | 580.126.09 (CUDA 13.0) |
| triton | **3.6.0** (torch 2.10.0+cu130) and **3.7.0** (torch 2.12.0+cu130) — both reproduce |
| dtype | bf16 inputs, fp32 accumulators |
Not reproducible on H200 (sm_90) — the identical downstream training pipeline is deterministic there.
## Reproduction
Self-contained script (also attached as `triton_sm103_repro.py`): the kernel mirrors flash-linear-attention's `chunk_gated_delta_rule_fwd_kernel_h_blockdim64` (K=V=128, BT=64, two `64×BV` fp32 state accumulators, per-head scalar gate). It launches each config directly with `num_warps`/`num_stages` and counts distinct bitwise output hashes over 10 identical calls.
Observed output on B300, Triton 3.6.0:
```text
sm=(10, 3) dev=NVIDIA B300 SXM6 AC triton=3.6.0 torch=2.10.0+cu130
HV=32 T=67856 K=128 V=128 BT=64 NT=1061
--- config matrix (B=4, 10 identical calls each; distinct>1 = nondeterministic) ---
grid= 512 BV32_w2_s1: distinct=1/10
grid= 512 BV32_w2_s2: distinct=1/10
grid= 512 BV32_w2_s3: distinct=1/10
grid= 512 BV32_w2_s4: distinct=1/10
grid= 512 BV32_w4_s1: distinct=1/10
grid= 512 BV32_w4_s2: distinct=10/10 <-- NONDETERMINISTIC
grid= 512 BV32_w4_s3: distinct=10/10 <-- NONDETERMINISTIC
grid= 512 BV32_w4_s4: distinct=1/10
grid= 512 BV32_w8_s1: distinct=1/10
grid= 512 BV32_w8_s2: distinct=10/10 <-- NONDETERMINISTIC
grid= 512 BV32_w8_s3: distinct=10/10 <-- NONDETERMINISTIC
grid= 512 BV32_w8_s4: distinct=1/10
grid= 256 BV64_w2_s1: distinct=1/10
grid= 256 BV64_w2_s2: distinct=1/10
grid= 256 BV64_w2_s3: distinct=1/10
grid= 256 BV64_w2_s4: distinct=1/10
grid= 256 BV64_w4_s1: distinct=1/10
grid= 256 BV64_w4_s2: distinct=10/10 <-- NONDETERMINISTIC
grid= 256 BV64_w4_s3: distinct=1/10
grid= 256 BV64_w4_s4: distinct=1/10
grid= 256 BV64_w8_s1: distinct=1/10
grid= 256 BV64_w8_s2: distinct=1/10
grid= 256 BV64_w8_s3: distinct=1/10
grid= 256 BV64_w8_s4: distinct=1/10
--- grid-size sweep for failing configs ---
BV32_w4_s2: grid=128:ok grid=256:FAIL grid=384:FAIL grid=512:FAIL grid=768:FAIL
BV32_w4_s3: grid=128:ok grid=256:FAIL grid=384:FAIL grid=512:FAIL grid=768:FAIL
BV64_w4_s2: grid=64:ok grid=128:ok grid=192:FAIL grid=256:FAIL grid=384:FAIL
BV32_w8_s2: grid=128:ok grid=256:FAIL grid=384:FAIL grid=512:FAIL grid=768:FAIL
FAILING CONFIGS: ['BV32_w4_s2', 'BV32_w4_s3', 'BV32_w8_s2', 'BV32_w8_s3', 'BV64_w4_s2']
```
Identical failing configs on **Triton 3.7.0** (torch 2.12.0+cu130, fresh venv, same machine):
```text
sm=(10, 3) dev=NVIDIA B300 SXM6 AC triton=3.7.0 torch=2.12.0+cu130
grid= 512 BV32_w4_s2: distinct=10/10 <-- NONDETERMINISTIC
grid= 512 BV32_w4_s3: distinct=10/10 <-- NONDETERMINISTIC
grid= 512 BV32_w8_s2: distinct=10/10 <-- NONDETERMINISTIC
grid= 512 BV32_w8_s3: distinct=10/10 <-- NONDETERMINISTIC
grid= 256 BV64_w4_s2: distinct=10/10 <-- NONDETERMINISTIC
FAILING CONFIGS: ['BV32_w4_s2', 'BV32_w4_s3', 'BV32_w8_s2', 'BV32_w8_s3', 'BV64_w4_s2']
```
Notes:
- Failures begin once the grid exceeds roughly one wave on this part (148 SMs), consistent with #9871's occupancy observation; the exact threshold is timing-dependent (BV32_w4_s3 failed at 128 in one sweep and passed in another).
- **The race is not limited to `num_warps=4`:** `BV=32, num_warps=8, num_stages∈{2,3}` is also non-deterministic (reproduced both with the attached standalone script above and with the original FLA kernel, 25/25 distinct outputs there), while all `num_warps=2` and all `num_warps=16` configs were deterministic in our sweeps.
- The loop bound here is a **uniform runtime scalar** (`T` passed with `do_not_specialize`, `NT = tl.cdiv(T, BT)`); no memory-loaded bound is needed on sm_103, unlike the sm_100 findings in #9871.
- Differences are not tiny ULP noise consolidated at one location: in the originating real workload, ~0.05% of output elements differed per call with max abs diff ~0.045 (bf16 outputs in [-3, 3]).
## Real-world impact
This is not synthetic: the failing configs are inside the autotune space of `flash-linear-attention`'s `chunk_gated_delta_rule_fwd_kernel_h_blockdim64`, and on B300 the autotuner **selects** `BV=64, num_warps=4, num_stages=2` as best. Consequences observed in production training of Qwen3.5-MoE (hybrid GatedDeltaNet/MoE, DeepSpeed ZeRO-3, bf16):
- every linear-attention forward on B300 is silently non-reproducible; and
- under non-reentrant activation checkpointing, the forward-vs-recompute divergence propagates to the MoE router, flips ~0.4% of top-k expert assignments, changes data-dependent tensor shapes in Liger-Kernel's fused-MoE routing metadata, and crashes with `torch.utils.checkpoint.CheckpointError` (field report: [linkedin/Liger-Kernel#1255](https://github.com/linkedin/Liger-Kernel/issues/1255); kernel-owner report: fla-org/flash-linear-attention <link to FLA issue>).
All current torch releases are exposed on sm_103: torch 2.10.0 and 2.11.0 pin Triton 3.6.0, and torch 2.12.0 (latest stable) pins Triton 3.7.0 — we verified the failure on both Triton versions.
## Asks
1. **PR #9615 does not fix sm_103** — we verified the repro fails identically on Triton 3.7.0, which contains it. Please reopen #9871 or treat this as the sm_103 tracking issue. (Open question for maintainers: does the #10075 regression test for #9871 pass on sm_103 hardware? Our data suggests the sm_103 failure mode is broader — `BV=64` and uniform loop bounds are affected — so it may be a related but distinct missing-fence/pipelining case.)
2. If feasible, a check on Triton **main** (post-3.7.0) on B300/GB300 hardware with the attached script would tell whether any later change already covers it — we could not test main on this machine.
3. Guidance for released stacks: torch 2.10/2.11 (Triton 3.6.0) and torch 2.12 (Triton 3.7.0) are all affected on sm_103. We are mitigating by restricting affected kernels to `num_warps=2`, which is deterministic in all our testing (~10% slower on the affected kernel); confirmation that this is a sound workaround (vs. coincidental) would be valuable.
## Repro script
See attached `triton_sm103_repro.py` (self-contained, ~150 lines: kernel + config matrix + grid sweep, prints the tables above).
```python
"""Standalone repro: tl.dot chunked-recurrence non-determinism on Blackwell sm_103 (B300).
The kernel mirrors flash-linear-attention's chunk_gated_delta_rule_fwd_kernel_h_blockdim64
(K=V=128, BT=64, per-head scalar gate, uniform runtime loop bound). Each (i_v, i_nh)
program owns a disjoint slice of the outputs and the chunk loop is sequential
in-program, so any run-to-run output difference with identical inputs is a
compiler/scheduling race, not a kernel logic error.
Observed on NVIDIA B300 SXM6 AC (sm_103, 148 SMs), Triton 3.6.0 AND 3.7.0:
- num_warps=4 + num_stages=2 (BV=32 and BV=64), num_warps=4 + num_stages=3 (BV=32),
and num_warps=8 + num_stages={2,3} (BV=32) produce bitwise-different outputs on
every call once grid >= ~128-256 CTAs.
- All num_warps=2 (and num_warps=16) configs deterministic.
Related: triton-lang/triton#9871 (sm_100), fixed by PR #9615 — that fix is in
Triton 3.7.0 but does NOT fix these sm_103 cases.
"""
import torch
import triton
import triton.language as tl
@triton.jit(do_not_specialize=['T'])
def fwd_h_repro(
k, v, w, v_new, g, h,
T,
HV: tl.constexpr, K: tl.constexpr, V: tl.constexpr,
BT: tl.constexpr, BV: tl.constexpr,
):
i_v, i_nh = tl.program_id(0), tl.program_id(1)
i_n, i_h = i_nh // HV, i_nh % HV
bos = i_n * T
NT = tl.cdiv(T, BT)
boh = i_n * NT
b_h1 = tl.zeros([64, BV], dtype=tl.float32)
b_h2 = tl.zeros([64, BV], dtype=tl.float32)
h += (boh * HV + i_h).to(tl.int64) * K * V
v += (bos * HV + i_h).to(tl.int64) * V
k += (bos * HV + i_h).to(tl.int64) * K
w += (bos * HV + i_h).to(tl.int64) * K
v_new += (bos * HV + i_h).to(tl.int64) * V
for i_t in range(NT):
i_t64 = i_t.to(tl.int64)
p_h1 = tl.make_block_ptr(h + i_t64 * HV * K * V, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0))
tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1))
p_h2 = tl.make_block_ptr(h + i_t64 * HV * K * V, (K, V), (V, 1), (64, i_v * BV), (64, BV), (1, 0))
tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), boundary_check=(0, 1))
p_w = tl.make_block_ptr(w, (T, K), (HV * K, 1), (i_t * BT, 0), (BT, 64), (1, 0))
b_w = tl.load(p_w, boundary_check=(0, 1))
b_v = tl.dot(b_w, b_h1.to(b_w.dtype))
p_w = tl.make_block_ptr(w, (T, K), (HV * K, 1), (i_t * BT, 64), (BT, 64), (1, 0))
b_w = tl.load(p_w, boundary_check=(0, 1))
b_v += tl.dot(b_w, b_h2.to(b_w.dtype))
p_v = tl.make_block_ptr(v, (T, V), (HV * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
b_v = tl.load(p_v, boundary_check=(0, 1)) - b_v
p_vn = tl.make_block_ptr(v_new, (T, V), (HV * V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0))
tl.store(p_vn, b_v.to(p_vn.dtype.element_ty), boundary_check=(0, 1))
last_idx = min((i_t + 1) * BT, T) - 1
m_t = (i_t * BT + tl.arange(0, BT)) < T
b_g_last = tl.load(g + (bos * HV + last_idx * HV + i_h).to(tl.int64)).to(tl.float32)
p_g = tl.make_block_ptr(g + (bos * HV + i_h).to(tl.int64), (T,), (HV,), (i_t * BT,), (BT,), (0,))
b_g = tl.load(p_g, boundary_check=(0,)).to(tl.float32)
b_v = b_v * tl.where(m_t, tl.exp2(b_g_last - b_g), 0)[:, None]
b_gl = tl.exp2(b_g_last)
b_h1 *= b_gl
b_h2 *= b_gl
b_v = b_v.to(k.dtype.element_ty)
p_k = tl.make_block_ptr(k, (K, T), (1, HV * K), (0, i_t * BT), (64, BT), (0, 1))
b_k = tl.load(p_k, boundary_check=(0, 1))
b_h1 += tl.dot(b_k, b_v)
p_k = tl.make_block_ptr(k, (K, T), (1, HV * K), (64, i_t * BT), (64, BT), (0, 1))
b_k = tl.load(p_k, boundary_check=(0, 1))
b_h2 += tl.dot(b_k, b_v)
def bithash(t: torch.Tensor, max_elems: int = 16_777_216) -> int:
"""Position-weighted bitwise hash (subsampled for big tensors)."""
v = t.detach().contiguous().view(torch.int16).flatten()
if v.numel() > max_elems:
v = v[::(v.numel() + max_elems - 1) // max_elems]
v = v.to(torch.int64)
wgt = torch.arange(v.numel(), device=v.device, dtype=torch.int64) % 8191 + 1
return int((v * wgt).sum().item())
def make_inputs(B: int, T: int, HV: int, K: int, V: int):
torch.manual_seed(0)
k = torch.nn.functional.normalize(
torch.randn(B, T, HV, K, device="cuda", dtype=torch.bfloat16), dim=-1
).to(torch.bfloat16).contiguous()
w = (torch.randn(B, T, HV, K, device="cuda", dtype=torch.bfloat16) * 0.05).contiguous()
v = torch.randn(B, T, HV, V, device="cuda", dtype=torch.bfloat16).contiguous()
g = (-torch.rand(B, T, HV, device="cuda", dtype=torch.float32) * 2.0).contiguous()
return k, w, v, g
def distinct_over_calls(B, T, HV, K, V, BT, BV, num_warps, num_stages, calls=10) -> int:
k, w, v, g = make_inputs(B, T, HV, K, V)
NT = triton.cdiv(T, BT)
grid = (triton.cdiv(V, BV), B * HV)
hashes = set()
for _ in range(calls):
h = k.new_empty(B, NT, HV, K, V)
v_new = torch.empty_like(v)
fwd_h_repro[grid](k, v, w, v_new, g, h, T, HV=HV, K=K, V=V, BT=BT, BV=BV,
num_warps=num_warps, num_stages=num_stages)
hashes.add((bithash(h), bithash(v_new)))
del h, v_new
return len(hashes)
def main() -> None:
HV, T, K, V, BT = 32, 67856, 128, 128, 64
print(f"sm={torch.cuda.get_device_capability()} dev={torch.cuda.get_device_name()} "
f"triton={triton.__version__} torch={torch.__version__}")
print(f"HV={HV} T={T} K={K} V={V} BT={BT} NT={triton.cdiv(T, BT)}")
print("\n--- config matrix (B=4, 10 identical calls each; distinct>1 = nondeterministic) ---")
fails = []
for BV in (32, 64):
for nw in (2, 4, 8):
for ns in (1, 2, 3, 4):
d = distinct_over_calls(4, T, HV, K, V, BT, BV, nw, ns)
grid_size = triton.cdiv(V, BV) * 4 * HV
tag = f"BV{BV}_w{nw}_s{ns}"
if d > 1:
fails.append(tag)
print(f" grid={grid_size:4d} {tag}: distinct={d}/10 {'<-- NONDETERMINISTIC' if d > 1 else ''}")
print("\n--- grid-size sweep for failing configs ---")
for BV, nw, ns in ((32, 4, 2), (32, 4, 3), (64, 4, 2), (32, 8, 2)):
line = f" BV{BV}_w{nw}_s{ns}: "
for B in (1, 2, 3, 4, 6):
d = distinct_over_calls(B, T, HV, K, V, BT, BV, nw, ns)
grid_size = triton.cdiv(V, BV) * B * HV
line += f"grid={grid_size}:{'FAIL' if d > 1 else 'ok'} "
print(line)
print("\nFAILING CONFIGS:", fails if fails else "none")
if __name__ == "__main__":
main()
```
### Environment details
| Component | Version |
|---|---|
| GPU | NVIDIA B300 SXM6 AC — sm_103 / capability (10, 3), 148 SMs |
| Driver | 580.126.09 (CUDA 13.0) |
| triton | **3.6.0** (torch 2.10.0+cu130) and **3.7.0** (torch 2.12.0+cu130) — both reproduce |
| dtype | bf16 inputs, fp32 accumulators |
Not reproducible on H200 (sm_90) — the identical downstream training pipeline is deterministic there.
关闭于 2026-06-15 9 条评论