Skip to content

getSequencerType — SCS / TAC / TEC 引擎选择

本页中的每个函数地址、枚举值、属性字符串字节模式和路由常量,均读取自 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d;build libtpu_lts_20260413_b_RC00)——来源包括具名函数的反编译 C++、demangle 后的符号表、嵌入的 proto 描述符字符串,以及 off_22010DE0 处的 .rodata 跳转表。其他版本会有所不同。

摘要

SparseCore 后端会把每个 lowering 后的 op 精确放置到三个 SparseCore 子引擎之一:SCS(标量控制)、TAC(tile-access / DMA)或 TEC(向量计算)。二进制文件将这种放置记录为每个函数一个名为 sc.sequencer 的字符串属性。这里不存在一个整体的“选择器”来推理某个 op 并返回引擎;相反,决策被拆成三层,本页从头到尾记录这些层:

  1. 一个策略分类器 GetTransferKind (@0x1351b140),它根据源/目标 memory-space 对以及一个 SparseCore 能力位,决定一次 off-tile 数据移动是 Stream(间接 gather/scatter)还是 DMA(连续块)传输。
  2. 一个region outlinerLowerSequencerFunctionsPass / OutlineSequencerFunction),它给每个按引擎 outline 出来的 func.func 标上 sc.sequencer = "scs" / "access" / "execute"
  3. 一个很简单的属性访问器 LowerMemrefToMlo::getSequencerType (@0x13507760),它把该字符串读回,使后续 pass 选择匹配的每引擎 bundle codec(SparseCoreScsCodecBase / …TacCodecBase / …TecCodecBase)。

支撑这一切的是 tpu::TpuSequencerType C++ 枚举及其 TpuSequencerTypeToString 跳转表,也就是用于确定每引擎资源数组大小的运行时编号。该 C++ 枚举给 SparseCore 引擎的编号是 {SCS=3, TAC=4, TEC=5},与 codec 模板参数编号完全相同。存在差一的对应方,但它是 protobuf 枚举 TpuSequencerTypeProto:该枚举插入了一个 INVALID=0 槽,因此编号为 {SCS=4, TAC=5, TEC=6}TpuSequencerTypeFromProto switch 会减一,从 proto 跨到 C++ 枚举。本页统一解释这三者。

重新实现契约:

  • op 级引擎标签是字符串,不是数字。 每个 op 的引擎归属是其所在 outline 函数上的 sc.sequencer StringAttr,三个已按字节确认的值只能是 "scs" / "access" / "execute"。重新实现者必须按字符串路由;两个数值型 TpuSequencerType 枚举都不会出现在 op 本身
  • getSequencerType 是访问器,不是决策。 LowerMemrefToMlo::getSequencerType(Operation&) 返回 optional<StringRef>:它读取 sc.sequencer(先查 inherent attr,再回退到 dictionary attr)并返回其值,或返回 nullopt。决策已在上游由 GetTransferKind + outlining 完成。
  • Stream-vs-DMA 由 memory spaces + 一个能力位决定。 GetTransferKind 会规范化每个 mlir::sparse_core::MemorySpace,按源空间跳转表分派,并且只有当目标空间位于 HBM/SPMEM/TILE_SPMEM gather 集合(bitmask 0x210018 / 0x210004)且 target 报告 SparseCore-variable 能力(vtable[+0xa0],在此 wheel 中为 0)时才设置 kStream;否则为 kDma,非法 pair 会给出 InvalidArgument 诊断。
  • 有两套编号,相差一,但分界在 proto-vs-C++,不是 runtime-vs-codec。 C++ tpu::TpuSequencerType 枚举(TpuSequencerTypeToString 渲染的枚举,且也是 codec EncoderBase 非类型模板参数使用的枚举)编号为 {TC=0, BARNA=1, BARNA_ADDR=2, SCS=3, TAC=4, TEC=5}——索引 0 没有 INVALID 槽,SCS 是 3,与 codec 模板相同。差一的对应方是 protobuf 枚举 TpuSequencerTypeProto,它确实以 INVALID=0 开始,因此编号为 {TC=1, BARNA=2, BARNA_ADDR=3, SCS=4, TAC=5, TEC=6, SCv0=7/8}TpuSequencerTypeFromProto (@0x20b36300) 是执行减一的桥(proto 4 → C++ 3,等等)。gfc (6acc60406) 家族只携带 codec 参数 3 和 5,其 SparseCoreTacCodecBase 完全不存在。
