跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

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

/code/22-pipeline-sync-multibuffer/

03-sync-count-vs-perf/README.md2550 字节
# 03 · 同步点数量 vs 稳态步长

## 目的

把「漏一个同步会偶发出错,多一个同步会掉性能」的**一半**(性能那一半)变成算术:
在一个 `copy -> compute` 的 `depth` 槽环里,加不同数量的 barrier,看稳态每轮步长
怎么变。

四个变体:

| 变体 | 加了什么 | 正确吗 |
| --- | --- | --- |
| `none` | 什么都不加 | **不正确**:没有任何东西阻止 `copy[k+depth]` 覆盖 `compute[k]` 正在读的槽 |
| `war` | 每次迭代一个释放屏障(消费者读完 → 下一个写者才能写),成本 `barrier` | 正确(这是环形多缓冲的最低要求) |
| `war+raw` | 再加一个获取屏障(数据到齐 → 消费者才读) | 正确(当可见性本身需要 fence 时必需) |
| `+cb` | 再在消费者链里插一个**多余**的屏障 | 正确但慢:`barrier` 全额落在关键路径上 |
| `sync-copy` | 退化成同步拷贝(每次拷贝等上一次落地) | 正确但慢:彻底失去异步重叠 |

## 运行前提

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

## 运行命令

```bash
python3 sync_count_model.py
python3 sync_count_model.py --latency 120 --compute 20 --barrier 8
python3 sync_count_model.py --latency 30 --compute 60 --barrier 8
```

## 期望输出(本机实际运行结果,默认参数)

```text
depth |             none |              war |          war+raw |              +cb |        sync-copy
    1 |    350cyc u= 91% |    920cyc u= 35% |   1048cyc u= 31% |   1048cyc u= 31% |   1048cyc u= 31%
    2 |    350cyc u= 91% |    476cyc u= 67% |    540cyc u= 59% |    548cyc u= 58% |    628cyc u= 51%
    3 |    350cyc u= 91% |    350cyc u= 91% |    388cyc u= 82% |    478cyc u= 67% |    628cyc u= 51%
    4 |    350cyc u= 91% |    350cyc u= 91% |    358cyc u= 89% |    478cyc u= 67% |    628cyc u= 51%
    6 |    350cyc u= 91% |    350cyc u= 91% |    358cyc u= 89% |    478cyc u= 67% |    628cyc u= 51%

Steady-state interval per iteration (last half of the loop):
  depth=3: none=  20.0  war=  20.0  war+raw=  22.6  +cb=  28.0  sync-copy=  38.0
  depth=4: none=  20.0  war=  20.0  war+raw=  20.0  +cb=  28.0  sync-copy=  38.0
```

(`u=` 是计算单元利用率 = `iterations × compute / makespan`。)

## 要读出什么

1. **必需的同步可以在深度足够时免费。** `war` 在 `depth ≥ 3` 时与 `none` 的
   makespan 完全一样:那 8 个周期的屏障被「已经在路上的那一份拷贝」盖住了。
   条件是 `latency ≳ compute + barrier`。
2. **多余的同步不会免费。** `+cb` 把屏障放进了消费者链,稳态步长从 20 涨到 28,
   正好是一个 `barrier`:这在 `depth` 加到 6 时也不会好转。
3. **深度不够时,必需品也会变成瓶颈。** `depth=1` 的 `war` 让稳态步长变成
   `latency + compute + barrier = 58`——这就是第 11 章那条
   `depth ≥ 1 + latency/compute` 判据在同步模型里的样子。
4. **彻底失去异步最贵。** `sync-copy` 在 `latency=30` 时稳态 38,在
   `--latency 120` 时稳态 128:这正是「把 `T.copy` 退化成同步拷贝 + 每语句一道
   barrier」的代价。

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

**CPU 逻辑验证**:在 macOS (Apple Silicon)、Python 3.11 上运行过。上面的数字是
**抽象周期数**,不是任何硬件上的测量值。**没有**在任何 GPU/NPU 上对照过。

## 这个模型不做什么

* 一个 `barrier` 在所有线程上只是「等到大家都到」,真实硬件上它还会随参与线程数、
  warp 分歧、以及 barrier 与访存的交互而变;这里只用一个常数 `barrier`;
* 它不区分 `__syncthreads()`、mbarrier 的 parity wait、昇腾的 SetFlag/WaitFlag:
  三种在模型里都是「一个固定成本的屏障」;
* 它不建模「漏一个同步」的**错误**行为(那需要在真实硬件上跑竞态,见正文 §5.1)。

← 浏览本章全部代码