跳到主要内容

08 内存模型 · 同步原语 · 固件 ABI 洞察

  • 章节编号:08
  • 所属层:I 软硬接口层(红线:与 07 章 ISA 一同随 RTL 冻结)
  • 关联 ADR:ADR-022(内存一致性模型)、ADR-023(同步原语)、ADR-024(固件/控制核职责与寄存器 ABI)
  • 上游依赖:03(SRAM/统一内存)、04(NoC 一致性/统一内存)、07(ISA/编程模型)

学习目标

  • 前置知识:读过 03 章(SRAM/统一内存)与 04 章(NoC 一致性),知道「软件管理 scratchpad」与「硬件缓存一致」是两种不同的内存范式;读过 07 章 ISA/编程模型,知道我们的控制核是一颗 RISC-V;对「弱内存序(weak memory ordering)」「内存屏障(memory barrier)」有直觉即可,无需读过任何形式化内存模型论文。

  • 学完产出:① 能说清「弱内存模型(weak memory model)为什么需要编译器显式插入屏障」——理解在弱序下两条无依赖的访存可以被硬件任意重排,而正确性依赖显式栅栏(fence)来建立可见性顺序;② 能用「set flag / wait flag」这一对显式同步原语,把 Cube(矩阵)/ Vector(向量)/ MTE(搬运)三单元编排成一条重叠流水,并说清「过同步(over-sync)」与「欠同步(under-sync)」各自的后果;③ 能论证「寄存器 ABI(Application Binary Interface,应用二进制接口)稳定性为何直接关系固件 OTA(Over-The-Air,空中升级)兼容」——理解一旦寄存器偏移/位域随版本漂移,旧驱动配新固件就会静默写错寄存器;④ 能读懂 07/08 两章为何一同随 RTL 冻结,理解「软硬接口红线」的工程含义。

  • 阅读姿势:盯住一条主线——「NPU 把『保证并发正确』的责任从硬件搬到了软件」。GPU/CPU 用昂贵的缓存一致协议让程序员几乎不必操心内存序;而我们这类 DSA(Domain-Specific Architecture,领域专用架构)为了能效与确定性,选择弱序 + 软件管理 scratchpad,把「什么时候该插屏障、什么时候该 set/wait flag」全部交给编译器与固件。本章讲的一切——内存一致性、同步原语、固件 ABI——都是在回答:当硬件不再替你兜底,软件这一侧的契约该如何定义并长期稳定。


1. 范围与目标

本章定义"数据在多计算单元间如何保证正确与高效":内存一致性模型、同步原语(屏障/栅栏/DMA 完成/计数器)、固件与控制核职责、寄存器 ABI 稳定性。它决定编译器能否生成正确高效的并发代码,以及软硬件长期可维护性。

核心问题

  1. 内存一致性:软件管理 scratchpad(弱序 + 显式同步) 还是硬件缓存一致?边界如何划?
  2. 同步原语:用什么机制(队列 + 事件、wait-count、DMA 完成中断、屏障)?
  3. 固件/控制核职责边界?如何兼顾灵活与稳定?
  4. 寄存器 ABI 如何保持稳定(支撑 07 章"编译兼容"与驱动长期维护)?

2. 需求洞察(承接 03/04/07 章)

  • 能效 + 确定性:软件管理 scratchpad + 显式 DMA(03 章)→ 内存模型应是弱序 + 编译器插入显式同步,而非昂贵的全局缓存一致;关键控制路径时序可预测(13 章)。
  • 多引擎并发:Cube/向量/DMA 多流水并发(达芬奇式)→ 需轻量队列 + 事件同步重叠计算与搬运。
  • 统一内存边界:NPU 内部 scratchpad 软件管理,但 NPU↔CPU 统一内存需缓存一致(04 章 ADR-017)——两种语义在边界清晰划分。
  • MLIR 可表达:同步/DMA/屏障须能映射为 MLIR 算子,供编译自动生成(16 章)。
  • 长期可维护:寄存器 ABI 稳定 → 驱动/固件解耦演进(09 章)。

