ADR-0024 Phase B (partial): scheduler-level collective drain

Root cause (hang diagnosis):
`kernel_runner.run()` captures `greenlet.getcurrent()` at spawn time as
the kernel greenlet's `_parent`. When a worker greenlet (say g0) calls
`dist.all_reduce` → `ctx.wait(h)` → `env.run(until=h0)`, the SimPy
scheduler steps pe_cpu processes, which in turn spawn kernel greenlets.
Those kernels' `_parent` becomes g0 (current greenlet at spawn). When a
kernel yields via switch_to_simpy, control jumps back up to g0's LAST
switch point — which is the main scheduler's `g.switch()` call — rather
than the kernel_runner's generator frame. Main then re-enters its
`for g in alive: g.switch()` loop mid-wait, producing nested greenlet
re-entry. Scheduler spins: g0 never completes, g1 appears to complete
out of order, infinite loop at 100% CPU.

Fix:
- AhbmCCLBackend.all_reduce: in multi-greenlet mode, submit via
  launch(_defer_wait=True), extend backend._pending_collective_handles,
  and yield to the parent greenlet. Worker does NOT call wait.
- benches/ccl_allreduce.py run(): after each scheduler round, the MAIN
  greenlet drains backend._pending_collective_handles. This keeps
  env.run invocation in the main context, so kernel_runner's spawned
  kernel greenlets have main as their _parent — no nested re-entry.
- Legacy single-driver path (no bench scheduler): all_reduce falls back
  to inline wait when g.parent is None.

Result:
- Multi-greenlet cross-SIP ring no longer hangs (was 100% CPU infinite
  loop in kernel_runner._switch_kernel).
- ring_default_ws still xfail(strict=True): now fails as a data
  correctness issue — DataExecutor reports only 1 math op for a 2-rank
  ring (expected 2). Cross-SIP op_log replay integration is the
  remaining Phase B task.

514 passed, 1 xfailed (strict).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 09:14:03 -07:00
parent 4ba0a83e71
commit 79124daab1
3 changed files with 42 additions and 15 deletions
+6 -7
View File
@@ -70,18 +70,17 @@ CASES = [
# Default fallback — no world_size override → ADR-0024 D1 derives
# from topology (SIP count = 2). Exercises the new SIP-level TP
# launcher + cross-SIP ring.
# XFAIL: ADR-0024 Phase A delivers launcher infrastructure; Phase B
# will finish cross-SIP ring kernel integration. Today this hangs in
# the SimPy drain despite ADR-0025's direction-addressing fix —
# suspected per-rank-tensor kernel_args / program_id mismatch under
# multi-greenlet dispatch. Separate Phase will diagnose.
# XFAIL: Phase A fix (scheduler-level wait) resolves the greenlet-
# re-entry hang, but Phase 2 DataExecutor still reports only 1 math
# op for a 2-rank ring (expected 2) — cross-SIP op_log replay
# integration pending ADR-0024 Phase B.
pytest.param(
"ring_allreduce_tcm", "kernbench.ccl.algorithms.ring_allreduce",
"ring_1d", "tcm", None, 8, 2,
id="ring_default_ws",
marks=pytest.mark.xfail(
reason="ADR-0024 Phase B: cross-SIP multi-greenlet kernel integration",
run=False, # skip execution to avoid hang; revisit in Phase B
reason="ADR-0024 Phase B: cross-SIP op_log replay integration",
strict=True,
),
),
# Buffer variants at 8-rank (fast — same kernel, different slot space).