Skip to content

Fix: initialize DSv4 decode buffers on device and bound hc_head_linear to valid rows - #1922

Merged
ChaoZheng109 merged 4 commits into
hw-native-sys:mainfrom
ChaoZheng109:feat/dsv4-device-side-init-main
Aug 20, 2026
Merged

ChaoZheng109 merged 4 commits into
hw-native-sys:mainfrom
ChaoZheng109:feat/dsv4-device-side-init-main

Conversation

@ChaoZheng109

@ChaoZheng109 ChaoZheng109 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What

Ports #1899 and #1871 to main. Both landed on perf/hbg-orch, so main still
carries the workaround they replaced: the DeepSeek-V4 FLASH decode cases dropped
their six orchestration-side set_initial_value(0) calls to avoid a segfault
under HBG, which left the mixes_raw AtomicAdd destination undefined and gave
up numerical correctness — and hc_head_linear still reads past the end of
x_flat, which is what stalls the non-Graph device body 12 tasks from the end.

Device-side initialization (#1899)

The six initializations move from the orchestrator to the device, so the TMR and
HBG orchestrations are identical here and the host never writes a GM-heap device
address.

  • h_tile_i8 padding rows: each of the five sh_gate_up_act_q* AIV kernels
    now clears the two padded rows owned by its logical block
    (zero_h_tile_i8_padding) before producing its output.
  • mixes_raw split-K destination: a new hc_head_mixes_zero AIV kernel
    (func_id 367) clears one up-to-16-row chunk per logical block; an explicit
    task dependency orders every hc_head_linear AtomicAdd store after the seed
    task. Both orchestrations (decode_fwd.cpp, decode_fwd_graph.cpp) submit it
    in place of the plain alloc_tensors + set_initial_value.

This closes the "AtomicAdd destinations cannot currently be initialized
correctly by HBG" runtime gap recorded in the HBG README.

hc_head_linear row-tail bound (#1871)

x_flat [8,16384] is a valid 512 KiB allocation, but both TLOAD views were
hard-coded as Shape<...,16,256> with a 16384-element row stride — an almost
1 MiB range, reading rows 8–15 out of bounds. HBG's exact-size per-tensor
allocation surfaces that as an MTE out-of-range fault at task 15959; TMR's
retained bump allocation keeps it inside a larger mapping and masks the same
kernel bug.

valid_rows = clamp(t_dim - row_base, 0, 16) now bounds the two x_flat views
and load tiles, the dependent matmul and accumulator tiles, and the mixes_raw
AtomicAdd store. Cube tile capacity stays at 16. This is the root cause of the
tail stall the HBG README documented, so that status section is rewritten around
what remains open: Graph activation.

Both full-network cases remain manual (368-kernel compile); READMEs and
docstrings are updated for the new kernel count and status.

Differences from the branch versions

Two doc claims on perf/hbg-orch do not hold on main and were rewritten
rather than copied:

  • the teardown-budget note referenced commit 342e29f1, which is branch-local
    and unreachable from main. The fix itself is on main, so the note now
    names _CLOSE_CHILD_REAP_TIMEOUT_S and its 60 s value instead of a sha.
  • the TMR README status heading and its "including this branch's 0cefc9a5
    pin" phrasing keep main's own wording ("blocked on the CI: bump pinned pto-isa for PTOAS v0.55 #1644 pto-isa pin
    bump" / "including current main") — main pins cd4a3d3f.

Relatedly, the stale (= pto_isa.pin) claim next to the generation ISA is
dropped, as in #1899: main does not pin the 83d01313 these kernels were
generated against.

Also in this PR

decode_fwd_hostbuild.cpp is deleted. It was never a build input — the case
points at decode_fwd_graph.cpp and nothing else read the file — and its
investigation value is spent now that the tail fault it isolated is root-caused
and fixed above. The HBG README loses the stall narrative that described only
that form (the per-task dump, the ruled-out table, the two-orchestration
provenance) and keeps the Graph-activation blocker, the reason the kernel
carries a row-tail bound, and the get_tensor_data runtime gap.

One commit also fixes two stale comments in the HBG test that predate this change
(they came in with #1900): the docstring and the ring-sizing comment still named
the predicated-dispatch variant with a static tile grid, while the case actually
builds decode_fwd_graph.cpp — the predicated form was an earlier, discarded
variant. No behavior or sizing change.

Test

Both full-network cases are manual and, on main, still blocked mid-network
by the pinned pto-isa (documented in the TMR README), so an end-to-end run
proves nothing here. The device evidence for the row-tail fix is in #1871
(task_20260817_204500_135435017977, task_20260817_210320_91023923204 — both
ranks outcome=0, PASSED, no MTE fault, 507018, TIMEOUT_EXIT or task-15959
stall).

Compile coverage against main's pinned ISA (cd4a3d3f) was verified here:

  • the new hc_head_mixes_zero.cpp, all five modified sh_gate_up_act_q*.cpp
    and the modified hc_head_linear.cpp compile for a2a3 (ccec, AIV/AIC);
  • both orchestrations — TMR decode_fwd.cpp and HBG decode_fwd_graph.cpp
    compile for their runtimes.

Full pre-commit run over the changed files passes.

…ator

Replace the six orchestration-side `set_initial_value(0)` calls in the
DeepSeek-V4 FLASH decode cases with device-side initialization, so the TMR
and HBG orchestrations are identical here and the host never writes a GM-heap
device address. Under HBG that write segfaults the chip subprocess, which is
why the calls were simply dropped when the case landed on main — leaving the
`mixes_raw` AtomicAdd destination undefined.

- `h_tile_i8` padding rows: each of the five `sh_gate_up_act_q*` AIV kernels
  clears the two padded rows owned by its logical block
  (`zero_h_tile_i8_padding`) before producing its output.
- `mixes_raw` split-K destination: a new `hc_head_mixes_zero` AIV kernel
  (func_id 367) clears one up-to-16-row chunk per logical block; an explicit
  task dependency orders every `hc_head_linear` AtomicAdd store after the seed
  task. `decode_fwd.cpp`, `decode_fwd_hostbuild.cpp` and `decode_fwd_graph.cpp`
  submit it in place of the plain `alloc_tensors` + `set_initial_value`.

Both full-network cases remain `manual` (368-kernel compile); READMEs and
docstrings are updated for the new kernel count and for the closed HBG
initialization gap.

Ported from hw-native-sys#1899, which landed on `perf/hbg-orch` rather than `main`.
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a476dac4-2bdf-42a3-aac0-23d129af6784

📥 Commits

Reviewing files that changed from the base of the PR and between 93adc38 and c3d2039.

📒 Files selected for processing (13)
  • examples/a2a3/host_build_graph/deepseek_v4_flash_decode/README.md
  • examples/a2a3/host_build_graph/deepseek_v4_flash_decode/kernels/orchestration/decode_fwd_graph.cpp
  • examples/a2a3/host_build_graph/deepseek_v4_flash_decode/kernels/orchestration/decode_fwd_hostbuild.cpp
  • examples/a2a3/host_build_graph/deepseek_v4_flash_decode/test_deepseek_v4_flash_decode.py
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/README.md
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/aiv/hc_head_mixes_zero.cpp
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/aiv/sh_gate_up_act_q.cpp
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/aiv/sh_gate_up_act_q_0.cpp
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/aiv/sh_gate_up_act_q_1.cpp
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/aiv/sh_gate_up_act_q_2.cpp
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/aiv/sh_gate_up_act_q_3.cpp
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/orchestration/decode_fwd.cpp
  • examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/test_deepseek_v4_flash_decode.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

DeepSeek-V4 initialization moved from host tensor setup to device-side kernels. Split-K accumulation now waits for an explicit zeroing task. Gate-up kernels clear padded INT8 rows. Documentation and kernel metadata now report 368 kernels.

Changes

DeepSeek-V4 initialization flow

Layer / File(s) Summary
Device zero-initialization kernels
examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/aiv/*
Added kernels and helpers that clear split-K destinations and block-specific INT8 padding rows.
Initialization task orchestration
examples/a2a3/host_build_graph/deepseek_v4_flash_decode/kernels/orchestration/*, examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/kernels/orchestration/decode_fwd.cpp, examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/test_deepseek_v4_flash_decode.py
Replaced host allocation with AIV task 367 and added a dependency before hc_head_linear atomic additions.
Runtime documentation and test metadata
examples/a2a3/host_build_graph/deepseek_v4_flash_decode/README.md, examples/a2a3/host_build_graph/deepseek_v4_flash_decode/test_deepseek_v4_flash_decode.py, examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/README.md, examples/a2a3/tensormap_and_ringbuffer/deepseek_v4_flash_decode/test_deepseek_v4_flash_decode.py
Updated kernel counts, runtime descriptions, initialization details, provenance, and compile information.

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

Merge Risk: ⚪ Minimal · up to c3d20

The PR moves decode-buffer initialization onto the device to avoid undefined AtomicAdd destinations while preserving the existing orchestration flow. No actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant HostGraph
  participant AIVTask367
  participant mixes_raw
  participant hc_head_linear
  HostGraph->>AIVTask367: Submit zero-initialization task
  AIVTask367->>mixes_raw: Write zero values
  HostGraph->>hc_head_linear: Register task 367 dependency
  AIVTask367-->>hc_head_linear: Signal completion
  hc_head_linear->>mixes_raw: Perform split-K atomic additions
Loading

Poem

I’m a rabbit with zeros to sow,
In padded rows where kernels grow.
Task three-six-seven clears the way,
Then atomic sums can safely play.
Three hundred sixty-eight now hop along!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
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.
Title check ✅ Passed The title clearly identifies both primary fixes: device-side buffer initialization and valid-row bounds for hc_head_linear.
Description check ✅ Passed The description directly explains the device-side initialization, row-tail fix, affected kernels, tests, and documentation updates.

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.

The HBG DSv4 test points at `decode_fwd_graph.cpp`, the Graph form. Its
docstring and ring-sizing comment still described the predicated-dispatch
variant with a static tile grid, which the README's ruled-out table records as
an earlier form that stalls at the same task and is not what the case builds.

The ring sizing itself is unchanged: `HBG_RECV_ROWS_PER_EXPERT` holds every
per-expert tile loop at a constant trip count, so tile scratch is still
allocated for all 32 experts per MoE layer.
@ChaoZheng109 ChaoZheng109 changed the title Fix: initialize DSv4 decode buffers on device instead of the orchestrator Fix: initialize DSv4 decode buffers on device and bound hc_head_linear to valid rows Aug 20, 2026
`x_flat [8,16384]` is a valid 512 KiB allocation, but both TLOAD views were
hard-coded as `Shape<...,16,256>` with a 16384-element row stride, covering an
almost 1 MiB range and reading rows 8-15 out of bounds. HBG's exact-size
per-tensor allocation surfaces the over-read as an MTE out-of-range fault at
task 15959; TMR's retained bump allocation keeps it inside a larger mapping and
masks the same kernel bug.

Derive `valid_rows = clamp(t_dim - row_base, 0, 16)` and use it for the two
`x_flat` views and load tiles, the dependent matmul and accumulator tiles, and
the `mixes_raw` AtomicAdd store. Cube tile capacity stays at 16.

The HBG README records the completed non-Graph device body, corrects the
earlier 13-of-16 dispatch reading of the stall, and keeps the original failure
signature under its own heading.

Ported from hw-native-sys#1871, which landed on `perf/hbg-orch` rather than `main`. The
teardown-budget reference is respelled as `_CLOSE_CHILD_REAP_TIMEOUT_S`, whose
branch-local commit id does not exist on `main`.
`decode_fwd_hostbuild.cpp` was never a build input: the case points at
`decode_fwd_graph.cpp`, and nothing else read the file. Its investigation value
is spent now that the tail fault it was used to isolate is root-caused and fixed
in `hc_head_linear`.

The README loses the stall narrative that only described this form — the
per-task dump, the ruled-out table and the two-orchestration provenance — and
keeps the Graph-activation blocker, the reason the kernel carries a row-tail
bound, and the `get_tensor_data` runtime gap.
@ChaoZheng109
ChaoZheng109 merged commit 43e61c6 into hw-native-sys:main Aug 20, 2026
16 checks passed
ChaoWao pushed a commit that referenced this pull request Aug 20, 2026
set_initial_value segfaulted the orchestrator on every host_build_graph path.
fill_tensor_initial_value memcpy'd to ChipTensor::buffer.addr, and on this
runtime that is a GM-heap device address the orchestrating host cannot load.
The file is a verbatim copy of the tensormap_and_ringbuffer one, where the
orchestrator runs on the AICPU and the store is correct; the copy kept the
store and lost its premise.

Removing it from this runtime rather than making it work, because there is
nothing left to serve. #1922 moved the DeepSeek-V4 case — the only caller that
needed it here — to device-side initialization, and the sole remaining caller
in the tree is the tensormap_and_ringbuffer scalar_data example, which
demonstrates the API itself. Supporting it would also stop short of the case it
was for: an initial value cannot survive Graph Execution, where each submission
materializes its outputs at addresses it derives for itself from a heap block
whose prior contents it never reads, so a value written once while recording
reaches none of the replays.

tensormap_and_ringbuffer keeps the fill. Its orchestrator runs on the AICPU,
which addresses the GM heap directly, so the feature works there and its
example and golden are untouched. The two runtimes hold separate copies of this
header, so the removal is theirs alone.

Dropping the method rather than failing at run time means an orchestration that
asks for it does not compile against host_build_graph, instead of building and
faulting on device.

The two fields occupied bytes [0, 16), aligned with ChipTensor::buffer so
init_tensor_from_create_info can cover cache line 1 in one 64-byte memcpy, and
start_offset has to stay at byte 24 for the offsetof assertions that follow.
Explicit padding holds the layout, and a new assertion pins its intent —
offsetof(TensorCreateInfo, __pad0__) == offsetof(ChipTensor, buffer) — so the
span is verified rather than described. Those bytes were already dead: the
memcpy is followed immediately by t.buffer = {addr, buffer_size}.

Verified on a2a3 hardware, including the tensormap_and_ringbuffer scalar_data
example, which is the one place in the tree that still sets an initial value
and the only regression surface for the shared header this change touches.
Also a2a3sim, a5sim, and the C++ unit tests.
ChaoZheng109 added a commit that referenced this pull request Aug 24, 2026
…_and_ringbuffer (#1975)

TensorCreateInfo::set_initial_value filled a fresh output inside the
hidden alloc task, where nothing could race the write. host_build_graph
dropped the same fill in #1930 because its host orchestrator cannot
store to the GM-heap device address; the two runtimes then held
different API surfaces for the same create-info header. This removes
the fill here too, so an orchestration written against one runtime
compiles against the other.

The only caller in the tree is the scalar_data example, which seeds a
1-element scalar. It now allocates first and writes with
set_tensor_data: the alloc task completes inline in the orchestrator
before any producer or consumer exists, so the write finds nothing to
wait for and runs immediately. Goldens are unchanged.

Whole-buffer initialization such as zeroing a large accumulator is not
served by per-element set_tensor_data; a task writes the buffer, with
task dependencies ordering every reader and writer after the seed —
the pattern #1922 established for the DeepSeek-V4 decode buffers.

The two occupied bytes [0, 16) are replaced by explicit __pad0__
padding aligned with ChipTensor::buffer, and a new offsetof assertion
pins that span, mirroring the host_build_graph layout. The stale
cross-reference in the shared tensor.h comment goes with it.

Verified: scalar_data on a2a3sim and a5sim with golden comparison
(both PASSED), the full C++ unit-test suite (117/117), and the Python
unit tests (1892 passed, 20 skipped).
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