Reduce-Window / Pooling 成本
本页所有地址都适用于来自
libtpu-0.0.40-cp314wheel 的libtpu.so(构建libtpu_lts_20260413_b_RC00,BuildID md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未 stripped)。.text/.rodataVMA == 文件偏移;.data.rel.roVMA − 0x200000 == 文件偏移。该二进制保留了完整 demangled C++ 符号。其他 libtpu 构建会不同。
摘要
RecordReduceWindowCycles 是成本模型 emitter,用来为 HLO kReduceWindow 定价:max-pool、average-pool 或任何窗口化归约。它是 RecordConvolutionCycles 这个 convolution emitter 的池化同级:两者复用同一个 per-call ConvState 维度乘积结构、相同的 Target 几何除数(SublaneCount、ChunksPerTile),以及相同的四 ResourceVector 输出协议。区别在于目的地。卷积把计算成本落到 MXU 管线(Matmul/Matpush,通过 MxuLatencyTable 定价);reduce-window 是向量归约,不是矩阵乘法,因此每个计算周期都会落到向量 / 跨 lane 管线:VectorLoad、VectorAluAny 和 Xlu。没有 MXU 流量。
熟悉的参考框架是 LLVM TargetTransformInfo 中归约 intrinsic 的成本 hook,但这个模型是吞吐形状,不是延迟形状:成本表示“向量单元为读取并合并窗口而保持忙碌多少周期”,计算方式是每个输出元素的计数乘以从逐代 CycleTable 取得的每 op 吞吐。窗口面积通过 ConvState 维度乘积进入成本,而 combiner,也就是 XLA 附加到每个 reduce-window 上的 to_apply 子计算(max-pool 的 maximum、avg-pool 的 add),会通过遍历其指令单独定价,并对每个被建模的 op 按每个窗口元素、每个输出收取一次费用。
成本的形状由窗口归约所跨的轴决定。GetReduceWindowType 把归约分类为 Lane(most-minor 轴)、Sublane(sublane 轴)或 Major(慢速/外层轴),每一类都有不同的叶子 emitter 和沉积模式:Lane 增加一个跨 lane 的 Xlu drain,Sublane 增加一个 sublane-shuffle 项,Major 则纯粹是 read-plus-combine。本页记录分发、每个叶子使用的由 ConvState 驱动的窗口面积公式、它乘上的每 op 吞吐,以及它们共享的 combiner-cost helper。
重新实现时的契约是:
- 四
ResourceVector入口协议,以及成本模型未 live 时的 trivial-zero 快捷路径。 ConvState构建(与 conv 共享),以及每个叶子读取的三个具名ConvState维度乘积:output-volume、window-extent 和 kernel-spatial-iteration count。GetReduceWindowType三路轴分发,以及每个叶子进行的槽位沉积(VectorLoad、VectorAluAny、Xlu),包括Target+0x4b0Xlu-rate 除数。- combiner 成本:遍历
to_apply,把每个被建模的 combiner opcode 分类为一个CycleTable::Instruction,并收取throughput(CT) × count,其中count是每个轴的窗口元素计数(Lane 为vol·(window−1),Sublane 为base·(window−1)加一个4·base项,Major 为整个vol)。
| 类 | xla::jellyfish::CostModel(cost_model.cc) |
| 入口 (4-RV) | RecordReduceWindowCycles @0x130b5ec0 |
| 轴分发 | fusion_util::GetReduceWindowType @0x1454d4a0 → −1 / 0 / 1 / 2 |
| Lane 叶子 | RecordLaneReduceWindowCycles @0x130c97e0(cost_model.cc:4931) |
| Sublane 叶子 | RecordSublaneReduceWindowCycles @0x130c9c60(cost_model.cc:4978) |
| Major 叶子 | RecordMajorReduceWindowCycles @0x130c9f00(cost_model.cc:5033) |
| Combiner 成本 | UpdateCostBasedOnReductionFunction @0x130c9a20(fatal @ cost_model.cc:6870) |
| 沉积 | ResourceVector::Acc(Resource, double) @0x1c89adc0 |
| 吞吐来源 | 逐代 CycleTable::GetCyclesForThroughput(Instruction)(经由 [CostModel+0x8]=CycleTable,再到 vtable slot [+0x10]) |
| 计算管线 | R[7] VectorLoad, R[5] VectorAluAny, R[2] Xlu — 无 MXU |
| 输入 DMA | RecordOperandCycles @0x130ca140 → R[9..12] MemXfer |
入口点与输出协议
目的
RecordReduceWindowCycles 从 output-fusion 成本路径中调用,是多个被定价的子 emitter 之一(conv/dot/reduce-window 家族)。它接收 reduce-window HLO、三个 Window 描述符(activations / 第二个 window / output),以及四个 ResourceVector 输出参数,并把池化成本沉积到它们中。四向量形式与 RecordConvolutionCycles 对齐:调用者稍后会用 MaxResourceCycles 的重叠规则折叠这四个子向量(见 resource-enum)。
入口点
RecordReduceWindowCycles (4-RV) @0x130b5ec0 ── prices a kReduceWindow
├─ convolution_util::GetConvLikeProperties ── derive ConvolutionDimensionNumbers + shapes
├─ window_util::HasBaseDilation ── dilation gate (blocks Lane/Sublane)
├─ (inline) build ConvState at rbp-0x1d0 ── Product(dim)/SublaneCount/ChunksPerTile
├─ fusion_util::GetReduceWindowType @0x1454d4a0 ── 0=Lane 1=Sublane 2=Major (−1=not lowerable)
├─ RecordLaneReduceWindowCycles @0x130c97e0 ── axis 0
├─ RecordSublaneReduceWindowCycles @0x130c9c60 ── axis 1
├─ RecordMajorReduceWindowCycles @0x130c9f00 ── axis 2
├─ RecordOperandCycles @0x130ca140 ── input DMA → R[9..12]
└─ RecordTopLevelConvolutionOutputCycles @0x130bcb80 ── output DMA (when top-level)
```text
### 算法
```c
function RecordReduceWindowCycles(rw, act_win, win2, out_win, rv0, rv1, rv2, rv3, fusion, top): // @0x130b5ec0
CHECK(rw->opcode() == kReduceWindow) // opcode byte +12 == 0x5e; cost_model.cc:5068
if (this->cost_model_live == 0): // CostModel byte +0x14 (the [a1+20] gate)
// TRIVIAL path: not modellable — deposit zero into the four vector/Xlu slots and return.
rv2.Acc(VectorLoad=7, 0.0) // 0x130b5fc3..6007 (vpxor → 0.0)
rv2.Acc(VectorAlu0=3, 0.0)
rv2.Acc(VectorAlu1=4, 0.0)
rv2.Acc(Xlu=2, 0.0)
return Ok
// REAL path
props = GetConvLikeProperties(rw) // ConvolutionDimensionNumbers + operand/result shapes
dilated = HasBaseDilation(act_win, dim=0) // window_util::HasBaseDilation → blocks Lane/Sublane
CHECK(rw_state.input_batch_is_most_minor) // cost_model.cc:5093
CHECK(!rw_state.output_feature_is_most_minor) // cost_model.cc:5094
state = BuildConvState(props, out_win, ...) // dim products / SublaneCount / ChunksPerTile (rbp-0x1d0)
// incl. the 64-bit window-volume multiply loop @0x130b6290
switch (GetReduceWindowType(rw)): // @0x1454d4a0
case 0: CHECK(!dilated) // cost_model.cc:5142
RecordLaneReduceWindowCycles(rw, axis_minor, state, rv2)
case 1: CHECK(!dilated) // cost_model.cc:5149
RecordSublaneReduceWindowCycles(rw, axis_minor, state, rv2)
case 2: RecordMajorReduceWindowCycles(rw, axis_minor, state, rv2)
// case −1 (not conv-lowerable) is filtered upstream by the fusion sentinel; not reached here.
if (top): // top-level (non-fused) reduce-window
RecordOperandCycles(rw->operand(0), op_state, ...) // input DMA → rv (R[9..12])
RecordTopLevelConvolutionOutputCycles(rw, out_win, ...) // output DMA → R[11..12]
return Ok函数映射
| 函数 | 地址 | 作用 |
|---|---|---|
CostModel::RecordReduceWindowCycles (4-RV) | 0x130b5ec0 | 顶层 emitter:CHECK、ConvState 构建、轴分发 |
fusion_util::GetReduceWindowType | 0x1454d4a0 | 轴分类器:−1 / 0 / 1 / 2 |
CostModel::RecordLaneReduceWindowCycles | 0x130c97e0 | lane-axis 叶子:VectorLoad + combine + Xlu drain |
CostModel::RecordSublaneReduceWindowCycles | 0x130c9c60 | sublane-axis 叶子:增加 sublane shuffle |
CostModel::RecordMajorReduceWindowCycles | 0x130c9f00 | major-axis 叶子:读密集,无 Xlu |
CostModel::UpdateCostBasedOnReductionFunction | 0x130c9a20 | combiner-op 成本(三个叶子共享) |
CostModel::RecordOperandCycles | 0x130ca140 | input-DMA 沉积 → R[9..12] MemXfer |
CostModel::RecordTopLevelConvolutionOutputCycles | 0x130bcb80 | output-DMA 沉积(仅顶层) |
ResourceVector::Acc(Resource, double) | 0x1c89adc0 | 槽位沉积原语(边界 < 23) |
convolution_util::GetConvLikeProperties | 0x13190bc0 | 共享的 conv/RW 维度编号 + shape 提取 |
window_util::HasBaseDilation | (call @0x130b62..) | 阻止 Lane/Sublane 的 dilation gate |
注 — trivial-zero 快捷路径(
CostModel+0x14 == 0)不是 no-op:它仍然向VectorLoad/VectorAlu0/VectorAlu1/Xlu显式沉积四个0.0周期。即使成本为零,沉积数量也必须匹配真实的 pooling op,因为下游会按位置折叠四个ResourceVector;如果重新实现时只是直接return而不调用这四次 zero-Acc,这些槽位在 fold 时会保持未初始化。沉积进入第三个输出向量(a9/rv2),也是每个真实路径叶子沉积的位置。
ConvState:共享的维度乘积结构
目的
reduce-window 不构建 ConvCostState。它按卷积配方,从 ConvolutionDimensionNumbers 和输出 Window 中内联构建更小的 per-call ConvState:每个维度字段都是 Product(dim_chunk_sizes) 除以一个 Target 几何常量(SublaneCount() = 8、ChunksPerTile() = 16),并配套一个可整除余数。相同的结构、相同的除法;完整字段映射见 convolution-cost-state。reduce-window 叶子只读取其中三个字段,而二进制通过 CHECK 字符串和 VLOG 参数给它们命名。
池化成本读取的三个字段
三个叶子 emitter 把 ConvState 当作 _QWORD 数组索引(a4[i] = ConvState + 8*i)。只有三个偏移承载池化成本,二进制会标识每个偏移:
ConvState 偏移 | a4[i] | 具名作用 | 证据 |
|---|---|---|---|
+0x18 | a4[3] | output-spatial 维度乘积 | VLOG arg 3(Lane/Sublane @cost_model.cc:4931/4978 中的 ", %ld") |
+0x20 | a4[4] | kernel-spatial-dims iteration count | CHECK 字符串 rw_state.kernel_spatial_dims_iteration_count == 1(Major @cost_model.cc:5033) |
+0x68 | a4[13] | output 维度乘积 / window extent(按轴) | VLOG arg 2(Lane/Sublane)+ combiner (a4[13]−1) |
+0x70 | a4[14] | window-extent / iteration count(被归约轴) | VLOG arg 1(Lane/Sublane)+ combiner (a4[14]−1) |
cost_model.cc:5033 的 CHECK 是唯一硬名称:在 Major 路径中,ConvState+0x20(a4[4])必须等于 1,并被明确标为 kernel-spatial-dims iteration count。这是 conv 的“有多少 kernel-window 位置需要迭代”字段;major-axis reduce-window 会把它的窗口折入单个 major 迭代,因此固定为 1。其他三个名称来自 Lane/Sublane VLOG 打印它们的顺序(win_extent、output、output_spatial),以及 combiner 对哪一个字段减 1(被归约轴的 window extent)。
怪癖 —
(+0x68, +0x70)这对字段是被归约轴字段对。在 Lane 叶子中,VectorLoad 体积由+0x70 · +0x18 · +0x20构建,而 combiner 窗口是+0x68;在 Sublane 叶子中,VectorLoad 体积同时包含+0x68和+0x70,而 combiner 窗口是+0x70。两个叶子读取同一结构,但把这对字段中的相反成员视为“正在归约的窗口”,因为 Lane 和 Sublane 跨越物理上不同的轴。重新实现时不能假设存在单一规范的 “window-size” 字段,窗口维度依赖轴。
轴分发:GetReduceWindowType
目的
成本形状由窗口扫描的物理轴决定。GetReduceWindowType(@0x1454d4a0,命名空间 fusion_util)返回轴类别。它首先询问 IsConvLowerableReduceWindow;如果 reduce-window 不能 lowered 到 conv 路径,则返回 −1(0xFFFFFFFF)。否则,它把 layout 映射到物理顺序(LayoutUtil::MakeLogicalToPhysical),并检查哪些窗口维度是非平凡的(window_util::IsTrivialWindowDimension)。
算法
function GetReduceWindowType(rw): // @0x1454d4a0
if !IsConvLowerableReduceWindow(rw): return -1 // not modellable via conv path
phys = MakeLogicalToPhysical(operand_layout)
for each non-most-minor physical dim d: // the "outer/major" dims (the loop)
if !IsTrivialWindowDimension(window[d]):
return 2 // Major: window on a slow axis
// window is confined to the two most-minor physical dims (minor0 = most-minor / lane axis)
if !IsTrivialWindowDimension(window[minor0]): return 0 // Lane: lane axis has a window
if !IsTrivialWindowDimension(window[minor1]): return 1 // Sublane: lane trivial, sublane non-trivial
return 2 // Major: both most-minor dims trivial
```text
主 emitter 用 `if (type) { if (type != 1) { if (type == 2) Major; } else Sublane; } else Lane;` 对结果分发,因此映射是 **0 → Lane、1 → Sublane、2 → Major**,由 `@0x130b5ec0` 中的三个调用点确认。(注意,反编译通过共享尾部到达 most-minor 对应的 `return 0`/`return 1`/`return 2`:当 `window[minor0]` 非平凡时,直接落到返回 `IsTrivial(window[minor0]) == 0` = Lane;当 `minor0` 平凡时,它测试 `minor1`,若 `minor1` 非平凡则返回 `IsTrivial(window[minor0]) == 1` = Sublane,否则保持 `result = 2` = Major。)
> **陷阱 —** 成本模型中有两个不同的 reduce-window “type −1/2” 过滤器,它们不是同一个 gate。output-fusion *dispatch*(在 `GetOutputFusionOrConvolutionCycles` 中)使用 sentinel,在构建任何状态之前*跳过* type 2 或 −1 的 reduce-window。这里的 emitter 只会作为一个*已定价*的子 emitter 被到达,此时**所有三种轴类型都会沉积**(Lane、Sublane、Major)。不要把上游 skip 逻辑带入叶子分发。type −1 永远到不了这个函数(它在上游被过滤),因此这里的 `case −1` 分支不会被执行。
---
## 三个叶子 Emitter
三个叶子共享签名 `Record<Axis>ReduceWindowCycles(rw, axis_minor:int, ConvState&, ResourceVector*)` 和沉积语法:通过 `Acc` 把吞吐周期写入具名 `ResourceVector` 槽位。它们的差异在于体积公式和触及的额外管线。每 op 吞吐是 `CycleTable::GetCyclesForThroughput(CT)`,通过 `CostModel` 的 `CycleTable` vtable slot `[+0x10]` 读取,其中 `CT` 是一个 `CycleTable::Instruction` 序号。每个 `CT` 背后的整数按代变化,位于 [`vf-cycletable`](vf-cycletable.md) / [`jf-cycletable`](jf-cycletable.md)。
### Lane:`RecordLaneReduceWindowCycles` `@0x130c97e0`
lane-axis 归约跨 most-minor(lane)轴归约,因此在逐元素 combine 之后,它必须在跨 lane 单元(`Xlu`)上把部分结果 drain 到 *across lanes*。这是唯一带 Xlu 项的叶子。
```c
function RecordLaneReduceWindowCycles(rw, axis_minor, st, rv): // @0x130c97e0
vol = st[+0x20] * st[+0x18] * st[+0x70] // a4[4]·a4[3]·a4[14] — output volume read into VMEM
window = st[+0x68] // a4[13]
rv.Acc(VectorLoad=7, (double)vol) // R[7]: input read into VMEM
// F16 pre-packed-feed special case (operand is f16 AND not a kBitcast-class feed, opcode != 0x3d):
if element_type(operand0) == F16(16) && operand0.opcode != 0x3d:
cyc = GetCyclesForThroughput(CT 0x16) // vtable [CycleTable+0x10]
rv.Acc(VectorAluAny=5, cyc * vol) // R[5]: extra per-element unpack-combine
UpdateCostBasedOnReductionFunction(to_apply(rw), vol * (window - 1), rv) // combiner cost
drain = GetCyclesForThroughput(CT 0x1b) // CT 0x1b = Xlu-class throughput
rv.Acc(Xlu=2, drain / Target[+0x4b0]) // R[2]: cross-lane reduction drain
// F16 residual unpack (only when axis_minor == 0 and the *result* is f16):
if axis_minor == 0 && element_type(result) == F16(16):
rv.Acc(VectorAluAny=5, ...) // R[5]R[7] VectorLoad=vol=ConvState[+0x20]·[+0x18]·[+0x70]:流入向量单元的输入体积。R[2] Xlu=throughput(CT 0x1b) / Target[+0x4b0]:裸 CT-0x1b 吞吐除以除数;反编译出的沉积中没有额外空间乘数(@0x130c97e0:GetCyclesForThroughput(27)→ 分子,[Target+0x4b0]→ 分母,单个vdivsd)。Target+0x4b0是 vector-ISA Xlu/cross-lane rate 除数,由Target::Init从TpuSequencerParts::vector_isa()填充,而不是ConvCostState字段(见下方修正)。它与 conv emitter 用于其Xlumxres 沉积的除数相同。- combiner 计数是
vol · (window − 1):归约对每个输出元素执行window − 1次 combine(窗口上的 tree reduce 触及window个输入,但执行window − 1次 combine)。
Sublane:RecordSublaneReduceWindowCycles @0x130c9c60
sublane 归约跨 8 个 sublane 归约。它不使用 Xlu drain,而是在 VectorAluAny 上收取一个 sublane-shuffle 项(shuffle/broadcast 家族落在 R[5],见 resource-enum),并且调用 combiner 两次:一次用于窗口归约,另一次用固定 4× 因子表示 cross-sublane combine tree。
function RecordSublaneReduceWindowCycles(rw, axis_minor, st, rv): // @0x130c9c60
base = st[+0x20] * st[+0x18] * st[+0x68] // a4[4]·a4[3]·a4[13]
window = st[+0x70] // a4[14]
vol = base * window // full output volume
rv.Acc(VectorLoad=7, (double)vol) // R[7]: input read
if element_type(operand0) == F16(16) && operand0.opcode != 0x3d: // same F16 gate as Lane
cyc = GetCyclesForThroughput(CT 0x16)
rv.Acc(VectorAluAny=5, cyc * vol)
UpdateCostBasedOnReductionFunction(to_apply(rw), base * (window - 1), rv) // window combine
SublaneCount() // = 8 (consulted, result folded into the shuffle)
r = CycleTable::GetResource(CT 0x15) // CT 0x15 → R[5] VectorAluAny (shuffle family)
rv.Acc(r, <shuffle cycles>) // R[5]: sublane shuffle
UpdateCostBasedOnReductionFunction(to_apply(rw), 4 * base, rv) // cross-sublane combine tree (×4)
if axis_minor == 0 && element_type(result) == F16(16):
rv.Acc(VectorAluAny=5, ...) // R[5] F16 residual
```text
> **怪癖 —** 第二次 combiner 调用使用字面量 `4 × base` 因子(`@0x130c9c60`,`4 * v9`),而不是 `base × (window−1)`。这个 `4` 是 cross-sublane reduction tree 的成本,它独立于窗口大小收费;sublane 归约会在固定 shuffle 网络上扇入,模型把其深度定价为常量 `4` 次 combiner pass。把这一项按 sublane count(8)或窗口大小缩放的重新实现,会错误定价每个 sublane-axis pool。
### Major:`RecordMajorReduceWindowCycles` `@0x130c9f00`
major-axis reduce-window 跨慢速/外层维度归约,因此主要受读取(较大)窗口体积的成本支配;没有跨 lane 或跨 sublane drain。
```c
function RecordMajorReduceWindowCycles(rw, axis_minor, st, rv): // @0x130c9f00
CHECK(st[+0x20] == 1) // "rw_state.kernel_spatial_dims_iteration_count == 1" cost_model.cc:5033
props = GetConvLikeProperties(rw)
window_vol = Product(window_dim.size for each window dimension) // 64-bit multiply over WindowDimension list
vol = st[+0x68] * st[+0x70] * st[+0x18] * window_vol // a4[13]·a4[14]·a4[3]·window_vol
rv.Acc(VectorLoad=7, (double)vol) // R[7]: read the whole window volume
UpdateCostBasedOnReductionFunction(to_apply(rw), vol, rv) // combiner: once per element of volR[7] VectorLoad承载完整的output_volume · window_volume读取。combiner 计数是整个vol(没有−1递减),因为 major 归约的 combine 计数会直接折入体积乘积,而不是被分离成每输出的 window-minus-one 项。- 没有
Xlu,没有 shuffle,没有 F16 residual:major reduce-window 只是 read-plus-combine。
Combiner 成本:UpdateCostBasedOnReductionFunction
目的
每个 reduce-window 都携带一个 to_apply 子计算:max-pool 的 maximum、sum/avg-pool 的 add,或任意用户归约。UpdateCostBasedOnReductionFunction(@0x130c9a20,三个叶子共享)为 combiner 本身定价:它遍历子计算的指令列表,并对每个被建模的 combiner op(跳过 parameter/get-tuple-element)把 opcode 映射到一个 CycleTable::Instruction,再向 VectorAluAny 收取 throughput(CT) × count。循环不会在第一个 op 后停止,to_apply 中每条被建模的指令都会贡献一次沉积;因此单 op 归约(常见的 maximum/add)收取一次,而多 op 用户归约会按每个被建模 op 收取一次。count 由调用者提供(上面计算的每输出窗口元素计数)。
算法
function UpdateCostBasedOnReductionFunction(comp, count, rv): // @0x130c9a20
for inst in comp->instructions(): // walks ALL instructions; deposits per modeled op
switch (inst->opcode()): // opcode byte +12
case 0x29 ')' , 0x52 'R': continue // parameter / get-tuple — no cost, skip
case 0x49 'I', 0x4a 'J': // (min/max-class combiner)
Resource = VectorAluAny(5); CT = 0x20
case 0x4b 'K': // multiply-class
Resource = VectorAluAny(5); CT = 0x14 // CT 0x14 for both int and float
if ElementIsFloating(operand): Resource = GetResource(CT)
default:
if (opcode != 0x03 add): FATAL "Reduction Function not modeled: Please file an XLA bug." // :6870
Resource = VectorAluAny(5); CT = 0x12 // CT 0x12 (add)
if ElementIsFloating(operand): Resource = GetResource(CT) // remap to the float-op slot
cyc = GetCyclesForThroughput(CT) // vtable [CycleTable+0x10]
rv.Acc(Resource, cyc * count) // the combiner deposit, charged for this op
// loop continues to the next instruction — every modeled combiner op in `comp` is charged
```text
opcode→CT 分类(`@0x130c9a20`):
| Combiner opcode | 类别 | CT(float) | CT(int) | Resource |
|---|---|---|---|---|
| `0x49`/`0x4a`(min/max,即 max-pool 情况) | compare-select | `0x20` | `0x20` | `R[5] VectorAluAny`(无 float remap) |
| `0x4b`(multiply) | mul | `0x14` | `0x14` | `R[5]`;float 经由 `GetResource(0x14)` remap 槽位 |
| `0x03` add(avg/sum-pool 情况) | add | `0x12` | `0x12` | `R[5]`;float 经由 `GetResource(0x12)` remap 槽位 |
| `0x29`(parameter)/ `0x52`(get-tuple-element) | 结构性 | — | — | (无沉积) |
| 其他任何项 | — | FATAL(`cost_model.cc:6870`) | — | — |
> **注 —** count 参数是唯一把 combiner 与窗口面积绑定起来的东西。Lane 传入 `vol·(window−1)`,Sublane 传入 `base·(window−1)` 加一个独立的 `4·base`,Major 传入整个 `vol`。因此 HLO 级 `HandleReduceWindow` estimator 收取的 `to_apply × window_volume × output` flop,在这里表现为 `throughput(CT) × count`:combiner 对每个窗口元素、每个输出执行一次,并按具体 combiner op(max vs add vs multiply)的逐代吞吐定价,而不是固定 flop。
>
> **陷阱 —** Xlu-rate 除数 `Target+0x4b0`(以及 matmul-rate 除数 `Target+0x4ac`)是 **`Target`** 字段,不是 `ConvCostState` 字段;它们是 chip-wide vector-ISA 速率,由 `Target::Init` 从 `TpuSequencerParts::vector_isa()` 填充(`vector_isa[+0xc]` → `Target+0x4ac` 和 `+0x4b0`)。Lane 叶子在 `@0x130c98f6` 处通过 `CostModel` 的 `CycleTable`(`[CostModel+0x8]` = `CycleTable`,然后是其 `Target` 指针,再到 `[Target+0x4b0]`)到达 `Target+0x4b0`;这是 conv emitter 给 mxres 沉积做除法时使用的同一个 vector-ISA Xlu 速率,而不是 per-reduce-window derate。
---
## 一个 Pool 如何端到端定价
把这些拼在一起,reduce-window 的计算成本完全落在 vector/cross-lane 管线上:
```text
max-pool / avg-pool cost (per the three leaves)
R[7] VectorLoad = output_volume (input read into VMEM)
R[5] VectorAluAny = throughput(combiner CT) · combiner_count (the to_apply op, once per window-elem)
+ throughput(CT 0x16) · vol (F16 unpack, only when operand is f16)
+ sublane shuffle (Sublane axis only, via GetResource(CT 0x15))
R[2] Xlu = throughput(CT 0x1b) / Target[+0x4b0] (Lane axis only — cross-lane drain)
R[9..12] MemXfer = RecordOperandCycles(input) (+ output DMA when top-level)随后 bundle 成本来自 MaxResourceCycles(resource-enum):三个 VectorAlu* 槽位按 50% port-balance 混合,四个 MemXfer 槽位串行求和,VectorLoad/Xlu 进入 plain-MAX 组。因此一个 pooling op 会受占主导者约束:输入读取(VectorLoad)、combiner 吞吐(VectorAluAny)、跨 lane drain(Xlu)或输入 DMA(MemXfer)。
与 conv emitter 的对比非常直接且有启发性:conv 叶子 RecordConvKernelCycles 会沉积到 R[0] Matpush、R[1] Matmul 和 R[2] Xlu(通过 MxuLatencyTable 按 matmul/matpush 速率定价),因为卷积是 systolic 矩阵乘法。reduce-window emitter 复用同一个 ConvState、同一个 Target+0x4b0 Xlu 除数,以及同一个四 ResourceVector 协议,但沉积到 VectorLoad/VectorAluAny/Xlu,从不沉积到 MXU 管线,因为窗口化归约是向量操作。相同脚手架,不同功能单元。
注意事项
- Dilation 阻止 Lane/Sublane。 如果
HasBaseDilation为 true,Lane 和 Sublane 叶子会CHECK失败(has_base_dilation == false,cost_model.cc:5142/5149)。因此 base-dilated window 只能作为 Major reduce-window 建模;重新实现必须把 dilated pool 路由到 major 路径,否则会触发断言。 - Layout 固定轴。
GetReduceWindowType依赖物理 layout(MakeLogicalToPhysical),因此同一个逻辑 reduce-window 可能根据 layout assignment 把哪个维度设为 most-minor 而分类为 Lane / Sublane / Major。成本从构造上就是 layout-sensitive 的。 - 两个 minor-ness CHECK(
input_batch_is_most_minor、!output_feature_is_most_minor)会 gate 整个真实路径。它们编码的假设是:conv-lowerable reduce-window 保持 batch most-minor,且 feature 不是 most-minor;违反任一条件的 HLO 不会由此 emitter 定价。 - 逐代吞吐。 每个
CT 0x12/0x14/0x16/0x1b/0x15/0x20都会通过GetCyclesForThroughput解析为逐代整数。combiner-op CT(0x12add、0x14mul、0x20min/max)在命中哪个槽位上代际不变(VectorAluAny),但在多少周期上不是,周期值位于逐代 cycle-table 页面。
相关组件
| 名称 | 关系 |
|---|---|
convolution-cost-state | 此 emitter 复用的 ConvState/ConvCostState 结构;同级 conv RecordConvKernelCycles |
resource-enum | 23 槽位 ResourceVector、Acc 沉积以及 MaxResourceCycles fold |
mxu-latency-overview | conv 路径使用而 pooling 路径不使用的 MXU 占用模型 |
vf-cycletable / jf-cycletable | GetCyclesForThroughput 返回的每 CT 吞吐整数 |
window-description-cost | RecordOperandCycles 背后的 conv/DMA byte+throughput 原语 |
slot-vpu | combiner CT 定价的 vadd/vmax/.xlane op 所属的 LLO VALU 槽位 |
交叉引用
- ConvolutionCostState —
ConvState/ConvCostState字段映射,以及与此处互为池化同级的 conv emitter - WindowDescription Byte-Cost —
RecordOperandCycles基于的 conv/DMA byte+throughput 原语 - VfCycleTable — combiner 和 drain CT 经由其解析的逐代
CycleTable::Instruction→ 吞吐整数 - JF CycleTable — Jellyfish/Dragonfish 吞吐表
- Resource Enum (23-slot) —
VectorLoad/VectorAluAny/Xlu槽位、Acc以及MaxResourceCycles重叠归约 - MXU Latency Overview — conv 路径用来定价且 pooling 路径绕过的 MXU reservation 模型
- Performance Overview —
GetCyclesForThroughput背后的基础 per-opcode latency grid - VPU (Vector-ALU) Slot — combiner 和 reduction CT 计费所针对的 LLO VALU/
.xlaneop