Stream Gather/Scatter
本页中的每个地址、字段偏移、opcode 值和 enum 值,均读取自
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d;buildlibtpu_lts_20260413_b_RC00)。.textVA 等于文件偏移0xe63c000,.rodata位于0x84a0000-- 二者均为恒等映射。除非另有标记,地址均来自 gfc(6acc60406 / TPU7x)实例;glc(Ghostlite, v6e)和 vfc(Viperfish, v5)同族实例在不同地址上承载相同 schema。其他版本会有所不同。
摘要
SparseCore Stream 引擎是间接 DMA 数据路径:硬件将 embedding id 列表转换为 HBM gather(table -> tile)或 scatter(tile -> table)事务流,并可选地在目标端执行原子读-改-加。它是一个 VLIW slot -- Stream sub-bundle -- 由单个 proto 消息 SparseCoreStream 编码而来,该消息在 SCS、TAC(仅 vfc/glc)和 TEC bundle 中以相同形式出现。这个 slot 正是 概览 中 host-table -> HBM -> SC gather 路径实际执行的部分:当概览说“TAC/TEC stream slot 发出 tile-fetch DMA”时,本页描述的就是该 slot 的位布局、编译器为它构建的描述符,以及硬件执行的逐元素地址运算。
SparseCoreStream 是一个共享 header 加上四路 oneof 的寻址形式:LinearStream(连续)、StridedStream(一个 stride 维度)、IndirectStream(embedding gather/scatter -- id 列表驱动地址)和 IndirectVregStream(id 列表从 vector register 而非内存流入;见 IndirectVregStream)。四者共享完全相同的 stream-control 字段尾部 -- sync flag、tile-local stride、filter、length-type、StreamOpcode 累加模式、OffTileMemoryType 池选择器,以及两个标量操作数。只有开头的操作数组不同。oneof 判别符会成为 6-bit slot opcode:LinearStream=0x3b、StridedStream=0x3a、IndirectStream=0x39、IndirectVregStream=0x38。
重新实现表面分为三层,本文将每层作为独立单元记录:(1)slot 编码 -- SparseCoreStream proto、其共享 header 和公共控制尾部、每种形式的 opcode,以及 IndirectStream 形式的位精确 encode/decode 映射;(2)间接 DMA 描述符 -- MLIR sc_tpu.indirect_stream_start op,及其四个操作数组 {Src, Dst, Offset, Sflag}Indices、tpu.enqueue_indirect_dma 如何 lower 到它,以及逐元素地址公式 addr_i = table_base + offset_list[i] * indirect_list_stride;(3)粒度模型 -- IndirectListType(word vs row offset)、OffTileMemoryType(HBM vs HBM_4B)、IndirectLengthType(fixed vs variable window)和 TileLocalStride(32 B...2 KB)如何设定读/写粒度。
对重新实现而言,契约如下:
- 一个 proto,三个 bundle slot。
SparseCoreStream由SparseCoreStreamEncoder::Encode(SCS bundle,32 B)、SparseCoreTecStreamEncoder::Encode(TEC bundle,64 B)和SparseCoreTacStreamEncoder::Encode(TAC bundle,64 B -- 仅 vfc/glc)编码。三者都接收SparseCoreStream const&。各引擎之间字段语义相同;只有 slot 在 bundle 内的 bit 位置不同。 oneof就是 opcode。 选择形式#8...#11会选择 6-bit opcode0x3b/0x3a/0x39/0x38。除形式判别符外没有单独的 opcode 字段;encoder 在 bundle bit 181 写入 opcode。StreamOpcode是累加模式,与形式正交。 GATHER/SCATTER 移动行;*_INTEGER_ADD/*_FLOAT_ADD变体在目标端执行原子读-改-加。对 HBM 的SCATTER_FLOAT_ADD(6)是 TensorCore MXU 无法执行的 embedding-gradient 累加原语。- 描述符就是 MLIR op 的操作数组。 id 列表是
OffsetIndices组;连续的Linear/Strided形式会去掉它。逐元素 row-stride 乘法是硬件 Stream 引擎内建的 -- lowering 中没有逐元素imul;编译器只分配操作数组并设置indirect_list_stride属性。
| Proto message | SparseCoreStream -- 共享 header(#1...#7)+ 4-form oneof(#8...#11) |
| SCS encoder | SparseCoreStreamEncoder::Encode @ 0x1eb9b4c0(gfc;32 B bundle) |
| TEC encoder | SparseCoreTecStreamEncoder::Encode @ 0x1ebe33e0(gfc;64 B bundle) |
| TAC encoder | SparseCoreTacStreamEncoder::Encode @ 0x1e8ee040(vfc;另 glc @ 0x1ea338e0;gfc 中不存在) |
| Form opcodes(6-bit @ bit 181) | Linear 0x3b · Strided 0x3a · Indirect 0x39 · IndirectVreg 0x38 |
| Accumulate mode | StreamOpcode(3-bit)-- GATHER=0...SCATTER_FLOAT_ADD=6 |
| Off-tile pool | OffTileMemoryType(3-bit)-- SPMEM=0, TILE_SPMEM_N=1, HBM=2, HBM_4B=3 |
| Descriptor op | sc_tpu.indirect_stream_start / .indirect_stream_add_start(AtLeastNOperands<6>) |
| Per-element address | addr_i = table_base + offset_list[i] * indirect_list_stride(HW;row-stride 缩放内建) |
| Per-gen presence | SCS+TEC Stream 存在于全部 3 个 SC 世代;TAC Stream 仅 vfc/glc(gfc 无 TAC) |
| Confidence | CONFIRMED(encode-offset / decode-shift / opcode-value 已与 decompile 双重核对),除非行内另有说明 |
SparseCoreStream Proto 和四种形式
目的
SparseCoreStream 是每个 Stream-engine slot 编码所依据的单一消息。它的职责是描述一个地址生成 stream -- 读或写什么、base 在哪里、逐元素地址如何计算,以及如何发出完成信号 -- 与承载它的子引擎无关。四种 oneof 形式是共享同一个控制面的四个地址生成器;embedding gather/scatter 正是 IndirectStream 形式。
消息布局
共享 header(#1...#7)和四形式 oneof(#8...#11):
message SparseCoreStream { // the Stream sub-bundle slot
// ---- shared header (all forms) ----
#1 normal_predication : SparsecoreNormalPredication (3-bit)
#2 normal_predication_inversion : bool
#3 rotate_predication : SparsecoreRotatePredication (4-bit, 16-entry ring)
#4 is_rotate_predication : bool
#5 latency : uint32 // scheduling-only, not emitted
#6 resource_usage : map<string,uint32> // scheduling-only
#7 bit_width : uint32 // scheduling-only
// ---- oneof addressing form ----
#8 linear_stream : LinearStream // opcode 0x3b
#9 strided_stream : StridedStream // opcode 0x3a
#10 indirect_stream : IndirectStream // opcode 0x39 ← gather/scatter
#11 indirect_vreg_stream : IndirectVregStream // opcode 0x38
}
```text
字段 `#5...#7`(`latency`、`resource_usage`、`bit_width`)是调度元数据;encoder 不会将它们发射到 bundle 中(它们驱动 SC 指令调度器,而不是硬件 slot)。四种形式只在*开头*的操作数上不同;它们都以如下相同的控制尾部结束。
### 公共控制尾部
每种形式都以一块相同的 stream-control 字段结尾。这里以 `IndirectStream` 字段名展示(由于开头操作数不同,各形式的编号也不同):
| Field | Type / width | Role |
|---|---|---|
| `sync_flag_count_type` | `SyncFlagCountType` (1) | 完成计数单位:WORD_4B=0 vs DESCRIPTOR=1 |
| `set_done_bit` | bool | 完成时拉起 stream-done flag |
| `tile_local_stride` | `TileLocalStride` (3) | 目标 tile 写入粒度(32 B...2 KB / NO_STRIDE) |
| `post_update_circular_buffer` | bool | 每个元素后推进 tile-local circular buffer |
| `indirect_list_type` | `IndirectListType` (1) | offset 解释:WORD_OFFSET=0 vs ROW_OFFSET=1 |
| `indirect_list_stride` | uint32 (6-bit slot) | **embedding row stride** -- 逐元素乘数 |
| `indirect_filter_en` | bool | 启用 id 过滤 |
| `indirect_filter_mode` | `IndirectFilterMode` (1) | SKIP=0 vs COMPACT=1 |
| `indirect_length_type` | `IndirectLengthType` (1) | FIXED=0 vs VARIABLE=1 lookup length |
| `indirect_offset_source` | `IndirectOffsetSource` (1) | offset/size 来自 SREG=0 vs CBREG window=1 |
| `post_update_indirect_offset_circular_buffer` | bool | 每个元素后推进 CBREG offset |
| `trace_en` | bool | 启用硬件 trace |
| `indirect_mask` | uint32 (4-bit) | lane/id mask |
| `stream_opcode` | `StreamOpcode` (3) | 累加模式(GATHER...SCATTER_FLOAT_ADD) |
| `gather_scatter_add_is_b16` | bool | bf16 vs f32 add-accumulate dtype |
| `tile_local_memory_type` | `TileLocalMemoryType` (1) | SMEM=0 vs TILE_SPMEM=1 目标 |
| `tile_local_stream_type` | `TileLocalStreamType` (1) | LINEAR=0 vs CIRCULAR_BUFFER=1 目标布局 |
| `off_tile_memory_type` | `OffTileMemoryType` (3) | off-tile 池(HBM table / SPMEM / TILE_SPMEM_N) |
| `s0_x` / `s1_x` | uint32 (5-bit SREG each) | 标量操作数 0/1 X(offset / size source register) |
| `s0_y` / `s1_y` | `ScalarY` (6-bit each) | 标量操作数 0/1 Y(operand-source encoding) |
各形式开头的操作数:
| Form | Opcode | Leading operands (before the common tail) |
|---|---|---|
| `LinearStream` | `0x3b` | `off_tile_start_offset[_valid]` -- 单个线性 base |
| `StridedStream` | `0x3a` | `stride_size[_valid]`, `stride_length_and_offset[_valid]` -- 一个 stride 维度 |
| `IndirectStream` | `0x39` | `indirect_offset[_valid]`(5-bit), `indirect_size_and_hbm4b_offset[_valid]`(5-bit) |
| `IndirectVregStream` | `0x38` | `indirect_offsets`, `indirect_access_lengths`(VREG-sourced), `off_tile_start_offset[_valid]` |
> **NOTE --** `Linear`/`Strided` 形式并不是 gather/scatter 路径本身的一部分;它们是连续和 strided 地址生成器,为 embedding lookup 周围的非索引传输 -- 批量 feed/drain -- 共享同一控制面(filter、length-type、sync flag)。只有 `IndirectStream`(及其 VREG 变体)会消费 id 列表。由于尾部共享,重新实现者可以从同一个 struct 编码四者:形式判别符选择开头操作数;其后的所有字段相同。
### 函数映射
| Function | Address (gfc) | Role |
|---|---|---|
| `SparseCoreStreamEncoder::Encode` | `0x1eb9b4c0` | 将 `SparseCoreStream` 编码到 SCS 32 B bundle slot |
| `SparseCoreStreamDecoder::Decode` | `0x1eb96be0` | 将 SCS Stream slot 解码回 `SparseCoreStream` |
| `SparseCoreTecStreamEncoder::Encode` | `0x1ebe33e0` | 编码到 TEC 64 B bundle slot |
| `SparseCoreTecStreamDecoder::Decode` | `0x1ebdd440` | 解码 TEC Stream slot |
| `SparseCoreTacStreamEncoder::Encode` | `0x1e8ee040`(vfc) | 编码到 TAC 64 B bundle slot(仅 vfc/glc) |
| `BitCopy` (generic bit-field packer) | `0x1fa0a900` | little-endian field packer `(dst, dst_off, src, src_off, nbits)` |
---
## Stream-Engine Enums
### 目的
Stream slot 携带十一个小 enum,以及 6-bit `ScalarY` operand-source code。它们的数值由 `.rodata` 中的 proto descriptor 固定,并且是 wire format 的一部分 -- 重新实现者必须精确复现。完整集合在有重叠的 [Vector Opcode Enum](vector-opcode-enum.md) 和 [Scalar Opcode Enum](scalar-opcode-enum.md) 页面中编目;Stream 专属项列在这里。
### 值
```text
StreamOpcode (3-bit) — the accumulate mode:
GATHER=0 GATHER_INTEGER_ADD=1 GATHER_FLOAT_ADD=2 RESERVED_0=3
SCATTER=4 SCATTER_INTEGER_ADD=5 SCATTER_FLOAT_ADD=6 RESERVED_1=7
OffTileMemoryType (3-bit) — the off-tile pool:
SPMEM=0 TILE_SPMEM_N=1 HBM=2 HBM_4B=3 RESERVED_0..3=4..7
IndirectOffsetSource (1-bit): SREG=0 CBREG=1
IndirectListType (1-bit): WORD_OFFSET=0 ROW_OFFSET=1
IndirectFilterMode (1-bit): SKIP=0 COMPACT=1
IndirectLengthType (1-bit): FIXED=0 VARIABLE=1
SyncFlagCountType (1-bit): WORD_4B=0 DESCRIPTOR=1
TileLocalMemoryType (1-bit): SMEM=0 TILE_SPMEM=1
TileLocalStreamType (1-bit): LINEAR=0 CIRCULAR_BUFFER=1
TileLocalStride (3-bit): 32B=0 64B=1 128B=2 256B=3 512B=4 1024B=5 2048B=6 NO_STRIDE=7
SparsecoreNormalPredication (3-bit): PREG0_IS_1=0 .. PREG6_IS_1=6, ALWAYS=7
SparsecoreRotatePredication (4-bit): PREG0_IS_1=0 .. PREG15_IS_1=15 (16-entry rotating ring)
ScalarY (6-bit, ~110 values): SREG_0..N + immediate slots (IMM0..3)
+ hardwired constants (ONE/TWO/FOUR/EIGHT/PI/E/HEX_*…)QUIRK --
StreamOpcode与 slot opcode 正交。 6-bit slot opcode(0x3b/0x3a/0x39/0x38)选择寻址形式;3-bitStreamOpcode选择在目标端做什么。单个IndirectStream(slot opcode0x39)可以是普通 GATHER(StreamOpcode=0)、gradientSCATTER_FLOAT_ADD(StreamOpcode=6),也可以是GATHER_FLOAT_ADD-- 它们都是同一种形式。将两个 opcode 字段混淆的重新实现者会错误解码每个 scatter-add。随后gather_scatter_add_is_b16bit 会将 add accumulate 收窄为 bf16 vs f32。
Stream Slot 位编码 -- IndirectStream 形式
目的
这是 gather/scatter slot 的位精确映射,从 codec 两侧给出:encode 侧(encoder 通过 BitCopy 写入的 bundle-relative bit offset)和 decode 侧(...Field::GetConcatenatedValue() accessor 读取的 decoded-struct word + shift)。二者描述同一个物理 slot;两侧共同确认字段宽度。
Encode 侧(bundle-relative)
SparseCoreStreamEncoder::Encode @ 0x1eb9b4c0 写入 32-byte(256-bit)SCS bundle。Bit offset 是传给 BitCopy 的 esi immediate:
Shared header (emitted first):
normal_predication @ bit 187, width 3
normal_predication_inversion @ bit 190, width 1
rotate_predication @ bit 187, width 4 (overlaps normal when is_rotate)
is_rotate_predication @ bit 191, width 1
Per-form opcode (oneof discriminator selects branch):
stream-form opcode @ bit 181, width 6
LinearStream=0x3b StridedStream=0x3a IndirectStream=0x39 IndirectVregStream=0x38
Form-leading operands (IndirectStream):
indirect_offset_valid @ bit 110, width 1
indirect_offset @ bit 105, width 5 (5-bit SREG)
indirect_size_and_hbm4b_offset_valid @ bit 104, width 1
indirect_size_and_hbm4b_offset @ bit 99, width 5
Common control tail (selected fields):
off_tile_memory_type @ bit 111, width 3
sync_flag_count_type @ bit 127, width 1
set_done_bit @ bit 128, width 1
post_update_circular_buffer @ bit 131, width 1
indirect_list_type @ bit 132, width 1
indirect_list_stride @ bit 133, width 4 (slot-narrowed from proto uint32)
tile_local_stride @ bit 137, width 3
indirect_filter_en @ bit 140, width 1
indirect_filter_mode @ bit 141, width 1
indirect_length_type @ bit 142, width 1
s0_x @ bit 143, width 6
s0_y @ bit 149, width 5 (encode narrows the 6-bit ScalarY to 5)
indirect_offset_source @ bit 155, width 1
post_update_indirect_offset_cb @ bit 156, width 1
trace/mask region @ bit 157, width 3 then bit 160, width 1 / 161, width 1 / 162, width 6
tile_local_memory_type @ bit 168, width 1
tile_local_stream_type @ bit 169, width 1
s1_y @ bit 170, width 6
s1_x @ bit 176, width 5
```text
> **NOTE -- TEC slot 是同一组字段,但 base 不同。** `SparseCoreTecStreamEncoder::Encode` @ `0x1ebe33e0` 从同一个 `SparseCoreStream` struct 编码到 64-byte(512-bit)TEC bundle;形式 opcode `0x3b/0x3a/0x39/0x38` 不变。唯一差异是 `IndirectVregStream` 的 `indirect_offsets` 操作数,TEC bundle 将其放在高位区域 bit 322(width 6)。TAC slot(vfc/glc)同样是在 TAC bundle base 上的同一组字段。
### Decode 侧(decoded-struct relative)
`IndirectStream` 的 `...Field::GetConcatenatedValue()` accessor 是权威字段标签。struct 以 64-bit word 读取;`+0x10` 是 QWORD index 2,`+0x18` 是 QWORD index 3。下表每一行都已通过读取 accessor body 确认:
| Field | Struct word | Shift | Width | Accessor addr (gfc) |
|---|---|---|---|---|
| `IndirectOffsetSource` | `+0x18` | 0 | 1 | `0x1eb9b320` |
| `PostUpdateIndirectOffsetCircularBuffer` | `+0x18` | 3 | 1 | -- |
| `TraceEn` | `+0x18` | 4 | 1 | -- |
| `IndirectMask` | `+0x18` | 5 | 4 | -- |
| `StreamOpcode` | `+0x18` | 9 | 3 | `0x1eb9b3a0` |
| `GatherScatterAddIsB16` | `+0x18` | 12 | 1 | `0x1eb9b3c0` |
| `TileLocalMemoryType` | `+0x18` | 13 | 1 | -- |
| `TileLocalStreamType` | `+0x18` | 14 | 1 | -- |
| `S1Y` | `+0x18` | 15 | 6 | -- |
| `S1X` | `+0x18` | 21 | 5 | -- |
| `SyncFlagCountType` | `+0x18` | 27 | 1 | -- |
| `SetDoneBit` | `+0x18` | 28 | 1 | -- |
| `TileLocalStride` | `+0x18` | 29 | 3 | `0x1eb9b240` |
| `PostUpdateCircularBuffer` | `+0x18` | 32 | 1 | -- |
| `IndirectListType` | `+0x18` | 33 | 1 | `0x1eb9b280` |
| `IndirectListStride` | `+0x18` | 34 | 6 | `0x1eb9b2a0` |
| `IndirectFilterEn` | `+0x18` | 40 | 1 | -- |
| `IndirectFilterMode` | `+0x18` | 41 | 1 | `0x1eb9b2e0` |
| `S0Y` | `+0x18` | 42 | 6 | -- |
| `IndirectSizeAndHbm4bOffset` | `+0x10` | 35 | 5 | -- |
| `IndirectSizeAndHbm4bOffsetValid` | `+0x10` | 40 | 1 | -- |
| `IndirectOffset` | `+0x10` | 41 | 5 | `0x1eb9b1a0` |
| `IndirectOffsetValid` | `+0x10` | 46 | 1 | `0x1eb9b180` |
| `OffTileMemoryType` | `+0x10` | 47 | 3 | `0x1eb9b420` |
| `IndirectLengthType` | `+0x10` | 63 | 1 | `0x1eb9b300` |
| `S0X` | `+0x1e` (u16) | 0 | 5 | `0x1eb9b440` |
形式 opcode 本身是位于 `+0x18` bit 53 的 6-bit 字段(decode word 的连续部分)。opcode matcher `SparseCoreStreamIndirectStreamOpcode::Matches` @ `0x1eb9aaa0` 读取 `(*((QWORD*)this + 3) & 0x7E0000000000000) == 0x720000000000000`,也就是 bits 53-58 == `0x39`。
> **GOTCHA -- `indirect_list_stride` 在 decoded struct 中是 6 bits,但在 SCS encode slot 中是 4 bits。** decode accessor 在 `+0x18>>34` 处以 `& 0x3F`(6-bit)mask;SCS `BitCopy` 只在 bundle bit 133 写入 4 bits。proto 字段是 `uint32`。观察到的最窄物理宽度是 SCS bundle 中的 4 bits。重新实现者必须在 encode 时将 stride 视为范围受限,而不能假设完整的 proto `uint32` 会到达硬件。每一代的确切物理计数器宽度尚未钉牢(LOW -- 见 Limits)。
---
## 间接 DMA 描述符
### 目的
描述符定义了 embedding-id 列表如何变成 HBM gather 地址。它不是编译器构建的独立内存 struct;它是 MLIR `sc_tpu.indirect_stream_start` op 的操作数结构,该 op 会 lower 到上面的 Stream slot 字段。框架 op `tpu.enqueue_indirect_dma`(TensorCore 侧)lower 到它,tiled-memref index expansion 展平操作数组,而在硬件 issue 时,Stream 引擎逐元素遍历 offset list。
### SC 侧 Op 及其操作数组
```text
sc_tpu.indirect_stream_start / sc_tpu.indirect_stream_add_start
Traits: ZeroResults, AtLeastNOperands<6>, AttrSizedOperandSegments
Operand groups (MutableOperandRange getters):
getSrcIndicesMutable() @ 0x145cd9a0 — HBM table-base memref + indices (the table)
getDstIndicesMutable() @ 0x145cda60 — TILE_SPMEM destination tile memref + indices
getOffsetIndicesMutable() @ 0x145cdc40 — the EMBEDDING-ID / offset list memref
getSflagIndicesMutable() @ 0x145cdb40 — completion sync-flag operand(s)
Attributes (getters):
getIndirectListType() @ 0x145cf400 — WORD_OFFSET vs ROW_OFFSET
getHbm4b() @ 0x145cf360 — 4-byte-granule HBM addressing
getTileLocalLengthPerStride() @ 0x145cf3a0 — destination granule per stride
getUpd() @ 0x145cf340 — post-update (advance circular buffer)
getEnableTrace() @ 0x145cf380 — hardware trace enable连续的同族 LinearStreamStartOp / StridedStreamStartOp 没有 OffsetIndices 组 -- 它们不携带逐元素 id 列表。_add_start 变体选择一个累加 StreamOpcode(*_FLOAT_ADD / *_INTEGER_ADD 模式)。
Lowering 和 Index Expansion
// tpu.enqueue_indirect_dma (TC side; AtLeastNOperands<4>, getAdd() = atomic-add mode)
// lowered by mlir::tpu::LowerMemrefToMlo::lowerEnqueueIndirectDma
// shapes verified by getVerifiedIndirectDmaShapes
// target SC partition picked by getRemoteDeviceAndSparseCoreIds
// -> sc_tpu.indirect_stream_start
function expandSCStreamStart<IndirectStreamStartOp>(op): // 0x134ead00
// ExpandTiledMemRefsPass flattens every tiled-layout memref index
for group in {Src, Dst, Offset, Sflag}Indices:
expanded = expandTiledIndices(group, tiles, loc, builder) // 0x134e1f20
// tiled memref index -> flat word offset
group.assign(expanded) // MutableOperandRange::assign
```text
`tpu.wait_indirect_dma`(`NOperands<3>`)是完成侧框架 op。`ExpandTiledMemRefsPass::addPattern<IndirectStreamStartOp>` 注册该 expansion(已在 decompile 中确认)。`_add_start`、`IndirectVectorStreamStartOp` 和 `StridedStreamStartOp` 的 expansion 是 `expandSCStreamStart<...>` 的同族实例化。
> **NOTE -- 描述符携带的是 index,而不是 address;乘法由硬件执行。** lowering 只重新分配操作数组并设置 `indirect_list_stride` 属性。没有发射逐元素 `imul`。row-stride 缩放(`offset_list[i] * indirect_list_stride`)是 Stream 引擎逐元素地址生成器内建的。这一点可信度为 HIGH(由字段名 + lowering 中缺少逐元素乘法推断),但并非从硬件寄存器描述中位确认。
---
## Gather/Scatter 粒度模型
### 目的
三个独立旋钮决定每个间接元素触及多少数据以及落在哪里。重新实现者用它们来匹配 embedding table 的 row width、HBM 对齐方式,以及 variable-length-lookup 行为。
### 逐元素地址公式
```c
// HW per-element gather/scatter, driven by the slot fields:
for i in 0 .. num_indices:
if indirect_filter_en and filtered(offset_list[i]):
continue // SKIP, or COMPACT out the slot
if indirect_list_type == ROW_OFFSET:
addr_i = table_base + offset_list[i] * indirect_list_stride // offset is a ROW index
else: // WORD_OFFSET
addr_i = table_base + offset_list[i] // offset is a byte/word offset
row = read_or_write(addr_i, granule = tile_local_stride)
if stream_opcode in {GATHER_*ADD, SCATTER_*ADD}:
atomic_add(addr_i, row, dtype = gather_scatter_add_is_b16 ? bf16 : f32)
if indirect_offset_source == CBREG and post_update_indirect_offset_cb:
advance_cbreg_offset_mod_size() // sliding embedding-table window三个粒度旋钮
| Knob | Field | Values | What it sizes |
|---|---|---|---|
| Offset interpretation | IndirectListType | WORD_OFFSET=0 / ROW_OFFSET=1 | offset_list[i] 是 byte/word offset,还是由 indirect_list_stride 缩放的 row index |
| HBM addressing granule | OffTileMemoryType | HBM=2 / HBM_4B=3 | HBM_4B 选择 4-byte-granule HBM addressing(indirect_size_and_hbm4b_offset 操作数携带 sub-word offset) |
| Destination write granule | TileLocalStride | 32 B...2 KB / NO_STRIDE | 进入目标 tile 的逐元素 write stride |
| Lookup length | IndirectLengthType | FIXED=0 / VARIABLE=1 | FIXED:每个 lookup 拉取相同的 row count;VARIABLE:逐行可变长度(variable-window lookup) |
row stride indirect_list_stride(embedding row width)是在 ROW_OFFSET 下的逐元素乘数。在 WORD_OFFSET 下,offset 已经是 word/byte offset,stride 不会对其缩放。
QUIRK --
indirect_size_and_hbm4b_offset是共享操作数。 同一个 5-bitIndirectStream操作数在普通 HBM 情况下携带 lookup size,在 HBM_4B 情况下携带 sub-word offset。解释方式由OffTileMemoryType==HBM_4B门控。字段名同时包含两个角色,正是因为硬件复用了它。重新实现者在打包该操作数时必须基于OffTileMemoryType分支。
Offset Source 和 Sliding Window
IndirectOffsetSource 选择逐元素 offset/size 的来源:
- SREG (0): offset/size 从
s0_x/s1_x(5-bit SREG selector)命名的 scalar register 读取,并配合s0_y/s1_y(6-bitScalarY)operand-source code。它是静态、per-stream 的。 - CBREG (1): offset/size 来自 circular-buffer register window;
post_update_indirect_offset_circular_buffer在每个元素后推进 CBREG offset,并对 window size 取模。这就是 sliding embedding-table window -- id 列表通过 CBREG ring 消费,而不是通过平坦 SREG。有关 circular-buffer register 布局,见 CBREG。
Filtering 和 Bounds
indirect_filter_en + indirect_filter_mode 控制哪些 id 会触发:SKIP 丢弃被过滤的 id(留下空洞),COMPACT 移除它(压缩输出)。filter value 由 SCS op sc_tpu.set_indirect_filter_value(SetIndirectFilterValueOp,通过 SparseCoreScalarAlu_SetIndirectFilterValue scalar op lower)在带外设置。id-range bounds check 由 AddStreamBoundChecksPass::runOnOperation @ 0x134c3fa0(其 AddStreamBufferChecks 有 Linear/Strided/Indirect overload)和 IndirectStreamBoundsCheckPass 插入;越界 id 会触发“does not match number of TPU embedding rows”诊断。
Scatter-Add Slot(Embedding-Gradient Accumulation)
目的
反向传播需要将一个 gradient slice 加到 embedding row 中。存在两条路径:(a)累加到 CBREG-windowed TILE_SPMEM 区域并使用 TEC vector-store op;或(b)发出 Stream SCATTER_FLOAT_ADD,直接进入 HBM embedding row。路径(b)就是 概览 称为关键原语的 in-HBM atomic FP-add。
TEC Vector-Store Add Slot
TileSpmemStoreCircularBufferPostUpdateAddF32(gfc)-- TILE_SPMEM circular-buffer post-update add store。decoded-struct word +0x30(DWORD index 12 / QWORD index 6),已通过读取 accessor body 确认:
| Field | Shift | Width | Accessor addr (gfc) | Meaning |
|---|---|---|---|---|
Mask | 8 | 5 | 0x1eccac40 | lane predicate / vmask |
Stride | 13 | 4 | 0x1eccac20 | 逐元素 address stride |
Offset | 17 | 3 | 0x1eccac00 | window 内 offset |
BaseAddress | 20 | 3 | 0x1eccabc0 | 显式 base(CBREG base 的替代) |
Cbreg | 23 | 4 | 0x1eccabe0 | 哪个 CBREG(-> 16 CBREGs) |
Source | 27 | 6 | 0x1eccaba0 | 被 scatter-added 的源 VREG |
该 op 按 accumulate dtype 存在 -- Add{Bf16,F32,S16,S32} -- 覆盖整个 store-form 家族(plain、CircularBuffer、CircularBufferPostUpdate、Indexed,...)。slot +0x30 处 Cbreg 的 4-bit 宽度(16 CBREGs)与 vfc(Viperfish)布局匹配,从而确认了 gfc(6acc60406)。完整 store-slot 家族见 VectorStore Slot,gather-load 对应项见 VectorLoad Slot。
Gradient Flow
TEC vector-loads gathered rows (TileSpmemLoadCircularBuffer[PostUpdate])
-> applies optimizer math (stochastic round-to-bf16 / FP8 pack on gfc TEC)
-> EITHER (a) TileSpmemStoreCircularBufferPostUpdateAdd{dt} (CBREG-windowed TILE_SPMEM)
OR (b) Stream SCATTER_FLOAT_ADD into HBM (StreamOpcode=6, OffTileMemoryType=HBM)
```text
路径(b)是直接进入 HBM 的 atomic FP-add(`StreamOpcode=6`,`gather_scatter_add_is_b16` 选择 accumulate dtype)-- 也就是 TensorCore 的 systolic array 无法执行的操作。
---
## SCS / TAC / TEC 引擎握手
### 目的
Stream slot 存在于所有三个 engine bundle 中,但某个给定的 gather/scatter 由一个引擎*发出*。具体是哪一个取决于世代,而引擎之间通过发布到 SMEM 的参数和跨引擎同步原语进行协调。这是 [架构](architecture.md) 页面端到端描述的 pipeline 的 slot-level 视图。
### Forward Lookup
```text
1. SCS computes per-lookup addressing params (table base, offset-list base, row
stride, window CBREG triple) and publishes them to SMEM.
2. ACCESS engine issues the indirect gather Stream op:
VF/GL: TAC bundle Stream slot (SparseCoreTacStreamEncoder @ 0x1e8ee040 vfc)
gfc: TEC bundle Stream slot (no TAC; SparseCoreTecStreamEncoder @ 0x1ebe33e0)
gated by TileWaitScsSmemOp (TEC waits for SCS's SMEM value)
The gather reads offset_list[i] (SREG or CBREG window),
computes HBM[table_base + offset*stride], DMAs the row -> TILE_SPMEM,
and (CBREG case) post-updates the CBREG offset mod size.
3. EXECUTE engine (TEC) vector-loads from TILE_SPMEM, reduces (sum / weighted /
max via the VectorExtended scan/segscan slots), emits the result tile.
4. Result tile DMA'd HBM->VMEM for MXU consumption; SC raises an SFLAG;
the TC's tpu.sem_wait advances.Sync Primitives
Cross-sub-engine (xla::tpu::sparse_core::collective::OffloadFactory):
SyncScsWithTec(builder, value, CoreKind) @ 0x133e9260
SyncTecWithScs(builder, v1, v2, CoreKind) @ 0x133e8fe0
CoreKind {kScs, kTec, kTac(VF/GL)}
gfc TAC-replacement:
TileWaitScsSmemOp / sc_tpu.tile_wait_scs_smem (TEC waits until SCS writes a
designated SMEM value, then proceeds with fetch+compute — collapses the
SCS<->TAC<->TEC three-way sync to a single SCS<->TEC boundary)
Cross-CORE (SC<->TC):
SFLAG memory (sflag / sflag_scs / sflag_tile), tpu.sem_wait / sem_signal /
fetch_and_add_sync; double-buffered (AllocateSflag(..., 2)).
Tile-task program model:
sc_tpu.tile_task (Access+Execute regions) launched via launch_tile_task /
prefetch_tile_task / tile_task_wait.
```text
> **NOTE -- 在 gfc(6acc60406)上,gather 从 TAC 迁移到 TEC。** 由于 TAC 被移除(gfc namespace 中 `SparseCoreTacStream*` 函数为零,而 vfc 中有 68 个 / glc 中有 71 个),access engine 的角色 -- 发出 indirect gather Stream op -- 折叠进 TEC,并通过 `TileWaitScsSmemOp` 同步,而不是三方 SCS/TAC/TEC 握手。Stream slot 的*编码*不变;只有发出引擎和同步拓扑不同。给定 Stream op 由哪个引擎承载,由 `LowerMemrefToMlo::getSequencerType` 决定;确切的 per-shape 决策未进行 bit-trace(见 [getSequencerType](getsequencertype.md))。
---
## 各世代存在性
| Mechanism | VF (vfc, Viperfish v5) | GL (glc, Ghostlite v6e) | GF (gfc, 6acc60406 / TPU7x) |
|---|:---:|:---:|:---:|
| SCS-bundle Stream slot (`SparseCoreStream`) | yes | yes | yes |
| TAC-bundle Stream slot (`TacStream`) | yes | yes | **-- (no TAC)** |
| TEC-bundle Stream slot (`TecStream`) | yes | yes | yes |
| `LinearStream` (opcode `0x3b`) | yes | yes | yes |
| `StridedStream` (opcode `0x3a`) | yes | yes | yes |
| `IndirectStream` (opcode `0x39`) gather/scatter | yes | yes | yes |
| `IndirectVregStream` (opcode `0x38`) | yes | yes | yes |
| `StreamOpcode` 8-value set (GATHER...SCATTER) | yes | yes | yes |
| `IndirectOffsetSource` SREG/CBREG | yes | yes | yes |
| `OffTileMemoryType` {SPMEM, TILE_SPMEM, HBM, HBM_4B} | yes | yes | yes |
| Indirect gather issued from ACCESS engine | TAC | TAC | TEC |
| Scatter-add CB store `...PostUpdateAdd{dt}` | int/float | 4 dtypes | 4 dtypes |
`TacStream` 函数数量是判别项:**gfc = 0**,**vfc = 68**,**glc = 71** 个已 decompile 的 `SparseCoreTacStream*` 函数。在 `jxc`(Jellyfish)或 `pxc`(Pufferfish)namespace 下没有出现 `SparseCoreStream` schema -- 这些世代没有 SparseCore。
---
## 限制和未解决项
| Item | Status |
|---|---|
| `SparseCoreStream` proto + 4-form `oneof`, all field numbers/names/types | 从 descriptor 解码;3 份副本(vfc/glc/gfc) |
| All Stream enums with numeric values | proto-descriptor 已确认 |
| Per-form opcodes `0x3b/0x3a/0x39/0x38` @ bit 181 | 来自 SCS + TEC encoder 的位精确结果 |
| `IndirectStream` encode offsets + decode shifts | 两侧一致;已读取 accessor body |
| Scatter-add slot `{Mask,Stride,Offset,BaseAddress,Cbreg,Source}` @ `+0x30` | 已读取 accessor body |
| Indirect-DMA descriptor operand groups + attribute getters | op getter 已在 decompile 中定位 |
| Per-element address formula (`table_base + offset*stride`, HW multiply) | 由字段名推断;lowering 中无逐元素 `imul` |
| Which engine issues a given Stream op per gen | 已定位 `getSequencerType`,未 bit-trace |
| Physical HW width of `indirect_list_stride` / SREG operands | proto `uint32`;slot 在 SCS 中编码 4-bit,decode 为 6-bit;HW counter width 未知 |
| `IndirectVregStream` VREG-read micro-op datapath | 字段位置已解码;VREG source 未 trace |
| `ROW_OFFSET` vs logical-replica row sharding (per-shard vs global row) | 未解决 |
| Absolute bit base of the SCS Stream slot within the 256-bit bundle | opcode @ bit 181 已知;完整 SCS slot-base partition 未交叉核对 |
---
## 交叉引用
- [SparseCore 概览](overview.md) -- 该 slot 执行的 host-table -> HBM -> SC gather 数据路径;关键的 in-HBM atomic add。
- [SparseCore 架构](architecture.md) -- 引擎角色和完整深度的 embedding datapath。
- [IndirectVregStream](indirect-vreg-stream.md) -- `0x38` 形式:id 列表从 vector register 而非内存列表流入。
- [VectorLoad Slot](vectorload-slot.md) -- 消费 streamed rows 的 TILE_SPMEM gather-load 对应项。
- [VectorStore Slot](vectorstore-slot.md) -- scatter-add `...PostUpdateAdd{dt}` 所属的 store-slot 家族。
- [CBREG Circular-Buffer Register](cbreg.md) -- CBREG offset source 推进的 sliding-window register。
- [getSequencerType](getsequencertype.md) -- 选择发出引擎的 SCS/TAC/TEC selection。
- [Vector Opcode Enum](vector-opcode-enum.md) / [Scalar Opcode Enum](scalar-opcode-enum.md) -- 周边 opcode roster。
- [GetSparseCoreConfig](getsparsecoreconfig.md) -- offload op-type 配置来源。
- [SparseCore vs Neuron MatmultSparse](sparsecore-vs-neuron-matmultsparse.md) -- indirect-gather 原语的跨厂商比较。
- **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 pointers & DMA -- [返回索引](../index.md)