本文へ移動
cccskills
無料GitHub で公開

vulkan-optimize

MNN Vulkan 后端 kernel/算子性能优化与新特性集成。覆盖 benchmark 基线、kernel 优化迭代、集成验证全流程,以及 GLSL .comp + makeshader 双轨、conv1x1/attention dispatcher 多路径(coopMat/subgroup/nosubgroup)、cooperative matrix、packed weight 设计、pipeline cache、CPU 侧调度瓶颈、Adreno/Mali/Apple 多 vendor 真机验证等参考知识。

インストール方法を見る

含まれるファイル(6)

  • SKILL.md17.7 KB
  • benchmark.md4.4 KB
  • integrate.md4.8 KB
  • kernel-opt.md4.2 KB
  • new-feature.md5.3 KB
  • optimization-handbook.md19.9 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

MNN Vulkan 优化 Skill

触发:优化 Vulkan 端 kernel 性能(conv/gemm/gemv/attention/elementwise 等),新增算子,调度选路或 pack layout 调整,或集成 Vulkan 新特性(cooperative matrix、subgroup、扩展等)。

边界:不读不改 schema/private/、source/internal/。

核心原则

  1. 改 .comp 必跑 makeshader。GLSL 源不会被构建系统直接编译;运行时读的是 AllShader.h/cpp 里由 makeshader.py(.comp → glslangValidator -V → spirv-opt -O → xxd 嵌入)生成的 SPIR-V 字节数组。每次改 .comp 后必须重生成并确认进二进制。最常见的"改了不生效"根因。⚠️ 但本机 glslang/spirv-opt 与仓库版本常不同,跑全量 makeshader 会重排/重编所有 shader 数组、污染 AllShader.cpp——必须只外科式重生成改动的那几个数组(见「Shader 修改流程」+ 手册陷阱 A)。

  2. dispatcher 选路要先摸清。Vulkan 同一个 op 常有多条 kernel:CoopMat(Adreno only)/ subgroup(Mali/Adreno)/ nosubgroup(兜底)/ FP gemm / 融合 vs 分离路径。改 kernel 前先读 host 选路代码,把目标 shape/dtype 代入确认落到哪一条。盲改经常发现根本没被调度。不要把 fallback(nosubgroup)路径性能当 baseline——它本身就慢。

  3. packed weight 必须 packing/unpack 双向镜像。host weight prepare shader 写出的字节布局要和 decode shader 读取逐 bit 匹配,任何一边改动都要双边同步改(详见 optimization-handbook.md §4)。

  4. 正确性 oracle 先于性能。GPU 输出错和算法错难分辨。优化前要有"已知正确"的 baseline,每步改完都比对它。数学等价的 kernel 改动应与 baseline 逐 token 一致。

  5. 真机才算数,且面向多个 vendor。Vulkan 同时跑 Adreno(Android)、Mali(Android)、Apple(iOS/MoltenVK)等,driver 稳定性和性能差异极大。Mac MoltenVK ≠ Android Adreno。进入性能/稳定性测试前先 adb devices 检查目标 Android 设备;没设备直接停下来给用户提需求。

  6. Buffer / Image 是编译期二选一。由 MNN_VULKAN_IMAGE 决定:=OFF → source/backend/vulkan/buffer/*(LLM 全链路走 buffer);=ON → source/backend/vulkan/image/*。两个后端是完全独立的代码树,一边改动另一边没有。改前先 grep MNN_VULKAN_IMAGE project/android/build_64/CMakeCache.txt 确认,并保证改的路径和 build 选的后端一致(详见手册陷阱 C)。

  7. 先量化瓶颈,再选优化杠杆。不要先猜再优化——用数字说话。Vulkan 特有的第一诊断:prefill 端到端常常受 CPU 侧命令录制/调度瓶颈,而非 GPU kernel(实测 attention GPU kernel 优化 −38% 但 e2e 0%)。动手前先分清瓶颈在 CPU 还是 GPU(方法论见 optimization-handbook.md §1)。

  8. 单点突破,迭代验证。每次只改一个优化点,验证正确性和性能后再下一个。

  9. 换 shader 后必清 pipeline cache。持久化的 VkPipelineCache(存在 tmp/mnn_cachefile.bin)在 shader 变了之后会 stale → 直接 segfault。改 shader 后测试/发布前先 rm tmp/mnn_cachefile.bin(详见手册陷阱 B)。

  10. 新特性必须有示例代码 + fallback。集成 coop matrix / subgroup / 扩展时:(1) 要求用户提供可运行示例——不同 vendor 对同一特性行为可能不同;(2) 用 runtime 特性检测分发,保留原路径做 fallback,不支持的设备不受影响。

  11. 名字必须等于真实物理量。改任何算子 / kernel 代码时,先核对沿途变量、字段、宏名是否就是它实际装的那个量(不只是自己新加的名字):blockSize 到底是"块内元素数"还是"每行块数"、分档判据的自变量是"head 数"还是 dispatch 的 workgroup 数。名字骗人不会报错——编译、对拍、单测都过,但基于它写的分档、阈值、门控会一起错。名字与实际索引算术不符就改名,并全仓确认零残留。


执行流程

通用规则

  1. 顺序执行:工作流内的步骤不能跳过。
  2. 允许回退:对某阶段结果不满意可回到该阶段重做。
  3. 每步验证:每个步骤有明确通过标准,通过后才进入下一步。

选择优化方向

场景方向说明
用户要求提升整个模型的推理性能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 之后的块(见 handbook §1)。

提取:每个 op/shader 的总耗时 + 占比排序、Top-N 瓶颈、端到端 prefill/decode tok/s 基线。

通过标准:有 op 级 + shader 级耗时排序,已确定第一个优化目标,并已判断瓶颈在 CPU 还是 GPU。

Step A.2:判断优化级别

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

Step A.3:验证并迭代

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


方向 B:用户指定算子优化

先 profile 算子内各 shader 耗时:

  • Kernel 级:定位最慢 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 门控,见 handbook 技巧 3/5)
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 再快端到端也不动(见 handbook §1.4 + 陷阱 F)。


Kernel 优化流程(方向 A/B 共用)

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

方向 C:新特性集成

文档目标
new-feature.md理解示例 → 评估兼容性 → 适配 MNN + 特性检测 → fallback → 验证

触发:用户说"用这个 Vulkan 特性/coop matrix/subgroup 扩展"并提供示例代码。没提供示例时主动要求。


收尾:沉淀经验到手册

完成一个较复杂的优化任务后(重写算子、新技巧奏效、踩了非显而易见的坑、验证了/排除了某方向),把可复用的方法论回写到 optimization-handbook.md——它是 Vulkan kernel/访存/调度级技巧与陷阱的唯一来源。

信号回写到
新的可迁移优化手法§2 新增技巧 + 登记 §5 速查表
别人也会踩、非显而易见的坑§3 新增陷阱
新的瓶颈定位/测量方法论§1
验证通过某 §6 候选方向从 §6 移入 §2 + §5,补实测收益
实测排除了某方向在 §6 标注「已排除 + 原因」,避免重复尝试

回写要求:只写方法论(适用场景/做法/注意/最小示例/实测收益),不写单次任务流水账;技巧编号是稳定 ID(只追加不复用);新增时顺手精简过时条目;某具体 shape/kernel 的一次性发现属于 agent memory,不进手册。若本次无可复用经验,跳过回写。这一步与 retrospective skill 互补。


参考知识

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

编译(project/android/build_64/,Vulkan buffer 后端 + LLM,SEP_BUILD=OFF 时 Vulkan 编进 libMNN.so):

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)

make MNN 不会连带重编 Vulkan 静态库;用 make llm_demo。改 shader 后 AllShader.cpp 变了也会重编。

推送 + 运行:

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(原则 9);否则 stale pipeline cache 会 segfault。

入口定位

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。

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 确认只有目标数组变了(无关 _len 行不应出现在 diff)。SPIR-V 合法性可 spirv-val 验证。

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

正确性验证

三层 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 存一份 baseline libMNN.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。

性能测量(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 是否稳定胜出,而非比两组绝对值。本仓库多次踩过跨 session 顺序测把噪声当收益的坑(见 handbook §1)。

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

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

优化技巧与常见陷阱

详见 optimization-handbook.md:

  • §1 性能分析方法论:roofline + CPU 调度 vs GPU kernel 的先判断(Vulkan prefill 关键);两块 profiler 的读法。
  • §2 优化技巧:coop matrix、subgroup 归约、epilogue 合并写(coalescing)、按 N 门控融合、融合 epilogue/scale、push_constant、indirect batch 等,每条含适用场景/做法/注意/收益。
  • §3 常见陷阱:makeshader 污染、pipeline cache stale、buffer/image、coop target-env、descriptor pool 在 Adreno 反变慢、attention 微优化对 prefill e2e 无效、热漂移、coop 只支持 int4/8、subgroup/nosubgroup 双 shader、weight pack 5 处同步、driver OOM 等。
  • §4 Packed weight 设计、§5 速查表、§6 候选方向(fixResizeCache 跨 forward 复用命令等)。

新特性集成参考

Vulkan 设备能力查询在 VulkanDevice:getCoopMatInfo().supportCoopMat / getSubgroupInfo() / getSubgroupSize() / getCoopMatInfo().selectedFP16CoopMatShape。新特性用 spec constant(layout(constant_id=N),host 经 getPipeline(name, types, localSize, spec) 传入)或 build 宏控制路径 + runtime 检测 fallback。coop/subgroup shader 需 --target-env vulkan1.1(makeshader 已按扩展关键字判断)。详见 new-feature.md。

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

为 MNN 框架新增算子。包含 Schema 定义、形状计算、几何计算、后端实现、单元测试的完整 TDD 流程。分 5 步执行,每步有独立测试标准。

日本語の概要は準備中です。原文の説明を表示しています。

alibaba/MNN1.6万2026年10月10日 更新

bugfix

無料

MNN 各类正确性/回归 bug 的排查入口,按 bug 类别分册组织,本文件只做症状分流。分册:内存别名与生命周期(arena reuse、`MemChunk`、融合引入的别名竞争)、量化误差与导出侧权重损坏(低 bit 打包、导出分块、PyTorch MPS/CUDA 大张量静默错误)、host 侧并发/线程竞争(共享所有权的引用计数被写坏、析构链崩溃、TSAN A/B 与编译期哨兵)、fp16 表示能力不足(长序列复读、position 塌缩,以及「实时计算→预计算查表」重构的三类陷阱)、GPU shader 越界与 command buffer 故障、后端 kernel 隐式假设违反(causal mask、layout 约定)、持久化缓存误信(weight-mmap sync 自我污染、跨模型缓存复用)、逐 run 不同的非确定性(未初始化内存/堆垃圾依赖、多线程动态分发×异构 kernel)。用户报告 MNN 输出乱码/退化、单测或 golden 对不上、改动后回归、换后端结果不同、开某开关才错、结果每次跑都不一样、或崩在析构链上且只在后台线程异步释放时偶现时使用。

日本語の概要は準備中です。原文の説明を表示しています。

alibaba/MNN1.6万2026年10月10日 更新

cpu

無料

MNN CPU 后端(ARM / x86_64 / RISC-V 三侧)的总入口,只做分流不承载技术内容。下分 `optimize/`(为什么慢、该改哪一层)与 `kernel/`(这条 kernel 怎么写对、怎么被选中)两个分支,`shared/` 放两者共用的构建测试跑分命令、env 开关注册表与 RISC-V 开发板远端验证纪律。做 CPU 侧的工作但还不确定该进哪个分支,或需要三侧结构差异对照(第二张函数表按什么分、二级表怎么构造、`Precision_Low` 语义、ISA A/B 怎么做——「三侧不同构对照表」全树唯一一份,就在本文件)时读这里。

日本語の概要は準備中です。原文の説明を表示しています。

alibaba/MNN1.6万2026年10月10日 更新

MNN CPU 后端 kernel 开发分支(`skills/cpu/` 下,另一分支是 `cpu/optimize` 性能归因)。覆盖标量 oracle → C++ SIMD → intrinsic → 汇编的四级实现阶梯、pack/kernel ABI 契约(tile、cell stride、后处理参数)、CoreFunctions 派发表注册与二级表安全构造、跨 ISA × 精度的正确性门禁,以及 AArch64 / x86_64 / RISC-V 三份实现参考。为 CPU 后端新写或移植 kernel(NEON / SSE / AVX / RVV intrinsic 或 .S 汇编)、新增一层 ISA、改 pack mode 或 tile 参数、把 kernel 挂进函数表时使用。已定位到 kernel 才进本分支。

日本語の概要は準備中です。原文の説明を表示しています。

alibaba/MNN1.6万2026年10月10日 更新

MNN CPU 后端性能归因分支(`skills/cpu/` 下,另一分支是 `cpu/kernel` kernel 开发)。按五层(Runtime 线程 / Executor 调度 / Layout 内存 / Dispatch 函数表 / Kernel ISA)定位瓶颈,含 bound 类型判定、op 级实验回路、跨框架 op 对 op 对比、跨层不一致的事后定位,以及 ARM / x86_64 / RISC-V 三侧「我到底跑在哪条 ISA 路径上」的诊断面。CPU 上算子或 LLM prefill/decode 慢、线程数或内存占用异常、出现性能回归、要与外部推理框架逐算子对比时使用。

日本語の概要は準備中です。原文の説明を表示しています。

alibaba/MNN1.6万2026年10月10日 更新

MNN Hexagon/HVX/HMX DSP 后端(`source/backend/hexagon`)的优化、重构、构建与回归验证。覆盖设备实测的相位分解、测量纪律、v79/v81 双架构差异、常见瓶颈模式、已否证方向与 cDSP crash 诊断。

日本語の概要は準備中です。原文の説明を表示しています。

alibaba/MNN1.6万2026年10月10日 更新

alibaba のスキルをすべて見る

このスキルの問題を報告する