Skip to content

TPU 版本代号矩阵

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

摘要

libtpu.so 是 XLA 加载的 PJRT 插件,用于在 Google TPU 硅片上编译和运行程序。每一条依赖目标 TPU 代际的代码路径——HAL 工厂路由、ISA 编解码器选择、bundle 编码、芯片常量查找——都以一个 6 值 C++ 枚举 tpu::TpuVersion 为键。从 CHECK 字符串和修饰符号名恢复出的枚举项是 kJellyfish=0kDragonfish=1kPufferfish=2kViperfish=3kGhostlite=4k6acc60406=5。本页是该枚举与其触及的另外四个身份空间之间的权威对照:线上 protobuf 枚举、内部代号字符串、外部 "TPU vN" 显示名,以及为其服务的 HAL 家族。

其结构类似 LLVM target-triple 问题。正如 nvptx64-nvidia-cuda 这样的 triple 会解析为一个门控每个后端决策的 Triple::ArchType 整数,TpuVersion 也是 libtpu 中门控每个 TPU 专属决策的整数——但与 triple 不同,它从不在用户文本中直接写出。用户提供 accelerator_type 字符串(v5pv6etpu7x);库通过解析器将其转换为 TpuVersion,此后所有逻辑都是整数分派。本页要解决的复杂点在于,运行时内部使用的整数(TpuVersion,从 0 开始,按时间顺序)不是线上传输的整数(TpuVersionProto,从 1 开始,并保留 TPU_VERSION_INVALID=0)。二者相差 1,将它们混同是阅读此二进制时最常见的错误。

本页先给出一个可无争议钉住枚举到代号映射的编译产物——TpuVersionToString 背后的 .data.rel.ro 指针表——随后给出读取它的三个事实来源函数,再给出完整的五轴对照表、逐代号特性矩阵,最后用置信度核算说明哪些行由保护代码钉住,哪些依赖声明顺序。

对重新实现而言,契约是:

  • 6 值 TpuVersion 枚举、其整数赋值,以及每个消费者在索引逐版本表前执行的边界检查(version < 6)。
  • TpuVersionTpuVersionProto 之间 internal = proto - 1 的差 1 关系,以及具体落实它的三个函数。
  • 枚举整数 ↔ 代号 ↔ 外部名称 ↔ HAL 家族对照,使重新实现会把同一硅片路由到同一工厂、编解码器和 bundle 编码器。
枚举tpu::TpuVersion — 6 个值,kJellyfish=0k6acc60406=5
规范映射tpu::TpuVersionToString @ 0x20b3a480 索引 off_22011BF0(6 项 rel.ro 指针表)
线上形式映射tpu::TpuVersionFromProto @ 0x20b3a8c0switch(proto),internal = proto − 1
Flag 反解析tpu::AbslUnparseFlag @ 0x20b3ab40 — 重新索引同一个 off_22011BF0 + 长度表
外部名称tpu::TpuVersionToExternalName @ 0x20b3a500 — switch 到 "TPU vN [lite]"
长度表unk_BDF3BD8 @ 0xBDF3BD8 — 并行的 6 项逐代号字节长度
边界每个读取者检查 version >= 6LogMessageFataltpu_version.cc:152
源文件learning/45eac/tpu/runtime/tpu_version.cc(从 fatal 日志字符串恢复)

规范指针表

无可争议的枚举到代号映射既不是 switch 语句,也不是运行时构造的字符串。它是一个编译进 .data.rel.ro、虚拟地址为 0x22011BF0 的六项指针数组。每个槽位保存一个重定位指针(R_X86_64_RELATIVE),指向 .rodata 中的空字符结尾代号;槽位 N 的重定位目标就是 TpuVersion N 的代号。因为该数组在链接时物化,并由枚举整数直接索引,所以它是其他每个身份轴的根。

tpu::TpuVersionToString0x20b3a480,115 字节)是规范读取者。其函数体是一次边界检查,随后是双表加载:

