Skip to content

chip_parts.binarypb 解码

本页中的所有地址、偏移和常量值,均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5 89edbbe81c5b328a958fe628a9f2207d,未 strip,ELF x86-64)。其他版本会有所不同。

摘要

TPU 编译器需要的每个按代号区分的硬件常量——HBM/VMEM/SMEM/SFLAG 容量、MXU lane/sublane 几何、TensorCore 和 HBM 时钟、寄存器文件宽度、DMA 粒度——并不以 C++ 字面量承载,而是位于一个序列化 protobuf blob <codename>_chip_parts.binarypb 中,并直接嵌入 libtpu.so.rodata 段。启动时,runtime 为当前活跃的 TpuVersion 解析出该 blob,将其解析为 TpuChipPartsProto,并把解码后的字段复制到 xla::jellyfish::Target 对象中,供 cost model、ISA emitter 和 topology 层读取。这是一个数据驱动的 HAL:同一个 C++ Target 类服务每一代硬件,只由加载进来的字节进行专门化。

这个机制类似 LLVM 的 TargetMachine.td 生成的 SubtargetInfo 表初始化,只是这里的表是运行时加载的 proto,而不是 TableGen 烘焙好的 struct。proto schema 位于二进制自身的 protodesc_cold descriptor pool 中,因此可以精确恢复;这些 blob 每个为 232–624 字节,并且可以逐字节解码,无需推断。解析函数 TpuChipParts::DefaultsForVersion 根据 version 字符串构造 embed:// 资源路径,并通过 tsl::ReadBinaryProto 读取它。

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

  • 资源模型:九个 embed://tpu_chip_parts/<name>_chip_parts.binarypb blob 通过一个 40 字节 FileWrapper descriptor 数组注册,每个都带有磁盘上的 size、md5 fingerprint 和经重定位的数据指针。
  • 解析路径version -> TpuVersionToString -> AsciiStrToLower -> StrCat("embed://...") -> ReadBinaryProto -> FromProto
  • proto schemaTpuChipPartsProto 及其配套的 TpuCorePartsProtoTpuSequencerPartsProtoTpuMemoryPartsProtoTpuSharedMemoryPartsProto,以及 tpu_chip_enums.proto 枚举,作为按 field number 编排的表。
Resolvertpu::TpuChipParts::DefaultsForVersion @ 0x20b1b040
源文件(resolver)learning/45eac/tpu/runtime/topology/tpu_chip_parts.cc:341
Parsertpu::TpuChipParts::FromProto @ 0x20b1b400; tpu::TpuMemoryParts::FromProto @ 0x20b333a0
Descriptor 数组.data.rel.ro VA 0x22010ED0(file off 0x21E10ED0),9 entries × 0x28 字节
Blob 区域.rodata VA 0x0BDF29A0..0x0BDF3AB8(VA == file offset)
Proto schemaprotodesc_cold: tpu_chip_parts.proto @ 0xC18FD80 及配套文件

资源模型

目的

chip_parts 是某一代 TPU 硬件能力几何的唯一事实来源。它与 chip_config(见 TpuChipConfig)不是同一回事:chip_parts 描述硅片是什么(core 数量、内存大小、MXU 维度、时钟),而 chip_config 描述运行时模式(bounce buffer、同步标志资源、设备端传输窗口)。两者都是 embed:// proto blob,但通过不同函数解析,并解析到不同对象中。

.rodata 中的 Blob 布局

九个 blob 连续放在 .rodata 中,该 section 的 VA 等于 file offset,因此可以直接切出字节。每个 blob 都由 .data.rel.ro VA 0x22010ED0 处一个 9 项数组中的 40 字节 FileWrapper descriptor 注册。descriptor 布局是:

