Skip to content

feat(vpto): add sdma_gm_gm with session-template expansion - #1324

Open
kerwinair wants to merge 6 commits into
hw-native-sys:mainfrom
kerwinair:feat/vpto-sdma-gm-gm-expand
Open

feat(vpto): add sdma_gm_gm with session-template expansion#1324
kerwinair wants to merge 6 commits into
hw-native-sys:mainfrom
kerwinair:feat/vpto-sdma-gm-gm-expand

Conversation

@kerwinair

Copy link
Copy Markdown

Add pto.sdma_gm_gm as a Layer-2 micro-op that copies between GM ranges from an ordinary AICore kernel. The session is a stack-local struct filled by pto.session_init from a host-written GM template; field layout is shared through AsyncSessionABI.h.

A2/A3 post SQEs and ring the doorbell through UB with a byte-granular MTE store. Handwritten pto.st_dev is rejected on A2/A3; A5 may use it for the doorbell. A5 uses the engine for local copies and GET. Remote PUT on A5 is expanded to a chunked GM-to-UB-to-GM copy, because that generation cannot write remotely through SDMA.

Runtime cases cover local, peer PUT, peer GET, and multicore transfers. Host workspace helpers stay in the test tree.

ISA chapter 19 documents the current ops. The broader comm programming model stays in docs/designs/; URMA/RDMA, pointer remote attributes, and fused notify are not in the current IR.

@@ -0,0 +1,189 @@
# 19. Async Communication

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PTODSL接口和文档也需要补充

@@ -0,0 +1,40 @@
// Copyright (c) 2026 Huawei Technologies Co., Ltd.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

新增ST用例统一使用PTODSL测试框架,参考:test/vpto/cases/micro-op/a5-extra/vmadd.py

kerwinair and others added 3 commits August 26, 2026 22:38
Add pto.sdma_gm_gm as a Layer-2 micro-op that copies between GM ranges
from an ordinary AICore kernel. The session is a stack-local struct
filled by pto.session_init from a host-written GM template; field
layout is shared through AsyncSessionABI.h.

A2/A3 post SQEs and ring the doorbell through UB with a byte-granular
MTE store. Handwritten pto.st_dev is rejected on A2/A3; A5 may use it
for the doorbell. A5 uses the engine for local copies and GET. Remote
PUT on A5 is expanded to a chunked GM-to-UB-to-GM copy, because that
generation cannot write remotely through SDMA.

Runtime cases cover local, peer PUT, peer GET, and multicore transfers.
Host workspace helpers stay in the test tree.

ISA chapter 19 documents the current ops. The broader comm programming
model stays in docs/designs/; URMA/RDMA, pointer remote attributes, and
fused notify are not in the current IR.
PTODSL built a kernel's shared library from two inputs it generated itself, the
launch code and the ptoas-produced kernel object. A kernel whose host side
already exists in C++ had no way in, leaving only two bad options: reimplement
the C++ in Python, or ship it as a second library the caller loads separately and
keeps in step by hand.

native_options names host C++ sources to compile and link into the same library,
the include directories they need, and the libraries to link against. Host source
contents are digested into the build's cache key, so editing one rebuilds rather
than silently reusing. Sources are compiled as host code, reusing the host flags
that already existed here but were never wired up.

compiled.native_library() reaches the result. Library loading moves out of
LaunchHandle so one loaded library is shared by every launch handle over a
specialization and by callers looking for a host symbol.

Paths resolve against the declaring file, as source= already does. Unknown keys,
library names that are really paths or flags, and include_dirs without
host_sources are all rejected when the decorator runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Review asked that new ST cases use the PTODSL test framework rather than
per-case C++ and shell harnesses. Four directories of kernel.pto, launch.cpp,
main.cpp and run.sh become one Python file.

Only the A5 {soft_put} form of pto.sdma_gm_gm moves, and that is the whole
constraint: it expands to a synchronous GM->UB->GM copy, so the transfer is done
when the kernel returns and the result is an ordinary golden comparison. The
engine form posts SQEs and rings a doorbell, so its destination is polled rather
than read once, which no golden harness expresses. It also only takes effect on
A5; a2/a3 ignore the attribute and post SQEs instead, so these cases are A5-only
by construction rather than by preference.

