Skip to content

host_build_graph: route TensorCreateInfo initial values through the host view - #1905

Closed
ChaoZheng109 wants to merge 1 commit into
hw-native-sys:mainfrom
ChaoZheng109:fix/hbg-set-initial-value
Closed

host_build_graph: route TensorCreateInfo initial values through the host view#1905
ChaoZheng109 wants to merge 1 commit into
hw-native-sys:mainfrom
ChaoZheng109:fix/hbg-set-initial-value

Conversation

@ChaoZheng109

@ChaoZheng109 ChaoZheng109 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What this fixes

TensorCreateInfo::set_initial_value segfaulted the orchestrator on every
host_build_graph path. fill_tensor_initial_value memcpy'd straight to
ChipTensor::buffer.addr — a GM-heap device address that 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 that store is correct. The copy kept the
store and lost its premise. HostTensorAccessor is the seam host_build_graph
added for exactly this difference — get_tensor_data / set_tensor_data were
routed through it, the fill was not.

It went unnoticed because host_build_graph had no set_initial_value user
at all
: all three callers in the tree are tensormap_and_ringbuffer cases,
and there was no host_build_graph scalar_data case. Moving the DeepSeek-V4
case onto this runtime is what first steps on it.

What changed

Two things were missing; only both together make it work.

  1. The fill goes through the accessor, from the orchestrator call sites that
    hold one and can report a failure. init_tensor_from_create_info goes back
    to materializing metadata alone.
  2. The GM heap is registered as a region — the only add() call site is the
    per-staged-tensor loop, so the heap resolved to nothing.

Regions are kind-scoped, not pooled. read/write see staged tensors only,
which is what keeps a runtime-created output unreadable during graph
construction. That documented rule is enforced today by the heap's absence
from the table; pooling would have turned a clean failure into a silent read of
an unwritten buffer. fill sees the heap only.

The heap registers with no host view — and no mapping either. Both would be
charged to every bind for a facility most orchestrations never use, so the
halHostRegister over the whole heap (256 MB by default, 2 GiB on the dsv4
case) is deferred to the first fill that needs it and made once. A run that
sets no initial value pays nothing.

Where a mapping cannot be had at all, fill stages instead of failing. A
fallback view in add()'s shape would have to span the heap, because
find_region indexes host_view + offset — that size is forced by read,
which serves arbitrary offsets at arbitrary times. fill is neither: write-only,
source is one value repeated, so one period of the pattern in a bounded buffer
pushed in chunks with copy_to_device covers any span — the same mechanism
set_tensor_data has used on a5 onboard since paged_attention. Host
memory is 64 KiB for the run, and the pushed bytes are the filled buffer's.

A fill that does not happen says which of four things stopped it — element
width, span outside the heap, absent host view, failed device copy — rather than
naming the one that motivated the diagnostic.

The push is per-call, never deferred: the heap is reclaimed and re-let within
one orchestration, so two fills can name the same address and only their issue
order is correct.

Platform How the bytes reach the heap
a2a3 onboard Heap mapped into host address space (halHostRegister), store lands directly — no copy
a5 onboard No host-map path → staged pattern pushed with copy_to_device
simulation A device pointer is already a host pointer

An initial value inside a Graph body is unsupported

Recording addresses an internal node's outputs in the private range based at
GRAPH_RECORD_VIRTUAL_BASE, and every submission of the resulting Definition
materializes its own from a heap block whose prior contents it never reads — so
a value written during recording reaches none of the replays.

Since #1897 that cannot be answered by re-running the body on the ordinary path:
the outer shell is published before the body is recorded. The recording is
marked unsupported and graph_commit reports PTO2_ERROR_INVALID_ARGS, so the
run fails loudly where it would otherwise have replayed outputs the
orchestration believes it initialized. SCALAR_DATA_ACCESS.md states the
restriction and the two ways around it (allocate outside the body and pass it
across the boundary, or have a task write the padding).