2.1 具身场景的实时约束如何压到内存序这一层

内存模型看似是「底层细节」,但具身负载对它有直接的硬约束。参考 M 层 21 章的靶标:π0 类流匹配 VLA 的 chunk 延迟目标是 ≤100ms,GR00T 在 Thor 上做到 92ms(10.9Hz),靠的正是 VLM backbone、DiT 去噪、数据搬运三者在时间轴上完全重叠。这种重叠只有在「弱序 + 显式同步」下才可能榨到极致:

  • 重叠的前提是允许重排:若采用强序(每条访存都等前一条全局可见),搬运与计算就无法并发,chunk 延迟会退化到「各阶段串行相加」。弱序放开了重排,才有重叠的空间。
  • 重叠的正确性靠显式同步兜底:允许重排的代价,是必须在真正有数据依赖处显式插屏障或 set/wait flag。编译器少插一处 → 数据竞争(欠同步);多插一处 → 流水气泡(过同步)。这条「精确同步」的钢丝,直接决定了能否吃到那个 10.9Hz。
  • 确定性是伺服环的红线:13 章的 1kHz 伺服环要求 WCET(Worst-Case Execution Time,最坏执行时间)可预测,而缓存一致协议的「隐式后台流量」是确定性的天敌——这也是我们不在 NPU 内部走硬件缓存一致的深层原因。

3. 技术现状与趋势(点名 + 来源)

3.1 软件管理 scratchpad + 解耦访问/执行(能效与确定性根本)

  • TPU:软件管理 Unified Buffer/VMEM/CMEM scratchpad + 可编程 DMA + 解耦访问/执行(地址发出即可完成,重叠 DMA 与计算);确定性强。
  • 华为达芬奇:L0A/L0B(输入)+ L0C(累加)缓冲 + MTE(Memory Transfer Engine) 搬运 + cube/vector/MTE 队列 + 事件同步,Cube 与 Vector 可并发。
  • 判断:NPU 内部采用软件管理多级 scratchpad + 显式 DMA + 解耦访问/执行(能效 + 确定性),由编译器编排。

3.2 内存一致性:弱序 + 作用域(scoped)+ 显式同步

  • GPU(PTX):relaxed + scoped 模型(thread/block/device/system 作用域);block 作用域栅栏比 device 快达 21× → 作用域是性能杠杆。
  • AMDGPU:availability/visibility + 缓存策略位(GLC/SLC/DLC)+ wait-count 寄存器精控缓存/等待。
  • MLIR:gpu dialect 的 barrier = 栅栏 + 线程同步;支持地址空间感知的 fence(只 flush 相关内存区);MMRA(内存模型放松注解) 在 lowering 中保留目标约束(避免保守地对所有地址空间加栅栏)。趋势:解耦"同步(thread sync)"与"栅栏(memory fence)"
  • 判断:采用弱序 + 作用域 + 编译器插入显式同步;NPU 本地 scratchpad 非缓存一致(靠显式 DMA/同步),仅在 NPU↔CPU 统一内存边界做缓存一致;同步/栅栏解耦、地址空间感知,便于 MLIR 高效 lowering。

3.3 同步原语

  • 机制候选:DMA 完成事件/中断wait-count 计数器(等待 N 个异步操作完成)、队列间事件同步(达芬奇)、屏障(同工作组)。
  • 判断:以异步队列 + 事件/计数器为主(重叠搬运与计算),屏障为辅;暴露为 MLIR 异步算子。

3.3a set/wait flag:显式同步原语的最小语义模型

