From 38fc985b9596cef2780412b6473adc6875b5df40 Mon Sep 17 00:00:00 2001 From: HecreReed <821896444@qq.com> Date: Tue, 30 Jun 2026 15:23:17 +0800 Subject: [PATCH] docs: draft fixpipe tpipe design --- docs/designs/fixpipe-tpipe-design.md | 797 +++++++++++++++++++++++++++ 1 file changed, 797 insertions(+) create mode 100644 docs/designs/fixpipe-tpipe-design.md diff --git a/docs/designs/fixpipe-tpipe-design.md b/docs/designs/fixpipe-tpipe-design.md new file mode 100644 index 000000000..08f4ed04f --- /dev/null +++ b/docs/designs/fixpipe-tpipe-design.md @@ -0,0 +1,797 @@ +# `tpipe` 支持 `fixpipe` 接口设计 + +## 1. 背景 + +最新 `pto-isa` 已提供 `TPUSH(pipe, tile)` 这一类 +fixpipe 接口。其中 `TConfig` 是 `TPUSH` 的模板参数,通常使用 +`FixpipeParams<...>` 表达,配置项至少包括: + +- `LayoutMode_t` +- `QuantMode_t` +- `ReluPreMode` + +并且当前 `FixpipeParams` 还继续承载: + +- `STPhase` +- `SubBlockId` +- `AtomicType` +- `ClipReluMode_t` +- `IsChannelSplit` + +典型写法如下: + +```cpp +using MyConfig = FixpipeParams< + LayoutMode_t::NZ2ND, + QuantMode_t::DEQF16, + ReluPreMode::NormalRelu>; +Pipe pipe(fifoMem, 0x0, 0x0); +AccTile accTile; +TASSIGN(accTile, 0x0); + +TPUSH(pipe, accTile); +``` + +这说明 `pto-isa` 的 C++ 用户接口已经允许“通过 `TPUSH` 的模板参数指定 +fixpipe 行为”。 + +但如果 PTOAS 直接把这套形状一比一投影到前端 IR,把 fixpipe config 绑定到每一条 +`tpush` 上,会引出两个抽象问题: + +- `TPOP` 端没有对称的 `TConfig` 接口,无法从消费侧表达“这一条 pipe entry 是按什么 + fixpipe 语义生产出来的” +- 同一条逻辑 pipe 上如果允许多次 `tpush` 带不同 config,consumer 很难在 IR 层证明 + 该 pipe 的 entry 类型、layout、quant 语义和同步契约始终一致 + +因此,这个设计文档讨论的不是“是否支持 fixpipe over tpipe”,而是“PTOAS 应该把 +fixpipe config 挂在哪一层”。 + +## 2. 目标 + +本文目标如下: + +- 在 PTOAS 中为 `tpipe` 增加对 fixpipe 型 `TPUSH` 的语义承载能力 +- 在 EmitC 层保持与 `pto-isa` 现有 `TPUSH<..., TConfig>` 调用形状兼容 +- 明确 PTOIR 当前只暴露“前端公开配置、且影响 pipe 公共语义”的 fixpipe 参数 +- 避免把 fixpipe config 建模成“某一次 `tpush` 的私有属性” +- 让 `TPOP` 端能够通过 pipe 级契约间接知道 entry 的生产语义 +- 第一版 `initialize_pipe` 只显式承载一个前端公开复合 attr: + `acc_push_epilogue` +- `acc_push_epilogue` 内部包含三个独立配置维度: + - `layout` + - `quant` + - `relu` +- 从第一版起明确覆盖 vector quant,而不是只覆盖 no-quant 或 scalar quant + +## 3. 现状 + +### 3.1 `pto-isa` 现状 + +`pto-isa` 当前公开语义存在以下不对称: + +- `TPUSH` 有 `TileSplitAxis` 重载 +- `TPUSH` 有 `GlobalData` 重载 +- `TPUSH` 有 `TConfig` 重载 +- `TPOP` 只有 `TileSplitAxis` / `GlobalData` 重载 +- `TPOP` 没有 `TConfig` 重载 + +因此,`TConfig` 更像 producer-side fixpipe 写入方式,而不是一次完整 pipe +transaction 的双端公共契约。 + +### 3.2 PTOAS 当前 pipe 设计现状 + +PTOAS 已经有前端 pipe 抽象: + +- `pto.aic_initialize_pipe` +- `pto.aiv_initialize_pipe` +- `pto.tpush_to_aiv` +- `pto.tpush_to_aic` +- `pto.tpop_from_aic` +- `pto.tpop_from_aiv` + +当前设计中: + +- `initialize_pipe` 负责声明 pipe 级配置 +- `tpush/tpop/tfree` 只带 `id + split` +- `nosplit` 已经是 pipe 级属性,而不是逐条 transfer op 属性 + +这和 `pto-isa` 的 `TPipe<..., IsNoSplit>` 语义是同一件事的两层表达: + +- PTOAS 前端用 `nosplit = true/false` 暴露给用户 +- lowering 时再映射为 `TPipe<..., IsNoSplit = true/false>` + +这说明 PTOAS 当前前端 IR 的总体方向,本来就更偏向“per pipe contract”,而不是 +“per transfer config”。 + +## 4. 核心判断 + +### 4.1 挂载层级 + +把 fixpipe config 建模为 **per pipe**,而不是 **per tpush op**。 + +也就是说: + +- PTOAS 前端 IR / PTOIR 中,fixpipe config 绑定到 `initialize_pipe` + 所定义的逻辑 pipe +- lowering 到 EmitC 时,再把该 pipe 的 fixpipe config 转写成具体的 + `TPUSH(pipe, tile)` 模板调用 + +这里的 **per pipe** 只表示“同一条 pipe 内语义稳定”,并不要求“同一个 kernel +里所有 fixpipe pipe 共用同一组 epilogue 配置”。 + +同一 kernel 中可以同时存在多条开启 `acc_push_epilogue` 的 pipe,并且它们的: + +- `layout` 可以不同 +- `quant` 可以不同 +- `relu` 可以不同 + +稳定性约束只在“单条 pipe 内”成立。 + +### 4.2 不用 per-`tpush` + +使用per-tpush会产生问题: + +- 同一条 pipe 的不同 producer push 可以携带不同 config,破坏 pipe entry 语义稳定性 +- `tpop` 无法在 op 自身上表达对应 config,只能靠隐式推导 +- verifier 很难判断 consumer tile type、layout、quant 结果类型是否与 producer 一致 +- lowering 到内部 pipe handle 后,pipe 本身不再是纯同步/地址资源,还会隐含一组逐次变化的 entry 语义 + +## 5. 承载范围 + +IR 层是否暴露某个 fixpipe 参数,不取决于 `pto-isa` 后端有没有消费它,而取决于它是否 +同时满足下面三个条件: + +- 用户在 PTOAS 前端确实可以配置它 +- 它会进入这条 pipe 的公共语义契约,而不是只影响某个 target 的实现路径 +- verifier 可以围绕它建立稳定的前端约束 + +按这个标准,第一版 PTOAS 前端 IR / PTOIR 只显式建模一个统一的 +pipe-level public contract: + +- `acc_push_epilogue` + +其内部再承载三个彼此独立的字段: + +- `layout` +- `quant` +- `relu` + +这里的“只建模三个字段”并不意味着 `FixpipeParams` 的其余模板参数只是占位。 +对照当前 `pto-isa` 实现,至少有一部分已经是生效语义: + +- A2/A3 的 fixpipe `TPUSH` 已消费 `AtomicType`、`STPhase`、`LayoutMode`、 + `QuantMode`、`ReluPreMode` +- A5 的 fixpipe `TPUSH` 已消费 `SubBlockId`、`STPhase`、`LayoutMode`、 + `QuantMode`、`ReluPreMode`;其 GM 路径还会消费 `AtomicType` + +但这些字段当前阶段暂不作为 PTOIR 前端公开配置项,原因也很直接: + +- `STPhase`、`AtomicType`、`SubBlockId` 虽然会被后端实现消费,但当前还不适合作为 + `initialize_pipe` 的前端公开配置 +- `ClipReluMode_t`、`IsChannelSplit` 目前也没有形成明确的前端公开配置需求 +- 在没有完整前端语义、verifier 和跨平台约束之前,它们不应被写进 IR surface, + 更不应成为 `initialize_pipe` 的公共 contract + +这三个字段虽然由一个复合 attr 承载,但语义上仍必须独立建模,不能折叠成单个互斥 +enum。 + +原因如下: + +- `LayoutMode_t::NZ2ND` / `NZ2DN` / `NZ2NZ` 描述 layout +- `QuantMode_t::NoQuant` / `DEQF16` / `VDEQF16` 等描述数值转换 +- `ReluPreMode::NoRelu` / `NormalRelu` 描述激活预处理 + +这三类语义可以自由组合,不是单选关系。 + +第一版暂不把其余模板参数纳入 PTOIR,原因是: + +- `STPhase`、`AtomicType`、`SubBlockId` 更接近具体 producer 执行路径、target + 特化或 lowering 选择;现阶段还不能证明它们都应统一建模为跨平台的 pipe-level + public contract +- `ClipReluMode_t`、`IsChannelSplit` 目前还缺少明确的 PTOAS 前端需求、类型推导 + 规则和 verifier 约束 +- 在这些字段正式成为 PTOIR 前端公开配置项之前,PTOAS 不应把它们“半暴露半忽略”; + 后续若确有需求,应单独补齐语义设计,再决定它们属于 `initialize_pipe` + attrs、producer-side config op,还是 target-specific lowering 选择 + +应明确: + +- `vector quant` 必须支持 +- `scalar quant` 必须支持 +- `NoQuant` 只是其中一种合法值 + +同时需要区分两层语义: + +- `acc_push_epilogue.quant` 只描述这条 pipe entry 采用哪一种量化/反量化模式 +- 真正的运行时量化参数仍来自 producer 侧额外量化配置 + +对 `DEQF16` / `VDEQF16` 这类模式,仅有 +`acc_push_epilogue.quant` 还不够;还需要与之匹配的 producer-side quant +state。该状态在 `pto-isa` 中通过: + +- `SET_QUANT_SCALAR(scalar)` +- `SET_QUANT_VECTOR(fpTile)` + +显式建立。 + +这类运行时量化参数不应折叠进 pipe attrs。它们可以在同一条 pipe 的不同 +`tpush` 之间变化,而不会改变该 pipe 的 entry layout / element type contract。 + +还要注意:PTOAS 当前已有的 `preQuantScalar : i64` 形式只服务于 +`tmov/tstore/textract/tinsert` 等直接数据搬运接口,对应的是 `pto-isa` 里 +`TMOV/TSTORE/TEXTRACT/TINSERT(..., uint64_t preQuantScalar)` 这一类接口,不等价于 +fixpipe `TPUSH` 所依赖的 `SET_QUANT_SCALAR(float)` / `SET_QUANT_VECTOR(...)` +状态,因此不建议直接复用。 + +## 6. 语义约束 + +### 6.1 适用方向 + +fixpipe-over-tpipe 仅支持: + +- producer 为 Cube +- source entry 为 `acc` tile +- 方向为 C2V + +前端语义目标需要覆盖 `pto-isa` 当前在 A2/A3 与 A5 上都已存在的 +`TPUSH` 能力,但允许后端按平台选择不同的数据路径。 + +只覆盖: + +- `pto.tpush_to_aiv` + +不覆盖: + +- `pto.tpush_to_aic` +- `global entry` 路径 +- V2C producer push + +也就是说,第一版统一的是前端 pipe contract: + +- Cube producer 生产 `acc` entry +- Vector consumer 消费 fixpipe 语义下的 C2V entry +- front-end 不把“该平台最终走 UB FIFO 还是 GM FIFO”暴露成额外用户接口差异 +- front-end 也不在 `dir_mask = 3` 的 DIR_BOTH pipe 上只对单侧方向局部挂载 + fixpipe config + +`pto-isa` 当前 `TConfig` 语义本质上描述的是 Acc producer 的 fixpipe 行为。 + +### 6.2 与 split 的关系 + +设计应规定: + +- fixpipe pipe 与 split pipe 互斥 +- 一条开启 fixpipe config 的 pipe,只允许 `split = 0` +- 也即 fixpipe pipe 必须等价于 `nosplit = true` + +对应到 `pto-isa` 后端时,这会落到 `TPipe<..., IsNoSplit = true>`;反过来, +PTOAS 中 `nosplit = false` 则对应 `TPipe<..., IsNoSplit = false>`。 + +原因不是“硬件绝对做不到所有组合”,而是: + +- `pto-isa` 当前 public API 没有 `TPUSH` 这种统一形状 +- `TPOP` 端仍只按 split 消费 +- 当前 PTOAS 若同时把 split 和 fixpipe 混进同一条 pipe,前端契约会变得不清晰 + +### 6.3 一条 pipe 的 config 稳定性 + +同一逻辑 pipe 上: + +- 只能存在一组 fixpipe config +- 所有绑定到该 pipe 的 producer `tpush` 都必须共享同一个 + `acc_push_epilogue` +- consumer `tpop` 结果类型必须与这组 pipe config 推导出的 entry type 一致 + +但这条约束不应被误解为“整个 kernel 里只能有一组 fixpipe config”。 + +不同逻辑 pipe 之间可以各自选择不同的 `acc_push_epilogue`。例如: + +- `pipe0` 使用 `layout = nz2nd, quant = deqf16_scalar, relu = normal_relu` +- `pipe1` 使用 `layout = nz2dn, quant = req8_scalar, relu = no_relu` + +这是合法的;要求只是在 `pipe0` 内始终保持 `pipe0` 的配置稳定,在 `pipe1` +内始终保持 `pipe1` 的配置稳定。 + +不允许: + +- 同一 pipe 上一次 `DEQF16`,下一次 `VDEQF16` +- 同一 pipe 上一次 `NZ2ND`,下一次 `NZ2DN` +- 同一 pipe 上混合 `NoRelu` 与 `NormalRelu` + +### 6.4 运行时量化参数 + +对 fixpipe pipe,需要把“静态 mode”和“动态 quant 参数”分开处理: + +- `acc_push_epilogue` 是 pipe-level contract +- scalar/vector quant 参数是 producer-side runtime state + +因此: + +- `NoQuant` 不要求额外 quant config op +- `DEQF16` 这类 scalar quant 模式要求在对应 `tpush` 之前存在匹配的 + producer-side scalar quant config +- `VDEQF16` 这类 vector quant 模式要求在对应 `tpush` 之前存在匹配的 + producer-side vector quant config + +这类 runtime quant state 的前端语义还应进一步明确为: + +- 它是 **per producer context** 的 producer-side config op,而不是 per pipe attr +- 底层 `pto-isa` 接口更接近 producer-side machine state;但为了消除多 pipe + 场景下“这一份 quant state 到底属于哪次 `TPUSH`”的歧义,PTOIR 第一版应主动把 + 前端 contract 收紧为“单次配对”语义 +- `pto.set_quant_scalar` 只作用于同一 producer context、同一基本块内、其后第一条 + 需要 scalar quant 的 fixpipe `TPUSH` +- `pto.set_quant_vector` 只作用于同一 producer context、同一基本块内、其后第一条 + 需要 vector quant 的 fixpipe `TPUSH` +- 一旦该 `TPUSH` 消费完对应 quant config,后续若还有新的 scalar/vector quant + `TPUSH`,必须重新显式发出新的 `pto.set_quant_*` +- 如果一个 `pto.set_quant_*` 在被下一次同类 `pto.set_quant_*` 覆盖前,可能匹配到 + 多条候选 fixpipe `TPUSH`,则该 IR 非法 +- 当 `acc_push_epilogue.quant = no_convert` 时,该次 `TPUSH` 不消费 quant state +- 因而,同一 kernel 内即使存在多条带不同 `acc_push_epilogue` 的 fixpipe pipe, + quant state 的归属也始终由“程序顺序 + 实际消费它的那条 `TPUSH`”唯一决定, + 而不是由 kernel 级全局唯一配置决定 + +这些 quant config 不应挂在 `initialize_pipe.acc_push_epilogue` 上,也不应并入 +`FixpipeParams` 的 pipe attrs。 + +当前 PTOAS 代码库里还没有与 `SET_QUANT_SCALAR` / `SET_QUANT_VECTOR` 一一对应的 +前端 pipe IR op 或 lowering 处理。因此如果第一版要真正支持 `DEQF16` / +`VDEQF16`,除了 pipe-level `acc_push_epilogue` 外,还需要补一套显式的 +producer-side quant-config 表达与 lowering。 + +建议第一版显式补两类前端 op: + +- `pto.set_quant_scalar` +- `pto.set_quant_vector` + +它们的职责不是修改 pipe attrs,而是在 producer 侧建立“后续某次 fixpipe +`TPUSH` 要消费的 runtime quant state”。 + +## 7. IR 形状 + +### 7.1 前端 IR + +推荐把 fixpipe 配置挂到 `initialize_pipe`: + +```mlir +pto.aic_initialize_pipe { + id = 0, + dir_mask = 1, + slot_size = 1024, + nosplit = true, + acc_push_epilogue = + #pto.acc_push_epilogue +}(...) +``` + +推荐新增如下复合 attr: + +```tablegen +def PTO_AccPushEpilogueAttr : AttrDef { + let mnemonic = "acc_push_epilogue"; + let parameters = (ins + EnumParameter:$layout, + EnumParameter:$quant, + EnumParameter:$relu + ); + let assemblyFormat = + "`<` `layout` `=` $layout `,` `quant` `=` $quant `,` `relu` `=` $relu `>`"; +} +``` + +也就是说,前端不再用 3 个分散的 attr 表达这组语义,而是统一收敛到 +`acc_push_epilogue` 这一个复合 attr。 + +与之绑定的 `tpush` 保持轻量: + +```mlir +pto.tpush_to_aiv(%acc_tile : !pto.tile_buf<...>) {id = 0, split = 0} +``` + +`tpop` 也不新增 config operand 或 attr: + +```mlir +%recv = pto.tpop_from_aic {id = 0, split = 0} -> !pto.tile_buf<...> +``` + +### 7.2 producer-side quant config IR + +为覆盖 `SET_QUANT_SCALAR` / `SET_QUANT_VECTOR`,建议在 PTOIR 中新增两类显式 op。 + +标量量化配置: + +```mlir +pto.set_quant_scalar(%scale : f32) +``` + +向量量化配置: + +```mlir +pto.set_quant_vector(%fp : !pto.tile_buf) +``` + +推荐语义: + +- `pto.set_quant_scalar` 表示设置同一 producer context 中、其后第一条匹配 + fixpipe `TPUSH` 将消费的 scalar quant state +- `pto.set_quant_vector` 表示设置同一 producer context 中、其后第一条匹配 + fixpipe `TPUSH` 将消费的 vector quant state +- 二者都属于 producer-side config op,而不是 pipe op +- 二者都不返回 SSA result;它们建模的是“影响后续 producer-side fixpipe push 的 + machine state” +- 第一版 verifier 先收紧为“同一基本块内顺序配对”,不要求跨 block 的一般化 + dominance 推导 +- 如果一条 `pto.set_quant_*` 可能匹配到多个候选 fixpipe `TPUSH`,则该 IR 非法; + 前端不接受依赖隐式残留 state 的写法 + +`pto-isa` 当前底层接口是: + +```cpp +SET_QUANT_SCALAR(scalar); +``` + +但在 PTOIR 设计中,不建议把 `OutType` 再额外挂成 `pto.set_quant_scalar` +的独立 attr。更合适的做法是: + +- `acc_push_epilogue.quant` 继续表达量化模式 +- consumer / destination tile 的 element type 继续表达结果类型 +- lowering 时根据这两者已经确定下来的结果类型,发射 + `SET_QUANT_SCALAR(scalar)` + +这样设计的原因是: + +- 对 `DEQF16Scalar`、`DEQF16Vec`、`QF322BF16PreScalar` 等模式,结果类型本来就已由 + pipe contract 与 consumer tile type共同确定,再单独暴露 `out_type` 只是重复信息 +- 对 `REQ8Scalar`、`QF322B8PreScalar` 这类 8-bit 模式,底层 `SET_QUANT_SCALAR` + 确实还需要区分 `int8_t` / `uint8_t`,但这个区别可直接来自既有的 + consumer / destination element type,而不需要再为 `pto.set_quant_scalar` + 新增一份并行 attr + +`pto.set_quant_vector` 同理不需要额外类型 attr,因为 `pto-isa` +`SET_QUANT_VECTOR(fpTile)` 当前只依赖输入 Scaling tile 本身。 + +### 7.3 前端 IR 使用样例 + +scalar quant 示例: + +```mlir +pto.aic_initialize_pipe { + id = 0, + dir_mask = 1, + slot_size = 1024, + nosplit = true, + acc_push_epilogue = + #pto.acc_push_epilogue +}(...) + +pto.set_quant_scalar(%scale : f32) +pto.tpush_to_aiv(%acc_tile : !pto.tile_buf) {id = 0, split = 0} +``` + +vector quant 示例: + +```mlir +pto.aic_initialize_pipe { + id = 0, + dir_mask = 1, + slot_size = 1024, + nosplit = true, + acc_push_epilogue = + #pto.acc_push_epilogue +}(...) + +pto.set_quant_vector(%fp_tile : !pto.tile_buf) +pto.tpush_to_aiv(%acc_tile : !pto.tile_buf) {id = 0, split = 0} +``` + +`NoQuant` 示例则不需要任何 quant-config op: + +```mlir +pto.aic_initialize_pipe { + id = 0, + dir_mask = 1, + slot_size = 1024, + nosplit = true, + acc_push_epilogue = + #pto.acc_push_epilogue +}(...) + +pto.tpush_to_aiv(%acc_tile : !pto.tile_buf) {id = 0, split = 0} +``` + +同一 kernel 中允许多条 fixpipe pipe 各自挂不同 `acc_push_epilogue`。例如: + +```mlir +pto.aic_initialize_pipe { + id = 0, + dir_mask = 1, + slot_size = 1024, + nosplit = true, + acc_push_epilogue = + #pto.acc_push_epilogue +}(...) + +pto.aic_initialize_pipe { + id = 1, + dir_mask = 1, + slot_size = 1024, + nosplit = true, + acc_push_epilogue = + #pto.acc_push_epilogue +}(...) + +pto.set_quant_scalar(%scale0 : f32) +pto.tpush_to_aiv(%acc0 : !pto.tile_buf) {id = 0, split = 0} + +pto.set_quant_vector(%fp1 : !pto.tile_buf) +pto.tpush_to_aiv(%acc1 : !pto.tile_buf) {id = 1, split = 0} +``` + +这时: + +- `pipe0` 与 `pipe1` 的 `acc_push_epilogue` 可以不同 +- `pto.set_quant_scalar(%scale0)` 只配对到后面的 `pipe0` 那次 scalar quant `TPUSH` +- `pto.set_quant_vector(%fp1)` 只配对到后面的 `pipe1` 那次 vector quant `TPUSH` + +### 7.4 为什么不把 config 写到 `tpush` + +不推荐如下形状: + +```mlir +pto.tpush_to_aiv(%acc_tile : !pto.tile_buf<...>) { + id = 0, + split = 0, + acc_push_epilogue = ... +} +``` + +因为它会把 pipe-level contract 拆散到多条 producer op 上。 + +同理,也不建议把 scalar/vector quant payload 直接塞进 `tpush` operand: + +```mlir +pto.tpush_to_aiv(%acc_tile, %scale_or_fp_tile) { ... } +``` + +因为 `SET_QUANT_SCALAR` / `SET_QUANT_VECTOR` 在 `pto-isa` 中本来就是独立的 +producer-side state config,而不是 `TPUSH` 的显式 operand。 + +## 8. 类型推导 + +对于 fixpipe pipe,consumer entry type 不是单纯由 source tile type 决定,而是由: + +- source tile type +- `acc_push_epilogue.quant` +- `acc_push_epilogue.layout` + +共同决定。 + +其中: + +- `acc_push_epilogue.quant` 决定结果元素类型 +- `acc_push_epilogue.layout` 决定结果 tile layout,例如 `NZ2ND` 导向 vec row-major 视角 +- `acc_push_epilogue.relu` 不改变类型,但改变数值语义 + +更具体地说,consumer element type 应对齐 `pto-isa` 当前 +`FixpipeConsDType_t::type` 的推导规则;但对 8-bit 模式,需要在 +PTOIR 前端额外保留 signedness 这一层语义。 + +如果同一 kernel 内存在多条 fixpipe pipe,则每一条 consumer / destination type +都应相对于“当前这条 `tpush` 所绑定的那条 pipe 的 `acc_push_epilogue`”独立推导, +而不是假定整个 kernel 只有一个全局唯一的 fixpipe 结果类型。 + +下文所说的 **consumer / destination element type**,指的是用户可见 consumer +`tpop` 结果 tile 的 element type;如果 lowering 先引入等价的内部 destination tile, +则该内部类型必须与用户可见 `tpop` 结果类型一致。 + +如果 source element type 记为 `SrcElemType`,那么: + +- 非 8-bit 模式下: + `consumer_elem_type = FixpipeConsDType_t::type` +- 8-bit 模式下: + `consumer_elem_type` 必须是 `si8` 或 `ui8`,其 signedness 来自 + consumer / destination element type,并在 lowering 时继续传给 + `SET_QUANT_SCALAR` +- `consumer_layout` 由 `acc_push_epilogue.layout` 决定。对当前 A5 no-split C2V UB + fixpipe 路径,可按 `pto-isa` 现有 `FixpipeVecTile` 规则校验: + - `layout = nz2nd` 时,consumer tile 应为 vec `BLayout::RowMajor` + - `layout = nz2dn` 时,consumer tile 应为 vec `BLayout::ColMajor` + - `layout = nz2nz` 时,consumer tile 应为 vec `BLayout::ColMajor`,并带 + `SLayout::RowMajor` + 对走 GM FIFO 视图的路径,前端语义上仍应满足同一 layout contract,只是内部可映射成 + 对应的 ND / DN / NZ 全局布局表示 +- `acc_push_epilogue.relu` 不参与类型推导 + +第一版文档建议至少按下面这组公共映射规则做 verifier 校验: + +| `acc_push_epilogue.quant` | `consumer_elem_type` | +|---|---| +| `no_convert` | `SrcElemType` | +| `f32_f16` | `f16` | +| `deqf16_scalar` / `deqf16_vec` | `f16` | +| `f32_bf16` / `qf322bf16_pre_scalar` | `bf16` | +| `req8_scalar` / `req8_vec` | 取决于 consumer / destination element type(`si8` 或 `ui8`) | +| `qf322b8_pre_scalar` / `qf322b8_pre_vec` | 取决于 consumer / destination element type(`si8` 或 `ui8`) | + +如果后续 PTOIR 第一版决定支持更多 `QuantMode_t` 枚举值,也应继续按 +`FixpipeConsDType_t` 的同一套规则扩展,而不是单独定义另一份类型映射。 + +需要单独说明的是:当前 `pto-isa` 的 `FixpipeConsDType_t` 在 8-bit 模式下会折叠成 +`int8_t`。PTOIR 前端设计不应直接把这一步折叠后的结果当成最终用户语义,而应继续以 +consumer / destination element type 保留 `si8` / `ui8` 区别,供 scalar quant +lowering 选择 `SET_QUANT_SCALAR` 或 `SET_QUANT_SCALAR`。 + +因此 verifier 应在 matched `initialize_pipe` 上做结果类型校验,而不是只在 +`tpush` 本地校验 source type。 + +需要注意,`SET_QUANT_SCALAR` / `SET_QUANT_VECTOR` 属于 producer-side runtime +quant state 约束,不属于 `FixpipeConsDType_t` 的类型推导本身: + +- consumer elem type 是否匹配: + 非 8-bit 模式看 `acc_push_epilogue.quant -> FixpipeConsDType_t`; + 8-bit 模式还要额外保留 consumer / destination type 给出的 signedness +- scalar/vector quant config 是否齐备,看对应 `SET_QUANT_SCALAR` / + `SET_QUANT_VECTOR` verifier 规则 +- 对 `SET_QUANT_SCALAR` 而言,`OutType` 不作为独立前端 attr 暴露; + lowering 直接从已验证通过的 consumer / destination element type中取值即可 + +## 9. Lowering 方案 + +### 9.1 前端到内部 pipe IR + +前端 `initialize_pipe` lowering 到内部 `!pto.pipe` 时: + +- 保留普通 pipe 字段:`id / dir_mask / slot_size / slot_num / nosplit` +- 额外挂上 `acc_push_epilogue` 字段 + +推荐内部 pipe 继续保持 opaque handle,不把这些字段塞进 `!pto.pipe` type 参数。 + +新增的 `pto.set_quant_scalar` / `pto.set_quant_vector` 不需要并入 `!pto.pipe`。 +它们应继续作为普通 producer-side op 保留在 IR 中,并在后续 lowering 中按顺序 +映射到 EmitC。 + +### 9.2 内部 pipe 到 EmitC + +当 `pto.tpush(%tile, %pipe)` 命中 fixpipe pipe 时,EmitC 不再生成普通: + +```cpp +TPUSH(pipe, tile); +``` + +而是生成: + +```cpp +using Pipe0FixpipeConfig = FixpipeParams< + LayoutMode_t::NZ2ND, + QuantMode_t::DEQF16, + ReluPreMode::NormalRelu>; +TPUSH(pipe, tile); +``` + +也就是说: + +- 前端 IR 不直接暴露 `TConfig` +- EmitC 阶段根据 pipe-level `acc_push_epilogue` 组装 `FixpipeParams`,并发射对应的 + `TPUSH(pipe, tile)` 模板调用 +- 若同一 kernel 中有多次命中同一条 fixpipe pipe 的 `TPUSH`,应复用同一个 + config type alias,而不是在每次 `TPUSH` 前重复声明同名 alias +- 若同一 kernel 中存在多条 fixpipe pipe,则应按 pipe 维度生成唯一别名,例如 + `Pipe0FixpipeConfig`、`Pipe1FixpipeConfig` +- 之所以推荐“每条 pipe 一个唯一 alias”,是为了避免在同一作用域下把同名 + alias 重复绑定到不同 `FixpipeParams<...>` 实参时引入 C++ 重定义冲突 +- 第一版 EmitC 只从 PTOIR 读取 `acc_push_epilogue`,再从中拆出 + `layout`、`quant`、`relu` +- `STPhase`、`AtomicType`、`SubBlockId`、`ClipReluMode_t`、`IsChannelSplit` + 虽然在 `pto-isa` 某些实现里可能被消费,但它们当前阶段暂不作为 PTOIR 前端公开配置项 +- 因此这里的“兼容”是指继续复用 `TPUSH>` + 这一调用形状,而不是承诺 PTOAS 前端已经完整覆盖全部 `FixpipeParams` + 模板参数语义 +- 若实现上不希望引入别名,也可以直接内联 + `FixpipeParams` 作为 + `TPUSH` 的第三个模板实参;本文档只把“每条 pipe 一个唯一 alias”作为推荐生成形状 +- `SET_QUANT_SCALAR` / `SET_QUANT_VECTOR` 不属于 `FixpipeParams` 模板参数; + 它们应作为独立 producer-side op 保留在 `TPUSH` 之前,并分别 lower 为: + - `pto.set_quant_scalar(%scale)` + -> `SET_QUANT_SCALAR(scale)` + - `pto.set_quant_vector(%fp_tile)` + -> `SET_QUANT_VECTOR(fpTile)` +- 其中 `SET_QUANT_SCALAR` 的 `OutType` 不来自独立前端 attr,而是直接从 + 与该 `pto.set_quant_scalar` 唯一配对的 fixpipe `TPUSH` 对应的 consumer / + destination element type 推导 +- 同一 kernel 中若存在多条带不同 `acc_push_epilogue` 的 fixpipe pipe,EmitC 应按 + 每条 `TPUSH` 实际绑定的 pipe 分别读取各自的 `acc_push_epilogue`,而不是假设 + kernel 内只有一份全局 fixpipe 配置 +- `pto.set_quant_scalar` / `pto.set_quant_vector` 的 lowering 也不应从“某个全局 + `TPOP` 类型”反推;更直接的做法是先利用前端 verifier 建立 + `set_quant_* -> 唯一后继 fixpipe TPUSH` 的配对关系,再从该 `TPUSH` 所属 pipe + 的 contract 和其 consumer / destination type发射具体 EmitC +- 如果这条唯一配对关系在前端 IR 中无法建立,应在 verifier 阶段报错,而不是把 + 歧义推迟到 EmitC +- 具体 `TPipe` 方向与数据路径由目标平台决定: + - A2/A3:对齐现有 `pto-isa`,fixpipe 型 C2V `TPUSH` 走 GM FIFO 路径, + Vector 侧 `TPOP` 再从 GM slot load 到本地 tile + - A5:对齐现有 `pto-isa`,可映射到 no-split 的 C2V UB FIFO 路径; + 若后续扩展到 GM FIFO 方向,也应复用同一套 pipe-level `acc_push_epilogue`, + 只在 lowering / EmitC 侧区分目标 `TPipe` 形态 + +### 9.3 `tpop` EmitC + +`tpop` 仍按当前 pipe entry 类型正常发射: + +```cpp +TPOP(pipe, tile); +``` + +它不需要生成额外 `TConfig`。 + +fixpipe 的生产语义由 producer `TPUSH` 和 pipe contract 保证,而不是由 `TPOP` +显式重复声明。 + +## 10. 验证规则 + +新增以下 verifier 规则: + +1. 开启 `acc_push_epilogue` 的 frontend `initialize_pipe` 必须是单向 C2V pipe,因此 + `dir_mask` 必须等于 `1`。 +2. 开启 `acc_push_epilogue` 时必须 `nosplit = true`,且所有绑定 data op 必须 `split = 0`。 +3. 同一逻辑 pipe 上不允许出现多组 `acc_push_epilogue`。 +4. 开启 `acc_push_epilogue` 的 pipe,其 producer entry 必须是 `acc` tile。 +5. consumer result element type 必须与 `acc_push_epilogue.quant` 的前端类型规则一致: + 其中 `SrcElemType` 指 producer acc tile 的元素类型。非 8-bit 模式按 + `FixpipeConsDType_t::type` + 校验;8-bit 模式要求结果类型为 `si8` 或 `ui8`,其 signedness 由 + consumer / destination element type给出。 +6. `acc_push_epilogue.layout` 与 consumer result tile layout 必须一致。第一版可按 + tile type 的 layout config 参数校验: + `nz2nd -> vec row_major`, + `nz2dn -> vec col_major`, + `nz2nz -> vec col_major + s_layout = row_major`。 +7. `DEQF16` 等 scalar quant 模式要求在对应 `TPUSH` 之前存在唯一匹配的 + `pto.set_quant_scalar`;第一版要求二者位于同一 producer context、同一基本块内, + 且该 `pto.set_quant_scalar` 只可被其后第一条匹配的 scalar quant fixpipe + `TPUSH` 消费。 +8. `VDEQF16` 等 vector quant 模式要求在对应 `TPUSH` 之前存在唯一匹配的 + `pto.set_quant_vector`;第一版要求二者位于同一 producer context、同一基本块内, + 且该 `pto.set_quant_vector` 只可被其后第一条匹配的 vector quant fixpipe + `TPUSH` 消费。 +9. 同一 kernel 可以存在多条带不同 `acc_push_epilogue` 的 fixpipe pipe;但每一条 + `pto.set_quant_*` 至多只能匹配一条实际消费它的 fixpipe `TPUSH`。如果在下一次 + 同类 `pto.set_quant_*` 出现之前,存在多条候选 `TPUSH` 可消费它,则 IR 非法。 +10. scalar quant lowering 到 `SET_QUANT_SCALAR` 时,`OutType` 应直接从 + 与该 `pto.set_quant_scalar` 唯一配对的 fixpipe `TPUSH` 对应的 consumer `tpop` + 结果 tile element type,或与之等价的 destination element type 推导,而不是 + 直接从 `FixpipeConsDType_t` 取值。 +11. `pto.set_quant_vector` 的输入必须是 `loc=scaling` 的 tile;其大小与 element + type 应满足目标平台 fixpipe vector quant payload 的参数布局要求。当前 + `pto-isa` 样例是以 `uint64_t` scaling tile 访问 packed scaling buffer, + 这只是现有样例的一种访问形式,不应理解为统一的前端语义类型约束。 +12. 第一版 PTOIR 只允许显式出现 `acc_push_epilogue` 这一个前端公开复合 attr, + 其内部字段限定为 `layout`、`quant`、`relu`。 +13. `STPhase`、`AtomicType`、`SubBlockId`、`ClipReluMode_t`、`IsChannelSplit` + 当前阶段暂不作为 PTOIR 前端公开配置项,也不允许以 attr 形式半暴露半忽略。 + +## 11. 测试建议 + +至少补以下测试: + +- `DEQF16` + `NZ2ND` + `NormalRelu` +- `VDEQF16` + `NZ2ND` + `NoRelu` +- `NoQuant` + `NZ2ND` +- `DEQF16` + `pto.set_quant_scalar` +- `VDEQF16` + `pto.set_quant_vector` +- `REQ8` + `ui8` consumer / destination type +- 同一 kernel 中两条 fixpipe pipe 使用不同 `acc_push_epilogue` 的正向 case +- 同一 kernel 中 scalar quant pipe 与 vector quant pipe 并存、各自配对 + `pto.set_quant_*` 的正向 case +- `DEQF16` 缺少对应 `pto.set_quant_scalar` 的 verifier 失败 case +- `VDEQF16` 缺少对应 `pto.set_quant_vector` 的 verifier 失败 case +- 单条 `pto.set_quant_scalar` 之后在下一次同类配置前出现两条候选 scalar quant + fixpipe `TPUSH` 的 verifier 失败 case +- `pto.set_quant_*` 与其消费它的 fixpipe `TPUSH` 不在同一基本块内的 verifier + 失败 case +- scalar quant lowering 推导出的 `OutType` 与 consumer / destination type 不一致的失败 case +- `pto.set_quant_vector` 输入不是 `loc=scaling` tile 的 verifier 失败 case +- 不同 `acc_push_epilogue.quant` 与 consumer type 不匹配的 verifier 失败 case +- fixpipe pipe 与 `split = 1/2` 混用的 verifier 失败 case +- 同一 pipe 上混入两组不同 `acc_push_epilogue` 的 verifier 失败 case