The four cases share two kernels and differ mostly in the session template the
host writes, which is the point: the transfer is described by session data, not by
the kernel. Multicore needs more than one block, so golden_output_case gains a
grid parameter.

The engine path keeps a spike under test/comm/ rather than a case, because what is
unproven there is not the transfer but whether an async workspace can attach to
the device and context torch_npu set up, whether memory it allocates is
addressable by a harness-launched kernel, and whether an engine-filled destination
is observable. It stays out of test/vpto/cases/ so the validation runner does not
pick up something needing a CANN 9.0 toolkit, and it has no skip path, because a
spike that quietly passes when it could not run answers nothing.

AsyncWorkspaceShim.cpp is what lets Python drive that without a second copy of the
session ABI: every field position and slot width stays in AsyncSessionABI.h, and
the shim takes named values and returns an opaque byte image. It rides into the
kernel's own library through native_options, so there is one build and one
artifact. HcclWindows.h goes away with the C++ cases that were its only caller.

The IR shapes these kernels rely on are pinned in a lit case as well, since the
Python cases only run where a device or simulator is available.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kerwinair
kerwinair force-pushed the feat/vpto-sdma-gm-gm-expand branch from 8ff7abe to f6a1f63 Compare August 26, 2026 14:40
kerwinair and others added 3 commits August 27, 2026 12:05
The option and command-line tests do not need MLIR bindings; importing
launch/codegen at package load time made collection fail on machines
that have not built PTOAS yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
from __future__ import annotations turned pto.ptr(...) into strings, so
@pto.jit rejected the same entry ABI the soft_put ST already uses.

Co-authored-by: Cursor <cursoragent@cursor.com>
bisheng --cce-fatobj-link does not pull the C++ runtime, so a host source
that uses std::string failed the kernel .so with undefined cxx11 symbols.

Co-authored-by: Cursor <cursoragent@cursor.com>
@zhangstevenunity

Copy link
Copy Markdown
Collaborator

关于微指令 session / template 建模的设计建议

经过讨论,先明确这里存在两个不同层级的概念:

  • !pto.async_session 服务于通信类 Tile Op,是 Tile IR 层的逻辑异步会话;
  • 本 PR 的 session 服务于 pto.sdma_gm_gm 等通信微指令,是设备执行层的 descriptor / queue handle。

因此不建议直接复用现有 !pto.async_session。不过,微指令 session 仍然建议建模为独立的 nominal type,而不是把通用 !pto.struct 作为公开 IR 契约。

总体结论

本 PR 将 session 拆成两部分的方向是合理的:

  1. 每个 core 在 kernel entry 加载一份本地配置快照;
  2. queue head/tail 等跨 launch 状态保留在 GM workspace 中。

需要调整的是抽象边界。建议分为三层:

层级 建议表示 含义
Tile IR !pto.async_session Tile 通信 API 的逻辑会话
通信微指令 IR !pto.comm_session<sdma>(名称可讨论) SDMA 微指令使用的本地 descriptor/handle
wrapper expansion / codegen 后 !pto.struct<...> / LLVM struct 13 字段物理 ABI

也就是说,!pto.struct 可以继续作为 lowering representation,但不建议成为用户、前端和微指令文档直接依赖的 session 类型。

当前公开 struct 形式有几个问题:

  • 13 个字段的顺序和宽度泄漏到文本 IR,ABI 演进会直接破坏已有 IR;
  • pto.struct_set %session[4] 只能靠约定表达 channel_idx,verifier 看不到字段语义;
  • !pto.struct 的栈存储/provenance 限制迫使 pto.session_init 采用 declare_struct + 原地写入 + 无结果 的形式,底层表示约束反向影响了语义层 API;
  • ContextGmTmpBufAddr 被压成 i64,地址空间、alias 和 queue resource 的副作用信息都会丢失。

%template 如何由用户配置

不建议让用户直接构造 %template : !pto.ptr<i8, gm> 的 13 个槽位。否则即使 device 侧有了 session 类型,ABI 复杂度也只是被转移到了 host 用户。

