Skip to content

HAL 系列

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so。其他版本会有所不同。

摘要

TPU 硬件抽象层(HAL)构建为经典的 C++ 抽象工厂框架。纯虚 tpu::TpuHalFactory 接口是注册键;单个具体基类 tpu::TpuHalHardwareFactoryBase 提供模板方法 Create/CanCreate;三个叶子工厂类分别接入一个 CreateImpl 分配器。库加载时,一组 google_init_module_* 静态初始化器会按每个 TpuVersion 向进程级注册表注册一个工厂实例,该注册表以 (TpuPlatformType, TpuVersion) 为键。运行时,TpuHalFactory::Get(version) 查找该键,匹配的工厂构造按系列划分的 TpuHal*HardwareImpl 对象,用来抽象某一代 TPU 芯片。

本页要澄清的反直觉事实是:这里有 三个工厂类,但有 五个初始化模块和 六个已注册的 TpuVersion 键。工厂层有意保持很薄:按代号区分的行为不放在不同的 C++ 子类中,而是放在按版本加载的数据驱动 TpuChipParts proto 中,以及同样以 TpuVersion 为键的并行 codec/cost-model 层级中。三个系列把六个代号划分为 JXC(Jellyfish v0、Dragonfish v1)、PXC(Pufferfish v2)和 VXC(Viperfish v3、Ghostlite v4、6acc60406 v5)。VXC 类被注册三次,分别由 vxcglcgfc 初始化模块注册到三个不同版本键下,这就是模块数量多于类数量的原因。

这个映射可以直接观察到。每个初始化模块的 TpuHalFactory::Register 调用都带有一个 make_unique<tpu::TpuHal{Jxc,Pxc,Vxc}HardwareFactory> 源字符串和一个数值 TpuVersion 参数;两者都以 CHECK(... is OK) 失败消息和立即数操作数的形式保留在二进制中。跨五个模块读取这些信息即可精确重建注册表。

对于重新实现,契约是:

  • 注册表键空间:(TpuPlatformType, TpuVersion),平台固定为 kHardware(值 0),版本范围为 0..5。
  • 五个初始化模块、它们的地址,以及每个 Register 调用绑定的工厂类和 TpuVersion
  • 为什么 3 类 / 5 模块 / 6 键的扇出是正确的,而不是标签错误:同一个 TpuHalVxcHardwareFactory 类在不同键下实例化三次。
工厂接口tpu::TpuHalFactory (_ZTI @ 0x21d34410;无独立 vtable)
工厂基类tpu::TpuHalHardwareFactoryBase — vtable 槽位:2 Create (0x1e80f560), 3 CanCreate (0x1e80f520), 4 CreateImpl(纯虚,按系列)
叶子工厂类3 — Jxc(anon ns)、Pxc(anon ns)、Vxc(global ns)
工厂对象16 B (operator new(0x10)): vtable ptr 位于 +0,TpuVersion dword 位于 +8
初始化模块5 — jxc, pxc, vxc, glc, gfc
已注册的 TpuVersion 键6 — 0..5
注册表g_hal_factories_by_type(按 platform type 索引),由 g_hal_factories_mutex 保护
注册表查找TpuHalFactory::Get(version, opt<platform>) @ 0x1fbb19c0(在 mutex 下)
构造入口TpuHal::Create(opt<platform>, version, profiler, wq) @ 0x1e814180

为什么数量是 3,而不是 2(也不是 5)

粗略阅读会让人以为有两个系列:旧的 PCIe 连接 TPU 与新的 fabric 连接 TPU。但二进制在两端都不支持这种看法。这里有三个不同的工厂,由它们的 vtable 和 C++ 链接属性区分:

