Skip to content

[Performance] HBG: 80~95% host-side overhead reduction - #1659

Open
SergioMartin86 wants to merge 1 commit into
hw-native-sys:mainfrom
huawei-csl:hbg-sm-init-on-write
Open

[Performance] HBG: 80~95% host-side overhead reduction#1659
SergioMartin86 wants to merge 1 commit into
hw-native-sys:mainfrom
huawei-csl:hbg-sm-init-on-write

Conversation

@SergioMartin86

Copy link
Copy Markdown
Contributor

Human Summary

The host side of HBG was taking 20x more time than the device side. This was due to an unnecessary resetting (zeroing) and copying of the entire scheduling workspace. This PR reduces the H2D transfer to the minimal required information, and the work structures are initialized-on-use, rather than pre-zeroed.

AI Summary

PR: host_build_graph — init-on-write SM mirror (skip zero+upload of the unused tail)

Branch hbg-sm-init-on-write off upstream/main (810fbcd1). One file, +25/−3.

Problem

Profiling HBG on a2a3 (upstream/main, exclusive card, device time via [STRACE])
showed the per-dispatch wall is 96–99.6% host bind; the on-device scheduler
(device_wall) is ~0.1%. bind runs in full on every launch (no amortization).

Splitting bind located the cost in run_host_orchestration: it allocates+zeroes a
full, ring-sized host mirror of the shared-memory region (std::vector<uint8_t> host_sm_buf(sm_size, 0)), the orchestrator fills it, then it is H2D-uploaded whole.
sm_size scales with the ring task window, not the workload — 81 MB (default ring) to
651 MB (4 GB ring). Measured floor:

prebuilt sub-op bgemm (sm 81 MB) paged_attention (sm 651 MB)
host SM mirror alloc+zero 43.6 ms 339.7 ms
SM H2D upload 14.9 ms 106.3 ms

The alloc+zero is page-fault-bound (fresh anonymous mmap, ~1.9 GB/s); the upload is the
full mirror. A run only uses a few dozen of the 16k–131k slots, so almost all of this is
work on capacity that is never touched.

Fix (init-on-write)

The SM is cleanly segmented (ring_segment_offsets): header → descriptors → payloads
→ slot_states → completion_flags. Payloads are ~97% of sm_size. Each task fully
initializes its own descriptor/payload/slot_state at submit, and the scheduler reads no
slot past total_tasks, so:

  • Do not zero the payload segment — leave it allocated-but-untouched (its unread tail
    costs no page faults). Still zero the small control segments (header+descriptors,
    slot_states+completion_flags), preserving every existing zero-assumption there.
  • Upload only the live prefix — one copy for header+descriptors+[0, total_tasks)
    payload prefix, one for the whole (small) slot_states+completion segments. The payload
    tail is never read on device, so it is not shipped.

Cost then tracks the run (tasks submitted), not the ring capacity.

Why it is safe

A completeness check enumerated every SM field the scheduler reads vs every per-slot init
site (reset_for_reuse, PTO2TaskPayload::init, prepare_task, the control-segment
memsets / init_per_ring). Result: every scheduler-read byte is explicitly initialized
at submit; nothing depends on the blanket zero.
Reads are bounded by current_task_index
(= total_tasks), so unsubmitted slots are never touched. The only bulk-zero-only bytes
are unread (padding, array tails, slots past the high-water mark).

Single-ring (PTO2_MAX_RING_DEPTH == 1), so no ring-loop; the offset math uses the
existing single-source-of-truth ring_segment_offsets.

Results (a2a3, exclusive card, medians)

workload total run bind device_wall golden
bgemm 90.9 → 16.0 ms (−82%) 87.4 → 12.1 ms 102 → 102 µs PASS
paged_attention 536 → 32.5 ms (−94%) 534 → 30.3 ms 57 → 57 µs PASS

The two SM-mirror ops collapse: bgemm 58.5 → 1.7 ms (−97%), paged 446 → 10.1 ms (−98%).
Device side is unchanged. Full HBG a2a3 scene-test suite golden: (filled after run).

Scope / follow-ups

  • a2a3 host_build_graph only. Orthogonal to Add streamed Graph Execution to host_build_graph #1444 (graph execution): that shrinks what
    goes into the SM; this fixes the SM buffer's alloc/zero/upload lifecycle. They compound.
  • Further, smaller wins (separate change): bound the control segments (descriptors,
    slot_states) to total_tasks too — for the big ring these are still window-sized
    (~7 ms). The arena build/upload (~6 ms) is the next floor.
  • Optional hardening the completeness check flagged: unconditionally clear the predicate
    target/elem_size in the op==NONE branch (currently zero only via the old blanket zero,
    but gated unread by has_predicate).

@coderabbitai

coderabbitai Bot commented Aug 3, 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: 83e91109-f573-4b65-ad93-a735ef1c6a52

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 host runtime now uses uniquely owned shared-memory storage, initializes only control regions, and uploads the live payload prefix plus complete control segments to the device.

Changes

Shared-memory staging

Layer / File(s) Summary
Selective initialization and device upload
src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp
The runtime uses std::unique_ptr storage, selectively clears control regions, and splits device uploads between the live payload prefix and complete control segments.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Poem

A rabbit hops through memory bright,
Leaves payload bytes untouched in flight.
Control flags clear, the slots align,
Two careful transfers cross the line.
“Efficient staging!” thumps my cheer.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main HBG performance improvement by reducing host-side overhead.
Description check ✅ Passed The description directly explains the host-side optimization, implementation scope, safety rationale, and measured results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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 `@src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp`:
- Around line 537-548: Validate total_tasks before computing payload_prefix_end
or performing relocation/copy operations: require it to be non-negative and no
greater than eff_task_window_sizes[0]. Reject invalid values early, preserving
the existing copy behavior only for valid task counts.
🪄 Autofix (Beta)

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: e9f8a9e0-e5d7-420b-a483-e1085fb0681e

📥 Commits

Reviewing files that changed from the base of the PR and between 71433ca and 87874ee.

📒 Files selected for processing (1)
  • src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp

Comment thread src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp
run_host_orchestration allocated and zeroed a full ring-sized host mirror
of shared memory (host_sm_buf(sm_size, 0)) and H2D-uploaded it whole on
every run. sm_size scales with the ring task window (81 MB default, 651 MB
at a 4 GB ring), not the workload, so nearly all of the alloc+zero and the
upload was spent on slots a run never touches. This host bind path is
96-99.6% of the per-dispatch wall.

The SM payload segment (~97% of sm_size) is written per task at submit and
read only for [0, total_tasks); its tail is never read on device. Leave that
segment allocated-but-untouched (no page faults), zero only the small control
segments, and upload only the live prefix plus the whole (small) slot_states
and completion_flags segments.

Every scheduler-read field is already explicitly initialized per slot, so
nothing depends on the removed blanket zero; reads are bounded by
current_task_index. Single-ring (PTO2_MAX_RING_DEPTH == 1).

bgemm 90.9 -> 16.0 ms (-82%), paged_attention 536 -> 32.5 ms (-94%) total
run; device_wall unchanged; golden passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqeALZTPEnDXTnbYfcnPfq
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.

1 participant