建议 template 本身也是一个带类型的 opaque launch argument,例如:

func.func @kernel(
    %template: !pto.comm_session_template<sdma>,
    ...)
{
    %bid = pto.get_block_idx
    %session = pto.session_init %template
        scratch(%tmp)
        channel(%bid)
        sync_id(3)
        : !pto.comm_session_template<sdma>
          -> !pto.comm_session<sdma>

    pto.sdma_gm_gm %dst, %src, %nbytes session(%session)
        : ...
}

!pto.comm_session_template<sdma> 在 kernel ABI lowering 后仍可只是一个 GM 指针;它的价值是让 IR/parser/verifier 区分“任意 ptr<i8>”和“合法 session template”。

Host/runtime 侧提供具名 builder,而不是让用户填写槽位:

workspace = pto.comm.AsyncWorkspace(channels=grid)
template = workspace.create_sdma_session_template(
    block_bytes=1 << 20,
    comm_block_offset=0,
    qos=6,
    dest_rank_id=0,
)
kernel(dst, src, template, nbytes)

Runtime 负责从 workspace 获取 ContextGm 和有效 channel 数,自动填写 Engine=SDMAFlags=valid 等固定字段,执行参数校验,使用共享 ABI header 序列化,分配/上传 GM buffer,并管理其生命周期。

本 PR 中 AsyncWorkspace.pack_session() 加上 C++ shim 已经是一个不错的原型:Python 只传具名参数,字段索引和布局统一来自 AsyncSessionABI.h。如果 template 是正式用户接口,建议将这套能力从 test/comm 提升为正式 runtime API。

字段所有权建议

13 个字段也不应全部由 host template 配置:

  • Host/runtime 负责:ContextGmChannelNumEngineDestRankIdQpIdxFlagsQos,以及希望在 launch 间动态调整的 BlockBytes/CommBlockOffset
  • Kernel/compiler 负责:ChannelIdxTmpBufAddr/TmpBufSizeSyncId。这些依赖 block id、UB 内存规划和 kernel 内 event 分配,host 不应传裸 UB 地址或猜测 event ID。

这些 kernel-owned 字段可以通过 session_init 的具名 operand/attr 或专门的 pto.session_set_channel 等 op 设置,不再暴露数字字段索引。

对本 PR 的落地建议

如果目标是稳定并公开这套微指令语法,建议:

  1. 增加独立的微指令 session nominal type,而不是复用 Tile async_session
  2. session_init 产生该类型,并用具名配置接口替代通用 struct_get/struct_set
  3. 在 wrapper expansion 后再物化当前 13 字段 struct,继续复用 AsyncSessionABI.h
  4. 提供正式的 host template builder,并同步 runtime、文档和端到端测试;
  5. 对 queue context 使用显式 typed handle 或专门的 side-effect resource,避免不同 session 指向同一 workspace 时 alias 信息缺失。

如果当前阶段只定位为内部 spike,也可以暂时保留 struct,但建议让它只出现在 expansion 后的内部 IR,不把完整 struct layout 固化为公开微指令语法。

@zhangstevenunity

Copy link
Copy Markdown
Collaborator

建议拆分同步 soft_put fallback 与异步 SDMA op

从 IR 设计看,把同步 fallback 作为异步 op 的一个 attribute,会导致同一个 op 的完成语义不统一:普通 pto.sdma_gm_gm 在提交 SQE 后立即返回,传输尚未完成;但 A5 上带 {soft_put} 时会展开为同步的 GM→UB→GM copy,op 返回时传输已经完成。也就是说,调用者和优化器不能仅根据 op 名称与接口判断它是否具有异步副作用、何时可以安全复用源/目标内存,语义还会随 attribute 和目标架构变化。

建议将两种语义拆开。异步 SDMA op 显式返回 event,并提供对应的 wait:

%event = pto.sdma_gm_gm_async ... -> !pto.sdma_event
pto.sdma_wait %event

同步 fallback 则使用独立微指令:

pto.soft_put_gm_gm ...  // synchronous

如果暂时不拆 op,至少也应在类型或接口层显式表示 completion contract,而不是仅通过 soft_put attribute 和 lowering 实现隐式改变同步/异步语义。这样优化器才能正确建模内存依赖,调用者也能从 IR 本身判断返回时传输是否完成。

