VectorLoad Slot
本页中的每个 opcode 值、mask immediate、字段 shift/width 以及各代计数,都是从
libtpu-0.0.40-cp314wheel(build-id89edbbe81c5b328a958fe628a9f2207d)里的libtpu.so按字节精确读出的 — 来源包括每个 op 的SparseCoreTecVectorLoadTileSpmemLoad<Form>Opcode::Matches()比较立即数、…<Form><Field>Field::GetConcatenatedValue()accessor shift,以及SparseCoreTecVectorExtended…SourceOneField/GetVexSourcePortEncodingdecode。地址适用于此 build;其他版本会不同。
摘要
VectorLoad 是 64-byte TEC bundle 中 VectorStore slot 的读侧镜像:该 slot 从每 tile SRAM(TILE_SPMEM)拉取 gathered row 到 VREG,使 VectorExtended scan engine 可以对其做 reduce。VectorStore 是一个 type×mode opcode 矩阵(它必须携带 accumulate dtype),而 VectorLoad 只编码寻址模式:3-bit opcode 字段上的 5 个 op,全部与 dtype 无关。元素类型由消费它的 op 设定,而不是由 load 设定 — 如果重新实现者在 load roster 中寻找 S32/F32/S16/Bf16 拆分(store 侧确实有),将找不到,也绝不能合成出来。
决定性的结构事实是:load 会把每个 operand field 都打包进 ONE word — struct offset 0x28 处的 8-byte word — 因为 load 产生的是 Dest(被写入的 VREG),而不是驱动 store-source mux。store 把它的数据字段放在 word 0x30(Source@27);load 把 Dest@52、Index@27 和地址构造字段全部放在 word 0x28。这两个 slot 在 bit 层面上是刻意对称的:load Dest(word0x28 >> 52 & 0x3f)正是 store 的 IndexedReturnValueAdd fetch-and-add 返回其 pre-add 值所用的同一个 mux 位置 — 这也解释了为什么 fetch-and-add 返回 pre-add 值(它是通过 load-Dest mux 做 load-then-add)。
本页还负责 load 两侧 embedding-reduce datapath 的三个组成:选择 scan carry-in source bus 的 SourceOne seed enum(VexSourcePortEncoding,8 个值);segmented-scan boundary operand(每 segment reset id,作为 SegmentedScanOp 的第二个 SSA operand 绑定);以及 load/return 路径上的 fetch-and-add scatter ordering/dedup 语义。它们记录在这里,是因为三者都通过 load-Dest mux 以及 load 所馈送的 V read port 被读取;scan opcode roster 本身在 VectorExtended 中。
对重新实现而言,契约是:
- opcode 只表示寻址模式;5 个 op,word
0x28bit 58(gfc/glc)处 3-bit 字段。opcode = Matches_cmp_immediate >> 58,连续 0..4。load opcode 中没有 dtype —VectorLoad与 dtype 无关,不同于 VectorStoreAddform。mode lattice 是plain · CircularBuffer · CircularBufferPostUpdate · Indexed · IndexedCircularBuffer。 - 所有 operand field 都在 word
0x28中,连续 bit 27..60,无重叠、无空隙。Index@27/6(Indexed form)、Mask@33/5、Stride@38/4、Offset@42/3、BaseAddress@45/3、Cbreg@48/4(CB form)、Dest@52/6、Opcode@58/3。store 侧 sibling 证明了bit-position symmetry:loadDest@word0x28 bit52== store fetch-and-addDest@word0x28 bit52。 SourceOne是 source-bus SELECTOR,不是常量。 word0x28bit 13 处的 3-bit 字段,选择VexSourcePortEncoding∈ {VST_SOURCE=0,V0_Y_VREG=1,V0_X=2,V1_Y_VREG=3,V1_X=4,V2_Y_VREG=5,V2_X=6,V3_Y_VREG=7}。它把 EUP scan-input/carry-in mux 路由到 VREG read port;reduction identity(0、±inf)是放进该 port 的值,不是硬连线 code。- Segmented scan 将 segment id 绑定为第二个 SSA operand。
sparse_core::SegmentedScanOp= 2 个 operand(operand 0 = data,operand 1 = segment_ids);普通ScanOp= 1 个 operand。segment id 被 register-allocated 到一个空闲 V read port;per-segment reset 读取 operand 1。 - fetch-and-add 返回 pre-add 值,dedup 让顺序无关紧要。 返回值通过 load-Dest mux 捕获;dedup pipeline(Sort → Uniquify → DuplicateCount)会在 scatter 前折叠重复 id,因此每个 unique row 只被触碰一次,其 multiplicity 已经折入。
| Slot | VectorLoad — 64-byte TEC bundle 的 TecVectorLoad slot |
| Opcode field | word 0x28 bit 58 处 3-bit(gfc/glc);bit 56 处 3-bit(vfc) |
| Opcode → mnemonic source | 每个 op 的 SparseCoreTecVectorLoadTileSpmemLoad<Form>Opcode::Matches() immediate(>> 58) |
| Op count (per gen) | vfc 5 · glc 5 · gfc 5 |
| Mode lattice | plain · CircularBuffer · CircularBufferPostUpdate · Indexed · IndexedCircularBuffer |
| Data word | word 0x28(所有字段 — Dest、Index、地址构造、opcode) |
| dtype | 无 — 只表示寻址模式(由消费 op 设定) |
| SourceOne seed | word 0x28 bit 13 处 3-bit → VexSourcePortEncoding(8 个值) |
| Confidence | CONFIRMED(decompile / Matches-immediate 与 accessor-shift 锚定),除非某行或 callout 另有说明 |
注意 — 本页负责
VectorLoadopcode roster、field decode、SourceOneseed enum、segment-operand binding,以及 fetch-and-add return ordering。64-byte bundle layout 在 TEC Engine 中;VEX scan opcode roster 在 VectorExtended 中;dedup pipeline 在 Dedup Multiplicity 中。 这里链接它们,不重复。
Slot Field Map
目的
VectorLoad slot 从 TILE_SPMEM 读取一行到 Dest VREG。地址由 {BaseAddress, Offset, Stride} 构造(或通过 Cbreg 使用 CBREG window),受 lane Mask 控制,并可选地由 Index VREG 做 per-element gather。与 store 侧不同,它没有 Source(没有 store mux)也没有 accumulate dtype:load 只选择从哪里读以及写入哪个 VREG。每个字段 — 包括 opcode 和 destination — 都位于 struct offset 0x28 处的 8-byte word。
Field Layout
已用 gfc plain TileSpmemLoad field accessor(SparseCoreTecVectorLoadTileSpmemLoad<Field>Field::GetConcatenatedValue)逐字节确认,每个都是单个 (word0x28 >> shift) & mask。在反编译 accessor 中,*((_QWORD*)this + 5) 是 byte offset 0x28 处的 8-byte word(5 × 8 = 0x28):
VectorLoad slot — word 0x28 (8 bytes), gfc plain TileSpmemLoad form
word0x28 bit: 13 27 33 38 42 45 48 52 58
┌───────┬────────┬────────┬──────┬─────┬──────┬──────┬────────┬───────┐
│SrcOne │ Index │ Mask │Stride│Offs.│Base │Cbreg │ Dest │Opcode │
│ 3b │ 6b │ 5b │ 4b │ 3b │ 3b │ 4b │ 6b │ 3b │
│ @13 │ @27 │ @33 │ @38 │ @42 │ @45 │ @48 │ @52 │ @58 │
└───────┴────────┴────────┴──────┴─────┴──────┴──────┴────────┴───────┘
(VEX scan Indexed* ◄──── address build ────► CB* the VREG the
seed sel) only only written mode
```text
> **注意 — `SourceOne@13` 是 `VectorExtended`(scan)字段,不是 `VectorLoad` 字段;它共享 word `0x28`,这里画出它是为了显示接口边界。** load 本身在 bit 27..60 携带 `{Dest, Cbreg, BaseAddress, Offset, Stride, Mask, Index}`。bit 13 处的 `SourceOne` 属于消费 loaded VREG 的 scan op(见 [§The SourceOne Seed Enum](#the-sourceone-seed-enum));它通过同一 word 中的 `…AddScanS32SourceOneField` accessor 得到逐字节确认。
| Field | Word | Shift | Width | Present in modes | Accessor (gfc) |
|---|---:|---:|---:|---|---|
| `Opcode` | `0x28` | 58 | 3 | 全部 | (Matches predicate, `byte+0x2f & 0x1c`) |
| `Dest` | `0x28` | 52 | 6 | 全部(loaded row 的 VREG) | `…TileSpmemLoadDestField` `0x1ecb9b20` |
| `Cbreg` | `0x28` | 48 | 4 | 仅 `CircularBuffer*`(→ 16 个 [CBREGs](cbreg.md)) | `…CircularBufferCbregField` `0x1ecb9be0` |
| `BaseAddress` | `0x28` | 45 | 3 | 全部(`TILE_SPMEM` tile base / CBREG 的替代) | `…TileSpmemLoadBaseAddressField` `0x1ecb9b40` |
| `Offset` | `0x28` | 42 | 3 | 全部(window 内 offset) | `…TileSpmemLoadOffsetField` `0x1ecb9b60` |
| `Stride` | `0x28` | 38 | 4 | 全部(per-element address stride) | `…TileSpmemLoadStrideField` `0x1ecb9b80` |
| `Mask` | `0x28` | 33 | 5 | 全部(lane predicate / vmask) | `…TileSpmemLoadMaskField` `0x1ecb9ba0` |
| `Index` | `0x28` | 27 | 6 | 仅 `Indexed*`(per-element gather VREG) | `…TileSpmemLoadIndexedIndexField` `0x1ecb9dc0` |
上面的 shift 来自 *plain* `TileSpmemLoad` op-form — 这是 canonical reference,因为它携带地址构造字段且没有 `Cbreg`/`Index`。accessor body 精确 decode 为:
```c
// SparseCoreTecVectorLoadTileSpmemLoadDestField::GetConcatenatedValue (gfc 0x1ecb9b20)
return (*((uint64_t *)this + 5) >> 52) & 0x3F; // word0x28 bit52, 6-bit — the loaded-row VREG
// …TileSpmemLoadBaseAddressField::GetConcatenatedValue (gfc 0x1ecb9b40)
return (*((uint64_t *)this + 5) >> 45) & 0x7; // word0x28 bit45, 3-bit — TILE_SPMEM tile base
// …TileSpmemLoadOffsetField::GetConcatenatedValue (gfc 0x1ecb9b60)
return (*((uint64_t *)this + 5) >> 42) & 0x7; // word0x28 bit42, 3-bit — within-window offset
// …TileSpmemLoadStrideField::GetConcatenatedValue (gfc 0x1ecb9b80)
return (*((uint64_t *)this + 5) >> 38) & 0xF; // word0x28 bit38, 4-bit — per-element stride
// …TileSpmemLoadMaskField::GetConcatenatedValue (gfc 0x1ecb9ba0)
return (*((uint64_t *)this + 5) >> 33) & 0x1F; // word0x28 bit33, 5-bit — lane predicate
// …TileSpmemLoadIndexedIndexField::GetConcatenatedValue (gfc 0x1ecb9dc0)
return (*((uint64_t *)this + 5) >> 27) & 0x3F; // word0x28 bit27, 6-bit — gather index VREG
// …TileSpmemLoadCircularBufferCbregField::GetConcatenatedValue (gfc 0x1ecb9be0)
return *((uint16_t *)this + 23) & 0xF; // 16-bit @ byte 0x2e = bits48..63, &0xf => bit48/4最密集的 op-form TileSpmemLoadIndexedCircularBuffer(Index accessor 0x1ecb9ea0、Cbreg 0x1ecb9e20、Dest 0x1ecb9e00)在 word 0x28 中携带 {Dest@52, Cbreg@48, BaseAddress@45, Offset@42, Stride@38, Mask@33, Index@27} 以及 Opcode@58 — 已验证它们连续装入 bit 27..60,无重叠、无空隙。
特性 — 每个 load 字段都在 word
0x28;没有第二个 data word。 VectorStore slot 会把字段分散在 word0x30(Source/地址构造)中,并把Dest借到 word0x28。load 没有要驱动的 store mux,因此把Dest、Index、地址构造字段以及 opcode 全部打包进单个 word0x28。移植 store 侧 decoder 的重新实现者必须把每个 shift 都重新基于0x28而不是0x30,并重新映射每个位置 — 字段顺序不同(store 有Source@27;load 有Index@27、Dest@52)。陷阱 — load 没有 dtype 字段。 5 个 load op 只表示寻址模式;没有像 VectorStore
Addform 那样的S32/F32/S16/Bf16拆分。loaded element type 由消费它的 VectorExtended/VectorAluop 固定,而不是由 load 固定。给VectorLoad建模 dtype operand 会与消费 op 的类型冲突并导致误 decode。是否存在位于 bundle 其他位置(相对 word0x28)的独立 load-width control bit 未搜索 — 该 negative 为LOW。
Load/Store bit-position symmetry
两个 slot 被刻意设计成镜像对称,而这种对称性正是 fetch-and-add 背后的机制:
word packed fields
VectorStore (0x30): Source@27 Mask@8 Stride@13 Offset@17 Base@20 Cbreg@23 Index@2 Opcode@33
+ Dest @ word0x28 bit52 (ReturnValueAdd forms only)
VectorLoad (0x28): Index@27 Mask@33 Stride@38 Offset@42 Base@45 Cbreg@48 Dest@52 Opcode@58
▲
store fetch-and-add Dest @word0x28 bit52 ════════════════════════════════════════╝ SAME mux
```text
store 把其数据字段放在 word `0x30`,因为它驱动 store mux;load 则把所有内容打包进 word `0x28`。它们在相同 bit 位置共享的*唯一*字段是 `Dest@word0x28 bit52` — load 在那里写入其 row,而 store 的 `IndexedReturnValueAdd` family 通过*相同*位置返回 pre-add 值。所有 8 个 gfc store `ReturnValueAdd` op 都已在 [store page](vectorstore-slot.md#the-fetch-and-add-return-path) 上确认从 word `0x28` bit 52 读取 `Dest`。这个 bit-position identity 是 fetch-and-add 是 load-then-add 的结构性证据(见 [§The Fetch-and-Add Return Path](#the-fetch-and-add-return-path))。
---
## 5-Op Addressing-Mode Roster
### opcode-recovery model
每个 load op-form 都是一个独立 C++ type `SparseCoreTecVectorLoadTileSpmemLoad<Form>Opcode`,携带 `Matches() const` predicate:它从 decoded-instruction word 中 mask 出 opcode 字段,并与该 op 的 signature 比较。该字段是 **word `0x28` bit 58 处 3-bit**(mask `0x1c00000000000000`),所以 `opcode = cmp_immediate >> 58`。base op(`TileSpmemLoad`,0)通过 `testb $0x1c, 0x2f(%rdi); sete` 测试 — `+0x2f` 处的 byte 是 word `0x28` 的 bits 56..63,mask `0x1c` 隔离 3 个 opcode bit(58..60),全零即 op 0。逐字节如下:
```c
// SparseCoreTecVectorLoadTileSpmemLoadOpcode::Matches (gfc 0x1ecb9a00)
return (*((uint8_t *)this + 47) & 0x1C) == 0; // byte +0x2f, opcode 0
// …TileSpmemLoadIndexedOpcode::Matches (gfc 0x1ecb9a60)
return (*((uint64_t *)this + 5) & 0x1C00000000000000) == 0xC00000000000000; // 0xc…>>58 = 3*((uint64_t*)this + 5) 是 byte offset 0x28 处的 word。已枚举所有 5 个 gfc load-op Matches() immediate;得到的 opcode set 是连续 0..4,无空洞、无重复。
完整 roster(gfc,5 个 op,逐字节确认)
| op | mnemonic (TileSpmem…) | mode | extra fields | Matches immediate (>>58) |
|---|---|---|---|---|
| 0 | Load | plain | — | byte+0x2f & 0x1c == 0 |
| 1 | LoadCircularBuffer | CB-windowed | Cbreg | 0x04…>>58 = 1 |
| 2 | LoadCircularBufferPostUpdate | CB + advance | Cbreg | 0x08…>>58 = 2 |
| 3 | LoadIndexed | indexed gather | Index | 0x0c…>>58 = 3 |
| 4 | LoadIndexedCircularBuffer | indexed gather, CB | Index,Cbreg | 0x10…>>58 = 4 |
每个 form 的 field set:
| op-form | field set |
|---|---|
TileSpmemLoad | {Dest, BaseAddress, Offset, Stride, Mask} |
+CircularBuffer / +CircularBufferPostUpdate | 增加 {Cbreg} |
+Indexed | 增加 {Index} |
+IndexedCircularBuffer(最密集) | 增加 {Cbreg, Index} |
mode lattice
| Mode | Marker in name | Behavior | Extra field | Embedding role |
|---|---|---|---|---|
| plain load | (无 qualifier) | 将寻址到的 word 读入 Dest | — | gathered-row load |
| CircularBuffer | CircularBuffer | 通过 CBREG window 寻址(16 个 CBREG,4-bit selector) | Cbreg | windowed minibatch tile read |
| +PostUpdate | …PostUpdate | load 后推进 CBREG offset | Cbreg | 无需单独 pointer bump 的 streaming tile read |
| Indexed | Indexed | 从 VREG 读取 per-element gather offset | Index | per-id row gather |
| IndexedCircularBuffer | Indexed…CircularBuffer | 在 CBREG window 内做 per-element gather | Index,Cbreg | windowed per-id gather |
load mode lattice 是 VectorStore lattice(plain / CircularBuffer / PostUpdate / Indexed)的读侧子集,减去 Add/ReturnValue accumulate 语义 — 这些只对写入有意义。Indexed form 提供 6-bit Index VREG(word0x28 bit27),让每个 lane 读取 addr + Index[lane] — 这是与把 TILE_SPMEM 填好的 Stream GATHER stage 配对的 per-id gather。
特性 —
CircularBufferPostUpdate存在于 non-indexed CB form,但不存在于 indexed form。 roster 有LoadCircularBufferPostUpdate(op 2),但没有LoadIndexedCircularBufferPostUpdate。这与 VectorStore 的 sparsity rule 一致(PostUpdate 只与 non-indexed CB 配对)。重新实现者不能合成 indexed post-update load;这 5 个条目就是全部可达 cell。注意 — consuming-stage dispatch 有 18 个 opcode 映射到这 5 个 op。 TEC bundle consumer(
ConsumeOneTecBundleInstruction,gfcutils::band;glc0x13a08e00)将一个 18-entry inner jump table 路由到这 5 个EmitVectorLoadOrStore<…SparseCoreTecVectorLoad_<Form>>leaf — 每个 op 的多个 opcode 是由 operand field 区分的 pred / non-pred / circular-mask MCInst form,而不是 op identity。opcode→op map 是 18→5;上面的 field decode 对每个 form 都相同。(Dispatch detail 在 code-gen 页面;本页负责 field decode。)
SourceOne Seed Enum {#the-sourceone-seed-enum}
它选择什么
load 下游的 reduction stage 通过名为 SourceOne 的 3-bit 字段读取其 scan seed / accumulator carry-in;该字段已在 word 0x28 bit 13 逐字节确认(跨 scan op invariant;从 …AddScanS32SourceOneField gfc 0x1eca7c40 读取):
// SparseCoreTecVectorExtendedAddScanS32SourceOneField::GetConcatenatedValue (gfc 0x1eca7c40)
return (uint8_t)HIBYTE(*((uint16_t *)this + 20)) >> 5;
// *((uint16_t*)this + 20) is the 16-bit at byte 0x28; HIBYTE is byte 0x29 (bits 8..15 of word0x28);
// >>5 takes the top 3 bits of that byte = bits 13..15 of word0x28 → 3-bit @ bit13.
```text
该值是 `asic_sw::deepsea::gxc::gfc::isa::VexSourcePortEncoding`。它**不是 constant-pool index**({0, +inf, -inf});它是 *source-bus selector* — 将 EUP scan-input/carry-in mux 路由到 V0/V1/V2/V3 VREG read port(`X` selector 或 `Y_VREG` selector sub-port)之一,或路由到 `VST` source bus。
### 8 个值
| value | enum name | role |
|---:|---|---|
| 0 | `VEX_SOURCE_PORT_ENCODING_VST_SOURCE` | VST(vector-store)source bus |
| 1 | `VEX_SOURCE_PORT_ENCODING_V0_Y_VREG` | V0 operand,`Y_VREG` sub-port |
| 2 | `VEX_SOURCE_PORT_ENCODING_V0_X` | V0 operand,`X` sub-port |
| 3 | `VEX_SOURCE_PORT_ENCODING_V1_Y_VREG` | V1 operand,`Y_VREG` sub-port |
| 4 | `VEX_SOURCE_PORT_ENCODING_V1_X` | V1 operand,`X` sub-port |
| 5 | `VEX_SOURCE_PORT_ENCODING_V2_Y_VREG` | V2 operand,`Y_VREG` sub-port |
| 6 | `VEX_SOURCE_PORT_ENCODING_V2_X` | V2 operand,`X` sub-port |
| 7 | `VEX_SOURCE_PORT_ENCODING_V3_Y_VREG` | V3 operand,`Y_VREG` sub-port |
该 enum 由 `xla::ghostlite::GhostliteProtoUtils::GetVexSourcePortEncoding(common_proto_utils::VregReadPort)`(gfc `0x1c5ee280`)交叉确认:这是一个 switch,将 compiler 的 logical `VregReadPort` 1:1 映射到这些 encoding — case 0..7 返回 encoding 0..7 与 OK status,而两个更高 port 被拒绝:**case 8(`V3_X`)返回 `InvalidArgument`("The V3_X slot (port number 8) cannot be used by a VEX instruction.")**,**case 9(`MISC_AUX`)返回 `InvalidArgument`("MISC_AUX not supported on GLC")**:
```c
// xla::ghostlite::GhostliteProtoUtils::GetVexSourcePortEncoding(common_proto_utils::VregReadPort) (gfc 0x1c5ee280)
switch (port) {
case 0: *(int*)(this+2) = 0; *(uint64_t*)this = 1 /*OK*/; return this; // VST_SOURCE
case 1: *(int*)(this+2) = 1; goto ok; // V0_Y_VREG
case 2: *(int*)(this+2) = 2; goto ok; // V0_X
// … cases 3..7 → encodings 3..7 (V1_Y_VREG, V1_X, V2_Y_VREG, V2_X, V3_Y_VREG)
case 8: /* V3_X — InvalidArgument: cannot be used by a VEX instruction */
case 9: /* MISC_AUX — InvalidArgument: not supported on GLC */
}SparsecoreVregReadPort proto enum(descriptor string 按声明顺序存储)是 {VST_SOURCE=0, V0_Y_VREG=1, V0_X=2, V1_Y_VREG=3, V1_X=4, V2_Y_VREG=5, V2_X=6, LANE_ID=7} — 与 encoding table 相同,都是 Y_VREG 在 X 之前。该函数实际接受的更宽 common_proto_utils::VregReadPort 会延伸到 proto 之外:port 8(V3_X)和 9(MISC_AUX)作为输入存在但被拒绝,输入 port 7 映射到 encoding 7(V3_Y_VREG)。
为什么它是 selector,而不是常量
SourceOne 选择哪个 read port(或 VST bus)馈送 scan 的 first/seed input。reduction identity element(Add 的 0,Min/Max 的 ±inf)是放进该 port 的值,而不是硬连线 SourceOne code。这就是 scan accumulator 跨 tile / 跨 V operand frame 链接的机制:把前一个 partial 写到选定 V port,将 SourceOne 设为该 port,scan 就以该 carry-in 继续。
特性 — 有 4 个 source-bus encoding(V0..V3),但 VEX frame 中只有 3 个 V operand pair。 VectorExtended operand frame 有 3 个 V operand pair(V0/V1/V2,各自一个
Y_VREG+Xselector),但SourceOne可以选择V3_Y_VREG(enc 7)。V3 是独立 physical read port(chained-accumulator / VST-feedback path,只能经由SourceOne到达)还是 VST source 的 alias,为HIGH— 结构上已确认它是可达 encoding,微架构上是推断。HasVexSourceBuses()是 per-target predicate(Ghostlite gfc / Jellyfish / Viperfish / Pufferfish),因此 bus set 依赖代际。
Segmented-Scan Boundary Operand
load 所馈送的 reduction stage 是 VectorExtended scan;对 embedding sum 而言,scan 是 segmented 的 — 它会在 per-sample boundary 处 reset running accumulator。该 boundary 在 MLIR 层被绑定为 SSA operand,已从 op trait pack 逐字节确认:
| MLIR op | operands | results | reduction kind |
|---|---|---|---|
sparse_core::ScanOp | AtLeastNOperands<1>(data) | OneResult | reduction_op attr |
sparse_core::SegmentedScanOp | NOperands<2>(data, segment_ids) | OneResult | reduction_op attr |
SegmentedScanOp::build(OpBuilder, OperationState, Type result, Value input, Value segment, StringAttr reduction_op)(gfc 0x145fd4a0)无条件发出两次 addOperands 调用(operand 0 = input,operand 1 = segment)— 即 exact-2 form NOperands<2>。sibling ScanOp::build(OpBuilder, OperationState, Type, Value, Value, StringAttr)(gfc 0x145f92e0/0x145f9480)把 FIRST addOperands 置于 if (first_value) 后,并无条件添加第二个 — 也就是 first operand optional 的 AtLeastNOperands<1> form。reduction_op StringAttr 在二者上都作为 property 存储。因此:
- operand 0 = 要 scan 的 data / value。
- operand 1 = segment ids — 当 segment id 变化时,scan reset running accumulator。这就是任务所问的 per-segment boundary。
reduction_op string 在 SegmentedScanOpLowering::matchAndRewrite(gfc 0x13589d40)中通过 byte-comparison decode:"sum"、"max"、"min"。lowering chain 按 segmented-vs-plain op identity 分叉,segmentation 在 intrinsic name 中由 .seg. infix 携带:
HLO embedding-sum
→ sparse_core::SegmentedScanOp (2 operands: data, segment_ids)
→ SegmentedScanOpLowering (gfc 0x1358 9d40) / ScanOpLowering<SegmentedScanOp,…> (0x135f3000)
→ LLVM intrinsic llvm.tpu.{add,max,min}[.full/.half][.seg].scan{1x,2x}[.index] (.seg = segmented)
vs sparse_core::ScanOp (1+ operands: data, optional second)
→ ScanOpLowering (0x1358 ab00) / ScanOpLowering<ScanOp,…> (0x135f2580)
→ llvm.tpu.{add,max,min}[.full/.half].scan{1x,2x}[.index] (no .seg infix)
```text
随后 SC isa_emitter 通过 `FindAndEmitToUnusedPort<SparsecoreVregReadPort, …>` 将每个 intrinsic operand register-allocate 到一个**空闲 V read port**,并写入匹配的 slot V-field(port→slot jump table)。因此 **segment-id operand 会落到分配时空闲的任意 V0/V1/V2 read port** — 它是 register-ALLOCATED V operand,不是固定 slot。绑定由 SSA operand #1 决定;放置位置由 read-port allocator 决定。
> **注意 — `.seg.` infix 是 segmentation marker;`scan1x`/`scan2x` 是 width/throughput marker,不是 operand count。** intrinsic family 是 `llvm.tpu.{add,max,min}[.full/.half][.seg].scan{1x,2x}[.index]`(全部在 binary 中逐字节确认:`llvm.tpu.add.seg.scan1x`、`llvm.tpu.add.full.seg.scan2x`、`llvm.tpu.max.seg.index.scan2x` 等)。判断 scan 是 segmented(per-sample embedding sum)的最清晰结构 marker,是 `.seg.` infix 加上 `SegmentedScanOp` 上的第二个 SSA operand(segment ids);`scan1x` vs `scan2x` 与 `.full` vs `.half` 是独立的 throughput/lane-width axis,不是 operand count。完整 `reduction_op` attr set 已确认为 `{sum, max, min}`;`mean`/`sqrtn` 是作为 attr 存在还是 post-scan divide,未枚举 — 该边界为 `LOW`。
---
## Fetch-and-Add Return Path {#the-fetch-and-add-return-path}
### 通过 load-Dest mux 返回 pre-add
[VectorStore](vectorstore-slot.md) 的 `IndexedReturnValueAdd*` family 是 fetch-and-add:它把 `Source` accumulate 到 `mem[addr + Index]`,并把 add **之前**存在的值返回到 `Dest` VREG。对*本页*而言,决定性事实是 return `Dest` 从 **word `0x28` bit 52 — 与 `VectorLoad` 写入 loaded row 的 mux 位置完全相同** 读取。所有 8 个 gfc store `ReturnValueAdd` op 都已确认从 word `0x28` bit 52 读取 `Dest`(该表由 [store page](vectorstore-slot.md#the-fetch-and-add-return-path) 负责)。因此 return 捕获 pre-add 值,因为该 op 在结构上是 load-then-add:
```c
// fetch-and-add structural model — the Dest is the LOAD mux, so it captures pre-add
function FetchAndAddStore(slot): // e.g. TileSpmemStoreIndexedReturnValueAddF32
addr = BuildAddress(BaseAddress, Offset, Stride, Cbreg?) // word0x30 fields (store side)
addr += Index[lane] // per-element scatter (Index @ word0x30 bit2)
old = mem[addr] // read THROUGH the load-Dest mux (word0x28 bit52)
Dest[lane] = old // return the PRE-add value (== VectorLoad Dest@52)
mem[addr] = old + Source[lane] // atomic accumulate (word0x30 Source @bit27)在 MLIR 层,fetch-and-add 是 sparse_core::VectorLoadStoreIdxAddOp,它有 result Type — 从 VectorLoadStoreIdxAddOp::build(OpBuilder, OperationState, Type, Value, Value, ValueRange, Value)(gfc 0x1459d840)确认,其 leading Type argument 就是 result type(返回的 pre-add 值)。普通 scatter-add sparse_core::VectorStoreIdxOp 没有 result Type。lowering(VectorLoadStoreIdxAddOpLowering,gfc 0x135c3…)计算 strided element pointer(Mul/Add/InsertElement),然后发出 LLVM::AddOp(read-modify-add)。LLVM intrinsic naming 确认了这一点:llvm.tpu.vst.msk.idx.ret.add.np / …ret.add.e4m3.np / …ret.add.e5m2.np(全部逐字节确认;ret.add = return-then-add,全部携带 .np suffix),相对的是 llvm.tpu.vst.[cb.]msk.idx.add[.e4m3/.e5m2][.np](普通 indexed scatter-add,没有 ret,并且也有 .cb. circular-buffer form)。
load 路径上的 scatter ordering 与 dedup
DLRM embedding scatter-add — ordering is made moot by collapsing duplicates first
gathered ids ──► SortInteger ──► Uniquify ──► DuplicateCount ──► scatter-add (fetch-and-add)
(VEX 20/21) (26/27) (24/25) each UNIQUE row touched ONCE
│ │ ▲
WithLaneIds inverse-map count× multiplicity ──┘
(routes result back) (forward: /count mean · backward: ×count grad)
```text
对于一个 vector 内并发的同地址 fetch-and-add(真正重复 index),per-lane commit order 是硬件行为,不存在于 C++ 中。在 embedding 路径里它不会触发:[dedup pipeline](dedup-multiplicity.md) — `SortInteger` → `Uniquify` → `DuplicateCount`(VEX scan op,逐字节确认的 op set,全部 `NOperands<2>`)— 会在 scatter 前折叠重复 id,因此每个 unique row 正好 fetch/update 一次。`WithLaneIds` variant(`UniqueWithLaneIdsOp` / `DuplicateCountWithLaneIdsOp`,`NResults<3>`)发出第三个 result:per-input → unique-representative lane-id map(inverse permutation),用于把每个 per-input result 路由回原始位置。`DuplicateCount` multiplicity 会缩放 gradient(backward ×count;forward 的 mean divisor),因此被折叠的 duplicate 会 accumulate 正确的 count× contribution。dedup *就是* correctness mechanism;fetch-and-add ordering 只对 raw、禁用 dedup 的 scatter 有意义,由 `xla_tpu_enable_sparse_core_computation_deduplication` gate 控制。
> **注意 — vector 内 duplicate-index commit order 是 silicon(raw scatter 为 LOW);count→weight 算术未 bit-trace。** 两点仍然是推断:(1) 一个 vector 内并发同地址 fetch-and-add 的精确 HW serialization 不在 C++ 中(对 dedup path 无关紧要,对 raw scatter 未指定);(2) 在 `IndexedReturnValueAdd` 之前将 `DuplicateCount` i32 result 折入 gradient 的精确算术未 bit-trace — op set 和 result count 为 CONFIRMED,`count×gradient` fold 是 lowering body,置信为 `LOW`。
---
## Embedding-Reduce Composition
`VectorLoad` 是 SparseCore embedding-reduce datapath 的 stage 2。完整组合如下(其他 stage 由链接页面负责):
```text
SparseCore embedding lookup + reduce — 5-stage TEC datapath
1. GATHER Stream IndirectStream id list → HBM[base+id*stride] → TILE_SPMEM (stream-gather-scatter)
2. LOAD VectorLoad TileSpmemLoad* TILE_SPMEM rows → VREGs (Dest/Index/Base/Offset/Stride/Mask/Cbreg
all @word0x28) ◄── THIS PAGE
3. REDUCE VectorExtended SegmentedAddScan operand0=data, operand1=segment_ids; SourceOne seed selects the
carry-in source bus (V0..V3 / VST) (vectorextended-vex)
4. DRAIN VectorResult EupResult/PopXrf* XRF (extended-result FIFO) → VRF (vector-opcode-enum)
5. SCATTER VectorStore …Add{dt} atomic scatter-add gradient INTO the row; fetch-and-add returns
pre-add (Dest@word0x28 bit52 — the load mux) (vectorstore-slot)Stage 2(本页)把 gathered rows 拉入 VREG;stage 3 的 SourceOne(本页)选择 scan seed,其 segment-id operand(本页)提供 per-sample reset;stage 5 的 fetch-and-add 通过本页的 load-Dest mux 返回其 pre-add 值。load 与 dtype 无关,因此同一组 5 个 op 服务每种 embedding width;类型在 stage 3 固定。
Function Map
| Symbol (gfc) | Address | Role |
|---|---|---|
…VectorLoadTileSpmemLoadOpcode::Matches | 0x1ecb9a00 | op 0 predicate(byte+0x2f & 0x1c == 0)— base op |
…VectorLoadTileSpmemLoadIndexedOpcode::Matches | 0x1ecb9a60 | op 3(cmp 0xc00000000000000 → >>58 = 3) |
…VectorLoadTileSpmemLoadDestField::GetConcatenatedValue | 0x1ecb9b20 | Dest @ word 0x28 >> 52 & 0x3f |
…VectorLoadTileSpmemLoadBaseAddressField | 0x1ecb9b40 | BaseAddress @ word 0x28 >> 45 & 0x7 |
…VectorLoadTileSpmemLoadOffsetField | 0x1ecb9b60 | Offset @ word 0x28 >> 42 & 0x7 |
…VectorLoadTileSpmemLoadStrideField | 0x1ecb9b80 | Stride @ word 0x28 >> 38 & 0xf |
…VectorLoadTileSpmemLoadMaskField | 0x1ecb9ba0 | Mask @ word 0x28 >> 33 & 0x1f |
…VectorLoadTileSpmemLoadCircularBufferCbregField | 0x1ecb9be0 | Cbreg @ word 0x28 >> 48 & 0xf(CB form) |
…VectorLoadTileSpmemLoadIndexedIndexField | 0x1ecb9dc0 | Index @ word 0x28 >> 27 & 0x3f(Indexed form) |
…VectorLoadTileSpmemLoadIndexedCircularBufferDestField | 0x1ecb9e00 | 最密集 form 的 Dest(同样 @52) |
…VectorExtendedAddScanS32SourceOneField::GetConcatenatedValue | 0x1eca7c40 | SourceOne @ word 0x28 >> 13(3-bit),op-invariant |
GhostliteProtoUtils::GetVexSourcePortEncoding | 0x1c5ee280 | VregReadPort → VexSourcePortEncoding 1:1(8 个值) |
mlir::sparse_core::SegmentedScanOp::build | 0x145fd4a0 | 2-operand build(operand0=data,operand1=segment_ids) |
mlir::sparse_core::VectorLoadStoreIdxAddOp::build | 0x1459d840 | fetch-and-add op;leading Type arg = result(pre-add) |
跨代锚点:vfc VectorLoad opcode field 是 word 0x28 bits[56:58] 处 3-bit(base op 通过 testb $0x7, 0x2f;CircularBuffer 通过 mask 0x07<<56 cmp 0x01<<56 → 1),命名为 TileSpmemIndexedLoad*(Indexed-prefix);gfc/glc 是 bits[58:60] 处 3-bit,命名为 TileSpmemLoadIndexed*(Indexed-suffix)— 向上移动 2 bit(GF 在 opcode 下方插入了 2 bit)。decode VALUES 0..4 和 operand-field 语义跨代稳定;per-gen count 在 vfc/glc/gfc 中都是 5 个 op。GF 在 opcode 下方插入的 2 bit 未识别 — LOW。
注意 —
TensorCoreVectorLoad*是不同 engine,不计入 5-count。0x1f9e9880+VectorLoadsymbol(TensorCoreVectorLoad0/1、TensorCoreVectorMiscVectorLoad)属于 TensorCore datapath,不属于 SparseCore TEC。只有SparseCoreTecVectorLoad*type 计入此 slot 的 5-op roster;重新实现者 grepVectorLoad时必须过滤SparseCoreTecprefix。
注意事项
- 每个 shift 都要重新基于 word
0x28。 将 store 侧 decoder 移植到 load 时,必须把所有 shift 重新基于0x28(不是0x30),并重新映射顺序:storeSource@27,loadIndex@27;store opcode @33,load opcode @58。load 的 single-word packing 是结构差异。 - load 上没有 dtype。 将
VectorLoaddecode 为仅表示寻址模式;element type 来自消费它的 VectorExtended/VectorAluop。load 上的 dtype operand 是建模错误。 SourceOne是 cross-tile scan-chaining 接口。 它选择 carry-in source bus,因此 reduction identity 是放在 V port 中的值,绝不是硬连线 code。把SourceOne当作 constant-pool index 会错误建模 accumulator chaining。- segment boundary 是 operand 1,register-allocated 到空闲 V port。 将 segment id 绑定为
SegmentedScanOp的第二个 SSA operand;放置位置(哪个 V0/V1/V2 port)由 read-port allocator 选择,并记录在 slot V-field 中。 - Fetch-and-add ordering 是 silicon(raw scatter 为 LOW)。 pre-add return 已结构确认(Dest = load mux @52、
.ret.addintrinsic、result-typedVectorLoadStoreIdxAddOp);vector 内 duplicate-index commit order 不在 C++ 中,对 non-deduplicated scatter 在此未定义。对 embedding path 它无关紧要(dedup 会先折叠 duplicate)。 - 未映射(LOW/inferred)。
count×gradientmultiplicity fold(lowering body);V3_Y_VREG 的 physical-port identity(第 4 个 read port vs VST alias);GF 在 opcode 下方插入的 2 bit(bits[56:58] → [58:60] vs vfc);word0x28外是否存在独立 load-width control bit;{sum, max, min}之外的完整reduction_opattr set。
Related Components
| Name | Relationship |
|---|---|
SparseCoreTecVectorLoadTileSpmemLoad*Opcode::Matches | 定义 opcode 值的 5 个 per-op predicate(0..4 @ word 0x28 bit 58) |
SparseCoreTecVectorStore* (0x1ecc9f40+) | store counterpart;共享 fetch-and-add 返回所经的 Dest mux(word 0x28 bit 52) |
SparseCoreTecVectorExtended*::SourceOne (0x1eca7c40 gfc) | 同一 word 0x28 中的 scan seed-port selector field |
GhostliteProtoUtils::GetVexSourcePortEncoding (0x1c5ee280) | 将 logical VregReadPort 映射到 8 个 VexSourcePortEncoding 值 |
mlir::sparse_core::SegmentedScanOp (build 0x145fd4a0) | 2-operand segmented scan;operand 1 = segment-id boundary |
mlir::sparse_core::VectorLoadStoreIdxAddOp (build 0x1459d840) | result-typed fetch-and-add;返回 pre-add 值 |
Cross-References
- TEC (Vector) Engine — 负责 64-byte bundle、slot base 与 encoder-dispatch model。
- VectorStore Slot — store 侧镜像;bit-position symmetry(load
Dest@52== store fetch-and-addDest@52)以及 33-op type×mode matrix。 - VectorExtended (VEX) — load 所馈送的 scan/sort/reduce slot;scan opcode roster 与
SourceOne/VstSourcefield 的归属页。 - Segmented Scan — embedding sum 使用的 per-segment-reset scan;这里摘要记录了 2-operand binding。
- TEC Vector Opcode Enumeration —
VectorAluopcode roster 与本页复用的 opcode-recovery model;VectorResultXRF-drain slot。 - Stream Gather/Scatter — load 前填充
TILE_SPMEM的GATHERstage;on-tile scatter-add 所镜像的 cross-HBMSCATTER_FLOAT_ADDatomic。 - CBREG —
Cbregselector 所索引的 16-CBREG circular-buffer register triple;CB load mode 继承的 wrap arithmetic。 - Dedup Multiplicity — precedes scatter-add 的 Sort→Uniquify→DuplicateCount 路径,使 fetch-and-add ordering 无关紧要。
- SparseCore Overview — 三类 SC engine、per-gen presence,以及 TEC vector slot 所在位置。
- Binary:
extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d) - Index entry: Part IX — SparseCore & BarnaCore / SparseCore ISA — back to index