Verification

  • a2a3 hardware — the initial_value case passes, plus one run with
    ring_heap raised to the dsv4 case's 2 GiB to confirm halHostRegister
    takes a span that size (3.4 s for the case, registration included).
  • a5 silicon — the case is level=2 and not manual, so st-onboard-a5
    (pytest examples tests/st --platform a5 --exclude-level 4) runs it, which is
    where the staged path executes. That job is green.
  • Staged path, on this box too — exercised by stubbing the sim runner's
    register_device_memory_to_host to return nullptr, so both branches of the
    mapping decision are covered locally: the initial_value case and
    paged_attention (the existing get_tensor_data user, mirrored under the
    stub) pass on a2a3sim and a5sim. Stub reverted.
  • Mapped path — a2a3sim and a5sim host_build_graph suites pass.
  • The Graph restriction — probed with a Graph-bodied variant: it reports
    FATAL(code=5) rather than producing wrong numbers, as documented.

New cases in tests/st/{a2a3,a5}/host_build_graph/initial_value/ cover both
materialization paths (alloc_tensors, and a submitted task's output via a
dummy task that dispatches no kernel so the value survives to its consumer).
Each value is observed by a kernel rather than get_tensor_data, which cannot
read a runtime-created tensor here.

Not covered

Nothing outstanding. An earlier revision of this description claimed the a5
staged path had no silicon verification; st-onboard-a5 does run the new case,
so that caveat is withdrawn.

@coderabbitai

coderabbitai Bot commented Aug 20, 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: 5ddae4e7-0bb1-4859-8c4b-76e80754df0d

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: 47d50672-dd0c-44ff-860e-1006cf3eb5fb

📥 Commits

Reviewing files that changed from the base of the PR and between f65b83c and 0a9500d.

📒 Files selected for processing (27)
  • src/a2a3/runtime/host_build_graph/docs/SCALAR_DATA_ACCESS.md
  • src/a2a3/runtime/host_build_graph/host/host_tensor_access.cpp
  • src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp
  • src/a2a3/runtime/host_build_graph/orchestration/pto_orchestration_api.h
  • src/a2a3/runtime/host_build_graph/runtime/host_tensor_access.h
  • src/a2a3/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp
  • src/a2a3/runtime/host_build_graph/runtime/orchestrator_core/pto_runtime2.cpp
  • src/a2a3/runtime/host_build_graph/runtime/pto_orchestrator.h
  • src/a2a3/runtime/host_build_graph/runtime/tensor_create_info.h
  • src/a5/runtime/host_build_graph/docs/SCALAR_DATA_ACCESS.md
  • src/a5/runtime/host_build_graph/host/host_tensor_access.cpp
  • src/a5/runtime/host_build_graph/host/runtime_maker.cpp
  • src/a5/runtime/host_build_graph/orchestration/pto_orchestration_api.h
  • src/a5/runtime/host_build_graph/runtime/host_tensor_access.h
  • src/a5/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp
  • src/a5/runtime/host_build_graph/runtime/orchestrator_core/pto_runtime2.cpp
  • src/a5/runtime/host_build_graph/runtime/pto_orchestrator.h
  • src/a5/runtime/host_build_graph/runtime/tensor_create_info.h
  • src/common/task_interface/tensor.h
  • tests/st/a2a3/host_build_graph/initial_value/kernels/orchestration/initial_value_graph_orch.cpp
  • tests/st/a2a3/host_build_graph/initial_value/kernels/orchestration/initial_value_orch.cpp
  • tests/st/a2a3/host_build_graph/initial_value/test_initial_value.py
  • tests/st/a2a3/host_build_graph/initial_value/test_initial_value_graph.py
  • tests/st/a5/host_build_graph/initial_value/kernels/orchestration/initial_value_graph_orch.cpp
  • tests/st/a5/host_build_graph/initial_value/kernels/orchestration/initial_value_orch.cpp
  • tests/st/a5/host_build_graph/initial_value/test_initial_value.py
  • tests/st/a5/host_build_graph/initial_value/test_initial_value_graph.py

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


📝 Walkthrough

Walkthrough