@zhangstevenunity

Copy link
Copy Markdown
Collaborator

其他需要明确的通信 session / SDMA IR 设计问题

除同步 soft_put fallback 与异步 op 混用的问题外,我认为下面几项也需要在接口定型前明确。这些不只是 lowering 的实现细节,会影响 IR 的资源、别名和并发语义。

1. Session 隐藏了真正的有状态 queue 资源

pto.sdma_gm_gm::getEffects() 当前把 queue 的读写副作用挂在 %session operand 上,但实际被修改的是 SessionField::ContextGm 间接指向的 GM queue tail。两个不同的 session struct 可以指向同一 queue,优化器却无法从 SSA value 判断它们 alias;同一个 session 选择不同 channel 时又可能互不冲突。

建议让 session/queue 成为显式的资源句柄,并为通信 queue 定义专门的 side-effect resource。仅把隐藏的 GM 状态表示为对 stack-local struct 的读写,不能准确描述实际 alias 关系。

2. Queue capacity/backpressure 没有 IR contract

一个 pto.sdma_gm_gm 可以按照 nbytes / block_bytes 拆成任意多个 SQE,但接口没有定义可用 slot 不足时应该等待、分批提交、返回失败,还是要求调用者预先保证容量。当前 lowering 直接循环写 SQE,因此 queue overrun 不只是缺少一次 bounds check,更根本的问题是 op 的 backpressure 语义没有定义。

建议先明确:异步 kick 是否可以为了等待空闲 SQ slot 而阻塞 scalar stream;如果不可以,则需要显式的 reserve/status 机制或可验证的容量前置条件。

3. Host 配置与 kernel/compiler 资源所有权混在同一个 template 中

TmpBufAddr/TmpBufSizeSyncId 和 per-core ChannelIdx 目前都由 Host template 提供,但 UB scratch 地址和 pipe event id 应由 kernel 的内存规划、同步资源分配负责,Host 无法知道它们是否与 kernel 内其他 UB/event 使用冲突;channel 与 block 的映射也通常是 kernel-side 决策。

建议 Host template 只携带通信域/runtime 拥有的信息,而 kernel/compiler 拥有的值作为显式 operand:

%s = pto.sdma_session_init %template
    channel(%bid)
    scratch(%tmp)
    sync_id(%event)
    : !pto.sdma_session_template -> !pto.comm_session<sdma>

4. 本地/远端地址以及 engine 类型没有进入类型系统

当前 local copy、remote GET 和 remote PUT 都使用普通 !pto.ptr<T, gm>,远端性质只藏在数值地址中。因此 verifier 无法判断 A5 remote PUT 是否需要特殊通路,也无法根据 IR 检查架构合法性。

同时 session ABI 有 Engine = SDMA/URMA/RDMA 字段,但 pto.sdma_gm_gm 不读取或验证该字段;任何具有相同 13-field struct shape 的 session 都会被接受。建议至少使用 #pto.remote 或等价的显式地址属性,并把微指令 session 设计成 engine-specific nominal type,例如:

!pto.comm_session<sdma>
!pto.comm_session<urma>
!pto.comm_session<rdma>

5. ABI 中存在未被当前 op 消费的配置字段

当前 lowering 没有消费 EngineDestRankIdQpIdxFlagsTmpBufSize 等字段。把未来 engine 的字段提前放进公共 13-field struct,会形成“用户可以配置,但 op 实际忽略”的接口契约,也增加 ABI 演进成本。

建议只在当前 engine-specific session 中暴露有明确语义并被验证/消费的配置;future engine 的状态放入对应 session 类型。底层 struct/template layout 可以继续作为 runtime 与 lowering 的内部 ABI,但不应成为用户直接操作的 micro-IR 接口。

总体建议是把以下四类状态分层,而不是压进一个可任意 struct_set 的裸 struct:

  1. Host/runtime 通信域配置;
  2. kernel/compiler 分配的 scratch、event 和 per-core channel;
  3. 持久化的 queue runtime state;
  4. 架构相关的 SQE encoding。

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.

3 participants