news 2026/9/15 12:19:32

MNN Vulkan 后端性能优化实战指南:从基准测试到 Kernel 优化的完整工作流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MNN Vulkan 后端性能优化实战指南:从基准测试到 Kernel 优化的完整工作流

MNN Vulkan 后端性能优化实战指南:从基准测试到 Kernel 优化的完整工作流

【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN

本文是 MNN 开源推理引擎 Vulkan 后端(buffer 后端,面向 Adreno / Mali / Apple 移动 GPU)kernel/算子性能优化与新特性集成的完整技术指南,覆盖基准建立、CPU/GPU 瓶颈判定、shader 外科式修改、三层正确性验证、交替 A/B 真机测速,以及 cooperative matrix / subgroup 等 Vulkan 新特性的集成流程。读完你将掌握一套可复现、防踩坑的 Vulkan 优化方法论,并能直接应用于 MNN 的 LLM prefill/decode 场景。

一、优化工作流总览与核心原则

Vulkan 后端的性能优化不能靠"先猜再优化",MNN 将其沉淀为一条可执行的流水线(依据 skills/vulkan-optimize/SKILL.md 与同目录 benchmark/kernel-opt/integrate 三份分步文档):

  1. 选择优化方向:模型级优化(方向 A)/ 指定算子优化(方向 B)/ 新特性集成(方向 C);
  2. 建立基准:Profile 全模型,先分 CPU/GPU 瓶颈,再定位 GPU 内瓶颈 kernel;
  3. 迭代优化:每次只改一个点,至少尝试 3 种技巧,交替 A/B 验证;
  4. 集成验证:全量回归 + 小/大模型跨规模验证 + 代码质量审查;
  5. 沉淀经验:把可复用方法论回写到 optimization-handbook.md。

整个流程围绕 11 条核心原则展开,其中最容易"改了不生效"的根因集中在这几条:

  • .comp必跑 makeshader 等价流程:GLSL 源不会被构建系统直接编译,运行时读的是AllShader.h/cpp里由makeshader.py生成的 SPIR-V 字节数组(.comp →glslangValidator -Vspirv-opt -Oxxd嵌入)。
  • dispatcher 选路要先摸清:同一个 op 常有多条 kernel(CoopMat / subgroup / nosubgroup / 融合 vs 分离),盲改往往根本没被调度;不要把 fallback(nosubgroup)路径性能当 baseline
  • packed weight 必须 packing/unpack 双向镜像:host weight prepare shader 写出的字节布局要与 decode shader 读取逐 bit 匹配。
  • 正确性 oracle 先于性能:优化前要有"已知正确"的 baseline,数学等价的改动应与 baseline 逐 token 一致。
  • 真机才算数,且面向多个 vendor:Vulkan 同时跑 Adreno(Android)、Mali(Android)、Apple(iOS/MoltenVK),driver 差异极大,Mac MoltenVK ≠ Android Adreno。
  • Buffer / Image 是编译期二选一:由MNN_VULKAN_IMAGE决定,两个后端是完全独立的代码树。
  • 换 shader 后必清 pipeline cache:持久化的VkPipelineCachetmp/mnn_cachefile.bin)在 shader 变更后会 stale → 直接 segfault。

二、选择优化方向:A/B/C 三轨

场景方向说明
提升整个模型推理性能A:模型级优化Profile 全模型 → 定位瓶颈(先分 CPU/GPU)→ kernel 或算子级优化 → 重新 profile → 迭代
用户指定优化某个算子B:指定算子优化直接进入指定算子,判断 kernel 级或算子级
集成 Vulkan 新特性(coop matrix / subgroup / 扩展)C:新特性集成理解示例代码 → 适配 MNN → 特性检测 + fallback → 验证

三个方向共享"顺序执行、允许回退、每步验证"的通用规则,每个步骤都有明确的通过标准。

三、方向 A:模型级优化的执行流程

