Skip to content

Fix: port hbg host-overhead reduction (#1659) to a5 - #1759

Merged
ChaoZheng109 merged 2 commits into
hw-native-sys:mainfrom
ChaoZheng109:fix/issue-1716-hbg-port-1659-a5
Aug 11, 2026
Merged

Fix: port hbg host-overhead reduction (#1659) to a5#1759
ChaoZheng109 merged 2 commits into
hw-native-sys:mainfrom
ChaoZheng109:fix/issue-1716-hbg-port-1659-a5

Conversation

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

Summary

Ports the host_build_graph per-dispatch host-overhead reduction from #1659 (a2a3-only) onto the a5 tree, file-for-file. HBG is one runtime kept in sync across two arch trees (#1706); a5 still carried the full pre-#1659 code, so it paid the same host bind cost the a2a3 change eliminated.

Fixes #1716.

What changed (mirrors #1659)

  • Bounded SM H2D upload — the host SM is allocated uninitialized with only the header zeroed; descriptors / payloads / slot_states / completion_flags are each uploaded bounded to [0, total_tasks). total_tasks is range-checked before it sizes the copies.
  • Init-on-write — per-slot reset_for_reuse() + completion-flag clear move from the boot-time whole-window loop into orch::prepare_task as each slot is claimed; the unclaimed tail is neither initialized, uploaded, nor read.
  • Skip the host-only orchestrator block (fanin_seen_epoch / scope_tasks / TensorMap) from the arena H2D, guarded by always_assert(orch_start <= orch_end).
  • Named overflow errorpush_ready_routed latches PTO2_ERROR_READY_QUEUE_OVERFLOW instead of dropping a ready task into an anonymous stall.
  • Tensormap reset dedup — drop the per-entry stores the preceding memset already zeroed.
  • Docs (RUNTIME_LOGIC.md §3.1) and the reset_for_reuse doc/comment sites updated to the init-on-write model.

Caveat resolved during the port

Per the issue and #1659, the ready queues ship in full (not bounded), mirroring a2a3 exactly, so the two trees stay identical for #1715 to build on. a5 does not bound them locally just because graph execution is not yet on a5.

Test / Validation

  • test_hbg_submit_poison.cpp added for a5 — pins the "every device-read SM field is written at submit" contract (fills the window with 0xAA poison, submits a representative mix, asserts no claimed slot reads poison).
  • cpput: 92/92 pass.
  • a5sim hbg scene tests: 8/8 pass (vector_example, paged_attention, prepared_callable).
  • Runtimes: a5 + a5sim host_build_graph compile clean.
  • a5 onboard not run — this box is a2a3 silicon (npu-smi/driver unavailable); host-side path validated via a5sim.

🤖 Generated with Claude Code

@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: 4a51ba78-22a5-4327-8d8a-92c25155618c

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 A5 host build graph now performs task-bounded uploads, initializes claimed slots during preparation, reports ready-queue overflow, and validates submission-time initialization with a poison test.

Changes

A5 host build graph runtime

Layer / File(s) Summary
Bounded uploads and claim-time initialization
src/a5/runtime/host_build_graph/host/runtime_maker.cpp, src/a5/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp, src/a5/runtime/host_build_graph/runtime/shared/*, src/a5/runtime/host_build_graph/runtime/pto_*.h, src/a5/runtime/host_build_graph/docs/RUNTIME_LOGIC.md
Shared-memory uploads use live task prefixes. Runtime-arena uploads omit host-only orchestration scratch space. Claimed slots reset reusable state and clear completion flags during prepare_task.
Ready-queue overflow reporting
src/a5/runtime/host_build_graph/common/pto_runtime_status.h, src/a5/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h
Ready-queue insertion failures latch PTO2_ERROR_READY_QUEUE_OVERFLOW as the scheduler error.
Poisoned submission validation
tests/ut/cpp/CMakeLists.txt, tests/ut/cpp/a5/test_hbg_submit_poison.cpp
The new test poisons shared-memory fields, submits representative tasks, and verifies that claimed device-read fields contain valid values.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

  • #1706 — Tracks the broader HBG runtime work that includes scheduler error handling and capacity-exhaustion testing.
  • #1736 — Covers ready-queue insertion failure reporting for the A5 scheduler.

Possibly related PRs

Poem

A rabbit found the slots in bloom,
And bounded bytes across the room.
Queues now speak when they are full,
While poisoned fields prove each pull.
The A5 graph hops clean and bright. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% 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 identifies the port of the host-overhead reduction from #1659 to the a5 HBG runtime.
Description check ✅ Passed The description directly explains the a5 port, its implementation changes, validation results, and testing limitation.
Linked Issues check ✅ Passed The changes implement all coding objectives in #1716, including bounded uploads, init-on-write, layout checks, overflow reporting, documentation, and the poison test.
Out of Scope Changes check ✅ Passed The changes remain within #1716 scope and support the required a5 port without unrelated code or behavior changes.

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 `@tests/ut/cpp/a5/test_hbg_submit_poison.cpp`:
- Around line 135-165: Extend the validation loop around ring.task_descriptors,
ring.task_payloads, and ring.slot_states to assert that st.task and st.payload
are bound to the corresponding descriptor and payload objects for each slot.
Compare both bindings against the addresses of desc and pl, preserving the
existing state and field checks.
🪄 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: 831fbee6-b201-4791-a15e-c707ff1ee7fd

📥 Commits

Reviewing files that changed from the base of the PR and between 69a73e9 and cd5a110.

📒 Files selected for processing (12)
  • src/a5/runtime/host_build_graph/common/pto_runtime_status.h
  • src/a5/runtime/host_build_graph/docs/RUNTIME_LOGIC.md
  • src/a5/runtime/host_build_graph/host/runtime_maker.cpp
  • src/a5/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp
  • src/a5/runtime/host_build_graph/runtime/pto_runtime2_types.h
  • src/a5/runtime/host_build_graph/runtime/pto_shared_memory.h
  • src/a5/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h
  • src/a5/runtime/host_build_graph/runtime/shared/pto_runtime2_init.cpp
  • src/a5/runtime/host_build_graph/runtime/shared/pto_shared_memory.cpp
  • src/a5/runtime/host_build_graph/runtime/shared/pto_tensormap.cpp
  • tests/ut/cpp/CMakeLists.txt
  • tests/ut/cpp/a5/test_hbg_submit_poison.cpp

Comment on lines +135 to +165
// Every claimed slot's device-read fields must carry real values, not poison.
for (int32_t local = 0; local < total; local++) {
SCOPED_TRACE(testing::Message() << "slot local_id=" << local);
const int32_t slot = ring.get_slot_by_task_id(local);
const PTO2TaskDescriptor &desc = ring.task_descriptors[slot];
const PTO2TaskPayload &pl = ring.task_payloads[slot];
const PTO2TaskSlotState &st = ring.slot_states[slot];

// Descriptor: the task id is written to this exact local id.
EXPECT_EQ(desc.task_id.local(), static_cast<uint32_t>(local));
// task_state is written at submit (reset_for_reuse skips it): PENDING for a
// dispatchable task, COMPLETED for a pre-completed hidden-alloc. Either way a
// real enum, never poison.
const PTO2TaskState state = st.task_state.load(std::memory_order_relaxed);
EXPECT_TRUE(state == PTO2_TASK_PENDING || state == PTO2_TASK_COMPLETED);
// Completion flag is written to a real 0/1 (pending vs pre-completed), not a
// poison byte (0xAA).
const uint8_t cflag = ring.completion_flags[slot].load(std::memory_order_relaxed);
EXPECT_LE(cflag, uint8_t{1});
// Payload counts are real, not the poison bit pattern.
EXPECT_GE(pl.fanin_count, 0);
EXPECT_LE(pl.fanin_count, PTO2_MAX_FANIN);
EXPECT_GE(pl.tensor_count, 0);
EXPECT_GE(pl.scalar_count, 0);
// predicate.op is a dispatch-time field, read only for tasks the device
// actually dispatches. submit_task_common writes it (NONE when unset); a
// pre-completed hidden-alloc is never dispatched, so it does not.
if (state == PTO2_TASK_PENDING) {
EXPECT_LE(static_cast<uint8_t>(pl.predicate.op), static_cast<uint8_t>(PredicateOp::LE));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the task and payload bindings.

The loop does not check st.task or st.payload. If bind_buffers() does not write either field, the poisoned pointer remains and this test passes. The device can then dereference an invalid slot pointer.

Proposed test addition
         const PTO2TaskSlotState &st = ring.slot_states[slot];

+        EXPECT_EQ(st.task, &ring.task_descriptors[slot]);
+        EXPECT_EQ(st.payload, &ring.task_payloads[slot]);
+
         // Descriptor: the task id is written to this exact local id.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Every claimed slot's device-read fields must carry real values, not poison.
for (int32_t local = 0; local < total; local++) {
SCOPED_TRACE(testing::Message() << "slot local_id=" << local);
const int32_t slot = ring.get_slot_by_task_id(local);
const PTO2TaskDescriptor &desc = ring.task_descriptors[slot];
const PTO2TaskPayload &pl = ring.task_payloads[slot];
const PTO2TaskSlotState &st = ring.slot_states[slot];
// Descriptor: the task id is written to this exact local id.
EXPECT_EQ(desc.task_id.local(), static_cast<uint32_t>(local));
// task_state is written at submit (reset_for_reuse skips it): PENDING for a
// dispatchable task, COMPLETED for a pre-completed hidden-alloc. Either way a
// real enum, never poison.
const PTO2TaskState state = st.task_state.load(std::memory_order_relaxed);
EXPECT_TRUE(state == PTO2_TASK_PENDING || state == PTO2_TASK_COMPLETED);
// Completion flag is written to a real 0/1 (pending vs pre-completed), not a
// poison byte (0xAA).
const uint8_t cflag = ring.completion_flags[slot].load(std::memory_order_relaxed);
EXPECT_LE(cflag, uint8_t{1});
// Payload counts are real, not the poison bit pattern.
EXPECT_GE(pl.fanin_count, 0);
EXPECT_LE(pl.fanin_count, PTO2_MAX_FANIN);
EXPECT_GE(pl.tensor_count, 0);
EXPECT_GE(pl.scalar_count, 0);
// predicate.op is a dispatch-time field, read only for tasks the device
// actually dispatches. submit_task_common writes it (NONE when unset); a
// pre-completed hidden-alloc is never dispatched, so it does not.
if (state == PTO2_TASK_PENDING) {
EXPECT_LE(static_cast<uint8_t>(pl.predicate.op), static_cast<uint8_t>(PredicateOp::LE));
}
}
// Every claimed slot's device-read fields must carry real values, not poison.
for (int32_t local = 0; local < total; local++) {
SCOPED_TRACE(testing::Message() << "slot local_id=" << local);
const int32_t slot = ring.get_slot_by_task_id(local);
const PTO2TaskDescriptor &desc = ring.task_descriptors[slot];
const PTO2TaskPayload &pl = ring.task_payloads[slot];
const PTO2TaskSlotState &st = ring.slot_states[slot];
EXPECT_EQ(st.task, &ring.task_descriptors[slot]);
EXPECT_EQ(st.payload, &ring.task_payloads[slot]);
// Descriptor: the task id is written to this exact local id.
EXPECT_EQ(desc.task_id.local(), static_cast<uint32_t>(local));
// task_state is written at submit (reset_for_reuse skips it): PENDING for a
// dispatchable task, COMPLETED for a pre-completed hidden-alloc. Either way a
// real enum, never poison.
const PTO2TaskState state = st.task_state.load(std::memory_order_relaxed);
EXPECT_TRUE(state == PTO2_TASK_PENDING || state == PTO2_TASK_COMPLETED);
// Completion flag is written to a real 0/1 (pending vs pre-completed), not a
// poison byte (0xAA).
const uint8_t cflag = ring.completion_flags[slot].load(std::memory_order_relaxed);
EXPECT_LE(cflag, uint8_t{1});
// Payload counts are real, not the poison bit pattern.
EXPECT_GE(pl.fanin_count, 0);
EXPECT_LE(pl.fanin_count, PTO2_MAX_FANIN);
EXPECT_GE(pl.tensor_count, 0);
EXPECT_GE(pl.scalar_count, 0);
// predicate.op is a dispatch-time field, read only for tasks the device
// actually dispatches. submit_task_common writes it (NONE when unset); a
// pre-completed hidden-alloc is never dispatched, so it does not.
if (state == PTO2_TASK_PENDING) {
EXPECT_LE(static_cast<uint8_t>(pl.predicate.op), static_cast<uint8_t>(PredicateOp::LE));
}
}
🤖 Prompt for 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.

In `@tests/ut/cpp/a5/test_hbg_submit_poison.cpp` around lines 135 - 165, Extend
the validation loop around ring.task_descriptors, ring.task_payloads, and
ring.slot_states to assert that st.task and st.payload are bound to the
corresponding descriptor and payload objects for each slot. Compare both
bindings against the addresses of desc and pl, preserving the existing state and
field checks.

ChaoZheng109 and others added 2 commits August 11, 2026 01:12
Fixes hw-native-sys#1716

only. HBG is one runtime kept in sync across two arch trees (hw-native-sys#1706), so a5
still paid the full pre-hw-native-sys#1659 host cost: a boot-time blanket slot reset,
whole-window completion-flag zero, full sm_size SM upload and full
arena_size arena upload, and no ready-queue overflow guard.

Port the diff onto the a5 tree file-for-file:

- Bounded SM H2D: allocate the host SM uninitialized, zero only the header,
  and upload each segment (descriptors / payloads / slot_states /
  completion_flags) bounded to [0, total_tasks). total_tasks is
  range-checked before it sizes the copies.
- Init-on-write: per-slot reset_for_reuse() + completion-flag clear move
  from the boot-time whole-window loop into orch::prepare_task as each slot
  is claimed. The unclaimed tail is neither initialized, uploaded, nor read.
- Skip the host-only orchestrator block (fanin_seen_epoch / scope_tasks /
  TensorMap) from the arena H2D; an always_assert(orch_start <= orch_end)
  guards the layout order before slicing it out.
- Latch PTO2_ERROR_READY_QUEUE_OVERFLOW from push_ready_routed instead of
  dropping a ready task into an anonymous forward-progress stall.
- Drop the redundant per-entry tensormap stores the preceding memset
  already zeroed.

The ready queues ship in full, mirroring a2a3, so the two trees stay
identical for hw-native-sys#1715 to build on — a5 does not bound them locally just
because graph execution is not yet on a5.

Adds tests/ut/cpp/a5/test_hbg_submit_poison.cpp pinning the "every
device-read SM field is written at submit" contract, and RUNTIME_LOGIC.md
§3.1 documenting the bounded-upload contract.

Validated: 92/92 cpput, 8/8 a5sim hbg scene tests (vector_example,
paged_attention, prepared_callable), a5 + a5sim runtimes compile clean.
char message[1024] -> std::array<char, 1024> (call sites use .data()/.size());
int32_t kernel_ids_capture[3] -> std::array<int32_t, PTO2_SUBTASK_SLOT_COUNT>,
removing the hardcoded 3. After this a5/host_build_graph pto_orchestrator.cpp
is byte-identical to the a2a3 build.

Co-Authored-By: Claude <noreply@anthropic.com>
@ChaoZheng109
ChaoZheng109 force-pushed the fix/issue-1716-hbg-port-1659-a5 branch from cd5a110 to fd58f9b Compare August 11, 2026 08:13
@ChaoZheng109
ChaoZheng109 merged commit 7d14aa4 into hw-native-sys:main Aug 11, 2026
19 checks passed
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] hbg: Port host-overhead reduction (#1659) to a5

1 participant