Reduce redundant host_build_graph fanin edges - #2067
Conversation
A fanin edge P -> C carries readiness only. When another producer Q of C already reaches P, the chain P -> ... -> Q -> C orders C behind P on its own and the direct edge decides nothing: the device still scans it in classify_fanin_state and still moves C between wake lists for it. Both paths that build an edge now drop such edges, at the resolution each can afford. The global submit path publishes one 64-bit ancestor word per task, indexed by task local id. A submit folds its producers' words -- each shifted by that producer's distance, which is distance addition -- into its own, then drops every producer the fold covers. Two host_build_graph properties keep this to a single word of state: a task id is its slot index, handed out by a forward-only bump allocator and never reclaimed, so it doubles as the global submission order and a distance is a subtraction; and a producer's word, published by its own submit, is never rewritten, so reading it needs no proof that the slot still holds the task that wrote it. FANIN_REACH_WINDOW bounds the proof at 64 ids, and a producer further back keeps its edge. A recorded Graph body reduces into the Definition's own fanin CSR, and carries an exact closure rather than a window: a body is capped at MAX_IN_GRAPH_TASKS, so a row is a fixed 128 B and the array 128 KiB of recorder scratch, and a Graph is recorded once against however many replays read the shortened CSR. A producer arbitrarily far back in the body is therefore reduced too. The two compose without either knowing about the other -- a body is ordered against everything before the Graph by the outer Graph task's own fanin, and a producer outside the recording window contributes no in-body edge at all. Because each ancestor row is already a closure, a chain of any length collapses in one pass rather than one hop at a time. Reduction rewrites readiness only. A global producer's buffer lifetime rides last_consumer_local_id, raised when the edge was appended and never lowered here, so a producer whose edge is dropped still waits for that consumer to retire before the host may overwrite it. A body's buffers come out of the Graph's own heap and are released when the Graph completes, never per task, so a dropped in-body edge holds no lifetime either. Every dropped producer is reached from another producer with a strictly larger id, so following the cover relation up terminates at one nothing covers: a task with producers always keeps an edge. Both paths assert that, because emptying a fanin would make the device treat the task as a root and dispatch it against unfinished producers -- a data race rather than a hang. A SIMPLER_DFX build reports the edges built and dropped once per orchestration; each Definition logs its shipped and reduced edge counts at DEBUG as it is laid out.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe runtime now reduces redundant fanin edges for global tasks and recorded Graph tasks. It adds bounded and exact reachability storage, preserves consumer retention gates, reports reduction statistics, updates documentation, and adds unit tests for both architectures. ChangesFanin transitive reduction
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to The PR removes redundant dependency edges while preserving task ordering and buffer-lifetime behavior for ordinary submissions and recorded Graphs. No actionable merge-blocking risk remains beyond normal checks and review. Sequence Diagram(s)sequenceDiagram
participant TaskSubmit
participant Orchestrator
participant FaninReach
participant SharedMemory
TaskSubmit->>Orchestrator: submit task with producer list
Orchestrator->>FaninReach: read producer ancestor words
Orchestrator->>Orchestrator: remove covered producers
Orchestrator->>FaninReach: publish task ancestor word
Orchestrator->>SharedMemory: publish reduced fanin region
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 38 functions across 5 files. (3 skipped: 3 unsupported.)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Measured on a2a3: qwen3-14B decode and DeepSeek-V4 FLASH decodeFills in the Edge removal (static, noise-free)Counted from this PR's own DFX output — qwen3-14B decode
DeepSeek-V4 FLASH decode (per rank, 2 ranks)
Per-Definition for dsv4: 877→456 (×3), 876→456 (×2), 164→78, 169→98, 206→114. The two workloads have opposite shapes, which is the useful part:
For scale: #2009 measured 40 redundant edges total on qwen3 under TMR, of which End-to-end latency (dynamic)Neutral on both workloads. 6 independent samples per arm, 20 rounds each, qwen3-14B decode
DeepSeek-V4 FLASH decode
Every delta sits inside the run-to-run band: Welch t is 0.24σ for qwen3 Stated plainly: dsv4's Device row leans slow by 156 us. It is not A first pass ran the four arms strictly serially (base→head→base→head) and Why large static removal and neutral latency are consistentReduction preserves reachability exactly, so no task's earliest start time So the 48–78% of edges removed is real, and the work removed with them was What the measurement does establish is that the reduction costs nothing to The payoff scales with fanin density and with how much of the device window is |
Summary
A fanin edge
P -> Cin host_build_graph carries readiness only. When anotherproducer
QofCalready reachesP, the chainP -> ... -> Q -> CordersCbehindPon its own and the direct edge decides nothing — yet the devicestill scans it in
classify_fanin_stateand still movesCbetween wake listsfor it. Both paths that build an edge now drop those edges.
This is the host_build_graph counterpart of #2009, which does the same for
tensormap_and_ringbuffer. hbg reaches further for two reasons: it builds the
whole graph on the host before the device starts, so it sees redundancy a ring
runtime never gets to observe (a producer tmr had already reclaimed builds no
edge at all); and it reclaims nothing, so an edge is pure readiness and can be
dropped outright rather than demoted to a lifetime-only edge.
Two paths, two resolutions
submit_taskrecord_nodeFANIN_REACH_WINDOW)The global path publishes one ancestor word per task, indexed by task local id.
A submit folds its producers' words — each shifted by that producer's distance,
which is distance addition — into its own, then drops every producer the fold
covers. Two properties keep that to a single word of state: a task id is its
slot index, handed out by a forward-only bump allocator and never reclaimed, so
it doubles as the global submission order and a distance is a subtraction; and
a producer's word, published by its own submit, is never rewritten, so reading
it needs no proof that the slot still holds the task that wrote it. That is the
whole of what #2009 needs a
(bitmap, seq)entry and a pin protocol for.A recorded body carries the exact closure instead of a window, because a
body is capped at
MAX_IN_GRAPH_TASKS = 1024(so a row is a fixed 128 B) and aGraph is recorded once against however many replays read the shortened CSR. A
producer arbitrarily far back in the body is reduced too.
The two compose without either knowing about the other: a body is ordered
against everything before the Graph by the outer Graph task's own fanin, and a
producer outside the recording window contributes no in-body edge at all.
Because each ancestor row is already a closure, a chain of any length collapses
in one pass rather than one hop at a time.
What is preserved
Reachability. Every dropped producer is reached from another producer with a
strictly larger id, so following the cover relation up terminates at one nothing
covers. A task with producers therefore always keeps an edge, and both paths
assert it — emptying a fanin would make the device treat the task as a root and
dispatch it against unfinished producers, a data race rather than a hang.
Retention. Reduction rewrites readiness only. A global producer's buffer
lifetime rides
last_consumer_local_id, raised when the edge was appended andnever lowered here, so a producer whose edge is dropped still waits for that
consumer to retire before the host may overwrite it. A body's buffers come out
of the Graph's own heap and are released when the Graph completes, never per
task, so a dropped in-body edge holds no lifetime either.
Conservatism at the window edge. A global producer further back than
FANIN_REACH_WINDOWkeeps its edge and contributes no bit — it and all itsancestors are unrepresentable in one word.
Observability
A
SIMPLER_DFXbuild reports the edges built and dropped once perorchestration; each Definition logs its shipped and reduced edge counts at DEBUG
as it is laid out. On the a2a3sim
graph_executionscene a 5-task body ships 5internal edges with 2 reduced away.
Testing
test_hbg_fanin_reduction, 12 cases per arch (a2a3 + a5): diamond,multi-hop chain, independent producers kept, chained coverage still
leaving one edge, both sides of the window boundary, order-preserving
compaction, dropped-edge reclaim gate; and for recorded bodies the CSR
diamond, a chain longer than
FANIN_REACH_WINDOWstill reduced,independent body producers, and body roots left alone
(6/8 and 2/4 respectively; the conservative cases stay green, as they
should)
test_hbg_graph_submit_failureand its a5twin fail 3 cases each on a clean
upstream/mainwith the same build —pre-existing, unrelated to fanin
Graph-recording path
at 64 for this PR
Follow-up
Widening
FANIN_REACH_WINDOWpast one word is a constant-size change (the foldbecomes multiword) and now only affects tasks outside a Graph, since bodies
already carry an exact closure. Worth revisiting once an A/B says the extra
removals repay the per-submit work.