Profile 全模型(op 级 + shader 级耗时) ↓ 先判断瓶颈在 CPU 调度 还是 GPU kernel(handbook §1.4) ├─ CPU 调度瓶颈 → 算子融合减 op / indirect batch / fixResizeCache(handbook §2/§6) └─ GPU kernel 瓶颈 → 定位耗时占比最高的 kernel/op ├─ Kernel 级 → Kernel 优化流程 └─ 算子级 → 算子级优化流程 ↓ 重新 Profile 全模型,验证整体提升 ↓ 定位下一个瓶颈,重复直到满足需求

Step A.1:Profile 全模型

-DMNN_GPU_TIME_PROFILE=ON编译——注意这是编译期宏而非运行时开关。Vulkan profiler 有两块输出:

  • [Execution Profiling](op 级):按 op 类型(Convolution / Attention / Raster / …)聚合 GPU 时间;
  • [Shader Profileing](shader 级):按具体 shader 名(glsl_..._comp)的 GPU timestamp。

⚠️ 两块输出都跨 execute 累计到程序退出,且含 load 期的 auto-tune forward——分析稳态时只取Prepare for tuning opt End之后的块。通过标准:拿到 op 级 + shader 级耗时排序,确定第一个优化目标,并已判断瓶颈在 CPU 还是 GPU。

Step A.2:判断优化级别

信号级别进入流程
端到端 ≫ GPU kernel 累计(GPU 只占几分之一)CPU 调度瓶颈算子融合 / indirect batch / fixResizeCache
单个 shader 占比高、本身有优化空间Kernel 级→ Kernel 优化流程
同一算子多个 shader 合计耗时高 / 有大量格式转换算子级→ 算子级优化流程
算子计算模式不适合 GPU(单 work-item 串行)算子级→ 算子级优化流程

Step A.3:验证并迭代

重新 profile,确认瓶颈耗时下降 + 端到端提升;未满足则回到 A.1 定位下一个瓶颈。

四、方向 B:指定算子优化与算子级优化流程

用户指定算子时,先 profile 算子内各 shader 耗时:最慢的单个 shader 走 Kernel 优化流程;跨 kernel 边界的问题(合并/拆分 dispatch、改中间排布、融合 epilogue)走算子级优化流程。

算子级优化流程(方向 A/B 共用):

定位算子内所有 shader(读 Execution::onEncode/onResize) ↓ Profile 各 shader 耗时占比 ↓ 整体分析策略: - 合并 dispatch(减少命令录制 + 中间 buffer;Vulkan CPU 调度重,收益常更大) - 融合 epilogue(把 unpack/scale 折进 matmul,省一次 dispatch + temp) - 拆分(提高并行度) - 改中间数据排布(消除格式转换 / raster) - 用 cooperative matrix 重写 matmul ↓ 迭代验证,直到算子整体性能满足需求

各手段的适用场景与本仓库的真实案例:

手段适用场景本仓库案例
融合 epiloguematmul 后有独立 unpack/转置 passconv1x1 coop 把 COOP_to_C4 折进 matmul epilogue(小 N 收益,大 N 反伤 occupancy → 按 N 门控)
coop matrix 重写GEMM/QKV,Adreno 支持 coopattention QK·V 用 coop(scalar qkv_acc 102ms → coop 54ms)
subgroup 归约有 tree reduction + barrierprefill softmax 用 subgroupMax/Add 替代树形(37→23ms)
合并 dispatch / indirect batch命令录制占大头MNN_GPU_RECORD_BATCH让多 op 共享 command buffer(prefill +56%)

Vulkan 关键提醒:算子级优化前先确认瓶颈是 GPU 还是 CPU 调度。若是 CPU 调度,GPU kernel 再快端到端也不动。

五、Kernel 优化流程:从瓶颈分析到交替 A/B