text
tpu::{anonymous}::TpuHalJxcHardwareFactory   _ZTV 0x215fe530   (anon ns)
tpu::{anonymous}::TpuHalPxcHardwareFactory   _ZTV 0x216085c8   (anon ns)
tpu::TpuHalVxcHardwareFactory                _ZTV 0x21cabf70   (global ns)
```text

JXC 和 PXC 位于匿名命名空间中(mangled `_ZN3tpu12_GLOBAL__N_1...`);VXC 是全局命名空间类(`_ZN3tpu24TpuHalVxcHardwareFactory...`)。这是工厂层在 C++ 可见结构上的唯一差异;行为上三者都是相同的模板方法工厂。

> **QUIRK —** 系列数量是三个,因为*类*数量是三个,而不是因为*代号*数量(六个)或*初始化模块*数量(五个)与它对齐。GXC 没有自己的工厂类:`gfc` 和 `glc` 初始化模块构造的是 `TpuHalVxcHardwareFactory` 实例。代号分类中的“GXC family”是在注册表层把版本 4 和 5 归组到 VXC 类上,而不是第四个 C++ 工厂。创建 `GxcHardwareFactory` 类的重新实现会偏离二进制,因为二进制从未声明过它。

这种划分并非任意。JXC 处理最早的两代(Jellyfish/Dragonfish),它们共享驱动路径和 BarnaCore 网格拓扑。PXC 只服务 Pufferfish。VXC 覆盖从 Viperfish 开始的所有 fabric 连接世代,并覆盖最多的芯片管理槽位(throttle、core-dump、host-sync-flag),因为这些芯片需要自己的机制。因此这三个类对应三个驱动时代,VXC 类吸收了 Pufferfish 之后的所有代号,而不是按代拆分。

---

## 五个初始化模块

每个模块都是一个内部链接(`_ZL44...`)静态初始化器:它分配一个 16 字节工厂对象(`operator new(0x10)`),把工厂 vtable 放入槽 0,在对象 +8 处写入嵌入的 `TpuVersion`,并调用 `TpuHalFactory::Register`。JXC 模块会这样做**两次**(版本 0 和 1);另外四个模块各做一次。每个 `Register` 结果都会用 `CHECK` 与 `OK`(值 1)比较,失败消息是 `make_unique<...>` 调用表达式,这就是工厂类和预期版本能以明文恢复的原因。

### 注册表

每次调用中的平台参数都是 `TpuPlatformType::kHardware`(0)。版本是第二个 `Register` 参数(一个立即数)。下面的代号是源字符串拼出的精确 `tpu::TpuVersion::k*` 枚举项;最后一列括号中的外部名称是规范的 `TpuVersionToExternalName`(`0x20b3a500`)输出,这里仅作为阅读辅助,并已与[代号矩阵](tpu-version-codename-matrix.md)核对。非助记代号(`6acc60406`)以及二进制中*不存在*的外部名称(`Trillium`、`Ironwood`)被有意排除在本页之外;这里的每个 token 都是 `libtpu.so` 中的字面量。

| 初始化模块 | 地址 | 工厂类 | TpuVersion | 枚举名 | 代号(外部) |
|---|---|---|---|---|---|
| `google_init_module_tpu_hal_jxc_hardware_impl`(第 1 次 `Register`) | 0x213e9d80 | `TpuHalJxcHardwareFactory` | 0 | `kJellyfish` | jellyfish (`TPU v2`) |
| `google_init_module_tpu_hal_jxc_hardware_impl`(第 2 次 `Register`) | 0x213e9d80 | `TpuHalJxcHardwareFactory` | 1 | `kDragonfish` | dragonfish (`TPU v3`) |
| `google_init_module_tpu_hal_pxc_hardware_impl` | 0x213e9ec0 | `TpuHalPxcHardwareFactory` | 2 | `kPufferfish` | pufferfish (`TPU v4`) |
| `google_init_module_tpu_hal_vxc_hardware_impl` | 0x213eed20 | `TpuHalVxcHardwareFactory` | 3 | `kViperfish` | viperfish (`TPU v5`) |
| `google_init_module_tpu_hal_glc_hardware_impl` | 0x213eb9e0 | `TpuHalVxcHardwareFactory` | 4 | `kGhostlite` | ghostlite (`TPU v6 lite`) |
| `google_init_module_tpu_hal_gfc_hardware_impl` | 0x213e9f60 | `TpuHalVxcHardwareFactory` | 5 | `k6acc60406` | 6acc60406 (`TPU7x`) |

每一行都直接来自反编译后的初始化模块:立即数 `Register(0, N, ...)` 操作数,以及 `make_unique<...>` `CHECK` 字符串中的 `tpu::TpuVersion::k*` token。

> **QUIRK —** PXC 的 `make_unique` 是唯一不带版本的构造器。JXC、VXC、GLC 和 GFC 的源字符串写作 `make_unique<...HardwareFactory>(tpu::TpuVersion::k<Codename>)`,但 PXC 是裸的 `std::make_unique<tpu::TpuHalPxcHardwareFactory>()`,因为 Pufferfish 是该工厂服务的唯一世代,所以无需把版本传给构造器。版本 dword 仍然写入工厂对象 +8(`*(int*)(obj+8) = 2`),也仍然作为 `Register` 键提供;省略的只是*构造器参数*。矩阵和概览都记录了这个不对称性;这是五个注册在其他方面统一的情况下,二进制可见的唯一差异。
>
> **NOTE —** JXC 初始化模块的源字符串把版本 0 命名为 `tpu::TpuVersion::kJellyfish`,把版本 1 命名为 `tpu::TpuVersion::kDragonfish`:JXC 系列是 Jellyfish (0) + Dragonfish (1),没有单独的 pre-Jellyfish 键。v5 枚举字面上就是 `k6acc60406`,“Ghostfish” 标签从未出现在二进制中。

### JXC:双重注册

`google_init_module_tpu_hal_jxc_hardware_impl` 是唯一注册两次的模块。它的主体从 0x213e9d80 处的反编译稍作清理如下:

```c
function google_init_module_tpu_hal_jxc_hardware_impl():   // 0x213e9d80
    f0 = operator new(0x10)                 // 16-byte factory
    f0[1]      = 0                           // TpuVersion = 0 (kJellyfish) at +8
    *(void**)f0 = &JxcFactory_vtable[+0x10]  // off_215FE540
    st = TpuHalFactory::Register(0, 0, f0)   // platform=kHardware, version=kJellyfish
    CHECK(st == OK)                          // "...make_unique<TpuHalJxcHardwareFactory>(kJellyfish)) is OK"

    f1 = operator new(0x10)
    f1[1]      = 1                           // TpuVersion = 1 (kDragonfish) at +8
    *(void**)f1 = &JxcFactory_vtable[+0x10]  // off_215FE540 (same vtable)
    st = TpuHalFactory::Register(0, 1, f1)   // version=kDragonfish
    CHECK(st == OK)                          // "...make_unique<TpuHalJxcHardwareFactory>(kDragonfish)) is OK"