达芬奇式三单元流水的同步骨架,可以抽象为一对最小原语:

  • set_flag(pipe, id):某单元(如 MTE)完成一段工作后,在指定的 event flag 上置位,声明「我这一步做完了,产出已对下游可见」。
  • wait_flag(pipe, id):下游单元(如 Cube)在动手前阻塞等待该 flag 置位,建立「我依赖的上游产出已就绪」的顺序保证。

这对原语与 wait-count 是同一思想的两种粒度:wait_flag 等一个具名事件,wait-count 等「N 个异步操作全部完成」。二者共同的语义本质是——在弱序内存模型里,人为地建立一条 happens-before(先于)边,把本可被硬件任意重排的两段访存,钉死成「上游可见 → 下游读取」的顺序。编译器(16 章)的核心工作,正是从数据流图里推导出哪些边需要 flag、插在哪个 pipe 上

3.4 固件 / 控制核 / 驱动分层

  • TPU 驱动分层:Kernel Driver 轻量(仅内存管理 + 中断,长期稳定)+ User-Space Driver 频繁更新(编译模型、重排数据、生成指令二进制)。
  • 判断:沿用 KMD 轻量稳定 + UMD 频繁迭代;固件/控制核(RISC-V,07 章)负责指令调度/DMA 编排/事件管理;寄存器 ABI 保持稳定以解耦演进(关联 09 章驱动)。

3.4a 寄存器 ABI 与固件 OTA:稳定性契约的边界

固件不是一次性烧录的死物——量产设备要长期通过 OTA 升级固件(修 bug、加调度策略、适配新算子)。这就引出一个隐蔽而致命的契约:驱动(KMD/UMD)通过一组约定的寄存器偏移与位域(bit field)与固件/硬件对话。这组约定就是寄存器 ABI。

  • ABI 稳定 = 旧驱动能配新固件:若寄存器 DMA_CTRL 的「启动位」在 v1 是 bit 0、v2 悄悄挪到 bit 4,那么升级固件后、驱动还没升级时,驱动写 bit 0 会静默写错——不报错、行为诡异,是最难查的一类兼容 bug。
  • 预留 + 版本管理是解法:ABI 里预留保留位(reserved bits)与一个 ABI_VERSION 寄存器;固件与驱动握手时先读版本,不匹配则拒绝或降级,而非盲目对话。
  • 红线来源:寄存器编码一旦随 RTL 冻结(07/08 同冻),硬件侧就改不动了;所有演进空间必须在冻结前以「预留位 + 版本协商」的形式设计进去。

3.5 形式化与验证(加深,v4)

方法代表适用Round
Lit/MLIR 回归LLVM 生态pass 正确性M1+
弱序模型检查herd7/DSL同步原语M2+
ISS 并发 harness自研DMA+event 时序M2+
Silicon WCET 实测岛侧1kHz 证据M4

判断:08 章内存序 须在 M2 前形式化文档化;与 30 Agent 生成代码的 ISS gate 联动。


3b. 由演进反推的芯片诉求(定性)

维度当前未来含义
一致性弱序 + 显式同步;边界缓存一致多 die 一致(UCIe,04 章)作用域可扩展
同步队列+事件+wait-count更多并发流可扩展事件/计数器空间
固件控制核编排更复杂调度固件可升级 + ABI 稳定

4. 候选方案与对比矩阵

4.1 内存一致性模型

候选能效确定性编译复杂度(低=好)编程友好小结
A. 全局硬件缓存一致2355编程易但能效/面积差,不适 NPU 主存
B. 弱序 + 作用域 + 软件管理 scratchpad + 显式同步5533建议方案:能效/确定性优,靠编译器托管
C. 完全静态无运行时同步5521极致确定但灵活性差,仅控制路径可用

建议方向:B 为主(NPU 内部);NPU↔CPU 边界用缓存一致统一内存(04 章);关键控制路径可局部采用 C 的静态调度。

4.2 同步原语 / 固件

  • 异步队列 + 事件 + wait-count + DMA 完成 + 屏障;KMD 轻量稳定 + UMD 频繁迭代;固件可升级;寄存器 ABI 稳定

