SparseCore 硬件架构
本页中的每个地址、偏移和值,均从
libtpu-0.0.40-cp314wheel(BuildID md589edbbe81c5b328a958fe628a9f2207d)内的libtpu.so逐字节读取而来。其他版本会有所不同。
摘要
本页是 SparseCore 后端所面向的硬件模型 — 在调度任何 SparseCore(SC)程序或放置任何片上缓冲区之前,重实现者必须重建的几何形状和内存布局。该模型由三项内容定义,而编译器会从同一个运行时对象读取这三项:停放在 Target+0x948 的 SparseCoreTarget 子描述符。它固定了一颗芯片有多少 tile-execute core、每个 core 有多宽、片上 SRAM 各层级有多大,以及硬件保留区域位于何处。该结构的逐字节字段映射由 SparseCoreTarget (Target+0x948) 维护;本页将其综合为架构 — engine→core 布局、四层内存模型,以及把几何信息转为已放置缓冲区的 bump/stack tile 分配算法。
最接近的心智模型是带 shared memory 的 GPU SM,但方向相反。SM 将连续 tile 流入大型寄存器文件,而 SC 将间接(由索引驱动)的 embedding row 流入很小的每 tile SRAM(TILE_SPMEM),再用宽向量引擎(TEC)做归约。计算结构被分为三个 VLIW 子引擎 — SCS(标量控制)、TAC(tile-fetch DMA issuer,仅存在于 Viperfish/Ghostlite)和 TEC(宽向量)— 它们通过 sync flag 和共享 SRAM 协同。每芯片 TEC core 数(SparseCoreTiles)、每个 core 的向量宽度(SparseCoreLaneCount)以及各层容量不是代码常量:SparseCoreTarget::Init 从每 codename 的 TpuCoreParts(最终来自 gzip 压缩的 chip_parts.binarypb)读取它们。唯一的代码常量是字面量 HBM 4b-word = 4、SCS group count = 2,以及 SPMEM stripe 粒度 = 32 bytes。
本页组织为四个单元:SparseCoreTarget 映射(几何信息的事实来源和 accessor gate)、engine→core 布局(SCS/TAC/TEC 如何映射到物理 core 和 tile)、片上内存模型(四个地址空间、字大小,以及 SPMEM↔TILE_SPMEM 拆分),以及 tile 分配算法(带 scope stack 的每地址空间 bump allocator、tile sub-frame carve-out、embedding row sharding、minibatch-fit 不等式和 HBM spill)。每个单元都会以每代差异收尾,其中只有一个真正的代码分支。
对重实现而言,契约是:
- 几何信息通过
SparseCoreTarget(Target+0x948)数据驱动。 重实现者必须在任何分配运行之前,从芯片的 core-parts 填充SparseCoreTiles([0x948]+0x90)、SparseCoreLaneCount([0x948]+0x94)、stream_granule_size([0x948]+0xA4)以及每层 capacity/word block([0x948]+0x10..+0x54)。四层内存大小从这里读取,而不是硬编码。 - 存在性 gate 是运行时拓扑测试。 每个
Target::SparseCore*accessor 都会 dispatch 虚函数SupportsSparseCore(vtable slot+0x260,0x1D48FD40),并在TpuTopology[+0x98]的 SC 数为零时LOG(FATAL)输出"SparseCore is not supported by this target"。不存在编译期 class 测试。 - 片上分配是带 scope stack 的 bump allocator — 绝不是 best-fit。
AllocationAssignmentPass遍历memref.allocaop,并按地址空间以 word 为单位 bump-reserve;tile-local 缓冲区会在 vector-lane-stripe 边界,从 SPMEM bump 指针中 carve 出一个专用 sub-frame,并在 pop 时回收。Deallocate是 no-op。 - allocator 中只有一个每代代码分支。 它是最后一个 TILE_SPMEM entry 中 circular-buffer 的硬件 bug 防护,针对 DeepseaVersion 3(Viperfish)和 4(Ghostlite)触发。其他所有内容 — 容量、word 宽度、tile 数、TAC 是否存在 — 都是数据驱动。
| 几何来源 | Target+0x948 处的 std::unique_ptr<SparseCoreTarget>;由 SparseCoreTarget::Init(0x1D612B20)从 SPARSE_CORE TpuCoreParts 构建 |
| 存在性 gate | Target::SupportsSparseCore(0x1D48FD40,vtable slot +0x260)— 读取 TpuTopology[+0x98] > 0 |
| 每芯片 tile 数 | SparseCoreTiles = [0x948]+0x90 = TpuCoreParts::SequencerCount(seq-type 5 = TEC)(v7x 6acc60406 = 16) |
| 每 tile 向量宽度 | SparseCoreLaneCount = [0x948]+0x94 = SequencerParts(5).vector_isa().lane_count(v7x = 16) |
| 引擎 | SCS(标量,32 B bundle) · TAC(tile-fetch DMA,64 B;仅 VF/GL) · TEC(向量,64 B;一个 TEC = 一个 tile) |
| 片上 SRAM 层级 | HBM(GB) · SPMEM(MB,芯片范围) · TILE_SPMEM(KB,每 tile) · TIMEM(每 tile 指令);SMEM/SFLAG 按 scope |
| Allocator | AllocationAssignmentPass(0x134D8240)— 每地址空间 bump + scope stack;Deallocate 被省略 |
| SPMEM stripe | 32 bytes(SparseCoreSpmemStripeGranularityBytes = 0x1D499440 → optional<32>) |
| 分配对齐 | SpmemAlignment = (SparseCoreTiles × lane_count) / 4 words(0x13DC5500) |
SparseCoreTarget 映射(Target+0x948)
目的
xla::jellyfish::Target 是 XLA TPU 后端在每个 codegen 和 cost 决策中查询的每代硬件描述符。它的 SparseCore 部分并非内联 — 它是在 Target+0x948 处单独分配的 std::unique_ptr<SparseCoreTarget>,由 SparseCoreTarget::Init(0x1D612B20)构建一次,并由 Target::Init 安装。该子对象是 SC 几何信息的单一事实来源:tile 数、lane 宽度、各层 SRAM 容量与 word 宽度、embedding param-region 基址、保留区域窗口,以及每代 capability bit。编译器从不硬编码每代大小;它从这里读取。
注意 — 完整的逐字节字段映射(每个
+0xNN偏移、类型和 Init store 位置)位于 SparseCoreTarget (Target+0x948)。本页只记录架构相关字段,以及 allocator 和引擎布局如何消费它们。对这里未复述的任何偏移,应以该页为准。
入口点
Target::Init ── builds + installs the sub-object
└─ SparseCoreTarget::Init (0x1D612B20) ── populate geometry from SPARSE_CORE TpuCoreParts
├─ TpuCoreParts::SequencerCount(cp, 5=TEC) ── SparseCoreTiles → [0x948]+0x90
├─ SequencerParts(5).vector_isa().lane_count── SparseCoreLaneCount → [0x948]+0x94
└─ MemoryParts(TILESPMEM/SPMEM/SMEM/SFLAG) ── per-tier capacity + word-size block [0x948]+0x10..+0x54
Target::SparseCore* (accessor surface) ── each gates on vtable[+0x260] then loads [0x948]+field
└─ Target::SupportsSparseCore (0x1D48FD40) ── TpuTopology[+0x98] > 0 (the presence gate)
```text
### 算法
`SparseCoreTarget::Init`(`0x1D612B20`)— 几何块,已对照反编译逐字节确认(十进制偏移来自反编译器;十六进制在注释中):
```c
function SparseCoreTarget_Init(sc, core_type, topology, target, core_parts): // 0x1D612B20
sc[88] = 4; // +0x58 HBM 4b-word size (literal)
sc[116] = 2; // +0x74 SCS sequencer/group count (literal)
sc[144] = TpuCoreParts.SequencerCount(core_parts, 5); // +0x90 SparseCoreTiles (seq type 5 = tile-exec)
vi = TpuSequencerParts.vector_isa(core_parts.SequencerParts(5));
if vi.has_vector_isa == 0: FATAL; // tile-exec sequencer must carry a VectorIsa
sc[148] = vi.lane_count; // +0x94 SparseCoreLaneCount
sc[152] = vi.lane_count << 2; // +0x98 SC lane bytes
if core_parts[488 /*+0x1E8 SparseCore submsg present*/]:
sc[160] = core_parts[480]; // +0xA0 tile_hbm_bandwidth_bytes_per_cycle (+0x1E0)
sc[164] = core_parts[484]; // +0xA4 stream_granule_size (+0x1E4)
// per-tier capacity/word block (+0x10..+0x54) populated from MemoryParts(type) above;
// barrier sync-flag base/count (+0x1D0/+0x1D4) from a SpecialPurposeSyncFlags sub-object.特殊点 — 有两套
TpuSequencerType编号;Init使用 codec-template 那套。 几何信息以 codec-template enum{SCS=3, TAC=4, TEC=5}中的TpuSequencerType值 5 = TEC 为键。这正是TpuCoreParts::SequencerCount/SequencerParts(0x20B2AA20/0x20B2AA60)实际采用的索引空间 — 两者都会对 6 槽 presence mask 做bittest64,并在index >= 6时ud1trap,因此有效范围是0..5,slot 5 是 tile-execute 池。Init读取SequencerCount(5)/SequencerParts(5)来确定 tile 和 lane 大小,而chip_partsproto 将该 16-instance 池标记为 tile-execute geometry。TpuSequencerTypeToString渲染的 C++TpuSequencerTypeenum 使用这同一套 codec-template 编号(off_22010DE0[3]="SparseCoreSequencer",[5]="…TileExecuteCoreSequencer")。另一套编号是 protobuf enumTpuSequencerTypeProto,它保留INVALID=0,因此把同样的引擎编号为{SCS=4, TAC=5, TEC=6}— 比 C++ enum 大一。chip_partsproto 将字段存为TpuSequencerTypeProto,但TpuSequencerParts::FromProto(0x20b30700)会先通过TpuSequencerTypeFromProto(减一),之后才索引 core-parts,因此内存中的TpuCoreParts以 codec-template{3,4,5}enum 为键。重实现者必须用{3,4,5}(即Init使用的那套)索引TpuCoreParts;喂入原始 proto{4,5,6}ordinal 会读错池。完整 off-by-one 对齐见 getSequencerType。
Accessor Surface 和存在性 Gate
每个 Target::SparseCore* accessor 都遵循同一种形状:dispatch +0x260 虚拟 gate,若为 false 则 LOG(FATAL),然后从 [0x948]+field 加载一个标量。反编译器将 Target+0x948 渲染为 *((_QWORD*)this + 297)(297 × 8 = 0x948)。SparseCoreTiles(0xFAAFA40)的代表性函数体:
function Target_SparseCoreTiles(this): // 0xFAAFA40
if !vtable[+0x260](this): // SupportsSparseCore
LOG(FATAL) << "SparseCore is not supported by this target"; // target.h:1704
return *(u32*)(this[297] + 0x90); // [0x948] + 0x90 (v7x = 16)
```text
| Accessor | Address | Reads | v7x value |
|---|---|---|---|
| `SparseCoreTiles` | `0xFAAFA40` | `[0x948]+0x90` | 16 |
| `SparseCoreLaneCount` | `0xF7906E0` | `[0x948]+0x94` | 16 |
| `SparseCoreHbm4bWordSizeBytes` | `0x1320C220` | `[0x948]+0x58` | 4 |
| `SparseCoreStreamGranuleSizeBytes` | `0x13886EE0` | `[0x948]+0xA4` | 4 |
| `GetSparseCoreBarrierSyncFlagCount` | `0x10972FA0` | `[0x948]+0x1D4` | — |
| `SupportsSparseCore` | `0x1D48FD40` | `TpuTopology[+0x98] > 0` | true |
| `SparseCoresPerLogicalDevice` | `0x135159C0` | `CoresPerChip / LogicalDevicesPerChip` | 2 |
> **易错点 —** gate 是*运行时拓扑*测试,而不是 class 测试。`SupportsSparseCore` 读取 `*(u32*)(TpuTopology* /*Target+0x3B8*/ + 0x98) > 0`,而不是 `[0x948]` 指针。重实现必须在任何 SC accessor 可达之前设置拓扑描述符的 SparseCore-count 字段;否则整个 accessor surface 都会以 `target.h:1704` FATAL trap。在 BarnaCore 世代(Jellyfish/Dragonfish/Pufferfish)以及仅 TC 的 lite die 上,该 count 为零,因此 `Target::Init` 永远不会构建子对象。
---
## Engine → Core 布局
### 目的
SC 计算结构由三个 VLIW 子引擎组成,每个都是拥有自身 bundle stream 和 codec 的独立机器。架构相关事实在于它们如何映射到*物理 core 和 tile*:一个 TEC sequencer 就是一个 tile,tile 数是 `SparseCoreTiles`,而 SCS/TAC/TEC 名册按世代变化。
### 布局
```text
┌──────────────────────── SparseCore (one of SparseCoresPerLogicalDevice) ─────────────┐
│ │
codec-tmpl type 3 │ SCS (1 control sequencer, 32 B bundle) │
(SPARSE_CORE_SEQ) │ ├─ program counter, address arithmetic, circular buffers │
│ ├─ chip-register reads (GTC, tile id, sparse-core id, DMA credits) │
│ └─ atomic + sync-flag slot (coordinates tiles ↔ tiles, SC ↔ TC) │
│ │
codec-tmpl type 4 │ TAC (tile-access core, 64 B bundle; VF/GL ONLY — absent on 6acc60406) │
(SPARSE_CORE_TILE_ │ └─ stream slot issues tile-fetch DMA HBM[base + idx·stride] → TILE_SPMEM │
ACCESS_CORE_SEQ) │ (no FPU, no vector ALU, no vector load/store — pure address + DMA) │
│ │
codec-tmpl type 5 │ TEC ×SparseCoreTiles (tile-execute cores, 64 B bundle — one TEC = one tile) │
(SPARSE_CORE_TILE_ │ ├─ 3 vector ALU slots + vector load/store + vector-extended + vector-result │
EXECUTE_CORE_SEQ) │ ├─ vector_isa.lane_count lanes (= SparseCoreLaneCount, read at SequencerParts(5)) │
│ └─ on 6acc60406: TEC stream slot also issues tile-fetch DMA (TAC role absorbed) │
└──────────────────────────────────────────────────────────────────────────────────── ┘芯片携带 SparseCoreTiles 个 TEC core;每个 TEC 拥有一个 TILE_SPMEM 窗口(向量 ALU 在其上计算的每 tile working set)以及一个 TIMEM(每 tile instruction memory)。单个 64-byte TEC bundle 可以在一个周期内发出三个 vector ALU op、一个 vector load、一个 vector store、一个 vector-extended op(scan/sort/uniquify)、一个 vector-result pop、两个 scalar ALU slot、immediate、DMA 和一个 stream slot — 这就是为什么该引擎是整个 SparseCore 中最宽的计算表面。
每代名册
引擎名册在不同 silicon 上不是常量。判别依据是每 codename 的 codec family;是否存在 SparseCore<Engine>CodecBase class 是二进制中的直接读数。
| Gen | Codename | Family ns | SCS | TAC | TEC | Tile-fetch issuer | SCs/TC | SCs/chip |
|---|---|---|---|---|---|---|---|---|
| v5p | Viperfish | vxc.vfc | Y | Y | Y | TAC stream | 4 | 8 |
| v6e | Ghostlite | gxc.glc | Y | Y | Y | TAC stream | 4 | 8 |
| v7x | 6acc60406 | gxc.gfc | Y | – | Y | TEC stream(无 TAC) | 4 | 4 |
特殊点 —
6acc60406(gfc)世代将 SCS+TAC+TEC 三引擎流水线折叠为 SCS+TEC。gfcnamespace 中有零个SparseCoreTac*symbol;TEC 通过自身 stream slot(IndirectStream/IndirectVregStream/LinearStream/StridedStream)吸收地址生成 + DMA 发出职责,而 SCS 计算 gather 地址,TEC 通过tile_wait_scs_smem读取它们。对 tile 分配而言,这改变的是谁消费 tile,而不是如何分配 tile — bump allocator 从来不知道 TAC。absorbed-role 细节见 TAC Engine,每引擎 bundle surface 见 SCS (Scalar) Engine / TEC (Vector) Engine。
注意事项
Jellyfish/Dragonfish/Pufferfish 世代没有 SparseCore(它们拥有的是已退役的 BarnaCore embedding accelerator)。在这些 target 上,存在性 gate 返回 false,本文描述的整个 SC 架构都不存在。每芯片 SC 数是 SparseCoreCountPerTensorCore × tensor_cores_per_chip;SparseCoreCountPerTensorCore(0x1C6CB760)计算 sparse_core_count_per_chip / LogicalDevicesPerChip,并同时断言 sparse_core_count_per_chip >= tensor_core_count_per_chip(lowering_util.cc:4488)和 … % … == 0(line 4489)— 也就是整数 4:1 SC:TC 比率。
片上内存模型
目的
SC 拥有四个地址空间层级,作用域递减、速度递增。编译器以 word 为单位寻址每个 SC SRAM 层级,其中 word 宽度按层级、按世代变化,来自 SparseCoreTarget capacity/word block。架构关键事实是 TILE_SPMEM 和 SPMEM 共享同一块物理 SRAM — TILE_SPMEM 是从 SPMEM 中 carve 出的 stripe,而不是独立 bank。
四个层级
| Tier | Scope | Size class | LLVM addrspace | Capacity field | Word-size field | Use |
|---|---|---|---|---|---|---|
| HBM | 芯片范围(与 TC 共享) | GB | 203 | Target::HbmSizeBytes | [0x948]+0x38 4b-word count | Embedding table、gradient buffer、spill |
| SPMEM | 芯片上所有 SC core | MB | 202 | [0x948]+0x2C | [0x948]+0x4C | 跨 SC 通信、大缓冲区、tile backing store |
| TILE_SPMEM | 每 tile(一个 TEC) | KB | 201 | [0x948]+0x28 | [0x948]+0x48 | 向量 ALU 计算的 local working set |
| TIMEM | 每 tile 指令内存 | small | 214 | [0x948]+0x10 | [0x948]+0x50 | Tile-local kernel code |
SC 还暴露按 scope 的 SMEM(标量内存,addrspace 0;TEC scope 中每 tile 的 smem_tile 219,每 SCS 的 smem_scs 224)和 SFLAG(sync-flag 池,addrspace 204;每 tile 的 sflag_tile 217,每 SCS 的 sflag_scs 223)。mlir::sparse_core::MemorySpace enum(1-based)通过 .rodata 0xAF36CE8 处的 22-entry table 映射到 LLVM address space,由 MemorySpaceToAddressSpace(0x14B78780)解码;scope(SCS vs TEC/tile)决定逻辑 spmem/timem 请求最终落入哪个物理 addrspace。
SPMEM ↔ TILE_SPMEM 拆分
SPMEM (chip-wide SC SRAM, capacity [0x948]+0x2C)
│
├─ divided evenly into SparseCoreTiles regions: total_spmem_bytes % num_tiles == 0
│ num_tiles <= sequencer_count_
│ num_tiles % num_groups == 0
│
└─ one tile's TILE_SPMEM window = a stripe of SPMEM, aligned to a full vector-lane stripe
private tile base = SPMEM bump offset / SparseCoreTiles (per-tile partition; 0x134DD340)
stripe granularity = 32 bytes (SparseCoreSpmemStripeGranularityBytes, 0x1D499440)
allocation alignment = SpmemAlignment = (SparseCoreTiles × lane_count) / 4 words (0x13DC5500)
```text
`SpmemAlignment`(`0x13DC5500`)返回 `*(u32*)(Target[0x948] + 0x90) × lane_count / 4` — 即 `+0x90` 的 tile 字段乘以向量 lane count,再除以 4。其意图是把每个 SPMEM/TILE_SPMEM 分配向上取整到完整 vector-lane stripe,使 TEC 的 vector load/store 总是命中对齐地址;匹配的 alignment-in-bits 检查会拒绝 `numElements × elementBitWidth` 不是 `SpmemAlignmentInBits` 倍数的 tile store。
> **注意 —** 逐字节反编译(`0x13DC5500`)计算 `*(u32*)(SparseCoreTarget[+0x90]) × lane_count / 4`,其中 `+0x90` 字段是 tile count(`SequencerCount(5)` 槽位),`lane_count` 通过 `SparseCoreTarget` vtable slot `+0xD0` 的虚函数获取。重实现者应从*实际两个几何字段*(`+0x90` 和向量 lane count)计算分子,而不是使用硬编码常量。(反编译会让整个 accessor 经过 `SupportsSparseCore()` gate — `target.h:1704` FATAL — 并在 lane-count optional 缺失时返回 1。)
每 tile sync-flag 预算在 `Init` 中推导为 `tec_sflag_capacity_bytes / sflag_bytes_per_word`(逐字节确认:`[a5+0x18] / [a5+0x40]`,其中 `[a5+0x18]` 是 `tec_sflag_parts.word_count × bytes_per_word`,`[a5+0x40]` 是 `scs_sflag_parts.bytes_per_word`);四个编译器保留 sync-flag watermarks 是该商值 `− {1,2,3,4}`(存于 `[a5]+0x1EC/0x1F0/0x1F4/0x1FC`),它们从用户池中保留 TAC↔TEC(或在 `6acc60406` 上为 SCS↔TEC)握手 flag。跨引擎地址空间目录见 [Memory Hierarchy](../targets/memory-hierarchy.md)。
### 注意事项
Embedding table **不会**放在 SC SRAM 中。它们位于 HBM,并在每次 lookup 时流入 TILE_SPMEM。因此“table allocation”是 HBM 放置(由运行时 BestFit allocator + memory-space assignment 处理,不属于 SC 范围)加上下文记录的 logical-replica row sharding。片上层级只保存瞬时的每窗口 working set。
---
## Tile 分配算法
### 目的
几何信息已知后,SC-MLO 编译器必须把每个中间缓冲区放入四个片上层级。它**不**使用运行时 HBM/VMEM best-fit allocator。SC 片上 allocator 是**带 scope stack 的每地址空间 bump allocator**,完全实现于 `AllocationAssignmentPass` MLIR pass。没有 free list、没有 coalescing、没有 splitting;`Deallocate` 是 no-op(`TileOverlayAllocationPass::ElideDeallocs` 会逐字面移除每个 `memref.dealloc`)。
### 入口点
```text
AllocationAssignmentPass::runOnOperation (0x134D8240) ── SC tile/SMEM/SFLAG bump-allocate driver
├─ reset allocation_stack_ to frame 0 (root)
├─ pre-reserve HW-reserved regions in addrspaces {20 timem-SCS, 14 sflag-tile, 21 timem-other}
├─ param_table_size_ = GetParamTableSize(module) ── 0x13DABDA0 (sc.param_table_size attr)
├─ IterateOverFunctions (twice): global+SCS, then per-func allocations
│ └─ WalkOpsForAllocations → Allocate(memref, core, is_circular_buffer) ── 0x134DB1E0
│ ├─ GetMemRefSize (0x134DC1C0) ── byte→word + alignment round-up
│ └─ MemoryUsage::Reserve (0x134D9700) ── the bump primitive
├─ for each sc_tpu.tile_task: PushToStackForTileAllocations (0x134DD340) → allocate → PopStack
└─ emit sc.alloc_high_water_mark + sc.execute_alloc_high_water_mark
GlobalAllocationAssignmentPass::DoAllocations (0x1351BF80) ── capacity check + llvm_tpu.spill_ranges
TileOverlayAllocationPass::runOnOperation (0x136025E0) ── TIMEM overlay bump alloc
PrepareHbmSpillPass::runOnOperation (0x135F3B60) ── per-SC HBM spill stack算法 — bump 原语
MemoryUsage::Reserve(0x134D9700)是核心,已对照反编译逐字节确认:它在 absl::flat_hash_map<int /*addrspace*/, long /*words*/> 中查找当前地址空间的 bump 指针,将其作为新分配的 base 返回,并按请求大小推进它。
function MemoryUsage_Reserve(usage, memory_space, size): // 0x134D9700
addrspace = MemorySpaceToAddressSpace(memory_space) // enum → LLVM addrspace (0x14B78780)
slot = usage_map.find_or_insert(addrspace) // flat_hash_map<int,long>
base = slot.value // current bump pointer (words)
slot.value = base + size // bump
return base // base offset of the new allocation
```text
### 算法 — `Allocate` 和 circular-buffer(VF/GL)防护
```c
function Allocate(memref, opt_core, is_circular_buffer): // 0x134DB1E0
space = GetMemorySpace(memref.type)
// remap (space, core_scope) → effective addrspace; core arrives as 0x100000000|core:
// 0x100000000 = SCS scope, 0x100000002 = TEC/tile scope
// timem + SCS → 20 ; timem + TEC → 14 ; spmem + SCS → 21 ; spmem + TEC → 16
// reject smem/sflag outside the remap with FATAL:
// "memory_space != MemorySpace::smem && memory_space != MemorySpace::sflag"
size = GetMemRefSize(memref) // 0x134DC1C0 (words, alignment round-up)
base = top_frame.Reserve(eff_space, size) // bump
last = base + size - 1
hi = GetUserAllocatableWordOffsets(target, eff_space) // 0x13DABC00 — per-space user window upper bound
if last > hi:
error("current allocation offset upper bound ({last} words) exceeds the legitimate "
"user allocatable offset upper bound ({hi} words) in memory space {space} ...")
// THE ONLY PER-GEN CODE BRANCH (byte-exact gate at 0x134DB1E0:195):
// guard fires iff (DeepseaVersion − 3) <= 1, i.e. version in {3,4} = Viperfish OR Ghostlite
if (uint)(target[+0x398 /*DeepseaVersion*/] - 3) <= 1 && is_circular_buffer && last > hi - 8:
emitOpError("Attempting to allocate circular buffer into last entry of TileSpmem. "
"This will result in an out-of-bounds tile-local stream on VFC due to a HW bug. ...")
return baseGetMemRefSize(0x134DC1C0)计算 byte_size = numElements × elementBitWidth / 8,检查字节大小是对应层级 word size 的倍数(否则 "memref is not padded correctly"),再除以 word 得到 word 数,并向上取整到 WordAlignmentInBytes / WordSizeInBytes。
算法 — scope stack 和 tile sub-frame
allocator 保存 allocation_stack_,它是由 40-byte frame 组成的 std::vector<MemoryUsage>。PushToStack(0x134DA8C0)追加一个复制父级 bump-pointer map 的 frame,因此 nested scope 会在父级 high-water mark 之上分配;PopStack(0x134DAB00)丢弃顶层 frame(nested allocation 由栈纪律回收),并断言 allocation_stack_.size() >= 2 — frame 0 是持久 root。
PushToStackForTileAllocations(0x134DD340)push 一个 tile sub-frame,并从 SPMEM bump 指针 carve TILE_SPMEM,已逐字节确认:
function PushToStackForTileAllocations(is_shared): // 0x134DD340
PushToStack()
top.tile_spmem_snapshot = top[addrspace 0x10] // snapshot spmem-TEC bump ptr
top.tile_sflag_snapshot = top[addrspace 0x0E] // snapshot sflag-tile bump ptr
top.tile_unused = 0
spmem_base = top[addrspace 3] // current SPMEM bump pointer
spmem_align = SparseCoreTarget[+0x90] * lane_count / 4 // = SpmemAlignment (words)
CHECK(spmem_base % spmem_align == 0)
// "prev_frame[kSpmemAddressSpace] % LlvmTpuDialect::SpmemAlignment(target_) == 0" (alloc pass:737)
if is_shared: tile_base = top[addrspace 2] // shared TILE_SPMEM window
else: tile_base = top[addrspace 3] / SparseCoreTarget[+0x90]
// private tile window = SPMEM offset / SparseCoreTiles
// (the SPMEM bump ptr divided by the tile count — partitions SPMEM into per-tile windows)
top[addrspace 2] = tile_base // set the tile bump pointer (MemoryUsage::operator[](…,2))
```text
> **特殊点 —** “tile fetch”和“tile evict”不是 RAM 操作 — 它们是 bump-and-pop。获取下一个 tile 是 bump-allocate 到下一个空闲 TILE_SPMEM 窗口;evict 是 `PopStack`,它回退 bump 指针,使 tile N+1 复用 tile N 释放的 offset。没有单独的 tile-cache SRAM;TILE_SPMEM *就是* cache,eviction 隐含在 stack discipline 中。把它建模为带显式 eviction 的独立 cache 会导致 working set 大小错误。
### Embedding Row Sharding
由于 table 位于 HBM,唯一的“per-SC”放置是 row sharding。`GetLogicalReplicaInfo`(`0x13CA1AE0`)在两个不变量下求解 `{logical_replica_count, feature_dim_split_factor, sample_dim_split_factor}`,两者都由 CHECK 字符串逐字节确认:
```text
logical_replica_count % physical_sparse_cores == 0 (logical_replica_util, line ~147)
RoundUpToPowerOf2(logical_replica_count) == logical_replica_count (line ~186)每张 table 被拆成 logical_replica_count 个 row-shard;row r 落入 shard r mod logical_replica_count;shard 以 round-robin 映射到 physical_sparse_cores。由于该 count 是 2 的幂,并且能整除 SC 数(GetNumSparseCores,0x13C9EBA0 = num_partitions × num_replicas × SCs_per_TC),每个 SC 的 row 数是均匀的。
Minibatch Fit
lookup window 的可变大小 ID 必须适配 TILE_SPMEM。CalculateVariableSizeWords(0x13CA3F20)计算 fit 不等式,其错误字符串已逐字节确认:
// byte-exact at 0x13CA3F20: a3 = max_nz_per_row, a4 = logical_replicas
max_nz_per_row_partitioned = max(ceil(a3 / a4), SparseCoreLaneCount) // clamp floor = [0x948]+0x94, the lane count (v7x = 16) — NOT sparse_cores_per_chip
variable_size_words = num_variable_size_allocations × max_nz_per_row_partitioned // num_variable_size_allocations = virtual method (*(estimator+0x38))()
require variable_size_words <= tile_spmem_words
// "Variable size allocations (%d * %d = %d words) do not fit in TileSpmem (%d words)."
```text
如果 batch 的 ID 超过该限制,mini-batching 会把 sample batch 拆成每个都能适配的窗口。`num_variable_size_allocations` 是 pipeline buffer 数(double-buffer ⇒ 2);六个 `allocation_estimator` 子类各自为自己的 pipeline-stage buffer 定大小。
### HBM Spill
当片上 working set 超过 SPMEM/TILE_SPMEM 时,`PrepareHbmSpillPass`(`0x135F3B60`)会供应一个每 SC 的 HBM-backed spill stack,大小为 `FLAGS_xla_sc_hbm_spill_stack` 个 4-byte word(addrspace 4/HBM 中的 1-D i32 memref),另加一个 11-word SMEM scratch pad,由 `sc_tpu.hbm_spill_stack_capture` 捕获。`DoAllocations`(`0x1351BF80`)发出 `llvm_tpu.spill_ranges`(`{smemStart/Limit, tilespmemStart/Limit}`),标记哪些 offset 驻留、哪些 spilled。spill 关闭时(`flag == 0`),过大的分配是硬编译错误(`"TileSpmem high-water mark exceeds memory capacity"`)。
### 函数映射
| Function | Address | Role |
|---|---|---|
| `AllocationAssignmentPass::runOnOperation` | `0x134D8240` | bump-allocate driver(保留 HW 区域、遍历 allocas、遍历 tile task) |
| `…::Allocate(MemRefType, opt<Core>, bool)` | `0x134DB1E0` | 分配一个 memref → base word offset;VFC 防护 |
| `…::GetMemRefSize(MemRefType)` | `0x134DC1C0` | byte→word + padding/alignment round-up |
| `…::MemoryUsage::Reserve(MemorySpace, long)` | `0x134D9700` | bump 原语(flat_hash_map) |
| `…::PushToStack` / `PopStack` | `0x134DA8C0` / `0x134DAB00` | scope-frame push(复制父级指针)/ pop(`size >= 2`) |
| `…::PushToStackForTileAllocations(bool)` | `0x134DD340` | tile sub-frame;在 stripe 边界从 SPMEM carve TILE_SPMEM |
| `GlobalAllocationAssignmentPass::DoAllocations` | `0x1351BF80` | capacity check + `llvm_tpu.spill_ranges` emit |
| `TileOverlayAllocationPass::runOnOperation` | `0x136025E0` | TIMEM overlay bump alloc;elide deallocs |
| `PrepareHbmSpillPass::runOnOperation` | `0x135F3B60` | 每 SC HBM spill stack |
| `lowering_util::GetUserAllocatableWordOffsets` | `0x13DABC00` | 每空间 user-window 上界 |
| `xla_mlo_util::{WordSizeInBytes,WordAlignmentInBytes,CapacityInBytes}` | `0x14A89D00` / `0x14A89E20` / `0x14A89EE0` | 每层 word 宽度 / 对齐 / 容量 |
| `mlir::sparse_core::MemorySpaceToAddressSpace` | `0x14B78780` | enum → LLVM addrspace(table `0xAF36CE8`) |
| `LlvmTpuDialect::SpmemAlignment` | `0x13DC5500` | `(tiles × lane_count) / 4` words |
| `logical_replica_util::GetLogicalReplicaInfo` | `0x13CA1AE0` | row-shard / replica-count solver(pow-2,整除 #SC) |
| `logical_replica_util::GetNumSparseCores` | `0x13C9EBA0` | mesh 上总 SC 数 |
| `lowering_util::SparseCoreCountPerTensorCore` | `0x1C6CB760` | `SCs_per_chip / TCs_per_chip`(4:1) |
| `VariableWindowAllocationEstimator::CalculateVariableSizeWords` | `0x13CA3F20` | TILE_SPMEM minibatch-fit 不等式 |
### 每代差异
| Aspect | Viperfish (VF) | Ghostlite (GL) | `6acc60406` (GF) |
|---|---|---|---|
| Allocator algorithm | bump + stack | bump + stack | bump + stack(相同) |
| SCs per TC / per chip | 4 / 8 | 4 / 8 | 4 / 4 |
| Tile-fetch issuer | TAC stream | TAC stream | TEC stream(无 TAC) |
| Access/Execute scope split | TAC + TEC | TAC + TEC | SCS + TEC(`tile_wait_scs_smem`) |
| TILE_SPMEM / SPMEM word + capacity | chip_parts(`[0x948]+0x48/+0x28`,`+0x4C/+0x2C`) | chip_parts | chip_parts |
| SPMEM stripe granularity | 32 B | 32 B | 32 B |
| `SpmemAlignment` (words) | `tiles·lane/4` | `tiles·lane/4` | `tiles·lane/4` |
| Circular-buffer-in-last-entry | **HW bug — guarded** | **HW bug — guarded** | OK |
| HBM spill | yes(flag) | yes(flag) | yes(flag) |
> **易错点 —** 整个 allocator 中*唯一*真正的每代代码分支是最后一个 TILE_SPMEM entry 中 circular-buffer 的防护。它的 gate(逐字节,`0x134DB1E0:195`)是 `(uint)(Target[+0x398 DeepseaVersion] − 3) <= 1`,也就是对 DeepseaVersion **3(Viperfish)和 4(Ghostlite)**触发 — *不只是* Viperfish — 同时还要求 `is_circular_buffer` 且 `last > hi − 8`。诊断文本提到 “VFC”(最早表征该 HW bug 的芯片),但代码路径同时对 VF 和 GL 应用防护;只有 `6acc60406`(version 5)豁免。其他每代差异 — 容量、word 宽度、tile 数、TAC 是否存在、每芯片 SC 数 — 都通过 `SparseCoreTarget`/`TpuCoreParts` 数据驱动,没有代码分支。重实现者应把 allocator 视为 generation-agnostic,并把所有每代变化推入几何描述符。
---
## 相关组件
| Name | Relationship |
|---|---|
| `SparseCoreTarget::Init`(`0x1D612B20`) | 填充本页架构所读取的 geometry struct |
| `Target::SupportsSparseCore`(`0x1D48FD40`) | 每个 accessor 和 allocator dispatch 都会经过的存在性 gate |
| `AllocationAssignmentPass`(`0x134D8240`) | 将几何信息转成已放置缓冲区的 bump/stack allocator |
| `SparseCoreHierarchicalSpmdPartitioner` | 将 SC program I/O pad 到 logical-replica shard 边界(SPMD plumbing) |
## 交叉引用
- [SparseCore 概览](overview.md) — Part IX 的导航入口;引擎名称、每代存在性和数据路径。
- [SparseCoreTarget (`Target+0x948`)](../targets/sparsecore-target-descriptor.md) — 逐字节字段映射、24 个虚拟 accessor,以及本页所综合的每 codename MXU 表。
- [SC 后端流水线](sc-backend-pipeline.md) — 运行 `AllocationAssignmentPass`(以及 MEGACORE barrier)的 SC-MLO pass pipeline。
- [SC Core 选择](sc-core-selection.md) — computation 如何分配给物理 SparseCore。
- [getSequencerType](getsequencertype.md) — SCS/TAC/TEC 引擎选择函数和 sequencer-type enum。
- [Region → Sequencer Outliner](region-to-sequencer-outliner.md) — 将 SC computation 切分为每引擎 bundle stream。
- [SCS(标量)引擎](scs-engine.md) · [TAC 引擎](tac-engine.md) · [TEC(向量)引擎](tec-engine.md) — 三个子引擎 bundle surface。
- [GetSparseCoreConfig](getsparsecoreconfig.md) — 后端与该几何信息一同读取的 offload op-type 配置。
- [Memory Hierarchy](../targets/memory-hierarchy.md) — SC 层级所嵌入的跨引擎地址空间目录。
- [每 Codename 常量表](../targets/per-codename-hw-constants.md) — 从 `chip_parts` 获取的每代 memory/core-count 表,几何信息据此解码。
- **Binary:** `extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so`(build-id `89edbbe81c5b328a958fe628a9f2207d`)
- **Index entry:** Part IX — SparseCore & BarnaCore / SparseCore engines — [返回索引](../index.md)