Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/designs/assets/fig1-topology.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/designs/assets/fig2-shared-memory.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
168 changes: 168 additions & 0 deletions docs/designs/vpto-comm-model-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# VPTO 通信模型

本文描述跨 rank 通信的编程模型:对称共享内存、单边访问、完成与可见性约定,
以及 `comm_scope` 边界。

非目标:集合通信算法、Runtime HCCL 绑定细节、Tile 层 DSL、CCU。

当前 IR 已落地的只有 `pto.session_init` 与 `pto.sdma_gm_gm`,写法见
[19. Async Communication](../isa/micro-isa/19-async-comm.md)。下文仍是模型约定。
下列条目尚未进入当前 IR,不要当成可编写语法:

- `!pto.async_session`、`pto.session_config`
- `#pto.remote` / `#pto.mr<rma>` 指针属性
- `pto.comm_scope`
- `pto.urma_gm_gm`、`pto.rdma_gm_gm`、`pto.sdma_gm_l2c`
- 融合 `*_signal` / `*_counter`
- kick 返回 CQ 完成记录

对端地址目前就是普通 `!pto.ptr<T, gm>`,由 host 按 `windowsIn` 同偏移算好后
作为 kernel 参数传入。A5 远端写需要 `{soft_put}`,该形态在 op 返回前完成拷贝。

## 1. 范式

采用 **PGAS / SHMEM** 式对称共享内存 + 单边访问:各 rank 共享段布局一致,
设备侧以「本端指针 + 目标 rank」读写对端同偏移数据,无需对端参与。

跨卡交换收敛为三件事:**寻址、搬运、显式同步**。Scale-up(节点内)与
Scale-out(跨节点)只更换引擎与通路,不改变编程面。

![](assets/fig1-topology.png)

一次通信分两阶段:

- **Host 准备期**:建通信域、协商对称共享段、按需注册鉴权 MR、初始化异步引擎;
随 launch 下发寻址上下文(只读)与引擎会话(有状态)。二者职责分离即可,
字段级 ABI 不在本文展开。
- **NPU 运行期**:kernel 内算址、发起搬运、用同步量或融合形态约定跨卡可见性。

```mermaid
flowchart LR
A["① Bootstrap<br/>带外交换 root info"] --> B["② BuildComm<br/>HCCL 建通信域"]
B --> C["③ 注册对称内存<br/>Window / 鉴权 RMA MR<br/>交换基址表 + token"]
B --> D["④ 逐引擎建 workspace<br/>SDMA · URMA · RDMA<br/>持久化于 device HBM"]
C --> E["CommDeviceContext<br/>寻址上下文 · 只读"]
D --> F["引擎 workspace"]
F --> G["AsyncSession<br/>有状态 · 引用 workspace"]
E --> K["launch 入参"]
G --> K
HC["CommContext<br/>host only · 不下设备"] -.->|"X"| K
```

## 2. 共享内存与指针属性

跨卡地址空间一律 `gm`。远近与鉴权不另开地址空间,由可组合指针属性表达:

| 形态 | 含义 |
|------|------|
| `!pto.ptr<T, gm>` | 本端、普通共享内存 |
| `!pto.ptr<T, gm, #pto.mr<rma>>` | 本端、已注册鉴权 RMA MR |
| `!pto.ptr<T, gm, #pto.remote>` | 远端、普通共享内存 |
| `!pto.ptr<T, gm, #pto.mr<rma>, #pto.remote>` | 远端且已注册 |

`#pto.remote` 管远近,`#pto.mr<rma>` 管鉴权;缺省分别为本端、未注册。
同偏移算址由调用方用 `CommDeviceContext.windowsIn[]` 自行完成:

```text
remote = windowsIn[peer] + (local − windowsIn[myRank])
```

结果以 `pto.castptr` 等既有手段成型为 `!pto.ptr<T, gm, #pto.remote>`(可与
`#pto.mr<rma>` 组合)。不设专用 remap op。

![](assets/fig2-shared-memory.png)

