跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 22 章 流水线、多缓冲与同步插入

/code/22-pipeline-sync-multibuffer/

04-ascend-flag-budget/README.md2408 字节
# 04 · 昇腾 flag 命名空间预算

## 目的

昇腾侧的多缓冲同步最后会落到 `SetFlag/WaitFlag` 上,而每一对
`hard_event`(producer PIPE → consumer PIPE)**只有 8 个 event-id 槽**
(`_vendor/tilelang-v0.1.15/src/ascend/transform/insert_sync.cc:2135`,
`kIntraCoreFlagLimit = 8`;跨核是 16,`:2136`)。一个 sync point 的 event id 是
`iteration % num_versions + base_id`(`MakeFlagEventId`,`:2299`),所以一个
`num_versions = v` 的同步点要吃 **v 个连续槽**,运行时无法跨越 8 的边界。

槽不够时,`RewriteFlagToBuf`
(`_vendor/tilelang-v0.1.15/src/ascend/transform/rewrite_flag_to_buf.cc:201-336`)
用 0/1 背包(容量 8)挑一组最能填满的保留为 `set_flag/wait_flag`,其余的
**溢出**到共享的 `asc_lock/asc_unlock` 互斥池——溢出不是消失,而是变成阻塞的
锁操作。

这个模型把上面这套算术摊开,回答「`num_stages` 加到多大时,瓶颈从 shared memory
变成 flag 槽位」。

## 运行前提

Python 3.9+,只用标准库。不需要 NPU。

## 运行命令

```bash
python3 flag_budget.py                # 逐 depth 扫,intra-core 8 槽
python3 flag_budget.py --sharing      # 允许可复用的同步点合并 base
python3 flag_budget.py --cross-core   # 跨核 16 槽命名空间
python3 flag_budget.py --depth 3      # 只看一档
```

## 期望输出(本机实际运行结果,节选)

```text
depth=2  num_versions per sync point = 2
  blocks: acquire_A=2, acquire_B=2, release_A=2, release_B=2
  allocator sum = 8 ids
  -> fits in 8 slots; no spill  (0 slots spare)
     acquire_A base  0  id = i % 2 + 0
     acquire_B base  2  id = i % 2 + 2
     release_A base  4  id = i % 2 + 4
     release_B base  6  id = i % 2 + 6

depth=3  num_versions per sync point = 3
  blocks: acquire_A=3, acquire_B=3, release_A=3, release_B=3
  allocator sum = 12 ids
  -> exceeds 8 slots: 0/1 knapsack keeps 6/8 slots as set/wait flags
     kept    : acquire_A, acquire_B
     spilled : release_A, release_B  -> asc_lock/asc_unlock mutex pool (blocking)
```

## 要读出什么

1. **`num_versions` 乘在每个同步点上。** 同一个逻辑依赖,深度 1 时只占 1 个
   flag id,深度 4 时占 4 个。
2. **8 个槽是很小的预算。** 两条 copy、两条 release,深度 3 就已经溢出;
   真实 kernel 的 sync point 远不止四个。
3. **溢出会改变性能口径。** 被背包丢掉的同步点从「事件标志」变成「互斥锁」,
   这是阻塞语义——正是 §6.2 里「多一个同步掉性能」的另一条路径。
4. **`--sharing` 是编译器真实在做的事。** `FlagAllocator` 的 `reusable_pairs_`
   (`insert_sync.cc:1729`、`:1742`)会把不可能同时在飞的同步点合并到同一个
   base,:depth 2 时 8 个 id 降到 6 个。
5. **跨核有 16 个槽**(`--cross-core`),所以「什么时候需要跨核同步」也在
   预算之内。

## 作者实际做到的验证级别

**CPU 逻辑验证**:在 macOS (Apple Silicon)、Python 3.11 上运行过。**没有**在任何
昇腾 NPU 上对照过。

## 这个模型不做什么

* 「可复用」的判据(`reusable_pairs_`)在真 pass 里由依赖分析给出,这里用一个
  `--sharing` 开关代表,是**上界**而不是等价实现;
* 它不建模跨核的 16 槽与 8 槽之间的关系,也不建模 `CrossCoreFlagReservation`
  对用户显式 flag 的预留;
* 它不给出任何性能数字,只说「保持在事件标志里」还是「溢出到锁」。

← 浏览本章全部代码