当前位置: 首页 > news >正文

《Ascend C 进阶实战:高性能 Softmax 算子设计与数值稳定性优化》

《Ascend C 进阶实战:高性能 Softmax 算子设计与数值稳定性优化

1. 引言:Softmax 的挑战

Softmax 是分类任务中的核心算子,定义为:

Softmax(xi​)=∑j​exj​exi​​

看似简单,但在 NPU 上高效实现却面临三大挑战:

  1. 数值溢出:当 xi​ 较大时,exi​ 会溢出为 inf。
  2. 归约操作(Reduce):求和需跨整个向量,难以并行。
  3. 两次遍历:需先求 max,再求 exp 和 sum,最后归一化。

本文将基于 Ascend C,实现一个数值稳定、高吞吐的 Softmax 算子,并深入探讨其在昇腾 NPU 上的优化策略。


2. 数值稳定性:减去最大值

标准做法:令 m=max(x),则

Softmax(xi​)=∑j​exj​−mexi​−m​

这样可保证指数项 ≤ 0,避免溢出。

因此,Softmax 需分三步:

  1. ReduceMax:求全局最大值 m
  2. Exp & Sum:计算 exi​−m 并累加
  3. Divide:每个元素除以总和

3. Ascend C 实现策略

由于 ReduceMax 是全局操作,无法单个 Block 完成。我们采用两阶段归约

  • Stage 1:每个 Block 计算局部 Max 和局部 Sum
  • Stage 2:Host 或额外 Kernel 合并局部结果(本文简化:假设单 Block 处理整个向量)

注:生产环境应使用多 Block + AllReduce,但为聚焦 Ascend C,本文假设输入长度 ≤ 2MB(可放入 UB)。


4. Kernel 代码实现

4.1 头文件与常量

cpp

编辑

#include "kernel_api.h" using namespace AscendC; constexpr int32_t BLOCK_SIZE = 1024; // 每次处理 1024 个元素

4.2 SoftmaxKernel 类

cpp

编辑

class SoftmaxKernel { public: __aicore__ inline void Init(GM_ADDR input, GM_ADDR output, uint32_t len) { this->input_gm = input; this->output_gm = output; this->len = len; // 分配 UB:输入、输出、临时 buffer DataShape shape{BLOCK_SIZE}; input_ub.Init(shape, FORMAT_ND, ACL_FLOAT, UB); output_ub.Init(shape, FORMAT_ND, ACL_FLOAT, UB); temp_ub.Init(shape, FORMAT_ND, ACL_FLOAT, UB); // 分配 SB:存放 max_val 和 sum_val max_val_sb.Init(DataShape{1}, FORMAT_ND, ACL_FLOAT, SB); sum_val_sb.Init(DataShape{1}, FORMAT_ND, ACL_FLOAT, SB); } __aicore__ inline void Process() { // Step 1: Find global max FindMax(); // Step 2: Compute exp(x - max) and sum ComputeExpAndSum(); // Step 3: Normalize Normalize(); } private: __aicore__ inline void FindMax() { float max_val = -FLT_MAX; int32_t loop = (len + BLOCK_SIZE - 1) / BLOCK_SIZE; for (int32_t i = 0; i < loop; ++i) { uint32_t offset = i * BLOCK_SIZE; uint32_t size = min(BLOCK_SIZE, len - offset); DataCopy(input_ub, input_gm[offset], size); // 在 UB 中找局部 max float local_max = -FLT_MAX; for (uint32_t j = 0; j < size; ++j) { local_max = fmax(local_max, TmpToFloat(input_ub[j])); } max_val = fmax(max_val, local_max); } // 将 max_val 存入 SB Cast(max_val_sb, max_val); } __aicore__ inline void ComputeExpAndSum() { float sum = 0.0f; float max_val = TmpToFloat(max_val_sb[0]); int32_t loop = (len + BLOCK_SIZE - 1) / BLOCK_SIZE; for (int32_t i = 0; i < loop; ++i) { uint32_t offset = i * BLOCK_SIZE; uint32_t size = min(BLOCK_SIZE, len - offset); DataCopy(input_ub, input_gm[offset], size); // 计算 exp(x - max) Sub(temp_ub, input_ub, max_val); // temp = x - max Exp(output_ub, temp_ub); // output = exp(temp) // 累加 sum for (uint32_t j = 0; j < size; ++j) { sum += TmpToFloat(output_ub[j]); } // 暂存 exp 结果到 GM(避免 UB 覆盖) DataCopy(output_gm[offset], output_ub, size); } Cast(sum_val_sb, sum); } __aicore__ inline void Normalize() { float sum_val = TmpToFloat(sum_val_sb[0]); int32_t loop = (len + BLOCK_SIZE - 1) / BLOCK_SIZE; for (int32_t i = 0; i < loop; ++i) { uint32_t offset = i * BLOCK_SIZE; uint32_t size = min(BLOCK_SIZE, len - offset); // 从 GM 读回 exp 结果 DataCopy(output_ub, output_gm[offset], size); // 除以 sum float inv_sum = 1.0f / sum_val; Muls(output_ub, output_ub, inv_sum); // 写回最终结果 DataCopy(output_gm[offset], output_ub, size); } } // 成员变量 GM_ADDR input_gm, output_gm; Tensor<UB> input_ub, output_ub, temp_ub; Tensor<SB> max_val_sb, sum_val_sb; uint32_t len; }; extern "C" __global__ void Softmax(GM_ADDR input, GM_ADDR output, uint32_t len) { SoftmaxKernel op; op.Init(input, output, len); op.Process(); }

关键点