它是什么SparseCore op→engine 分配机制(SCS / TAC / TEC)
op 级标签sc.sequencer StringAttr(12 字符名称);值为 "scs" / "access" / "execute"
访问器LowerMemrefToMlo::getSequencerType @0x13507760optional<StringRef>
策略分类器xla::tpu::sparse_core::GetTransferKind @0x1351b140(kStream vs kDma)
OutlinerLowerSequencerFunctionsPass::runOnOperation @0x13532120OutlineSequencerFunction
枚举tpu::TpuSequencerTypeTpuSequencerTypeToString @0x20b362e0 over off_22010DE0
C++ 枚举(ToString + codec)TC=0 · BARNA=1 · BARNA_ADDR=2 · SCS=3 · TAC=4 · TEC=5(无 INVALID 槽)
Codec 模板枚举SCS=3 · TAC=4 · TEC=5EncoderBase<…, TpuSequencerType=N>)——与 C++ 枚举相同
Proto 枚举(差一对应方)TpuSequencerTypeProto: INVALID=0 · TC=1 · BARNA=2 · BARNA_ADDR=3 · SCS=4 · TAC=5 · TEC=6 · SCv0=7/8
6acc60406 (gfc)无 TAC:gfc::…SparseCoreTacCodecBase = 0 个文件;只有 codec 参数 3 与 5
置信度CONFIRMED(由函数字节 / 符号 / 字符串表锚定),除非某行另有说明

引擎角色本身见 SparseCore 概览架构;outliner 产物见 Region → Sequencer Outliner


三层机制概览

一个 off-tile memory op(tpu.enqueue_dmatpu.enqueue_indirect_dma)在进入 sequencer 专属 bundle 前会经过三层决策:

text
  tpu.enqueue_dma / tpu.enqueue_indirect_dma        (TC-framework op, has memref operands)

            ▼  LowerMemrefToMlo::lowerEnqueueDma         @0x135105a0
               LowerMemrefToMlo::lowerEnqueueIndirectDma @0x13511da0

            ▼  getTransferKind<EnqueueDMAOp> @0x135114a0  →  GetTransferKind @0x1351b140
            │      (srcMemSpace, dstMemSpace, local/remote bits, capability)

        ┌───┴────────────────────────┐
   kStream  ([result+8]=1)        kDma  ([result+8]=0 / InvalidArgument)
   gather/scatter slot            SparseCoreDma bulk slot
            │                              │
            ▼                              ▼
        emitted into a TileTask region (Access vs Execute)

            ▼  LowerSequencerFunctionsPass / OutlineSequencerFunction
               stamps the outlined func.func with:
                 sc.sequencer = "scs"     (control sequencer  → SCS)
                 sc.sequencer = "access"  (tile-fetch / gather → TAC)   [VF/GL only]
                 sc.sequencer = "execute" (vector compute      → TEC)

            ▼  later passes:
               LowerMemrefToMlo::getSequencerType(op) @0x13507760  → reads sc.sequencer
               → select per-engine codec: SparseCoreScsCodecBase / …Tac… / …Tec…
```text

第 1 层(`GetTransferKind`)回答“这是 gather/scatter 还是 bulk copy?”。第 2 层(outliner)回答“这个 op 属于哪个引擎的程序?”,并把答案写成字符串。第 3 层(`getSequencerType`)是驱动 codec 选择的读回。三个数值型 `TpuSequencerType` 值只存在于*资源定尺*层(每引擎 bundle-limit 表),从不出现在 op 层。

> **注意 — 并不存在一个根据 op opcode 返回 SCS/TAC/TEC 的 `getSequencerType`。** 如果重新实现者期待一个 `switch(op.kind)` 选择器,是找不到的。op→engine 映射已经完全物化为*被 outline 出来的函数*上的 `sc.sequencer` 字符串;op 从其被 outline 进入的函数继承引擎(由下面的 `ParentFuncHasCoreSequencerTypeAttribute` trait 强制)。`getSequencerType` 只会重新读取该字符串。

---

## 第 3 层:`getSequencerType` 访问器

具名函数是三层里最简单的一层,也是本页标题所指的函数。反编译结果(`@0x13507760`)显示,它是一个字符串属性 getter,返回 17 字节的 `optional<StringRef>`(8 字节 data ptr、8 字节 length、1 字节 present flag):

```c
// mlir::tpu::LowerMemrefToMlo::getSequencerType(this=result, op)  @0x13507760
//   result layout: [0]=StringRef.data, [8]=StringRef.size, [16]=present
optional<StringRef> getSequencerType(Operation& op) {
  // 1. fast path: inherent attr if the op's registered-info bit is set
  //    (*((u32*)op + 11) >= 0x1000000) AND getInherentAttr succeeds
  Attribute a = op.getInherentAttr("sc.sequencer", /*len=*/12);
  // 2. fallback: dictionary attr lookup on the op's attr dict (op + 56)
  if (!a) a = op.getDiscardableAttrDictionary().get("sc.sequencer", 12);
  // 3. must be a StringAttr (TypeID check against StringAttr::id)
  if (a && typeid(a) == StringAttr::id) {
    result = { StringAttr::getValue(a), /*present=*/1 };   // "scs"/"access"/"execute"
  } else {
    result = { /*present=*/0 };                            // nullopt
  }
  return result;
}