5. 关键权衡、风险与依赖

  • 能效 vs 编程易用:软件管理弱序模型能效高但把复杂度推给编译器 → 强依赖 MLIR 后端正确高效插入同步(16 章);若编译器不成熟,正确性/性能双输(关键风险)。
  • 一致性边界划分:NPU scratchpad(软件管理)与 NPU-CPU(硬件一致)边界必须清晰,否则出现难调并发 bug。
  • 同步开销:作用域过大栅栏昂贵(GPU 经验 21× 差异)→ 需精细作用域;但易出"过同步/欠同步"bug(需形式化/测试,30 章)。
  • ABI 稳定 vs 演进:寄存器 ABI 要稳(驱动维护)又要可扩展(新功能)→ 预留 + 版本管理。
  • 依赖:07(ISA)、03/04(内存/一致)、12-13(运行时/实时)、16(编译同步生成)、09(驱动)。

6. 结论与待决项

初步结论

  1. NPU 内部:弱序 + 作用域 + 软件管理多级 scratchpad + 显式 DMA + 解耦访问/执行,编译器托管同步;NPU↔CPU 边界缓存一致统一内存
  2. 同步以异步队列 + 事件/wait-count + DMA 完成为主,屏障为辅;同步与栅栏解耦、地址空间感知(MLIR 友好)。
  3. KMD 轻量稳定 + UMD 频繁迭代;固件/控制核(RISC-V)编排;寄存器 ABI 稳定 + 预留扩展

待决项

  • 作用域层级与栅栏语义的精确定义(形式化优先,避免并发 bug)——编译 + 硬件 + 验证联合。
  • wait-count/事件数量与队列深度——结合并发流水数定。
  • 固件可升级机制与安全(签名/回滚,关联 28 章)。

ADR 候选

  • ADR-022 内存一致性:NPU 内弱序 + 作用域 + 软件管理 scratchpad + 显式同步;NPU↔CPU 缓存一致统一内存。
  • ADR-023 同步原语:异步队列 + 事件 + wait-count + DMA 完成 + 屏障;同步/栅栏解耦、地址空间感知。
  • ADR-024 固件/ABI:KMD 轻量稳定 + UMD 迭代 + 可升级固件;寄存器 ABI 稳定且预留扩展。

7. 端→边→中心演进影响

  • 作用域模型从单芯片扩展到多 die(UCIe 跨 die 一致,04 章) 与中心多卡;软件管理 + 显式同步范式可一致沿用。
  • 不可逆点:内存序语义、同步原语编码、寄存器 ABI 随 RTL 冻结;需形式化定义 + 预留扩展,避免后续并发语义返工。

深入思考

下面三题每题先给题干,再用 <details> 折叠一份图文并茂的参考答案。建议先合上答案自己想 3 分钟,再展开对照。

思考题 1:弱内存模型为何必须显式插屏障

你在 NPU 上写了两步:先让 MTE 把权重从 HBM 搬进片上 buffer,再让 Cube 读该 buffer 做矩阵乘。你没插任何同步,仿真时对了,上真机偶发算出垃圾。结合 3.2 节弱序模型2.1 节实时约束,解释为什么弱序下这两条无显式依赖的访存会被硬件重排,以及一条 wait_flag 屏障如何用「happens-before 边」修复它。为什么不能干脆全程强序省心?

展开参考答案(含弱序重排时序图 + 算一遍)

结论:弱内存模型允许硬件对没有显式顺序约束的两条访存任意重排,以换取重叠搬运与计算的能效;代价是「搬运完成」与「Cube 读取」之间那条真实的数据依赖,硬件看不见,必须由软件用 wait_flag 显式钉成一条 happens-before 边,否则 Cube 可能读到搬运尚未落地的旧数据。