## 3. 完成与可见性(E2 / E3)

| 事件 | 保证 | 观测 |
|------|------|------|
| **E2** | 本端 source 可复用 | 异步:轮询搬运返回的 CQ 完成记录;同步 MTE:指令/pipe 完成即成立 |
| **E3** | 对端可见本次 payload | 写远端同步量,或使用融合 `*_signal` / `*_counter` |

E2 与 E3 相互独立:等到 E2 **不**代表对端可见。分离写法必须先到 E2 再发同步量;
融合形态同事务保证,对端观测到同步量即可读 payload。

```mermaid
sequenceDiagram
participant H as Host
participant D as rank i Kernel
participant Li as rank i 对称共享内存
participant Rj as rank j 对称共享内存
participant P as rank j Kernel

H->>D: launch(寻址上下文, AsyncSession, 数据 buffer)
H->>P: launch(寻址上下文, AsyncSession, 数据 buffer)
D->>Li: 取得本端 payload / 同步量地址
D->>D: windowsIn 同偏移算址 → #pto.remote 指针

alt 同步通路(MTE)
D->>Rj: 单边写 payload
Note over D,Rj: 返回即本端完成
else 异步通路(DMA 引擎)
D->>Rj: kick 单边写 payload(不阻塞标量流)
D->>D: wait event → E2:本端源可复用
end

D->>Rj: 写 signal / atomic add counter → 发布 E3
loop 同步量未满足
P->>Rj: wait / test 本端 signal / counter
Rj-->>P: 未满足则继续轮询
end
P->>Rj: 读取本端 payload
Rj-->>P: payload(已保证可见)
```

跨 rank E3 **不**复用 `cmo.cacheinvalid` / `fence.barrier_all`(核间粗栅栏)。

## 4. 同步量(内存约定,无新 op)

跨 rank 同步量是对称段内用户自划的 `i32` 位置,不是专用指令族:

| 用法 | 写者 | 发布 |
|------|------|------|
| **signal** | 单写者 | `stg` / `store` / 远端 `mte_ub_gm` |
| **counter** | 多写者汇合 | `atomic_add` |

观测:本端 `dcci` + `ldg`;等待写成 IR 轮询。与片上 SC 信号量
(`set_intra_core` 等)互不合并:核间用 SC,跨 rank 用 GM 同步量。

## 5. 融合搬运+同步

异步通路可将 E3 发布并进同一搬运事务:`*_gm_gm_signal` / `*_gm_gm_counter`。
这是跨 rank 同步唯一新增的 mnemonic 族;独立发布仍用 §4 的普通访存。
MTE 同步通路无融合形态。

## 6. `comm_scope`

`comm_scope` 是 `section.vector` / `section.cube` 内的词法区域,给通信资源
(session / 完成记录等)划寿命边界,并作为 sync 分析锚点。应对齐
`pto.vecscope` 写在 `docs/vpto-spec.md` 的层级;本节暂存约定,后续迁入该处。

```mlir
pto.section.vector {
pto.vecscope { /* 计算 */ }
pto.comm_scope {
%dst = pto.castptr %remote_i64 : i64 -> !pto.ptr<f16, gm, #pto.remote>
%cq = pto.sdma_gm_gm %dst, %src, %nbytes session(%sess)
-> !pto.ptr<i64, gm>
}
}
```

| 项 | 约定 |
|----|------|
| 位置 | `section.vector` / `section.cube`;**不**进入 `vecscope` |
| 职责 | 资源寿命边界 + Sync 分析锚点 |
| vs `vecscope` | 通信 kick、同步量读写、session、远端指针构造落在 `comm_scope` |
| 推断 | session/event 流可按 SSA 穿线推断;纯同步量流需显式书写 |
| cube | AIC 只发 GM↔GM kick 时 PlanMemory 锚点弱化;资源/Sync 锚点仍成立 |

开放问题:出口是否默认强制 E2(与正确性正交,影响跨窗 overlap)。

