Skip to content

Add: persistent kernel-context execution resources (K2) - #2176

Draft
YunjiQin wants to merge 2 commits into
hw-native-sys:mainfrom
YunjiQin:k2/persistent-state
Draft

Add: persistent kernel-context execution resources (K2)#2176
YunjiQin wants to merge 2 commits into
hw-native-sys:mainfrom
YunjiQin:k2/persistent-state

Conversation

@YunjiQin

@YunjiQin YunjiQin commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Dependency and review scope

Depends on #2064 (K1), rebased onto its current head 31b03d92d6908d3141589de35369e3b67aef0a82.
There is exactly one K2 commit above that base.

K1 is still open and its branch belongs to another fork, so this Draft PR targets main and temporarily includes the dependency in GitHub's diff. Review the K2-only comparison. Rebase onto merged K1 before marking this PR ready.

Summary

  • Own a dedicated AICPU stream, hidden AICore stream, and five context events created with ACL_EVENT_SYNC.
  • Allocate the device Runtime image, architecture register table, device KernelArgs, and callable uploads once; reuse them across preparation and release them at explicit close.
  • Implement onboard kernel init and prepare on K1's write-once mode latch. Borrow the caller's current device without changing caller-owned ACL/device lifecycle state.
  • Preserve retryable teardown ownership: failed persistent releases remain tracked, device context/runtime SO stay live, and explicit C++ finalize failures reach Python before registries are cleared.
  • Validate the entire flexible-length callable image before parsing or uploading any variable tail data.
  • Reject destruction of an unclosed kernel context. Kernel fatal teardown skips program mode's device-reset recovery policy.

Review fixes

  • Validate callable signatures, names, binary extent, canonical child offsets, child headers/binaries, and exact total image length.
  • On persistent release failure, skip allocator finalization and retain both owner and allocator records for a real explicit-close retry. The onboard test injects failure into PersistentKernelArgs through the real finalize_common path.
  • Make explicit ChipWorker::finalize() throw on device teardown failure; its destructor catches failures at the non-throwing cleanup boundary.

Validation

  • Editable rebuild completed for all runtime variants.
  • C++ no-hardware suite: 146 passed.
  • CPU Python (test_chip_worker.py, test_kernel_mode_c_api.py): 53 passed, 36 skipped.
  • A2/A3 onboard persistent-free retry: 2 passed (HBG and TMR).
  • Earlier full A2/A3 lifecycle run after rebase: the other 14 lifecycle cases passed; the two persistent-free cases exposed the now-fixed false-success path and then passed in the targeted rerun.
  • Commit-time pre-commit suite passed, including clang-format, clang-tidy, cpplint, ruff, and pyright.

Tests cover complete callable truncation/range rejection, prepare reuse, duplicate IDs, persistent allocation retry, stream/event close retry, explicit wrapper retry, unclosed destruction, fatal-device admission shutdown, and zero calls to all intercepted ACL/device lifecycle APIs during kernel mode.

Boundaries / remaining acceptance

  • supported() remains zero and kernel launch remains a rejecting stub. Fake downstream consumption is not a production binder/event-protocol test.
  • A5 binaries build; A5 hardware has not been tested on this A2/A3 host.
  • No real poisoned-card recovery or full ACLGraph/TMR/HBG execution claim. Capture feasibility probes are separate evidence.
  • DFX-enabled preparation and close-time artifact flush remain outside this implementation.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

Kernel mode borrows the caller's device and stream for a bounded
asynchronous operator. Define its C ABI, invocation envelope, and
lifecycle contracts without enabling kernel execution in any backend.

- Add the four simpler_kernel_mode entries, validating unsupported
  stubs, and distinct invalid-argument and invalid-state errors.
- Latch the execution identity once. Program init latches PROGRAM
  before side effects; errors and finalization cannot change the mode.
  Separate thread binding, program attachment, and borrowed-device
  adoption, and guard the onboard device lifecycle by that identity.
- Latch PROGRAM in ensure_acl_ready before its first ACL call. The
  entry is reachable without simpler_init, so taking ACL ownership is
  itself what makes a context a program context; leaving it unlatched
  would let a later kernel latch coexist with acl_ready_, whose
  finalize resets a device the context does not own.