用一个「重排窗口」算一遍,理解为何是「偶发」而非「必现」:

  1. 设 MTE 一次 DMA 搬运耗时约 500 周期(HBM 延迟量级),Cube 从发射到实际发起读取约 20 周期
  2. 弱序下解耦访问/执行:MTE「发出地址即返回」,控制核紧接着就发射了 Cube 指令。于是 Cube 的读取在 第 20 周期 就可能命中 buffer,而 DMA 要到 第 500 周期 才真正写完。
  3. 中间这 480 周期 就是「重排窗口」——Cube 有 480 周期的概率读到旧值。仿真器若把 DMA 建模成「瞬时完成」,窗口为 0,所以仿真永远对、真机偶发错。
  4. 插入 set_flag(mte_done) / wait_flag(mte_done) 后,Cube 被阻塞到第 500 周期 flag 置位才读——重排窗口被压成 0,建立起「DMA 写完 happens-before Cube 读」的顺序,bug 消失。

为什么不全程强序:见下表,强序省了脑子,但把「搬运与计算重叠」这条能效命脉也一起掐断了。

维度全程强序(每条访存等全局可见)弱序 + 精确显式同步
正确性心智负担低(硬件兜底)高(编译器须精确插 flag)
搬运/计算重叠几乎不可能(串行相加)可完全重叠(能吃到 10.9Hz)
能效/面积差(全局排序逻辑昂贵)优(DSA 首选)
确定性中(隐式流量)强(显式、可 WCET 分析)

这正是 3.2 节选 B 方案(弱序 + 作用域 + 显式同步)、2.1 节强调「重叠是延迟靶标前提」的根因——回链见 4.1 节候选对比。

思考题 2:用 set/wait flag 编排三单元流水

给你 MTE(搬运)、Cube(矩阵)、Vector(激活)三个异步单元,要对分成 N 块的一批数据做「搬入 → 矩阵乘 → 激活」的流水。结合 3.3a 节 set/wait flag 语义,画出让三单元时间轴重叠的 flag 编排,并说明「欠同步」漏一条 flag、「过同步」多一条 flag 分别会怎样。

展开参考答案(含三单元流水编排图 + 对比表)

结论:三单元流水的编排本质是给每一块数据在相邻单元间架一对 set/wait flag,让第 N 块的搬运、第 N-1 块的矩阵乘、第 N-2 块的激活在同一时刻分别在三个单元上跑——flag 只钉「真实数据依赖」这一条边,漏一条则数据竞争,多一条则制造无谓等待的气泡。

编排规则(每块 i 两对 flag):MTE 搬完第 i 块 → set_flag(mte, i);Cube 算第 i 块前 wait_flag(mte, i),算完 → set_flag(cube, i);Vector 激活第 i 块前 wait_flag(cube, i)。稳态时三单元在时间轴上完全重叠,总耗时从「N×(搬+算+激)」压缩到「≈ N × max(搬, 算, 激) + 两级填充」,这就是达芬奇三单元异步流水的能效来源,也对应 21 章「搬运与计算重叠」的靶标。

情形症状根因定位手段
欠同步(漏 wait_flag(mte,i))偶发数据竞争,Cube 读到未搬完的块,结果非确定少钉了一条真实 happens-before 边ISS 并发 harness(3.5 节)+ herd7 弱序模型检查
过同步(在无依赖处多插 flag / 用过大作用域栅栏)结果正确但吞吐掉、单元空转等待制造了本不存在的顺序约束 → 流水气泡profiling 看单元 occupancy;对照 GPU 作用域 21× 教训
精确同步(每条真实依赖一对 flag)正确且三单元满流水只钉必需的边目标态

要点:编译器(16 章)自动插 flag 时,走的正是「欠同步伤正确性、过同步伤性能」这条钢丝——回链见 5 节「同步开销」与 3.3a 节。

思考题 3:寄存器 ABI 稳定性为何关系固件 OTA 兼容

