Skip to content

Fix: harden HBG Graph Execution across A2/A3 and A5 - #1767

Merged
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
zmnobug:fix/issue-1736-harden-hbg-graph-execution
Aug 11, 2026
Merged

Fix: harden HBG Graph Execution across A2/A3 and A5#1767
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
zmnobug:fix/issue-1736-harden-hbg-graph-execution

Conversation

@zmnobug

@zmnobug zmnobug commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Validate exact Graph wire-image lengths, embedded Definition bounds, and retained tensor-patch capacity during affine replay.
  • Make Graph activation one-shot and distinguish resolved internal nodes from host-visible stream-task completion.
  • Surface ready/prepare queue overflow and invalid Graph-node completion through idempotent scheduler shutdown.
  • Pin the fatal fanin and no-partial-upload invariants without demoting failed Graph slots to DUMMY, with matching A2/A3 and A5 regression coverage.

Testing

  • Rebuilt all runtimes after rebasing onto the latest upstream/main.
  • Relevant C++ unit tests: 4 passed.
  • A2/A3 HBG simulation: 28 passed, 4 skipped.
  • A5 HBG simulation: 18 passed.
  • Onboard hardware CI (A5 hardware is not available locally).

Fixes #1736

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5789afa9-a11f-412d-9d3c-514bffbc3496

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change hardens A2A3 and A5 host-build-graph execution. It validates graph submissions and replay state, centralizes scheduler failure handling, separates resolution from host completion, adds graph-slot rollback, documents error 104, and adds parallel regression tests.

Changes

Host-build-graph hardening

Layer / File(s) Summary
Graph submission and execution validation
src/{a2a3,a5}/runtime/host_build_graph/{host,runtime}/..., tests/ut/cpp/{a2a3,a5}/test_graph_cache.cpp
Uploads and embedded definitions now require valid bounds and exact wire sizes. Affine replay checks patch bounds. Activation signaling is one-shot. Execution layout assertions were added.
Scheduler failure and completion flow
src/{a2a3,a5}/runtime/host_build_graph/runtime/{pto_async_wait.h,scheduler}/..., src/common/runtime_status/error_names.h, docs/troubleshooting/device-error-codes.md, tests/ut/cpp/{a2a3,a5}/test_graph_cache.cpp
Ready-queue, completion, materialization, and resolution failures now use centralized shutdown handling. Completion polling reports resolved tasks separately from host tasks. Error 104 mappings and diagnostics were added.
Graph submission rollback and regression coverage
src/{a2a3,a5}/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp, tests/ut/cpp/CMakeLists.txt, tests/ut/cpp/common/test_hbg_graph_submit_rollback.cpp
Failed fanin computation converts an uncommitted graph slot to DUMMY. A2A3 and A5 rollback test targets verify this behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GraphSubmit
  participant PTO2SchedulerState
  participant SchedulerContext
  participant Runtime
  GraphSubmit->>PTO2SchedulerState: enqueue graph preparation
  PTO2SchedulerState->>PTO2SchedulerState: detect queue or completion error
  PTO2SchedulerState->>SchedulerContext: call fail_scheduler
  SchedulerContext->>Runtime: trigger one-time emergency shutdown
Loading

Possibly related issues

Possibly related PRs

Poem

