[AI][昇腾950] 低 Bit 量化
第一部分:量化算法总览
1.1 对称量化 (Symmetric Quantization)
核心原理:零点固定为 0,仅需 scale 因子。
量化: x_quant = round(x_fp32 / scale)
反量化: x_fp32 = x_quant × scale
- scale: 通常取
max(|x|) / (2^(bit-1) - 1) - INT8 对称范围: [-128, 127] (S8)
- 优势: 实现简单,硬件友好,zero-point 无需存储
- 劣势: 对偏置分布的数据(如 ReLU 激活值)有较大精度损失
1.2 非对称量化 (Asymmetric Quantization)
核心原理:引入非零零点 (zero-point),覆盖偏置分布。
量化: x_quant = round(x_fp32 / scale) + zero_point
反量化: x_fp32 = (x_quant - zero_point) × scale
- scale:
(max(x) - min(x)) / (2^bit - 1) - zero_point:
round(-min(x) / scale),范围 [0, 2^bit-1] - UINT8 范围: [0, 255],zero_point 覆盖偏置
- 劣势: 矩阵乘需要额外 ZP 校正项:
(W-ZP_w)(X-ZP_x) = WX - W·ZP_x - ZP_w·X + ZP_w·ZP_x
1.3 Per-Tensor vs Per-Channel vs Per-Group 量化
概念辨析
| 张量类型 | Shape | Per-Channel 的轴 | 参数数量 | 典型场景 |
|---|---|---|---|---|
| 权重 W (Linear/Conv) | [C_out, C_in] 或 [C_out, C_in, H, W] | C_out 轴 (输出通道) | C_out 个 scale + C_out 个 ZP | 最常见:每个输出通道独立量化 |
| 激活值 X | [N, C_in] 或 [N, C_in, H, W] | C_in 轴 (输入通道) | C_in 个 scale | 较少见:输入通道间动态范围差异大时使用 |
| 激活值 X (Transformer) | [N, SeqLen, C] | C 轴 (隐藏维度) | C 个 scale | LLM 量化常用 |
| 偏置 B | [C_out] | C_out 轴 | C_out 个 scale | 配合 W 的 per-channel |
关键结论:Per-Channel 不是 N 轴(batch)的量化。N 轴的"per-element"量化几乎不用——batch 内每个样本分布不同,无法用固定 scale。
参数数量对比
| 粒度 | Scale 参数数量 | ZP 参数数量 | 总参数 (INT8) | 存储位置 |
|---|---|---|---|---|
| Per-Tensor | 1 个 | 1 个 | 2 | PBUF 1 槽位 |
| Per-Channel (C_out=64) | 64 个 | 64 个 | 128 | PBUF 64 槽位 (vector mode) |
| Per-Channel (C_in=128) | 128 个 | 128 个 | 256 | PBUF 128 槽位 |
| Per-Group MX (block=32) | K/32 × M/16 个 | 0 | K/32 × M/16 | MX_MEM 8KB |
| Per-Group MX (block=64) | K/64 个 | 0 | K/64 | MX_MEM |
Per-Channel 的轴对齐约束
FIXP VREQ8 (vector mode) 的 per-channel 沿 C0 方向:
NZ layout 最内层是 C0 (典型 C0=16)
→ PBUF 存 C 个 scale (按 C0 stride 索引)
→ 硬件自动每读 C0 个元素后切换到下一组 scale
例如: C=64, C0=16:
C1=0: C0=0..15 使用 scale[0..15]
C1=1: C0=16..31 使用 scale[16..31]
C1=2: C0=32..47 使用 scale[32..47]
C1=3: C0=48..63 使用 scale[48..63]
共 64 个 scale 参数, 不是 1 个!
各粒度适用场景
| 粒度 | 描述 | 精度 | 硬件代价 | 映射 |
|---|---|---|---|---|
| Per-Tensor | 整个张量共享 1 组 (scale, zp) | 低 | 最低 | FIXP scalar mode (1 槽位) |
| Per-Channel | 每个通道独立 (scale, zp), C_out 或 C_in 轴 | 高 | 中 (C 个参数) | FIXP vector mode (C/C0 组槽位) |
| Per-Group / Block-wise | 每 N 元素一组 scale(如 32/64/128) | 最高 | 高 | CUBE MX_MEM 8KB |
1.4 动态量化 vs 静态量化
| 模式 | scale/zero_point 计算时机 | 典型场景 | 映射 |
|---|---|---|---|
| 动态量化 | 每次推理时在线计算 | 激活值量化 (activation 分布未知) | VEC 在线计算 scale → PBUF → FIXP REQ |
| 静态量化 | 离线标定后固化 | 权重量化 (权重离线可得分布) | 软件预计算 scale 写入 L1 → MOV_L1_TO_FB |
| QAT (量化感知训练) | 训练时插入 fake-quant 节点 | 最高精度 | SIMD VSELRV2 (4→8/16bit LUT) 模拟;SIMD convert 指令支持 |
1.5 Fake Quantization
原理:训练时模拟量化误差(forward: 量化+反量化,backward: STE 直通),使模型适应低精度。
forward: x_out = dequant(quant(x_in, scale, zp), scale, zp) // 模拟量化噪声
backward: ∂L/∂x_in ≈ ∂L/∂x_out // STE 梯度近似
SIMD VSELRV2 指令 — 4bit 输入 → 8/16bit LUT 查找表输出,支持硬件加速 fake-quant 前向。
1.6 Microscaling (MX) — 块级共享 Exponent
原理(OCP Microscaling Format):每 N 个低 bit 元素共享一个 8-bit scale (2^N 形式),Scale = (要素值 × 2^scale) 恢复实际值。
| MX 格式 | 元素 bit | block size | scale bit | 额外共享 bit |
|---|---|---|---|---|
| MX-FP4 (E1M2/E2M1) | 4 | 32 | 8 (2^N) | — |
| MX-FP8 (E4M3/E5M2) | 8 | 32 | 8 (2^N) | — |
| HiF4 | 4 | 64 | 8 (2^N) | E1_8 + E1_16 (每 8/4 元素共享 1 bit exponent) |
内积公式:result = Σ (a_i × 2^(scale_a)) × (b_i × 2^(scale_b)) = 2^(scale_a+scale_b) × Σ a_i × b_i
CUBE MMAD_MX 指令,8KB MX_MEM 每 L0A/L0B,硬件自动读取+叠 scale 到 exponent。
1.7 非对称混合精度量化 (Asymmetric Dtype)
原理:Activation 和 Weight 使用不同 bit width 或不同格式。
| 模式 | Activation | Weight | 典型场景 |
|---|---|---|---|
| A16W8 | FP16/BF16 (16bit) | FP8/S8 (8bit) | W 量化到 8bit,A 保持 FP16 |
| A16W4 | FP16/BF16 (16bit) | MX-FP4/S4 (4bit) | W 量化到 4bit |
| A8W4 | MX-FP8 (8bit) | MX-FP4/HiF4 (4bit) | A/W 同时低 bit |
CUBE 原生支持 A16W8/A16W4/A8W4 模式,硬件自动将低 bit 矩阵转为高 bit 等价表示。
1.8 线性量化中的偏置校正 (Bias Correction)
量化后的偏置项需要额外校正:
不带 ZP: bias_quant = bias_fp32 / scale_out
带 ZP: bias_quant = (bias_fp32 - ZP_w × ΣX - ZP_x × ΣW + |W|·ZP_w·ZP_x) / scale_out
CUBE 内置 BiasBuf — L0C 可从 BiasBuf 读取 bias 初值,硬件完成累加;ZP 校正项需 VEC 软件补偿(CUBE 无原生 ZP 支持)。
1.9 量化算法适用场景对比
| 算法 | 精度损失 | 存储压缩 | 算力提升 | 硬件支持度 |
|---|---|---|---|---|
| INT8 对称 per-tensor | 中 | 4× (vs FP32) | 2× (vs FP16) | ★★★★★ CUBE+FIXP 原生 |
| INT8 非对称 per-channel | 中低 | 4× | 2× | ★★★★ FIXP vector mode; CUBE 需 ZP 补偿 |
| FP8 (E4M3/E5M2) | 低 | 4× | 2× | ★★★★★ CUBE+SIMD 原生 |
| MX-FP4 | 中 | 8× | 4× | ★★★★★ CUBE+MX_MEM 原生 |
| A16W8 | 低 (A 无损) | 2× (W 压缩) | 1× | ★★★★★ CUBE 原生 |
| A16W4 | 中 (A 无损) | 4× (W 压缩) | 1× | ★★★★★ CUBE 原生 |
| A8W4 | 中 | 4~8× | 2× | ★★★★★ CUBE 原生 |
| QAT (低 bit 训练) | 最低 | 同量化 | 同量化 | ★★★★ SIMD VSELRV2 + convert |
| Per-Group (Microscaling) | 最低 | 略增 (scale 存储) | 同 dtype | ★★★★★ MX_MEM 原生 |
第二部分:Dtype 维度
2.1 CUBE 矩阵核支持的数据类型谱系
| 类别 | 数据类型 | 位宽 | Fractal Size (A×B) | 相对 FP16 算力倍数 | |
|---|---|---|---|---|---|
| 普通浮点 | FP16 / BF16 | 16 | (16,16)×(16,16) | 1× (基线) | |
| 普通浮点 | TF32 | 19 | (16,8)×(8,16) | 0.5× | |
| 普通浮点 | FP32 | 32 | (16,1)×(1,16) | 1/16× | |
| 低 bit | INT8 (S8×S8) | 8 | (16,32)×(32,16) | 2× | |
| 低 bit | HiF8 | 8 | (16,32)×(32,16) | 2× | |
| 低 bit | FP8 (E4M3/E5M2) | 8 | (16,32)×(32,16) | 2× | |
| 低 bit | S8×S4 | 8/4 | (16,32)×(32,16) | 2× | |
| 低 bit | MX-FP4 (E1M2/E2M1) | 4 | (16,64)×(64,16) | 4× | |
| 低 bit | HiF4 | 4 | (16,32)×(32,16) | 2× | |
| 非对称 | A16W8 | 16/8 | (16,16)×(16,16) | 1× | |
| 非对称 | A16W4 | 16/4 | (16,16)×(16,16) | 1× | |
| 非对称 | A8W4 | 8/4 | (16,32)×(32,16) | 2× |
关键差异:FP4/E4M3 不支持 NaN/Inf,硬件转为 NaN→Zero / Inf→±MAX sat mode。与 IEEE 754 不一致,需软件感知。
第三部分:Layout 维度
3.1 数据排布总览
| 排布 | 维度 | 全称 | 使用场景 | 硬件支持路径 |
|---|---|---|---|---|
| NZ (NC1HWC0) | 5D | N-C1-H-W-C0 | CUBE 计算内部标准排布 | 默认(L0A/L0B/L0C) |
| ND (NHWC) | 4D | N-H-W-C | 推理输出、标准框架 | FIXP NZ2ND |
| DN (NCHW) | 4D | N-C-H-W | 训练、标准输入 | FIXP NZ2DN |
3.2 低 bit 量化场景 Layout 差异
| 维度 | 普通 FP16 | 低 bit 量化 | 软件负担 |
|---|---|---|---|
| L0A/L0B 排布 | FMIX: K1M1M0K0 4 Bank | FP4 需 6 Bank,HiF4 分两个 Fractal (E1_8/E1_16) | 无 — 硬件自动 |
| MX Memory | 不存在 | 独立 8KB MX_MEM (L0A/L0B 各),每 32/64 元素共享 scale | 有 — 需编排 MX 值搬运 |
| 输出 Layout 转换 | VEC 手动 NZ→ND/DN | FIXP NZ2ND/NZ2DN 随路转换,支持全低 bit dtype | 减负 — 省 VEC pass |
| INT4 存储 | 不适用 | S4 存于 L1 时需关注 2 元素/byte 的打包排布 | 略有 — 但 MTE/FIXP 透明 |
3.3 FIXP Layout 转换 — NZ2ND vs NZ2DN
| 特性 | NZ2ND (5D→ND) | NZ2DN (5D→DN) |
|---|---|---|
| 源数据 | L0C 内 NC1HWC0 (5D) | 同 |
| 目标格式 | NHWC (ND) | NCHW (DN) |
| C0-stride | 不支持 | 支持(并行度降至 C0/2) |
| 支持 dtype | S32/FP32/FP16/BF16/FP8/HiF8/B8/S4 | 同 |
| 写 OUT/L1 带宽 | 128B/cyc | 128B/cyc |
| 写 UB 带宽 | 256B/cyc | 128B/cyc |
| 行合并优化 | N==loop2_dst_stride 条件触发 | M==loop1_dst_stride 条件触发 |
NZ2ND 地址计算(4 层嵌套循环):软件通过 loop_src_stride / loop_dst_stride 控制每层维度步长,数据块内部必须连续。
3.4 MX_MEM Layout(低 bit 量化特有)
| dtype | block size | MX 值粒度 | MX_MEM 地址 |
|---|---|---|---|
| FP4 / FP8 | 32 元素 | 2^N scale | Base + 32B×⌈K/64⌉×⌊m/16⌋ + 32B×⌊k/64⌋ |
| HiF4 | 64 元素 | 2^N + E1_8 + E1_16 | Base + 64B×⌈K/64⌉×⌊m/16⌋ + 64B×⌊k/64⌋ |
HiF4 MX 值结构(64bit):
[63:56] Scale (8bit, 2^N)
[55:24] Reserved
[23:16] E1_8 (8bit, 每8元素的共享 exponent bit)
[15:8] E1_16 (8bit, 每16元素中后8元素的共享 exponent bit, 上半部=第一个8)
[7:0] Reserved
第四部分:转换(Conversion)维度
4.1 SIMD VEC 转换指令矩阵
| 转换方向 | 指令 | 精度模式 |
|---|---|---|
| FP32 → FP16 | VCVT | RNE |
| FP32 → BF16 | VCVT | RNE / Round-to-Odd ( 新增) |
| FP32 → FP8 (E4M3/E5M2) | VCVT | RNE |
| FP32 → HiF8 | VCVT | RNE |
| BF16 → FP8 | VCVT | RNE |
| BF16 → E8M0 | VCVT | — |
| 4bit → 8/16bit | VSELRV2 | LUT 查表 |
| FP16 → (VEC internal) | — | RNE |
4.2 Round-to-Odd 算法 — 低 bit 转换的精度保障
问题:FP32 → FP8 无直接指令时,需双步转换:FP32 → BF16(RNE) → FP8(RNE),但 RNE 两次舍入会产生 double rounding 误差。
Round-to-Odd 解决方案:
FP32 mantissa LSB 全部 OR-reduce → 1 bit → 填入 BF16 mantissa LSB
效果:不执行任何 rounding up/down,等价于生成 sticky bit
然后 BF16 → FP8 只需一次 round,误差等同于一次 FP32 → FP8
** 新增**:VCVT 指令的 round_to_odd 模式,专为多级转换链设计。这是低 bit 量化特有的精度 key feature。
第六部分:端到端量化工作流 — 映射
6.1 权重离线量化 (Weight-Only Quantization)
训练完模型
→ 离线: 统计 W per-channel max → scale_w (per-channel 非对称可选 zp_w)
→ 离线: W_fp32 → W_int8/W_fp8/W_fp4 (软件量化)
→ 离线: 存入 memory (S8/S4 打包, FP8 直接)
→ 推理时: MTE L2Prefetch→L1→L0A/L0B (dtype 透明搬运)
→ CUBE MMAD_MX / mmad a16w4 / mmad a16w8
6.2 激活值在线量化 (Activation Quantization with FIXP)
CUBE 计算 → L0C (FP32/S32)
→ MOV_L1_TO_FB(PBUF) {scale_a, zp_a, relu_param} ← 离线/VEC 在线计算的 scale
→ FIX_L0C_TO_OUT(REQ8, NZ2ND) {自动: quant+relu+layout convert}
→ OUT (S8, ND layout)
对比普通流程:省掉 VEC 一次 LD→MUL→ADD→ROUND→CLIP→ST→MTE NZ2ND 完整 pass。
6.3 MX 模式完整流程
离线: W_fp32 → W_mx_fp4 + scale_w_{per 32 elem} (2^N 编码)
→ 软件: scale_w 写入 MX_MEM (L0B 侧), W_mx_fp4 写入 L1→L0B
在线: A_fp32 → A_mx_fp8 + scale_a_{per 32 elem}
→ 软件: scale_a 写入 MX_MEM (L0A 侧), A 写入 L1→L0A
→ CUBE: MMAD_MX mx_fp4 → 硬件自动读 MX_MEM scale → 内积×(2^(scale_a+scale_b))
→ L0C (FP32)
6.4 QAT/fake-quant 训练流程
训练 forward:
W_fp16 → VSELRV2 (4bit LUT, fake quant to S4) → MMAD (FP16 域)
A_fp16 → VSELRV2 → MMAD
训练 backward (STE):
∂L/∂W_quant ≈ ∂L/∂W_fp16 // 梯度直通
更新: W_fp16 -= lr × ∂L/∂W_quant
推理:
离线: W_fp16 → W_int4 (最终量化)
在线: CUBE S8×S4 / MMAD_MX fp4
第七部分:Scale 与 Data Layout — 数据演示与复杂性分析
本节通过具体数值演示各种 scale 策略与数据排布的交互,揭示量化算法从理论到硬件实现的真实复杂性。
7.1 Scale 粒度对比 — 具体数值演示
7.1.1 示例数据
假设一个 2 通道 × 2 行 × 2 列的激活值张量 (C=2, H=2, W=2),FP32:
通道 0 (C0): 通道 1 (C1):
列0 列1 列0 列1
行0 [0.8 -1.2] 行0 [-0.15 0.25]
行1 [-0.6 0.3] 行1 [0.10 -0.05]
展平为: A = [0.8, -1.2, -0.6, 0.3, -0.15, 0.25, 0.10, -0.05]
7.1.2 Per-Tensor 对称量化到 INT8 (S8)
scale = max(|A|) / 127 = 1.2 / 127 ≈ 0.009449
所有通道共享同一个 scale
量化值:
C0: [ 0.8/scale≈85, -1.2/scale≈-127, -0.6/scale≈-64, 0.3/scale≈32 ]
C1: [-0.15/scale≈-16, 0.25/scale≈26, 0.10/scale≈11, -0.05/scale≈-5 ]
反量化的精度损失 (C1 受害最大):
C1_0: -16×0.009449 = -0.15118 (原 -0.15, 误差 0.8%)
C1_1: 26×0.009449 = 0.24567 (原 0.25, 误差 1.7%) ← C1 小信号被粗粒度 scale 压低
C1_3: -5×0.009449 = -0.04724 (原 -0.05, 误差 5.5%)
映射: FIXP REQ8, scalar mode
MOV_L1_TO_FB: 写 1 组 {scale} 到 PBUF
FIX_L0C_TO_OUT: 读 L0C 8 个 S32 → 1 个 scale 统一量化 → 写 OUT S8
硬件复杂度: ★☆☆☆☆ — PBUF 仅 1 个 scale 槽位,指令配置最简单
精度风险: ★★★★☆ — 跨通道 scale 差异大时 C1 等小信号通道精度严重损失
7.1.3 Per-Channel 对称量化到 INT8 (S8)
通道 0: scale_0 = max(|0.8,-1.2,-0.6,0.3|) / 127 = 1.2/127 ≈ 0.009449
通道 1: scale_1 = max(|-0.15,0.25,0.10,-0.05|) / 127 = 0.25/127 ≈ 0.001969
量化值:
C0: [85, -127, -64, 32] (同上)
C1: [-76, 127, 51, -25] ← 精度大幅提升!
反量化精度:
C1_0: -76×0.001969 = -0.14964 (原 -0.15, 误差 0.24%) ← 精度提升 3.3×
C1_3: -25×0.001969 = -0.04923 (原 -0.05, 误差 1.5%) ← 精度提升 3.7×
映射: FIXP VREQ8, vector mode
MOV_L1_TO_FB: 写 C=2 组 {scale_c} 到 PBUF (每 C0 通道 1 组)
写入量: 2×4B = 8B (vs scalar 的 4B)
FIX_L0C_TO_OUT: 读 L0C → per-C0 取出对应 scale → 量化 → 写 OUT
硬件复杂度: ★★☆☆☆ — PBUF 需存 per-C0 scale,但 C0 维度是 NZ 最内层,硬件自然对齐
精度收益: ★★★☆☆ — C0 方向对齐好时几乎无额外开销
7.1.4 Per-Group 量化 — MX 模式 (FP4 + Microscale)
假设 K=128 维度的权重矩阵的 K 方向一行 128 个元素,用 MX-FP4(E1M2):
原始 FP32 值 (K 方向连续 128 元素):
[0.011, 0.023, -0.008, 0.045, ..., 0.031, -0.019, 0.006, 0.052]
↑ block 0 (32 elem) ↑ block 1 (32 elem) ↑ block 2 ↑ block 3
每 32 元素计算 MX scale:
block 0: max(|0.011,0.023,...,0.045|) = 0.045
scale_0 = 2^ceil(log2(0.045)) = 2^(-5) = 0.03125 (MX 仅支持 2^N)
block 1: max(|...|) = 0.052
scale_1 = 2^(-5) = 0.03125 (恰好相同)
block 2: max(|...|) = 0.098
scale_2 = 2^(-4) = 0.0625
block 3: max(|...|) = 0.015
scale_3 = 2^(-7) = 0.0078125
MX_MEM 存储 (8B/block):
block 0: scale=0xFB ( -5 的 2's complement 8bit) → 实际 scale = 2^(-5)
block 1: scale=0xFB → 2^(-5)
block 2: scale=0xFC (-4) → 2^(-4)
block 3: scale=0xF9 (-7) → 2^(-7)
内积计算 (CUBE MMAD_MX):
block 0 内积: Σ(FP4_a × FP4_b) × 2^(-5-5) = Σ(FP4_a × FP4_b) × 2^(-10)
block 2 内积: Σ(FP4_a × FP4_b) × 2^(-4-4) = Σ(FP4_a × FP4_b) × 2^(-8)
复杂性揭示:
- block 0 和 block 3 指数差 3 个数量级 — 硬件需在 accumulator 中对齐 mantissa 后再加
- MX scale 限制为 2^N 意味着 scale 可能放大 (例: 0.045→0.03125, 浪费约 30% 动态范围)
- 128 元素需要 128×4bit(元素) + 4×8bit(scale) = 68B vs 128×16bit FP16 = 256B, 仅 26.6% 存储
7.1.5 Scale 转换的精度陷阱 — Round-to-Odd 数值演示
原始值: FP32 = 0.00097
错误路径 (双 RNE):
Step1 FP32→BF16(RNE): 0.00097 的 BF16 最近可表示值为 0.000971 → 向上舍入了一次
Step2 BF16→FP8(RNE): 0.000971 的 FP8 最近可表示值为 0.00095 → 又向下舍入
最终: 0.00095, 误差 2.1%
正确路径 (Round-to-Odd):
Step1 FP32→BF16(RTO): 0.00097 → BF16 mantissa LSB = FP32 LSBs 的 OR-reduce
= 0.000968 (mantissa LSB=1, 不动原值, 仅生成 sticky)
Step2 BF16→FP8(RNE): 0.000968 → FP8 0.00095 (round to nearest, tie even)
最终: 0.00095, 误差 2.1% — 虽然结果相同,但 round-to-odd 保证了这个 case 的正确性
更关键的 case:
原始 FP32 = 恰好位于 BF16 的两个可表示值正中间 (0.0009766)
双 RNE: 第一次 round up → 0.000977, 第二次 round up → 0.0010 (双舍入! 偏差 +2.3%)
单 RTO+RNE: sticky=0 → round to even → 0.00095 (偏差 -2.7%, 但这是正确的一次舍入行为)
7.2 数据排布 (Layout) 数据演示
以 C=4, H=2, W=2, C0=2 的张量为例。
7.2.1 逻辑张量
逻辑视图 (NCHW, DN format):
N=1, C=4, H=2, W=2
通道 0: 通道 1: 通道 2: 通道 3:
[0.1 0.2] [0.5 0.6] [0.9 1.0] [1.3 1.4]
[0.3 0.4] [0.7 0.8] [1.1 1.2] [1.5 1.6]
7.2.2 DN Layout (NCHW) — 内存排布
内存地址递增 →
[N0,C0,H0,W0] [N0,C0,H0,W1] [N0,C0,H1,W0] [N0,C0,H1,W1]
0.1 0.2 0.3 0.4
[N0,C1,H0,W0] [N0,C1,H0,W1] [N0,C1,H1,W0] [N0,C1,H1,W1]
0.5 0.6 0.7 0.8
[N0,C2,H0,W0] ...
0.9 1.0 1.1 1.2
[N0,C3,H0,W0] ...
1.3 1.4 1.5 1.6
特征: H×W 连续 → 适合卷积 kernel 滑动窗口
跨通道不连续 → CUBE 需要矩阵乘法时不友好
7.2.3 ND Layout (NHWC) — 内存排布
内存地址递增 →
[N0,H0,W0,C0] [N0,H0,W0,C1] [N0,H0,W0,C2] [N0,H0,W0,C3]
0.1 0.5 0.9 1.3 ← 4 通道打包
[N0,H0,W1,C0] [N0,H0,W1,C1] [N0,H0,W1,C2] [N0,H0,W1,C3]
0.2 0.6 1.0 1.4
[N0,H1,W0,C0] [N0,H1,W0,C1] [N0,H1,W0,C2] [N0,H1,W0,C3]
0.3 0.7 1.1 1.5
[N0,H1,W1,C0] [N0,H1,W1,C1] [N0,H1,W1,C2] [N0,H1,W1,C3]
0.4 0.8 1.2 1.6
特征: 同像素点跨通道连续 → 适合 1×1 Conv / GEMM 直接 load
空间上分散 → depthwise conv 不友好
7.2.4 NZ Layout (NC1HWC0) — CUBE 标准排布 (C0=2)
C0=2 意味着每 2 个通道打一个分形块 (fractal block)
分形块 0 (包含 C0~C1, i.e. C1=0):
C1 维度 = C // C0 = 4 // 2 = 2 个分形块
每个分形块内部: H×W×C0 元素连续
内存地址递增 (分形块 0, C1=0):
地址 0: [C0_block0, H0, W0, c=0] = [C=0, H=0, W=0] = 0.1
地址 1: [C0_block0, H0, W0, c=1] = [C=1, H=0, W=0] = 0.5 ← C0=2 连续
地址 2: [C0_block0, H0, W1, c=0] = [C=0, H=0, W=1] = 0.2
地址 3: [C0_block0, H0, W1, c=1] = [C=1, H=0, W=1] = 0.6
地址 4: [C0_block0, H1, W0, c=0] = [C=0, H=1, W=0] = 0.3
地址 5: [C0_block0, H1, W0, c=1] = [C=1, H=1, W=0] = 0.7
地址 6: [C0_block0, H1, W1, c=0] = [C=0, H=1, W=1] = 0.4
地址 7: [C0_block0, H1, W1, c=1] = [C=1, H=1, W=1] = 0.8
分形块 1 (包含 C2~C3, i.e. C1=1):
地址 8: [C0_block1, H0, W0, c=0] = [C=2, H=0, W=0] = 0.9
地址 9: [C0_block1, H0, W0, c=1] = [C=3, H=0, W=0] = 1.3
地址 10: [C0_block1, H0, W1, c=0] = [C=2, H=0, W=1] = 1.0
地址 11: [C0_block1, H0, W1, c=1] = [C=3, H=0, W=1] = 1.4
地址 12: [C0_block1, H1, W0, c=0] = [C=2, H=1, W=0] = 1.1
地址 13: [C0_block1, H1, W0, c=1] = [C=3, H=1, W=0] = 1.5
地址 14: [C0_block1, H1, W1, c=0] = [C=2, H=1, W=1] = 1.2
地址 15: [C0_block1, H1, W1, c=1] = [C=3, H=1, W=1] = 1.6
特征: C0 方向是最内层连续 → CUBE L0A/L0B 直接按 row 读取
C1 方向控制分形块切换 → 对应 CUBE fractal M/N 维度
7.2.5 Layout 转换的 FIXP 操作 — 数值演示
NZ→ND 转换 (FIXP NZ2ND, 上述 NZ tensor → NHWC):
FIXP 从 L0C 读取 NZ 数据 → 4 层嵌套循环产生 ND:
loop0 (C1=0..1): // 最外层迭代: C1=0 → C1=1
loop1 (H=0..1): // 第二层: H=0 → H=1
loop2 (W=0..1): // 第三层: W=0 → W=1
loop3 (C0=0..1):// 最内层: c=0 → c=1
读取: L0C[N0, C1, H, W, c]
写出: OUT[N0, H, W, C1*C0 + c]
NZ 输入 (内存地址 0~15, 见 7.2.4):
addr 0: 0.1 (C1=0,H0,W0,c0) → 写到 ND 位置 (H0,W0,C0)
addr 1: 0.5 (C1=0,H0,W0,c1) → 写到 ND 位置 (H0,W0,C1)
addr 2: 0.2 (C1=0,H0,W1,c0) → 写到 ND 位置 (H0,W1,C0)
...
ND 输出 (内存地址递增):
[H0,W0,C0]=0.1, [H0,W0,C1]=0.5, [H0,W0,C2]=0.9, [H0,W0,C3]=1.3 ← 同像素 4 通道
[H0,W1,C0]=0.2, [H0,W1,C1]=0.6, [H0,W1,C2]=1.0, [H0,W1,C3]=1.4
...
NZ→DN 转换 (FIXP NZ2DN, NZ→NCHW):
loop0 (C1=0..1):
loop1 (C0=0..1): // C0 在 H/W 之前! (DN 的 C 连续)
loop2 (H=0..1):
loop3 (W=0..1):
读取: L0C[N0, C1, H, W, c]
写出: OUT[N0, C1*C0 + c, H, W]
DN 输出:
C=0: [0.1, 0.2] [0.3, 0.4] ← 通道 0 全部空间 (H×W)
C=1: [0.5, 0.6] [0.7, 0.8] ← 通道 1 全部空间
C=2: [0.9, 1.0] [1.1, 1.2] ← 通道 2
C=3: [1.3, 1.4] [1.5, 1.6] ← 通道 3
关键区别: NZ2DN 比 NZ2ND 多了 C0-stride 支持
C0-stride 时并行度降为 C0/2 (从 4 通道并行降为 2 通道)
7.3 Scale × Layout 交互 — 复杂性的根源
7.3.1 Per-Channel Scale + NZ Layout 的自然对齐
NZ Layout 下 C0 是最内层维度,per-channel scale 按 C0 对齐:
NZ 内 C0 方向 (C0=2):
[C=0, C=1] ← 两个通道在一个 C0 block 内连续
PBUF 存储 per-C0 scale:
Scale_0 (for C=0) = 0.009449
Scale_1 (for C=1) = 0.001969
FIXP 量化时:
读 NZ[addr0] = 0.1 (C=0) → 用 scale_0 → quant = 11
读 NZ[addr1] = 0.5 (C=1) → 用 scale_1 → quant = 254 ← 无缝切换 C0 内 scale!
读 NZ[addr2] = 0.2 (C=0) → 用 scale_0 → quant = 21
...
C0 方向 scale 切换: 硬件每读 C0(=2) 个元素后自动切换到下一组 scale
复杂度: 极低 — C0 内 scale 是 PBUF 的连续 entry
7.3.2 Per-Channel Scale + DN Layout — 复杂寻址
DN Layout 下, channel 在 N 和 H×W 之间:
DN 内存: [C0,H0,W0][C0,H0,W1][C0,H1,W0][C0,H1,W1][C1,H0,W0]...[C3,H1,W1]
如果要用 per-channel scale 做 NZ2DN:
FIXP 读 L0C(NZ) → 内部缓存+HW_MRG+COL_BUF 转置 → 按 DN 写出
转置过程中 H×W 和 C 的维度交换:
原来 L0C 中 C0 方向连续的元素, 转置后分散到不同 DN 行
per-channel scale 需在转置前/后插入 → 转置前应用 (量化在 NZ 域做)
FIXP 处理顺序:
1. L0C 读出 NZ 数据 (C0 连续)
2. Pre_Proc (Quant/Relu) — 此时仍然是 NZ 域的 C0 对齐 → scale 自然对齐
3. Fixpipe NZ2DN 转置 → 输出 DN
4. 如果 post-quant 在转置后做 → scale 需按 C 通道随机访问
结论: FIXP 的 quant 在 pre-proc 阶段做 (NZ 域, C0 自然对齐),避免了 scale 跨 layout 的
复杂寻址问题。这是硬件-软件协同设计的精妙之处。
7.3.3 MX Scale + CUBE L0A/L0B Layout — 分形块尺度
CUBE MMAD_MX, FP4, 16×16 PE Slice:
L0A FP4 Layout (K1M1M0K0, A matrix M×K):
M fractal: 16 elem (row 方向 16)
K fractal: 64 elem (col 方向 64, 因为 FP4 Fractal K = 64)
每 32 个 K 方向元素共享 MX scale → K 方向 64 元素需要 2 组 MX scale:
block_0: elements k=0..31 → scale_a0 = 2^(sa0)
block_1: elements k=32..63 → scale_a1 = 2^(sa1)
L0B FP4 Layout (K1N1N0K0, B matrix K×N):
K fractal: 64 elem
N fractal: 16 elem
同 K 方向 64 元素, 也需要 2 组 MX scale:
block_0: elements k=0..31 → scale_b0 = 2^(sb0)
block_1: elements k=32..63 → scale_b1 = 2^(sb1)
乘法过程 (1 个 fractal):
subblock 0: Σ(A[k=0..31] × B[k=0..31] ) × 2^(sa0+sb0)
subblock 1: Σ(A[k=32..63] × B[k=32..63]) × 2^(sa1+sb1)
→ 对齐 exponent 后相加 → L0C(FP32 accumulator)
关键复杂性:
1. L0A 和 L0B 的 MX scale 需要按 {K/32} 粒度一一配对
2. subblock 0 和 subblock 1 的 exponent 可能不同 (sa0+sb0 ≠ sa1+sb1)
硬件需在 group summation (E2a 阶段) 对齐 exponent
3. HiF4 更复杂: block size=64 (只有 1 组 MX scale), 但额外 E1_8/E1_16
每 8/4 元素共享 1bit exponent → 一个 fractal 内 8 种 exponent 组合
MX scale 与数据排布的对齐关系:
L0A K1M1M0K0 排布中, K 是第二维 (K1K0):
- K0=64 时, K 方向的 64 元素物理连续 (便于 burst 读 MX_MEM)
- K1 方向是 fractal 级切换 (不同 fractal 的 MX scale 在 MX_MEM 的不同 row)
MX_MEM Bank 布局: FP4 → MX_MEM 4 Bank × 16B/Bank
→ PE Slice 16 个 port 同时读取 16 个 row 的 MX scale
→ 每个 PE 需要知道自己负责的 K 范围对应哪个 MX block
7.3.4 量化 + Layout 转换全链路数值演示
以一个完整算子为例: Linear(W × X + B), H=128→64, INT8 per-channel 量化。
输入: X_fp32 (128-dim vector) → FIXP quant to INT8
权重: W_fp32 (64×128 matrix) → offline quant to INT8 per-channel (C0=16)
偏置: B_fp32 (64-dim vector) → offline quant to INT32
输出: Y_fp32 (64-dim vector) ← CUBE S32 output
Step 1: X 量化 (VEC/FIXP)
X_fp32 = [0.12, -0.34, 0.56, ...] (128 elem)
FIXP REQ8, per-tensor: scale_x = max(|X|)/127
→ X_int8 = [round(x/scale_x) for x in X]
Step 2: MTE 搬运权重到 L0A/L0B (ND2NZ)
W_int8 在 memory 中 per-C0=16 打包为 NZ 格式:
离线量化的 W_int8[64,128] → MTE ND2NZ(C0=16)
→ L0A NZ: fractal K=32, M=16 (INT8 Fractal: (16,32)×(32,16))
关键问题: per-channel scale_w 的 C0=16 与 NZ 的 C0=16 对齐
scale_w[0:15] 对应 NZ 的 C1=0 内 16 个 C0 通道
scale_w[16:31] 对应 NZ 的 C1=1 内 16 个 C0 通道
... 共 64/16 = 4 组
Step 3: CUBE 计算 (INT8 fractal)
PE 计算: S8×S8 → S32 accumulator
L0C 输出: S32 NZ layout, C0=16
Step 4: FIXP 量化输出 + NZ2ND (随路)
MOV_L1_TO_FB: 加载 per-channel scale_y[0..63] (VREQ8, vector mode)
还要加载 bias_q[0..63] 到 PBUF
FIX_L0C_TO_OUT(REQ8, NZ2ND):
Pre_Proc:
C0=0: (L0C_val + bias_q[0]) * scale_y[0] → S8
C0=1: (L0C_val + bias_q[1]) * scale_y[1] → S8
... (64 个 C0 通道, scale_y 在 PBUF 中连续, 自动递增)
Fixpipe:
NZ2ND: [N=1, C1=0..3, C0=0..15, H=1, W=1] → [N=1, H=1, W=1, C=0..63]
全链路 Layout 变化:
Memory: X(DN) ──(VEC)──→ X(NZ) ──(MTE→L0B)──→ CUBE
Memory: W(DN) ──(offline quant)──→ W(NZ) ──(MTE→L0A)──→ CUBE
CUBE: L0A(NZ) × L0B(NZ) → L0C(NZ, S32)
FIXP: L0C(NZ, S32) ──(REQ8+NZ2ND)──→ OUT(ND, S8)
Scale 的粒度取舍在链路中的权衡:
- X 用 per-tensor scale → 量化简单,但大动态范围通道受害
- W 用 per-channel scale → C0=16 对齐 NZ, 量化参数 PBUF 可直接按 C0 步进
- Y 用 per-channel scale → 同上
- 全程 scale 转换需软件在 VEC/FIXP 间协调传递
更多推荐


所有评论(0)