| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(Quantize): Quantize算子补齐ascend910b原生AscendC实现 Co-authored-by: zhaohujie<zhaohujie@huawei.com> # message auto-generated for no-merge-commit merge: !7420 merge worktree-quantize-910b into master feat(Quantize): Quantize算子补齐ascend910b原生AscendC实现 Created-by: zhaohujie Commit-by: zhaohujie Merged-by: cann-robot Description: ## 描述 为 Quantize 算子补齐 **ascend910b** 的原生 AscendC 实现,落在 experimental/quant/quantize/。 **改动原因**:仓库现有的 quant/quantize 仅覆盖 ascend950 —— kernel 入口 quantize_apt.cpp 只 #include arch35/*_regbase.h 且以 __NPU_ARCH__ == 3510 守护,host tiling 仅在 op_host/arch35/,二进制配置仅在 op_host/config/ascend950/, AICore().AddConfig 也只声明了 ascend950。因此该算子在 ascend910b 上没有可用 kernel。 **所采取的方法**: - 新增 ascend910b(DAV_2201 标准编程模型,非 regbase)原生 kernel op_kernel/quantize.{cpp,h}:统一 fp32 计算路径 (x -> fp32 -> 标量倒数 Muls(1/scale) -> Adds(zero_points) -> 饱和 CastOut 阶梯)。 - 新增 host 侧 op_host/quantize_tiling.{cpp,h}(per-tensor / per-channel 多核与 UB 切分)、op_host/quantize_infershape.cpp, 并在 op_host/quantize_def.cpp 增加 AddConfig("ascend910b")。 - 复用源算子 SoC 无关的 aclnn / L0 接口层,交付可调用的 aclnnQuantize 两段式接口。 - 计算语义:y = round(x / scales + zero_points),再按 dtype 属性做饱和定点转换。 **支持范围(首版)**:per-tensor、per-channel(axis 语义);x ∈ {FLOAT, FLOAT16, BFLOAT16}、 scales ∈ {FLOAT, BFLOAT16}、zero_points ∈ {INT8, UINT8, INT32, BFLOAT16, 缺省}、y ∈ {INT8, UINT8, INT32}; 属性 dtype(必选)、axis(可选,默认 1);固定 round-to-nearest / div / no-sqrt。 **暂不支持(后续扩展)**:fp8 输出(HIFLOAT8 / FLOAT8_E5M2 / FLOAT8_E4M3FN —— ascend910b 无 fp8 能力)、 per-head、per-channel-nddma、FP32 zero_points。 **实现要点**:zero_points 按其**运行时真实 dtype** 读取(dtype 码由 tiling 携带),不依赖 kernel 二进制的编译期占位类型。 原因是 kernel 二进制按 (x, scales, y) dtype 组合去重分发,可选输入 zero_points 的 dtype 不在分发键内 —— 若按编译期类型读取,INT32 的 zero_points 会被分发到以 INT8 编译的二进制并以 1 字节步长误读(per-channel 下取到错误字节)。 ## 关联的Issue https://gitcode.com/cann/ops-nn/issues/4047 ## 测试 测试环境:Atlas A2(Ascend 910B3 / ascend910b),CANN 9.0.0。 **UT(CPU 孪生)—— 28/28 PASS** - tests/ut/op_host/:tiling 5 项(tiling key 选择、channelNum / rowLen / totalRows / hasZeroPoint、多核切分、空 tensor)+ infershape 2 项 - tests/ut/op_api/:aclnn dtype 与错误码矩阵 15 项(空指针、scales 非 1 维、scales/zero_points 元素数不匹配、 per-channel 元素数 != x.shape[axis]、axis 越界、非法输出 dtype) - tests/ut/op_kernel/:ICPU_RUN_KF 对 numpy golden 6 项(覆盖 fp16/bf16 cast-in、int8/uint8/int32 CastOut、两种 tiling key、有无 zero_points) - 命令:bash build.sh -u --ophost | --opapi | --opkernel --experimental --soc=ascend910b --ops=quantize **ST 精度(上板)—— 208/208 PASS** - 判据 stand_quantize(|diff| <= 1);pyaclnn(NPU)对 cpu golden saturate_cast_to_y(rint(x / scales + zero_points)) - 覆盖:per-tensor 114 例 / per-channel 94 例;x 为 fp32 55 例、fp16 53 例、bf16 100 例; 含 int8 / uint8 / int32 输出、UINT8 饱和 clamp [0,255]、rint 半整数边界、rowLen=1 的病态 per-channel、多核大 shape、空 tensor - 用例集:tests/st/aclnnQuantize/{atk_aclnnQuantize.json, executor_aclnnQuantize.py} **构建** - bash build.sh --pkg --experimental --soc=ascend910b --ops=quantize 通过。 **性能** - 本算子在 CANN 9.0.0 部署包中对所有 SoC 均未部署 builtin,源仓仅有的 ascend950 实现无法在 ascend910b 上运行, 因此**不存在可运行的相对基线**,不给出加速比。 - 对新 kernel 自身做了 2 轮优化(标量倒数 Muls 取代 Duplicate + 真 Div,释放 scale 广播 UB 缓冲; SetDeqScale 提到 Init 只设一次并去掉冗余的尾部 PipeBarrier<PIPE_V>):8 组代表性用例的 device 时延合计 776.2us -> 766.7us(1.012×,向量受限的 int8 per-tensor 用例实测 -2.7% ~ -3.1%)。两轮优化后精度全程保持 208/208。 ## 文档更新 - 新增 experimental/quant/quantize/README.md(功能与计算公式、产品支持情况、参数说明、支持范围与约束、调用说明)。 - 新增 experimental/quant/quantize/docs/aclnnQuantize.md(aclnnQuantize 两段式接口文档)。 ## 类型标签 <!-- [x] 表示选中 --> - [ ] Bug修复 - [x] 新特性 - [ ] 性能优化 - [ ] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 ## 附Quantize实现与测试报告 ## 摘要(当前实现) ascend910b(DAV_2201 标准编程模型)的原生 AscendC Quantize kernel,落在 experimental/quant/quantize/, 复用既有 SoC 无关的 aclnnQuantize 两段式接口层。计算 y = round(x / scales + zero_points) 后按 dtype 属性做饱和定点转换。对外入口:aclnnQuantizeGetWorkspaceSize / aclnnQuantize。支持 per-tensor 与 per-channel(axis 语义)两种模式;x ∈ {FLOAT, FLOAT16, BFLOAT16}、scales ∈ {FLOAT, BFLOAT16}、 zero_points ∈ {INT8, UINT8, INT32, BFLOAT16, 缺省}、输出 y ∈ {INT8, UINT8, INT32};固定 round-to-nearest / div / no-sqrt。实现要点:统一 fp32 计算路径(x→fp32 → 标量倒数 Muls(1/scale) → Adds(zp) → 饱和 CastOut 阶梯),per-channel 逐输出行取 scale/zero_points 标量;zero_points 按其运行时真实 dtype 读取(由 tiling 携带),不受 kernel 二进制编译期占位类型影响。(不支持,后续扩展:fp8 输出 HIFLOAT8/FLOAT8_E5M2/FLOAT8_E4M3FN、per-head、per-channel-nddma、FP32 zero_points。) ## 1. 精度测试(vs CPU golden) - 判据:stand_quantize(|diff| <= 1) 结果:**208 / 208 PASS** - golden:saturate_cast_to_y( rint( x / scales + zero_points ) )(numpy),pyaclnn(NPU)对 cpu。 | 分组 | 覆盖 | 用例数 | 结果 | |---|---|---|---| | 全量 | per-tensor + per-channel × {fp32,fp16,bf16}→{int8,uint8,int32} × zp{int8,uint8,int32,bf16} | 208 | ✅ 208/208 | | 按模式 | per-tensor | 114 | ✅ | | 按模式 | per-channel(含 rowLen=1 病态行、大 rowLen、多核大 shape) | 94 | ✅ | | 按 x dtype | FLOAT32 | 55 | ✅ | | 按 x dtype | FLOAT16 | 53 | ✅ | | 按 x dtype | BFLOAT16 | 100 | ✅ | | 边界 | UINT8 饱和 clamp [0,255]、INT8 饱和、rint 半整数、空 tensor(workspaceSize=0) | (含于上)| ✅ | > 说明:现有用例集每例均带 zero_points 张量(zeroPoints 缺省的用例在 aclnn 路径无法执行), > kernel 的 hasZeroPoint==0 分支(仅少一次 Adds)由带 zp 用例严格上界覆盖。 ## 2. 对比测试(vs 默认基线) - **基线**:**无可运行基线**。Quantize 在本机 CANN 9.0.0 部署包中对所有 SoC 均未部署为 builtin; 源仓 quant/quantize 仅有 ascend950(arch35)实现,无法在 ascend910b 硬件上运行;无 canndev TBE/DSL 参考。 - **精度等价**:208 / 208 PASS(对 CPU golden,见 §1)。 - **性能**:**N/A** —— 无同款默认实现可在 ascend910b 上净测,故不虚构加速比。 | shape | 新算子 device(us) | 默认基线 device(us) | 加速比(基线/新) | 备注 | |---|---|---|---|---| | —— | —— | N/A | **N/A** | 目标芯片上不存在可运行的默认 Quantize 实现 | ## 3. 性能优化(加速比 progression) - **状态**:做了 2 轮自相对优化(无 NPU 基线,锚点为初始 kernel 自身的绝对时延,8 组代表性 shape 的 device µs 之和; 共享机器 ±15% 争用噪声,取 MIN-of-N)。初始合计 776.23us → 最终 766.65us。正确性(208/208)为每轮硬约束。 | 迭代 | 主导 bound | 优化项 | 加速比(vs 初始) | 精度保持 | |---|---|---|---|---| | 0(基线) | VEC(int8/uint8 饱和阶梯 + Duplicate+Div + 逐 op barrier) | —— | 1.000× (776.23us) | ✅ 208/208 | | 1 | VEC | 标量倒数 Muls(1/scale) 取代 Duplicate+真 Div,释放 scale 广播 UB 缓冲 | ~1.00× (779.68us,低噪声用例 P0/P1 实测 −5~6%) | ✅ 208/208 | | 2 | VEC | SetDeqScale(1.0) 提到 Init 只设一次 + 去掉冗余尾 PipeBarrier<PIPE_V> | **1.012× (766.65us)** | ✅ 208/208 | - **最优 = 迭代 2**(两项叠加):766.65us,较初始 **1.012×**;在低噪声、向量受限的 int8 per-tensor 用例 (bf16→int32、bf16→int8)实测 −2.7% ~ −3.1%;内存受限(rowLen=1)与阶梯吞吐受限(per-channel uint8, 14% mem-util)的用例基本持平。 - **瓶颈(前→后)**:性质不变——int8/uint8 输出用例受限于 fp32→int32→half→int8 三段饱和 cast 阶梯的向量吞吐 (14–33% mem-util);迭代 2 削掉阶梯周边的 barrier/标量开销后,阶梯本身的 cast 吞吐成为地板。 - **技术说明**:唯一仍有头部空间的手段是把三段 cast 阶梯合并为两段(fp32→half→int8),但 fp32→half 中间舍入可能与精确 fp32→int32 rint 相差 1,会把已处于 ±1 容差内的结果推到 |diff|=2,故为保持精度未采用。 其余安全微优化均在本机 ±2~3% 的 MIN-of-3 噪声地板以下。无一轮被回退。 ## 4. 交付物 - op_host/:quantize_def.cpp(OpType + ascend910b 配置)+ quantize_infershape.cpp + quantize_tiling.{cpp,h} (per-tensor / per-channel 多核切分,携带 zpDtype 运行时类型)。 - op_kernel/:quantize.cpp(kernel 入口)+ quantize.h(统一 fp32 计算 + 饱和 CastOut 阶梯)。 - op_api/:aclnn_quantize.{cpp,h}(aclnnQuantize 两段式)+ quantize.{cpp,h}(L0 l0op::Quantize)。 - CMakeLists.txt、README.md、docs/aclnnQuantize.md、examples/test_aclnn_quantize.cpp。 - 测试:tests/ut/(op_host tiling+infershape、op_api aclnn、op_kernel ICPU_RUN_KF)+ tests/st/aclnnQuantize/(ATK 用例集 atk_aclnnQuantize.json + executor_aclnnQuantize.py)。 - 后续可扩展(纯技术,非本次范围):fp8 输出、per-head 与 per-channel-nddma 布局、FP32 zero_points; 性能侧的三段→两段 cast 阶梯合并(需先解决 ±1 精度风险)。 See merge request: cann/ops-nn!7420 | 1 个月前 | |
feat(Quantize): Quantize算子补齐ascend910b原生AscendC实现 Co-authored-by: zhaohujie<zhaohujie@huawei.com> # message auto-generated for no-merge-commit merge: !7420 merge worktree-quantize-910b into master feat(Quantize): Quantize算子补齐ascend910b原生AscendC实现 Created-by: zhaohujie Commit-by: zhaohujie Merged-by: cann-robot Description: ## 描述 为 Quantize 算子补齐 **ascend910b** 的原生 AscendC 实现,落在 experimental/quant/quantize/。 **改动原因**:仓库现有的 quant/quantize 仅覆盖 ascend950 —— kernel 入口 quantize_apt.cpp 只 #include arch35/*_regbase.h 且以 __NPU_ARCH__ == 3510 守护,host tiling 仅在 op_host/arch35/,二进制配置仅在 op_host/config/ascend950/, AICore().AddConfig 也只声明了 ascend950。因此该算子在 ascend910b 上没有可用 kernel。 **所采取的方法**: - 新增 ascend910b(DAV_2201 标准编程模型,非 regbase)原生 kernel op_kernel/quantize.{cpp,h}:统一 fp32 计算路径 (x -> fp32 -> 标量倒数 Muls(1/scale) -> Adds(zero_points) -> 饱和 CastOut 阶梯)。 - 新增 host 侧 op_host/quantize_tiling.{cpp,h}(per-tensor / per-channel 多核与 UB 切分)、op_host/quantize_infershape.cpp, 并在 op_host/quantize_def.cpp 增加 AddConfig("ascend910b")。 - 复用源算子 SoC 无关的 aclnn / L0 接口层,交付可调用的 aclnnQuantize 两段式接口。 - 计算语义:y = round(x / scales + zero_points),再按 dtype 属性做饱和定点转换。 **支持范围(首版)**:per-tensor、per-channel(axis 语义);x ∈ {FLOAT, FLOAT16, BFLOAT16}、 scales ∈ {FLOAT, BFLOAT16}、zero_points ∈ {INT8, UINT8, INT32, BFLOAT16, 缺省}、y ∈ {INT8, UINT8, INT32}; 属性 dtype(必选)、axis(可选,默认 1);固定 round-to-nearest / div / no-sqrt。 **暂不支持(后续扩展)**:fp8 输出(HIFLOAT8 / FLOAT8_E5M2 / FLOAT8_E4M3FN —— ascend910b 无 fp8 能力)、 per-head、per-channel-nddma、FP32 zero_points。 **实现要点**:zero_points 按其**运行时真实 dtype** 读取(dtype 码由 tiling 携带),不依赖 kernel 二进制的编译期占位类型。 原因是 kernel 二进制按 (x, scales, y) dtype 组合去重分发,可选输入 zero_points 的 dtype 不在分发键内 —— 若按编译期类型读取,INT32 的 zero_points 会被分发到以 INT8 编译的二进制并以 1 字节步长误读(per-channel 下取到错误字节)。 ## 关联的Issue https://gitcode.com/cann/ops-nn/issues/4047 ## 测试 测试环境:Atlas A2(Ascend 910B3 / ascend910b),CANN 9.0.0。 **UT(CPU 孪生)—— 28/28 PASS** - tests/ut/op_host/:tiling 5 项(tiling key 选择、channelNum / rowLen / totalRows / hasZeroPoint、多核切分、空 tensor)+ infershape 2 项 - tests/ut/op_api/:aclnn dtype 与错误码矩阵 15 项(空指针、scales 非 1 维、scales/zero_points 元素数不匹配、 per-channel 元素数 != x.shape[axis]、axis 越界、非法输出 dtype) - tests/ut/op_kernel/:ICPU_RUN_KF 对 numpy golden 6 项(覆盖 fp16/bf16 cast-in、int8/uint8/int32 CastOut、两种 tiling key、有无 zero_points) - 命令:bash build.sh -u --ophost | --opapi | --opkernel --experimental --soc=ascend910b --ops=quantize **ST 精度(上板)—— 208/208 PASS** - 判据 stand_quantize(|diff| <= 1);pyaclnn(NPU)对 cpu golden saturate_cast_to_y(rint(x / scales + zero_points)) - 覆盖:per-tensor 114 例 / per-channel 94 例;x 为 fp32 55 例、fp16 53 例、bf16 100 例; 含 int8 / uint8 / int32 输出、UINT8 饱和 clamp [0,255]、rint 半整数边界、rowLen=1 的病态 per-channel、多核大 shape、空 tensor - 用例集:tests/st/aclnnQuantize/{atk_aclnnQuantize.json, executor_aclnnQuantize.py} **构建** - bash build.sh --pkg --experimental --soc=ascend910b --ops=quantize 通过。 **性能** - 本算子在 CANN 9.0.0 部署包中对所有 SoC 均未部署 builtin,源仓仅有的 ascend950 实现无法在 ascend910b 上运行, 因此**不存在可运行的相对基线**,不给出加速比。 - 对新 kernel 自身做了 2 轮优化(标量倒数 Muls 取代 Duplicate + 真 Div,释放 scale 广播 UB 缓冲; SetDeqScale 提到 Init 只设一次并去掉冗余的尾部 PipeBarrier<PIPE_V>):8 组代表性用例的 device 时延合计 776.2us -> 766.7us(1.012×,向量受限的 int8 per-tensor 用例实测 -2.7% ~ -3.1%)。两轮优化后精度全程保持 208/208。 ## 文档更新 - 新增 experimental/quant/quantize/README.md(功能与计算公式、产品支持情况、参数说明、支持范围与约束、调用说明)。 - 新增 experimental/quant/quantize/docs/aclnnQuantize.md(aclnnQuantize 两段式接口文档)。 ## 类型标签 <!-- [x] 表示选中 --> - [ ] Bug修复 - [x] 新特性 - [ ] 性能优化 - [ ] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 ## 附Quantize实现与测试报告 ## 摘要(当前实现) ascend910b(DAV_2201 标准编程模型)的原生 AscendC Quantize kernel,落在 experimental/quant/quantize/, 复用既有 SoC 无关的 aclnnQuantize 两段式接口层。计算 y = round(x / scales + zero_points) 后按 dtype 属性做饱和定点转换。对外入口:aclnnQuantizeGetWorkspaceSize / aclnnQuantize。支持 per-tensor 与 per-channel(axis 语义)两种模式;x ∈ {FLOAT, FLOAT16, BFLOAT16}、scales ∈ {FLOAT, BFLOAT16}、 zero_points ∈ {INT8, UINT8, INT32, BFLOAT16, 缺省}、输出 y ∈ {INT8, UINT8, INT32};固定 round-to-nearest / div / no-sqrt。实现要点:统一 fp32 计算路径(x→fp32 → 标量倒数 Muls(1/scale) → Adds(zp) → 饱和 CastOut 阶梯),per-channel 逐输出行取 scale/zero_points 标量;zero_points 按其运行时真实 dtype 读取(由 tiling 携带),不受 kernel 二进制编译期占位类型影响。(不支持,后续扩展:fp8 输出 HIFLOAT8/FLOAT8_E5M2/FLOAT8_E4M3FN、per-head、per-channel-nddma、FP32 zero_points。) ## 1. 精度测试(vs CPU golden) - 判据:stand_quantize(|diff| <= 1) 结果:**208 / 208 PASS** - golden:saturate_cast_to_y( rint( x / scales + zero_points ) )(numpy),pyaclnn(NPU)对 cpu。 | 分组 | 覆盖 | 用例数 | 结果 | |---|---|---|---| | 全量 | per-tensor + per-channel × {fp32,fp16,bf16}→{int8,uint8,int32} × zp{int8,uint8,int32,bf16} | 208 | ✅ 208/208 | | 按模式 | per-tensor | 114 | ✅ | | 按模式 | per-channel(含 rowLen=1 病态行、大 rowLen、多核大 shape) | 94 | ✅ | | 按 x dtype | FLOAT32 | 55 | ✅ | | 按 x dtype | FLOAT16 | 53 | ✅ | | 按 x dtype | BFLOAT16 | 100 | ✅ | | 边界 | UINT8 饱和 clamp [0,255]、INT8 饱和、rint 半整数、空 tensor(workspaceSize=0) | (含于上)| ✅ | > 说明:现有用例集每例均带 zero_points 张量(zeroPoints 缺省的用例在 aclnn 路径无法执行), > kernel 的 hasZeroPoint==0 分支(仅少一次 Adds)由带 zp 用例严格上界覆盖。 ## 2. 对比测试(vs 默认基线) - **基线**:**无可运行基线**。Quantize 在本机 CANN 9.0.0 部署包中对所有 SoC 均未部署为 builtin; 源仓 quant/quantize 仅有 ascend950(arch35)实现,无法在 ascend910b 硬件上运行;无 canndev TBE/DSL 参考。 - **精度等价**:208 / 208 PASS(对 CPU golden,见 §1)。 - **性能**:**N/A** —— 无同款默认实现可在 ascend910b 上净测,故不虚构加速比。 | shape | 新算子 device(us) | 默认基线 device(us) | 加速比(基线/新) | 备注 | |---|---|---|---|---| | —— | —— | N/A | **N/A** | 目标芯片上不存在可运行的默认 Quantize 实现 | ## 3. 性能优化(加速比 progression) - **状态**:做了 2 轮自相对优化(无 NPU 基线,锚点为初始 kernel 自身的绝对时延,8 组代表性 shape 的 device µs 之和; 共享机器 ±15% 争用噪声,取 MIN-of-N)。初始合计 776.23us → 最终 766.65us。正确性(208/208)为每轮硬约束。 | 迭代 | 主导 bound | 优化项 | 加速比(vs 初始) | 精度保持 | |---|---|---|---|---| | 0(基线) | VEC(int8/uint8 饱和阶梯 + Duplicate+Div + 逐 op barrier) | —— | 1.000× (776.23us) | ✅ 208/208 | | 1 | VEC | 标量倒数 Muls(1/scale) 取代 Duplicate+真 Div,释放 scale 广播 UB 缓冲 | ~1.00× (779.68us,低噪声用例 P0/P1 实测 −5~6%) | ✅ 208/208 | | 2 | VEC | SetDeqScale(1.0) 提到 Init 只设一次 + 去掉冗余尾 PipeBarrier<PIPE_V> | **1.012× (766.65us)** | ✅ 208/208 | - **最优 = 迭代 2**(两项叠加):766.65us,较初始 **1.012×**;在低噪声、向量受限的 int8 per-tensor 用例 (bf16→int32、bf16→int8)实测 −2.7% ~ −3.1%;内存受限(rowLen=1)与阶梯吞吐受限(per-channel uint8, 14% mem-util)的用例基本持平。 - **瓶颈(前→后)**:性质不变——int8/uint8 输出用例受限于 fp32→int32→half→int8 三段饱和 cast 阶梯的向量吞吐 (14–33% mem-util);迭代 2 削掉阶梯周边的 barrier/标量开销后,阶梯本身的 cast 吞吐成为地板。 - **技术说明**:唯一仍有头部空间的手段是把三段 cast 阶梯合并为两段(fp32→half→int8),但 fp32→half 中间舍入可能与精确 fp32→int32 rint 相差 1,会把已处于 ±1 容差内的结果推到 |diff|=2,故为保持精度未采用。 其余安全微优化均在本机 ±2~3% 的 MIN-of-3 噪声地板以下。无一轮被回退。 ## 4. 交付物 - op_host/:quantize_def.cpp(OpType + ascend910b 配置)+ quantize_infershape.cpp + quantize_tiling.{cpp,h} (per-tensor / per-channel 多核切分,携带 zpDtype 运行时类型)。 - op_kernel/:quantize.cpp(kernel 入口)+ quantize.h(统一 fp32 计算 + 饱和 CastOut 阶梯)。 - op_api/:aclnn_quantize.{cpp,h}(aclnnQuantize 两段式)+ quantize.{cpp,h}(L0 l0op::Quantize)。 - CMakeLists.txt、README.md、docs/aclnnQuantize.md、examples/test_aclnn_quantize.cpp。 - 测试:tests/ut/(op_host tiling+infershape、op_api aclnn、op_kernel ICPU_RUN_KF)+ tests/st/aclnnQuantize/(ATK 用例集 atk_aclnnQuantize.json + executor_aclnnQuantize.py)。 - 后续可扩展(纯技术,非本次范围):fp8 输出、per-head 与 per-channel-nddma 布局、FP32 zero_points; 性能侧的三段→两段 cast 阶梯合并(需先解决 ±1 精度风险)。 See merge request: cann/ops-nn!7420 | 1 个月前 | |
feat(Quantize): Quantize算子补齐ascend910b原生AscendC实现 Co-authored-by: zhaohujie<zhaohujie@huawei.com> # message auto-generated for no-merge-commit merge: !7420 merge worktree-quantize-910b into master feat(Quantize): Quantize算子补齐ascend910b原生AscendC实现 Created-by: zhaohujie Commit-by: zhaohujie Merged-by: cann-robot Description: ## 描述 为 Quantize 算子补齐 **ascend910b** 的原生 AscendC 实现,落在 experimental/quant/quantize/。 **改动原因**:仓库现有的 quant/quantize 仅覆盖 ascend950 —— kernel 入口 quantize_apt.cpp 只 #include arch35/*_regbase.h 且以 __NPU_ARCH__ == 3510 守护,host tiling 仅在 op_host/arch35/,二进制配置仅在 op_host/config/ascend950/, AICore().AddConfig 也只声明了 ascend950。因此该算子在 ascend910b 上没有可用 kernel。 **所采取的方法**: - 新增 ascend910b(DAV_2201 标准编程模型,非 regbase)原生 kernel op_kernel/quantize.{cpp,h}:统一 fp32 计算路径 (x -> fp32 -> 标量倒数 Muls(1/scale) -> Adds(zero_points) -> 饱和 CastOut 阶梯)。 - 新增 host 侧 op_host/quantize_tiling.{cpp,h}(per-tensor / per-channel 多核与 UB 切分)、op_host/quantize_infershape.cpp, 并在 op_host/quantize_def.cpp 增加 AddConfig("ascend910b")。 - 复用源算子 SoC 无关的 aclnn / L0 接口层,交付可调用的 aclnnQuantize 两段式接口。 - 计算语义:y = round(x / scales + zero_points),再按 dtype 属性做饱和定点转换。 **支持范围(首版)**:per-tensor、per-channel(axis 语义);x ∈ {FLOAT, FLOAT16, BFLOAT16}、 scales ∈ {FLOAT, BFLOAT16}、zero_points ∈ {INT8, UINT8, INT32, BFLOAT16, 缺省}、y ∈ {INT8, UINT8, INT32}; 属性 dtype(必选)、axis(可选,默认 1);固定 round-to-nearest / div / no-sqrt。 **暂不支持(后续扩展)**:fp8 输出(HIFLOAT8 / FLOAT8_E5M2 / FLOAT8_E4M3FN —— ascend910b 无 fp8 能力)、 per-head、per-channel-nddma、FP32 zero_points。 **实现要点**:zero_points 按其**运行时真实 dtype** 读取(dtype 码由 tiling 携带),不依赖 kernel 二进制的编译期占位类型。 原因是 kernel 二进制按 (x, scales, y) dtype 组合去重分发,可选输入 zero_points 的 dtype 不在分发键内 —— 若按编译期类型读取,INT32 的 zero_points 会被分发到以 INT8 编译的二进制并以 1 字节步长误读(per-channel 下取到错误字节)。 ## 关联的Issue https://gitcode.com/cann/ops-nn/issues/4047 ## 测试 测试环境:Atlas A2(Ascend 910B3 / ascend910b),CANN 9.0.0。 **UT(CPU 孪生)—— 28/28 PASS** - tests/ut/op_host/:tiling 5 项(tiling key 选择、channelNum / rowLen / totalRows / hasZeroPoint、多核切分、空 tensor)+ infershape 2 项 - tests/ut/op_api/:aclnn dtype 与错误码矩阵 15 项(空指针、scales 非 1 维、scales/zero_points 元素数不匹配、 per-channel 元素数 != x.shape[axis]、axis 越界、非法输出 dtype) - tests/ut/op_kernel/:ICPU_RUN_KF 对 numpy golden 6 项(覆盖 fp16/bf16 cast-in、int8/uint8/int32 CastOut、两种 tiling key、有无 zero_points) - 命令:bash build.sh -u --ophost | --opapi | --opkernel --experimental --soc=ascend910b --ops=quantize **ST 精度(上板)—— 208/208 PASS** - 判据 stand_quantize(|diff| <= 1);pyaclnn(NPU)对 cpu golden saturate_cast_to_y(rint(x / scales + zero_points)) - 覆盖:per-tensor 114 例 / per-channel 94 例;x 为 fp32 55 例、fp16 53 例、bf16 100 例; 含 int8 / uint8 / int32 输出、UINT8 饱和 clamp [0,255]、rint 半整数边界、rowLen=1 的病态 per-channel、多核大 shape、空 tensor - 用例集:tests/st/aclnnQuantize/{atk_aclnnQuantize.json, executor_aclnnQuantize.py} **构建** - bash build.sh --pkg --experimental --soc=ascend910b --ops=quantize 通过。 **性能** - 本算子在 CANN 9.0.0 部署包中对所有 SoC 均未部署 builtin,源仓仅有的 ascend950 实现无法在 ascend910b 上运行, 因此**不存在可运行的相对基线**,不给出加速比。 - 对新 kernel 自身做了 2 轮优化(标量倒数 Muls 取代 Duplicate + 真 Div,释放 scale 广播 UB 缓冲; SetDeqScale 提到 Init 只设一次并去掉冗余的尾部 PipeBarrier<PIPE_V>):8 组代表性用例的 device 时延合计 776.2us -> 766.7us(1.012×,向量受限的 int8 per-tensor 用例实测 -2.7% ~ -3.1%)。两轮优化后精度全程保持 208/208。 ## 文档更新 - 新增 experimental/quant/quantize/README.md(功能与计算公式、产品支持情况、参数说明、支持范围与约束、调用说明)。 - 新增 experimental/quant/quantize/docs/aclnnQuantize.md(aclnnQuantize 两段式接口文档)。 ## 类型标签 <!-- [x] 表示选中 --> - [ ] Bug修复 - [x] 新特性 - [ ] 性能优化 - [ ] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 ## 附Quantize实现与测试报告 ## 摘要(当前实现) ascend910b(DAV_2201 标准编程模型)的原生 AscendC Quantize kernel,落在 experimental/quant/quantize/, 复用既有 SoC 无关的 aclnnQuantize 两段式接口层。计算 y = round(x / scales + zero_points) 后按 dtype 属性做饱和定点转换。对外入口:aclnnQuantizeGetWorkspaceSize / aclnnQuantize。支持 per-tensor 与 per-channel(axis 语义)两种模式;x ∈ {FLOAT, FLOAT16, BFLOAT16}、scales ∈ {FLOAT, BFLOAT16}、 zero_points ∈ {INT8, UINT8, INT32, BFLOAT16, 缺省}、输出 y ∈ {INT8, UINT8, INT32};固定 round-to-nearest / div / no-sqrt。实现要点:统一 fp32 计算路径(x→fp32 → 标量倒数 Muls(1/scale) → Adds(zp) → 饱和 CastOut 阶梯),per-channel 逐输出行取 scale/zero_points 标量;zero_points 按其运行时真实 dtype 读取(由 tiling 携带),不受 kernel 二进制编译期占位类型影响。(不支持,后续扩展:fp8 输出 HIFLOAT8/FLOAT8_E5M2/FLOAT8_E4M3FN、per-head、per-channel-nddma、FP32 zero_points。) ## 1. 精度测试(vs CPU golden) - 判据:stand_quantize(|diff| <= 1) 结果:**208 / 208 PASS** - golden:saturate_cast_to_y( rint( x / scales + zero_points ) )(numpy),pyaclnn(NPU)对 cpu。 | 分组 | 覆盖 | 用例数 | 结果 | |---|---|---|---| | 全量 | per-tensor + per-channel × {fp32,fp16,bf16}→{int8,uint8,int32} × zp{int8,uint8,int32,bf16} | 208 | ✅ 208/208 | | 按模式 | per-tensor | 114 | ✅ | | 按模式 | per-channel(含 rowLen=1 病态行、大 rowLen、多核大 shape) | 94 | ✅ | | 按 x dtype | FLOAT32 | 55 | ✅ | | 按 x dtype | FLOAT16 | 53 | ✅ | | 按 x dtype | BFLOAT16 | 100 | ✅ | | 边界 | UINT8 饱和 clamp [0,255]、INT8 饱和、rint 半整数、空 tensor(workspaceSize=0) | (含于上)| ✅ | > 说明:现有用例集每例均带 zero_points 张量(zeroPoints 缺省的用例在 aclnn 路径无法执行), > kernel 的 hasZeroPoint==0 分支(仅少一次 Adds)由带 zp 用例严格上界覆盖。 ## 2. 对比测试(vs 默认基线) - **基线**:**无可运行基线**。Quantize 在本机 CANN 9.0.0 部署包中对所有 SoC 均未部署为 builtin; 源仓 quant/quantize 仅有 ascend950(arch35)实现,无法在 ascend910b 硬件上运行;无 canndev TBE/DSL 参考。 - **精度等价**:208 / 208 PASS(对 CPU golden,见 §1)。 - **性能**:**N/A** —— 无同款默认实现可在 ascend910b 上净测,故不虚构加速比。 | shape | 新算子 device(us) | 默认基线 device(us) | 加速比(基线/新) | 备注 | |---|---|---|---|---| | —— | —— | N/A | **N/A** | 目标芯片上不存在可运行的默认 Quantize 实现 | ## 3. 性能优化(加速比 progression) - **状态**:做了 2 轮自相对优化(无 NPU 基线,锚点为初始 kernel 自身的绝对时延,8 组代表性 shape 的 device µs 之和; 共享机器 ±15% 争用噪声,取 MIN-of-N)。初始合计 776.23us → 最终 766.65us。正确性(208/208)为每轮硬约束。 | 迭代 | 主导 bound | 优化项 | 加速比(vs 初始) | 精度保持 | |---|---|---|---|---| | 0(基线) | VEC(int8/uint8 饱和阶梯 + Duplicate+Div + 逐 op barrier) | —— | 1.000× (776.23us) | ✅ 208/208 | | 1 | VEC | 标量倒数 Muls(1/scale) 取代 Duplicate+真 Div,释放 scale 广播 UB 缓冲 | ~1.00× (779.68us,低噪声用例 P0/P1 实测 −5~6%) | ✅ 208/208 | | 2 | VEC | SetDeqScale(1.0) 提到 Init 只设一次 + 去掉冗余尾 PipeBarrier<PIPE_V> | **1.012× (766.65us)** | ✅ 208/208 | - **最优 = 迭代 2**(两项叠加):766.65us,较初始 **1.012×**;在低噪声、向量受限的 int8 per-tensor 用例 (bf16→int32、bf16→int8)实测 −2.7% ~ −3.1%;内存受限(rowLen=1)与阶梯吞吐受限(per-channel uint8, 14% mem-util)的用例基本持平。 - **瓶颈(前→后)**:性质不变——int8/uint8 输出用例受限于 fp32→int32→half→int8 三段饱和 cast 阶梯的向量吞吐 (14–33% mem-util);迭代 2 削掉阶梯周边的 barrier/标量开销后,阶梯本身的 cast 吞吐成为地板。 - **技术说明**:唯一仍有头部空间的手段是把三段 cast 阶梯合并为两段(fp32→half→int8),但 fp32→half 中间舍入可能与精确 fp32→int32 rint 相差 1,会把已处于 ±1 容差内的结果推到 |diff|=2,故为保持精度未采用。 其余安全微优化均在本机 ±2~3% 的 MIN-of-3 噪声地板以下。无一轮被回退。 ## 4. 交付物 - op_host/:quantize_def.cpp(OpType + ascend910b 配置)+ quantize_infershape.cpp + quantize_tiling.{cpp,h} (per-tensor / per-channel 多核切分,携带 zpDtype 运行时类型)。 - op_kernel/:quantize.cpp(kernel 入口)+ quantize.h(统一 fp32 计算 + 饱和 CastOut 阶梯)。 - op_api/:aclnn_quantize.{cpp,h}(aclnnQuantize 两段式)+ quantize.{cpp,h}(L0 l0op::Quantize)。 - CMakeLists.txt、README.md、docs/aclnnQuantize.md、examples/test_aclnn_quantize.cpp。 - 测试:tests/ut/(op_host tiling+infershape、op_api aclnn、op_kernel ICPU_RUN_KF)+ tests/st/aclnnQuantize/(ATK 用例集 atk_aclnnQuantize.json + executor_aclnnQuantize.py)。 - 后续可扩展(纯技术,非本次范围):fp8 输出、per-head 与 per-channel-nddma 布局、FP32 zero_points; 性能侧的三段→两段 cast 阶梯合并(需先解决 ±1 精度风险)。 See merge request: cann/ops-nn!7420 | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 |