## 7. 通路总览

| 通路 | 承载 |
|------|------|
| 同步远端 | 核内 MTE:`mte_gm_ub` / `mte_ub_gm` + `#pto.remote` |
| 异步 GM↔GM | SDMA / URMA / RDMA + session |
| 融合 notify | `*_gm_gm_signal` / `*_gm_gm_counter` |
4 changes: 2 additions & 2 deletions docs/isa/micro-isa/18-special-scalar.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ execution scope and cache-control contract are different.
- no `l1cache` or `l2cache` policy attribute is accepted;
- the op must appear in an ordinary AICore entry function, outside both a
`pto.simt_entry` function and `pto.section.simt`;
- the supported target profile is A5 with CANN output version 9.0.0 official
or newer.
- `pto.ld_dev` is supported on A2/A3 and on A5 with CANN output version 9.0.0
official or newer. `pto.st_dev` is A5-only with that same CANN profile.

Both operations are non-atomic. They do not imply synchronization, memory
ordering, cache invalidation, cache writeback, or an L2 cache policy. Programs
Expand Down
200 changes: 200 additions & 0 deletions docs/isa/micro-isa/19-async-comm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# 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接口和文档也需要补充


> **Category:** Asynchronous GM↔GM engine transfers
> **Pipelines:** SDMA engine kick from an ordinary AICore scalar stream

This group copies a contiguous GM range through the SDMA engine. The kick does
not wait for the engine except where `{soft_put}` is documented below. The op
does not publish a completion record; local drain and cross-rank visibility are
arranged by the caller.

This document describes:

- `pto.session_init`
- `pto.sdma_gm_gm`

There is no `mte_gm_gm`. Synchronous GM↔UB copies remain in
[2. DMA Copy Programming](02-dma-copy.md).

These ops must sit in an ordinary AICore entry function. They are illegal
inside `pto.simt_entry` functions and `pto.section.simt`.

---

## Session

A session cannot be a kernel argument: only `pto.declare_struct` may produce a
`!pto.struct`. The host therefore writes a GM template, and the kernel declares
its own struct and fills it with `pto.session_init`.

The session type is fixed:

```mlir
!pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>
```

The template uses one 8-byte slot per field, in the same order. Narrow fields
occupy the low half of their slot. Each core fills its own copy, so a session
is per-core even when the template is shared and read-only.

After the fill, a kernel may retune individual fields with `pto.struct_set`.
The channel group is field 4, which is how a multi-core launch gives each core
its own queue without the host naming the core.

---

## Operation Summary

| Operation | Purpose |
|-----------|---------|
| `pto.session_init` | Copy the host template into a stack-local session struct |
| `pto.sdma_gm_gm` | Kick a contiguous GM→GM copy through the session |

---

### `pto.session_init`

- **Purpose:** Fill `session` in place from the host-written GM template.
- **Syntax:**

```mlir
pto.session_init %sess, %sess_gm
: !pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>,
!pto.ptr<i8, gm>
```

- **Operands:**

| Operand | Type | Description |
|---------|------|-------------|
| `%sess` | the 13-field session struct | Destination; written in place, no result |
| `%sess_gm` | `!pto.ptr<T, gm>` | Base of the host template |

- **Results:** None.
- **Constraints:**
- `%sess` must use the session struct type above.
- `%sess_gm` must be a GM pointer.
- Must be outside SIMT entry functions and `pto.section.simt`.
- Must be inside an ordinary AICore `pto.kernel` function.
- **Semantics:** Copy each template slot into the corresponding struct field.
The caller keeps using the value `pto.declare_struct` produced.

```text
for i in 0 .. 13:
session[i] = template_slot[i]
```

- **Example:**

```mlir
%sess = pto.declare_struct
-> !pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>
pto.session_init %sess, %sess_gm
: !pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>,
!pto.ptr<i8, gm>
```

---

### `pto.sdma_gm_gm`