text
struct FileWrapper {              // 0x28 bytes
    const char* name;   // +0x00  R_X86_64_RELATIVE reloc (0 on disk, filled at load)
    const void* data;   // +0x08  R_X86_64_RELATIVE reloc -> blob VA in .rodata
    int64_t     size;   // +0x10  ON DISK (the serialized byte length)
    uint8_t     fp[16]; // +0x18  ON DISK (md5 fingerprint of the blob)
};
```text

> **GOTCHA —** `name` 和 `data` 指针在**磁盘镜像中读作零**:它们是由动态加载器在加载时填充的 `R_X86_64_RELATIVE` 重定位。只有 `size` 和 16 字节 md5 `fp` 是磁盘上的字面值。信任磁盘上指针字段的读取器会断定数组为空,并认为只有恰好被直接检查数据的两个 blob 存在。正确读取方式是解析 `data` 重定位的 *addend*(它指向真实 blob VA),并用磁盘上的 md5 校验它。九个 `fp` 字段中的每一个都匹配其重定位 addend 指向的 blob 的 md5。
>
> **NOTE —** 九个 blob 全部都已嵌入(上面的重定位陷阱让数组看起来很短):每个都已按其 descriptor 的 `fp` 字段完成 md5 验证并逐字节解码,因此每个按代号页面都携带确认值。

九个 descriptor 按数组顺序列出,并给出重定位解析后的 data VA:

下面的全部九个 `fp` 字段、`data` 重定位 addend、size 和 `version` 字节,均已独立重新推导:每个 blob 都在其重定位解析出的 `blob VA` 处切出、计算 md5,并与磁盘上的 `fp` 匹配——每行都匹配,因此每行都是 CONFIRMED。

| # | Resource key | desc VA | blob VA | size (B) | md5 (== `fp`) | first bytes |
|---|---|---|---|---:|---|---|
| 0 | `6acc60406_tensornode_chip_parts.binarypb` | `0x22010ED0` | `0x0BDF29A0` | 504 | `d1e4bea3…dec694a5` | `08 06` |
| 1 | `6acc60406_chip_parts.binarypb` | `0x22010EF8` | `0x0BDF2BA0` | 546 | `f5c490e6…02fd8029` | `08 06` |
| 2 | `ghostlite_chip_parts.binarypb` | `0x22010F20` | `0x0BDF2DD0` | 564 | `010c6352…13f5807f` | `08 05` |
| 3 | `viperfish_chip_parts.binarypb` | `0x22010F48` | `0x0BDF3010` | 601 | `fccc06a7…e84c9dcf` | `08 04` |
| 4 | `viperfish_lite_chip_parts.binarypb` | `0x22010F70` | `0x0BDF3270` | 232 | `a8e02254…064cb465` | `08 04` |
| 5 | `pufferfish_lite_chip_parts.binarypb` | `0x22010F98` | `0x0BDF3360` | 277 | `fb066c9a…d1ff501c` | `08 03` |
| 6 | `pufferfish_chip_parts.binarypb` | `0x22010FC0` | `0x0BDF3480` | 624 | `acdf3a9e…49af3fb2` | `08 03` |
| 7 | `jellyfish_chip_parts.binarypb` | `0x22010FE8` | `0x0BDF3700` | 435 | `f86192ba…c4adecda` | `08 01` |
| 8 | `dragonfish_chip_parts.binarypb` | `0x22011010` | `0x0BDF38C0` | 504 | `d3d51f67…80f61047` | `08 02` |

`first bytes` 列是 `tag=0x08`(field 1,varint),后跟 `TpuVersionProto` 值:jellyfish=1、dragonfish=2、pufferfish(+lite)=3、viperfish(+lite)=4、ghostlite=5、6acc60406=6。两个 `6acc60406` blob 只在 package multiplicity 上不同——`tensornode` 是一个 die(1 个 TensorCore、2 个 SparseCore、1 个 HBM stack);裸 `6acc60406` blob 是完整的双 die megachip(数量翻倍)。完整解码见 [按代号常量表](per-codename-hw-constants.md)。

### 解析路径

`DefaultsForVersion` 在运行时构造资源名并读取它。重建出的逻辑:

```c
StatusOr<TpuChipParts> DefaultsForVersion(TpuVersion v, string_view variant):  // sub_20b1b040
    proto = TpuChipPartsProto()
    name  = AsciiStrToLower(TpuVersionToString(v))   // e.g. "jellyfish", "6acc60406"
    if variant.non_empty():                           // tensornode / lite selector
        StrAppend(&name, "_", variant)
    filename = StrCat("embed://tpu_chip_parts/", name, "_chip_parts.binarypb")
    status = tsl::ReadBinaryProto(Env::Default(), filename, &proto)   // tpu_chip_parts.cc:343
    CHECK(status == Ok) << "Failed to parse TpuChipPartsProto."
    return TpuChipParts::FromProto(proto)             // sub_20b1b400

因此选择 v=0(jellyfish)会得到 jellyfish_chip_parts.binarypb 资源,v=4(ghostlite)会得到 ghostlite_… blob,依此类推。embed:// VFS scheme 将资源名映射回 name 字符串匹配的 FileWrapper;随后 ReadBinaryProto 解析该 blob 的字节。由于名称是从 version 计算出来的,每一代的 blob 都是活的,不是死数据——resolver 会为每一代构造有效 key。

NOTE — DefaultsForVersion 在 parse error 时会 CHECK 失败(fatal),而不是返回降级默认值。C++ 中没有烘焙的 fallback geometry;如果 blob 缺失或格式错误,runtime 会 abort。这就是数据驱动 HAL 背后的架构承诺:proto 就是硬件描述。


恢复出的 Proto Schema

