cmem_load Slot(Pufferfish)
地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so。其他版本会不同。
摘要
cmem_load slot 是 51-byte TensorCore bundle 中唯一仅属于 Pufferfish 的 slot:一个专用的 16 位区域(绝对位 103..118),用于从 constant-memory(CMEM)池发起读取,并可在同一个 bundle 周期中与常规 VMEM vector_load slot 共存。它只存在于 Pufferfish(TPU v4),其他地方都没有,因为 Pufferfish 是唯一真正建模 CMEM 的生产代号——它是唯一一个 MemBanks(kCmem) 返回真实 bank 数(32)的 Target,也是唯一一个具有非零 CMEM DMA 带宽的目标。在 Jellyfish / Viperfish / Ghostlite / Ghostfish 上,MemBanks(kCmem) 是 LogFatal,并且这些 codec 中不存在任何 *::isa::TensorCoreCmemLoad 类。CMEM 是 v4 特性,因此读取它的 bundle slot 也是 v4 slot。
从功能上说,该 slot 是第二个独立的内存读取端口。它的寻址字段——sublane mask、base-address selector、offset、stride——与常规 vector_load slot 完全镜像;唯一差别是地址目标是 MemorySpace = kCmem (= 4),并按 CmemWordSizeBytes 而非 VmemWordSizeBytes 缩放。由于 cmem_load 专用区域(103..118)与 vector_load 区域(119..140)互不重叠,一个 bundle 可以同时发出一次 CMEM 读取和一次 VMEM 读取——这就是 v4 双操作数抓取 datapath,能以单端口两倍速率供给 MXU operand pipe。CMEM 以及此 slot 的存在原因正在于此。
本页将该 slot 作为重新实现目标来记录:它在线路格式中的字段图(绝对位精度,从 TensorCoreCmemLoadEncoder::Encode 按字节精确解码)、三路 oneof tag(empty / Noop / CmemLoad)和 kNeverExecute = 31 idle 编码、它从 bundle 共享 Y-register 和 immediate operand pool 中取得的资源,以及 emit → proto → SlotMap → address-scale 桥接如何把一次 EmitVectorCmemLoad 调用变成 bundle 位和 CMEM 字节地址。CMEM 池、分配器、banking 和逐代可用性由 CMEM Pool 页面负责;本页负责 bundle slot。
对重新实现而言,契约如下:
- 专用区域(103..118): SublaneMask @103/3、BaseAddress @106/2、Offset @108/2、Stride @110/3、has-bit @113/1、Predication @114/5——每个都由一次
BitCopy(buf, abs_bit, &field, 0, width)写入。 - 共享池取用: 三个 5 位 Y-register selector(@251/246/241 = Vs2/Vs1/Vs0)以及最多四个 16 位 immediate(@304/288/272/256),从其他所有 slot 共用的同一池中分配,并由 submessage has-bit 门控。
- proto+0x50 处的 oneof tag:
0→ empty(仅 predication),5→ Noop(predication 强制为 31 =kNeverExecute),6→ CmemLoad(issue 变体,写入所有寻址 + operand 字段)。 - CMEM-address 桥接:
EmitVectorCmemLoad→EmitVectorLoadCommon<…CmemLoad>→proto_utils::Place*SlotMap binder;字节地址由CmemAddrScaled缩放,并用MakeCmemConstant(MemorySpace = kCmem = 4)构造。 - 为何仅 v4: 只有
PufferfishTarget::MemBanks(kCmem)返回值(32);专用 slot 使 CMEM 成为可在一个 bundle 中与 VMEM 共发射的读取端口。
| Encoder | pxc::isa::TensorCoreCmemLoadEncoder::Encode(TensorCoreCmemLoad const&, Span<uint8>) @ 0x1ecf89a0 |
| 位原语 | BitCopy(void*, int dst_bit, void const*, int src_bit, int nbits) @ 0x1fa0a900 — dst_bit == 绝对 bundle 位 |
| 专用区域 | 绝对位 103..118(16 位)— 与 vector_load @119..140 不重叠 |
| Oneof tag | proto +0x50: 0=empty, 5=Noop (pred=31), 6=CmemLoad (issue) |
| Submessage 默认值 | TensorCoreCmemLoad_globals_ @ 0x223fb410(外层),TensorCoreCmemLoad_CmemLoad_globals_ @ 0x223fb3c8(内层) |
| Emit 桥接 | PufferfishTensorCoreEmitter::EmitVectorCmemLoad @ 0x14120a40 → EmitVectorLoadCommon<…CmemLoad> @ 0x14120f40 |
| 每 bundle 一个的门控 | EmitVectorCmemLoad 在 bundle_proto[+0x10] & 0x40 上拒绝(cmem_load has-bit 已设置);PopulatedSlotsInBundle @ 0x1d2ea840 构建 slot 集合 |
| 地址缩放 | LloRegionBuilder::CmemAddrScaled @ 0x1d539980(byte→word,按 CmemWordSizeBytes) |
| 常量地址 | LloAddress::MakeCmemConstant @ 0x1d60ba20 → LloAddress(MemorySpace=4=kCmem, off) |
| 为何仅 v4 | PufferfishTarget::MemBanks(kCmem) @ 0x1d493900 = 32;JF/VF/GL/GF LogFatal |
| Has-bit(bundle dispatch) | TensorCoreBundle proto +0x10 处 12 位 slot has-mask 中的 0x040(第 7 个 slot);slot submessage 指针位于 proto +0x48 |
线路格式字段图
该 slot 由 TensorCoreCmemLoadEncoder::Encode(0x1ecf89a0)编码,它用一次共享 bit-packing 原语 BitCopy(buf, abs_bit, &field, 0, width) 写入每个字段。与整个 Pufferfish bundle 一样,字段的绝对 bundle 位就是字面的 dst_bit 参数——没有需要反推的移位算术,也没有要减掉的 header offset(见 Pufferfish 51B Bundle §The Direct-BitCopy Model)。专用区域为十六位,即 103..118;operand register 和 immediate 位于 bundle 的共享池中,而不在专用区域内。
// pxc::isa::TensorCoreCmemLoadEncoder::Encode(proto, buf) @ 0x1ecf89a0 (decoded byte-exactly)
pred = proto[+0x20];
BitCopy(buf, 114, &pred, 0, 5); // Predication @114/5 — written first, unconditionally
tag = proto[+0x50]; // oneof discriminator
if (tag == 0) return OK; // empty slot: only predication written
if (tag == 5) { pred = 31; BitCopy(buf, 114, &pred, 0, 5); return OK; } // Noop = kNeverExecute
// tag == 6 (CmemLoad, the issue variant):
one = 1; BitCopy(buf, 113, &one, 0, 1); // has-bit (slot-present marker) = const 1
inner = proto[+0x48]; // the CmemLoad submessage (or _globals_ default if clear)
if (inner.has[0x10] & 0x01) BitCopy(buf, 110, &inner.Stride, 0, 3); // Stride
if (inner.has[0x10] & 0x02) BitCopy(buf, 108, &inner.Offset, 0, 2); // Offset
if (inner.has[0x10] & 0x04) BitCopy(buf, 106, &inner.BaseAddress, 0, 2); // BaseAddress
if (inner.has[0x10] & 0x08) BitCopy(buf, 103, &inner.SublaneMask, 0, 3); // SublaneMask
// ----- shared operand pool (co-allocated with the other slots) -----
if (inner.has[0x10] & 0x10) BitCopy(buf, 251, &inner.Vs2, 0, 5); // Vs2 register selector
if (inner.has[0x10] & 0x20) BitCopy(buf, 246, &inner.Vs1, 0, 5); // Vs1
if (inner.has[0x10] & 0x40) BitCopy(buf, 241, &inner.Vs0, 0, 5); // Vs0 (dest VREG / base)
if (inner.has[0x10] & 0x80) BitCopy(buf, 304, &inner.Imm, 0, 16); // shared immediate word
if (inner.has[0x11] & 0x01) BitCopy(buf, 288, &inner.Imm, 0, 16); // shared immediate word
if (inner.has[0x11] & 0x02) BitCopy(buf, 272, &inner.Imm, 0, 16); // shared immediate word
if (inner.has[0x11] & 0x04) BitCopy(buf, 256, &inner.Imm, 0, 16); // shared immediate word
```text
| 字段 | 绝对位 | 宽度 | proto off | has-bit | 角色 |
|---|---:|---:|---|---|---|
| SublaneMask | 103 | 3 | `+0x24` | `[0x10]&0x08` | sublane predicate mask |
| BaseAddress | 106 | 2 | `+0x20` | `[0x10]&0x04` | `BaseAddressEncoding`: ZERO / vs0 / vs1 / vs2 |
| Offset | 108 | 2 | `+0x1c` | `[0x10]&0x02` | `OffsetEncoding` mode selector |
| Stride | 110 | 3 | `+0x18` | `[0x10]&0x01` | stride / feature-length mode |
| has-bit | 113 | 1 | (const 1) | — | slot-present marker |
| Predication | 114 | 5 | `+0x20`(outer) | — | `0..14` pred reg / `15` always / `31` never(默认 `0x1f`) |
| Vs0 | 241 | 5 | `+0x30` | `[0x10]&0x40` | Y-register selector — dest VREG / base(共享池) |
| Vs1 | 246 | 5 | `+0x2c` | `[0x10]&0x20` | Y-register selector(共享池) |
| Vs2 | 251 | 5 | `+0x28` | `[0x10]&0x10` | Y-register selector(共享池) |
| Imm | 304 | 16 | `+0x34` | `[0x10]&0x80` | 共享 immediate word(offset / index) |
| Imm | 288 | 16 | `+0x38` | `[0x11]&0x01` | 共享 immediate word |
| Imm | 272 | 16 | `+0x3c` | `[0x11]&0x02` | 共享 immediate word |
| Imm | 256 | 16 | `+0x40` | `[0x11]&0x04` | 共享 immediate word |
decode 侧 accessor 独立确认了宽度:`TensorCoreCmemLoad{Field}::GetConcatenatedValue`(`0x1ecf8820..0x1ecf8980`)读取 proto 内部结构时使用 `shr`+`and` mask,其 pop-count 与线路格式中的 `BitCopy` 宽度完全一致——5 位 predication、3 位 sublane/stride、2 位 base/offset、5 位 Y-reg、16 位 immediate。
> **注意 —** [Pufferfish 51B Bundle](bundle-pf-51b.md) 页面列出了同一 @103..118 区域(sublane-mask @103/3、stride @110/3、offset @108/2、base @106/2、has @113/1、pred @114/5),两个页面在位级完全一致。两个页面中的逐字段角色,都来自专用 `TensorCoreCmemLoad{Field}` accessor 符号并以相同名称命名;本页额外解析了共享池中的 Y-register 和 immediate 位置。
---
## Oneof Tag 与 Idle 编码
`TensorCoreCmemLoad` submessage 在 proto `+0x50` 处携带 oneof discriminator,encoder 首先读取它,用以选择三种行为之一:
| Tag | 变体 | Encoder 行为 |
|---:|---|---|
| `0` | empty | 仅写入 Predication @114;encoder 返回 |
| `5` | Noop | Predication 强制为 `31`(`kNeverExecute`);返回 |
| `6` | CmemLoad | issue 变体——设置 has-bit @113,写入所有寻址 + operand 字段 |
因此 idle slot 携带 Predication = `31`(`kNeverExecute`),这与 Pufferfish bundle 其他部分使用的 empty-slot 值相同。当 bundle codec 在[十二 slot dispatch](bundle-pf-51b.md) 中发现该 slot 的 has-bit 未置位时,它会替换为 `TensorCoreCmemLoad_globals_` 默认实例(`0x223fb410`);在 issue 变体内部,逐字段 has-bit 未置位的字段会采用 `TensorCoreCmemLoad_CmemLoad_globals_` 默认值(`0x223fb3c8`)。这两层默认值——缺失 slot 一层,present slot 内部缺失字段一层——让 encoder 能在部分填充的 submessage 上无分支地运行。
> **陷阱 — predication 0 是活跃操作,不是“skip”。** bundle buffer 在编码前会被 `memset` 为零,但 predicate `0` 是有效的 predicate-register 引用,不是 empty marker。如果重新实现把未用的 cmem_load slot 留在清零默认值,会发出一个由 predicate register 0 门控的 CMEM 读取。empty 值始终是 tag-0/tag-5 写入的显式 `31`(或 `_globals_` 默认值中盖上的 31),从来不是清零 buffer。
---
## 共享 Operand Pool
cmem_load slot 在专用区域中拥有自己的寻址字段(sublane / base / offset / stride),但它的**寄存器和 immediate operand 来自 bundle 的共享池**——也就是两个 VALU lane 和另外两个 memory slot 共同取用的同三个 5 位 Y-register selector(绝对位 241/246/251)以及六个 16 位 immediate word(绝对位 256/272/288/304/320/338)。encoder 会写入池中的 slot(Vs2 @251、Vs1 @246、Vs0 @241、immediate @304/288/272/256),但并不决定每个 operand 使用*哪个*池条目;这个绑定更早发生在 proto-build 层,通过以 `TensorCoreCmemLoad_CmemLoad` 为参数的 `xla::pufferfish::proto_utils::Place*` 模板函数完成:
```c
// proto_utils binders for cmem_load (proto-build layer) — same SlotMap pool as VALU / vector_load / vector_store
PlaceSublaneMask<CmemLoad>(optional<SregnoOrImm>, SlotMap<ulong,3>&, SlotMap<ImmValue,6>&, *); // @0x1c492080
PlaceBaseAddressRegister<CmemLoad>(uint, SlotMap<ulong,3>&, *); // @0x1c492b60
PlaceVsRegister<BaseAddress,CmemLoad>(uint, SlotMap<ulong,3>&, *); // @0x1c492be0
PlaceOffsetImmediate<CmemLoad>(ImmValue, SlotMap<ImmValue,6>&, *); // @0x1c4937a0
PlaceStride<CmemLoad>(SregnoOrImm, SlotMap<ulong,3>&, …); // @0x1c4a9420Vs0 selector(绝对位 241)接收加载出的数据——即目标 VREG——而 BaseAddress(2 位)从同一个 Y-register 池中选择 ZERO / vs0 / vs1 / vs2 作为地址基址,offset/index immediate 则占用某个 16 位共享 slot。这与 bundle 其他部分共享的 SlotMap<ulong,3>(三个 Y-register selector)和 SlotMap<ImmValue,6>(六个 immediate)是同一个。
陷阱 — 限制共发射的是共享池,而不是专用区域。 cmem_load 专用区域(103..118)与 vector_load(119..140)互不重叠,因此这两个 slot 在线路格式上可以自由共存。但二者的 register 和 immediate 都来自同一个三 Y-register / 六 immediate 池。一个同时发出 cmem_load、vector_load 和两个 VALU lane 的 bundle,所有这些操作合计最多只能命名三个不同 Y-register 和六个不同 immediate。SlotMap allocator 在 proto-build 时强制执行这一点,并在
Encode运行前拒绝资源超订的 bundle。
Emit → Proto → SlotMap → Address 桥接
CMEM load 通过 PufferfishTensorCoreEmitter::EmitVectorCmemLoad(0x14120a40)到达该 slot,其签名为 (SregnoOrImm base, SregnoOrImm, ImmValue offset, optional<SregnoOrImm> sublane_mask)。emit 路径如下:
// PufferfishTensorCoreEmitter::EmitVectorCmemLoad @ 0x14120a40 (decoded)
bundle = CurrentBundle(); // @0x140fea80 — the bundle under construction
slots = PopulatedSlotsInBundle(bundle); // @0x1d2ea840 — one-cmem-load-per-bundle legality
// (rejects a second cmem_load in the same bundle — the "bundle has cmem load instruction already" diagnostic)
sub = DefaultConstruct<TensorCoreCmemLoad>(); // build the slot submessage; oneof tag (proto+0x50) := 6
// predicate value chosen by querying the emitter's Predication state, then stamped into sub[+0x20]:
// is_always_execute (0x1d5b22e0) -> 15 ; is_never_execute (0x1d5b2300) -> 31 ; else 16*reg | sub-index
EmitVectorLoadCommon<TensorCoreCmemLoad_CmemLoad>(sub, base, offset, sublane_mask); // @0x14120f40
```text
`EmitVectorLoadCommon<…CmemLoad>`(`0x14120f40`)是共享字段放置例程——与常规 `vector_load` slot 使用的是*同一个*例程,只是模板参数为 CmemLoad submessage——它会调用上面的 `Place*` SlotMap binder。该 slot 的寻址 submessage 与 vector_load 形状相同;唯一分歧是地址空间。
CMEM 字节地址本身是单独生成的,并以 offset immediate 加 base Y-register 的形式进入 bundle:
```c
// LloAddress::MakeCmemConstant(long off) @ 0x1d60ba20 (decoded)
return LloAddress(/*MemorySpace=*/4 /*=kCmem*/, off); // calls LloAddress(MS, long) with esi = 0x4
// LloRegionBuilder::CmemAddrScaled(LloValue* base, LloValue* idx, long k) @ 0x1d539980
return AddrScaled(base, idx, k, /*Granule=*/CmemWordSizeBytes); // @0x1d538880, with a kCmem failure guardMakeCmemConstant 构造一个 MemorySpace = 4 = kCmem 的 LloAddress——字节精确确认(调用 constructor 时 esi = 4)。CmemAddrScaled 按 CmemWordSizeBytes 执行 byte→word 缩放,因此 bundle 携带的是 word offset(byte→word 转换发生在 bundle packer 中,而不是 issue 时)。缩放后的 word offset 变成 Offset immediate(通过 PlaceOffsetImmediate),CMEM base SREG(来自 llo.saddr.cmem / ScalarAddressCmem)变成 BaseAddress Y-register(通过 PlaceBaseAddressRegister)。不存在独立的 “CmemAllocator”——地址推导共享 CMEM Pool 页面描述的 ProgramMemoryAllocator(MS = kCmem) / BestFitAllocator 路径。
注意 — 不存在 cmem_load → sparsity-DMA 边;二者从不共现。 cmem_load emit 链只调用 SlotMap 字段 binder,从不调用 DMA-descriptor 或 sparsity emitter。
EmitVectorCmemLoad(0x14120a40)的完整 callee 集合是CurrentBundle(0x140fea80)、PopulatedSlotsInBundle(0x1d2ea840,每 bundle 一个的合法性检查)、Arena::DefaultConstruct<TensorCoreCmemLoad>/<…CmemLoad>、PlaceStride(0x1c4a9420)以及EmitVectorLoadCommon<…CmemLoad>(0x14120f40);而EmitVectorLoadCommon的 callee 集合正是PlaceSublaneMask(0x1c492080)、PlaceBaseAddressRegister(0x1c492b60)、PlaceOffsetImmediate(0x1c4937a0)外加SregnoOrImm/ImmValueaccessor——没有 DMA,没有 sparsity。附近有两个结构上彼此分离的东西:(1) sparsity 是 v5+ SparseCore ISA——每个SparseCore*Dma{Encoder,Decoder}符号都位于asic_sw::deepsea::gxc::{gfc,glc}::isa下的SparseCoreScsCodecBase/SparseCoreTecCodecBase/SparseCoreTacCodecBase内,而 v4pxc::isa命名空间根本没有 sparsity codec(它仅有的 “sparse” 符号是pxc::profiler::BcFsmSparseReducetrace event,不是 ISA slot);(2) cmem_load 附近唯一的Dma*是 codec 级program-image chunkerpxc::isa::TensorCoreCodecBase<…TensorCoreCmemLoadEncoder…>::Dma{Encode,Decode},其中GetBundlesPerDmaChunk(0x1d224f20)=0xa(10 个 bundle),GetBytesPerDmaChunk(0x1d224f40)=0x200(512 字节)——也就是十个 51-byte bundle 打包成一个 512-byte instruction-stream DMA chunk。TensorCoreCmemLoadEncoder只是 codec 模板参数化的约 14 个 slot encoder 之一;chunker 流式传输整个 bundle image,且与 slot 无关。CMEM(仅 v4 的读取端口)和 SparseCore DMA(v5+)从不共享目标、命名空间或代码路径。
为何该 Slot 仅属于 Pufferfish
cmem_load slot 只存在于 Pufferfish,而不存在于任何其他代号,因为 Pufferfish 是唯一建模 CMEM 层级的 Target:
PufferfishTarget::MemBanks(kCmem)(0x1d493900)返回32——它按(MemorySpace − 3)索引0xb5305c8处的{16, 32, 8}rodata 表,因此kCmem (= 4)→ 索引 1 → 32 banks。JellyfishTarget、ViperfishTarget和GhostliteTarget在MemBanks(kCmem)上都会LogFatal——反编译出的PufferfishTarget::MemBanks是v4 = MS − 3; if (v4 >= 3) LogFatal("Unsupported memory space specified for MemBanks()"); return table[v4];,而其他目标只处理kVmem (= 3)和kSmem (= 5)。- 只有
PufferfishTarget覆盖了LocalDmaBandwidth*Cmemvirtual(1121 GB/s VMEM→CMEM,2339 GB/s CMEM→VMEM,50 ns 初始 DMA 延迟)。其他所有代号都继承 base zero,cost model 将其读作“未建模”,并以此阻止 MSA 将任何内容放入 CMEM。 - 任何其他代号上都不存在
pxc类似的TensorCoreCmemLoad类。Viperfish 有EmitVectorCmemLoademitter 符号,但会将其 lower 到常规VectorLoad/VectorStoreslot——不存在vxc::vfc::isa::TensorCoreCmemLoad。Ghostlite(gxc::glc)和 Ghostfish(gxc::gfc)既没有该 slot,也没有 emitter。
由于专用 cmem_load 区域(103..118)与 vector_load 区域(119..140)互不重叠,一个 Pufferfish bundle 可以在同一个周期中共发射一次 CMEM 读取和一次 VMEM 读取。对 MXU pipe 而言,这会让 operand-fetch 带宽翻倍;这就是 CMEM 存在于 v4 的原因,也是专用 slot 在 bundle 层面的体现。
尚未钉住的内容
- cmem_load 寻址模式子集。 该 slot 复用
vector_load寻址 submessage;它是否支持全部四种 vector_load 寻址模式(Vmem / Shuffled / Indexed-Iar0 / Indexed-Iar1),还是仅支持 CMEM 受限子集,本页没有逐分支分离。 - immediate-slot 分区。 encoder 为 cmem_load 写入六个共享 immediate slot 中的四个(@256/272/288/304);它是否可能占用 @320/338 slot,由 SlotMap 分配层(对
SlotMap<ImmValue,6>的PlaceOffsetImmediate)决定,在 encoder 中不可见。 - decode 侧 gap 行为。 encoder 的字段位置与
GetConcatenatedValue宽度 accessor 一致;TensorCoreCmemLoadDecoder::Decode函数体(线路格式→proto 的逆过程)未在此反汇编。 - 字面的 CMEM word granule。
Cmq16BIndirectStateFactory命名指向 16 bytes;精确值由嵌入的chip_parts.binarypb提供,并在启动时读入Target +0x510。
交叉引用
- Pufferfish 51B Bundle — 该 slot 所在的完整 51-byte slot map(区域 103..118)、十二 slot has-bit dispatch(proto
+0x10处 slot has-mask 中的 has-bit0x040;submessage 指针位于 proto+0x48),以及 direct-BitCopy绝对位模型。 - Memory-Load Slot — 常规 VMEM
vector_loadslot(绝对位 119..140),其寻址 submessage 被 cmem_load 镜像,以及共享的EmitVectorLoadCommonplacement routine。 - CMEM Pool — constant-memory 池、
ProgramMemoryAllocator(MS=kCmem)/BestFitAllocator路径、banking、逐代可用性,以及为何只有 Pufferfish 建模 CMEM。