Skip to content

Bundle 模型

本页中的每个偏移、值和地址都逐字节精确读取自 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5 89edbbe81c5b328a958fe628a9f2207d)。其他版本会有所不同。

摘要

TPU TensorCore 是一台 VLIW 机器:序列器每个周期取一条固定宽度的 bundle,并将其中的每个槽同时发射到不同的执行单元。运行时没有依赖跟踪——bundle 字本身就是发射包,编译器负责证明同一个 bundle 中的槽彼此独立。bundle 字是一个扁平字节缓冲区,其宽度按硅片代固定:Jellyfish(TPU v2)为 41 字节,Pufferfish(TPU v4)为 51 字节,Viperfish(TPU v5)、Ghostlite(TPU v6 lite)和 6acc60406(TPU7x)为 64 字节。这些宽度不是自由参数。Jellyfish 是极端情况——它的 41 字节缓冲区由 EncoderJf::EncodeBundleInternal 内部的 operator new(0x29) 分配硬性固定。Pufferfish 从 EncoderPfTensorCore::BundleSizeBytes 以内联常量返回 51,而 v5+ 的 64 字节宽度通过以 (TpuVersion, TpuSequencerType) 为键的 codec 元数据表返回(Viperfish/Ghostlite 通过 vtable 调用计算大小,而不是在编码器 BundleSizeBytes 中使用内联字面量)。随后编码路径按各代固定的字节/位偏移把槽放入缓冲区。

注意 — 位编号约定。 本页以及它链接到的槽页面中的每个绝对位位置都是 LSB-first:位 0 是字节 0 的最低有效位,通用 v5+ 打包器 BitCopy(dst, dst_bit, src, src_bit, nbits)0x1fa0a900)从按 LSB 编号的 dst_bit 开始写入 nbits。Jellyfish 直接打包编码器通过 shl/or 移位常量使用相同约定。编码路径中不存在 MSB-first / 大端位序。

bundle 是线缆格式,不是编译器 IR。在它之上是 LLO 指令流:一串带类型的 LloInstruction,每条由 opcode + operands + memory-space + predicate 组成。bundle 打包器把相互独立的 LLO op 分组进一个 Bundle 对象,其带类型的子字段(ScalarInstructionVectorAluInstructionVectorExtendedInstructionVectorLoadInstructionVectorStoreInstructionVectorResultInstructionMiscInstruction,再加上 bundle header)与硬件槽一一对应;每代的 Encoder<gen>::EncodeBundleInternal 再把该 Bundle 序列化为固定宽度字节缓冲区。本页记录这个模型:bundle 是什么、每代 bundle 多宽以及二进制中如何决定该宽度、槽分类以及每个槽驱动哪个引擎、空槽(kNeverExecute)约定,以及编码/解码往返的高层图景。逐槽位布局在各个槽页面中;每代完整槽映射在每代 bundle 页面中。

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

  • 三种 bundle 字节宽度(41 / 51 / 64),以及它们由 (TpuVersion, TpuSequencerType) codec 元数据查找选择,而不是通过对较窄 bundle 做字节扩展。
  • 槽分类:哪个带类型的 Bundle 子指令驱动哪个引擎(MXU / VPU / SPU / 序列器 / 内存 / 谓词 / 循环 / 立即数 / EUP),以及数量如何跨代增长。
  • 同时发射语义:bundle 内没有写后读转发;编译器必须跨 bundle 或通过 sync flag 串行化真实依赖。
  • 空槽约定:未使用槽携带写入 bundle header 的 kNeverExecute 谓词,而不是任意垃圾。
  • 编码/解码对:Encoder<gen>::EncodeBundleInternal 写入字节缓冲区;Decoder<gen>::Decode* 重建 Bundle