c
const char *TpuVersionToString(unsigned version) {     // sub_20B3A480
    if (version >= 6) {                                // bounds guard
        LogMessageFatal("learning/45eac/tpu/runtime/tpu_version.cc", 152);
        log << "Invalid TPU version " << (TpuVersion)version;   // fatal
    }
    return off_22011BF0[version];                      // rel.ro pointer table
    // a parallel load of unk_BDF3BD8[version] returns the length in rdx
}
```text

反汇编直接钉住了表地址和第一项——`lea rax, off_22011BF0` 被符号化器注释为 `"jellyfish"`,第二个 `lea rdx, unk_BDF3BD8` 加载由同一寄存器索引的并行长度表。读取六个槽位各自的重定位加数,可得到按枚举顺序排列的代号:

| `TpuVersion` | 表槽位 (rel.ro) | `.rodata` 目标 | 代号字面量 | 长度 |
|---:|---|---|---|---:|
| 0 | `0x22011BF0` | `0x863F064` | `jellyfish` | 9 |
| 1 | `0x22011BF8` | `0x863F392` | `dragonfish` | 10 |
| 2 | `0x22011C00` | `0x863F1C4` | `pufferfish` | 10 |
| 3 | `0x22011C08` | `0x863F172` | `viperfish` | 9 |
| 4 | `0x22011C10` | `0x86864E0` | `ghostlite` | 9 |
| 5 | `0x22011C18` | `0x863F0CF` | `6acc60406` | 9 |

位于 `0xBDF3BD8` 的并行长度表存储 `{9, 10, 10, 9, 9, 9}`,因此代号可以作为带长度的 `string_view` 返回,而无需 `strlen`。这些长度由外部名称构造器确认,它们把相同字节计数作为立即数嵌入(见下文)。

> **注意 —** 该表是代号字符串能按枚举索引访问到的*唯一*位置。`dragonfish` 没有 `xla_df_` flag 前缀,没有专用 bundle-restrictions 类,并且共享 Jellyfish 的编码器——但它有自己的指针表槽位和自己的代号字面量。若重新实现从*消费者*(flag 前缀、编码器家族)推导代号列表,就会少算;指针表才是权威名册。

---

## 三个事实来源函数

`tpu_version.cc` 中的三个函数定义了该枚举的外部契约。它们彼此独立——不同调用点、不同输出形式——但都同意同一个 6 值空间,因此重新实现可以把任意一个视为事实基础,并用其他两个进行校验。

### `TpuVersionToString` — 枚举 → 内部代号

上文已覆盖(`0x20b3a480`)。通过 `off_22011BF0[version]` 返回 `.rodata` 代号。这是在日志行、调试 dump,以及 `gxc::glc` / `gxc::gfc` 命名空间派生中使用的形式。

### `AbslUnparseFlag` — 枚举 → flag 字符串

`tpu::AbslUnparseFlag`(`0x20b3ab40`)是 `--tpu_version=` flag 的 Abseil 命令行 flag 反解析器。它执行**相同**的边界检查(`version >= 6`,于 `tpu_version.cc:152` fatal),索引**相同**的 `off_22011BF0` 指针表和**相同**的 `qword_BDF3BD8` 长度表,然后把代号 `memcpy` 到调用方的字符串缓冲区并补空字符:

```c
void AbslUnparseFlag(string *out, unsigned version) {  // sub_20B3AB40
    if (version >= 6)
        LogMessageFatal(".../tpu_version.cc", 152), log << "Invalid TPU version " << ...;
    const char *name = off_22011BF0[version];           // same table as ToString
    size_t      len  = qword_BDF3BD8[version];           // same length table
    out->size = len; memcpy(out->data, name, len); out->data[len] = 0;
}

两个独立函数读取完全相同的编译表,这是说明映射为 {0:jellyfish … 5:6acc60406} 而非某种排列的最强内部佐证。flag 会与运行时记录的同一组代号字符串往返。

TpuVersionFromProto — 线上枚举 → 内部枚举

tpu::TpuVersionFromProto0x20b3a8c0)把线上 TpuVersionProto(从 1 开始)转换为内部 TpuVersion(从 0 开始),返回 StatusOr<TpuVersion>。函数体是显式的逐 case switch,每个分支都写入 internal = proto − 1 并设置 status-OK 标志:

c
StatusOr<TpuVersion> TpuVersionFromProto(TpuVersionProto proto) {  // sub_20B3A8C0
    switch (proto) {
        case 1: result.value = 0; result.ok = 1; break;   // TPU_VERSION_JELLYFISH  -> kJellyfish
        case 2: result.value = 1; result.ok = 1; break;   // TPU_VERSION_DRAGONFISH -> kDragonfish
        case 3: result.value = 2; result.ok = 1; break;   // TPU_VERSION_PUFFERFISH -> kPufferfish
        case 4: result.value = 3; result.ok = 1; break;   // TPU_VERSION_VIPERFISH  -> kViperfish
        case 5: result.value = 4; result.ok = 1; break;   // TPU_VERSION_GHOSTLITE  -> kGhostlite
        case 6: result.value = 5; result.ok = 1; break;   // TPU_VERSION_6acc60406  -> k6acc60406
        default:                                            // 0 / >6
            result.status = MakeError("Invalid TPU version: " + proto,
                                      ".../tpu_version.cc", 421);
    }
    return result;
}
```text

全部六个有效 case 都统一满足 `internal = proto − 1`。`proto = 0`(`TPU_VERSION_INVALID`)和 `proto > 6` 落入 default 分支,并产生一个非 OK `Status`,消息为第 421 行的 `"Invalid TPU version: <N>"`。`tpu::TpuVersionFromProtoOrDie`(`0x20b3aa20`)对它进行封装:调用 `FromProto`,检查 status,并在失败时于第 428 行用 `"Could not read TPU version from protobuf."` 触发 `LogMessageFatal`。当 proto 已知格式良好时,大多数内部调用点使用 "OrDie" 变体——例如 `ProgramProtoUtil::BundleCount` 读取 proto 侧 version 字段,并在按内部值 switch 之前直接送入 `FromProtoOrDie`。

> **注意 —** 反编译出的 `FromProto` 函数体是显式六分支 `switch(proto)`,每个分支都独立存储自己的 `proto-1` 值,而不是一个 `proto-1` 算术加跳转表惯用法。两者会降低为等价代码(`internal = proto - 1`,default = error);上面展示的逐 case switch 是字节精确的源级形式。契约——proto N 映射到 internal N−1,proto 0>6 是错误——无论哪种写法都成立。

---

## 五轴对照表

`TpuVersion` 是五个身份空间的枢纽。下表是合并后的对照;每一列都由不同的二进制产物验证,因此重新实现可以从整数重建任意一个轴,并用其他轴校验。

| `TpuVersion` (int) | 枚举标签 | 代号 (`ToString`) | `TpuVersionProto`(线上) | 外部名称 (`ToExternalName`) | HAL 家族 / 工厂 |
|---:|---|---|---:|---|---|
| 0 | `kJellyfish` | `jellyfish` | 1 (`TPU_VERSION_JELLYFISH`) | `TPU v2` | JXC / `TpuHalJxcHardwareFactory` |
| 1 | `kDragonfish` | `dragonfish` | 2 (`TPU_VERSION_DRAGONFISH`) | `TPU v3` | JXC / `TpuHalJxcHardwareFactory` |
| 2 | `kPufferfish` | `pufferfish` | 3 (`TPU_VERSION_PUFFERFISH`) | `TPU v4` (`… lite`) | PXC / `TpuHalPxcHardwareFactory` |
| 3 | `kViperfish` | `viperfish` | 4 (`TPU_VERSION_VIPERFISH`) | `TPU v5` (`… lite`) | VXC / `TpuHalVxcHardwareFactory` |
| 4 | `kGhostlite` | `ghostlite` | 5 (`TPU_VERSION_GHOSTLITE`) | `TPU v6 lite` | VXC / `TpuHalVxcHardwareFactory` |
| 5 | `k6acc60406` | `6acc60406` | 6 (`TPU_VERSION_6acc60406`) | `TPU7x` | VXC / `TpuHalVxcHardwareFactory` |

> **注意 — proto 枚举项名称基于代号,而不是市场名称。** `TpuVersionProto` 枚举项拼写为 `TPU_VERSION_<CODENAME>`(`TPU_VERSION_INVALID`=0,随后 `TPU_VERSION_JELLYFISH`=1 … `TPU_VERSION_6acc60406`=6),由 `0xC1928DB`–`0xC19297D` 的连续 descriptor 字符串块确认。二进制中没有任何 `TPU_V2`/`TPU_V3`/`TPU_V7X` 枚举项——`TPU v2…TPU7x` 字符串是 `TpuVersionToExternalName`(`0x20b3a500`)发出的独立*外部/市场*轴。不要混淆线上枚举项名称和外部名称:`TPU_VERSION_JELLYFISH`(线上)与 `TPU v2`(外部)是在两个不同轴上命名同一硅片。

这些轴能整齐对齐,是因为每个消费者都索引同一个整数。有些轴的细节值得钉住:

- **代号到 `viperfish` 为止都是鱼名,之后开始退化。**0-3 代使用真实鱼类代号(`jellyfish`、`dragonfish`、`pufferfish`、`viperfish`)。第 4 代是 `ghostlite`——一个缩写,不是鱼。第 5 代是 `6acc60406`——一个混淆过的 9 字符标签,也是唯一非助记代号。字符串 `ghostfish` 在二进制中出现零次;第 5 代唯一规范内部名是 `6acc60406`。

- **外部名称对第 2 和第 3 代携带 `lite` 区分符。** `TpuVersionToExternalName`(`0x20b3a500`)和 `TpuVersionAndVariantToHumanReadableName`(`0x20b3b040`)都会对 variant `string_view` 分支:当 variant 正好是 4 字节且等于小端 dword `1702127980`(= `0x6574696c` = ASCII `"lite"`)时,`pufferfish` 读作 `"TPU v4 lite"`,`viperfish` 读作 `"TPU v5 lite"`。第 4 和第 5 代有固定外部名称(`"TPU v6 lite"`、`"TPU7x"`),没有 variant 分支。

```text
TpuVersionToExternalName(version, variant_sv):       // sub_20B3A500
  0 -> "TPU v2"
  1 -> "TPU v3"
  2 -> (variant=="lite") ? "TPU v4 lite" : "TPU v4"   // lite = dword 0x6574696c
  3 -> (variant=="lite") ? "TPU v5 lite" : "TPU v5"
  4 -> "TPU v6 lite"
  5 -> "TPU7x"
  _ -> "Unknown TPU version"
  • HAL 家族把三个代号折叠到一个工厂。 第 0-1 代共享 TpuHalJxcHardwareFactory;第 2 代拥有自己的 TpuHalPxcHardwareFactory(构造时没有 version 参数,因为 PXC 只服务 Pufferfish);第 3、4、5 代都共享 TpuHalVxcHardwareFactory,仅通过工厂构造时传入的 TpuVersion 值区分。符号表确认恰好存在三个 HAL 工厂类——TpuHalJxcHardwareFactoryTpuHalPxcHardwareFactoryTpuHalVxcHardwareFactory。代号到注册 init module(google_init_module_tpu_hal_* 翻译单元)的映射以及逐版本分派位于 HAL 家族