需要保留两个结构事实:

  • 属性名称是 12 字符字符串 "sc.sequencer"(该字面量和长度 12 被烘进反编译体中的 getInherentAttr(a2, "sc.sequencer", 12) 调用)。相同的 name+length pair 也出现在 HasCoreSequencerTypeAttributeHasExecuteSequencerTypeAttribute(见下文)中,确认三者读取的是同一属性。
  • 两步 inherent→dictionary 查找镜像了 MLIR 在固有属性(在 op 定义上声明)与可丢弃字典属性之间的拆分。访问器接受任一来源,因此 outliner 可以用对 op kind 方便的路径附加 sc.sequencer
属性
Function VA0x13507760
属性名 / 长度"sc.sequencer" / 12
返回类型optional<StringRef>(data、size、+16 处 present-byte)
查找顺序inherent attr,然后 discardable dictionary attr
类型保护StringAttr TypeID(TypeIDResolver<StringAttr>::id
返回值"scs" / "access" / "execute"

属性值 — 按字节确认

三个合法的 sc.sequencer 值不只是表里的字符串;其中两个会被专门的谓词函数匹配,其反编译字节比较钉住了精确拼写和长度。ScDialect::HasCoreSequencerTypeAttribute (@0x14599ec0) 和 ScDialect::HasExecuteSequencerTypeAttribute (@0x1459a020) 都复用同一个 sc.sequencer(len-12)访问器,然后把 StringAttr 值与长度及 packed byte literal 比较:

c
// HasCoreSequencerTypeAttribute @0x14599ec0  — value == "scs"
if (len == 3)
  return ( (*(u16*)v ^ 0x6373) | (*(u8*)(v+2) ^ 0x73) ) == 0;   // 's','c' | 's'
//   0x6373 LE = bytes {0x73='s', 0x63='c'};  v[2]=0x73='s'  →  "scs"

// HasExecuteSequencerTypeAttribute @0x1459a020 — value == "execute"
if (len == 7)
  return ( (*(u32*)v ^ 0x63657865) | (*(u32*)(v+3) ^ 0x65747563) ) == 0;
//   0x63657865 LE = {'e','x','e','c'};  (v+3) 0x65747563 LE = {'c','u','t','e'}
//   overlapping at offset 3  →  "exec"+"cute" = "execute"
```text

解码 little-endian mask:

| `sc.sequencer` 值 | 引擎 | 长度 | Byte-literal 证据 | 谓词 |
|---|---|:---:|---|---|
| `"scs"` | **SCS**(标量控制) | 3 | `0x6373`="sc", `0x73`="s" | `HasCoreSequencerTypeAttribute` `@0x14599ec0` |
| `"execute"` | **TEC**(向量计算) | 7 | `0x63657865`="exec", `0x65747563`="cute" | `HasExecuteSequencerTypeAttribute` `@0x1459a020` |
| `"access"` | **TAC**(tile-access / DMA) | 6 | —(无专用 `Has*` 谓词) ||

> **陷阱 — `"access"` 没有专用谓词。** SCS 与 TEC 各有一个 `Has…SequencerTypeAttribute` 测试,因为 SC-MLO pipeline 会在 SCS↔TEC 边界上运行;第三个值 `"access"`(TAC)在此二进制文件中没有对应的 `Has*` 函数。这与 6acc60406 (`gfc`) 家族已完全删除 TAC 一致:在最新代上,本应落入 `"access"` 函数的工作会折叠进 `"execute"` 函数(见 [TAC 引擎](tac-engine.md) 和 [Region → Sequencer Outliner](region-to-sequencer-outliner.md))。只建模 SCS/TEC pair 的重新实现者可以生成正确的 6acc60406 代码;只有 Viperfish/Ghostlite 需要 `"access"` 值。

### 父函数 trait

`sc.sequencer` 属性位于*被 outline 出来的函数*上,而不是单个 op 上。要求该属性存在的 op 会带有 `OpTrait::ParentFuncHasCoreSequencerTypeAttribute` trait(已在 `TileTaskWaitOp` `@0x14689880` 验证;同一 trait 附着于 TileTask family)。共享检查是 `ParentHasSequencerTypeAttribute` (`@0x1353e980`):

```c
// ParentHasSequencerTypeAttribute @0x1353e980
//   walk parent ops until the enclosing LLVMFuncOp, then test BOTH predicates
for (op = start; ; op = op->getBlock()->getParentOp()) {
  if (!op) return false;
  if (typeid(*op) == LLVMFuncOp::id) break;          // reached the outlined func
}
h_core = HasCoreSequencerTypeAttribute(func);        // "scs"?
h_exec = HasExecuteSequencerTypeAttribute(func);     // "execute"?
// require BOTH predicate calls to have evaluated (present bit 0x100 set on each),
// then return their OR of the low (match) bits
return (h_core & 0x100) && (h_exec & 0x100) ? (h_core | h_exec) & 1 : false;

因此该 trait 会沿 op 树上爬到外围 LLVM::LLVMFuncOp,并断言该函数已标记为 "scs""execute"。这是二进制文件中对每个 TileTask op 都在 verify 时已知其引擎的函数内运行的强制;引擎归属是函数作用域属性,不是 per-op 字段。


第 1 层:GetTransferKind — Stream vs DMA

在 op 能被 outline 进某个引擎前,lowering 必须决定它是 Stream(间接 gather/scatter,embedding datapath)还是 DMA(连续块移动)。这就是 xla::tpu::sparse_core::GetTransferKind (@0x1351b140),它通过类型化 wrapper getTransferKind<EnqueueDMAOp> (@0x135114a0) 和 getTransferKind<WaitDMA2Op> (@0x135145e0),从 LowerMemrefToMlo::lowerEnqueueDma (@0x135105a0) 以及 lowerEnqueueIndirectDma (@0x13511da0) 到达。

其签名(demangled)为:GetTransferKind(const jellyfish::Target&, mlir::sparse_core::MemorySpace src, MemorySpace dst, bool, bool, bool, bool),返回 FailureOr<TransferKind>。反编译主体:

c
// GetTransferKind @0x1351b140  (args: target a2; src a3; dst a4;
//   a5=src-local, a6=dst-local, a7=capability-allowed-flag, a8=strict-ordering)
// 1. normalize spmem: a space encoded as 1 maps to (16 if a7 else 21)
if (src == 1) src = 5*(a7 ^ 1) + 16;       // → 16 (cap) or 21 (no cap)
if (dst == 1) dst = 5*(a7 ^ 1) + 16;
// 2. kStream only when BOTH endpoints are local (a6 & a5 == 1)
if ((a6 & a5) == 1) {
  switch (src) {                            // jump table on the source memory space
    case 2:  /* HBM   */  ... if (dst<=0x15 && bittest(0x210018, dst)) ok;   // 2162712
                          else if (dst==6) ok only if target.vtable[+0xa0]()  // SupportsScVar
    case 3:  /* HBM_4B*/  ... if (dst<=0x15 && bittest(0x210004, dst)) ok;    // 2162692
    case 4:  case 21:     ... if (dst==2) ok;                                 // → HBM
    case 6:               ... ok only if a7 && dst==2 && target.vtable[+0xa0]()
    case 16: /* SPMEM */  ... if (a7 && ((dst-2)&~2)==0) ok;                  // dst in {2,4}
    default:              goto kDma;
  }
  result.kind = kStream; result.present = 1;   // [this+8]=1; [this]=1
  return;
}
// 3. otherwise kDma — but only for a recognized legal contiguous pair;
//    an unrecognized pair builds an InvalidArgument status
kDma:
  if (legal_dma_pair(src, dst, a6)) { result.kind = kDma; [this]=1; return; }
  // diagnostic (transfer_emitter.cc:196):
  return InvalidArgument(
    "SparseCore does not support transfers with %s ordering from %s %v to %s %v "
    "issued %sfrom TEC.", ordering, srcLocality, src, dstLocality, dst, fromTec);
```text

这些路由常量对重新实现很重要:

| 机制 || 含义 |
|---|---|---|
| spmem 规范化 | `src/dst==15*(¬cap)+16` | encoded `1` → 16(cap)或 21(no cap) |
| both-local gate | `(dst_local & src_local) == 1` | kStream 要求两个端点均为 local |
| HBM dst-set bitmask | `0x210018` (2162712) over `dst` | 来自 HBM 源的可 gather 目标 |
| HBM_4B dst-set bitmask | `0x210004` (2162692) over `dst` | 来自 HBM_4B 的可 gather 目标 |
| 能力槽 | `target` vtable `+0xa0` | `SupportsScVar` 谓词 |
| kStream 结果 | `[result+0]=1, [result+8]=1` | FailureOr success + `kind=kStream` |
| kDma 结果 | `[result+0]=1, [result+8]=0` | FailureOr success + `kind=kDma` |
| illegal-pair diag | `transfer_emitter.cc:196` `InvalidArgument` | "SparseCore does not support transfers…" |

> **注意 — 该能力位是 `SupportsScVar` 谓词,并且在此 wheel 中为 0** 对 `target.vtable[+0xa0]()` 设门的 case(`src==6` 以及 HBM 的 `dst==6` 子分支)会调用 `SparseCoreTarget` 上的虚方法;交叉引用把此槽解析为 `SupportsScVar`(Ghostlite `0x1d499340`,Viperfish `0x1d49c7e0`),在这里发布的每一代上都返回 0。因此这些由能力设门的 Stream 路径在此 build 中被*编译掉*,会落入 kDma 路径。面向这些芯片的重新实现者必须把 `SupportsScVar` 视为 false
>
> **陷阱 — 诊断文本说 "from TEC",确认 kDma/Stream 拆分以 TEC 为中心。** `transfer_emitter.cc:196` 消息("…issued %sfrom TEC")和 `MemorySpace` 操作数表明分类器是在推理*从 TEC 向量引擎发出的*传输。这直接对应 [IndirectVregStream](indirect-vreg-stream.md) 是一种仅 TEC 使用的 Stream 形式:gather/scatter datapath 锚定在 TEC 上,而 `GetTransferKind` 是决定 off-tile 移动使用该 datapath(kStream)还是普通 bulk descriptor(kDma)的 gate。

完整 memory-space 枚举以及按 (core,mem) 解析 DMA-destination 的细节不在本文范围内;Stream-slot descriptor 见 [Stream Gather/Scatter](stream-gather-scatter.md),这些 lowering 在 pass 顺序中的位置见 [SC 后端 Pipeline](sc-backend-pipeline.md)。

---

## 第 2 层:Region Outlining — `sc.sequencer` 写入处

`GetTransferKind` 的 kStream/kDma 结果会结合 op 的数据依赖,决定某个 op 被发射到哪个 TileTask region 中。随后 `LowerSequencerFunctionsPass::runOnOperation` (`@0x13532120`) 把每个 region outline 成独立的 `LLVM::LLVMFuncOp`,并给它打上 `sc.sequencer` 字符串。反编译后的 pass 主体很大(它还通过 `GetParameterTable` `@0x13534ec0` 构建每引擎参数表,并通过 `LoadPointersFromHbm` `@0x13536c40` 加载 HBM 指针);引擎打标步骤是附加 StringAttr 的 `OutlineSequencerFunction` 回调。

outliner 产出的映射:

| TileTask region | `sc.sequencer` | 引擎 | 承载内容 | 存在于 |
|---|---|---|---|---|
| Control / sequencer | `"scs"` | **SCS** | program counter、addressing、sync-flag/atomic issue、SCS Stream/DMA slots | vfc · glc · gfc |
| Access | `"access"` | **TAC** | tile-fetch DMA issue、gather-stream issue、address staging | vfc · glc only |
| Execute | `"execute"` | **TEC** | vector reductions、pack/unpack、TEC Stream slot(incl. IndirectVreg) | vfc · glc · gfc |

> **注意 — MLIR tile-task pipeline 只发射 `"scs"` 与 `"execute"`;`"access"` 是 codec/proto 路径上的引擎。** 上面的 `"access"` 行是 *`TpuSequencerType=4` TAC codec* 服务的引擎,并且在 Viperfish/Ghostlite 上真实存在。但 outlining 回调(`0x136066e0`)的反编译显示它只引用 `"execute"` 值字符串(`@0x8681624`,7 chars)和 `sc.sequencer`:它在**每一代**上都无条件把 tile body 标为 `"execute"`,从不写入 `"access"`(不存在 `HasAccessSequencerTypeAttribute` 谓词,也没有任何 lowering 链中的 length-6 `"access"` compare)。TAC 引擎通过 legacy `ProgramWrapper.tac` proto 字段和独立的 `SparseCoreTacCodecBase` 到达,而不是通过 MLIR tile-task outliner。因此在 VF/GL 上,*MLIR* 路径同样通过 TEC Stream slot 把 gather/scatter 折叠进 `"execute"`(TEC)函数——这与 6acc60406 的形态相同,后者还完全没有 TAC codec。完整的字节级说明见 [TEC 引擎](tec-engine.md),支撑这一点的 TEC-exclusive Stream 形式见 [IndirectVregStream](indirect-vreg-stream.md)。

对于给定 lowering 后 op,选择 Access region 还是 Execute region 的精确 per-op 规则没有在本分析中逐字节追踪(`GetTransferKind` 结果加上 op 的 tile-data 依赖会输入其中)。这里标为 LOW,并由 [Region → Sequencer Outliner](region-to-sequencer-outliner.md) 负责。

---

## `TpuSequencerType` 枚举及其跳转表

字符串机制底下是数值型 `tpu::TpuSequencerType` 枚举,用于确定每引擎资源表(例如 bundle limit)的大小与索引。`TpuSequencerTypeToString` (`@0x20b362e0`) 会把它渲染成文本;该函数是一个纯跳转表:

```c
// tpu::TpuSequencerTypeToString(unsigned a1)  @0x20b362e0
__int64 TpuSequencerTypeToString(unsigned a1) {
  return (__int64) *(&off_22010DE0 + a1);   // indexed array of C-string pointers
}

off_22010DE0 表直接索引到 .rodata 中确认的字符串表字面量(通过数组的 R_X86_64_RELATIVE relocation 解析)。其顺序确定了 C++ 运行时编号,并且第一个条目是 TensorCore sequencer,不是 INVALID 占位符:

C++ 枚举值(表索引)off_22010DE0[i] 字符串字面量简称Bundle
0"TensorCoreSequencer"TC
1"BarnaCoreSequencer"Barna
2"BarnaCoreAddressHandler"Barna-AH
3"SparseCoreSequencer"SCS32 B
4"SparseCoreTileAccessCoreSequencer"TAC64 B
5"SparseCoreTileExecuteCoreSequencer"TEC64 B

off_22010DE0 数组正好有这六个 SparseCore/TensorCore/BarnaCore 条目;索引 6 已属于相邻的无关字符串数组("IMEM")。因此 C++ 枚举没有 INVALID 槽,也没有 SCv0 条目;SCv0 只存在于 protobuf 枚举(见下文)。解析 relocation:索引 0 → 0x85b767c "TensorCoreSequencer",索引 3 → 0x85b76b3 "SparseCoreSequencer",索引 5 → 0x85b7690 "SparseCoreTileExecuteCoreSequencer"

差一之处:C++/codec 枚举 vs protobuf 枚举

codec 层与 TpuSequencerTypeToString 层共享同一个 C++ 枚举。每引擎 codec 以 TpuSequencerType 作为非类型模板参数进行模板化:EncoderBase<…SparseCore{Scs,Tac,Tec}CodecBase…, TpuSequencerType=N>;这里的值为 {SCS=3, TAC=4, TEC=5},与上面的 off_22010DE0 索引完全匹配(nm 显示 demangled 模板字面量 (TpuSequencerType)3 ×32 与 (TpuSequencerType)4 ×16;codec-metadata BundleSizeBytes(TpuVersion, TpuSequencerType) @0x1ecf7180 消费同一个 C++ 枚举)。codec 与 runtime 之间没有差一,两者都是 {SCS=3, TAC=4, TEC=5}

差一发生在第三套编号上:protobuf 枚举 TpuSequencerTypeProto.rodata 中的描述符 TpuSequencerTypeProto,完整 TPU_SEQUENCER_TYPE_* 字面量)。它确实INVALID=0 开头,并编号为 {INVALID=0, TC=1, BARNA=2, BARNA_ADDR=3, SCS=4, TAC=5, TEC=6, SCv0=7, SCv0-AH=8}。proto→C++ 桥是 tpu::TpuSequencerTypeFromProto (@0x20b36300);其 switch 映射 proto 1→0, 2→1, 3→2, 4→3, 5→4, 6→5(SparseCore 块上就是字面减一),并且Invalid sequencer type 拒绝 proto 7/8(SCv0),确认 SCv0 完全没有 C++ 枚举值:

引擎sc.sequencer 字符串C++ 枚举(ToString + codec)Proto 枚举
SCS"scs"34
TAC"access"4(仅 vfc/glc)5
TEC"execute"56

陷阱 — +1 发生在 proto 边界,而不是 codec 边界。 对同一引擎而言,protobuf TpuSequencerTypeProto 值比 C++ TpuSequencerType(以及 codec 模板)值大一,因为只有 proto 枚举保留了 INVALID=0TpuSequencerTypeFromProto @0x20b36300 是受认可的转换点(减一)。把 proto ordinal 直接送入 codec 模板选择器的重新实现会选错引擎;把 C++ 枚举直接传过去则是正确的。op 级分配不使用任何数字,而是使用 sc.sequencer 字符串。

Codec-base 存在性确认 gfc 缺失 TAC

按 family namespace 统计反编译出的 SparseCore{Scs,Tac,Tec}CodecBase 实例化文件,直接确认了差一表所暗示的 TAC 移除(gfc 只携带 codec 参数 3 与 5,从不携带 4):

Family ns代际SparseCoreScsCodecBase 文件SparseCoreTacCodecBase 文件SparseCoreTecCodecBase 文件
vfcViperfish131313
glcGhostlite303030
gfc6acc6040631032

6acc60406 (gfc) namespace 中 SparseCoreTacCodecBase 文件为,而 Viperfish/Ghostlite 分别为 13/30;SCS 与 TEC codec base 则存在。gfc 中没有以 TpuSequencerType=4 参数化的 codec,因此运行时在 6acc60406 上永远无法选择 TAC 引擎,"access" sequencer 值在那里不可达——这正是第 2 层记录的折叠。


