Skip to content

Refactor: split the L2 tensor argument from each runtime's working tensor - #1974

Merged
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:refactor/tensor-layout-decouple
Aug 25, 2026
Merged

ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:refactor/tensor-layout-decouple

Conversation

@ChaoWao

@ChaoWao ChaoWao commented Aug 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

An argument arrives at L2 as a ChipTensor, and the runtime then decides things
about it: which task produced it, the version its OverlapMap keys on, whether
dependency tracking is creator-only. Those decisions lived on ChipTensor itself,
in src/common/task_interface/, so one type served the boundary and both
runtimes' working state
.

The code already recorded the mismatch:

  • create_from_chip_args asserted !t.manual_dep && t.version == 0 — two fields
    that are meaningless on an argument.
  • docs/buffer-abi.md:33 called ChipTensor "L2 leaf, internal" and :35 said
    "You never build a ChipTensor", while ChipWorker.run took a container of them
    and nb::class_<ChipTensor> exposed a make() factory.

Where it came from

#1093 needed the strided view #808 had just given the runtime's tensor to reach the
argument boundary, and got there by promoting the runtime-private header into
task_interface/ rather than adding strides to the 40 B ContinuousTensor it
replaced. That cost 40 B → 128 B per wire tensor plus a MAILBOX_SIZE doubling,
for a capability the PR itself noted was unused — and two months on
make_tensor_arg / make_chip_tensor_arg still reject non-contiguous tensors.
#1729 undid the fusion on the L3 wire with a cost argument that applies equally
here; nobody undid it at L2.

The two types

ChipTensor (72 B) is a task argument — a resolved buffer and a strided view:

struct ChipTensor {
    PTOBufferHandle buffer;
    uint64_t start_offset;
    uint32_t shapes[MAX_TENSOR_DIMS];
    uint32_t strides[MAX_TENSOR_DIMS];
    uint32_t ndims;
    DataType dtype;
    AddressSpace address_space;
};

simpler::hbg::Tensor / simpler::tmr::Tensor (128 B) are the same geometry
plus owner_task_id, version, manual_dep and the two caches derived from it.
One copy each, shared across architectures, placed the way
src/common/host_build_graph/graph_cache.h already established; simpler::hbg was
an existing namespace.

Runtime::set_orch_args is the single place one becomes the other. Both
runtimes call it only from host/runtime_maker.cpp, so adoption happens on the
host before any orchestration runs — including for tensormap_and_ringbuffer,
whose AICPU executor now reads an already-adopted
simpler::tmr::EntryArgsStorage instead of converting per run. From there inward
nothing holds the argument form.

A tensor.h in each runtime's runtime/ directory sits first on that runtime's
include path, so existing #include "tensor.h" lines resolve to both types without
an include sweep.

What the boundary type dropped

dropped why
owner_task_id, version, manual_dep the runtime's decisions, not the caller's
is_contiguous, extent_elem_cache derived from the geometry; now methods on the boundary type, cached only where a hot path reads them per task
view ops + the copy helpers an argument is not a thing you take views of; nothing outside a runtime called them
manual_dep / version factory parameters every caller passed false / 0
#include "task_id.h", friend PTO2TaskPayload the boundary names no TaskId and no runtime type

sizeof(ChipStorageTaskArgs): ~33.8 KB → 19464 B at the 256-tensor cap.
MAILBOX_SIZE is unaffected — since #1729 the frame is sized by the 144 B wire
Tensor.

Prerequisite: three structs sharing one byte layout

ChipTensor, PTO2TensorMapEntry and TensorCreateInfo shared a byte-level
layout so three copy paths could each be a single 64-byte memcpy, held by 21
hand-written offsetof assertions across four trees
. Two of the three were
shaped backwards to satisfy it:

  • TensorCreateInfo carried __pad0__ / __pad2__ / __pad_flags__ purely to
    occupy ChipTensor::buffer / ::owner_task_id / ::address_space.
  • the entry's memcpy wrote ChipTensor::buffer.size into a
    PTO2TensorMapEntry * field, its comment noting this was "harmless because
    link_entry() overwrites next_in_bucket immediately after"
    .