Bundle 对象platforms_deepsea::jellyfish::isa::Bundle(带类型的槽子字段)
线缆格式扁平字节缓冲区,宽度按 TpuVersion 固定
Jellyfish (v2)41 B — 由 EncoderJf::EncodeBundleInternal @ 0x1e86c7c0 中的 operator new(0x29) 硬性固定;JellyfishCodecMetadata::BundleSizeBytes @ 0x1ecf7460 也返回 41
Dragonfish (v3)41 B — 共享 Jellyfish/EncoderDf codec 路径(CreateEncoderJfDf @ 0x1e835b80
Pufferfish (v4)51 BEncoderPfTensorCore::BundleSizeBytes @ 0x1d227740 返回 51PufferfishCodecMetadata::BundleSizeBytes @ 0x1ecf7ac0 返回 51
Viperfish (v5)64 BViperfishCodecMetadata::BundleSizeBytes @ 0x1ee71320 返回 64EncoderVfTensorCore::BundleSizeBytes @ 0x1d2f8520 通过 vtable+48 调用到达它
Ghostlite (v6 lite)64 BGhostliteCodecMetadata::BundleSizeBytes @ 0x1eeb7640 返回 64EncoderGlTensorCore::BundleSizeBytes @ 0x1d332540 通过 vtable+48 调用到达它
6acc60406 (TPU7x)64 B — 已注册的 codec 元数据;由 CreateEncoderGlGf @ 0x1e831020 构建的 gfc TensorCore codec(case version==5
宽度分派codec_metadata::BundleSizeBytes(TpuVersion, TpuSequencerType) @ 0x1ecf7180GetMetadataOrDie → vtable +16
编码器工厂CreateEncoderJfDf @ 0x1e835b80(JF+DF),CreateEncoderPf @ 0x1e835cc0CreateEncoderVf @ 0x1e835dc0CreateEncoderGlGf @ 0x1e831020(GL+GF)
TpuVersion 枚举6 个值,0..5TpuVersionToString @ 0x20b3a480≥ 6 trap);外部名称来自 Codename Matrix
位编号LSB-first;BitCopy 打包器 @ 0x1fa0a900
空槽标记HardwareBundleBits::kNeverExecute 谓词,写入 bundle header
编码入口Encoder<gen>::EncodeBundleInternal(例如 Jellyfish @ 0x1e86c7c0
置信度CONFIRMED(以字节为锚),除非某行另有说明

Bundle 是什么

bundle 是一个 VLIW 发射字:一段固定宽度的字节序列,由序列器从 IMEM 取出,解码为 N 个并行槽,并在单个周期内分派。每个槽驱动一个执行单元——矩阵单元、一个 vector-ALU lane、标量流水线、一个内存端口、序列器的控制逻辑——且 bundle 中的每个槽同时触发。

该模型是经典的静态调度 VLIW,陷阱也是经典 VLIW 陷阱:没有 scoreboard。原始发现精确地描述了这一点——一个序列器每周期发射 N 个并行操作,编译器负责合法 bundle 打包,并且没有运行时依赖跟踪,因为 ISA 假设编译器已经证明正确性。重新实现者必须编码两个后果:

  • 无 bundle 内转发。 如果槽 A 写寄存器 X,而同一个 bundle 中的槽 B 读取 X,B 看到的是值(ISA 将结果视为未定义)。正确的打包器绝不会把真实依赖放在一个 bundle 内。
  • 跨 bundle 排序是编译器的职责。 真实依赖要么通过把消费者放入后续 bundle 满足(顺序发射推进程序计数器),要么对于异步引擎(MXU 完成、DMA)由序列器槽发出的 sync-flag / semaphore wait 满足。MXU 尤其是解耦的:一次矩阵 push 进入 systolic array,结果在许多周期后通过 result-FIFO 槽读回,并受编译器在 MxuAssigner / MxuSequence 中跟踪的 latch 簿记约束。

陷阱 — bundle 字是发射包,不是基本块。 如果重新实现把 LLO op 调度成每条指令独占一个周期,就会让 bundle 填充不足,错过该架构的核心;如果把有依赖的 op 打包进同一个 bundle 并期待寄存器转发,就会产生静默错误结果。bundle 边界是写→读可见性的硬周期边界。

编译器侧的 Bundle 对象正好建模这样一个字。它不是通用指令列表——它是一条记录,每个槽类有一个带类型、可选的子字段。EncoderJf::EncodeBundleInternal0x1e86c7c0)按具体 C++ 类型读取这些子字段——platforms_deepsea::jellyfish::isa::ScalarInstructionVectorAluInstructionVectorExtendedInstructionVectorLoadInstructionVectorStoreInstructionVectorResultInstructionMiscInstruction——存在的子字段变成填充槽,不存在的子字段变成空槽。


每代 Bundle 宽度

bundle 字节宽度按 TpuVersion 固定,是该模型中最受 load 锚定的事实:每个按代划分的 codec 都以内联常量返回它。TPU 恰好有六个版本——tpu::TpuVersionToString0x20b3a480)索引一个 6 项指针表(off_22011BF0),并对任何版本 ≥ 6 调用 LogMessageFatal——其代号(kJellyfishkDragonfishkPufferfishkViperfishkGhostlitek6acc60406)都逐字出现在二进制的 .rodata 中。

