[Performance] A2/A3/A5 host_build_graph: skip redundant orchestrator init in arena build - #1763
Conversation
📝 WalkthroughWalkthroughThe runtime layout setup no longer initializes the orchestrator or handles that initialization failure. The orchestrator remains zero-initialized until ChangesRuntime initialization
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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 |
f8bd118 to
0e6eb15
Compare
|
The optimization itself is correct and well-covered — I verified the four safety pillars (single caller of the 8-arg One ask: please apply the same change to a5 There is no divergence here — I checked, a5 has the identical double-init pattern:
So the same removal + the matching doc-comment update in |
runtime_init_data_from_layout initialized the orchestrator against the device SM while building the prebuilt arena, but its only caller (the host bind path in runtime_maker) immediately re-initializes the orchestrator against the host SM in run_host_orchestration, once the host SM buffer is allocated, then relocates it for the device. The first init is therefore dead work: its arena content (the tensormap and fanin_seen_epoch memsets) is overwritten by the re-init, and the orchestrator arena block is not uploaded to the device at all (the AICPU boots scheduler-only, per hw-native-sys#1659). The AICPU never calls runtime_init_data_from_layout, so no device path depends on it either. Initialize only the scheduler here and let run_host_orchestration own the single orchestrator init. rt->orchestrator stays zeroed (from the existing memset) until that point; the intervening runtime_wire_arena_pointers only sets pointers and does not read orchestrator state. Effect (paged_attention, a2a3 onboard, median of 50 rounds): the arena-build phase of bind drops from ~2565 us to ~1373 us (-46%) by removing the redundant tensormap init, cutting host bind wall from ~8677 us to ~7494 us (-13.6%). Device time unchanged. Applies the identical change to the a5 host_build_graph sibling (near-duplicate tree, same double-init pattern, no arch-level divergence): the orchestrator init removed from runtime_init_data_from_layout plus the matching doc-comment updates in pto_runtime2.h and pto_runtime2_init.cpp. All four safety invariants hold on a5 as on a2a3 -- single caller of the 8-arg runtime_init_data_from_layout (the host bind path in runtime_maker), the surviving orchestrator re-init against the host SM in run_host_orchestration, the orchestrator arena block never uploaded, and runtime_wire_arena_pointers between the two only setting pointers. a5 is compile-verified (host lib links clean); not perf-measured, as this box is a2a3 silicon. Testing: full host_build_graph golden suite (22 passed, 2 skipped; the 2 failures, run_stream_reuse and worker_async_fifo, reproduce identically on a baseline build and are unrelated -- arena-bank-commit path, untouched here). Adds a concurrent_prepare_stress white-box test that drives a two-deep overlapping pipeline over both arena banks (each run prepared while its predecessor is active), golden-checked over many iterations; it passes identically on this change and a baseline build, exercising the concurrent prepare/bind path this change touches.
0e6eb15 to
a2a4244
Compare
Done — applied the identical change to a5 host_build_graph in this PR (same commit, pto_runtime2_init.cpp + pto_runtime2.h). Agreed there's no divergence; I re-verified all four safety pillars on a5:
The removed init and the two doc-comment updates match the a2a3 change verbatim. a5 is compile-verified only (host lib links clean; the dev box is a2a3 silicon, so the perf numbers stay a2a3); all pre-commit hooks pass on all six files. The concurrent_prepare_stress test stays under tests/st/a2a3/ since it exercises the shared bind/prepare logic rather than anything arch-specific. Kept it a single commit; force-pushed. Will bundle the a5 sibling in the same change for HBG work going forward. |
Human Summary
The run_host_orchestration() function was being called twice. This PR removes the redundant call.
Applies to A2/A3/A5.
AI Summary
PR notes —
host_build_graph: skip redundant orchestrator init in arena buildhbg-skip-redundant-orch-init(commita2a42446, offupstream/maincf0fbc06)mirrored across a2a3 and a5, plus a concurrent-prepare white-box stress test
(
concurrent_prepare_stress/+ isolated-worker conftest).host_build_graphon both a2a3 and a5 (see a5 parity)hbg-ready-queue-8192(different file; either merge order works).TL;DR
The host bind path initialized the orchestrator twice per run: once against
the device SM while building the prebuilt arena, then again against the host SM
in
run_host_orchestration. The first init is dead work — its arena content isoverwritten by the second, and the orchestrator arena block is never uploaded to
the device. Skipping it cuts the
arena_buildphase of bind by ~46% (theredundant tensormap memset), trimming host bind wall −13.6% on paged_attention
(8677 → 7494 µs), device time unchanged, golden-clean.
The double-init
Two calls to
PTO2OrchestratorState::init_data_from_layouthappen per bind:runtime_init_data_from_layout(pto_runtime2_init.cpp) callsrt->orchestrator.init_data_from_layout(..., sm_dev_base, ...)against thedevice SM while assembling the prebuilt arena image.
run_host_orchestration(runtime_maker.cpp) callsrt->orchestrator.init_data_from_layout(..., host_sm, ...)against the hostSM, because the orchestrator's submit loop runs on the host and writes task
descriptors into a host-side SM buffer that is allocated only at that point.
After the submit loop the image is relocated (host→device pointers) and the
live prefix is uploaded.
init_data_from_layoutdoes two kinds of work: arena work (thefanin_seen_epochmemset +tensor_map.init_data_from_layout— the expensive,SM-independent part) and SM-pointer setup (
task_allocator.init,sm_header— cheap, SM-specific). Both inits memset the same arena regions; only the SM
pointers differ. So the first init's expensive arena work is entirely redone by
the second.
Why the first init is safe to drop
runtime_init_data_from_layoutis called fromexactly one place — the host bind path (
runtime_maker.cpp). The AICPU bootsscheduler-only (per [Performance] HBG: up to 99% host-side overhead reduction #1659) and never calls it, so no device path depends on it.
run_host_orchestrationright after, which re-inits the orchestrator againstthe host SM. The first init's result never survives.
H2D upload, so whatever the first init wrote there does not reach the device.
runtime_wire_arena_pointersonly sets pointers (it does not read orchestrator state), and
rt->orchestratoris already zeroed by thememset(rt, 0, ...)at the top ofruntime_init_data_from_layout. So leaving it zeroed untilrun_host_orchestrationis well-defined.The change initializes only the scheduler in
runtime_init_data_from_layoutandlets
run_host_orchestrationown the single orchestrator init.a5 parity
The change is mirrored into the a5
host_build_graphsibling in the samecommit — a2a3 and a5 are near-duplicate trees with no architectural divergence
here. All four safety pillars above were re-verified on a5:
runtime_init_data_from_layoutis called fromexactly one place, the a5 host bind path (
src/a5/.../host/runtime_maker.cpp);the AICPU never calls it.
run_host_orchestrationre-inits theorchestrator against the host SM (
src/a5/.../host/runtime_maker.cpp), same asa2a3.
block.
runtime_wire_arena_pointersonly setspointers.
Files:
src/a5/.../runtime/shared/pto_runtime2_init.cpp(the orchestrator initremoved from the 8-arg
runtime_init_data_from_layout, replaced by the sameexplanatory comment) and
src/a5/.../runtime/pto_runtime2.h(matching doccomment). Verification: a5 (and a2a3) host libs compile + link clean; all
pre-commit hooks pass on all six files. a5 is compile-verified only — not
perf-measured, as the dev box is a2a3 silicon (the numbers below are a2a3). The
concurrent_prepare_stresstest lives undertests/st/a2a3/and exercises theshared bind/prepare logic; it is not duplicated per-arch.
Going forward, HBG optimizations land in both arches in one commit unless there
is a genuine arch-level difference.
Measurement
a2a3 onboard, paged_attention, median of 50 rounds, from the
[STRACE]bind spantree (default capacity 65536; this PR does not change the queue size).
orch_reinit(the host-SM init the submit loop actually needs) is untouched, asexpected — this PR removes only the duplicate. Device time is flat.
Correctness
Full
host_build_graphgolden suite (pytest tests/st/a2a3/host_build_graph/,a2a3 onboard): 22 passed, 2 skipped, 2 failed. Both failures are
pre-existing and unrelated, verified by rebuilding the runtime source at
cf0fbc06baseline (orch init present) with a matching binding — both reproduceidentically:
run_stream_reuse::test_depth_two_slots_own_separate_resources— "a servedbank is uncommitted: …, 0x0" (arena-bank-commit path; already tracked).
worker_async_fifo::TestWorkerAsyncWholeRunFifo::test_run—507901finalize/hdc-disconnect (same native-run pipeline-slot / arena-bank family).
The diff touches neither path (
ChipWorker::arena_bank_gm_heap_base/ native-runfinalize are untouched), and the pass/fail set is identical with and without the
change.
Concurrent-prepare stress (added by this PR)
Because the orchestrator init is per-run scratch, the one place worth exercising
is concurrent prepare — a successor prepared (bound) into bank B while a
predecessor is still executing in bank A (
overlaps_active_run). Ordinaryblocking
run()never hits it, and the two existing tests that do(
run_stream_reuse,worker_async_fifo) are the pre-existing-broken ones, sothey cannot confirm safety.
concurrent_prepare_stress/adds a white-box test that drives a 2-deep pipelineover both banks through the canonical direct-chip lane
(
_submit_chip_run_direct, the exact path production L2Worker.runuses):40 iterations, distinct per-iteration inputs, each result golden-checked, so any
cross-bank orchestrator interference would fail. It passes identically on this
change and a baseline build (orch init present) — confirming the test is sound
and the change is safe under concurrent prepare. This is safe by construction:
each run has its own arena bank → its own
PTO2Runtime/orchestrator; theorchestrator block is never uploaded to the device; and nothing reads it between
the (removed) init and the surviving host-SM init.
Scope & follow-ups
host_build_graph. The same double-init existed in a5 HBG and isfixed in this same commit (see a5 parity); a5 is compile-verified,
perf-measured on a2a3.
orch_reinit(~1.3 ms) is now the largest single bindsub-phase — the surviving orchestrator init (tensormap memset against host
SM). Reducing it (bounded tensormap, or a persistent-zeroed pool that the
submit loop cleans up) is the logical successor, and harder.
Reproduce
python tests/st/a2a3/host_build_graph/paged_attention/test_paged_attention.py \ -p a2a3 -d <dev> --rounds 50 --skip-golden python -m simpler_setup.tools.strace_timing <captured-stderr> --tree # Host wall + bind sub-phases cmake --build build/cache/a2a3/onboard/host_build_graph/host -j"$(nproc)" cmake --build build/cache/a2a3/onboard/host_build_graph/aicpu -j"$(nproc)" # a5 compile-verify (no a5 silicon on this box, so build-only): cmake --build build/cache/a5/onboard/host_build_graph/host -j"$(nproc)" # Concurrent-prepare overlap stress: python -m pytest tests/st/a2a3/host_build_graph/concurrent_prepare_stress/ \ --platform a2a3 --device <dev>