Each struct now assigns the fields it wants, by name. The optimization the memcpy
existed for survives as intent: a canonically contiguous source has the derived
pair recomputed from shapes rather than read across.
PTO2TensorMapEntry::copy_tensor_create_info is deleted — no callers in any
tree, and the only reason TensorCreateInfo had to be punnable onto an entry. The
entry keeps its own two-cache-line split and the assertions describing it.

Consequence: each case owns the sources it compiles

A source that names one runtime's Tensor cannot be compiled under the other, and
57 scene-test sources were: a host_build_graph case naming the
tensormap_and_ringbuffer case's kernels, and two tensormap_and_ringbuffer
classes taking an hbg class's CALLABLE verbatim. That worked only because
ChipTensor happened to be identical in both trees — the coupling this PR removes.

Each borrowing case now carries its own sources (67 files) and names local paths.
The two class-reuse cases rebase the inherited CALLABLE onto their own kernels/
copy via a _rebase_callable helper, so the Python that drives them stays in one
file while the C++ each runtime compiles is its own.

The cost is real and worth stating: spmd_multiblock_mix,
alternating_matmul_add, batch_paged_attention, paged_attention_unroll and
benchmark_bgemm now exist once per runtime, and the copies can drift. The
judgement is that a case asserting something about host_build_graph should not
depend on a file the other runtime owns.

Incidental

  • examples/ orchestration and kernel sources lose their Generated by PyPTO IR Compiler marker: they are this repository's sources, and this PR edits them.
  • tensor_create_info.h said host_build_graph has no initial-value fill "unlike
    the tensormap_and_ringbuffer copy of this header"
    , which The recorder thread owns its recording storage #1981 made false by
    removing that fill. Both copies now describe what is there.
  • docs/buffer-abi.md moves with the wire it describes: type table, field table
    and size figures.

Testing

  • Full rebuild, both runtimes × both architectures — every static_assert holds
  • cpput 117/117 (-LE requires_hardware)
  • pyut 1908 passed, 14 skipped
  • a2a3sim scene tests: 21 cases green
  • a5sim scene tests: 17 cases green
  • onboard a2a3 / a5 — left to CI's isolated runners; this dev box had 11/16
    dies held by other users with four jobs queued ahead

An earlier revision of this branch (layout decoupling only) passed
st-onboard-a2a3, st-onboard-a5 and st-network1-onboard-a2a3.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 24, 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: 8170bbfd-ab76-466d-855c-41fb9dec0f9c

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

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: 83ef61c9-fc18-40a3-9a34-4b93a7fb5a73

📥 Commits

Reviewing files that changed from the base of the PR and between 969c6a6 and 9b747a3.

📒 Files selected for processing (9)
  • src/a2a3/runtime/host_build_graph/runtime/tensor_create_info.h
  • src/a2a3/runtime/host_build_graph/runtime/tensormap.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/tensor_create_info.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/tensormap.h
  • src/a5/runtime/host_build_graph/runtime/tensor_create_info.h
  • src/a5/runtime/host_build_graph/runtime/tensormap.h
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/tensor_create_info.h
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/tensormap.h
  • src/common/task_interface/tensor.h

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


📝 Walkthrough

Walkthrough

TensorCreateInfo and PTO2TensorMapEntry no longer mirror ChipTensor cache-line layouts. They now copy fields explicitly. ChipTensor geometry initialization separates core metadata from derived row-major fields across creation and view operations.

Changes

Tensor metadata and geometry

Layer / File(s) Summary
ChipTensor geometry initialization
src/common/task_interface/tensor.h
Adds init_geometry_from and refresh_row_major_derived. Copy, view, transpose, permute, slice, and reshape operations use explicit geometry initialization.
TensorCreateInfo materialization
src/a2a3/runtime/.../tensor_create_info.h, src/a5/runtime/.../tensor_create_info.h, src/a2a3/runtime/.../tensor_create_info.h, src/a5/runtime/.../tensor_create_info.h
Removes layout-mirroring fields and alignment. Copying uses assignment. Materialization assigns ChipTensor fields explicitly and refreshes derived metadata.
Tensor-map entry population
src/a2a3/runtime/.../tensormap.h, src/a5/runtime/.../tensormap.h, src/a2a3/runtime/.../tensormap.h, src/a5/runtime/.../tensormap.h
Replaces 64-byte copies with explicit field copying. Removes copy_tensor_create_info and field-offset assertions while retaining entry-size checks.

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