TpuVersion代号公开名称Bundle 字节宽度来源
0JellyfishTPU v241JellyfishCodecMetadata::BundleSizeBytes @ 0x1ecf7460 return 41
1DragonfishTPU v341共享 Jellyfish codec 元数据(component 0)
2PufferfishTPU v451EncoderPfTensorCore::BundleSizeBytes @ 0x1d227740 return 51; PufferfishCodecMetadata::BundleSizeBytes @ 0x1ecf7ac0 return 51
3ViperfishTPU v564ViperfishCodecMetadata::BundleSizeBytes @ 0x1ee71320 return 64
4GhostliteTPU v6 lite64GhostliteCodecMetadata::BundleSizeBytes @ 0x1eeb7640 return 64
56acc60406TPU7x64已注册的 codec 元数据;与 v5+ 同属 64-B 类

TpuVersion 到其宽度的分派是两步间接,而不是 switch

c
// codec_metadata::BundleSizeBytes(TpuVersion v, TpuSequencerType t)  @ 0x1ecf7180
metadata = GetMetadataOrDie(v);            // 0x1ecf6f60 — abseil flat_hash_map lookup
return metadata->vtable[+16](t);           // virtual BundleSizeBytes(t) on the per-gen class
```text

`GetMetadataOrDie`(`0x1ecf6f60`)以一个进程全局的 `CodecMetadataRegistry` 为键源——它是一个 `absl::flat_hash_map<tpu::TpuVersion, CodecMetadata const*>`,通过 `StaticMapBase` 单例构建一次——如果版本从未注册,则以 "Codec metadata not registered for TpuVersion" 终止。每个注册的 `CodecMetadata` 都是按代划分的类(`JellyfishCodecMetadata`、`PufferfishCodecMetadata`、`ViperfishCodecMetadata`、`GhostliteCodecMetadata`),其 `BundleSizeBytes` 虚函数返回上面的内联常量。没有单独以 `6acc60406` 命名的元数据符号;`k6acc60406`(TPU7x)版本由一个复用 64 字节 v5+ 类形状的注册项服务。

> **怪癖 — 宽度以 `(version, sequencer-type)` 为键,第二个参数很重要。** `BundleSizeBytes` 接收一个 `TpuSequencerType`。对 TensorCore 序列器(component `0`),Jellyfish 返回 41;对 BarnaCore 序列器(component `1`),*同一个* `JellyfishCodecMetadata` 返回 **16**(`0x1ecf7460`:`if (t==1) return 16`),其他任何 component 都触发致命 "Unhandled component"。Pufferfish BarnaCore 子核心有自己的 32 字节 bundle(`EncoderPfBarnaCoreSequencer::BundleSizeBytes` @ `0x1d229220` `return 32`,`EncoderPfBarnaCoreChannel` @ `0x1d22bb00` `return 32`)。把“bundle 宽度”当作每芯片标量的重新实现会漏掉同一芯片拥有多个不同 bundle 宽度的序列器类型这一事实。

这些宽度是彼此不兼容的线缆格式。41 字节 Jellyfish bundle 即使做字节扩展,也无法作为 51 字节 Pufferfish bundle 读取:每代都有自己的 `Encoder<gen>` / `Decoder<gen>` 对和自己的槽偏移映射。宽度跨代增长,是因为槽数量增长(更多 vector-ALU lane、更多 vector-load 端口、Pufferfish 的专用 cmem-load 槽、6acc60406 的谓词槽),而不是因为字段被填充。

---

## HBM / DMA 块包装

bundle 宽度是*发射*宽度。bundle 存在 HBM 中并经 DMA 传输到 IMEM 时的宽度略大,而且 bundle 被分组成固定块。Jellyfish 明确展示了这两个事实。

`JellyfishCodecMetadata::BundleSizeBytesForHbm`(`0x1ecf74c0`)对 TensorCore 返回 **42**——比 41 字节发射 bundle 宽 1 字节——也就是说,每个存储的 bundle 带有 1 字节 chunk/metadata 前缀,片上序列器在发射前会剥离它。随后 `EncoderJf::GetBundleByteOffset`(`0x1e86db80`)按如下方式计算一个 bundle 在 DMA 块内的字节偏移:

```c
// EncoderJf::GetBundleByteOffset(bundle_index n)  @ 0x1e86db80
if (n < 0 || n >= bundles_per_chunk)
    return error("Requested bundle number N exceeds limit of "
                 "bundles-per-chunk (...)");          // encoder_jf.cc:3202
offset = (n / 3) * 128                                // 3 bundles per 128-B chunk
       + (n % 3) * (BundleSizeBytes() + 2);           // 41 + 2 = 43-B stride within chunk