SCv0 — 仅枚举保留

两个尾部 proto 值(TPU_SEQUENCER_TYPE_SPARSE_CORE_V0_SEQUENCER = 7,…_V0_ADDRESS_HANDLER = 8)命名的是旧的单体 SparseCore 前身。它们在此 build 中仅作为 TpuSequencerTypeProto 上的 proto-descriptor 字面量保留:它们不在 off_22010DE0 C++ TpuSequencerTypeToString 表中(该表在 TEC,即索引 5 处停止),并且 TpuSequencerTypeFromProto @0x20b36300 会以 Invalid sequencer type 拒绝它们,因此它们完全没有 C++ TpuSequencerType 值。没有任何 SCv0 codec、encoder、decoder 或 sc.sequencer 值(如 "scs0" 等)随此 build 发布。引擎选择机制永远不会产生 SCv0 标签;getSequencerType 只返回三个 live 值。完整的 SCv0 废弃说明见 SparseCore 概览


重新实现清单

要复现 SparseCore 引擎选择:

  1. sc.sequencer 建模为函数作用域 StringAttr,合法值恰好为 "scs" / "access" / "execute" 三个。在 outlining 期间附加它;不要逐 op 附加。通过父函数 trait 在 TileTask op 上强制它存在。
  2. getSequencerType 实现为纯访问器:先查 inherent-attr,回退到 dictionary-attr,进行 StringAttr 类型保护,返回 optional<StringRef>。它不做任何决策。
  3. GetTransferKind 实现为 kStream/kDma gate,包括精确的 memory-space 规范化(1 → 5*(¬cap)+16)、both-local gate、每个源空间对应的 0x210018/0x210004 目标 bitmask、SupportsScVar 能力调用(在这些芯片上为 false),以及非法 pair 的 InvalidArgument fallback。
  4. 按 region outline,并在 TAC 不存在时把 "access" 折叠到 "execute"(6acc60406 / gfc 家族)。是否存在 "access" 函数应由 target 是否携带 SparseCoreTacCodecBase 设门。
  5. 分离 proto 与 C++ 编号:C++ TpuSequencerType {SCS=3,TAC=4,TEC=5} 同时用于资源定尺表TpuSequencerTypeToString、codec-metadata)和 codec 选择(模板参数),因此这两者之间无需转换。唯一的 +1 在 protobuf 边界:TpuSequencerTypeProto {SCS=4,TAC=5,TEC=6} 在索引任何以 C++ 枚举为 key 的表前,必须经过 TpuSequencerTypeFromProto(减一)。

