ARTICLE DETAIL

资讯详情

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

ik_llama.cpp iqk_mul_mat.cpp 重构拆分解读:将 18 kLOC 巨型单文件变为秒级并行编译的 GEMM/FA 内核架构

ik_llama.cpp iqk_mul_mat.cpp 重构拆分解读:将 18 kLOC 巨型单文件变为秒级并行编译的 GEMM/FA 内核架构 ik_llama.cpp iqk_mul_mat.cpp 重构拆分解读将 18 kLOC 巨型单文件变为秒级并行编译的 GEMM/FA 内核架构【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp本文以 ik_llama.cpp 项目的 PR #435Refactor iqk_mul_mat.cpp作者 ikawrakow2025-05-20 创建为线索剖析该项目 CPU 端矩阵乘法GEMM与 Flash AttentionFA内核的一次关键工程重构将约 18 kLOC 的重度模板化单文件拆分为按量化族划分的多个编译单元使 iqk 内核目录的完整构建时间从高端 CPU 超过 2 分钟、部分 Android 用户实测 30 分钟下降到约 13~17 秒。读完本文你将掌握 ik_llama.cpp 中iqk内核目录的文件职责划分、CMake 构建开关的用法、GEMM/FA 内核的模板化设计思路以及这次重构带来的工程经验与遗留问题。一、重构背景一个巨型模板单文件成为编译瓶颈在本次重构之前ik_llama.cpp 把 CPU 端的矩阵乘法GEMM内核与 Flash AttentionFA内核全部塞进同一个源文件iqk_mul_mat.cpp。随着项目不断加入新的量化类型与加速内核该文件膨胀到约18 kLOC内部充满了重度模板化的 C 代码——同一个模板函数针对不同量化类型k-quants、i-quants、IQX_K、1-bit 量化、legacy quants 等以及不同的 SIMD 指令集组合进行实例化编译器需要反复展开模板并生成海量代码。由此带来的直接代价是编译时间失控在高端桌面 CPU如 Ryzen上单文件编译耗时超过 2 分钟有用户报告在 Android 手机上编译耗时达到30 分钟编译还难以利用多核并行——单文件编译本质上只能由少数编译线程处理正如 PR 对话中用户 saood06 指出它无法用满你的 48 个核因为必须先编出 libggml.so。这个问题在仓库的 issue 体系中也有对应记录PR 描述中明确 Closes #183即 github-data/issues/183 - Refactor_ update ggml library_.md。重构的动机非常纯粹把模板实例化的爆炸性工作量按主题切分到多个独立编译单元让它们能够并行编译从而把构建时间压缩到秒级。二、拆分方案按量化族划分 GEMM、按头尺寸划分 FAPR #435 的核心动作是把iqk_mul_mat.cpp拆分为多个职责单一的文件。这些文件在当前仓库中已全部落地位于 ggml/src/iqk/ 目录下。下表对照 PR 描述中的划分与当前仓库的真实文件文件相对仓库根路径职责ggml/src/iqk/iqk_gemm_floats.cpp作用于float 张量fp16/fp32的 GEMM 内核ggml/src/iqk/iqk_gemm_1bit.cppBitNet以及IQ1_S、IQ1_M含 repacked 变体的 GEMM 内核ggml/src/iqk/iqk_gemm_kquants.cppk-quants与 repacked k-quants 的 GEMM 内核ggml/src/iqk/iqk_gemm_iquants.cppi-quants与 repacked i-quants 的 GEMM 内核ggml/src/iqk/iqk_gemm_iqk_quants.cppIQX_K系列及 repacked 的 GEMM 内核ggml/src/iqk/iqk_gemm_legacy_quants.cpplegacy quants如Q4_0等及 repacked 的 GEMM 内核ggml/src/iqk/iqk_mul_mat.cpp仅保留GEMM 业务逻辑内核调度、行分块、MoE 路由等编译显著加快ggml/src/iqk/fa/iqk_fa_templates.hFA 内核的模板头文件被各 FA 实例化源文件 includeggml/src/iqk/fa/iqk_fa__.cpp针对特定K/V attention head 尺寸组合的 FA 模板实例化文件从当前仓库的实际行数可以直观感受拆分的效果iqk_mul_mat.cpp现在只有约 2 384 行ggml/src/iqk/iqk_mul_mat.cpp而模板实例化的重活被分散到了多个 1 000~6 000 行的独立编译单元中例如iqk_gemm_iqk_quants.cpp约 5 933 行、iqk_gemm_kquants.cpp约 4 902 行、iqk_gemm_iquants.cpp约 4 078 行。此外拆分后的目录在后续演化中还追加了 PR 描述中未列出的文件例如 ggml/src/iqk/iqk_gemm_ktquants.cpp对应另一类 kt-quants 量化格式的 GEMM 内核、ggml/src/iqk/iqk_flash_attn.cpp、ggml/src/iqk/iqk_kda.cpp 等说明这套按主题拆分的目录结构在重构之后保持了良好的扩展性。FA 模板实例化文件按 K/V 注意力头尺寸组合命名当前仓库中共有 9 个iqk_fa_64_64.cpp、iqk_fa_96_96.cpp、iqk_fa_128_128.cpp、iqk_fa_192_128.cpp、iqk_fa_192_192.cpp、iqk_fa_256_256.cpp、iqk_fa_320_256.cpp、iqk_fa_512_512.cpp、iqk_fa_576_512.cpp它们共同构成 ggml/src/iqk/fa/ 目录。以 ggml/src/iqk/fa/iqk_fa_128_128.cpp 为例每个实例化文件只有约 45 行includeiqk_fa_templates.h后通过宏IQK_FA_CASE定义入口并在内部按nk是否整除 128/64/32 选择不同 tile 尺寸的iqk_flash_helper_T128, 128, BLOCK模板实例同时针对__AVX512BF16__提供 bf16 K-cache 的特化路径。模板本体只写一次在iqk_fa_templates.h实例化拆分到多个小文件并行编译这就是 FA 部分能够并行化的关键。三、编译时间的量化收益PR 作者在描述中给出了拆分完成后、并行编译iqk目录的实测数据2025-05 时的环境平台指令集全新编译耗时Ryzen 7950XZen4~17 秒Ryzen 5975WXAVX2~15 秒Apple M2 MaxARM_NEON~13 秒作者还解释了差异来源Zen4 编译时间更长是因为它额外编译了另外两个平台不原生支持的bf16 内核。单个 GEMM 文件只需 5~6 秒编译FA 实例化文件才是构建时间的绝对主导——作者也表示未来可以继续拆分 FA 文件但 15 秒左右的编译时间已经完全可以接受。用户侧的反馈同样印证了收益。PR 对话中 saood06 报告在其双路 Xeon E5-2690 v3 机器上使用cmake .. -DGGML_RPCON -DGGML_IQK_FA_ALL_QUANTS1; cmake --build . --config Release -j 48构建编译时间从~18 分钟下降到 ~7 分钟且整体 CPU 利用率更高虽然仍无法用满 48 核因为必须先编译 libggml.so。该用户同时提到 llama.cpp 本体编译也是耗时大头但iqk_mul_mat.cpp在重构前确实占用了构建时间的绝大部分。四、构建配置如何启用与验证这套内核拆分后的 GEMM/FA 内核在 ik_llama.cpp 的 CMake 体系中通过三个开关控制均位于 ggml/CMakeLists.txtGGML_IQK_MUL_MAT使用优化后的 iqk 矩阵乘法内核默认 ONL115GGML_IQK_FLASH_ATTENTION启用 IQK 的 CPU FlashAttention 内核默认 ONL141GGML_IQK_FA_ALL_QUANTS为 IQK FlashAttention 编译全部量化类型的内核默认 ONL142。在 ggml/src/CMakeLists.txt 中可以看到这三个开关如何组织源码当GGML_IQK_MUL_MAT开启时iqk_mul_mat.cpp、iqk_kda.cpp、iqk_flash_attn.cpp、全部fa/iqk_fa_*_*.cpp以及六个iqk_gemm_*.cpp会一起加入GGML_SOURCES_IQK_MM同时定义GGML_USE_IQK_MULMAT宏若GGML_IQK_FLASH_ATTENTION开启则追加定义GGML_IQK_FLASH_ATTENTION并视GGML_IQK_FA_ALL_QUANTS决定是否定义GGML_IQK_FA_ALL_QUANTS控制 FA 是否编译所有量化类型关闭可进一步缩短编译时间。值得注意的硬件前提iqk 内核并非对所有 CPU 架构生效。ggml/src/iqk/iqk_config.h 中IQK_IMPLEMENT决定 iqk 实现是否被编译进目标仅在__AVX2__或__ARM_FEATURE_DOTPROD之一成立时定义——即只有支持 AVX2 的 x86 平台和支持 DOTPROD 扩展如 ARMv8.2的 ARM 平台才会启用这套内核且同一文件L42-L63显示在 x86 上还会根据是否具备 AVX-512AVX512FVNNIVLBWDQ与 AVX-VNNI 进一步选择HAVE_FANCY_SIMD/HAVE_VNNI256等高级指令路径。因此在较老的 x86 CPU 上可能无法获得这套优化这一点在 PR 描述中亦有呼应作者希望得到 CPU-only 推理用户、特别是非新架构用户的实际测试反馈。五、源码纵深GEMM 内核解包复用的设计思想拆文件解决的是工程可维护性问题而内核本身的高性能来自一套在 ggml/src/iqk/iqk_mul_mat.cpp 头部注释中明确记载的设计思想将 k-quants、i-quants 与 legacy quants 的解量化unpacking quants 与 block scales准备好用于与对应 Q8_X quants 做点积这一过程本身是耗时的。因此在 prompt processing 所需的QX × Q8_X矩阵乘法中可以复用同一份解包后的 QX quants 与 scales与多个 Q8_X 列相乘从而获得显著加速。也就是说量化矩阵乘法的瓶颈往往不在乘加本身而在把量化数据解包成适合 SIMD 点积的形式。PR 描述与源码共同指向的声明是这套实现对 k-quants、i-quants、legacy quants 的矩阵-向量/矩阵-矩阵乘法使 prompt processing 相比 mainline llama.cpp 快 150%~350%视量化类型而定fp16/fp32 矩阵乘法在 AVX2 上实现、fp16×fp16 在 ARM_NEON 上实现且浮点路径使用 tiling分块来提升缓存友好性。拆文件后业务逻辑与内核实例的边界变得更加清晰调度层iqk_mul_mat.cpp中的MulMat结构L57-L120持有按行数 nrc_y1..8 实例化的函数指针数组funcs[IQK_MAX_NY]IQK_MAX_NY在 ggml/src/iqk/iqk_common.h 定义为 8并在 nrc_y≥16 时走 16 行的批量函数func16同时对 x 方向按k_x_step 64分块调度自动把剩余的 y 行数拆分到不同的模板实例上——这是让模板实例化数量可控而非为每种行数都生成专用代码的关键设计数据层iqk_common.h定义了DataInfo源/目标行定位、MoE 场景的row_mapping间接寻址、mul_mat_t函数签名以及大量 SIMD 辅助AVX2/AVX-512/NEON 的点积、累加、转置、sign 处理等各iqk_gemm_*.cpp通过IQK_SET_MUL_MAT_FUNCTIONS系列宏把模板内核注册进调度表API 层ggml/src/iqk/iqk_mul_mat.h 对外暴露 C 接口包括iqk_mul_mat2D 矩阵乘、iqk_mul_mat_4d4D 张量批量乘、iqk_mul_mat_moeMoE 专家路由乘、iqk_moe_fused_up_gateMoE up/gate 融合、iqk_flash_attn_noalibiFlash Attention、iqk_topk_moe、iqk_fused_delta_net等供 ggml 后端统一调用。从源码结构看这次重构不只是物理拆文件还顺势把模板实例化注册与业务调度彻底解耦——之后新增量化类型时只需新增/修改对应的iqk_gemm_*文件并注册内核而不必再触碰调度主逻辑。六、测试与质量保障50 量化类型的覆盖PR 描述明确这是一次大规模变更massive change。作者对50 种量化类型含 row-interleaved quants在AVX2、Zen4、ARM_NEON三个平台上做了回归测试覆盖所有可能的内核组合同时公开呼吁 CPU-only 推理用户帮助做更多平台测试。从当前仓库看相关测试基础设施也保留了下来例如 tests/test-iq4-ks-kt-decode.cpp 这类针对特定量化族解码路径的测试以及 ci/run.sh 中的 CI 脚本可用于验证量化内核的正确性。仓库中与本次重构直接相关的档案还包括github-data/pull_requests/435 - Refactor iqk_mul_mat.cpp.md本文依据的原始 PR 文档以及被其关闭的 github-data/issues/183 - Refactor_ update ggml library_.md。七、遗留问题与后续一次值得记录的性能回归案例重构并非一帆风顺。PR 对话中用户 cmoncure 通过 git bisect 定位到 commitb94cd3b即本 PR 的合并提交导致其机器上DeepSeek 模型 TGtoken generation生成速度下降约 30%并提供了首个坏提交的哈希与提交信息。作者的回应是请其提交包含完整相关细节的 issue 以便跟进Please file an issue with all the relevant details。这一案例的工程启示值得记录纯结构重构也可能引入性能回归——即使指令集与算法未变文件拆分后编译器对跨编译单元的内联/优化决策会改变不同平台、不同模型上的结果可能不一致回归报告需要可复现的最小信息——模型、量化类型、硬件、构建配置、基准命令缺一不可git bisect 是定位首个坏提交的高效手段大规模重构必须预留足够的平台验证窗口——PR 作者坦言希望已覆盖所有可能组合但仍欢迎 CPU-only 用户补充测试。八、小结PR #435 是一次教科书式的巨型模板文件重构把 18 kLOC 的重度模板化单文件按量化族floats / 1-bit / k-quants / i-quants / IQX_K / legacy quants拆分为独立 GEMM 编译单元并把 FA 模板本体与按注意力头尺寸组合的实例化文件分离最终将iqk目录的全新并行编译时间从分钟级压到约 13~17 秒高端桌面/笔记本平台用户实测也从 ~18 分钟降至 ~7 分钟同时保持了 50 量化类型在 AVX2 / Zen4 / ARM_NEON 上的正确性。这套结构在当前仓库 ggml/src/iqk/ 中完整保留并继续演进后续追加了iqk_gemm_ktquants.cpp、iqk_kda.cpp等其模板只写一次、实例化按维度拆分并行编译的思路对任何面临模板实例化爆炸与构建时间失控的 C 项目都具有直接的借鉴价值。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表