Summary
SynthesizeAllReduceSignals currently rejects pld.tensor.allreduce inside a for/while loop (CheckAllReduceCall, repeating_scope_depth_ == 0). With the host collective builtin kernels now self-clearing (#2279 — every kernel restores its signal cells to zero after each call), the restriction is no longer justified by the barrier protocol: it exists only because the pass mints a fresh synthesized signal allocation per call, which cannot be placed per dynamic iteration. This issue tracks lifting the restriction — a single per-host_orch shared signal plus in-loop support — originally deferred from #2279 as a follow-up.
Motivation / Use Case
Proposed API / Behavior
for step in pl.range(num_steps):
data = pld.tensor.allreduce(data, signal, op=pld.ReduceOp.Sum) # explicit signal — allowed
and, once shared-signal synthesis lands, the fully ergonomic form:
for step in pl.range(num_steps):
data = pld.tensor.allreduce(data, op=pld.ReduceOp.Sum) # synthesized signal, hoisted to host_orch entry
Pass change in SynthesizeAllReduceSignals:
- Emit one synthesized signal per
host_orch function, allocated before the body exactly as MakeSignalBinding does today (world_size * 4 bytes → [world_size, 1] INT32 window), and pass it to every implicit-signal allreduce.
- Delete the
repeating_scope_depth_ == 0 check (explicit signals pass through unchanged).
- Keep the mesh
[NR, 1] vs ring [2*(NR-1)+1, NR] signal-shape guard (ValidateMeshSignalShape).
- Update the pass doc and the debugging "rejected inside loop" row (en/zh), and add loop + implicit-signal STs (convert the existing
test_host_tensor_allreduce_signal_reuse to a live pl.range loop).
Alternatives Considered
- Keep the restriction and require users to manage signals manually: rejected — signal-window management is the most error-prone part of the collective API and the HOST rail is the only rail still loop-restricted.
- Hoist only the explicit-signal case (move the check after the arg-count branch): a reasonable minimal first step, but the synthesized path needs the shared-signal synthesis to be fully ergonomic, so the shared-signal approach is preferred.
Additional Context
Summary
SynthesizeAllReduceSignalscurrently rejectspld.tensor.allreduceinside afor/whileloop (CheckAllReduceCall,repeating_scope_depth_ == 0). With the host collective builtin kernels now self-clearing (#2279 — every kernel restores its signal cells to zero after each call), the restriction is no longer justified by the barrier protocol: it exists only because the pass mints a fresh synthesized signal allocation per call, which cannot be placed per dynamic iteration. This issue tracks lifting the restriction — a single per-host_orchshared signal plus in-loop support — originally deferred from #2279 as a follow-up.Motivation / Use Case
repeating_scope_depth_ == 0check runs before theargs_.size() == 2branch).Proposed API / Behavior
and, once shared-signal synthesis lands, the fully ergonomic form:
Pass change in
SynthesizeAllReduceSignals:host_orchfunction, allocated before the body exactly asMakeSignalBindingdoes today (world_size * 4bytes →[world_size, 1]INT32 window), and pass it to every implicit-signal allreduce.repeating_scope_depth_ == 0check (explicit signals pass through unchanged).[NR, 1]vs ring[2*(NR-1)+1, NR]signal-shape guard (ValidateMeshSignalShape).test_host_tensor_allreduce_signal_reuseto a livepl.rangeloop).Alternatives Considered
Additional Context
feat/synthesize-allreduce-shared-signalonfork-gbisbas, previously verified in sim: UTs + allreduce basic/signal-reuse/loop STs P=2/P=4).