置信度摘要

论断证据
getSequencerType 是返回 optional<StringRef> 的属性访问器反编译 @0x13507760:inherent→dictionary sc.sequencer 查找,StringAttr guard
属性名是 "sc.sequencer"(12 字符)三个 reader 函数中的 getInherentAttr(…, 12) 字面量
"scs" → SCS,"execute" → TEC,"access" → TACHasCore… @0x14599ec0 / HasExecute… @0x1459a020 中的 byte-literal compare;"access" 是第三个值(无谓词)
引擎标签是函数作用域;op 通过 parent-func trait 继承ParentHasSequencerTypeAttribute @0x1353e980 上爬到 LLVMFuncOp;trait 已在 TileTaskWaitOp @0x14689880 验证
GetTransferKind 根据 memory-space pair + capability 选择 kStream vs kDma反编译 @0x1351b140:both-local gate、bitmask 0x210018/0x210004vtable[+0xa0]transfer_emitter.cc:196 diag
SupportsScVar 能力在这些代际上为 0(能力设门的 Stream 路径被编译掉)vtable[+0xa0] 解析为 SupportsScVar(GL 0x1d499340 / VF 0x1d49c7e0),=0
Feeder:lowerEnqueueDma @0x135105a0lowerEnqueueIndirectDma @0x13511da0getTransferKind<…> @0x135114a0/@0x135145e0demangled 反编译符号存在
Outliner 按 region 写入 sc.sequencerLowerSequencerFunctionsPass::runOnOperation @0x13532120 + OutlineSequencerFunction
6acc60406 (gfc) 把 "access" 折叠到 "execute"(无 TAC)gfc::…SparseCoreTacCodecBase = 0 个文件 vs 13/30;无 HasAccessSequencerTypeAttribute
TpuSequencerTypeToString 是 over off_22010DE0 的跳转表反编译 @0x20b362e0*(&off_22010DE0 + a1)
C++ 枚举顺序 via off_22010DE0 = {TC=0, BARNA=1, BARNA_ADDR=2, SCS=3, TAC=4, TEC=5};无 INVALID 槽R_X86_64_RELATIVE relocation 解析 idx0→"TensorCoreSequencer"、idx3→"SparseCoreSequencer"、idx5→"SparseCoreTileExecuteCoreSequencer";idx6→"IMEM"(相邻表)
Codec 模板枚举 {SCS=3,TAC=4,TEC=5} == C++ 枚举(没有差一)nm (TpuSequencerType)3×32 / )4×16;codec-metadata BundleSizeBytes @0x1ecf7180 以同一 C++ 枚举为 key
Proto 枚举 {INVALID=0…SCS=4,TAC=5,TEC=6,SCv0=7/8};相对 C++ 枚举 +1,并由 TpuSequencerTypeFromProto 桥接TpuSequencerTypeProto descriptor literals;FromProto @0x20b36300 switch 映射 proto 4→3,5→4,6→5,拒绝 7/8
per-op Access-vs-Execute region 规则;runtime→codec 转换点本分析未逐字节追踪

交叉引用

  • SparseCore 概览 — 三类引擎、每代存在性以及 SCv0 仅枚举保留的故事。
  • 架构 — 引擎角色,以及 engine split 服务的 embedding datapath。
  • SCS(标量)引擎"scs" 控制 sequencer。
  • TAC 引擎"access" tile-fetch 引擎,以及它在 6acc60406 (gfc) 家族上的移除。
  • TEC(向量)引擎"execute" 向量计算引擎。
  • Region → Sequencer Outliner — 把计算划分为每引擎函数并写入 sc.sequencer 的 pass。
  • IndirectVregStream — 仅 TEC 使用的 Stream 形式,其存在把 kStream datapath 锚定在 TEC 上。
  • Stream Gather/Scatter — kStream 路径到达的 indirect-DMA descriptor。
  • SC 后端 PipelineGetTransferKind、outlining 与 getSequencerType 在 SparseCore pass 顺序中的位置。
  • Binary: extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d
  • 索引条目: Part IX — SparseCore & BarnaCore / SparseCore engines — 返回索引