完整 schema 从二进制的 protodesc_cold pool 中的 FileDescriptorProto 恢复(tpu_chip_parts.proto @ 0xC18FD80tpu_core_parts.proto @ 0xC190810tpu_sequencer_parts.proto @ 0xC191340tpu_memory_parts.proto @ 0xC191750tpu_shared_memory_parts.proto @ 0xC1919B0tpu_chip_enums.proto @ 0xC191B90)。下面的每个 field number 都已通过按该 schema 解码嵌入 blob 确认;解码逐字节精确,并在全部九个 blob 上自洽。

TpuChipPartsProto — 顶层消息

下面的 field name、number、type 和 label 直接从 0xC18FD80 处的 tpu_chip_parts.proto FileDescriptorProto 读取(每行的 type_name/label 都在 descriptor 中),并且每个编号字段都已从全部九个 blob 中独立重新解码——因此每行都是 CONFIRMED。

Field#Type含义
version1TpuVersionProtogeneration 标签(1..6)
cores2repeated Core每个 core type 一个 entry(TensorCore + BarnaCore/SparseCore)
shared_memories3repeated SharedMemoryHBM(总是存在)和 CMEM(仅 Pufferfish)
uhi_sync_flag_memory_parts4TpuMemoryPartsProtoUHI 同步标志区域(存在时)
local_shared_memory_mappings5repeated LocalSharedMemoryMappingcore -> HBM stack 拓扑
dma_requirements6DmaRequirementsProtoalignment / granule / max-single-DMA
variant_name7stringlite blob 上为 "lite",其他为空
misc8MiscPropertiesProto同步标志功能 flag
driver_abi_version9int64每一代上都是 1

嵌套和配套消息

text
Core           { TpuCoreTypeProto type=1; TpuCorePartsProto parts=2; int32 count=3; }
SharedMemory   { TpuSharedMemoryTypeProto type=1; TpuSharedMemoryPartsProto parts=2; int32 count=3; }
DmaRequirementsProto { int64 host_alignment_bytes=1; int64 device_alignment_bytes=2;
                       int64 granule_bytes=3; int64 sync_flag_granule_bytes=4;
                       int64 max_single_host_dma_bytes=5; }
MiscPropertiesProto  { int32 max_slice_size_for_all_to_all_routing=1;
                       bool has_extra_done_bit_in_sync_flags=2;
                       bool is_host_sync_flag_access_async=3;
                       bool supports_sync_flag_mode_count_dones=4; }

TpuCorePartsProto    { TpuVersionProto version=1; TpuCoreTypeProto type=2;
                       repeated Sequencer sequencers=3; repeated Memory memories=4;
                       int32 frequency_mhz=5; int32 host_interrupt_count=6;
                       BarnaCore barna_core=7; SparseCore sparse_core=8; }
  Sequencer  { TpuSequencerTypeProto type=1; TpuSequencerPartsProto parts=2; int32 count=3; }
  Memory     { TpuMemoryTypeProto type=1; TpuMemoryPartsProto parts=2; int32 count=3; }
  SparseCore { int32 dreg_word_count=1; int32 dreg_bytes_per_word=2;
               int32 tile_hbm_bandwidth_bytes_per_cycle=3; int32 stream_granule_size=4; }

TpuSequencerPartsProto { TpuVersionProto version=1; TpuSequencerTypeProto type=2;
                         repeated Register registers=3; ScalarIsa scalar_isa=4;
                         VectorIsa vector_isa=5; BarnaCoreFsm barna_core_fsm=6; }
  Register  { TpuRegisterTypeProto type=1; int32 count=2; }
  VectorIsa { int32 lane_count=2; int32 sublane_count=3; int32 issue_latency_cycle_count=4;
              int32 mxu_count=5; int32 xlu_count=6; int32 iar_count=7; }

TpuMemoryPartsProto       { TpuVersionProto version=1; TpuMemoryTypeProto type=2;
                            bool holds_instructions=3; bool supports_dma=4;
                            int32 bytes_per_word=5; int64 word_base=6; int64 word_count=7;
                            int64 bundle_count=8; int64 bytes_per_instruction_dma_chunk=9;
                            int64 bundles_per_instruction_dma_chunk=10; }
TpuSharedMemoryPartsProto { TpuVersionProto version=1; TpuSharedMemoryTypeProto type=2;
                            int32 bytes_per_word=3; int64 word_count=4; int32 frequency_mhz=5;
                            int32 channel_count=6; int32 ports_per_channel=7;
                            int32 bytes_per_port=8; int64 bytes_per_second=9; }