Merge Risk: ⚪ Minimal · up to 9b747

This refactor preserves the documented tensor behavior and passes the supplied build and test coverage; no actionable merge-blocking risk remains beyond normal checks.

Sequence Diagram(s)

sequenceDiagram
  participant TensorCreateInfo
  participant TensorMapEntry
  participant ChipTensor
  TensorCreateInfo->>ChipTensor: materialize metadata and shapes
  ChipTensor->>ChipTensor: refresh derived row-major geometry
  ChipTensor->>TensorMapEntry: copy address, metadata, and shapes
  TensorMapEntry->>TensorMapEntry: retain linkage and populate geometry
Loading

Poem

I’m a rabbit with fields neatly sewn,
No cache-line mirror is shown.
Shapes hop in place,
Strides find their base,
And typed copies now lead the way.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
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 accurately summarizes the main refactor separating the L2 tensor argument from each runtime's working tensor.
Description check ✅ Passed The description clearly explains the tensor layout refactor, affected components, rationale, testing, and pending onboard validation.

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.

@ChaoWao
ChaoWao force-pushed the refactor/tensor-layout-decouple branch from 9b747a3 to 53df0d9 Compare August 24, 2026 07:51
@ChaoWao ChaoWao changed the title Refactor: give each tensor-adjacent struct its own layout Refactor: split the L2 tensor argument from each runtime's working tensor Aug 24, 2026
@ChaoWao
ChaoWao force-pushed the refactor/tensor-layout-decouple branch 7 times, most recently from cef6dd0 to 8f95650 Compare August 25, 2026 07:14
…nsor

An argument arrives at L2 as a `ChipTensor`, and the runtime then decides things
about it: which task produced it, the version its OverlapMap keys on, whether
dependency tracking is creator-only. Those decisions lived on `ChipTensor` itself,
in `src/common/task_interface/`, so one type served the boundary and both
runtimes' working state. `create_from_chip_args` recorded the mismatch as an
assertion — `debug_assert(!t.manual_dep && t.version == 0)`, two fields that are
meaningless on an argument. `docs/buffer-abi.md` recorded it too, calling
`ChipTensor` "L2 leaf, internal" and saying "You never build a `ChipTensor`" while
`ChipWorker.run` took a container of them and `nb::class_<ChipTensor>` exposed a
`make()` factory.

The fusion dates to hw-native-sys#1093, which needed the strided view hw-native-sys#808 had just given the
runtime's tensor to reach the argument boundary, and got there by promoting the
runtime-private header into `task_interface/` rather than adding strides to the
40 B `ContinuousTensor` it replaced. It cost 40 B → 128 B per wire tensor and a
mailbox doubling for a capability that PR noted was not yet used: two months on,
`make_tensor_arg` and `make_chip_tensor_arg` still reject non-contiguous tensors.
with a cost argument that applies equally here; nobody undid it at L2.

`ChipTensor` (72 B) is a task argument: a resolved buffer and a strided view.

    struct ChipTensor {
        PTOBufferHandle buffer;
        uint64_t start_offset;
        uint32_t shapes[MAX_TENSOR_DIMS];
        uint32_t strides[MAX_TENSOR_DIMS];
        uint32_t ndims;
        DataType dtype;
        AddressSpace address_space;
    };

`simpler::hbg::Tensor` and `simpler::tmr::Tensor` (128 B) are the same geometry
plus `owner_task_id`, `version`, `manual_dep` and the two caches derived from the
geometry. One copy each, shared across architectures, placed the way
`src/common/host_build_graph/graph_cache.h` already established. `simpler::hbg`
was an existing namespace.

`Runtime::set_orch_args` is the single place one becomes the other. Both runtimes
call it only from `host/runtime_maker.cpp`, so adoption happens on the host before
any orchestration runs — including for `tensormap_and_ringbuffer`, whose AICPU
executor now reads an already-adopted `simpler::tmr::EntryArgsStorage` instead of
converting per run. From there inward nothing holds the argument form:
orchestration, the payload, the TensorMap and the kernels all name their runtime's
type.

A `tensor.h` in each runtime's `runtime/` directory sits first on that runtime's
include path, so existing `#include "tensor.h"` lines resolve to both types
without an include sweep. That shim names the two headers by their path under
`src/common`, which every platform target but `aicore` already had on its include
path; the four `aicore` CMakeLists gain the line their `aicpu` and `host` siblings
carry.