两次注册使用同一个工厂 vtable(off_215FE540)。区分两个已注册实例的唯一内容,是 +8 处的 TpuVersion 字节,以及注册表存储它们所用的键。同样的代码形态也出现在另外四个模块中,只是各自只有一个 Register

VXC:三个模块,一个类

vxcglcgfc 是三个独立的初始化模块,它们各自构造一个 TpuHalVxcHardwareFactorymake_unique<tpu::TpuHalVxcHardwareFactory>),并把它注册到不同版本下:

text
0x213eed20  vxc  →  Register(0, 3, make_unique<TpuHalVxcHardwareFactory>)  // kViperfish
0x213eb9e0  glc  →  Register(0, 4, make_unique<TpuHalVxcHardwareFactory>)  // kGhostlite
0x213e9f60  gfc  →  Register(0, 5, make_unique<TpuHalVxcHardwareFactory>)  // k6acc60406
```text

三者都引用 0x21cabf70 处的同一个工厂 vtable,并生成相同的 `TpuHalVxcHardwareImpl` 对象(`CreateImpl` @ 0x1d110e00)。某个具体实例服务的代号完全由注册表键决定,绝不由类身份决定。

> **NOTE —** Viperfish、Ghostlite 和 6acc60406 的按代号硬件常量并未编码在三个子类中。它们来自每个版本加载的 `TpuChipParts` proto(`embed://tpu_chip_parts/<version>_chip_parts.binarypb`),以及按 `TpuVersion` 切换的并行 `TpuCodec` / `CycleTable` 层级。HAL 工厂有意不做按代号分支。参见 [chip_parts.binarypb 解码](chip-parts-binarypb.md)和[按代号常量表](per-codename-hw-constants.md)。

---

## 注册表与构造流程

注册表是一个以 `(TpuPlatformType, TpuVersion)` 为键的表,在静态初始化时填充,并在运行时读取。从调用方想要 HAL 到对象存在的端到端路径如下:

```text
caller wants HAL for TpuVersion v
   |
   v
TpuHal::Create(opt<platform>, v, profiler, wq)          0x1e814180
   |
   v
TpuHalFactory::Get(v, opt<platform>)                    0x1fbb19c0   (registry lookup under mutex)
   |
   v  &factory  (one of Jxc / Pxc / Vxc factory instance)
   |
factory->Create(wq) via factory-vtable slot 2           0x1e80f560 = HardwareFactoryBase::Create
   |- this->CanCreate()                                  factory-vtable slot 3 (0x1e80f520) — hardware probe
   |    └─ ScanHardwareDevices(); ok iff scanned version == factory's registered TpuVersion (factory+8)
   |- if !CanCreate: build NotFound "No <device> device found."
   |- if  CanCreate: this->CreateImpl(wq)                factory-vtable slot 4 (per-family)
   v
new TpuHal{Jxc,Pxc,Vxc}HardwareImpl                      (208 B Jxc/Pxc, 216 B Vxc)

HardwareFactoryBase::Create(0x1e80f560)是共享的模板方法,通过工厂 vtable 槽 2 到达。它在工厂自身的 vtable 上分派两次,而不是在 work-queue 的 vtable 上:槽 3 是 CanCreate 探测,成功时槽 4 是按系列实现的 CreateImpl

GOTCHA — 反编译器把 Create 的第二个寄存器参数标成 TpuHostWorkQueue*,这会让两个间接调用看起来像是 work-queue vtable 分派。事实并非如此。调用方(TpuHal::Create,0x1e814180)调用的是 factory_vtable[2](ret_slot, factory, wq);在 Create 内部,槽 3 和槽 4 正是通过这个 factory 指针分派,work-queue 只是作为尾随参数继续传递。把可用性路由到 work-queue 方法而不是工厂自身 CanCreate 的重新实现,将无法匹配二进制。

CanCreate(0x1e80f520)是基类公开的继承具体谓词:它调用 tpu::ScanHardwareDevices,然后仅当扫描到的硬件版本等于工厂已注册的 TpuVersion(初始化模块写入 factory+8 的 dword),且至少发现一个设备时返回 true。探测失败时,Create 会构造一个 NotFound 状态,消息为 "No <device> device found.",该消息通过 tpu_hal_hardware_factory_base.cc 中的 util::NotFoundErrorBuilder 构建。

按系列的 CreateImpl 是小型分配器 stub;其布局记录在 TpuHal 类层级页面。工厂 vtable 槽位图和 impl-vtable 覆盖矩阵位于 HAL 工厂覆盖矩阵页面。


交叉引用