分析 shader 的计算强度 / BW 利用率(handbook §1) ↓ 判断瓶颈类型(compute / memory / occupancy) ↓ 从 §2 技巧 + §5 速查表选匹配方法 ↓ 实施 → 验证正确性 → 测量性能(交替 A/B) ↓ 未达预期?换一种技巧重试
阶段文档目标
基准benchmark.md建立性能基准,分析瓶颈类型
优化kernel-opt.md至少尝试 3 种技术,迭代提升
集成integrate.md全量回归、代码审查、性能报告

迭代至少 3 种技术,按难度递进:先手(低难度)push_constant、indirect batch、epilogue 合并写;再上(中难度)subgroup 归约、融合 epilogue + 按规模门控;最后(高难度)cooperative matrix 重写、算子整体重写。停止条件(须全满足):已试 ≥3 种 / 达标或连续 3 次 <5% 提升 / 每次都有 A/B 数据记录。

六、编译与真机运行(Android,唯一来源)

编译

cd project/android/build_64 # 首次配置(确认 buffer 后端 + LLM) cmake .. -DMNN_VULKAN=ON -DMNN_VULKAN_IMAGE=OFF -DMNN_BUILD_LLM=ON \ -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -DMNN_LOW_MEMORY=ON -DMNN_ARM82=ON make llm_demo -j8 # SEP_BUILD=OFF → 会重编 libMNN.so # 需要 GPU per-op/shader 耗时:cmake -DMNN_GPU_TIME_PROFILE=ON . #(测干净 tok/s 时务必 =OFF)