- Decide a kernel-mode arena capacity refusal across every region
  before touching any of them. The commit sequence rolls all regions
  back on failure, so a refusal raised from inside it would release
  the committed bases a captured graph still names.
- Pin the 40-byte invocation header and every field offset. Make its
  trailing reserved bytes explicit and require both reserved fields
  to be zero. Payload validation belongs to the AICPU consumer.
- Derive scalar counts from the callable signature. Preserve the
  existing factory signatures and serialized layout, with fixed
  callable offsets and tests that ignore historical padding bytes.
- Require the kernel capability probe when loading a runtime. Resolve
  its lifecycle entries only for a supported context and publish the
  pointers after initialization succeeds.
- Provide the kernel phase/cleanup state machine and restricted,
  non-throwing operation tables, compiled in its unit test until
  production integration. Keep stream/event handles encapsulated.
- Cover wire bytes, scalar counts, error contracts, cleanup retries,
  arena refusal over real committed regions, and shared-library
  capability negotiation including failed-init recovery. Keep
  uniform-export checks for all eight components.

Kernel init remains unsupported, so no production call latches KERNEL.
Production integration must supply generation checks, safe enqueue/close
serialization, and graph-resource lifetime enforcement.

@nalinaly nalinaly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

本次为静态代码 review,未运行测试、未修改实现。建议修复三处逻辑问题后再合并,具体触发条件和修改建议见对应行内意见:

  1. prepare 未验证 callable 完整长度,截断的可变尾部可导致 Host 越界读取。
  2. 持久内存连续释放失败时,allocator 清空跟踪记录,破坏 close 的真实重试能力。
  3. C++ finalize 正常返回失败分支,导致 Python 误判成功并清空 registry。

本次不将 capture 外 init/prepare 的同步契约讨论列为缺陷。

Comment thread src/common/platform/onboard/host/c_api_shared.cpp
Comment thread src/common/platform/onboard/host/device_runner_base.cpp
Comment thread src/common/worker/chip_worker.cpp Outdated
Provision persistent kernel streams, events, runtime arguments, and callable uploads on top of the K1 ABI. Preserve retryable teardown ownership, validate complete callable images, and surface explicit finalize failures through the public worker API.
@ChaoWao

ChaoWao commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

上板实测:K2 押的那个前提已经验过了(过),外加一条它该写进契约的约束

这个 PR 拥有的东西——一条专用 AICPU 流 + 一条 hidden AICore 流 + 五个 ACL_EVENT_SYNC context event——正好是我这几周探针测的对象。#2064 已合(1d1ddc81),把相关结果集中给到这里。

环境:a2a3 onboard、CANN 9.0.0、torch_npu 2.7.1。代码与全部结果在 docs-kernel-mode/probes/,每条附复跑命令与观测边界。


✅ 1. "常驻 event 集被多张捕获图共享"——这是 K2 的核心押注,此前没人验过。已验,过。

kernel 模式的两条流和五个 event 是建一次、活到 context 结束的;而 vLLM 每个 batch 桶捕一张图,于是 N 张图会 record/wait 同一批 event 对象。H7「分桶切换」默认这成立。

Probe D(两张图共享同一批常驻 event/stream,各写自己的 pattern 进同一对 device slot):

结果
第二张图在同一批 event/stream 上捕获 未抛异常
交替 replay A B A B A A B中间不清零 7/7,每步 slot 恰好翻成该图的 pattern
背靠背 replay,中间不做主机同步(AB/BA/AA/BB) 4/4
replay 之后在同一批 event 上跑 eager
自己的 graph pool 再捕一张图

零 ACL 错误。背靠背那一臂是最硬的——同一个 event 在上一次 record 可能还在飞的时候被第二次 record,这正是常驻 event 集最该崩的地方,也是 vLLM 实际会产生的形状。

K2 的资源模型可以按"context 常驻"设计,不需要 per-graph 复制。

✅ 2. 双流形状与 capture 相容,两条独立路径各验一次

  • Probe B′(经 torch_npu.npu.graph):AICPU 专用流与 AICore hidden 流的工作都进了图、都随 replay 重放,3/3,零 ACL 错误。
  • Probe C(raw aclmdlRICapture* 直驱,不经 torch 包装):同一形状独立复核,CaptureEnd 正常出图。

