OpenCV DNN 的 MLAS 集成:vendored SGEMM 与 FlashAttention 内核的裁剪、补丁与构建全解
发布时间:2026/9/7 23:04:38 锦皓数字建站

OpenCV DNN 的 MLAS 集成vendored SGEMM 与 FlashAttention 内核的裁剪、补丁与构建全解【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv本文基于 3rdparty/mlas/README.md 展开系统讲解 OpenCV 如何把 ONNX Runtime 的 MLASMicrosoft Linear Algebra Subprograms数学内核库裁剪为一个仅含 SGEMM 与 FlashAttention 的 vendored 子集并将其编入opencv_dnn模块。读完后你能掌握vendored 源码子集的取舍逻辑、五处本地补丁含线程层替换为cv::parallel_for_的具体内容、CMake 架构探测与编译宏HAVE_MLAS、MLAS_GEMM_ONLY等的作用机制以及 DNN 各层GEMM/MatMul/Attention/SDPA调用 MLAS 的完整入口与回退路径。1. MLAS 是什么vendored 了哪些内容MLAS 是一个包含处理器优化 GEMM 内核与平台特定线程代码的计算库是 ONNX Runtime 内部默认使用的数学内核库见 README 原文。OpenCV 引入它的目的是为dnn模块的前向计算提供两条高性能路径单精度 SGEMM覆盖全连接层、MatMul、线性投影和融合多头注意力FlashAttention覆盖 Attention/SDPA 层而不必把整个 MLAS 库拖进来。1.1 版本溯源README 完整记录了上游溯源信息便于日后重新 vendoring 时对齐上游来源ONNX Runtime 的onnxruntime/core/mlas/导入日期2026-05-04上游 commit62f742f1aa0c3102745ed35e3d869eaee845b9ac2026-04-30导入时main上最后一次触碰 MLAS 的提交随 ORT v1.26.0 发布许可证MIT许可证文本见 LICENSE。1.2 只导入 SGEMM 子集与 MlasFlashAttentionREADME 明确说明vendored 的是SGEMM单精度 GEMM子集加上MlasFlashAttention融合多头注意力其余部分量化 GEMM、卷积、FP16-dispatch SoftMax 等全部排除——这样 DNN 模块拿到快的 SGEMM 与 FlashAttention 路径而不会引入整库的构建负担。逐文件来看文件作用lib/sgemm.cppSGEMM 调度与 host 侧粘合代码lib/compute.cppsoftmax / exp / row-max / sum-exp 内核。实际只运行MlasReduceMaximumF32Kernel与MlasComputeSumExpF32Kernel的可移植 C 回退版本没有导入任何按架构特化的.Ssoftmax 内核文件中 FP16/GQA 模板特化会编译但永不运行lib/flashattn.cppMlasFlashAttention/MlasFlashAttentionThreaded入口依赖sgemm.cpp中的MlasSgemmOperation和上述两个可移植内核lib/softmax.hcompute.cpp引用的头文件仅含纯 FP16-dispatch typedef在纯 FP32 构建下无害lib/arch/各架构的 SGEMM 内核见第 4 节顶层布局为inc/ — 公开 MLAS 头文件inc/mlas.h、inc/mlas_float16.h从上游逐字保留lib/ — 实现除第 3 节列出的本地补丁外逐字保留上游按架构放在子目录x86_64/、aarch64/、arm/、power/、riscv64/、loongarch64/、s390x/、sve/、kleidiai/CMakeLists.txt — OpenCV 侧构建粘合构建 OBJECT 库opencv_dnn_mlas其目标文件直接链接进opencv_dnnthreading_opencv.cpp — 对上游lib/threading.cpp的 OpenCV 侧替换见第 3 节携带 OpenCV 许可证头。1.3 版权说明大部分文件 © Microsoft CorporationMIT 授权。lib/中部分上游贡献保留额外的 MIT 版权头README 指出其中两个来源lib/kleidiai/mlasi_kleidiai.h© Arm Limited 2025与erf_neon_fp16.{h,cpp}、gelu_neon_fp16.{h,cpp}© FUJITSU LIMITED 2025与 Microsoft 联合。OpenCV 自己编写的三个文件CMakeLists.txt、threading_opencv.cpp、README采用 OpenCV 顶层 Apache 2.0 许可。2. DNN 模块如何接入 MLAS在 modules/dnn/CMakeLists.txt 中DNN 模块的处理逻辑是先把HAVE_MLAS置 0并清空OPENCV_DNN_MLAS_ENABLED、OPENCV_DNN_MLAS_SKIP_REASON等缓存变量防止上一次配置残留add_subdirectory(3rdparty/mlas)——只有当 3rdparty/mlas/CMakeLists.txt 成功走完时才会在父作用域设置HAVE_MLAS 1、MLAS_OBJECTS与MLAS_INCLUDE_DIRS见 CMakeLists.txt 尾部若HAVE_MLAS为真add_definitions(-DHAVE_MLAS1)并加入头文件目录打印 “DNN: MLAS (vendored). enabled.”否则打印 “disabled — host arch/OS not wired up.”最后通过ocv_glob_module_sources(... ${MLAS_OBJECTS})把 OBJECT 库的目标文件并入opencv_dnn的编译列表modules/dnn/CMakeLists.txt#L575。因此HAVE_MLAS就是编译期总开关它驱动 mlas_gemm.hpp 提供真实实现或全部返回 false 的内联桩见第 6 节。3. 五处本地补丁re-vendor 时必须重放README 的 “Local patches against upstream” 一节是维护这套 vendored 代码的关键每一处对上游的修改都在 patches/ 下存有 unified-diff 形式与 3rdparty/zlib/patches 同一约定重新导入上游后执行git apply --directory3rdparty/mlas patches/*.diff即可恢复。五处修改逐一说明3.1 丢弃 lib/threading.cpp改用 cv::parallel_for_上游lib/threading.cpp的三个线程入口MlasExecuteThreaded、MlasTrySimpleParallel、MlasTryBatchParallel被整体移除不 vendor改为在 modules/dnn/src/layers/cpu_kernels/mlas_threading.cpp 中基于cv::parallel_for_重新实现。从源码结构看这个替换是必要且巧妙的上游线程文件在BUILD_MLAS_NO_ONNXRUNTIME且ThreadPool为 nullptr 时退化为串行 for 循环更关键的是 MLAS 内部会调用MlasGetMaximumThreadCount()决定分区数standalone 构建下它返回 1——即使MlasTrySimpleParallel是并行的最终也只发射一次迭代所以补丁同时修了两半入口函数改走cv::parallel_for_mlas_threading.cpp#L44-L96对Iterations 1直接同步执行否则切cv::Range分片加上 3.2 中让线程数查询走 OpenCV。由于该文件被#if defined(MLAS_OPENCV_THREADING)包裹mlas_threading.cpp#L24它只编译进opencv_dnn_mlasOBJECT 库当opencv_dnn的递归 src glob 捡到它时因该宏不在其编译作用域内而成为空文件避免重复符号。文件内还提供了一个针对MLAS_GEMM_ONLY的桩mlasi.h的MLAS_PLATFORM结构体以MlasGemmQuantDispatchDefault作为类内初始化器但该真实定义在未编译的qgemm_kernel_default.cpp里故此处声明一个零初始化实例保证可链接mlas_threading.cpp#L31-L36。3.2 mlasi.h线程数改由 OpenCV 提供头文件路径改写对应 patches/0001-mlasi-opencv-threading.diff#include core/mlas/inc/mlas.hORT 树内路径改写为#include ../inc/mlas.h因为 vendored 后该树内路径不存在当定义了MLAS_OPENCV_THREADING时MlasGetMaximumThreadCount()返回cv::getNumThreads()。补丁通过一个extern C int opencv_dnn_mlas_max_threads();自由函数间接调用避免把opencv2/core/utility.hpp拖进每个 MLAS 翻译单元实现即 mlas_threading.cpp#L38-L42对返回值做了n 0 ? n : 1的保底。这一补丁直接决定了第 6 节中 FlashAttention 的thread_count与 SGEMM 的 M 维分区都能按 OpenCV 的线程预算切分而不是 ORT 的或 standalone 的 1。3.3 platform.cppMLAS_GEMM_ONLY 裁剪构造函数对应 patches/0002-platform-gemm-only.diff是改动最大的一处非 SGEMM 的调度字段全部包进MLAS_GEMM_ONLY门禁使 SGEMM-only 子集无需其余 MLAS 源码即可构建文件顶部的erf_neon_fp16.h/gelu_neon_fp16.h头文件包含也被!defined(MLAS_GEMM_ONLY)门禁因为它们会传递引入未 vendored 的 FP16 源码fp16_common.h、softmax_kernel_neon.hMLAS_GEMM_ONLY下的替换构造函数只赋值sgemm.cpp实际读取的四个调度字段GemmFloatKernel、KernelM1Routinex86_64、KernelM1TransposeBRoutinex86_64、TransposePackB16x4Routinex86_64/loongarch同时把ReduceMaximumF32Kernel与ComputeSumExpF32Kernel指向compute.cpp中的可移植回退实现让MlasFlashAttention在没有按架构 softmax 内核的情况下也能工作原始完整 ORT 构造函数原样保留在#else分支中方便未来 re-vendor。从 diff 注释可以推断在这个构建里调用任何非 SGEMM / 非 FlashAttention 的 MLAS API 都是未定义行为这也与 README “只导入 SGEMM 子集”的边界一致。3.4 mlas.h 与两个 ORT 内部 shim对应 patches/0003-mlas-h-msc-ver-guard.diff把 inc/mlas.h 中的_MSC_VER检查包进defined()避免 GCC/Clang 下-Wundef告警。lib/core/common/narrow.h 与 lib/core/common/common.hMLAS 会调用的 ORT 内部头文件上游只作为 ORT include 存在MLAS 源码树里没有因此这是新增文件而非编辑无需.diff。4. 构建体系架构探测、内核选择与编译宏CMakeLists.txt 头部注释即是一份构建契约摘要vendored 子集 SGEMM MlasFlashAttentionlib/platform.cpp中非 SGEMM 调度行被#if 0掉搜索MLAS_GEMM_ONLY可见重新 vendor 后必须重放补丁。4.1 构建标志README 与 CMake 源码共同确认的编译定义HAVE_MLAS由 CMakeLists.txt#L269 以set(HAVE_MLAS 1 PARENT_SCOPE)交给 dnn 模块BUILD_MLAS_NO_ONNXRUNTIME1、MLAS_OPENCV_THREADING1、MLAS_GEMM_ONLY1作为私有编译宏挂在 OBJECT 库上CMakeLists.txt#L243-L247另有OPENCV_DNN_MLAS_ENABLED/OPENCV_DNN_MLAS_SKIP_REASON两个内部状态变量CMakeLists.txt#L66-L72用于在顶层配置摘要里标记 MLAS 是否启用、未启用时给出人读原因。4.2 平台跳过与 ASM 编译器探测两条硬性跳过规则值得注意Windows 直接返回CMakeLists.txt#L74-L76——WIN32下不启用 MLASDNN 回退到内置 SGEMMASM 语言探测check_language(ASM)在 Android NDK 工具链下是 no-opNDK 为每个 ABI 预设了CMAKE_ASM_COMPILER随后enable_language(ASM)会在 NDK CMake 3.22.1 组合下于 generate 阶段失败。因此需要汇编的架构x86_64/x86/aarch64/loongarch64在无 ASM 编译器时直接跳过写入 skip 原因并告警CMakeLists.txt#L78-L104而 Android armv7a 仍可通过纯 C 的sgemmc.cpp路径启用。4.3 各架构内核与编译标志架构探测逻辑CMakeLists.txt#L42-L62依据CMAKE_SYSTEM_NAME/CMAKE_SYSTEM_PROCESSOR设置MLAS_X86_64、MLAS_ARM64等标志随后按架构选源文件并逐文件设置 ISA 编译标志架构内核源文件关键编译标志x86_64SSE2、AVX含 M1 快速路径与TransposePackB16x4、FMA3、AVX512F 共 8 个.S文件CMakeLists.txt#L108-L131-msse2/-mavx/-mavx2 -mfma -mf16c/-mavx512f逐文件经set_source_files_properties指定32 位 x86x86/SgemmKernelSse2.S、x86/SgemmKernelAvx.SAndroid 额外加x86.get_pc_thunk.S-msse2、-mavxARM 32 位纯 C 的 arm/sgemmc.cpp无.S内核全局追加-mfpuneonaarch64aarch64/SgemmKernelNeon.S、aarch64/SgemvKernelNeon.S—POWERpower/SgemmKernelPower.cpp 恒编译若编译器支持-mcpupower10则追加SgemmKernelPOWER10.cpp与SgemmKernelPackA.SAIX 除外-DSINGLE、-O2 -mcpupower10loongarch64LSX 与 LASX 两档.S内核及TransposePackB16x4变体全局追加-mlsx -mlasxs390xs390x/SgemmKernelZVECTOR.cpp 等-DSINGLE、-mvx -mzvector -marchz15riscv64通过check_cxx_source_compiles以-marchrv64gcv -mabilp64d编译一段__riscv_vsetvl_e32m1测试代码探测 RVV支持则编译 riscv64/sgemm_kernel_rvv.cpp 与sgemm_pack_b_rvv.cpp否则只有标量回退逐文件-marchrv64gcv -mabilp64dWASM / 未知架构标量路径 scalar/SgemmKernelScalar.cpp—此外还有两处 GCC/Clang 专属的“胶水”修复CMakeLists.txt#L249-L267对.S汇编加-Wa,--noexecstack因为 MLAS 的.S文件缺少.note.GNU-stackC 侧用-include cstring强制包含platform.cpp的MlasInitAMX()调用syscall()却只包含sys/syscall.hglibc 在unistd.h声明它故在 x86/x86_64 非 Windows 下对该文件追加-include unistd.h。5. DNN 调用入口mlas_gemm 包装层README “Caller in OpenCV” 一节指出把 OpenCV GEMM 调度到 MLAS 的薄包装位于 mlas_gemm.hpp / mlas_gemm.cpp。它只包含公开头文件mlas.h且在 MLAS 不可用或请求的形状不受支持时回退到既有的 fast_gemm 路径。包装层提供 6 个 APImlas_gemm.hpp#L16-L93mlasAvailable()可用性探测。实现是一次性静态初始化调用MlasGetPreferredBufferAlignment()并要求对齐值落在(0, 256]区间mlas_gemm.cpp#L17-L23mlasSgemm()行主序C : alpha*op(A)*op(B) beta*C内部组装MLAS_SGEMM_DATA_PARAMS后调用MlasGemmmlas_gemm.cpp#L25-L56mlasSgemmBatch()批处理 SGEMMM/N/K 与 leading dim 全批共享用逐批元素偏移A_offsets/B_offsets/C_offsets从三个基址指针定位各批数据对应MlasGemmBatchmlas_gemm.cpp#L58-L96mlasSgemmPackBSize()/mlasSgemmPackB()/mlasSgemmPacked()B 矩阵“打包一次、多次复用”的三段式 API——先按(trans_a, trans_b, N, K)查询打包缓冲字节数由调用方分配并执行打包之后每次mlasSgemmPacked直接传入预打包 BBIsPacked true时ldb被置 0 忽略mlas_gemm.cpp#L98-L155mlasFlashAttentionBufferBytesPerThread()与mlasFlashAttention()见 5.1。所有函数在M 0 || N 0 || K 0或 MLAS 不可用时返回 false由上层触发 fast_gemm 回退HAVE_MLAS未定义时头文件提供全套返回 false 的 inline 桩mlas_gemm.hpp#L95-L118使调用方代码无需条件编译。5.1 FlashAttention 的内存布局与 scratch 计算mlas_gemm.hpp#L66-L93 的注释完整定义了语义计算output[b, i, h, :] softmax(scale * Q[b,h,i,:] K[b,h,:,:]^T) V[b,h,:,:]融合进单个 tiled 内核不物化q_seq × kv_seq 的注意力矩阵布局行主序、FP32 连续query为[batch, num_heads, q_seq_len, qk_head_size]key为[batch, num_heads, kv_seq_len, qk_head_size]value为[batch, num_heads, kv_seq_len, v_head_size]而output是[batch, q_seq_len, num_heads, v_head_size]——注意输出头维在序列维之后与输入不同scale通常为1/sqrt(qk_head_size)q_block_size/kv_block_size是 q、kv 序列方向的 tile 大小注释示例取 256scratch必须是调用方拥有的、至少thread_count * mlasFlashAttentionBufferBytesPerThread(...)字节的缓冲thread_count是 MLAS 工作线程数典型取值即cv::getNumThreads()。单线程 scratch 的公式实现为(q*(2 kv vd)) * sizeof(float)mlas_gemm.cpp#L157-L170注释解释了它对应flashattn.cpp中的排布l[q] m[q] intermediate[q*kv] temp_output[q*vd]row-max/row-sum 各一份、q×kv 中间注意力分块、q×vd 部分输出。mlasFlashAttention本身则把这些参数填入MlasFlashAttentionThreadedArgs并调用MlasFlashAttention(args, nullptr)mlas_gemm.cpp#L172-L211ThreadPool传 null——实际并行由第 3.1 节的cv::parallel_for_实现承担。5.2 DNN 各层对 MLAS 的落点从源码可以确认四个层的落点搜索mlasAvailable的结果GEMM 层gemm_layer.cpp 在权重侧走“预打包 B”快路径——当mlasAvailable() !opt.use_rvv时先mlasSgemmPackBSize查询缓冲分配后mlasSgemmPackB打包权重gemm_layer.cpp#L298-L313前向时对每个 batch 行块调用mlasSgemmPackedgemm_layer.cpp#L489。这与 5 节三段式 API 的设计意图吻合B权重打包一次多 batch 复用fast_gemm 回退路径fast_gemm.cpp#L604 在单 GEMM 处直接尝试mlasSgemm批处理路径fast_gemm.cpp#L715-L724先把lda约定翻译成 MLAS 的转置标志lda1 1即 A 视为行主、lda0 1即转置再调mlasSgemmBatch失败则回到 fast_gemm 内核Attention / SDPA 层attention_layer.cpp 与 sdpa_layer.cpp 引用了mlasFlashAttention路径即第 5.1 节的融合注意力入口MatMul 层matmul_layer.cpp 经由 fast_gemm 间接受益于 MLAS 的 SGEMM/BatchGEMM。6. 上游单元测试与许可边界README 最后说明SGEMM 内核的单元测试位于上游 ONNX Runtime 的onnxruntime/test/mlas没有被 vendor 进本仓库集成验证由 OpenCV 自己的 DNN 测试承担。这也给出了一个使用边界提示若需要针对 MLAS 内核本身的独立回归只能在上游语境中进行本仓库内的可信度依据是 DNN 模块测试的整体行为。综合来看这套 vendored MLAS 的完整工作链是modules/dnn/CMakeLists.txt触发 3rdparty/mlas/CMakeLists.txt 的架构探测 → 成功则生成opencv_dnn_mlasOBJECT 库上游platform.cpp/sgemm.cpp/compute.cpp/flashattn.cpp OpenCV 的 mlas_threading.cpp并定义HAVE_MLAS→ 编译期 mlas_gemm 提供 SGEMM/BatchGEMM/打包/FlashAttention 六个 API → 运行期 GEMM、MatMul、Attention、SDPA 层先探mlasAvailable()不可用或形状不支持时无缝回退 fast_gemm。任何一环失败Windows、Emscripten、无 ASM 编译器都不会使构建中断只会让 DNN 退回既有 SGEMM 路径这也是 README 中“host arch/OS not wired up”状态变量存在的原因。【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。