- **Purpose:** Copy `%nbytes` contiguous bytes from `%src` to `%dst` through
the SDMA engine attached to `%sess`.
- **Syntax:**

```mlir
pto.sdma_gm_gm %dst, %src, %nbytes session(%sess)
{block_bytes = $block}? {channel_idx = $ch}? {soft_put}?
: !pto.ptr<T, gm>, !pto.ptr<U, gm>, i64,
!pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>
```

- **Operands and attributes:**

| Name | Type | Description |
|------|------|-------------|
| `%dst` | `!pto.ptr<T, gm>` | Destination range; may name peer memory by address |
| `%src` | `!pto.ptr<U, gm>` | Source range; may name peer memory by address |
| `%nbytes` | `i64` | Contiguous byte count |
| `session(%sess)` | the 13-field session struct | Required session |
| `block_bytes` | optional `i64` attr | Split size in bytes; omitted uses the session value |
| `channel_idx` | optional `i64` attr | Channel group for this kick; omitted uses the session value |
| `soft_put` | optional unit attr | A5 remote-write completion path; ignored on A2/A3 |

- **Results:** None.
- **Constraints:**
- `%dst` and `%src` must be GM pointers. Element types need not match; the
transfer is counted in bytes.
- `%sess` must use the session struct type above.
- There is no stride or burst model.
- `block_bytes`, when present, must be positive and a multiple of 64.
- `channel_idx`, when present, must be less than 40.
- Must be outside SIMT entry functions and `pto.section.simt`.
- Must be inside an ordinary AICore `pto.kernel` function.
- **Semantics:** Post a copy of `%nbytes` bytes from `%src` to `%dst`. The
session supplies the engine connection, the default split, the channel group,
and the service class. Either pointer may address peer memory; peer-ness is
the numeric address, not a pointer attribute.

Without `{soft_put}` the kick does not wait for the engine. Returning from
the kernel does not mean the destination is visible. The caller observes
completion by an agreed host-side check or a later sync object.

`{soft_put}` is for a remote write on A5. That generation's engine does not
perform a remote write, so this attr makes the copy complete before the op
returns. A2/A3 ignore it and still post to the engine.

```text
if soft_put and target is A5:
copy nbytes bytes from src to dst # finished when the op returns
else:
post the copy to the session's engine
return without waiting
```

- **Example (local copy):**

```mlir
%sess = pto.declare_struct
-> !pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>
pto.session_init %sess, %sess_gm
: !pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>,
!pto.ptr<i8, gm>
pto.sdma_gm_gm %dst, %src, %nbytes session(%sess)
: !pto.ptr<i8, gm>, !pto.ptr<i8, gm>, i64,
!pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>
```

- **Example (A5 remote write):**

```mlir
pto.sdma_gm_gm %dst, %src, %nbytes session(%sess) {soft_put}
: !pto.ptr<i8, gm>, !pto.ptr<i8, gm>, i64,
!pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>
```

- **Example (per-core channel after init):**

```mlir
%bid = pto.get_block_idx
%bid32 = arith.trunci %bid : i64 to i32
pto.session_init %sess, %sess_gm
: !pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>,
!pto.ptr<i8, gm>
pto.struct_set %sess[4], %bid32
: !pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>, i32
pto.sdma_gm_gm %dst, %src, %nbytes session(%sess)
: !pto.ptr<i8, gm>, !pto.ptr<i8, gm>, i64,
!pto.struct<i64, i64, i32, i32, i32, i32, i64, i64, i32, i32, i32, i32, i32>
```

---

## PTODSL

PTODSL explicit mode exposes the same two operations as `pto.session_init` and
`pto.sdma_gm_gm`. The session type is `pto.async_session_type()`. A session still
cannot be a kernel argument: the host writes the GM template, and the kernel
declares its own struct then fills it. See
[7.7 GM↔GM SDMA](../../../ptodsl/docs/user_guide/07-data-movement-ops.md#77-gmgm-sdma-ptosession_init-and-ptosdma_gm_gm)
in the PTODSL user guide.
Loading