A rabbit checks each graph byte,
And guards the queue through day and night.
Failed fanin leaves a dummy trace,
Resolved hops count at their own pace.
Two runtimes now thump proud and bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the cross-architecture hardening of HBG Graph Execution for A2/A3 and A5.
Description check ✅ Passed The description directly explains the hardening changes, regression coverage, testing results, and hardware CI limitation.
Linked Issues check ✅ Passed The changes implement the linked issue objectives for validation, scheduler error handling, rollback, one-shot activation, and matching A2/A3 and A5 tests.
Out of Scope Changes check ✅ Passed The documentation, error mappings, runtime changes, and tests support the linked hardening objectives without evident unrelated scope.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/troubleshooting/device-error-codes.md`:
- Around line 32-35: Update the runtime execution description in the section
containing host_build_graph and tensormap_and_ringbuffer: state that
host_build_graph performs orchestration on the host and transfers the prepared
image to the AICPU scheduler, while preserving the existing description of
tensormap_and_ringbuffer.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 995ae446-d35c-4e0e-9f2e-d4a5ba4c5bf2

📥 Commits

Reviewing files that changed from the base of the PR and between 8616c73 and a69a75b.

📒 Files selected for processing (24)
  • docs/troubleshooting/device-error-codes.md
  • src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp
  • src/a2a3/runtime/host_build_graph/runtime/graph_execution.h
  • src/a2a3/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp
  • src/a2a3/runtime/host_build_graph/runtime/pto_async_wait.h
  • src/a2a3/runtime/host_build_graph/runtime/scheduler/graph_execution.cpp
  • src/a2a3/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h
  • src/a2a3/runtime/host_build_graph/runtime/scheduler/scheduler_cold_path.cpp
  • src/a2a3/runtime/host_build_graph/runtime/scheduler/scheduler_context.h
  • src/a2a3/runtime/host_build_graph/runtime/scheduler/scheduler_dispatch.cpp
  • src/a5/runtime/host_build_graph/host/runtime_maker.cpp
  • src/a5/runtime/host_build_graph/runtime/graph_execution.h
  • src/a5/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp
  • src/a5/runtime/host_build_graph/runtime/pto_async_wait.h
  • src/a5/runtime/host_build_graph/runtime/scheduler/graph_execution.cpp
  • src/a5/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h
  • src/a5/runtime/host_build_graph/runtime/scheduler/scheduler_cold_path.cpp
  • src/a5/runtime/host_build_graph/runtime/scheduler/scheduler_context.h
  • src/a5/runtime/host_build_graph/runtime/scheduler/scheduler_dispatch.cpp
  • src/common/runtime_status/error_names.h
  • tests/ut/cpp/CMakeLists.txt
  • tests/ut/cpp/a2a3/test_graph_cache.cpp
  • tests/ut/cpp/a5/test_graph_cache.cpp
  • tests/ut/cpp/common/test_hbg_graph_submit_rollback.cpp

Comment thread docs/troubleshooting/device-error-codes.md Outdated
@ChaoZheng109

Copy link
Copy Markdown
Collaborator

Question on GraphSlotRollback — does it guard a path that's actually reachable?

I traced the fanin-failure path and I think the residual outer slot can never reach execution once fatal is latched, which would make the DUMMY demotion (and GraphSlotRollback itself) unnecessary today. Wanted to sanity-check this reasoning.

The failure is host-side, during orchestration: append_fanin_or_fail calls orch_mark_fatal(DEP_POOL_OVERFLOW) and then returns false, so orch->fatal is set before graph_submit_definition returns. From there:

  • graph_end returns false on the failed graph_submit_definition — no dispatch.
  • end_scope / begin_scope / submit_task all short-circuit with if (orch->fatal) return;.
  • The only path that submits scope tasks to the scheduler is scheduler->on_scope_end(...) inside end_scope (pto_orchestrator.cpp), and end_scope bails at the top on fatal — so the residual slot is never submitted.
  • The residual slot is not in pending_uploads (the early return precedes push_back), so upload_graph_submissions never ships it.
  • destroy() only nulls the scope_tasks / scope_begins pointers; it doesn't walk them or interpret task_kind.

So as far as I can see, after a fanin failure the half-built GRAPH slot is never seen by the scheduler and never uploaded — it just sits in a fatal orchestrator until teardown. The DUMMY conversion doesn't appear to guard a path that fires.

If that holds, I'd lean toward dropping GraphSlotRollback rather than demoting to DUMMY:

  1. The demotion doesn't fix a correctness/executability problem — fatal already isolates the slot.
  2. ~GraphSlotRollback() is unconditional: it doesn't check that fatal was actually set. If a future change adds a non-fatal early return between construction and commit(), the slot would be silently demoted to DUMMY and the orchestrator would keep running, masking the failure — and leaking a window slot, since the task allocator has no per-slot free(). That silent-degradation shape is exactly what we don't want in the dev phase.

The invariant genuinely worth pinning is "fanin failure ⇒ fatal + no partial upload," which the test already checks via EXPECT_EQ(graph_host_upload_count(...), uploads_before_failure). The task_kind == DUMMY assertion is the part I'd drop together with the rollback.

If I've missed a path that does reach the residual slot — e.g. the AICPU scheduler scanning the ring independently of on_scope_end, or an arch where end_scope isn't fatal-gated — happy to be corrected; I just couldn't find one on a2a3/a5 host_build_graph.

- Validate Graph wire images and affine replay patch bounds
- Separate internal node resolution from host task completion
- Surface queue overflow and invalid completion via scheduler shutdown
- Pin fatal fanin and no-partial-upload invariants on A2/A3 and A5
@zmnobug
zmnobug force-pushed the fix/issue-1736-harden-hbg-graph-execution branch from a8fd522 to b2a205d Compare August 11, 2026 09:06
@zmnobug

zmnobug commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@ChaoZheng109 Thanks for tracing this path. You are right: append_fanin_or_fail latches fatal before returning false, fatal prevents scope submission, the failed Graph submission never enters pending_uploads, and the device scheduler observes the orchestration error before task classification. The DUMMY conversion therefore did not protect a reachable execution path and could mask a future non-fatal early return.

Updated in b2a205d:

  • Removed GraphSlotRollback and the DUMMY demotion from both A2/A3 and A5.
  • Renamed the regression to FaninFailureLatchesFatalWithoutPartialUpload.
  • Kept the meaningful assertions: fatal is latched with DEP_POOL_OVERFLOW, the replay is not submitted, and no partial upload is added.
  • Rebased onto the latest main, including the new A5 off-ring first-invocation path from hbg: record Graph off the ring on A5 so the first invocation is one GRAPH task #1780.

Validation on the rebased result: 4 relevant C++ tests passed; A2/A3 HBG simulation passed with 28 passed / 4 skipped; A5 HBG simulation passed with 18 passed.

@ChaoZheng109
ChaoZheng109 merged commit 0a1525f into hw-native-sys:main Aug 11, 2026
19 checks passed
SergioMartin86 added a commit to huawei-csl/simpler that referenced this pull request Aug 13, 2026
The on-device Graph scheduler expanded a GRAPH task into all its nodes before
dispatching any of them: graph_execution_materialize_slice reached PREPARED only
after the last node, and activate_prepared_graph routed the roots only at that
point. For a large graph the AICores idle through the whole expansion — on the
qwen3-14B 3-layer decode that front-loaded ramp is ~530 us per layer during
which every core waits.

Route roots (and any node whose producers have all completed) as soon as they
materialize, once the outer Graph task's external-dependency gate has opened,
so compute overlaps the remaining expansion.

This is possible only because of the polling-completion scheduler: the per-slot
completion_flags byte + task_state completion mirror introduced for
tensormap_and_ringbuffer in hw-native-sys#1137 and adapted to host_build_graph in hw-native-sys#1435. That
flag lets a consumer discover a producer that completed mid-expansion by reading
its state instead of relying solely on the producer's wake list. Under the older
pure wake-list model, dispatching before full materialization would have lost
wakes if a producer finished before its consumer registered — which is exactly
why the whole-graph PREPARED gate existed.

- Materialization-time registration moves from register_initial_graph_waiter (a
  raw wake-list store that assumed no producer could complete during expansion)
  to register_graph_wake, which resolves a producer that completed mid-expansion
  through its task_state completion flag (the hw-native-sys#1137 / hw-native-sys#1435 mechanism) instead of
  losing the wake. The graph's topological node order guarantees every producer a
  node references is already constructed.
- execution.nodes is published at MATERIALIZING (under materialize_busy) so the
  wake path can read producer slots while the graph is still expanding.
- Roots reach the ready queue through route_cursor, a monotonic per-execution
  cursor that makes routing idempotent across the per-slice calls and the final
  call at the activation meet, so each root is pushed exactly once. Non-roots
  reach the queue only through their producers' wake list, unchanged.

Because a node now dispatches before the graph reaches ACTIVE, it can complete
while the graph is still MATERIALIZING or PREPARED. hw-native-sys#1767's complete_task rejects
a node completion unless the graph is ACTIVE; that guard now accepts MATERIALIZING
and PREPARED as well, rejecting only SUBMITTED (execution not yet localized) and
COMPLETED (already retired). Without this, the largest graphs (longest
materialization tail) fault with sched_error_code=5 INVALID_ARGS while the small
graph_execution scene tests, which reach ACTIVE before any node completes, pass.

Correctness: the three graph_execution scene tests (2D replay, AIC+AIV,
MIX-SPMD) and the qwen3-14B 3-layer graph-execution golden pass, and the qwen3-14B
40-layer host-graph decode (the largest graph) passes 6/6 where it faulted 0/6
without the completion-state fix. Three host-side unit tests
(tests/ut/cpp/{a2a3,a5}/test_graph_activation.cpp) cover the interleavings
deterministically: a consumer registered after its producer completed and drained
routes through the task_state re-scan; graph_incremental_publish routes
all-producers-complete consumers and wake-chains the rest; and complete_task
accepts a node completion in MATERIALIZING/PREPARED/ACTIVE and rejects it in
SUBMITTED/COMPLETED.

Applies the identical change to the a5 host_build_graph sibling (same graph
scheduler; a5 is compile-verified, not perf-measured on this a2a3 box).
ChaoWao pushed a commit that referenced this pull request Aug 14, 2026
…ize (#1804)

The on-device Graph scheduler expanded a GRAPH task into all its nodes before
dispatching any of them: graph_execution_materialize_slice reached PREPARED only
after the last node, and activate_prepared_graph routed the roots only at that
point. For a large graph the AICores idle through the whole expansion — on the
qwen3-14B 3-layer decode that front-loaded ramp is ~530 us per layer during
which every core waits.

Route roots (and any node whose producers have all completed) as soon as they
materialize, once the outer Graph task's external-dependency gate has opened,
so compute overlaps the remaining expansion.

This is possible only because of the polling-completion scheduler: the per-slot
completion_flags byte + task_state completion mirror introduced for
tensormap_and_ringbuffer in #1137 and adapted to host_build_graph in #1435. That
flag lets a consumer discover a producer that completed mid-expansion by reading
its state instead of relying solely on the producer's wake list. Under the older
pure wake-list model, dispatching before full materialization would have lost
wakes if a producer finished before its consumer registered — which is exactly
why the whole-graph PREPARED gate existed.

- Materialization-time registration moves from register_initial_graph_waiter (a
  raw wake-list store that assumed no producer could complete during expansion)
  to register_graph_wake, which resolves a producer that completed mid-expansion
  through its task_state completion flag (the #1137 / #1435 mechanism) instead of
  losing the wake. The graph's topological node order guarantees every producer a
  node references is already constructed.
- execution.nodes is published at MATERIALIZING (under materialize_busy) so the
  wake path can read producer slots while the graph is still expanding.
- Roots reach the ready queue through route_cursor, a monotonic per-execution
  cursor that makes routing idempotent across the per-slice calls and the final
  call at the activation meet, so each root is pushed exactly once. Non-roots
  reach the queue only through their producers' wake list, unchanged.

Because a node now dispatches before the graph reaches ACTIVE, it can complete
while the graph is still MATERIALIZING or PREPARED. #1767's complete_task rejects
a node completion unless the graph is ACTIVE; that guard now accepts MATERIALIZING
and PREPARED as well, rejecting only SUBMITTED (execution not yet localized) and
COMPLETED (already retired). Without this, the largest graphs (longest
materialization tail) fault with sched_error_code=5 INVALID_ARGS while the small
graph_execution scene tests, which reach ACTIVE before any node completes, pass.

Correctness: the three graph_execution scene tests (2D replay, AIC+AIV,
MIX-SPMD) and the qwen3-14B 3-layer graph-execution golden pass, and the qwen3-14B
40-layer host-graph decode (the largest graph) passes 6/6 where it faulted 0/6
without the completion-state fix. Three host-side unit tests
(tests/ut/cpp/{a2a3,a5}/test_graph_activation.cpp) cover the interleavings
deterministically: a consumer registered after its producer completed and drained
routes through the task_state re-scan; graph_incremental_publish routes
all-producers-complete consumers and wake-chains the rest; and complete_task
accepts a node completion in MATERIALIZING/PREPARED/ACTIVE and rejects it in
SUBMITTED/COMPLETED.

Applies the identical change to the a5 host_build_graph sibling (same graph
scheduler; a5 is compile-verified, not perf-measured on this a2a3 box).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Code Health] Harden HBG Graph Execution across A2/A3 and A5

2 participants