十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

使用 PyPTO 在昇腾 NPU 上实现 FFN 前馈网络模块:激活函数、动态形状与 Tiling 配置实战

使用 PyPTO 在昇腾 NPU 上实现 FFN 前馈网络模块:激活函数、动态形状与 Tiling 配置实战 使用 PyPTO 在昇腾 NPU 上实现 FFN 前馈网络模块激活函数、动态形状与 Tiling 配置实战【免费下载链接】pyptoPyPTO发音: pai p-t-oParallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto导读本文基于 CANN PyPTO 仓库中的 FFN 模块样例 及其核心实现 ffn_module.py系统讲解如何用 PyPTO 的并行张量/分块Parallel Tensor/Tile编程范式在昇腾 NPU 上实现一个完整、可运行的 Transformer 前馈网络Feed-Forward Network模块。读完本文你将掌握 FFN 的两种架构形态标准 FFN 与 SwiGLU FFN、FFNConfig配置类中每个参数的语义与调优方法、静态/动态 Batch 两种执行路径的底层实现matmul、view、assemble、loop等 PyPTO 核心 API以及如何在 NPU 与 SIM 两种运行模式下执行并验证该模块。FFN 模块的核心特性该样例用约 600 行 Python 代码把配置、算子编排、kernel 编译、结果校验完整地串成一条可运行的链路核心特性包括多种激活函数支持 GELU、SwiGLU 和 ReLU 三种激活覆盖 LLM 推理中最常见的 FFN 变体静态与动态形状既支持固定 Batch Size 的静态编译路径也支持 Batch Size 运行时变化的动态路径按basic_batch分块循环处理可配置的 Tiling通过vec_tile_shape与cube_tile_shape显式控制向量运算与矩阵Cube运算的分块形状直接面向 NPU 性能优化工程化实现使用dataclass定义配置、完整的类型注解typing.Literal、以及针对 NPU 与 SIM 双运行模式的封装。代码结构与运行方法文件布局样例目录 examples/02_intermediate/basic_nn/ffn/ 下包含ffn_module.pyFFN 模块的核心实现与测试脚本同时承担模块定义与测试入口双重角色README.md使用说明文档。环境准备运行前需要配置 CANN 环境变量与设备 ID# 配置 CANN 环境变量默认路径安装、以 root 用户为例非 root 用户将 /usr/local 替换为 ${HOME} # 上述环境变量配置只在当前窗口生效可按需写入 .bashrc 等配置文件 source /usr/local/Ascend/ascend-toolkit/set_env.sh # 设置设备 ID export TILE_FWK_DEVICE_ID0TILE_FWK_DEVICE_ID的作用在源码中有明确校验逻辑ffn_module.py中的get_device_id()会读取该环境变量并转换为整数未设置或非整数时给出提示并拒绝继续运行NPU 模式下还会调用torch.npu.set_device(device_id)绑定设备。执行测试# 运行所有测试用例 python3 ffn_module.py # 列出所有可用的测试用例 python3 ffn_module.py --list脚本还支持更细粒度的控制方式# 只运行某一个用例ID 由 --list 输出 python3 ffn_module.py ffn_static_gelu::test_ffn_static_gelu # 以 SIM仿真模式运行无需真实 NPU python3 ffn_module.py --run_mode simmain()中通过argparse注册了example_id、--list、--run_mode三个参数其中--run_mode取值限定为npu或sim。脚本共注册 4 个用例用例 ID名称说明ffn_static_gelu::test_ffn_static_geluStatic FFN with GELU静态 Batch GELUffn_static_swiglu::test_ffn_static_swigluStatic FFN with SwiGLU静态 Batch SwiGLUffn_static_relu::test_ffn_static_reluStatic FFN with ReLU静态 Batch ReLUffn_dynamic_gelu::test_ffn_dynamic_geluDynamic FFN with GELU动态 Batch GELU关于--run_mode sim的判定逻辑脚本通过_peek_run_mode_from_argv()在模块加载阶段提前扫描sys.argv从而决定模块级装饰器pypto.frontend.jit(runtime_options{run_mode: global_run_mode})使用哪个运行模式保证 kernel 在编译期就按正确的目标模式生成。FFN 架构说明FFN 模块实现标准的 Transformer 前馈网络逻辑输入为[B, H]输出仍为[B, H]其中BBatch Size、HHidden Size、IIntermediate Size。标准 FFNGELU/ReLUInput [B, H] → Gate Projection [B, H] [H, I] → [B, I] → Activation (GELU/ReLU) → Down Projection [B, I] [I, H] → [B, H] → Output [B, H]SwiGLU FFNInput [B, H] → Gate Projection [B, H] [H, I] → [B, I] → Up Projection [B, H] [H, I] → [B, I] → SwiGLU(Gate, Up) → [B, I] → Down Projection [B, I] [I, H] → [B, H] → Output [B, H]SwiGLU 相比标准 FFN 多了一路 Gate 投影激活结果由Swish(Gate) * Up计算得到其中Swish(x) x * sigmoid(x)。在源码中这两条路径由统一的 kernelffn_activation_kernel()根据config.activation分支实现GELU/SwiGLU 各自独立做 Up/Gate 矩阵乘ReLU 则只有一路投影是理解 FFN 变体在 NPU 上落地的最小可运行示例。关键配置参数FFNConfig配置类使用 Pythondataclass定义位于 ffn_module.py并作为编译期常量直接参与 kernel 函数的调度与 Tiling 决策参数类型默认值描述hidden_sizeint必填隐藏层维度大小intermediate_sizeint必填中间层维度大小activationstrgelu激活函数gelu、swiglu或reludtypepypto.DataTypeDT_FP16计算使用的数据类型use_dynamic_shapeboolFalse是否支持动态 Batch Sizevec_tile_shapetuple(64, 128)向量运算的 Tiling 形状cube_tile_shapetuple(64, 128, 128)矩阵运算的 Tiling 形状basic_batchint32动态处理时的基础 Batch 大小batch_sizeint必填当前用例的 Batch Size驱动输入张量创建run_modepypto.RunModepypto.RunMode.NPU执行模式NPU 或 SIM其中dtype使用pypto.DataType枚举如 enum.py 中定义的DT_FP16、DT_FP32、DT_BF16等样例测试中常见组合为GELU/SwiGLU 用例使用pypto.DT_BF16对应 PyTorch 的torch.bfloat16ReLU 用例使用pypto.DT_FP16对应torch.float16。两个 Tiling 参数的底层语义Tiling 参数直接对应 PyPTO 的 Scope 配置接口见 _controller.pyvec_tile_shape对应pypto.set_vec_tile_shapes(*shapes)设置向量Vector计算中每个维度的分块形状。该调用最终写入pypto_impl.SetScope({vec_tile_shapes: ...})影响逐元素类算子的数据切分粒度cube_tile_shape对应pypto.set_cube_tile_shapes(m, k, n)设置 Cube矩阵乘计算中左矩阵 M 维、K 维、右矩阵 N 维的分块形状并连同 L1/L0 缓存层级一起参与切分决策。在 kernel 内部cube_tile_shape元组会被解构成三组[tile, tile]列表再传给set_cube_tile_shapes。在动态路径的dynamic_gelu_activation_core()中还能看到矩阵乘维度约定Down 投影前调用pypto.set_matrix_size([basic_batch, intermediate_size, hidden_size])显式声明矩阵尺寸。三种激活函数的 NPU 实现激活函数以计算核心core函数的形式实现全部基于 PyPTO 张量 API 组合而成并统一将中间计算提升到 FP32 以提升数值稳定性最终再转回目标精度。GELUsigmoid 近似GELU_COEFF 1.702 def gelu_activation_core(x: pypto.tensor) - pypto.tensor: pypto.set_vec_tile_shapes(*x.shape[:2] if len(x.shape) 2 else (32, 128)) x_fp32 pypto.cast(x, pypto.DT_FP32) x_scaled pypto.mul(x_fp32, GELU_COEFF) x_neg pypto.mul(x_scaled, F_NEGA_1) exp_neg pypto.exp(x_neg) ones pypto.full(exp_neg.shape, 1.0, exp_neg.dtype, valid_shapeexp_neg.shape) sigmoid pypto.div(ones, pypto.add(exp_neg, F_1)) activated pypto.cast(pypto.mul(x_fp32, sigmoid), pypto.DT_BF16) return activated即gelu(x) ≈ x * sigmoid(1.702 * x)该近似形式在 NVIDIA 的 TransformerEngine 中也有使用属于高性能计算常用实现。对应 PyTorch 参考实现是gelu_torch(x) x * torch.sigmoid(1.702 * x)。SwiGLUdef swiglu_activation_core(gate: pypto.tensor, up: pypto.tensor) - pypto.tensor: gate_fp32 pypto.cast(gate, pypto.DT_FP32) up_fp32 pypto.cast(up, pypto.DT_FP32) pypto.set_vec_tile_shapes(*gate.shape[:2] if len(gate.shape) 2 else (32, 128)) gate_neg pypto.mul(gate_fp32, F_NEGA_1) exp_neg pypto.exp(gate_neg) ones pypto.full(exp_neg.shape, F_1, exp_neg.dtype, valid_shapeexp_neg.shape) sigmoid pypto.div(ones, pypto.add(exp_neg, ones)) swish pypto.mul(gate_fp32, sigmoid) return pypto.cast(pypto.mul(swish, up_fp32), pypto.DT_BF16)即SwiGLU(gate, up) Swish(gate) * up gate * sigmoid(gate) * up对应 PyTorch 参考实现swiglu_torch。ReLUdef relu_activation_core(x: pypto.tensor) - pypto.tensor: pypto.set_vec_tile_shapes(*x.shape[:2] if len(x.shape) 2 else (32, 128)) zero pypto.full(x.shape, 0, x.dtype, valid_shapex.shape) return pypto.maximum(x, zero)即max(0, x)用pypto.full构造零张量后经pypto.maximum完成逐元素取最大。可以看到三个 core 函数都遵守同一模式先用set_vec_tile_shapes设置向量 Tiling再以算子链cast → mul → exp → div → maximum 等表达激活计算。这些算子与矩阵乘、视图、组装等一起构成了 PyPTO 的 张量算子集。静态与动态 Batch 两条执行路径静态路径ffn_activation_kernel静态路径由pypto.frontend.jit装饰的ffn_activation_kernel()实现一次编译、固定 Batch 执行。其结构为根据config.cube_tile_shape设置 Cube Tiling根据config.vec_tile_shape设置 Vector Tiling按config.activation分支计算upSwiGLU 时额外计算gate并施加激活执行 Down 投影pypto.matmul(activated, down_proj_weight, config.dtype, b_transFalse)通过pypto.assemble(result, [0, 0], output)将结果写回输出张量。其中pypto.matmul的签名见 op/matmul.py为matmul(input, mat2, out_dtype, *, a_transFalse, b_transFalse, ...)out_dtype指定输出数据类型b_transFalse表示右矩阵不转置即权重以[I, H]布局直接参与[B, I] [I, H]运算extend_params还支持偏置、反量化、ReLU 融合等扩展能力。pypto.assemble见 operation.py将小张量按偏移量组装进大张量等价于切片的逆操作。动态路径dynamic_gelu_activation_core当use_dynamic_shapeTrue时动态用例会切换到dynamic_gelu_activation_core()。其核心思路是分块循环 视图切片 逐块组装由batch_size hidden_states.shape[0]读取运行时 Batch用ceil_div(batch_size, basic_batch)计算需要迭代的次数ceil_div(a, b) (a b - 1) // b用pypto.loop(0, num_iterations, 1, nameLOOP_FFN_BATCH, idx_nameidx)建立符号化循环。pypto.loop是 PyPTO 的图构建期循环原语见 _controller.py支持name、idx_name、unroll_list、submit_before_loop等控制参数返回迭代器供for使用每次迭代通过pypto.view(hidden_states, [basic_batch, hidden_size], [batch_offset, 0], valid_shape...)切出当前 Batch 分块。需要注意pypto.view与torch.view语义完全不同它更接近 slice见 operation.py按shape与offsets从输入张量提取子视图valid_shape用于声明有效形状以处理越界情况如最后一个分块不足basic_batch时用(batch_size - batch_offset).min(basic_batch)约束对每个分块依次执行 Up 投影 → GELU 激活 → Down 投影用pypto.assemble(output_chunk, [batch_offset, 0], output)把各分块结果按偏移量拼回完整输出。动态用例特意选择batch_size 32、basic_batch 16迭代 2 次以验证非 2 的幂次 Batch 也能正确处理。kernel 的编译与运行模式pypto.frontend.jit装饰器见 frontend/parser/entry.py在装饰期把 Python 函数编译为 PTO IRruntime_options{run_mode: global_run_mode}决定运行目标。RunMode是IntEnumentry.pyNPU 0、SIM 1。_set_run_mode()的优先级为显式指定run_mode 检测到ASCEND_HOME_PATH判定已安装 CANN默认 NPU 回退 SIM。NPU 模式下最终通过pypto_impl.LaunchKernelTorch把编译好的 kernel 与 torch 张量一起发射到当前 NPU 流上执行。与 PyTorch 参考实现的精度对齐每个测试用例都构造了 PyTorch 参考计算作为 goldenGELUup down其中激活用gelu_torchSwiGLUswiglu_torch(gate.float(), up.float())后与down相乘ReLUtorch.relu(up) down。NPU 模式下用assert_allclose(output.cpu().to(torch.float32), ref, rtol3e-3, atol3e-3)校验ReLU 用例额外打印最大绝对差SIM 模式则跳过精度断言仅打印输出形状与范围。所有用例均通过pypto.options(pass_options{enable_slice: True})开启 slice 相关编译 pass。最佳实践性能调优根据模型规模调整vec_tile_shape和cube_tile_shape。较大的 Tile 通常能提供更好的计算密度但会占用更多内部缓存L0/L1需要结合 NPU 实际缓存容量与算子特性权衡数据类型对于 LLM 推理推荐使用DT_BF16以获得更好的精度与性能平衡。样例中 GELU/SwiGLU 用例即采用DT_BF16计算、FP32 中间累加的策略动态 Batch当推理服务的 Batch Size 频繁变化时开启use_dynamic_shape并设置合理的basic_batch。basic_batch本质上是按块处理的粒度越大则循环次数越少、单块计算密度越高但要求 NPU 显存能容纳单块的全部中间张量过小则循环与视图/组装开销占比上升。样例中basic_batch16配合batch_size32演示了 2 次迭代的场景先 SIM 后 NPU开发阶段可用python3 ffn_module.py --run_mode sim在无 NPU 环境完成功能验证再切回npu模式做真实硬件上的精度与性能验证降低对硬件的依赖。注意事项本模块目前主要支持 2D 张量输入[B, H]更高维输入需自行扩展view/matmul对 3D/4D 张量的支持已在 op/matmul.py 中说明可作扩展参考GELU 激活采用常用于高性能计算的 sigmoid 近似实现x * sigmoid(1.702x)与 PyTorch 默认的精确误差函数形式存在数值差异测试精度断言rtolatol3e-3正是为吸收这类近似误差而设置请确保 NPU 上有足够的显存来容纳权重和中间张量动态模式下还需额外容纳basic_batch分块对应的中间结果运行 NPU 模式前必须正确配置 CANN 环境set_env.sh并设置TILE_FWK_DEVICE_ID否则get_device_id()会阻止脚本继续执行。进一步探索FFN 样例完整实现上述全部 kernel、激活 core、测试用例的源码Tiling 控制接口set_vec_tile_shapes/set_cube_tile_shapes/set_matrix_size/loop的定义与完整参数说明matmul 算子矩阵乘的转置、偏置融合、反量化等扩展参数view / assemble 算子张量切片与组装的语义与签名JIT 编译与运行模式pypto.frontend.jit的选项体系与RunMode判定逻辑数据类型枚举DT_FP16/DT_FP32/DT_BF16等数据类型定义【免费下载链接】pyptoPyPTO发音: pai p-t-oParallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表