`is_contiguous` and `extent_elem` become methods that compute from the geometry;
a runtime that reads them per task caches them on its own `Tensor`. The view ops
and the copy helpers that maintained those caches move with them — an argument is
not a thing you take views of, and nothing outside a runtime called them.
`init_external` and both factories lose their `manual_dep` / `version` parameters,
which every caller passed as `false` / `0`. `PTO2TaskPayload` is no longer a
friend, and the boundary header no longer includes `task_id.h`: it names no TaskId
at all.

`sizeof(ChipStorageTaskArgs)` falls from ~33.8 KB to 19464 B at the 256-tensor
cap. `MAILBOX_SIZE` is unaffected — since hw-native-sys#1729 the frame is sized by the 144 B
wire `Tensor`.

`ChipTensor`, `PTO2TensorMapEntry` and `TensorCreateInfo` shared a byte-level
layout so three copy paths could each be a single 64-byte `memcpy`, held by 21
hand-written `offsetof` assertions across four trees. Two of the three were shaped
backwards to satisfy it: `TensorCreateInfo` carried `__pad0__` / `__pad2__` /
`__pad_flags__` purely to occupy `ChipTensor::buffer` / `::owner_task_id` /
`::address_space`, and the entry's `memcpy` wrote `ChipTensor::buffer.size` into a
`PTO2TensorMapEntry *` field, its comment noting this was "harmless because
`link_entry()` overwrites `next_in_bucket` immediately after".

Each struct now assigns the fields it wants, by name. The optimization the
`memcpy` existed for survives as intent: a canonically contiguous source has the
derived pair recomputed from `shapes` rather than read across.
`PTO2TensorMapEntry::copy_tensor_create_info` is deleted — it had no callers in
any tree, and was the only reason `TensorCreateInfo` had to be punnable onto an
entry. The entry keeps its own two-cache-line split and the assertions describing
it; what goes is the claim that its bytes agree with a different struct's.

A source that names one runtime's `Tensor` cannot be compiled under the other, and
57 scene-test sources were: a `host_build_graph` case naming the
`tensormap_and_ringbuffer` case's kernels, and two `tensormap_and_ringbuffer`
classes taking an hbg class's `CALLABLE` verbatim. That worked only because
`ChipTensor` happened to be identical in both trees — the coupling this change
removes.

Each borrowing case now carries its own sources, 73 files, and names local paths.
The two class-reuse cases rebase the inherited `CALLABLE` onto their own
`kernels/` copy through a `_rebase_callable` helper, so the Python that drives
them stays in one file while the C++ each runtime compiles is its own.
`task_timing_slots` carries the runtime in a function default rather than a
`@scene_test` decorator, so its sources move under `kernels/<runtime>/` and the
helper builds the path from the runtime it is driving. The cost is real:
`spmd_multiblock_mix`, `alternating_matmul_add`, `batch_paged_attention`,
`paged_attention_unroll`, `benchmark_bgemm` and the task-timing kernels now exist
once per runtime and the copies can drift. A case that asserts something about
`host_build_graph` should not depend on a file the other runtime owns.

`examples/` orchestration and kernel sources lose their `Generated by PyPTO IR
Compiler` marker: they are this repository's sources, and this change edits them.

364 of those kernels declare a ptoas helper as `template <typename ChipTensor>`,
shadowing the type with a parameter name. The parameter is `TensorT` now: a
generated helper that means "any tensor with .data()" should not name one, and the
shadowing is what made the rename ambiguous in the first place.

## Kernels name no runtime

A kernel reads a payload element and its code is identical whichever orchestrator
filled it, so naming a runtime there carries no information — and several kernels
are compiled under both. `examples/a2a3/host_build_graph/deepseek_v4_flash_decode`
re-points all 368 of the `tensormap_and_ringbuffer` case's incores at that case's
directory; `test_task_timing_e2e` drives one AIV kernel under both. Each runtime's
`runtime/tensor.h` therefore exports

    using TaskTensor = simpler::{hbg,tmr}::Tensor;

and every kernel names `TaskTensor`. It resolves per translation unit, so it is one
type per build, not a third type. The alternative was duplicating 13 MB of generated
MoE kernels into the hbg tree, where the two copies would drift.

