> ## Content Index
> Fetch the complete content index at: https://blog.hacking.pub/llms.txt
> Use this file to discover other available public pages before exploring further.

# Kapitsa 源码拆解 04：GPU 与 ANE 如何同步、并行与回滚？
- URL: https://blog.hacking.pub/2026/10/26/kapitsa-919b1b2-4/
- Published: 2026-10-26T18:14:03.000Z
- Updated: 2026-10-26T18:14:03.000Z
- Description: 沿 ready/done、两半 wavefront 和失败重跑，理解“同时工作”背后的依赖关系。
- Author: chainhelen
- Tags: Kapitsa, ANE, 调度

Kapitsa 用 ready / done 事件约束 GPU 与 ANE 的读写顺序，用 wavefront 调度重叠两组 token 的计算。ANE 失败时，恢复检查点并由 GPU 重算整个 chunk。本文对应这三条代码路径。

**版本范围：**本系列固定阅读 [Kapitsa 提交 919b1b2a6782ec205e23725dee76ee5f57de2857](https://github.com/nace-ai/kapitsa/tree/919b1b2a6782ec205e23725dee76ee5f57de2857?ref=blog.hacking.pub)；该快照的 [CMakeLists.txt](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/CMakeLists.txt?ref=blog.hacking.pub#L1-L8) 标注版本为 **0.2.17**。以下是源码拆解与教学算例，不是本机性能实测；其他版本的默认参数和实现可能不同。

## ready / done 约束读写顺序

GPU 完成输入打包后发出 ready，ANE 等待 ready 再读取；ANE 完成计算后发出 done，GPU 等待 done 再合并结果。共享内存允许两侧访问同一块数据，事件保证读取发生在写入完成之后。

![CPU 创建和绑定 IOSurface，编排 GPU 命令并提交 ANE 请求。GPU 打包完成后发 ready，ANE 等待 ready 执行并在写回结果后发 done，GPU 等待 done 后合并结果。](https://blog.hacking.pub/qiniu/content/images/2026/10/k4-events-with-cpu-20261027.png)

图 1：CPU 创建并绑定共享内存、提交两侧任务。橙色箭头表示 CPU 控制，虚线表示 ready / done 依赖；图中未按实际耗时排列。

[Handoff 的定义](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/ane/ane.h?ref=blog.hacking.pub#L88-L115) 为 GPU→ANE 的 ready 和 ANE→GPU 的 done 分别维护递增计数。每个任务使用对应的事件值，两个 token 行分组交错执行时也能区分各自的交接。

FfnSplit 的 pack 准备输入，launch 提交 ANE 任务，join 等待并合并结果，stage\_layer 准备权重。CPU 通过这些接口编排 GPU 与 ANE 的工作顺序。

## GPU 与 ANE 的并行执行：Wavefront 调度

普通 split 按层执行。GPU 计算 Attention 或递归 mixer 时，ANE 没有本层的 FFN 输入可用，只能空闲；进入 FFN 后，两侧才同时计算各自的通道份额。

Wavefront 将一个 prefill chunk 的 token 行分成前后两组 A、B，错开它们的层间进度。**A/B 划分的是 token 行；GPU/ANE 划分的是 FFN 中间通道。**两种划分同时存在。

![把token行分成A和B后，ANE计算A的FFN时，GPU有机会推进B的mixer；之后ANE处理B，GPU推进下一层A。](https://blog.hacking.pub/qiniu/content/images/2026/10/draft-series-20261026-k4-wave.png)

图 2：示意 mixer 与 ANE FFN 的重叠；实际路径还包含 GPU 的 FFN 份额、join 和权重准备。

[enc\_prefill\_wave 前的调度说明](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/model/qwen35%5Fane.cpp?ref=blog.hacking.pub#L337-L383) 规定：ANE 计算第 i 层 A 的 FFN 时，GPU 推进第 i 层 B 的 mixer；ANE 计算第 i 层 B 的 FFN 时，GPU 推进第 i+1 层 A 的 mixer。

同一层中，B 的 mixer 仍在 A 之后执行，以承接 A 更新的 KV 与递归状态。A、B 各有独立的激活 IOSurface；权重 IOSurface 用两组存储按层轮换。

## 并发收益受工作量和内存开销限制

两侧工作量不均衡时，较短的一侧仍要等待。输入旋转与打包、权重准备、事件同步和结果合并也占用时间，而且 GPU 与 ANE 共享系统内存带宽。并发能减少空闲时间，实际收益取决于这些开销。

该快照的 Qwen 路径默认尝试两组 wavefront，但 chunk 必须满足行数条件。较小的 chunk 使用普通 split 或 GPU 路径。

## 失败后恢复检查点并重算整个 chunk

![教学例：p=1024、T=1024 的 chunk 在第6层 ANE 执行失败。ane_finish_chunk 返回 false 后，从 ane_ck_ 恢复递归状态，GPU 用同样的 p、T 重算所有层；完成后才将 p 推进到2048。](https://blog.hacking.pub/qiniu/content/images/2026/10/k4-chunk-replay-example-20261027.png)

图 3：教学例中的 chunk 包含位置 1024–2047 的 1024 个 token 行。数值和失败层为示例设定；save、ane\_finish\_chunk、restore、encode\_prefill 和 p += T 对应该版本的真实调用。

Handoff 的 [watchdog](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/ane/ane.mm?ref=blog.hacking.pub#L646-L666) 监测任务开始后的超时。[ANE 无法启动](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/ane/ane.mm?ref=blog.hacking.pub#L669-L675)、[执行失败](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/ane/ane.mm?ref=blog.hacking.pub#L610-L621)或超时时，Handoff [停用 ANE 交接，并推进事件值](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/ane/ane.mm?ref=blog.hacking.pub#L549-L557)，解除 GPU 的等待。**解除等待不代表输出有效，当前 chunk 仍需回滚。**

Engine 在执行 split chunk 前[保存检查点](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/engine/engine.cpp?ref=blog.hacking.pub#L2665-L2671)。如果 [ane\_finish\_chunk()](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/model/qwen35%5Fane.cpp?ref=blog.hacking.pub#L58-L71) 失败，会[恢复 chunk 开始前的状态](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/engine/engine.cpp?ref=blog.hacking.pub#L2709-L2717)，再调用 [encode\_prefill](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/engine/engine.cpp?ref=blog.hacking.pub#L2713-L2717) 在 GPU 上重算同一段。可以直接对照 [保存、检查、恢复与重跑](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/engine/engine.cpp?ref=blog.hacking.pub#L2665-L2724)。

chunk 内已经执行的层可能更新了递归状态或部分缓存，因此不能只补最后一次合并。[恢复检查点](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/model/qwen35.cpp?ref=blog.hacking.pub#L1430-L1445)后重算整个 chunk，才能同时恢复计算结果和模型状态。

## 对照源码阅读

- [pack / launch / join 接口](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/ane/ffn%5Fsplit.h?ref=blog.hacking.pub#L72-L94)
- [双向事件与 watchdog](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/ane/ane.h?ref=blog.hacking.pub#L88-L115)
- [wavefront 顺序](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/model/qwen35%5Fane.cpp?ref=blog.hacking.pub#L337-L383)
- [失败后的恢复与重跑](https://github.com/nace-ai/kapitsa/blob/919b1b2a6782ec205e23725dee76ee5f57de2857/src/engine/engine.cpp?ref=blog.hacking.pub#L2665-L2724)