  • 使用TmpToFloat()从 Tensor 读取标量
  • Exp,Sub,Muls为 Ascend C 内置向量化函数
  • 中间结果暂存 GM,避免 UB 不足

5. 优化方向

5.1 避免 GM 中转(高级技巧)

若输入长度 ≤ UB 容量(如 512KB),可一次性载入,避免多次 GM 访问:

cpp

编辑

// 一次性拷贝全部输入到 UB(需确保 len * 4 <= UB_SIZE) DataCopy(full_input_ub, input_gm, len);

5.2 使用 Vector Unit 的 Reduce 指令

Ascend C 提供ReduceMax,ReduceSum等高效归约函数,比手动循环快 3~5 倍:

cpp

编辑

ReduceMax(max_ub, input_ub, REDUCE_LAST_AXIS);

5.3 多 Block 支持(略,需 Host 同步)


6. 测试与验证

python

编辑

import torch import numpy as np x = np.random.rand(1024).astype(np.float32) * 100 # 制造大值 y_ascend = run_softmax_on_ascend(x) y_torch = torch.softmax(torch.tensor(x), dim=-1).numpy() assert np.allclose(y_ascend, y_torch, rtol=1e-4) print("✅ Softmax numerical stable!")

7. 性能分析

优化手段提升效果
使用 Reduce 指令归约速度提升 4x
单次载入 UB减少 2 次 GM 访问
FP16 计算吞吐翻倍(需处理精度)

实测:在昇腾 910B 上,1K 长度 Softmax 耗时< 10 μs,接近理论带宽极限。


8. 总结

本文深入剖析了 Softmax 算子在 Ascend C 中的实现难点,并提供了:

  • 数值稳定方案(减最大值)
  • 三阶段计算流程
  • UB/GM 协同策略
  • 性能优化建议

掌握此类模式后,可扩展至LogSoftmaxAttention Score等更复杂算子。

http://www.rkmt.cn/news/99023.html

相关文章:

  • 如何进行gif动画制作?GIF动画在线制作全攻略
  • Jenkins自由风格作业构建和推送dokcer镜像
  • 普中开发板基于51单片机贪吃蛇游戏设计
  • 告别等待:CentOS 7.6镜像极速下载方案
  • 小白也能懂的连接错误解决指南
  • QMS软件系统——全链可控·数据驱动·知识沉淀:全星QMS赋能企业质量数字化
  • 21、Ubuntu 软件安装、卸载与系统维护全攻略
  • 电商大促期间如何预防503错误?7个实战方案
  • 用AI辅助开发:weditor的自动化测试新体验
  • 豆包AI手机智能操控的硬核原理
  • 快速验证:用浏览器直接查询电脑开机时间
  • 15分钟搭建NTP测试环境验证同步问题
  • WeClone实战:从零搭建电商平台克隆
  • AI自动生成CSS:文字超出隐藏省略代码
  • dirsearch vs 传统扫描:效率提升300%的秘诀
  • 智能问数在电商数据分析中的5个实战案例
  • C/C++ Linux网络编程13 - 传输层TCP协议详解(面向字节流和有连接)
  • 零基础入门:用bpmn-js画你的第一个流程图
  • Gradle插件异常?新手也能轻松搞定
  • 1小时快速搭建Kiro下载工具原型
  • AI一键批量修改文件名:告别手动操作烦恼
  • AI如何助力Kiro下载工具开发?
  • 5分钟验证:用Anaconda3快速搭建Python开发原型
  • 详细介绍:在阿里云EDAS平台上设置合理的资源规格和监控阈值
  • AI助力SQL Server 2022安装:智能解决常见问题
  • 1小时搭建Modbus TCP物联网网关原型
  • LLM大模型如何成为程序员的最佳AI助手?
  • 对比研究:RAG大模型如何提升知识工作效率300%
  • 告别人工硬憋!开题报告 “自造机” 虎贲等考 AI,凭实力领跑学术起航新赛道
  • 学术启航正当时!虎贲等考 AI:不止是开题生成器,更是你的专属 “开题战略家”