要点:

  • 参数MNN_VULKAN_IMAGE=OFF明确走buffer 后端(LLM 全链路走 buffer);=ON则是source/backend/vulkan/image/*的独立代码树,两边改动互不影响;
  • make MNN不会连带重编 Vulkan 静态库,必须用make llm_demo;改 shader 后AllShader.cpp变了也会重编;
  • 该宏在 source/backend/vulkan/CMakeLists.txt 中定义,并分别驱动 buffer/image 两棵代码树的构建。

推送 + 运行

adb push libMNN.so llm_demo /data/local/tmp/MNN/ adb -s <serial> shell "cd /data/local/tmp/MNN && rm -f tmp/mnn_cachefile.bin && \ LD_LIBRARY_PATH=. ./llm_demo <model>/config_vk.json <prompt.txt> <ndecode>"

换 shader 后必清tmp/mnn_cachefile.bin;否则 stale pipeline cache 会 segfault。profile 命令示例(benchmark.md §0.3):adb -s <serial> shell "cd /data/local/tmp/MNN && rm -f tmp/mnn_cachefile.bin && LD_LIBRARY_PATH=. ./llm_demo <model>/config_vk.json 512.txt 2 2>&1" > prof.txt

入口定位

grep -rn "OpType_<MyOp>" source/backend/vulkan/buffer/execution/ # buffer 后端 grep -rn "OpType_<MyOp>" source/backend/vulkan/image/execution/ # image 后端

conv1x1 低 bit 选路(VulkanConvolution.cpp 的onCreate):

useInt8Conv && is1x1 ├─ coopMat supported && Adreno │ ├─ perChannelAsym + S8S8S32 → VulkanConv1x1CoopA8 │ └─ else → VulkanConv1x1Coop (CoopMat 只支持 int4/int8) └─ → VulkanConv1x1General (native int8/int4/int2/int3) else → VulkanConvolutionSlideWindowsInt8

attention prefill 走 VulkanAttention.cpp:rearrange_q → init_state → (per k-block: qk → softmax → qkv) → finalize,coop QKV / subgroup softmax 由设备能力选路。每个onEncode决定本次 dispatch 哪些 shader,把目标 shape 代入确认,再改对应.comp。仓库中已存在attention_prefill_coop_qk.compattention_prefill_coop_qkv.compdynamic_w8a8_coop_gemm.comp等 coop 路径 shader(见 source/backend/vulkan/buffer/execution/glsl)。

七、Shader 修改流程(含防污染)

# 1) 编辑 .comp(确认 buffer 还是 image 后端) vi source/backend/vulkan/buffer/execution/glsl/<my_kernel>.comp # 2) 新文件在 glsl/macro.json 登记 useFP16(决定是否生成 _FP16 变体) # 3) 重新生成 SPIR-V 嵌入数组

⚠️不要直接跑全量makeshader.py——本机 glslang/spirv-opt 与仓库版本不同,会把所有 shader 数组重编/重排,AllShader.cpp出现几万行无关 diff(污染)。正确做法是只重生成改动的那几个数组,外科式替换进AllShader.cpp

# 对每个改动的 shader(fp32 + fp16 变体),用与 makeshader 相同的管线单独编译: # header(FP32/FP16) + body → glslangValidator -V [--target-env vulkan1.1(若含 coop/subgroup/memory_scope)] → spirv-opt -O → xxd -i # 得到 const unsigned char glsl_<name>_comp[]={...}; unsigned int glsl_<name>_comp_len=N; # 再用脚本精确替换 AllShader.cpp 里对应的那一段(正则匹配数组头到 _len 行)。 # 新增 shader 还要在 AllShader.h(extern 声明)+ VulkanShaderMap.cpp(name→数组 map)追加。

改完确认:grep -c 'glsl_<name>_comp_len' AllShader.cpp且用git diff --stat确认只有目标数组变了。SPIR-V 合法性可用spirv-val验证。

新加 kernel 同步检查清单.comp主路径 +_nosubgroup变体都加 /macro.json登记 / host pipeline 选择分支 /AllShader.{cpp,h}+VulkanShaderMap.cpp三处注册 / weight stride & buffer size 重算 / dispatcher 显式选路或 fallback。

八、正确性验证:三层 Oracle

三层 oracle(数值层 dump tensor → op 层MNNV2Basic.out单层 → 端到端跑模型)。端到端关 sampler 随机性temperature:0.0,greedy),CPU/Vulkan 同 prompt 前 N token 应一致(fp16 误差内)。

  • CPU oracle 不可用时的兜底(低 bit 路径 CPU 本身就乱):用冻结 baseline 二进制做 GPU-baseline vs GPU-opt 的逐 token 贪心对比(数学等价改动应完全一致)。改 kernel 前先git stash存一份 baselinelibMNN.so
  • 模型本身可能就坏:小模型极低 bit 量化 CPU 跑也乱(如 Qwen3-0.6B 的 w2/w3),用 4B/8B 才是有效验证样本。
  • Mac MoltenVK 行为不代表 Android,最终验收必须 Android 真机。
  • 数值容忍:fp16 vs fp16 abs<1e-2;量化 dequant+fp16 abs<1e-1。

集成阶段的回归命令(integrate.md):Vulkan 的 forward type 为 7,单测用adb -s <serial> shell "cd /data/local/tmp/MNN && LD_LIBRARY_PATH=. ./run_test.out op/XxxTest 7 <precision> <numThread>",全量 op 把op/XxxTest换成op/,通过标准是all tests passed

九、性能测量:Android 真机上的防噪声方法

小收益最容易被噪声骗,手机 GPU 有两个陷阱:

  1. 冷启动首跑不算数:首跑含 auto-tune + pipeline 编译。清了mnn_cachefile.bin后第一次也是冷的。先 warm 再测
  2. 跨时段热漂移 ~±8–10%:设备温度随时间变,"先测完 base 再测 opt"不可比。必须交替 A/B——base 和 opt 两份二进制(或同一二进制不同 env)背靠背配对:base→opt→base→opt,看每轮配对里 opt 是否稳定胜出,而非比两组绝对值。

换 shader 做 A/B 时,每次切库 pipeline cache 会 stale;每轮跑前rm tmp/mnn_cachefile.bin(tok/s 不含 load,清 cache 只影响 load 时间,A/B 仍公平)。

MNN_GPU_TIME_PROFILE=ON的 build 只看相对占比(放大绝对耗时且改变调度);最终收益以不带 profile 的干净 build 的端到端 tok/s 为准

十、优化技巧速查与瓶颈分析方法论

先做 roofline 分析

  • 计算强度AI = FLOPs / Bytes,与ridge_point = peak_FLOPS / peak_BW比:AI < ridge→ memory-bound;> ridge→ compute-bound。典型:GEMV(decode) memory-bound;GEMM(prefill) 随 batch 增长;elementwise/raster ≈ memory-bound。移动 GPU LPDDR 理论带宽打 6–8 折为实测可达。
  • BW 利用率BW_util = actual_bytes / kernel_time / peak_BW:<50% 访存模式/launch 有问题;50–70% cache miss/WG 大小可调;>70% 只能减数据量(packed 存储)。
  • occupancy 墙:roofline 说 memory-bound 但减流量/减 ALU 都不涨 → 大概率是 occupancy 墙。Vulkan compute shader 的 occupancy 受寄存器和**共享内存(shared数组)**双重限制——本仓库 conv1x1 融合 epilogue 引入shared[64*64](8KB)使大 N conv occupancy 下降、+148ms。

⚠️ Vulkan 第一诊断:CPU 调度 vs GPU kernel

这是 Vulkan 区别于 OpenCL/Metal 的最关键一步。MNN Vulkan 的 prefill 端到端常常受 CPU 侧命令录制/调度瓶颈,而非 GPU kernel。实测数据(Adreno,Qwen3-0.6B,514-token prefill):

阶段耗时占比
GPU 计算~263ms60%
命令录制(op onResize/onEncode ×1129)~63ms14% ← CPU 侧最大
submit / 等待~52ms12%
allocMemory 其余 + geometry + shape + 真显存分配~47ms11%

结论:CPU 调度开销(~187ms)> GPU kernel 中可优化的部分。判断方法:用MNN_GPU_TIME_PROFILE=ON拿 GPU kernel 累计,与干净 build 的端到端 wall 时间比——GPU 累计 ≈ wall → GPU-bound,优化 kernel 有效;GPU 累计 ≪ wall → CPU 调度 bound,优化 kernel 无效,改去攻调度。模型规模不同瓶颈会翻转:小模型(0.6B)CPU-bound,大 int4 模型(4B)conv GPU 占比大反而 GPU-bound。

技巧速查表(摘自 optimization-handbook.md §5)

#技巧针对瓶颈适用场景难度收益参考
1Cooperative matrix 重写 matmulcompute/访存(GEMM)Adreno + coop,K 维够大QKV −47% kernel
2Subgroup 归约替代 tree reduction归约 barriersoftmax/reduce,支持 subgroupsoftmax −38% kernel
3Epilogue 合并写(coalesced store)memory(scatter 写)NC4HW4/转置 epilogue 非合并大 N conv 回收约一半 regression
4融合 epilogue(unpack/scale 折进 matmul)dispatch + temp 往返matmul 后紧跟独立逐元素 pass小模型 prefill +8%
5按输出规模门控融合 vs 分离occupancy融合用 shared、大 N 反伤4B −6%→+2.6%
6push_constant 替代小 uniformCPU 命令录制uniform ≤128B 的 opraster onResize −15~25%(e2e 小)
7indirect batch(RECORD_BATCH)submit + 命令录制多 op 推理prefill +56%

先分清 CPU 调度 vs GPU kernel:CPU-bound 选 6/7 + §6 候选;GPU-bound 选 1–5。

关键技巧要点

  • Coop matrix 重写 matmul(技巧 1):用coopmat<...>+coopMatLoad/coopMatMulAdd/coopMatStore替代 scalar 累加;COOP_M/N/K 用设备selectedFP16CoopMatShape(Adreno 常 16×16×16 或 64×64×16)经 spec constant 传入;local_size_x = subgroup size。coop matrix 只对 int8/int4 有硬件定义;K 维太小时 shared staging 开销盖过收益(headDim=128 做 coop-QK 实测 e2e −3%,负收益,已弃)。
  • Subgroup 归约(技巧 2):一个 workgroup = 一个 subgroup(local_size_x_id=0设为 subgroup size),用subgroupMax/subgroupAdd替代"shared 数组 + barrier + stride 折半"的树形归约;需#extension GL_KHR_shader_subgroup_arithmetic : require+--target-env vulkan1.1;host 按getSubgroupSize()动态设 local size,别 hardcode。⚠️ softmax kernel −38% 但 prefill e2e 0%(CPU-bound),收益体现在散热/长文脉/大模型。
  • Epilogue 合并写(技巧 3):让输出的连续维在相邻线程间最快变化。NC4HW4 输出[N/4, M, 4],epilogue 循环里让m = idx % TILE_M(gm 最快)而非m = idx / n4PerTile——后者相邻线程写地址跨步M(=token 数),完全非合并。
  • 融合 epilogue(技巧 4):matmul 把 tile 存进sharedbarrier()后直接按目标布局写出(含 bias/激活),省掉"写 temp → 独立 pass 读 temp"的一次 dispatch。⚠️ 引入的sharedstaging会吃 occupancy——必须配合技巧 5 按规模门控。
  • 按规模门控(技巧 5):保留两条路径——小 N 走融合(零 temp,省 dispatch),大 N 走分离(matmul 写 row-major temp,不用 shared → 高 occupancy+ 一个独立 unpack pass)。阈值靠逐 conv dump 出真实 N 分布确定(2B 最大 conv N=6144 偏好融合、4B N=9728 偏好分离,阈值取 8192),并做成 env 可调(MNN_VK_CONV_FUSE_MAXN)。
  • push_constant(技巧 6)layout(binding=N) uniformlayout(push_constant) uniform,host 侧allocUniform+writeBuffervkCmdPushConstants。收益比预期小(raster onResize 只降 15–25%),因为vkAllocateDescriptorSets才是大头。
  • indirect batch(技巧 7):开MNN_GPU_RECORD_BATCHScheduleConfig::mode0x200),让mDirect=false——1000+ 个 op 打包到少数 command buffer segment,submit 从 ~1129 次降到少数次。LLM 里通过 config"vulkan_record_batch": true开启。这是攻 CPU 瓶颈的最大单一杠杆。

十一、常见陷阱速查

#陷阱后果规避
A全量 makeshader 污染 AllShader.cpp几万行无关 diff只外科式重生成改动数组
B持久化 pipeline cache 换 shader 后 stale直接 segfault每轮rm tmp/mnn_cachefile.bin
Cbuffer/image 编译期二选一,改错树白改grep MNN_VULKAN_IMAGE CMakeCache.txt
Dcoop/subgroup shader 缺 target-env vulkan1.1spirv-opt 拒绝/静默回退手动重生成时必带--target-env vulkan1.1
Edescriptor set 池化在 Adreno 反变慢decode −12%(净负)目标 driver 实测后再动
Fattention GPU kernel 微优化对 prefill e2e 常无效e2e 0%动 kernel 前先确认 GPU-bound
G跨 session 顺序测 A/B 被热漂移欺骗噪声当收益必须交替 A/B
Hcooperative matrix 只支持 int8/int4数值乱/driver crashdispatcher 显式 gate,w2/w3 走 General
Ishared 数组吃 occupancy大 N 净 +148ms评估 occupancy,大规模走无-shared 分离路径
JCPU 侧大头是 vkAllocateDescriptorSets(82%)push_constant 只省小头攻 descriptor set 或跨 forward 复用命令
Ksubgroup 与 nosubgroup 双 shader 必须同步老 Mali 挂或走错逻辑两个变体都改
Lcoop 端数 tile 越界读靠 robustBufferAccess 兜底(UB)输出对但是 UBhost 侧把 workspace 的 M 维 padding 到 COOP_M 倍数
M未扩展路径吃错 buffer 大小OOB 读到下个 op 数据搜所有以mIsInt4分流处同步加处理
Ndriver OOM / 设备重启手机重启先小模型验通路,崩了让设备恢复

十二、Packed weight 设计与新特性集成

Packed weight 设计(§4)

新加 quant bit / 调 tile 排布时先固定 5 个量:tile(最小访问区块,Vulkan conv1x1 常见行主[N, padK/W]每 word 装 W 个 weight)/ word 内 weight 数(w2:16, w4:8, w8:4 per uint32)/ 多 word split(w3 用 lo2+hi1 双 word,[N, padK/16, 2])/ bit 顺序(低 bit 先)/ signed 存储。

  • signed/unsigned 与 originOffset:导出器写出的 alphab = min + offset_signed*scaleoriginOffset 已折进 bias。Vulkan int8 path 用bitfieldExtract(packedW,0,8)会 sign-extend,signed bytes 直接当 signed 解,无需再减 offset(这点和 OpenCL/Metal 从 unsigned 解不同)。
  • w3 split[N, padK/16, 2]uint pairs,pair[0] 低 16bit 装 16 个 2bit,pair[1] 低 16bit 装 16 个 1bit;decodeq = (low2>>(i*2))&3 | ((hi1>>i)&1)<<2,减 4 得 signed[-4,3]。host buffer size 乘wordsPerGroup=2,shader 内 stride 也 ×2。

方向 C:新特性集成流程

触发条件是用户说"用这个 Vulkan 特性/coop matrix/subgroup 扩展"并提供示例代码;没提供示例时主动要求(.comp + host 或完整可运行 demo、特性说明、目标算子、目标平台、预期收益)。

集成步骤:特性检测 → shader 适配(用FLOAT宏、NC4HW4、spec constant、扩展 require + 头部注释)→ 外科式重生成 + 三处注册 → host 选路(if (feature supported) 用新 pipeline else 原路径,两条都 createSet + 绑定)→ 编译验证。Fallback 必须设计:coop 目前一般 gate 在gpuType()==ADRENO && supportCoopMat

设备能力查询VulkanDevice(runtime 目录)中:

auto coop = vkBn->getDevice().getCoopMatInfo(); // supportCoopMat, selectedFP16CoopMatShape const auto& sg = vkBn->getDevice().getSubgroupInfo(); // size, stages, ops uint32_t sgSize = vkBn->getDevice().getSubgroupSize();

新特性用 spec constant(layout(constant_id=N),host 经getPipeline(name, types, localSize, spec)传入)或 build 宏控制路径 + runtime 检测 fallback。coop/subgroup shader 需--target-env vulkan1.1(makeshader 已按扩展关键字判断)。仓库中 coop 相关 host 实现可参考 VulkanConv1x1CoopA8.cpp、VulkanConv1x1CoopAFP16.cpp 与 VulkanAttention.cpp。

十三、集成验证与经验沉淀

优化进入 MNN 主体前必须完成:全量回归run_test.out op/ 7 ...全过)+跨模型规模验证(至少小模型 + 大模型各测一遍,如 0.6B + 4B,确认没有一个规模退步)+代码质量审查(shader 三处注册、subgroup/nosubgroup 双变体、occupancy 评估、fallback 完善、阈值 env 可调)+性能报告(实测交替 A/B 数据,不写"预期")。

提交信息格式建议:

[Vulkan:Perf] Optimize Xxx (coalesce epilogue / gate fusion by N / ...) - 技术1 - 技术2 Performance: 0.6B +x% / 2B +x% / 4B +x% prefill (Adreno, interleaved A/B) All op/ tests passed

MNN 有 clang-format-diff pre-commit 钩子,手写 .cpp/.hpp 被拦时用git clang-format HEAD只格式化改动行,但不要让它重排 AllShader.cpp 的字节数组

沉淀经验:完成一个较复杂的优化任务后(重写算子、新技巧奏效、踩了非显而易见的坑、验证/排除了某方向),把可复用的方法论回写到 optimization-handbook.md——它是 Vulkan kernel/访存/调度级技巧与陷阱的唯一来源:新技巧进 §2 + 登记 §5 速查表;新陷阱进 §3;新瓶颈定位/测量方法论进 §1;验证通过某候选方向从 §6 移入 §2;实测排除某方向在 §6 标注"已排除 + 原因"。只写方法论,不写单次任务流水账;技巧编号是稳定 ID(只追加不复用)。某具体 shape/kernel 的一次性发现属于 agent memory,不进手册。

十四、候选优化方向(尚未落地 / 部分已排除)

有依据但未在 MNN 落地实测,或已实测排除,想用先做 spike 验证:

  • 候选 A:fixResizeCache 跨 forward 复用命令——同 shape 的第二次 forward 整段跳过 resize/encode,预期省整个_allocForTensor(~59ms)。风险中、收益结构性,未落地
  • 候选 B:算子融合减 op 数——在 GeometryComputer 层合并 raster,或 Vulkan 后端把连续 raster 打包成一个 dispatch。工作量大。
  • 候选 C:region 合并——已排除(LLM 场景):实测 LLM raster 只有 1% 可合并(max_run=2),省 ~0.4ms,噪声内。
  • 候选 D:descriptor set 池化——已排除(Adreno):decode −12% 净负,其它 driver 可重试。
  • 候选 E:coop matrix 用于 QK——已排除(headDim=128):K 维太小,e2e −3%,更大 headDim 可重试。

结语

MNN Vulkan 后端的性能优化是一个"先分清 CPU/GPU 瓶颈 → 建立可对比基线 → 单点突破 → 交替 A/B 验证 → 集成回归 → 沉淀方法论"的闭环。核心心法总结为三点:.comp必走外科式 makeshader 流程真机交替 A/B 才是唯一可信的测量方式先量化瓶颈再选优化杠杆。按本文工作流操作,即可在 Adreno/Mali/Apple 多 vendor 真机上稳定推进 conv/gemm/attention 等算子的 kernel 与调度级优化。

【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 12:17:49

用ima构建个人知识库:从导入到AI检索的完整教程

很多人收藏了一堆好文章、存了一堆PDF和笔记&#xff0c;真到用的时候却翻不到、找不到&#xff0c;知识散落得到处都是。腾讯出的免费工具 ima&#xff0c;就是来解决这个问题的——它能把网页、文档、碎片想法统一收进一个个人知识库&#xff0c;然后用 AI 检索的方式直接问、…

作者头像 李华
网站建设 2026/9/15 12:16:10

Unity后处理实战指南:从URP配置到性能优化与调参心法

很多朋友做Unity项目做到最后&#xff0c;总觉得画面“差口气”。模型面数不低&#xff0c;贴图也是高清的&#xff0c;灯光该打的也打了&#xff0c;但渲染出来就是干巴巴的&#xff0c;没有那种“商业项目”的质感。这时候&#xff0c;八成是后处理&#xff08;Post Processi…

作者头像 李华
网站建设 2026/9/15 12:16:06

Serverless AI运行时:智能体开发的新架构演进

1. 项目概述&#xff1a;Serverless AI运行时的演进与价值十年前我第一次接触Serverless架构时&#xff0c;就被其"按需付费、免运维"的特性所吸引。如今在AI应用爆发式增长的背景下&#xff0c;传统Serverless运行时已经无法满足智能体开发的需求。最近我在为金融行…

作者头像 李华
网站建设 2026/9/15 12:15:44

如何用Semantica导出知识图谱到Neo4j?CSV导出实战教程

如何用Semantica导出知识图谱到Neo4j&#xff1f;CSV导出实战教程 【免费下载链接】semantica Graph-Native Infrastructure for Context and Accountable AI Systems 项目地址: https://gitcode.com/GitHub_Trending/sema/semantica Semantica 是一款"图原生"…

作者头像 李华