两个常量由此浮现,且都与记录的模型匹配:每个 DMA 块 3 个 bundlen / 3 分组)和 128 字节块粒度BundleSizeBytes() + 2 步长(Jellyfish 为 43 字节)是块内的逐 bundle 槽位;三个这样的槽位(129 字节载荷)向上对齐到带块 header 的 128 字节 chunk。"exceeds limit of bundles-per-chunk" 断言字符串是二进制自身给该常量的名称。

发射 bundle (B)HBM bundle (B)Bundle / DMA 块来源
Jellyfish41423BundleSizeBytesForHbm @ 0x1ecf74c0; GetBundleByteOffset @ 0x1e86db80
Pufferfish51(codec-metadata ForHbm10EncoderPfTensorCore::BundleSizeBytesForDma @ 0x1d227760
Viperfish+64641每代 BundleSizeBytesForDma(vtable)

注意 — 每块 bundle 数按代不同,并随 bundle 变宽而缩小:41 字节 bundle 每个 128 字节块打包 3 个,而 64 字节 v5+ bundle 实际上自己填满一个块。硬编码“每块 3 个 bundle”的重新实现会错位每个 v4+ HBM 镜像。


槽分类

bundle 的槽横跨 TensorCore 的执行单元进行划分。编译器侧 Bundle 对象把该划分编码为每个槽类一个带类型的子指令;下面的名称是 EncodeBundleInternal 分派到的具体 C++ 类型,并锚定到 Jellyfish 编码器中反编译符号的使用。

槽类Bundle 子指令驱动首代槽页面
序列器 / 标量ScalarInstructionSPU + 控制流、循环计数器、sync;每个 bundle 一个JellyfishSPU, Sequencer
Vector ALU (VPU)VectorAluInstructionVPU lane;数量按代增长(Viperfish 上为四路)JellyfishVPU
EUP / 超越函数VectorExtendedInstruction扩展单元超越函数;也馈入 MXU(DecodeVectorExtendedToMxuNumJellyfishEUP, MXU
Vector loadVectorLoadInstruction内存加载端口;Viperfish 上为三路JellyfishMemory-Load
Vector storeVectorStoreInstruction内存存储端口JellyfishMemory-Store
Vector resultVectorResultInstructionMXU result-FIFO 读取(latch result → vreg)JellyfishResultFifo
MiscMiscInstructionmask/M-register、rotate-count、立即数设置辅助Jellyfishvcreate_mask, Immediate
Bundle headerHardwareBundleBits每 bundle 谓词 + 边界位JellyfishPredicate
cmem-loadTensorCoreCmemLoad第二 scratchpad 加载端口Pufferfishcmem_load
SparsitySparseCore sub-bundleSparseCore 控制ViperfishSparsity
Predicate专用谓词槽每 bundle 最多 4 个谓词定义 op6acc60406Predicate

MXU 并不是与其他槽同意义上的单个“槽”。矩阵工作通过 EUP / vector-extended 路径和一组 latch/matpush 操作(MxuAssignerMxuSequenceMatrixMultiplyAccumulateFunctorDistributeMatpushesEvenly)进入阵列,结果稍后通过 vector-result 槽读回。MXU 槽页面matprep/IAR/latch 页面覆盖了这个两阶段模型。

跨代槽数增长

bundle 变宽是因为槽群增长。字节宽度是以字节为锚的(见上文);槽数量根据每代槽类型和 bundle 宽度重建,最好把它们视为增长的形状,而不是 load 锚定的标量。

Bundle B槽数(约)更宽字带来的能力
Jellyfish41~9scalar + 1 VPU + EUP + load/store/result + misc + header
Pufferfish51~12添加 cmem_load 槽;加宽 vector-extended 路径
Viperfish64~16四路 VPU、三路 vector-load、ScalarSubBundle、SparseCore 标准化
Ghostlite64~16与 Viperfish 容量相同;opcode/encoding 差异,添加 vector-misc 槽
6acc6040664~17添加专用谓词槽(最多 4 个谓词定义 op)

注意 — 槽数(9 / 12 / 16 / 16 / 17)不是直接提取的常量。三种字节宽度(41 / 51 / 64)是来自 BundleSizeBytes 的内联常量返回,但槽数由每代槽类型名册(每个 Bundle 暴露哪些带类型子指令)加上 bundle 宽度推导而来。Jellyfish 评为 HIGH(其 EncodeBundleInternal 枚举了七个带类型槽类加三个 header 载体),后续代在每个按代 EncodeBundleInternal 被逐一追踪前评为 MEDIUM/LOW。每代 bundle 页面(JellyfishPufferfishViperfish)承载已确认映射。


空槽和 NOP 约定

未使用槽不是清零后被忽略的垃圾;它携带定义好的“什么也不做”编码,因而解码器可以往返它。机制是 bundle 谓词EncoderJf::EncodeBundleInternal 开头先把 kNeverExecute 谓词值盖印到 bundle header 字段中,然后再填充任何槽:

c
// EncoderJf::EncodeBundleInternal prologue  @ 0x1e86c7c0
nx = HardwareBundleBits::kNeverExecute & 0x1F;        // 5-bit predicate field
// write kNeverExecute into the bundle's predicate sub-fields (header bits)
bundle[+45] = (nx << 53) | (nx << 26);
bundle[+29] = kNeverExecute << 11;
bundle[+22] = 32 * nx;
// ... then fill present slots, leaving absent slots at kNeverExecute
```text

填充槽随后用真实的 `PredicateAndPolarity` 值覆盖其谓词字段;未触及的槽保持为 `kNeverExecute`,硬件将其读取为“这个槽的 op 在本周期被谓词关闭”。极端谓词值的运行时查询是极性辅助函数 `PredicateAndPolarity::IsAlwaysExecute`(`0x1d5a6ac0`)和 `IsNeverExecute`(`0x1d5a6b00`)。这是规范的空槽编码——完整逐槽图景见 [NOP / Unused-Slot Canonical Encoding](nop-canonical.md),包括为什么确定性空编码对位精确测试很重要。

> **陷阱 — 空并不意味着全零。** 因为默认谓词盖印是 `kNeverExecute`(一个非零 5 位模式,复制进多个 header 字段),新初始化的 bundle *不是*零缓冲区。重新实现若 memset bundle 为零,然后只填充活跃槽,会让非活跃槽停在 predicate-0(它可能表示“always execute”),把空槽变成活的垃圾 op。

---

## 编码 / 解码往返

bundle 有一对对称操作:编码器把 `Bundle` 序列化为字节缓冲区,解码器从字节重建 `Bundle`。两者都按代划分。

```text
LLO stream ──(bundle packer)──▶ Bundle (typed slots) ──▶ Encoder<gen>::EncodeBundleInternal ──▶ N-byte word

N-byte word ──▶ Decoder<gen>::Decode* ──▶ Bundle (typed slots) ──(disassembly / verification)─────┘
  • 编码。 Encoder<gen>::EncodeBundle(公开,接收 BundleFacade 代理)调用 EncodeBundleInternal,它按该代固定字节偏移写入每个存在的槽,并让不存在的槽保持 kNeverExecute。Jellyfish:EncoderJf::EncodeBundleInternal @ 0x1e86c7c0。较新的代通过 TpuCodec<gen>::EncodeBundle(例如 TpuCodecViperfish::EncodeBundle @ 0x1e8449a0)路由,再按 TpuSequencerType 分派到正确的 Encoder<gen> 实例。
  • 解码。 Decoder<gen>::Decode<Slot>Instruction 把字节窗口读回带类型子字段——例如 DecoderJf::DecodeVectorLoadInstruction0x1e84c4a0)、DecoderJf::DecodeVectorStoreInstruction0x1e84b600)、DecoderDf::DecodeVectorExtendedToMxuNum0x1e849680)。顶层 tpu::DecodeBundleBytes<DecoderPfTensorCore>0x1e843de0)接收 absl::Span<const uint8_t> 并返回一个 BundleFacade 代理。解码侧记录在 Decode-Side: JF / PFDecode-Side: VF / GXC 中。

对 BarnaCore-Pxc(Pufferfish)路径,逐槽位还会额外流经 LLVM-MC 239-bit recordinsertBits;对每个 TensorCore 和 V5+ opcode,MC record 都以全零构建,真实字节完全来自这个 proto-bundle Encoder<gen> 路径。MC record 与 bundle 字之间的关系——一个是逐指令,另一个是逐 bundle——详见 Record Format


交叉引用

  • Jellyfish 41-Byte Bundle — 41 字节 bundle 的完整已确认槽映射(EncodeBundleInternal)。
  • Record Format — 239-bit MC APInt record;逐指令 record 如何关联到逐 bundle 字。
  • MC-EmittergetBinaryCodeForInstr;BarnaCore-Pxc 路径如何让位流经 record,以及 V5+ 的零基默认值。
  • LLO Bundle Packing — 把独立 LLO op 分组到本页所序列化的带类型 Bundle 槽中的调度器。