The name is not `Tensor`: `buffer.h`'s L3+ wire `Tensor` is visible in every
orchestration translation unit, and one spelling meaning two types by context is
what this change exists to remove.

Cross-runtime *orchestration* sources need no name — `const auto &a =
orch_args.tensor(0).ref()` is enough, and only kernels must spell the type inside a
`reinterpret_cast`.

`tensor_create_info.h`'s doc paragraph said `host_build_graph` has no
initial-value fill "unlike the tensormap_and_ringbuffer copy of this header",
which hw-native-sys#1981 made false by removing that fill; both copies now describe what is
there.

The copies were taken from sources whose spellings `main` has since retired — hw-native-sys#1980
finished that retirement repo-wide — so they carry the current ones.

`test_hbg_sm_compaction` compared `GraphTensor` against `ChipTensor` to justify
packing Graph boundaries into the payload's tensor slots, and read that pool as
`ChipTensor`. The pool is the payload's, so both now name the runtime's `Tensor` —
the assertion was true only while one type served both roles.

Verification: full rebuild of both runtimes on both architectures, cpput 119/119,
pyut 1908 passed / 14 skipped, a2a3sim 21 cases and a5sim 17 cases green. The sim
sweeps build only the cases they run, so every orchestration source was also
syntax-checked against its runtime's include path — 129 (source, runtime, arch)
compiles, which is what caught a copy under host_build_graph/ still calling
simpler::tmr::make_tensor_external. A second check reads which runtime *strings* a
Python driver mentions rather than its @scene_test decorator; that is what caught
task_timing_slots.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ChaoWao
ChaoWao merged commit 57edd46 into hw-native-sys:main Aug 25, 2026
20 checks passed
@ChaoWao
ChaoWao deleted the refactor/tensor-layout-decouple branch August 25, 2026 08:11
ChaoWao added a commit to ChaoWao/simpler-fork that referenced this pull request Aug 26, 2026
Three kinds of stale documentation, all of it naming something the tree does not
contain. `doc-consistency.md` §3: when a referent is gone, the reference goes too.

**`problems.md`, deleted.** A personal working audit of `host_build_graph` —
findings, their status, and notes on what to look at next. It was never meant to be
here. It arrived in hw-native-sys#1974 through a `git add -A` that swept it up with that
change's 958 files, and hw-native-sys#2012 then updated it rather than noticing it. Nothing
references it (`git grep problems.md` is empty), it is absent from `mkdocs.yml`,
and `wheel.packages` is `["simpler_setup", "python/simpler"]` so it never shipped.
Its closed items name the PRs that closed them, each of which carries the
reasoning in its own commit message.

**`last_task_alive`, in hbg's `RUNTIME_LOGIC.md`.** The paragraph gives four
reasons `SchedulerState` holds no per-run content, and the third was "hbg never
advances `last_task_alive`". That was accurate when written — hbg inherited the
field from `tensormap_and_ringbuffer` and did not advance it — but hw-native-sys#1837 deleted
the field with the rest of the reclamation paths, for exactly the reason the
sentence gives, and hw-native-sys#2004 then removed the ring that held it. The clause now names
the one thing in that list that does not exist. It is replaced by the live fact
from the same runtime: polling reserves no wiring or dependency pool, because
readiness comes from the task table's `completion_flags`.

**`ring 0`, in hbg's `SCALAR_DATA_ACCESS.md`.** Ownership validation was described
as checking that "the task ID ... carries ring 0, the only ring HBG places tasks
on". hbg has had no ring since hw-native-sys#2004 (`git grep 'rings\['` over its tree is
empty), and since hw-native-sys#2012 the check is on the id *space*, not a ring index. The
bullet now says what the code does and why a `GRAPH_NODE` id fails it.

**`pto_runtime2_init`, in three `scheduler.h` files.** A comment credited it with
reserving and wiring the early-dispatch queues. `git grep pto_runtime2_init`
returned only those three comments — the symbol exists nowhere. The work is done by
`SchedulerState::init_data_from_layout`, which the comment now names. Only three of
the four runtime trees carried the line; `src/a5/runtime/tensormap_and_ringbuffer`
words that member differently.