The host-build-graph runtime now initializes runtime-created GM-heap outputs through TensorCreateInfo::set_initial_value. Host tensor access supports heap fills, orchestration applies values during allocation and submission, graph recording falls back to ordinary execution, and A2A3/A5 tests cover both paths.

Changes

Runtime initial-value handling

Layer / File(s) Summary
GM-heap fill access
src/a2a3/runtime/host_build_graph/runtime/host_tensor_access.h, src/a2a3/runtime/host_build_graph/host/host_tensor_access.cpp, src/a5/runtime/host_build_graph/runtime/host_tensor_access.h, src/a5/runtime/host_build_graph/host/host_tensor_access.cpp, src/a2a3/runtime/host_build_graph/runtime/orchestrator_core/pto_runtime2.cpp, src/a5/runtime/host_build_graph/runtime/orchestrator_core/pto_runtime2.cpp
Host tensor regions now identify staged tensors or GM-heap ranges. GM-heap fills support mapped and viewless regions, repeated element patterns, partial tails, chunked staging, and device-copy failure reporting.
Host heap wiring and initialization contract
src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp, src/a5/runtime/host_build_graph/host/runtime_maker.cpp, src/a2a3/runtime/host_build_graph/runtime/pto_orchestrator.h, src/a5/runtime/host_build_graph/runtime/pto_orchestrator.h, src/a2a3/runtime/host_build_graph/runtime/tensor_create_info.h, src/a5/runtime/host_build_graph/runtime/tensor_create_info.h, src/common/task_interface/tensor.h, src/a2a3/runtime/host_build_graph/orchestration/pto_orchestration_api.h, src/a5/runtime/host_build_graph/orchestration/pto_orchestration_api.h, src/a2a3/runtime/host_build_graph/docs/SCALAR_DATA_ACCESS.md, src/a5/runtime/host_build_graph/docs/SCALAR_DATA_ACCESS.md
The host runtime registers pooled GM-heap spans and passes the accessor to the orchestrator. Tensor materialization no longer applies initial values. Documentation directs runtime-created output initialization to TensorCreateInfo::set_initial_value.
Orchestrator initial-value execution
src/a2a3/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp, src/a5/runtime/host_build_graph/runtime/orchestrator_core/pto_orchestrator.cpp
Task submission and kernel-less allocation apply output initial values through host tensor access. Failed mapping or fills produce invalid-argument failures. Graph recording marks initial-value nodes unsupported and uses ordinary execution fallback.
Initial-value behavior tests
tests/st/a2a3/host_build_graph/initial_value/*, tests/st/a5/host_build_graph/initial_value/*
A2A3 and A5 tests validate allocation and dummy-task initial values for ordinary orchestration and graph fallback execution.

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

Merge Risk: 🔵 Low · up to 0a950

The PR fixes host-side initial-value writes and adds a staged A5 path for devices without a host mapping. Physical A5 hardware has not exercised the chunked device-copy behavior, leaving a bounded compatibility risk; the PR is mergeable with explicit owner awareness and follow-up validation.

Possibly related PRs

Poem

A rabbit fills the heap with care,
Seven here and eleven there.
Graphs that cannot hold the seed
Hop to ordinary paths with speed.
Staged views stay neatly spun—
Initial values reach everyone.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% 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 summarizes the main change: routing TensorCreateInfo initial values through the host view in host_build_graph.
Description check ✅ Passed The description is detailed and directly explains the crash fix, implementation changes, graph behavior, and verification coverage.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/hbg-set-initial-value

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.

@ChaoZheng109

Copy link
Copy Markdown
Collaborator Author

review notes

代码审查过的结论:修复定位准确,payload.tensors[i]args 的下标对齐、TensorArgType::OUTPUT 与 union 活跃成员的对应、heap_bytessetup_static_arena(total_heap_size, …) 的一致性、Graph body 内 alloc_tensors 已被 graph_record_mark_unsupported 先行接管、orch->fatal 会让半准备的 task slot 变惰性 —— 这几处都逐一核对过,没有问题。a2a3/a5 两棵树的 8 个源文件 hunk 逐字节相同。

下面是需要跟进的几点。


Should fix

1. GM 堆的 host 映射是无条件的,代价随 PTO2_RING_HEAP 线性增长

runtime_maker.cppadd_heap() 会走进 add_region(),而 add_region() 总是先调用 register_device_memory_to_host。于是每一次 bind(bind_callable_to_runtime_impl)都会对整个 GM 堆做一次 halHostRegister,close() 时再做一次 halHostUnregister —— 哪怕这次编排一个 initial value 都没有用到

  • HBG 默认 PTO2_HEAP_SIZE = 256 MB(单 ring),dsv4 场景是 2 GiB。
  • 紧挨着的注释写的是 A run that sets no initial value never touches this region。这对 fill 成立,但对注册本身不成立,读者会被误导。
  • PR 描述里的 3.4 s 是「整个用例 + 注册」的合计,注册本身的开销没有被单独拆出来。而 BindHostViewClose 这个 phase record 现在会额外含进 256 MB–2 GiB 的 unregister,注册侧则落在别的 phase 里,归因会变糊。

建议改成首次 fill 时惰性映射:region 先登记为「尚未映射」,fill 命中且 host_view == nullptr 时再尝试注册,失败才降级到 fill_staged。这样默认路径零新增开销,a5 的 staged 路径行为完全不变。退一步,至少把 bind 侧的注册开销单独 phase-record 出来并给出实测数字,同时把那条注释改准确。

2. apply_initial_values 的 fatal 消息把四种失因说成了一种

消息固定为 "set_initial_value: no host view for the GM heap at …",但 fill() 返回 false 有四条路径:

  • region 未命中(地址不在堆内)
  • elem_size == 0(非法 dtype,get_element_size 越界返回 0)
  • elem_size > sizeof(value)
  • copy_to_device 失败

后三种发生时这条消息会把排查引向完全错误的方向;PTO2_ERROR_INVALID_ARGS 用来表示「DMA 推送失败」也不贴切。建议让 fill 区分失因,或至少把消息改成中性的措辞并带上 elem_size / dtype。


Consider

3. 弱符号 host_tensor_fill 的 fallback 正是本 PR 修的那个 bug

pto_orchestrator.cpp 里的 weak 版本直接 memcpydev_addr。这个强弱符号分派与 pto_runtime2.cpphost_tensor_read/write 的既有模式一致,模式本身是成熟的。但它意味着:一旦哪天链接顺序或构建配置出岔子、强符号没进 libhost_runtime.so,原 bug 会静默复活,且没有任何断言拦得住(现象仍然是 segfault,但排查会绕回原点)。可以考虑加一个零开销的一次性自检,比如让强符号导出一个哨兵、在 run_host_orchestration 里 assert 一次。

4. rt->orchestrator.tensor_access 没有配对清空

rt->tensor_access 和新增的 rt->orchestrator.tensor_access 都指向栈上的 HostTensorAccessor,但两者都不会在 tensor_access.close() 之后置空 —— 函数返回后指针即悬空,而 rt 活在跨 run 的 arena 里。前者是既有问题,本 PR 只是照抄了同样的形状。值得一提的是,同一个文件往上 60 行就有现成的正确范式 GraphHostStateBinding(RAII,析构时置回 nullptr)。把这两个赋值一起收进一个同形的 binding,成本很低。

5. heap_bytes 求和重复了一遍,且新循环少了溢出保护

run_host_orchestration 里重算了一遍 total_heap_size,而调用方早已算过、且带 uint64_t 溢出检查。实际不可达(前一处溢出会先 return -1),但把 total_heap_size 直接传下来比重算更清楚。


描述里的「Not covered」一节可能已经过时

PR 描述写「The a5 staged path has no silicon verification」。但新用例 tests/st/a5/host_build_graph/initial_value/ 声明了 "platforms": ["a5sim", "a5"],而 CI 的 st-onboard-a5 跑的正是 pytest examples tests/st --platform a5 --exclude-level 4(用例是 level=2,不被排除),该作业已通过。也就是说 a5 的 copy_to_device staged 路径大概率已经在真实 a5 硅片上被覆盖了

我没能直接拉到那个 job 的日志确认用例确实执行、而非被 MANUAL_MODE 过滤掉(blob 返回 403)。麻烦确认一下并更新这一节 —— 目前的措辞低估了本 PR 自己的验证覆盖度。

@ChaoZheng109
ChaoZheng109 force-pushed the fix/hbg-set-initial-value branch from 80e2880 to a8bc4fd Compare August 20, 2026 06:56
@ChaoZheng109

Copy link
Copy Markdown
Collaborator Author

Addressed 1, 2, 4, 5, 6; skipping 3.

1 — GM 堆映射改为惰性。 确认属实:add_region 原先无条件 halHostRegister,默认 256 MB、dsv4 2 GiB,且注释确实把 fill 的性质错说成了注册的性质。现在 add_heap 只登记 span,映射推迟到第一次 fill 命中(ensure_region_mapped,map_attempted 保证只尝试一次)。不设初值的 run 完全不碰堆,默认路径零新增开销;a5 的 staged 路径行为不变 —— 拿不到映射时照旧 stage+push。注释与 SCALAR_DATA_ACCESS.md 已改准。

2 — fatal 消息区分失因。 新增 HostTensorFillStatus(BadElementSize / NoRegion / NoHostView / PushFailed),fill 返回它,消息带上 dtypeelem_size,不再把四种失因都说成 "no host view"。

4 — 加了 RAII binding。 照同文件 GraphHostStateBinding 的形状写了 HostTensorAccessBinding,同时接管 rt->tensor_accessrt->orchestrator.tensor_access,析构置空 —— 顺带修掉了既有的那半个悬垂。

5 — 直接传 total_heap_size run_host_orchestration 多收一个参数,不再重算。

6 — 覆盖度表述已更正,并做了你没能完成的那步确认。 _st-npu-a5.yml:82pytest examples tests/st --platform a5 --exclude-level 4 ... --manual '$MANUAL_MODE';用例 level=2 且无 manual 键。日志 blob 我这边同样 403,所以改用等价证据:本机按 CI 相同过滤条件收集,得到 1 test collectedst-onboard-a5 已通过 ⇒ staged 路径确已在 a5 硅片上跑过。"Not covered" 一节已撤回。

3 — 不做。 哨兵自检防的是链接顺序/构建配置出岔子这种假想场景,需要动强符号导出与 run_host_orchestration;而现在两条分支都有用例覆盖(a2a3 走映射、a5 走 staged,本地再用打桩强制走另一支),真出问题会被测试逮到而不是只能靠断言。收益不足以抵这份接口面。若你坚持要,我另开一条处理。

验证:C++ UT 107/107;a2a3sim 10 passed / a5sim 6 passed;a2a3 上板 initial_value 通过;打桩强制 staged 路径下 initial_value + paged_attention 在两个 sim 上通过(桩已撤,工作区干净)。

@ChaoZheng109
ChaoZheng109 force-pushed the fix/hbg-set-initial-value branch from a8bc4fd to fbb6da9 Compare August 20, 2026 07:05
…ost view

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. HostTensorAccessor is the seam host_build_graph
added for exactly that difference, and get_tensor_data / set_tensor_data were
routed through it, but the fill was not. Nothing caught it because
host_build_graph had no set_initial_value user at all: all three callers in
the tree are tensormap_and_ringbuffer cases, and there was no host_build_graph
scalar_data case.

Two things were missing, and only both together make it work. The fill now
goes through the accessor, from the orchestrator call sites that hold one and
can report a failure; init_tensor_from_create_info goes back to materializing
metadata alone. And the GM heap is registered as a region, since the only
add() call site is the per-staged-tensor loop, so the heap resolved to nothing.

host_tensor_fill's weak fallback sits in pto_orchestrator.cpp rather than
beside the host_tensor_read / host_tensor_write ones in pto_runtime2.cpp: its
only caller is there, and the C++ unit tests that link the orchestrator do not
link the runtime translation unit, so the pair's placement does not carry over.

Regions are kind-scoped rather than pooled. read/write serve the scalar-access
API and see staged tensors only, which is what keeps a runtime-created output
unreadable during graph construction; that rule is enforced today by the heap's
absence from the table, and pooling would have turned a documented failure into
a silent read of an unwritten buffer. fill sees the heap only, matching the one
kind of buffer a TensorCreateInfo can produce.

The heap registers with no host view of its own, and no mapping either. Both
would be charged to every bind for a facility most orchestrations never use, so
the halHostRegister over the whole heap — 256 MB by default, 2 GiB on the dsv4
case — is deferred to the first fill that needs it and made once. A run that
sets no initial value pays nothing.

Where a mapping cannot be had at all (a5 onboard, whose DeviceRunnerBase
default returns nullptr), fill stages the bytes instead of failing. A fallback
view in the shape add() uses would have to span the heap, because find_region
indexes host_view + offset; that size is forced by read, which serves arbitrary
offsets at arbitrary times and needs the bytes to persist. fill is neither: it
is write-only and its source is one value repeated, so one period of the
pattern in a bounded buffer, pushed in chunks with copy_to_device, covers any
span — the same mechanism set_tensor_data has used on a5 since paged_attention.
Host memory is 64 KiB for the run rather than proportional to the heap, and the
pushed bytes are the filled buffer's.

The push is per-call, never deferred to a flush. The heap is reclaimed and
re-let within one orchestration, so two fills can name the same address and
only the order they were issued in is correct.

A fill that does not happen reports which of the three things stopped it —
element width, span outside the heap, failed device copy — rather than naming
the one that motivated the diagnostic. A region without a host view is not one
of them: on a platform with no host-map path that is the ordinary state, and
the staged push that follows reports its own outcome. The accessor's
tensor_access pointers are installed by an RAII binding in the shape of the
GraphHostStateBinding beside it, so they do not outlive the stack accessor they
name; run_host_orchestration takes the heap span its caller already summed
under overflow check rather than re-deriving it.

An initial value inside a Graph body is marked unsupported. Recording addresses
an internal node's outputs in the private range based at
GRAPH_RECORD_VIRTUAL_BASE, and every submission of the resulting Definition
materializes its own from a heap block whose prior contents it never reads, so
a value written during recording reaches none of the replays. Since hw-native-sys#1897 that
cannot be answered by re-running the body — its outer shell is published before
the body is recorded — so graph_commit reports PTO2_ERROR_INVALID_ARGS and the
run fails where it would otherwise have replayed outputs the orchestration
believes it initialized. SCALAR_DATA_ACCESS.md states the restriction and the
two ways around it.

tests/st/{a2a3,a5}/host_build_graph/initial_value/ covers both materialization
paths — alloc_tensors and a submitted task's output, via a dummy task that
dispatches no kernel so the value survives to its consumer. Each value is
observed by a kernel rather than get_tensor_data, which cannot read a
runtime-created tensor here.

Verified on a2a3 hardware, including one run with ring_heap raised to the
dsv4 case's 2 GiB to confirm halHostRegister takes a span that size (3.4 s for
the case, registration included). The case is level 2 and not manual, so
st-onboard-a5 — pytest examples tests/st --platform a5 --exclude-level 4 — runs
it on a5 silicon, which is where the staged path executes. It is additionally
exercised on a2a3 by stubbing the sim runner's register_device_memory_to_host
to return nullptr, so both branches of the mapping decision are covered on this
box: the initial_value case and paged_attention (the existing get_tensor_data
user, which then runs mirrored) pass on a2a3sim and a5sim under the stub. The
mapped path passes the a2a3sim and a5sim host_build_graph suites.
@ChaoZheng109

Copy link
Copy Markdown
Collaborator Author

#1922 的关系:这两个 PR 是互斥方案,不是先后关系

前一轮的 5 条意见都已处理(惰性映射、状态枚举、RAII binding、total_heap_size 传参、描述里的 Not covered),代码本身没有新的正确性问题。但看到 #1922 之后,我认为要先回答一个更上层的问题:这个接口在 HBG 上还该不该存在。

两者文件上不冲突(#1905src/ + tests/st/,#1922examples/),所以不会有 merge 冲突来提醒。但逻辑上是互斥的:两个都合,等于花 330 行运行时代价去实现一个零真实用户的接口。

1. #1922 合入后,真实用户归零

mainset_initial_value 的全部调用点:

位置 #1922 之后
dsv4 TMR decode_fwd.cpp × 6 全部删除
examples/{a2a3,a5}/tensormap_and_ringbuffer/scalar_data/ × 1 保留

scalar_data 那一处是演示这个 API 自己的示例 —— 注释就写着 Step 4: Runtime-created scalar output with initial value / Step 5: Verifies initial value was written correctly。一个接口只剩下自己的 demo,是该删的信号,不是该留的理由。

2. Graph 变体已经在 main 上,不是未来的假设

examples/a2a3/host_build_graph/deepseek_v4_flash_decode/kernels/orchestration/decode_fwd_graph.cpp 已经存在。而本 PR 明确把 Graph body 内的初值标成 unsupported、由 graph_commitINVALID_ARGS

也就是说 即便合了 #1905,dsv4 三份编排里的 Graph 那份照样用不了这个接口,还是得走 device-side。本 PR 修好的能力,对它要服务的用例有三分之一从一开始就不适用。而这不是实现没做到 —— 初值与 record/replay 是语义上的冲突,补不上。

3. 那 6 行不是 codegen 契约

它们随 #1838 一次性进入 decode_fwd.cpp;本机的 pypto-libpypto-dev(IR 编译器)clone 里没有任何 set_initial_value / initial_value 的 emit 代码,是手工加进生成文件的。这是本机证据、不是全网证据,但方向明确 —— 见下面「需要确认的一件事」。


建议

#1922 先合;本 PR 关掉;另开一个删除 PR。

本 PR 里唯一与接口存废无关、值得单独捞出来的是两处文档修正:

  1. SCALAR_DATA_ACCESS.md 里 “runtime-created output 不可读写” 的措辞,从 “no registered host view” 改成 “not a staged tensor”。这个说法本身更准确,与接口在不在无关。

  2. host_tensor_access.h 里那条论断是错的,必须修掉:

    The write lands immediately, never deferred to a flush: the GM heap is reclaimed and re-let within one orchestration, so two fills can name the same address and only the order they were issued in is correct.

    HBG 的 PTO2TaskAllocator 是纯 forward-bump、从不回收:

    • pto_ring_buffer.h:71 — “no task slot or heap byte is ever reclaimed while allocation is in progress. Both rings are therefore forward-only”
    • pto_ring_buffer.h:19 — “Neither resource is reclaimed during a run”
    • pto_runtime2_types.h:470 — “Whole-graph-resident hbg never reclaims at runtime”
    • 堆不够时报的是 FATAL: Graph Heap Exhausted! + “nothing is reclaimed mid-run”,一个立即的容量判决

    在 HBG 上两次 fill 永远不可能命名同一个地址。这又是一次 TMR 的性质被带进 HBG 的论证 —— 与本 PR 要修的原 bug 是同一种错误。行为(立即推送)没问题,但给出的理由是假的,而且会让读者以为存在一种实际不存在的覆写场景。

删除 PR 的范围:

  • TensorCreateInfo::set_initial_value / has_initial_value / initial_value(HBG + TMR 四棵树)
  • fill_tensor_initial_value(TMR 两棵树)与 init_tensor_from_create_info 里的调用
  • scalar_data 示例的 Step 4 / Step 5(示例主体 get_tensor_data / set_tensor_data 不受影响)
  • SCALAR_DATA_ACCESS.md 相关段落 + src/common/task_interface/tensor.h 的交叉引用注释

删除时真正要小心的地方不是 API 表面,是布局

// --- Bytes [0, 32): TensorCreateInfo-only fields ---
// These occupy the same positions as ChipTensor::buffer, ChipTensor::owner_task_id,
// and ChipTensor::start_offset. ...
uint64_t initial_value;      // [0, 8)
bool has_initial_value;      // [8, 9)
uint8_t __pad1__[7];         // [9, 16)

这前 16 字节是刻意与 ChipTensor 对齐的 —— init_tensor_from_create_info 靠一次 64 字节 memcpy 覆盖整条 cache line 1,ci 预置了 start_offset=0 / is_contiguous=true 让它们无需单独重置。直接删字段会移动后面所有成员、破坏这个优化。

要么换成显式 padding 保持布局不变(省事,但留一个无名洞),要么连同那个 memcpy 技巧一起重新设计。这是删除 PR 的实际风险所在。


需要确认的一件事

codestyle.md §10 Tier C,TensorCreateInfo 属于编排 API 的对外契约,删字段不该单方面拍板。上面第 3 点的证据指向「没有外部生成器依赖」,但值得跟 pypto codegen 那边确认一句。

如果确认还有别的模型在用,那就退回次选方案:#1922 先合,本 PR 作为过渡实现后合,同时记一条 issue 指向删除,并补上一条 debug 级提示 —— ActiveMask 非空且 output 带初值时提醒一次,挡住「kernel 全写、初值不可观测但代价照付」的静默误用(a5 上那是整块 buffer 的 copy_to_device,落在编排关键路径、每次 run 重付)。

@ChaoZheng109

Copy link
Copy Markdown
Collaborator Author

关闭。#1922 已合入 main(43e61c69),采纳上一条评论的方案 A。

关闭理由

  • dsv4 的 6 处 set_initial_value 已在 Fix: initialize DSv4 decode buffers on device and bound hc_head_linear to valid rows #1922 中改为 device 侧初始化,main 上归零。
  • 该接口在 main 上仅剩 examples/{a2a3,a5}/tensormap_and_ringbuffer/scalar_data/ 一个调用点,而那处正是演示这个 API 自身。
  • Graph 形态的编排从语义上就用不了它 —— 初值与 record/replay 冲突,本 PR 也只能把它标成 unsupported。修好的能力对原本要服务的用例有三分之一天然不适用。

为零真实用户的接口背 ~330 行运行时代码不划算。

更正:上一条评论建议捞出的两处文档修正,其实不需要捞

那两处的措辞问题都是本 PR 自己造成的,不是 main 上独立存在的缺陷:

  1. SCALAR_DATA_ACCESS.md 的 “Unsupported: no registered host view” —— 在 main 上是准确的,因为 main 从不注册 GM 堆(runtime_maker.cpptensor_access.add 只在 per-staged-tensor 循环里)。运行时报的错也正是 “no host view for device address”。它只有在本 PR 注册了堆之后才变得不精确。pto_orchestration_api.h 里同样的措辞同理。
  2. host_tensor_access.h 里那条 “the GM heap is reclaimed and re-let within one orchestration” —— 这段文字只存在于本 PR 新增的 fill() 文档中,main 上没有 fill()

关掉本 PR,两处随之消失。所以没有可捞的文档修正。

顺带确认那条论断确实是错的:pto_ring_buffer.h:19/71/247 明确 HBG 是 forward-only bump、“nothing is reclaimed mid-run”,我另查了是否存在任何回退堆顶的路径 —— 没有,reserve_heap_scratch 也是纯 bump。这是把 TMR 的性质套到 HBG 上,与本 PR 要修的原 bug 属同一类错误。

一个确实独立存在、仍在 main 上的缺陷

rt->tensor_access 悬垂:runtime_maker.cpp:585 把栈上 HostTensorAccessor 的地址存进跨 run 存活的 rt,全仓没有任何 tensor_access = nullptr。目前是潜在问题(每次 bind 都会在使用前重新赋值),但生命周期不匹配是真的,同文件 518 行的 GraphHostStateBinding 就是现成范式。这条与本接口存废无关,已单独记下。

后续

删除 set_initial_value 的 PR 另开。按 codestyle.md §10 Tier C,TensorCreateInfo 属编排 API 对外契约,动字段前需与 pypto codegen 侧确认(上一条评论给的是本机 clone 证据,不是全网证据)。删除时的实际风险在 TensorCreateInfo 前 16 字节与 ChipTensor cache line 1 的布局对齐,而非 API 表面。

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