设备已量产出货,要 OTA 升级固件加一个新的 DMA 调度策略。新固件把寄存器 DMA_CTRL 的「优先级」字段从预留位挪到了 bit 8。用户设备先升了固件、驱动还是旧版。结合 3.4a 节,推演会发生什么,并说明「预留位 + ABI_VERSION 握手」如何避免这场静默事故。为什么这条约束必须在 RTL 冻结前就设计好?

展开参考答案(含 ABI 版本握手图 + 对比)

结论:寄存器 ABI 是驱动与固件/硬件对话的二进制契约;一旦某字段的位置随固件版本漂移,而旧驱动仍按老偏移读写,就会静默写错寄存器——不报错、行为诡异,是最难查的兼容 bug;解法是预留保留位加 ABI_VERSION 握手,让双方对话前先确认契约一致,不一致则拒绝或降级而非盲目对话。

事故推演(旧驱动 + 新固件):

  1. 新固件把「优先级」放到 DMA_CTRL bit 8;旧驱动仍按 v1 认知,把优先级写到 bit 4。
  2. 驱动写 bit 4 时,这一位在 v2 语义里可能对应「另一种控制含义」(如触发某种模式)——于是 DMA 以非预期方式启动。
  3. 硬件不会「因为你写错位」而报错——寄存器写入天然合法,只是语义错位。结果是 DMA 偶发乱序/超时,现场极难复现定位。
  4. 有了 ABI_VERSION:驱动加载时先读版本,发现固件是 v2 而自己是 v1、协议不兼容 → 拒绝加载并告警,或降级到只用双方都认的兼容子集。静默事故变成显式、可诊断的失败。
维度无 ABI 版本管理预留位 + ABI_VERSION 握手
旧驱动 + 新固件静默写错寄存器,难查显式拒绝/降级 + 告警
新字段扩展空间无,只能挪动现有位从预留位分配,不动老位
OTA 升级安全性高风险(顺序敏感)可乱序升级,握手兜底
RTL 冻结后可改否否(位编码已固化)预留位在冻结前已备好

为何必须冻结前设计好:寄存器的位编码随 07/08 章一同随 RTL 冻结,流片后硬件侧改不动;所有未来演进空间只能以「预留位 + 版本协商」的形式在冻结前预置。这正是 7 节把「寄存器 ABI」列为不可逆点、ADR-024 强调「稳定且预留扩展」的根因——回链见 3.4a 节与 5 节「ABI 稳定 vs 演进」。


附:信息来源

区分公开/估计;参考时间 2026-06。roadmap 类信息以官方为准。

  • 软件管理 scratchpad / 解耦访问执行 / 驱动分层:Jouppi et al. TPU 论文(Unified Buffer、可编程 DMA、decoupled access/execute、KMD/UMD 分层);TPU v4(CMEM)。[公开]
  • 达芬奇内存/同步:Huawei DaVinci(HotChips 31)与昇腾资料(L0A/L0B/L0C、MTE、cube/vector/MTE 队列 + 事件同步、set/wait flag)。[公开]
  • 弱序/作用域内存模型:NVIDIA PTX scoped memory model(ISCA'22 形式化);SIGARCH《GPU Memory Consistency》(block 作用域栅栏比 device 快达 21×);LLVM AMDGPU Memory Model(availability/visibility、GLC/SLC/DLC、wait-count)。[公开]
  • MLIR 同步:MLIR gpu dialect(barrier=fence+sync、地址空间感知 fence、同步/栅栏解耦);LLVM MMRA。[公开]
  • 具身实时靶标(π0 chunk ≤100ms、GR00T Thor 92ms/10.9Hz):见 M 层 21 章来源(π0.7/GR00T N1.7 GA 状态以官方为准)。[公开]
  • 一致性边界/同步取舍、ABI 版本握手/OTA 兼容:基于上述公开资料的工程判断。[估计]