**`run_from_blob`, in `docs/buffer-abi.md`.** A present-tense table row told the
chip leaf to "hand the POD blob to `run_from_blob`", a name that has never existed
(introduced with the row in hw-native-sys#1599). The row now names the three functions that do
the work — `read_args_from_blob`, `ImportRegistry.materialize_args`,
`_submit_chip_run_materialized` — matching `worker.py`'s `submit_frame`.

Deliberately untouched: `tensormap_and_ringbuffer`'s ring documentation.
`current_task_index`, `task_window_mask`, `advance_lock` and `last_task_alive` are
all live there (162 uses across 20 non-doc files); hw-native-sys#2004 dropped the ring from
`host_build_graph` only, and said so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ChaoWao added a commit that referenced this pull request Aug 26, 2026
Three kinds of stale documentation, all of it naming something the tree does not
contain. `doc-consistency.md` §3: when a referent is gone, the reference goes too.

**`problems.md`, deleted.** A personal working audit of `host_build_graph` —
findings, their status, and notes on what to look at next. It was never meant to be
here. It arrived in #1974 through a `git add -A` that swept it up with that
change's 958 files, and #2012 then updated it rather than noticing it. Nothing
references it (`git grep problems.md` is empty), it is absent from `mkdocs.yml`,
and `wheel.packages` is `["simpler_setup", "python/simpler"]` so it never shipped.
Its closed items name the PRs that closed them, each of which carries the
reasoning in its own commit message.

**`last_task_alive`, in hbg's `RUNTIME_LOGIC.md`.** The paragraph gives four
reasons `SchedulerState` holds no per-run content, and the third was "hbg never
advances `last_task_alive`". That was accurate when written — hbg inherited the
field from `tensormap_and_ringbuffer` and did not advance it — but #1837 deleted
the field with the rest of the reclamation paths, for exactly the reason the
sentence gives, and #2004 then removed the ring that held it. The clause now names
the one thing in that list that does not exist. It is replaced by the live fact
from the same runtime: polling reserves no wiring or dependency pool, because
readiness comes from the task table's `completion_flags`.

**`ring 0`, in hbg's `SCALAR_DATA_ACCESS.md`.** Ownership validation was described
as checking that "the task ID ... carries ring 0, the only ring HBG places tasks
on". hbg has had no ring since #2004 (`git grep 'rings\['` over its tree is
empty), and since #2012 the check is on the id *space*, not a ring index. The
bullet now says what the code does and why a `GRAPH_NODE` id fails it.

**`pto_runtime2_init`, in three `scheduler.h` files.** A comment credited it with
reserving and wiring the early-dispatch queues. `git grep pto_runtime2_init`
returned only those three comments — the symbol exists nowhere. The work is done by
`SchedulerState::init_data_from_layout`, which the comment now names. Only three of
the four runtime trees carried the line; `src/a5/runtime/tensormap_and_ringbuffer`
words that member differently.

