
ik_llama.cpp 修复 Termux/Android 构建IQK_API 符号可见性、ARM DOTPROD 特性与移动端 CPU 推理实践【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp本指南基于 ik_llama.cpp 仓库中 Pull Request #336《Fix termux/android build》的完整修复记录深入讲解该 PR 修复的 Android/Termux 构建失败根因Android 默认隐藏符号可见性导致链接器找不到 iqk 函数、IQK_API导出宏的设计、iqk_flash_attn_noalibi声明修复以及围绕__ARM_FEATURE_DOTPROD展开的移动端 CPU 推理可行性讨论。读完本文你将理解 iqk 内核在非 x86 平台上的编译开关机制掌握在 Android 设备上正确编译、运行 ik_llama.cpp 并开展基准测试的方法以及移动端量化模型推理的性能特点。一、PR #336 背景Termux/Android 构建失败的根源ik_llama.cpp 的核心竞争力之一是其高度优化的 iqk 矩阵乘法与量化内核。这些代码集中位于 ggml/src/iqk 目录。然而当贡献者saood06尝试在 Android 设备的 Termux 环境中构建该仓库时遇到了链接失败——在设备上能够复现编译错误。ikawrakow项目作者在 PR 讨论中直接点出了根因问题出在 Android 上iqk 函数没有指定可见性visibilityAndroid 默认使用隐藏可见性hidden visibility所以链接器找不到 iqk 函数。这是典型的 Android 平台符号导出问题Android NDK 编译共享库时默认采用-fvisibilityhidden所有未显式标注导出属性的函数符号都会被隐藏导致链接阶段无法解析对 iqk 内核函数的引用。该 PR 同时修复了 #159Termux 编译问题并在 github-data/issues/387bitnet 1.58 在 Termux 上段错误等后续 issue 中继续演进。二、核心修复一IQK_API符号可见性宏2.1 修复方案讨论针对隐藏可见性导致的链接失败项目作者给出了两个候选方案新增一个类似GGML_API的IQK_API宏直接复用GGML_API——因为 iqk 代码是作为ggml库的一部分编译的。贡献者saood06尝试了方案 2即 PR 中的 Attempt fix 3但未能成功最终选择了方案 1用独立的IQK_API宏进行了清理。2.2IQK_API宏的最终实现在代码评审阶段ikawrakow 给出了兼顾静态库与共享库场景的完整实现建议该建议已成为仓库中 ggml/src/iqk/iqk_config.h 的实际代码#ifdef GGML_SHARED # if defined(_WIN32) !defined(__MINGW32__) # ifdef GGML_BUILD # define IQK_API __declspec(dllexport) # else # define IQK_API __declspec(dllimport) # endif # else # define IQK_API __attribute__ ((visibility (default))) # endif #else # define IQK_API #endif该宏的语义分解如下编译场景IQK_API展开作用共享库 Windows非 MinGW 正在构建 ggml__declspec(dllexport)导出符号共享库 Windows非 MinGW 外部使用__declspec(dllimport)导入符号共享库 其他平台含 Android/Linux/macOS__attribute__((visibility(default)))强制符号对外可见覆盖 Android 的 hidden 默认值静态库未定义GGML_SHARED空无需可见性标注评审时作者特别强调要让它也能用于静态构建To have this also work for a static built因为静态库场景下不需要也不应引入 dll 导入导出的语义这正是#else分支置空的原因。2.3IQK_API在源码中的实际应用该宏被广泛标注在 iqk 内核的对外接口上既包括声明头文件也包括定义实现文件例如ggml/src/iqk/iqk_mul_mat.h 中声明了iqk_mul_mat、iqk_mul_mat_4d、iqk_mul_mat_moe、iqk_moe_fused_up_gate、iqk_dequant_type、iqk_fa_work_buffer_size、iqk_flash_attn_noalibi、iqk_topk_moe、iqk_fused_delta_net、iqk_indexer_topk等函数ggml/src/iqk/iqk_mul_mat.cpp 中extern C IQK_API int iqk_dequant_type(int type, int Ny)以及iqk_mul_mat系列的实现均带extern C IQK_API前缀ggml/src/iqk/iqk_kda.cpp 与 ggml/src/iqk/iqk_cpu_ops.h 中iqk_kda的声明与定义同样使用IQK_API。extern C保证符号按 C 链接约定导出避免 C 名字修饰IQK_API则负责可见性/导入导出。两者组合使用确保 iqk 内核在 Android 等隐藏可见性平台上也能被链接器正确解析。三、核心修复二iqk_flash_attn_noalibi声明修复PR 还顺带修复了第二个编译问题iqk_flash_attn_noalibi的定义definition在IQK_IMPLEMENT未定义的情况下发生了变化。在 iqk 的架构中ggml/src/iqk/iqk_config.h 通过以下逻辑决定是否启用 iqk 实现#if defined IQK_IMPLEMENT #undef IQK_IMPLEMENT #endif #if defined __AVX2__ || defined __ARM_FEATURE_DOTPROD #define IQK_IMPLEMENT #endif也就是说只有 x86 的 AVX2 或 ARM 的 DOTPROD向量点积扩展可用时IQK_IMPLEMENT才会被定义。贡献者saood06的测试设备不支持__ARM_FEATURE_DOTPROD因此IQK_IMPLEMENT未被定义暴露了iqk_flash_attn_noalibi定义与声明不一致的问题。修复后的定义位于 ggml/src/iqk/iqk_flash_attn.cppextern C IQK_API bool iqk_flash_attn_noalibi(int type_q, int type_mask, float max_bias, int neq3, int neq2, long nbq3, long nbq2, int nek3, int nek2, long nbk3, long nbk2, int nev3, int nev2, long nbv3, long nbv2, int ne2, int ne1, long nb1, int int_type_k_in, // type of k int int_type_v, // type of v int Dk, // K head size int Dv, // V head size int neq1, // number of columns in q int nek1, // number of rows in k int stride_q, // distance between q columns in bytes int stride_k, // distance between k rows in bytes int stride_v, // distance between v rows in bytes int stride_m, // distance between mask rows (in bytes const void * q, // q matrix. const void * k, // k matrix. Assumed to be fp16, nq x nk elements const void * v, // v matrix. Assumed to be fp16, nq x nk elements const void * mask, // mask. If not null, assumed to be fp16. nq x nk elements const void * sinks, // mask. If not null, assumed to be fp16. nq x nk elements float scale, // scale applied before softmax float softcap, // if 0, a soft-cap operation is applied before softmax float * qkv, // v*softmax(scale*(k*q)) [[maybe_unused]] void * work_buffer_in, [[maybe_unused]] barrier_t barrier, [[maybe_unused]] void * barrier_data, ...);该函数的入口实现与文件整体一样被#if defined IQK_IMPLEMENT defined GGML_IQK_FLASH_ATTENTION守卫iqk_flash_attn.cpp只有同时满足CPU 支持 iqk 所需特性且编译时启用了 flash attention才会参与编译。PR 修复的正是它在IQK_IMPLEMENT未定义时的签名一致性问题并移除了重复的extern C IQK_API前缀评审中作者提出这里真的需要重复写extern C IQK_API吗贡献者随即修改。四、ARM 特性依赖__ARM_FEATURE_DOTPROD与ggml_vdotq_s32回退4.1 DOTPROD 是 iqk 在 ARM 上的硬门槛从 iqk_config.h 可见iqk 在 ARM 上的启用完全依赖__ARM_FEATURE_DOTPRODARMv8.2-A 起引入的点积指令扩展常见于支持dotprod的 armv8.2-a 或 armv9 内核。这是编译期宏由编译器根据-march/-mcpu参数自动定义因此在 Android 上能否启用 iqk 优化取决于你为构建指定的架构标志。4.2ggml_vdotq_s32无 DOTPROD 时的软件回退ikawrakow 在讨论中指出iqk 代码中一致地使用了ggml_vdotq_s32而 ggml 在__ARM_FEATURE_DOTPROD不可用时提供了实现。这在 ggml/src/ggml-impl.h 中得到验证#if !defined(__ARM_FEATURE_DOTPROD) inline static int32x4_t ggml_vdotq_s32(int32x4_t acc, int8x16_t a, int8x16_t b) { const int16x8_t p0 vmull_s8(vget_low_s8 (a), vget_low_s8 (b)); const int16x8_t p1 vmull_s8(vget_high_s8(a), vget_high_s8(b)); return vaddq_s32(acc, vaddq_s32(vpaddlq_s16(p0), vpaddlq_s16(p1))); } #else #define ggml_vdotq_s32(a, b, c) vdotq_s32(a, b, c) #endif // !defined(__ARM_FEATURE_DOTPROD)有 DOTPROD 时直接映射到硬件指令vdotq_s32没有时用vmull_s8vpaddlq_s16模拟点积累加。在 ggml/src/ggml-quants.c 等多处量化内核中ggml_vdotq_s32被大量用于 Q2/Q3/Q4/Q5/Q6 各类量化格式的矩阵乘累加。作者指出唯一缺少的 DOTPROD 替代实现是vdotq_laneq_s32按 lane 加载的点积变体。如果补齐它理论上就能在任意通用__aarch64__上启用 iqk而不再要求__ARM_FEATURE_DOTPROD。这一点在 PR 中并未落地但对理解 iqk 的 ARM 支持边界很有价值。4.3 IQK 的构建开关GGML_IQK_MUL_MAT除了架构特性宏iqk 代码是否编入构建还由 CMake 选项GGML_IQK_MUL_MAT控制。在 ggml/src/CMakeLists.txt 中iqk/iqk_quantize.cpp、iqk/iqk_cpu_ops.cpp始终在源文件列表中开启GGML_IQK_MUL_MAT后追加iqk_mul_mat.cpp、iqk_kda.cpp、iqk_flash_attn.cpp、fa/iqk_fa_*.cpp及全部iqk_gemm_*.cpp并定义GGML_USE_IQK_MULMAT即-DGGML_USE_IQK_MULMATflash attention 部分额外受GGML_IQK_FLASH_ATTENTION与GGML_IQK_FA_ALL_QUANTS控制后者决定 KV cache 是否支持 q4_0/q4_1/iq4_nl 等更多量化类型见 iqk_flash_attn.cpp。五、Android/Termux 实战构建、运行与基准测试5.1 两种 Android 构建路径仓库 docs/android.md 提供了两条 Android 构建路径均与 PR #336 的修复直接相关路径一Termux 内构建无需 rootapt update apt upgrade -y apt install git make cmake建议将模型文件放在~/目录以获得最佳性能Termux 的存储重定向会带来额外 IO 开销cd storage/downloads mv model.gguf ~/路径二Android NDK 交叉编译在 PC 上执行$ mkdir build-android $ cd build-android $ export NDKyour_ndk_directory $ cmake -DCMAKE_TOOLCHAIN_FILE$NDK/build/cmake/android.toolchain.cmake -DANDROID_ABIarm64-v8a -DANDROID_PLATFORMandroid-23 -DCMAKE_C_FLAGS-marcharmv8.4adotprod .. $ make注意这里显式传入-marcharmv8.4adotprod——正如 PR 所揭示的dotprod是激活 iqk 内核的关键架构标志。arm64-v8a是所有现代 64 位 Android 设备通用的 ABI而android-23对应 Android 6.0 的最低 API 级别。编译产物需要处理 Android 的权限模型sdcard 上无法修改文件权限因此要将可执行文件拷贝到 Termux 的 home 目录并添加执行权限$ cp -r /sdcard/llama.cpp/bin /data/data/com.termux/files/home/ $ cd /data/data/com.termux/files/home/bin $ chmod x ./*随后即可运行$ cd /data/data/com.termux/files/home/bin $ ./llama-cli -m ../model/model.gguf -n 128 -cml5.2 架构标志选择的实战经验来自 PR 实测PR 作者在真实设备上积累了两个关键经验armv9-a编译出的二进制可能触发非法指令即使设备 CPU 声称支持 armv9 指令集armv9-a构建仍可能出现 illegal instruction。改为armv8.2-adotprodfp16后可以正常运行必须手动指定架构标志不指定时构建出的二进制输出为乱码gibberish指定正确标志后编译时间显著变长——这恰恰说明iqk_mul_mat.cpp等 iqk 源文件真正进入了编译流程。5.3 移动端基准测试llama-sweep-bench 实测数据PR 作者在手机上1×3.00 GHz Cortex-X2 3×2.40 GHz Cortex-A710 的 SoC使用 4 线程、关闭 flash attention 跑出了当时的最佳结果命令为bin/llama-sweep-bench -m ~/ggml-model-iq2_bn_r4.gguf -t 4模型为IQ2_B量化1-bit 级的 BitNet 模型测试结果如下PPTGN_KVT_PP sS_PP t/sT_TG sS_TG t/s512128010.26149.905.13024.9551212851211.84043.246.44519.86512128102416.33631.346.92518.48512128153613.91436.807.68516.66512128204814.82534.548.16815.67512128256017.94028.548.69414.72512128307219.04026.898.91114.36512128358420.54924.929.31913.74数据解读要点prompt 处理PP短上下文约 50 t/s随 KV cache 增长N_KV 增加下降明显说明大上下文下的访存压力对移动端 CPU 影响显著token 生成TG从约 25 t/s 降至约 14 t/s同样呈现随上下文增长而衰减的规律作者强调这些数字在多次运行间波动极大即使使用taskset将进程固定在性能核上也无法稳定复现原因是系统调度器、热降频thermal throttling与核心分配等多种因素叠加。5.4 性能不稳定的根因讨论与 ARM 优化方向的启示PR 讨论后半段深入剖析了移动端性能波动与 iqk ARM 优化的适配问题值得 iqk 使用者与移植者关注热降频与调度作者回顾了自己约 10 年前的移动端数值计算实验——手机满载一两分钟后性能就会彻底崩溃。现代 SoC尤其是该测试设备的 SoC以热降频著称即使设备已经发热性能仍可能大幅波动这让跨后端对比如 CPU vs Vulkan难以得出可靠结论寄存器溢出register spillageikawrakow 指出其 ARM 优化完全基于 Apple M2 芯片调优常常使用超过可用数量的向量寄存器。在 M2 Max 上寄存器溢出spill带来的代价反而低于少用向量寄存器但低端 ARM 处理器可能无法很好地处理这种取舍。这正是 iqk 内核在旗舰手机以外的 ARM 设备上可能不是最优的原因之一编译器差异作者使用的是 clangM2 开发环境并推测编译器未必能为低端芯片生成最优代码——PR 作者在设备上也只尝试过 clang结论iqk 在移动端尤其非 Apple 芯片仍属于能跑但未充分调优的状态作者因此考虑购入树莓派或 Rock 5B 开发板来系统性地支持移动/低端 ARM 设备。可以推断后续的 ARM 优化工作将围绕寄存器分配策略、编译器选择以及 DOTPROD 依赖的进一步放宽展开。5.5 移动端推理的价值判断与生态现状即便存在上述限制PR 讨论仍指出了 iqk 上 Android 的价值点CPU 推理可作为 Vulkan 的替代许多手机 GPU 算力有限而 Vulkan 后端的性能一般虽在持续改进。iqk 修复构建后在 CPU 上运行 ik_llama.cpp 成为移动端可行的备选方案BitNet 模型是移动端的理想展示场景1-bit 级量化模型对内存带宽和算力要求极低PR 作者明确表示 BitNet需移植是移动端的最佳契合点。相关生态问题可参考 github-data/discussions/401Termux 上安装 bitnet 等 CPU 模型的讨论与 github-data/issues/387bitnet 1.58 在 Termux 上段错误的排查跨后端对比的候选组合作者建议用 LLaMA-3B 的IQ4_XS/IQ4_KS或 BitNet 模型在 CPU、Vulkan 与 OpenCL 三个后端上对比。六、总结与可验证结论PR #336 是 ik_llama.cpp 移动端支持的关键一步其核心结论可归纳为Android 构建失败的根因是共享库默认隐藏符号可见性而非计算代码本身的问题IQK_API宏ggml/src/iqk/iqk_config.h通过__attribute__((visibility(default)))与 Windows 的dllexport/dllimport分支同时覆盖共享库与静态库场景与extern C组合标注在 iqk_mul_mat.h、iqk_mul_mat.cpp、iqk_kda.cpp 等所有对外接口上iqk_flash_attn_noalibi的声明修复保证了IQK_IMPLEMENT未定义时也能编译通过ARM 上启用 iqk 的硬性前提是编译时具备__ARM_FEATURE_DOTPROD无该特性时ggml_vdotq_s32提供软件回退ggml/src/ggml-impl.hvdotq_laneq_s32是尚未补齐的最后一块拼图移动端实测表明 ik_llama.cpp 已能在 Android 上运行并产出合理结果IQ2_B 模型约 25 t/s 生成速度但性能受热降频与调度影响波动较大且 iqk 的 ARM 优化目前以 Apple M2 为基准对低端 ARM 芯片的适配仍在演进中。对于希望在移动设备上使用 ik_llama.cpp 的开发者正确的构建姿势是使用 NDK 或 Termux 工具链为arm64-v8a目标显式指定-marcharmv8.2-adotprodfp16而非激进的 armv9-a开启GGML_IQK_MUL_MAT让 iqk 内核进入编译再用llama-sweep-bench结合taskset固定核心进行基准测试并对多次运行间的性能波动保持预期。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考