MNN Vulkan 新特性集成实战:Cooperative Matrix / Subgroup 扩展从示例代码到落地
【免费下载链接】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 中「方向 C:集成 Vulkan 新特性」这一独立优化轨道,完整讲解如何把用户提供的 Vulkan 新特性示例代码(如 VK_KHR_cooperative_matrix、GL_KHR_shader_subgroup_arithmetic 等扩展)适配进 MNN Vulkan 后端,覆盖输入收集、示例理解、兼容性评估、shader 适配、特性检测、fallback 设计与多设备验证全流程。读完你将掌握一条可复用的新特性集成流水线,并能基于VulkanDevice的设备能力查询接口(coopMat / subgroup)在 MNN 中安全地启用 GPU 新特性而不错伤不支持的设备。
一、新特性集成轨道:定位与前置条件
在 MNN 的 Vulkan 优化技能体系中(见 skills/vulkan-optimize/SKILL.md),共有三个并行优化方向:方向 A「模型级优化」、方向 B「指定算子优化」,以及本指南所属的方向 C「新特性集成」。三者是并列的独立轨道,方向 C 的触发条件是:用户提供了 Vulkan 新特性示例代码或参考实现,并希望将其适配集成进 MNN(例如用 cooperative matrix 加速 GEMM、用 subgroup shuffle 免 barrier 等)。
方向 C 的参考文档即本指南(skills/vulkan-optimize/new-feature.md),整体复杂度评估为中-高,其核心约束有两个:
- 必须有示例代码。集成新特性的前提是用户提供
.compshader + host 代码,或一个完整可运行的 demo。没有示例时应当主动要求补上——不同 GPU vendor 对同一特性的行为可能完全不同,没有参考实现就无从对齐语义。 - 必须设计 fallback。新特性(尤其 coop matrix)目前主要只在 Adreno 上可用,集成时必须保证不支持的设备(Mali、Apple、旧驱动)回退到原路径且功能正确。
开始前必须确认以下输入清单,缺任何一项都需主动要求补齐:
□ 示例代码(.comp + host,或完整可运行 demo) □ 特性说明:解决什么问题?(如 coop matrix 加速 GEMM、subgroup shuffle 免 barrier) □ 目标算子:用在 MNN 哪个算子? □ 目标平台:哪些 GPU 支持?(Adreno / Mali / Apple;coop 目前主要 Adreno) □ 预期收益:性能 / 精度 / 功能?二、第一步:理解示例代码
拿到示例后,需要同时从 Host 端与 Device 端两个层面拆解,才能判断它能否映射到 MNN 的既有抽象上。
2.1 Host 端要回答的问题
- 用了哪些 Vulkan API / 设备能力查询?pipeline / descriptor / push constant 是怎么构建的?
local_size/ dispatch grid 如何配置?spec constant 传了什么值?
2.2 Device 端(.comp)要回答的问题
- 用了哪些新内置(
coopMatMulAdd/subgroupAdd等)、新 layout(constant_id)、扩展 require? - 数据类型是
FLOAT宏还是裸类型?内存作用域 / 同步(memory_scope)如何处理?
2.3 关键逻辑
- 核心计算流程是什么;新特性在哪个环节起作用;示例自身是否带有 fallback?
重要提示:coop / subgroup 相关特性要求SPIR-V ≥ 1.3,因此编译示例时必须带
--target-env vulkan1.1。如示例可独立运行,应先在目标设备上验证示例本身正确(编译 +spirv-val校验),再谈集成——示例不成立,后面的一切都没有意义。
三、第二步:评估兼容性——一张表对齐 MNN 与示例
集成前必须逐项核对示例做法与 MNN 做法的差异,这是决定改动量的关键。下表是 MNN Vulkan 后端的标准适配维度:
| 适配维度 | MNN 做法 | 示例做法 | 改动 |
|---|---|---|---|
| 数据排布 | NC4HW4 | ? | ? |
| 数据类型 | FLOAT/FLOAT4(fp16/fp32 宏) | ? | ? |
| Buffer/Image | 编译期MNN_VULKAN_IMAGE | ? | ? |
| 内存管理 | vkBn->onAcquireBuffer/getMemoryPool | ? | ? |
| pipeline 构建 | vkBn->getPipeline(name, types, localSize, spec) | ? | ? |
| 参数传递 | descriptor set writeBuffer + push constant / uniform | ? | ? |
| local_size | getSubgroupSize()动态 | ? | ? |
3.1 Buffer / Image 是编译期二选一
MNN 的 Vulkan 后端分两棵完全独立的代码树,由编译期宏MNN_VULKAN_IMAGE决定:
MNN_VULKAN_IMAGE=OFF→source/backend/vulkan/buffer/*(LLM 全链路走 buffer);MNN_VULKAN_IMAGE=ON→source/backend/vulkan/image/*。
改 shader 前必须先grep MNN_VULKAN_IMAGE project/android/build_64/CMakeCache.txt确认当前构建走的是哪个后端,并保证改的路径与构建选的后端一致,否则改动不会生效。
3.2 设备特性检测:VulkanDevice是唯一入口
MNN 将设备能力查询集中在 VulkanDevice.hpp 中。与方向 C 直接相关的查询接口如下:
auto coop = vkBn->getDevice().getCoopMatInfo(); // supportCoopMat, selectedFP16CoopMatShape const auto& sg = vkBn->getDevice().getSubgroupInfo(); // size, stages, ops uint32_t sgSize = vkBn->getDevice().getSubgroupSize();从 VulkanDevice.hpp 的源码结构可以看到三组关键数据结构:
SubgroupInfo:size(subgroup 大小)、stages(支持的 shader stage)、ops(VkSubgroupFeatureFlags,如 arithmetic / ballot / shuffle 能力)、quadAllStages。getSubgroupSize()即返回mSubgroupInfo.size。CoopMatInfo:supportCoopMat(是否支持 cooperative matrix)、enabledCoopMatFeatures(启用的 feature 位)、fp32CoopMatShape/fp16CoopMatShape/s8CoopMatShape(各类型支持的全部 {M, N, K} 形状)、以及selectedFP32CoopMatShape/selectedFP16CoopMatShape/selectedS8CoopMatShape(选中的最优形状,{M, N, K}三元组)。其中supportS8S8S32专用于 W8A8 conv1x1 prefill 路径的 S8S8→S32 cooperative matrix。
这些数据来自 VulkanDevice.cpp 的checkCoopMat运行时探测流程:
- 通过
vkGetInstanceProcAddr获取vkGetPhysicalDeviceFeatures2(带 KHR 兜底); - 检查
VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME扩展是否存在,不存在直接返回; - 通过
VkPhysicalDeviceCooperativeMatrixFeaturesKHR查询cooperativeMatrixfeature 是否为VK_TRUE; - 调用
vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR枚举全部VkCooperativeMatrixPropertiesKHR,过滤scope == VK_SCOPE_SUBGROUP_KHR且非 saturating accumulation 的条目; - 按 A/B/C/Result 类型分别归类为 FP16(FLOAT16×FLOAT16→FLOAT16)、FP32(FLOAT32×FLOAT32→FLOAT32)、S8S8S32(SINT8×SINT8→SINT32)三类形状;
- 对每一类按
M*N*K总面积取最大者作为selected*形状; - 最后置
supportCoopMat = true,supportS8S8S32依据 s8 形状列表是否为空。
Fallback 必须设计:支持特性 → 走新路径;不支持 → 走原路径(保证功能正确)。以 coop matrix 为例,MNN 的通行 gate 条件是gpuType()==ADRENO && supportCoopMat(见下文 §5 的 conv1x1 选路源码)。
四、第三步:实施集成——五步递进,每步编译验证
集成过程严格按序逐步进行,每一步都要编译验证,避免问题堆积到最后无法定位。
第 1 步:特性检测
复用VulkanDevice已有的查询能力,必要时新增查询;在 host 代码中写if (supported) 建新 pipeline的分支逻辑。特性检测的正确性直接影响后续一切选路,务必先单测验证检测结果与真机实际能力一致。
第 2 步:shader 适配为 MNN.comp
把示例 shader 改写为 MNN 的.comp规范:
- 用
FLOAT/FLOAT4宏(fp16/fp32 自动切换),而不是裸类型; - 适配 NC4HW4 数据排布;
- 用 spec constant(
layout(constant_id=N))传 COOP_M/N/K、activation 等编译期可调参数; - 在头部写清扩展 require 与特性注释;
- coop / subgroup / memory_scope 必须进
--target-env vulkan1.1——这是最容易踩的陷阱(见 §7 常见问题表第一行),缺了它 glslangValidator 或 spirv-opt 会直接拒绝编译。
第 3 步:外科式重生成 shader 数组
MNN 的 GLSL 源不会被构建系统直接编译,运行时读的是AllShader.h/cpp中由makeshader.py(.comp→glslangValidator -V→spirv-opt -O→xxd嵌入)生成的 SPIR-V 字节数组(脚本位于 source/backend/vulkan/buffer/compiler/makeshader.py 等三处)。
⚠️严禁直接跑全量 makeshader:本机 glslang/spirv-opt 与仓库版本常不同,跑全量会把所有 shader 数组重编/重排,污染
AllShader.cpp(出现几万行无关 diff)。正确做法是只重生成改动的那几个数组:对每个改动的 shader(fp32 + fp16 变体),用与 makeshader 相同的管线单独编译,得到const unsigned char glsl_<name>_comp[]={...}; unsigned int glsl_<name>_comp_len=N;,再用脚本精确替换AllShader.cpp中对应段落。
新增 shader 还需要三处注册:
AllShader.h:追加 extern 声明;AllShader.cpp:替换/追加字节数组;VulkanShaderMap.cpp:把 name → 数组 的映射注册进 map。
以仓库中已有的 coop shader 为例,VulkanShaderMap.cpp 的注册形式为:
mMaps.insert(std::make_pair("glsl_attention_prefill_coop_qkv_comp", std::make_pair(glsl_attention_prefill_coop_qkv_comp,glsl_attention_prefill_coop_qkv_comp_len))); mMaps.insert(std::make_pair("glsl_attention_prefill_coop_qkv_FP16_comp", std::make_pair(glsl_attention_prefill_coop_qkv_FP16_comp,glsl_attention_prefill_coop_qkv_FP16_comp_len)));可以看到仓库里已经沉淀了attention_prefill_coop_qkv、attention_prefill_coop_qk、matmul_coop、matmul_coop_rm、dynamic_w8a8_coop_gemm、int4_weight_to_coop、int8_weight_to_coop、attention_prefill_coop_scale_oacc等 coop 系列 shader,说明这条集成流水线在仓库中已有大量实际落地样本可参照。
此外,新.comp文件必须在对应的macro.json中登记useFP16(决定是否生成_FP16变体),可选地声明macro列表(按宏名批量生成变体)。格式参见 buffer/execution/glsl/macro.json:
{ "blit.comp": { "useFP16": true, "macro": [ "C4" ] }, "reduce_int.comp": { "useFP16": false, "macro": [ "VMAX", "VMIN", "MEAN", "PROD", "SUM" ] } }改完确认无污染:grep -c 'glsl_<name>_comp_len' AllShader.cpp,并用git diff --stat确认 diff 中只有目标数组(无关_len行不应出现)。
第 4 步:host 选路
在onCreate/onEncode中写if (feature supported) 用新 pipeline else 原路径,两条路径都走 createSet + 绑定,用独立 pipeline/分支隔离,不要修改原 shader(防止 fallback 被破坏)。
仓库中 conv1x1 的选路是一个典型样本。在 VulkanConvolution.cpp 的onCreate中:
auto coopMatInfo = extra->getDevice().getCoopMatInfo(); const char* disableCoopMatEnv = getenv("MNN_VULKAN_DISABLE_COOPMAT"); const bool disableCoopMat = disableCoopMatEnv != nullptr && std::string(disableCoopMatEnv) == "1"; const bool useCoopMat = !disableCoopMat && coopMatInfo.supportCoopMat && supportSubgroupArithmetic && extra->gpuType() == VulkanRuntime::ADRENO; // CoopMat path only supports int4/int8 weight. For 2/3-bit, go to ...随后在useInt8Conv && is1x1分支内:perChannelAsym + S8S8S32→VulkanConv1x1CoopA8(W8A8 prefill 路径);否则 →VulkanConv1x1Coop(CoopMat 只支持 int4/int8);再 fallback 到VulkanConv1x1General(native int8/int4/int2/int3)。同时仓库还提供了MNN_VULKAN_DISABLE_COOPMAT=1环境变量作为运行时开关,便于不重编二进制就临时关闭 coop 路径做 A/B——这是新特性集成时可以复用的工程实践。
attention prefill 的选路则走 VulkanAttention.cpp:rearrange_q → init_state → (per k-block: qk → softmax → qkv) → finalize,coop QKV / subgroup softmax 由设备能力选路,每个onEncode决定本次 dispatch 哪些 shader。集成新特性时务必把目标 shape 代入选路逻辑确认真正落到新路径,避免"改了没被调度"。
第 5 步:编译验证
cd project/android/build_64 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注意:
make MNN不会连带重编 Vulkan 静态库,必须用make llm_demo;改 shader 后AllShader.cpp变了也会触发重编。
推送到真机并运行(换 shader 后必须先清 pipeline cache,否则 stale cache 直接 segfault):
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>"五、第四步:验证——正确性、性能、兼容性三管齐下
正确性:三层 oracle
- 数值层:dump tensor 对比;
- op 层:
MNNV2Basic.out单层对比; - 端到端:跑完整模型,关 sampler 随机性(
temperature:0.0, greedy),CPU/Vulkan 同 prompt 前 N token 应一致(fp16 误差内)。
数学等价的改动应与 baseline逐 token 一致。支持和不支持特性的设备都要测——fallback 路径同样必须验证正确。若 CPU oracle 不可用(低 bit 路径 CPU 本身就乱),用冻结 baseline 二进制做 GPU-baseline vs GPU-opt 的逐 token 贪心对比。注意 Mac MoltenVK 行为不代表 Android,最终验收必须 Android 真机。
性能:交替 A/B 而非顺序对比
手机 GPU 有两大测量陷阱:
- 冷启动首跑不算数:首跑含 auto-tune + pipeline 编译,清 cache 后第一次仍是冷的,先 warm 再测;
- 跨时段热漂移约 ±8–10%:先测 base 再测 opt 不可比,必须交替 A/B——base 和 opt 两份二进制(或同一二进制不同 env)背靠背配对:
base→opt→base→opt,看每轮配对里 opt 是否稳定胜出。
换 shader 做 A/B 时每轮跑前rm tmp/mnn_cachefile.bin(tok/s 不含 load,清 cache 只影响 load 时间,A/B 仍公平)。带MNN_GPU_TIME_PROFILE=ON的 build 只用于看相对占比(会放大绝对耗时并改变调度),最终收益以不带 profile 的干净 build 端到端 tok/s 为准。
兼容性
- 全量 op 回归,确保无回归;
- 不支持特性的设备(Mali / 旧驱动)走 fallback 无性能退步。
六、第五步:文档与提交
在 shader 头部注释中写明:特性 / 适用设备 / 原理 / fallback。提交信息遵循仓库约定格式:
[Vulkan:Feature] Add <特性> for <算子> - Runtime feature detection via VulkanDevice - New shader using <特性>, --target-env vulkan1.1 - Fallback path for unsupported devices (Mali/old driver) - Tested on <设备>, <加速比>七、通过标准与常见问题排查
通过标准
- 示例充分理解(能解释原理/核心逻辑)
- 特性检测正确(runtime 能判设备是否支持)
- shader 适配 MNN(FLOAT 宏 / NC4HW4 / spec constant / target-env)
- fallback 存在且正确(不支持设备走原路径)
- shader 外科式重生成 + 三处注册,AllShader.cpp 无污染
- 支持 + 不支持设备都验证通过
- 有交替 A/B 性能数据
常见问题速查表
| 问题 | 原因 | 修复 |
|---|---|---|
| shader 编译失败 / spirv-opt 拒绝 | 缺--target-env vulkan1.1 | coop/subgroup/memory_scope 必带 |
| 运行 segfault | pipeline cache stale / descriptor layout mismatch | rm mnn_cachefile.bin;核对 binding/push constant size host↔shader |
| 支持设备上性能反降 | 特性 overhead > 收益(如 coop K 维太小) | 限制特定 shape 才走新路径(如 coop-QK headDim=128 属负收益) |
| fallback 被破坏 | 改动影响原路径 | 用独立 pipeline/分支隔离,别改原 shader |
| 数值乱 | 用错 layout(coop 只 int4/8) | dispatcher 显式 gate(见 conv1x1 选路中isLowBit23判断) |
八、更多参考
- Vulkan 优化 Skill 总览:方向 C 与方向 A/B 的关系、编译与真机运行、shader 修改流程(防污染)、正确性三层 oracle、交替 A/B 方法论。
- 优化手册:coop matrix、subgroup 归约、epilogue 融合等技巧与陷阱的唯一权威来源(§1 性能分析方法论、§2 优化技巧、§3 常见陷阱、§4 Packed weight 设计、§5 速查表、§6 候选方向)。
- 基准建立、kernel 优化、集成回归:方向 A/B 的配套流程。
- 源码参考:VulkanDevice.hpp(特性查询接口)、VulkanDevice.cpp(coopMat 运行时探测)、VulkanConvolution.cpp(conv1x1 coop 选路样本)、VulkanShaderMap.cpp(shader 注册样本)、macro.json(变体登记格式)。
【免费下载链接】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),仅供参考