昇腾 NPU 上的 HiFloat4 4-bit 块缩放伪量化算子:AMCTamct_ops/hifloat4_cast完全解析
【免费下载链接】amctAMCT是CANN提供的昇腾AI处理器亲和的模型压缩工具仓。项目地址: https://gitcode.com/cann/amct
本技术指南以 CANN AMCT 仓库中amct_ops/hifloat4_cast算子包为核心,系统讲解 HiFloat4(HiF4)4-bit 块缩放浮点格式的原理、FP→HiF4→FP 伪量化仿真的接口用法、AscendC 设备端内核的逐级量化流水线,以及面向 A2/A3/A5 平台的编译与测试方法。读完本文,你将掌握hifloat4_fake_quant的完整调用方式与内部实现机制,能够在自己的量化部署流程中直接复用该算子,并理解其与 CPU 参考实现、测试用例之间的对应关系。
HiFloat4 格式概述:S1P2 元素 + 三级块缩放
HiFloat4(HiF4)是一种 4-bit 块缩放浮点格式,用于在保持可用精度的前提下把 FP16/BF16 权重或激活压缩到平均每元素 4 bit 的量级。其编码分为两层:
- 元素级 S1P2 表示:每个元素使用 1 位符号(sign)+ 2 位尾数(mantissa),组内可表达的数值幅度共 8 种(
S1PNg,其中Ng=2,即组内编码占 3 bit)。由 hifp_impl.py 中的常量可知:E6_OFFSET = 48、E6M2_MBITS = 2、NG = 2,与 README 描述的 S1P2 语义完全一致。 - 三级缩放(block scaling):沿量化维每 64 个元素共享一组缩放因子:
- L1(
scale_factor):覆盖整个 64 元素块,以 E6M2 格式存储,由块内最大值推导得出; - L2:每 8 个元素一个微指数(micro exponent),取值 ∈ {1, 2};
- L3:每 4 个元素一个微指数,取值 ∈ {1, 2}。
- L1(
需要特别强调的是,该算子只实现伪量化仿真(fake-quant):即完成一次 FP → HiF4 → FP 的量化再反量化往返,输出与输入同 shape、同 dtype 的浮点张量,并不产生真实的打包 4-bit 数据。这种仿真方式适合在训练或 PTQ 标定阶段以可导/无损替换的方式评估量化误差。
算子能力:按输入 dtype 选择底层内核
hifloat4_cast根据输入张量的 dtype 分发到不同的设备端内核,两条路径在内部均以 fp32 计算,差异只在转换时机与 bf16 舍入位置:
| 输入 dtype | 底层内核 | 内部计算方式 |
|---|---|---|
| BF16 | hifx_kernel_bf16 | fp32:加载后转 fp32,仅在指定计算点(scale factor、倒数、组内值)做 bf16 舍入,输出时转回 bf16 |
| FP16 | hifx_kernel | fp32:先在主机侧转 fp32,计算完成后输出转回 fp16 |
该分派逻辑可在 hifloat4_cast_torch.cpp 中确认:cvt_fp32对 FP16 输入在主机侧完成 fp32 提升,随后按in_dtype == at::kBFloat16选择run_hifx_kernel_bf16,否则走run_hifx_kernel。BF16 路径则由内核内的vconv_bf162f32在加载后完成转换(见 hifloat4_cast_kernel.cpp)。
接口说明:hifloat4_fake_quant(x, qdim=-1)
函数签名与语义
hifloat4_fake_quant(x: torch.Tensor, qdim: int = -1) -> torch.Tensor沿qdim维将 FP16/BF16 张量按每 64 个元素一个共享 scale 分块,执行一次 FP → HiF4 → FP 伪量化仿真。
参数:
x:NPU 设备上的输入张量,dtype 为torch.float16或torch.bfloat16,支持任意非空 shape;qdim:分块维度,默认-1(最后一维)。
返回值:
- 与输入同 shape、同 dtype 的张量,包含 HiFloat4 量化误差。
异常:
RuntimeError:dtype 不是 float16 / bfloat16,或输入张量不在 NPU 设备上;RuntimeError:qdim维长度不是 64 的倍数。
示例:
from amct_ops.hifloat4_cast import hifloat4_fake_quant y = hifloat4_fake_quant(x) # 沿最后一维分块 y = hifloat4_fake_quant(w, qdim=1) # Linear 权重 [out, in] 沿 in 维分块 y = torch.ops.amct.hifloat4_fake_quant(x, -1) # 等价的 torch.ops 调用主机侧处理流程
从 hifloat4_cast_torch.cpp 的实现可以看出,主机侧完成以下五步:
- 维度校验:
qdim支持负数索引(qdim >= 0 ? qdim : qdim + nd),越界会抛RuntimeError; - PermuteToLast:把
qdim维移动到最后一轴(input.permute(perm).contiguous()),使内核始终沿最后一维归约;若qdim本来就是末维则仅做contiguous(); - 64 对齐校验 + 512 对齐补零:先校验末维长度是 64 的倍数(否则抛错),再通过
PadLastDim以 0 补齐到 512 的倍数——由于 0 不影响块最大值,补零是安全的; - 内核调用:以
HIF4_BLOCK_DIM = 40的 blockDim、HIF4_MANT_BIT = 3调用对应内核; - 还原:
slice(-1, 0, n)裁掉补零,必要时把结果Unpermute回原始维度顺序。
源码级原理剖析
Python 包装层:加载 .so 并注册算子
- ops.py 中
hifloat4_fake_quant是一个极薄的包装,直接转发给torch.ops.amct.hifloat4_fake_quant(x, qdim); - init.py 在包导入时通过
torch.ops.load_library加载同目录下的libhifloat4_cast_ops.so,这是torch.ops.amct命名空间能够解析的前提;导入前先import torch_npu以注册 PrivateUse1 后端。
算子注册:schema、NPU 实现与 Meta 实现
register.cpp 展示了完整的注册骨架:
TORCH_LIBRARY_FRAGMENT(amct, m) { m.def("hifloat4_fake_quant(Tensor input, int qdim=-1) -> Tensor"); } TORCH_LIBRARY_IMPL(amct, PrivateUse1, m) { ... } // NPU 实现:dtype/device 校验 TORCH_LIBRARY_IMPL(amct, Meta, m) { ... } // 形状推导:输出与输入同 shape/dtype其中PrivateUse1 实现负责设备与 dtype 校验(允许 float16 / bfloat16 / float32),Meta 实现声明输出形状与输入完全一致,因此 shape 推断是恒等映射。
设备端内核:12 级计算流水线
内核主体是一个手工编写的 AscendC 模板类Hifv14Kernel<T, E6MX>,其 Compute() 将一次 512 元素批(BATCH = 512,向量指令按 64 元素段SEG_ELEMS = 64操作)的量化拆分为 12 个小阶段:
LoadInput → ZeroTail → PrepareSignAbs → HandleNonFinite → ReduceBlockMax → PoisonBlock → RoundScaleE6M2 → ComputeExp1 → Exp2PartMax → Exp2Select → ComputeMantissa → Reconstruct → StoreOutput各阶段核心语义(与 hifloat4_cast_kernel.cpp 头部注释及实现一致):
- ReduceBlockMax(L1 scale):
scale_factor = round_E6M2(bf16(max/7)),并钳制到[2^-48, 49152];vcmax归约块最大值、vbrcb广播,随后通过vconv_f322bf16r/vconv_bf162f32完成 bf16 舍入; - ComputeExp1(L2 微指数):
exp1 = (max8 * rec >= 4) ? 2 : 1,其中rec = 1/scale经过 bf16 舍入; - Exp2PartMax / Exp2Select(L3 微指数):先分别求 4 元素组的奇偶半组最大值(
vcgmax),再判定exp2 = (max4 / exp1 * rec >= 2) ? 2 : 1; - ComputeMantissa(S1P2 组内编码):
mant = abs / exp2 / exp1 * rec,先做 bf16 舍入,再通过*4 + 0.5后截断实现 round-half-up,最终钳制到±1.75(即±(2 - 2^-2)); - Reconstruct:
out = sign * mant * exp2 * exp1 * scale,四步连乘还原。
非有限值处理:块级 NaN 投毒(Poison Block)
README 未展开但源码注释与测试明确强调的细节是:含 NaN/±Inf 的块整体反量化为 NaN。其处理策略值得关注:
- 在
HandleNonFinite中,非有限元素对块最大值贡献被掩蔽为 0(vsel选择 0),避免污染 scale; - 块内任意元素非有限,则该块的干净标志位被置脏(
PoisonBlock); - 但 NaN 替换被延迟到 Reconstruct 输出阶段执行,而不是提前注入 scale——因为 NaN 一旦流入
vdiv/vconv会触发 AI Core 异常(测试注释中提到异常码 507057),导致后续所有 launch 失败。
这一点在 test_hifloat4_cast.py 的test_kernel_nan_block_poison_keeps_device_healthy回归测试中有完整覆盖:含 NaN/Inf 的块输出全 NaN,干净块与 CPU 参考逐位一致,且后续正常输入仍可正常启动内核。
与 CPU 参考实现的关系
算子正确性以纯 PyTorch 参考实现为基准,参考实现在 hifp_impl.py 中:
hif4_pack/hif4_unpack(L260-L334)实现真实的 2-D HiF4 打包/解包(输出 scale + value 两类 uint8 字节),其校验规则(n_cols % 64 == 0、仅支持 2-D)与 NPU 算子保持一致;hifloat4_fake_quant(L347-L357)是一个分发函数:当输入在 NPU 且为 FP16/BF16 时优先调用amct_ops内核,否则回退到纯 torch 参考实现。这意味着即使没有安装amct_ops,同一接口仍可在 CPU 上完成等效仿真。
目录结构
amct_ops/hifloat4_cast/ ├── op_kernel/ │ ├── hifloat4_cast_kernel.cpp # 设备端内核(FP→HiF4→FP 仿真往返) │ └── tensorutils.h # Ascend C 工具函数(内核侧张量操作) ├── op_extension/ │ ├── hifloat4_cast_torch.cpp # PyTorch 主机实现(qdim 预处理与内核调用) │ ├── ops.h # PyTorch C++ 扩展函数声明 │ └── register.cpp # TORCH_LIBRARY 注册 ├── python/ │ └── hifloat4_cast/ │ ├── __init__.py # Python 包入口(加载 .so 并注册算子) │ └── ops.py # Python 接口实现 ├── CMakeLists.txt # CMake 构建配置 ├── README.md # 算子文档(中文) └── README_en.md # 算子文档(英文)环境依赖
- CANN 9.0.0
- Python ≥ 3.9
- PyTorch + torch_npu(与对应 CANN 版本匹配)
构建时还需保证ASCEND_HOME_PATH已通过source $ASCEND_HOME_PATH/set_env.sh设置——CMakeLists 中会对其显式检查并报错(见 CMakeLists.txt)。
编译方法
方法一:统一打包构建(推荐)
在amct_ops目录下执行:
cd amct_ops bash ops_build.sh [--soc <soc>] [hifloat4_cast]平台与 NPU 架构对照:
--soc | 平台 | --npu-arch | 默认 |
|---|---|---|---|
ascend910b | A2(910B1/B2/B3,UB 256 KB) | dav-2201 | ✓ |
ascend910_93 | A3(910_93,UB 512 KB) | dav-2201 | |
ascend950 | A5(UB 512 KB+) | dav-3510 |
A2/A3 共用同一 ISA(
dav-2201),构建产物相同(UB 差异由运行时GetCoreMemSize()解决,见 CMakeLists.txt 注释)。 A5 构建要求当前 CANN 编译器支持dav-3510。若bisheng报Unsupported NPU architecture or soc,需切换到支持 A5 目标的 CANN 编译环境。
常用命令组合:
bash ops_build.sh # 所有算子,默认平台 bash ops_build.sh --soc ascend910_93 # 所有算子,指定平台 bash ops_build.sh hifloat4_cast # 指定算子,默认平台 bash ops_build.sh --soc ascend950 hifloat4_cast # 指定算子,指定平台 pip install dist/amct_ops-*.whl方法二:单算子独立编译(开发调试)
通过-DNPU_ARCH指定目标平台(默认 A2):
| 平台 | -DNPU_ARCH |
|---|---|
| Atlas A2 系列产品 | dav-2201(默认) |
| Atlas A3 系列产品 | dav-2201(与 A2 相同) |
| Ascend950PR / Ascend950DT | dav-3510 |
cd amct_ops/hifloat4_cast source $ASCEND_HOME_PATH/set_env.sh mkdir -p build && cd build cmake .. -DNPU_ARCH=dav-2201 # A2 / A3(默认) # cmake .. -DNPU_ARCH=dav-3510 # A5 make -j8构建产物位于:
build/libhifloat4_cast_ops.so已知 CMake 警告:使用 pip 安装的 PyTorch 构建时,find_package(Torch)可能输出如下警告:
static library kineto_LIBRARY-NOTFOUND not found.该警告来自 PyTorch 自带的TorchConfig.cmake,表示未找到 Kineto profiler 的静态库。hifloat4_cast不依赖 PyTorch profiler / Kineto 能力,只要 CMake configure、编译与链接成功,此警告可安全忽略。
使用示例
import torch import torch_npu from amct_ops.hifloat4_cast import hifloat4_fake_quant # 伪量化仿真(FP16/BF16 → HiF4 → FP16/BF16) x = torch.randn(1024, 256, dtype=torch.bfloat16, device='npu') y = hifloat4_fake_quant(x) # shape [1024, 256], dtype bfloat16 # 指定分块维度 w = torch.randn(4096, 1024, dtype=torch.float16, device='npu') y = hifloat4_fake_quant(w, qdim=1) # 沿 in_features 分块也可通过torch.ops.amct直接调用底层算子:
y = torch.ops.amct.hifloat4_fake_quant(x, -1)精度验证
精度验证方法详见 tests/amct_ops/test_hifloat4_cast.py:
| 测试场景 | 结果 |
|---|---|
| 随机 BF16/FP16 伪量化往返仿真 | ✓ 与 CPU 参考实现逐元素一致(max abs diff < 1e-6) |
| 非 64 对齐维度 | ✓ 抛出 RuntimeError(fake-quant 与 pack 行为一致) |
| 零输入 | ✓ 无 NaN,输出全零 |
| NaN/Inf 块投毒(回归) | ✓ 非有限块整体为 NaN,干净块与参考逐位一致,设备健康 |
其中逐元素一致性测试(_assert_kernel_matches_reference)将 NPU 内核输出与hifloat4_fake_quant纯 torch 参考在 1e-6 容差内比较;CPU-only 的 shape 回归测试还覆盖了hif4_pack的 scale/value 打包形状(如 2x128 → scale (2,2,4) + value (2,64)),并守护了 squeeze 索引错位的历史 bug。
测试方法
在仓库根目录执行:
# 先构建 amct_ops,再通过 staging 运行测试 bash amct_ops/ops_build.sh hifloat4_cast # NPU 内核与 CPU 参考实现逐元素一致性验证 + shape 回归测试 PYTHONPATH=amct_ops/staging python3 -m unittest tests.amct_ops.test_hifloat4_cast也可以先安装 wheel 再运行测试:
pip install amct_ops/dist/amct_ops-*.whl python3 -m unittest tests.amct_ops.test_hifloat4_cast更多测试执行说明参见 tests/amct_ops/README.md。
小结
amct_ops/hifloat4_cast以“单次 FP→HiF4→FP 伪量化仿真”为核心,将 S1P2 元素编码与 L1/L2/L3 三级 64 元素块缩放完整落地为 AscendC 内核,并提供了从 Python 接口、torch.ops 注册、主机侧预处理到设备端 12 级量化流水线的全链路实现。无论是评估 HiFloat4 对特定层权重的量化误差、在 PTQ 标定中做误差仿真,还是将其作为打包算子的精度基准,hifloat4_fake_quant都是一份开箱即用、可独立编译与验证的参考实现;其与 hifp_impl.py 中的 CPU 参考保持同一套语义,为跨端一致性与后续训练/推理集成提供了可靠基础。
【免费下载链接】amctAMCT是CANN提供的昇腾AI处理器亲和的模型压缩工具仓。项目地址: https://gitcode.com/cann/amct
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考