```text

> **QUIRK —** memory 和 shared-memory 的大小存为 `bytes_per_word × word_count`,从不存为单个 byte count。`6acc60406` 上的 HBM 是 `32 × 3,187,671,040 = 102,005,473,280 B`(95 GiB);只读取 `word_count` 会因为 word 因子而低估。“word” 是该内存的原生访问粒度:v5p/v6e/v7 HBM 为 32 B,v2/v3 HBM 为 1024 B,所有 VMEM 为 512 B,SMEM/SFLAG 为 4 B。`TpuMemoryParts::FromProto` 会先验证每个字段(`bytes_per_word > 0`、`word_count > 0`、指令内存不得设置 `word_base`/`word_count` 等),再将 region 打包到 64 字节堆记录(`operator new(0x40)`)中。
>
> **GOTCHA(HBM/CMEM packing)—** HBM 和 CMEM 层级(`shared_memories` field 3)通过 `tpu::TpuSharedMemoryParts::FromProto` @ `0x20b34aa0` 解析,这是一条与 `TpuMemoryParts::FromProto` *分离*的路径;内存中的 record 与 proto wire form 有两点不同,重新实现者必须精确复现。**(1)** `bytes_per_word` 字段(`TpuSharedMemoryPartsProto` field 3)**不按字节存储**——`FromProto` 运行 `_BitScanReverse(bytes_per_word)`,并在 record offset `+0x20` 存储 **log2**(例如 `32 B → 5`、`512 B → 9`);byte count 必须重新计算为 `1 << log2`。**(2)** 解码后的 record 是一个 **48 字节**堆对象(`operator new(0x30)`),不同于 64 字节的 `TpuMemoryParts` record,其字段顺序为 `{ version:+0x00, type:+0x04, word_count:+0x08 (int64), frequency_mhz:+0x10, channel_count:+0x14, ports_per_channel:+0x18, bytes_per_port:+0x1c, bytes_per_word_log2:+0x20, bytes_per_second:+0x28 (int64) }`。validator(源文件 `tpu_shared_memory_parts.cc`)强制 **power-of-two `bytes_per_word` bounded to `[8, 32768]`**("Shared memories must have words between 8 and 32768 bytes")、`word_count > 0`、`frequency_mhz ≥ 0`、`channel_count ≥ 0`,以及 `ports_per_channel`/`bytes_per_port` 一致性规则(两者同时为零,或两者同时为正),然后再打包——因此,带 4 B word 的 HBM/CMEM blob(对经由 `TpuMemoryParts` 的 SMEM/SFLAG 合法)会在 shared-memory 路径中被拒绝。

### 相关枚举(`tpu_chip_enums.proto`)

```text
TpuCoreTypeProto         : 1 TENSOR_CORE, 2 BARNA_CORE, 3 SPARSE_CORE
TpuSequencerTypeProto    : 1 TC_SEQ, 2 BC_SEQ, 3 BC_ADDR, 4 SC_SEQ, 5 SC_TAC, 6 SC_TEC
TpuRegisterTypeProto     : 1 SREG, 2 VREG, 3 PREG, 4 VMREG
TpuMemoryTypeProto       : 1 IMEM, 2 VIMEM, 3 TILEIMEM, 4 SMEM, 5 SFLAG, 6 TACSFLAG,
                           7 TECSFLAG, 8 VMEM, 9 TILESPMEM, 10 SPMEM, 11 TACSMEM, 12 TECSMEM
TpuSharedMemoryTypeProto : 1 HBM, 2 CMEM
TpuVersionProto          : 1=jellyfish 2=dragonfish 3=pufferfish 4=viperfish 5=ghostlite 6=6acc60406

chip_parts 承载什么(以及不承载什么)

chip_parts 承载 Target 能力表面所需的一切:每个 memory size、每个 core/sequencer/register count、MXU lane_count/sublane_count/mxu_count/xlu_count/iar_count、TensorCore 和 HBM frequency_mhz、HBM bytes_per_second,以及 DMA granule/alignment block。有两类常量在 proto 中:

  • VMEM/SMEM/CMEM bank count。 这些是按代号 *Target::MemBanks override 中的 C++ 字面量,不是 proto 字段。见 按代号常量表
  • issue_latency_cycle_count(VectorIsa field 4)在每个 blob 中都缺失(默认 0),包括全部六个较老 generation——真实的 MXU/VPU issue latency 位于按代号 cost model 中,而不是 chip_parts 中。MXU systolic depth 同样不是显式 proto 字段:proto 给出 lane_count=128mxu_count,但 v6e/v7 的 256×256 systolic 维度是单独的 GhostliteTarget C++ override。

NOTE — 该 proto 中没有 dtype/sparsity 能力字段。每种格式的 peak FLOPS 和支持精度编码在按代号 ISA 与 accuracy 表中,而不在 chip_parts 中。


相关组件

名称关系
TpuChipConfig::Createchip_config(mode/resource)blob 的并行 embed:// resolver,不是 capability geometry
TpuChipParts::FromProto将 blob 解析为 TpuChipParts;填充 Target field block +0x438..+0x510
xla::jellyfish::Target解码后常量填充的 runtime 对象;由 cost model、ISA emitter、topology 读取

交叉引用