Skip to content

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 桥接: EmitVectorCmemLoadEmitVectorLoadCommon<…CmemLoad>proto_utils::Place* SlotMap binder;字节地址由 CmemAddrScaled 缩放,并用 MakeCmemConstant(MemorySpace = kCmem = 4) 构造。
  • 为何仅 v4: 只有 PufferfishTarget::MemBanks(kCmem) 返回值(32);专用 slot 使 CMEM 成为可在一个 bundle 中与 VMEM 共发射的读取端口。
Encoderpxc::isa::TensorCoreCmemLoadEncoder::Encode(TensorCoreCmemLoad const&, Span<uint8>) @ 0x1ecf89a0
位原语BitCopy(void*, int dst_bit, void const*, int src_bit, int nbits) @ 0x1fa0a900dst_bit == 绝对 bundle 位
专用区域绝对位 103..118(16 位)— 与 vector_load @119..140 不重叠
Oneof tagproto +0x50: 0=empty, 5=Noop (pred=31), 6=CmemLoad (issue)
Submessage 默认值TensorCoreCmemLoad_globals_ @ 0x223fb410(外层),TensorCoreCmemLoad_CmemLoad_globals_ @ 0x223fb3c8(内层)
Emit 桥接PufferfishTensorCoreEmitter::EmitVectorCmemLoad @ 0x14120a40EmitVectorLoadCommon<…CmemLoad> @ 0x14120f40
每 bundle 一个的门控EmitVectorCmemLoadbundle_proto[+0x10] & 0x40 上拒绝(cmem_load has-bit 已设置);PopulatedSlotsInBundle @ 0x1d2ea840 构建 slot 集合
地址缩放LloRegionBuilder::CmemAddrScaled @ 0x1d539980(byte→word,按 CmemWordSizeBytes
常量地址LloAddress::MakeCmemConstant @ 0x1d60ba20LloAddress(MemorySpace=4=kCmem, off)
为何仅 v4PufferfishTarget::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::Encode0x1ecf89a0)编码,它用一次共享 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 的共享池中,而不在专用区域内。

c
// 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>&, …);                                       // @0x1c4a9420

Vs0 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::EmitVectorCmemLoad0x14120a40)到达该 slot,其签名为 (SregnoOrImm base, SregnoOrImm, ImmValue offset, optional<SregnoOrImm> sublane_mask)。emit 路径如下:

c
// 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 guard

MakeCmemConstant 构造一个 MemorySpace = 4 = kCmemLloAddress——字节精确确认(调用 constructor 时 esi = 4)。CmemAddrScaledCmemWordSizeBytes 执行 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。EmitVectorCmemLoad0x14120a40)的完整 callee 集合是 CurrentBundle0x140fea80)、PopulatedSlotsInBundle0x1d2ea840,每 bundle 一个的合法性检查)、Arena::DefaultConstruct<TensorCoreCmemLoad> / <…CmemLoad>PlaceStride0x1c4a9420)以及 EmitVectorLoadCommon<…CmemLoad>0x14120f40);而 EmitVectorLoadCommon 的 callee 集合正是 PlaceSublaneMask0x1c492080)、PlaceBaseAddressRegister0x1c492b60)、PlaceOffsetImmediate0x1c4937a0)外加 SregnoOrImm/ImmValue accessor——没有 DMA,没有 sparsity。附近有两个结构上彼此分离的东西:(1) sparsity 是 v5+ SparseCore ISA——每个 SparseCore*Dma{Encoder,Decoder} 符号都位于 asic_sw::deepsea::gxc::{gfc,glc}::isa 下的 SparseCoreScsCodecBase / SparseCoreTecCodecBase / SparseCoreTacCodecBase 内,而 v4 pxc::isa 命名空间根本没有 sparsity codec(它仅有的 “sparse” 符号是 pxc::profiler::BcFsmSparseReduce trace event,不是 ISA slot);(2) cmem_load 附近唯一的 Dma* 是 codec 级program-image chunker pxc::isa::TensorCoreCodecBase<…TensorCoreCmemLoadEncoder…>::Dma{Encode,Decode},其中 GetBundlesPerDmaChunk0x1d224f20)= 0xa(10 个 bundle),GetBytesPerDmaChunk0x1d224f40)= 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。JellyfishTargetViperfishTargetGhostliteTargetMemBanks(kCmem) 上都会 LogFatal——反编译出的 PufferfishTarget::MemBanksv4 = MS − 3; if (v4 >= 3) LogFatal("Unsupported memory space specified for MemBanks()"); return table[v4];,而其他目标只处理 kVmem (= 3)kSmem (= 5)
  • 只有 PufferfishTarget 覆盖了 LocalDmaBandwidth*Cmem virtual(1121 GB/s VMEM→CMEM,2339 GB/s CMEM→VMEM,50 ns 初始 DMA 延迟)。其他所有代号都继承 base zero,cost model 将其读作“未建模”,并以此阻止 MSA 将任何内容放入 CMEM。
  • 任何其他代号上都不存在 pxc 类似的 TensorCoreCmemLoad 类。Viperfish 有 EmitVectorCmemLoad emitter 符号,但会将其 lower 到常规 VectorLoad / VectorStore slot——不存在 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-bit 0x040;submessage 指针位于 proto +0x48),以及 direct-BitCopy 绝对位模型。
  • Memory-Load Slot — 常规 VMEM vector_load slot(绝对位 119..140),其寻址 submessage 被 cmem_load 镜像,以及共享的 EmitVectorLoadCommon placement routine。
  • CMEM Pool — constant-memory 池、ProgramMemoryAllocator(MS=kCmem) / BestFitAllocator 路径、banking、逐代可用性,以及为何只有 Pufferfish 建模 CMEM。