计划把 Probe B 标为"最要紧""不通则整段返工,930 前返工窗口极窄"——已经通了


🔴 3. 一条 K2 应当写进契约的约束:这五个 event 不能跨捕获边界被等待

Probe C 实测aclrtStreamWaitEvent 等一个在捕获开始之前 record 的 event107024,当场拒绝(107024 正是 capture 隔离错误)。

K2 只创建和持有这批 event,record/wait 经 ops 表(record_event / wait_event)交给 binder。但事件集的生命周期契约属于 K2,所以这条约束应当由 K2 的头文件声明,而不是留给每个消费者自己发现。

这不是假设,已经有一个实例#2187 binder 里

if (h.consume_prepare_tail &&
    !step(KernelLaunchStep::PrepareWait, ops.wait_event(ops.context, h.caller, h.prepare_tail)))

PrepareTail 在 prepare 期 record(capture 之外),launch 时在 caller 流上入队一次 wait ——捕获下必被 107024 拒。而且它是条件性的(f.pending_prepare),"prepare 一次、捕获、replay N 次"的测试永远踩不到。我已在 #2187 上单独提了。

建议 K2 的 event 集文档写明:每个 event 的"上一次 record"与"这次 wait"必须落在同一个捕获上下文内,否则 107024。 这条同时也解释了 §12.2 为什么两个存活分支都要求"不 enqueue 旧 tail wait"。

🟠 4. caller_stream 别放进常驻状态——目前没放,建议写死

我核过:本 PR 里 caller_stream 只作为入参出现,没有被存进任何常驻结构。这是对的,建议在头注释里写成契约,免得 K5/K4 后面往里加。两个互相独立的理由:

① capturetorch_npu.npu.graph 进入时切到它自己的捕获流,outer != capture。拿捕获外那条流做 record/wait 的后果是——入队不报错、capture 不报错、replay 静默地什么都不做,只有数值不对(Probe B′ 负对照,已复现)。

② 任务队列TASK_QUEUE_ENABLE=1 时 torch 把 op 投给 acl_thread 后台发射。队列深 4000、每臂 40×4 轮——每次现取 current_stream() 的臂 0/160 违例,缓存指针的臂 4 次违例(读到 3997、3907,即先前 torch op 还没提交到流上)。注意"现取就没事"是观察不是保证,0/160 不构成契约。

🟡 5. DFX 的 start 半边落在这个 PR

#2202。今天 kernel 模式下 DFX 既不启动也不落盘——不是会出错,是根本不转:start_shared_collectors_for_run 只从 launch_run 可达(device_runner.cpp:556),导出只从 drain 路径可达(device_runner_base.cpp:1975),kernel 模式两头都没有。

修法是把括号从 run 作用域挪到 context 作用域:start() 放 kernel init(本 PR)quiesce()+导出放 close()#2163/#2200/#2201/#2204 之后这两个函数已改收显式参数 (const DfxRunConfig&, uint32_t pipeline_slot),所以 kernel context 可以直接拿自己的配置调它们,不必先把状态摆到 runner 成员上。kernel 模式 pipeline_depth == 1,slot 恒 0。


附:两条顺带确认

① 本 PR 把 kernel_execution_state.cpp 加进了 HOST_RUNTIME_SOURCES(platform CMakeLists 两处)。我在 K1 上提过这个文件当时位于 src/ 却不被任何 library target 引用、只编进自己的 UT——这里修掉了,谢谢。

② 计划里有一句现在不准(代码没问题,只是描述):K1 卡说"kernel context 的 device_id_ 恒未设置,finalize() 开头即返回,kernel 分支不可达"。实测 adopt_borrowed_device 会设 device_id_device_runner_base.cpp:559),所以 finalize 主体对 kernel 契约是可达的;但 merged main 里有 if (!execution_mode_latch().is_kernel()) 守住设备复位段,且 ensure_acl_ready 先 latch PROGRAM 使 kernel context 的 acl_ready_ 恒 false,所以行为是对的。与本 PR 的"Kernel fatal teardown skips program mode's device-reset recovery policy"一致。

边界:以上全部限 a2a3,a5 一个没跑——按非目标 #12 不得由 a2a3 推断 a5。Probe E(handshake 最小失效区)我没跑,⑦∥K7 那边别当它已完成。

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.

4 participants