易错点 — HAL 工厂类和 HAL init module 并不命名同一件事。第 4 和第 5 代分别通过名为 glcgfc 的 init module 注册,但二者都构造共享的 TpuHalVxcHardwareFactory。若重新实现从 glc init-module 名称推断出“Ghostlite 有一个 GlcFactory”,就会发明出一个并不存在的类。工厂类是 VXC;glc/gfc 命名的是子核心 ISA 家族,不是工厂。


编解码器选择确认顺序

指令选择编解码器工厂是枚举整数顺序的独立确认。tpu::TpuCodec::Create0x1e835fa0)是一个干净的 switch(TpuVersion),按代号调用各自的 CreateTpuCodec* 构造器:

c
StatusOr<TpuCodec*> TpuCodec::Create(TpuVersion version) {   // sub_1E835FA0
    switch (version) {
        case 0: codec = CreateTpuCodecJellyfish();  break;
        case 1: codec = CreateTpuCodecDragonfish(); break;
        case 2: codec = CreateTpuCodecPufferfish(); break;
        case 3: codec = CreateTpuCodecViperfish();  break;
        case 4: codec = CreateTpuCodecGhostlite();  break;   // named
        case 5: codec = sub_1E838380();             break;   // anonymous v5 codec
    }
    result.value = codec; result.ok = 1; return result;
}
```text

case 0-4 各自调用一个反修饰后的 `CreateTpuCodec<Codename>` 工厂;这些名称与指针表代号逐一匹配。case 5 是关键:不存在 `CreateTpuCodec6acc60406` 符号。v5 编解码器由一个匿名工厂(`sub_1E838380`)构造,该工厂安装一个没有命名 `_ZTV` / `_ZTI` 符号的 vtable。第 4 代的命名编解码器是 `TpuCodecGhostlite`(130 个交叉引用;vtable 位于 `0x21d35c00`);第 5 代编解码器只通过字符串注册具现化(`6acc60406BundleRestrictions`、`6acc60406HardwareScanner`、`6acc60406RouteCacheSet`),从不作为 `TpuCodec6acc60406` C++ 类出现。这种不对称——Ghostlite 完全命名,6acc60406 被混淆且匿名——在每个轴上都会重复出现,也是该二进制自身表明第 5 代是此构建中新近、暴露最少硅片的信号。

bundle 编码器分派(`tpu::ProgramProtoUtil::BundleCount`,`0x1e830e80`)从第三个角度确认相同分组。它读取 proto 侧 version 字段,通过 `TpuVersionFromProtoOrDie` 转为内部值,然后对内部值 switch

```text
BundleCount internal-version switch:                 // sub_1E830E80
  case 0, 1 -> CreateEncoderJfDf   (Jellyfish + Dragonfish share)
  case 2    -> CreateEncoderPf     (Pufferfish)
  case 3    -> CreateEncoderVf     (Viperfish)
  case 4, 5 -> CreateEncoderGlGf   (Ghostlite + 6acc60406 share)