**`run_from_blob`, in `docs/buffer-abi.md`.** A present-tense table row told the
chip leaf to "hand the POD blob to `run_from_blob`", a name that has never existed
(introduced with the row in #1599). The row now names the three functions that do
the work — `read_args_from_blob`, `ImportRegistry.materialize_args`,
`_submit_chip_run_materialized` — matching `worker.py`'s `submit_frame`.

Deliberately untouched: `tensormap_and_ringbuffer`'s ring documentation.
`current_task_index`, `task_window_mask`, `advance_lock` and `last_task_alive` are
all live there (162 uses across 20 non-doc files); #2004 dropped the ring from
`host_build_graph` only, and said so.
ChaoWao added a commit that referenced this pull request Aug 26, 2026
`src/common/{host_build_graph,tensormap_and_ringbuffer}/tensor.h` sits on every
kernel translation unit's include path: each runtime's `runtime/tensor.h` umbrella
includes it to supply that runtime's working `Tensor`, and kernels name it through
the umbrella's `TaskTensor` alias. Its last line was a runtime-internal container,

    using EntryArgsStorage =
        TaskArgsTpl<Tensor, uint64_t, CHIP_MAX_TENSOR_ARGS, CHIP_MAX_SCALAR_ARGS>;

and that line was the file's only reason to include `task_interface/task_args.h`,
and through it `task_interface/buffer.h`. So every kernel pulled in the L3+
argument surface it has no use for, plus the address-free wire `Tensor` that
buffer.h declares at global scope. `EntryArgsStorage` has no kernel consumer at
all — the readers are `runtime/runtime.h`, `runtime/types.h`,
`runtime/shared/runtime.cpp`, `host/runtime_maker.cpp`, and for tmr
`aicpu/aicpu_executor.cpp`.

It moves to a sibling `entry_args.h` per runtime, which includes `tensor.h` and
`task_args.h` for itself. The eight runtime headers that name the alias include
that instead of the umbrella; the six .cpp consumers already reach it through
`runtime.h`. No declaration changes and nothing else moves.

The wire `Tensor` and a runtime's `Tensor` share a name in different scopes, so
while the edge existed a kernel could not write `using Tensor =
simpler::hbg::Tensor;` — the alias was a conflicting redeclaration of buffer.h's
struct. Both umbrellas now accept it. Confirmed against a control that restores
the include by hand and still fails, so the check answers to the edge rather than
to the alias.

Verified with the full product build — both arches x both runtimes x sim and
onboard, host, aicpu and aicore artifacts all rebuilt — cpput 119/119, and both
sim scene-test sweeps in the shape `_st-sim-{a2a3,a5}.yml` runs them: a2a3sim 60
passed / 8 skipped, a5sim 53 passed, each reconciling against `--collect-only`.

The scene-test corpus's incores were also compiled with `--manual include` on both
arches, which is the coverage #1974 lacked when it renamed a type only manual-only
cases used. That reports six failures per arch, all pre-existing on this base and
all carrying #1974's signature (`no member named 'tmr' in namespace 'simpler'`) in
four files: the two qwen `vendor/paged_attention_cce` entries #2023 fixes, and the
two `spmd_paged_attention` files it reports separately. No failure names
`entry_args.h`, `task_args.h`, `buffer.h`, a conflicting `Tensor` declaration or an
ambiguous call.
ChaoWao added a commit that referenced this pull request Aug 26, 2026
#1974 split `ChipTensor` from `simpler::hbg::Tensor` / `simpler::tmr::Tensor`
and rewrote the scene-test corpus onto the new spellings. Four sources per
arch are compiled under *both* runtimes, and those took the
`simpler::tmr::` spelling, so `host_build_graph` no longer builds them:

  tests/st/{a2a3,a5}/.../spmd_paged_attention/kernels/mix/
      paged_attention_parallel.cpp
  tests/st/{a2a3,a5}/.../spmd_paged_attention/kernels/orchestration/
      spmd_paged_attention_orch.cpp
  examples/{a2a3,a5}/.../qwen3_14b_decode/kernels/vendor/
      paged_attention_cce/tiling/entry.cpp
  examples/{a2a3,a5}/.../qwen3_14b_decode/kernels/vendor/
      paged_attention_cce/kernel/fai_body.hpp

Every case that reaches them is `manual: True`, and per-PR CI runs
`--manual exclude`, so the break is invisible there: the jobs pass because
they never compile these files.

A kernel does not care which orchestrator produced its payload, so the three
kernel-side sources name `TaskTensor` — the per-translation-unit alias each
runtime's `runtime/tensor.h` exports for exactly this. `fai_body.hpp` reaches
`host_build_graph` through `attention_rope/entry.cpp`, which the qwen3
CALLABLE lists twice.

Orchestration cannot be made runtime-agnostic that way: it builds tensors
through `simpler::<runtime>::make_tensor_external`, whose constructor is
private to a friend factory, so naming the type alone is not enough.
`host_build_graph` therefore gets its own copy of the SPMD orchestration,
which is what `run_stream_reuse` and `worker_async_fifo` already do — the
copy differs from its `tensormap_and_ringbuffer` sibling only in the
namespace. The A5 shim gains the `CALLABLE` override it was missing; it
inherited the TMR one whole, absolute paths included, so it compiled the TMR
orchestration under HBG.

The A5 copy drops `set_block_count`. Its `#if defined(SRC_A5_..._TYPES_H_)`
selected `set_core_num` for `tensormap_and_ringbuffer`, and this file is
`host_build_graph` only, where the live entry point is `set_block_num`.

Verified on a2a3 silicon: each kernel source compiles under both runtimes and
each orchestration copy under its own; `spmd_paged_attention` (3 cases) and
`qwen3_14b_decode` pass onboard under both runtimes with `--manual include`.
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