0+1(JfDf)和 4+5(GlGf)的配对是直接二进制证据,说明 Dragonfish 复用 Jellyfish 的编码器,而 6acc60406 复用 Ghostlite 的 GlGf 编码器——这与代号命名空间(v4 的 gxc::glc、v5 的 gxc::gfc)暗示的共享完全一致。


逐代号特性矩阵

下面的特性存在性来自逐代号 C++ 类、bundle-restriction 注册,以及 SparseCore / BarnaCore 命名空间分布。架构弧线是加速器产品线常见形态:融合的第一代数据流引擎(用于 HBM embedding 的 BarnaCore),中期转向 fetch/load 核心拆分,后期加入专用 SparseCore。

代号TensorCoreBarnaCoreSparseCoreBundle restrictions
jellyfishJellyfishBundleRestrictions
dragonfish共享 JellyfishBundleRestrictions
pufferfishPufferfishBundleRestrictions
viperfishViperfishBundleRestrictions
ghostliteGhostliteBundleRestrictions
6acc604066acc60406BundleRestrictions(字符串注册)

矩阵解读:

  • TensorCore 普遍存在。 每一代都有 TensorCore;它是常量。
  • BarnaCore 是早期代际的 embedding 引擎。 存在于 Jellyfish 到 Pufferfish(platforms_deepsea::jellyfish::barna_core 命名空间有内容,且 ComputeThreadCountPerBarnaClump 函数接受一个 TpuVersion 参数),从 Viperfish 起退役。
  • SparseCore 随 Viperfish 到来。 Viperfish、Ghostlite 和 6acc60406 携带 SparseCore 支持;rodata 字符串 "FusionDebugger supports Viperfish and later platforms" 佐证了 Viperfish 及之后的边界,而 xla_sc_ flag 前缀族(SparseCore flag)被门控到这三代。
  • 除共享配对外,bundle restrictions 按代号划分。 存在四个命名代号 *BundleRestrictions 类——JellyfishBundleRestrictionsPufferfishBundleRestrictionsViperfishBundleRestrictionsGhostliteBundleRestrictions(每个都有自己的 _ZTV/_ZTI/_ZTS 三元组),它们继承 TpuBundleRestrictions 基类;Dragonfish 共享 Jellyfish 的实现。6acc60406BundleRestrictions 行为 MEDIUM,因为它只通过字符串注册——不存在该名称的反修饰 C++ 类,这与第 5 代的匿名编解码器模式一致。

置信度核算

该矩阵的可信度取决于知道哪些赋值由保护代码钉住,哪些依赖声明顺序。这个区分对重新实现很重要:钉住的行可以逐字信任;按顺序推断的行则应在二进制版本变化时重新推导。

  • 整数 0 和 5 为 HIGH,由保护代码钉住。 第 0 代(kJellyfish)是第一个表槽位、第一个 CreateTpuCodec case,以及 xor %esi,%esi(值 0)Register 调用——其为零这一点在结构上固定。第 5 代(k6acc60406)在顶端被钉住:ToString/AbslUnparseFlag 中的 version >= 6 边界检查证明枚举恰好有六个值且 5 是最大值;TpuVersionFromProto 证明 proto 6 → internal 5;编解码器 switch 和编码器分派都终止于 case 5。没有第七个值,也没有空洞。

  • 整数 1-4 在代号映射上是 HIGH,但它们的相对顺序依赖指针表槽位顺序,而该顺序等于 proto 声明顺序。 0x22011BF0 的指针表是权威,并且它在链接时按枚举顺序物化,因此 1-4 的代号到整数绑定与 0 和 5 一样稳固。被推断(而非由保护代码钉住)的是此顺序同时也是硅片时间顺序——dragonfishjellyfish 新、viperfishpufferfish 新,依此类推。该时间顺序与每个轴(外部名称 v2 < v3 < v4 < v5 < v6 < v7x、proto 值、GetTpuType superpod 顺序)一致,但它是从声明顺序推断出来的,不是来自某个单一保护。

  • 外部 "TPU vN" 名称为 HIGH,由 switch 直接钉住。 ToExternalNameToHumanReadableName 是带字面量字符串分支的显式 switch;v4=TPU v4v6 liteTPU7x 绑定没有歧义。

  • 6acc60406 → 公开市场名称绑定超出此二进制范围。 字符串 TrilliumIronwoodGhostfishlibtpu.so 中出现零次。库只把第 5 代命名为 6acc60406(内部)、TPU7x(外部显示)以及 tpu7x/tpu7(解析器接受的 Cloud accelerator_type 字符串)。任何把 6acc60406 映射到公开产品代号的说法,都是叠加到二进制之上的外部事实,并非源于二进制;wiki 将其标为 LOW,并记录在 Marketing / Cloud Naming

怪异点 — 混淆标签 6acc60406 是字面代号,不是工具解析失败留下的占位符。它是指针表槽位 5 的重定位目标、第 5 代的 ToString 返回值,也是嵌入资源 6acc60406_chip_parts.binarypb 的前缀。构建这一代的人有意剥离了助记代号,并交付了一个 9 字符、看起来像十六进制的标签——其他每个代号都是单词,只有这一项是 token。重新实现应把 6acc60406 视为规范名称,不要把它“修正”为鱼名。


交叉引用

  • 双枚举(Proto vs Internal) — 完整说明 internal = proto − 1 的差 1 关系,包含完整线上值表和 FromProto/ToProto 函数体
  • 第 IV 部分概览TpuVersion 如何贯穿 HAL 路由、芯片常量和 ISA 选择
  • HAL 家族 — JXC / PXC / VXC 工厂路由和逐代号 init module
  • 子核心分类 — fetch/load 核心拆分(pfc/plcvfc/vlc)以及 gxc::glc / gxc::gfc ISA 子家族
  • 逐代号硬件常量 — 由 TpuVersion 门控的芯片常量
  • PCI 设备 ID — 在发现阶段将硅片映射到 TpuVersion 的 DeviceIdentifiers 记录
  • Marketing / Cloud Naming — 外部 accelerator_type 字符串,以及为什么 Trillium/Ironwood 不在二进制中
  • ISA 概览TpuVersion 选择的编解码器和 bundle 编码器家族