3.1 LLM

本节介绍如何在 CIX P1 开发平台上运行本地大语言模型,主要包括 llama.cpp 和 MNN 两种推理路径。

本节使用 llama.cpp 和 MNN 社区版本进行部署。开发者可以从上游仓库获取当前最新源码,根据实际需要启用 CPU、KleidiAI、Vulkan 或 OpenCL 等能力。

由于 llama.cpp 和 MNN 均为持续更新的开源项目,不同版本的构建选项、程序名称、命令参数及后端支持可能发生变化。使用社区源码时,应记录实际使用的 Git Commit,并以当前版本的 README、构建说明和程序 –help 输出为准。

两种框架的主要区别如下:

路径

模型格式

适用场景

使用建议

llama.cpp CPU / KleidiAI

GGUF

首次跑通和基础文本生成

推荐优先验证

llama.cpp Vulkan

GGUF

Vulkan GPU 后端验证

可选,需确认 Vulkan Runtime

MNN CPU / KleidiAI

MNN 模型目录

MNN 模型加载和文本推理

使用经过验证的 MNN 模型

MNN OpenCL

MNN 模型目录

OpenCL GPU 后端验证

可选,以板端实际结果为准

推荐验证顺序如下:

llama.cpp CPU / KleidiAI
→ MNN CPU / KleidiAI
→ llama.cpp Vulkan
→ MNN OpenCL

建议先完成 CPU 路径验证,再验证 Vulkan 和 OpenCL。GPU 路径依赖 CIX GO 图形引擎、GPU 驱动及对应 Runtime,系统能够识别 GPU 并不代表推理框架一定已经成功使用 GPU。

3.1.1 llama.cpp

llama.cpp 是面向本地大语言模型推理的轻量级开源框架,主要用于运行 GGUF 格式模型。

本节介绍:

  • llama.cpp 社区源码获取和编译;

  • CPU / KleidiAI 推理;

  • CPU 线程数和 taskset 绑核;

  • Vulkan GPU 推理;

  • CPU 与 Vulkan 性能测试。

KleidiAI 是面向 Arm CPU 的优化微内核库,属于 llama.cpp CPU 后端的性能优化能力,并不是独立的硬件推理后端。

Vulkan 用于将部分或全部模型层卸载至 GPU。

3.1.1.1 准备环境

开始前,请确认开发板已经启动至赛事指定的 Debian 12 系统,并具备网络连接和足够的磁盘空间。

建议预留:

  • 模型存储空间:根据实际 GGUF 模型大小确定;

  • 源码和编译空间:建议至少预留 5 GB;

  • 运行内存:应大于模型文件、KV Cache 和运行时缓冲区所需空间之和。

安装基础依赖:

sudo apt update
sudo apt install -y \
git \
cmake \
build-essential \
wget \
ca-certificates \
util-linux

其中,util-linux 软件包提供后续绑核使用的 taskset 命令。

检查基础工具:

git --version
cmake --version
gcc --version
taskset --version

3.1.1.2 获取 llama.cpp 社区源码

创建工作目录:

mkdir -p ~/local-llm-test
cd ~/local-llm-test

获取社区源码:

git clone https://github.com/ggml-org/llama.cpp.git

进入源码目录:

cd ~/local-llm-test/llama.cpp

记录当前源码 Commit:

git rev-parse HEAD

建议将输出保存到文件:

git rev-parse HEAD \
| tee ~/local-llm-test/llama-cpp-commit.txt

如果源码目录已经存在,可更新至当前社区版本:

cd ~/local-llm-test/llama.cpp
git pull --ff-only

更新完成后再次记录 Commit,并重新执行编译。

3.1.1.3 编译 CPU / KleidiAI 版本

清理旧构建目录:

cd ~/local-llm-test/llama.cpp
rm -rf build-cpu

配置 Release 构建并启用 KleidiAI:

cmake -S . -B build-cpu \
-DCMAKE_BUILD_TYPE=Release \
-DGGML_CPU_KLEIDIAI=ON

检查编译配置:

grep "GGML_CPU_KLEIDIAI" \
build-cpu/CMakeCache.txt

预期输出包含:

GGML_CPU_KLEIDIAI:BOOL=ON

开始编译:

cmake --build build-cpu -j4

-j4 表示同时使用四个编译任务。

如果开发板内存充足,可以适当提高:

cmake --build build-cpu -j8

如果编译过程中出现进程被终止、Killed 或内存不足,应降低并行数:

cmake --build build-cpu -j2

检查主要编译产物:

ls -lh build-cpu/bin/llama-cli
ls -lh build-cpu/bin/llama-bench

查看程序版本和参数:

./build-cpu/bin/llama-cli --version
./build-cpu/bin/llama-cli --help
./build-cpu/bin/llama-bench --help

如果 llama-cli 和 llama-bench 均已生成,说明 CPU 版本编译完成。

3.1.1.4 准备 GGUF 模型

本节以 Qwen2.5-0.5B-Instruct-GGUF 的 Q4_K_M 量化模型作为基础验证模型。

该模型参数规模较小,适合优先验证模型下载、模型加载和文本生成链路。

创建模型目录:

mkdir -p ~/models/qwen2.5-0.5b
cd ~/models/qwen2.5-0.5b

模型页面:

点击此处跳转

下载 Q4_K_M 模型:

wget -c \
-O qwen2.5-0.5b-instruct-q4_k_m.gguf \
"https://modelscope.cn/models/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/master/qwen2.5-0.5b-instruct-q4_k_m.gguf"

检查模型文件:

ls -lh \
~/models/qwen2.5-0.5b/qwen2.5-0.5b-instruct-q4_k_m.gguf

进一步检查文件大小:

stat -c "%n: %s bytes" \
~/models/qwen2.5-0.5b/qwen2.5-0.5b-instruct-q4_k_m.gguf

如果模型文件只有几 KB,通常表示下载到了错误页面、网络连接中断或模型未完整下载。

删除异常文件后重新下载:

rm -f \
~/models/qwen2.5-0.5b/qwen2.5-0.5b-instruct-q4_k_m.gguf

如果赛事资源包已经提供经过验证的 GGUF 模型,建议优先使用赛事资源包中的模型,并将后续命令中的模型路径替换为实际路径。

设置模型路径变量:

MODEL=~/models/qwen2.5-0.5b/qwen2.5-0.5b-instruct-q4_k_m.gguf

确认变量指向有效文件:

ls -lh "$MODEL"

3.1.1.5 检查 CPU 拓扑并设置绑核范围

CIX P1 包含 8 个 Cortex-A720 核心和 4 个 Cortex-A520 小核。CPU 推理时建议绑定 Cortex-A720 核心,避免推理线程被调度到低性能核心,从而影响性能和测试稳定性。

查看 CPU 编号和最高频率:

lscpu -e=CPU,CORE,ONLINE,MAXMHZ,MINMHZ

在赛事指定的 CIX P1 Debian 12 环境中,CPU 核心对应关系如下:

CPU 编号

核心类型

最高频率

0、1

Cortex-A720 高性能核心

约 2.6 GHz

10、11

Cortex-A720 高性能核心

约 2.5 GHz

6、7

Cortex-A720 核心

约 2.3 GHz

8、9

Cortex-A720 核心

约 2.2 GHz

2–5

Cortex-A520 小核

约 1.8 GHz

推荐使用全部 8 个 Cortex-A720 核心进行 CPU / KleidiAI 推理:

CPU_LIST=0,1,6-11
THREAD_NUM=8

如需测试 4 核配置,绑定当前频率最高的 4 个 Cortex-A720 核心:

CPU_LIST=0,1,10,11
THREAD_NUM=4

检查当前设置:

echo "CPU_LIST=$CPU_LIST"
echo "THREAD_NUM=$THREAD_NUM"
taskset -c "$CPU_LIST" true

CPU_LIST 中的 CPU 数量应与 THREAD_NUM 保持一致。

-t 仅用于设置 llama.cpp 的 CPU 推理线程数,不能代替 taskset 的 CPU 亲和性设置。

3.1.1.6 运行 CPU / KleidiAI 推理

进入 llama.cpp 源码目录:

cd ~/local-llm-test/llama.cpp

推荐使用 taskset 固定 CPU 范围,并使线程数与绑定 CPU 数量保持一致:

taskset -c "$CPU_LIST" \
./build-cpu/bin/llama-cli \
-m "$MODEL" \
-p "请用一句话介绍你自己。" \
-t "$THREAD_NUM" \
-n 128

主要参数说明如下:

参数

说明

taskset -c

限制进程只能在指定逻辑 CPU 上运行

-m

GGUF 模型文件路径

-p

输入提示词

-t

CPU 推理线程数

-n

最大生成 Token 数

如果模型能够正常加载并输出文本,说明 llama.cpp CPU 推理链路基本可用。

不同 llama.cpp 版本在对话模板、交互模式和单轮运行参数方面可能存在差异。如果程序生成结束后进入交互状态,可输入:

exit

或按:

Ctrl+C

退出程序。

如果当前版本提供单轮运行参数,可通过以下命令查询:

./build-cpu/bin/llama-cli --help \
| grep -Ei "single|conversation|interactive"

不要直接假设不同版本均支持完全相同的单轮参数。

如果输出内容异常、出现大量重复文本或未按照指令回答,应检查:

  1. 模型是否完整;

  2. 当前版本是否识别模型内置 Chat Template;

  3. 是否启用了不合适的交互模式;

  4. 模型量化格式是否与当前版本兼容;

  5. 当前版本的 llama-cli –help 是否提供对应的对话参数。

3.1.1.7 验证 KleidiAI

保存完整运行日志:

taskset -c "$CPU_LIST" \
./build-cpu/bin/llama-cli \
-m "$MODEL" \
-p "请用一句话介绍你自己。" \
-t "$THREAD_NUM" \
-n 128 \
2>&1 | tee ~/local-llm-test/llama-cpu.log

检查日志中的 KleidiAI 信息:

grep -i "KLEIDIAI" \
~/local-llm-test/llama-cpu.log

启用 KleidiAI 后,日志中可能出现类似内容:

CPU_KLEIDIAI

GGML_CPU_KLEIDIAI=ON 表示编译时已经集成 KleidiAI 支持。

运行日志中出现 CPU_KLEIDIAI 相关信息,可以进一步说明当前运行过程加载了 KleidiAI CPU 后端。

由于社区版本可能调整日志格式,如果未搜索到 KleidiAI 字符串,应同时检查:

grep "GGML_CPU_KLEIDIAI" \
build-cpu/CMakeCache.txt
./build-cpu/bin/llama-cli --version

日志中没有出现 KleidiAI 信息并不一定代表程序运行失败,但不能仅凭模型成功输出判断 KleidiAI 已经实际生效。

正式性能验证时,应结合以下信息综合判断:

  • llama.cpp Commit;

  • CMake 配置;

  • 运行日志;

  • CPU 指令能力;

  • 相同模型和参数下的性能结果。

3.1.1.8 CPU 性能测试

使用 llama-bench 进行基础测试:

cd ~/local-llm-test/llama.cpp

先执行一次预热:

taskset -c "$CPU_LIST" \
./build-cpu/bin/llama-bench \
-m "$MODEL" \
-p 128 \
-n 32 \
-t "$THREAD_NUM" \
-r 1

正式测试:

taskset -c "$CPU_LIST" \
./build-cpu/bin/llama-bench \
-m "$MODEL" \
-p 512 \
-n 128 \
-t "$THREAD_NUM" \
-r 3 \
| tee ~/local-llm-test/llama-cpu-bench.log

主要参数说明:

参数

说明

-p 512

Prompt Processing 测试使用 512 个输入 Token

-n 128

Text Generation 测试生成 128 个 Token

-t

CPU 推理线程数

-r 3

每项重复测试三次

重点记录:

  • pp512;

  • tg128;

  • Token/s;

  • 线程数;

  • CPU affinity;

  • 模型名称及量化方式;

  • llama.cpp Commit;

  • 系统温度和后台负载。

CPU 绑核通常用于减少操作系统调度带来的波动,提高多次性能测试结果的一致性。

进行正式性能对比时,应固定:

  • 模型文件;

  • CPU 线程数;

  • CPU 绑定范围;

  • Prompt Token 数;

  • 生成 Token 数;

  • 系统电源状态;

  • 散热状态;

  • 后台进程负载。

3.1.1.9 可选:编译 Vulkan 版本

llama.cpp Vulkan 后端依赖 CIX GO 图形引擎提供的 GPU 驱动和 Vulkan Runtime。

执行本节前,应先确认 GO 图形引擎已经正常安装。随后按照本节命令安装

vulkan-tools,并使用 vulkaninfo 检查 Vulkan Runtime 和板载 GPU。

安装 Vulkan 编译和检查工具:

sudo apt update
sudo apt install -y \
vulkan-tools \
libvulkan-dev \
glslc \
spirv-headers

检查 Vulkan Runtime:

vulkaninfo --summary

如果当前版本的 vulkaninfo 不支持 –summary,可以执行:

vulkaninfo | head -n 100

输出中能够识别板载 GPU,且未出现 Vulkan Loader、ICD 或设备初始化错误,说明 Vulkan Runtime 基本可用。

进入源码目录并清理旧构建目录:

cd ~/local-llm-test/llama.cpp
rm -rf build-vulkan

配置 Vulkan 构建,同时保留 KleidiAI CPU 支持:

cmake -S . -B build-vulkan \
-DCMAKE_BUILD_TYPE=Release \
-DGGML_VULKAN=ON \
-DGGML_CPU_KLEIDIAI=ON

检查编译配置:

grep -E "GGML_VULKAN|GGML_CPU_KLEIDIAI" \
build-vulkan/CMakeCache.txt

预期输出包含:

GGML_VULKAN:BOOL=ON
GGML_CPU_KLEIDIAI:BOOL=ON

开始编译:

cmake --build build-vulkan -j4

检查编译产物:

ls -lh build-vulkan/bin/llama-cli
ls -lh build-vulkan/bin/llama-bench

3.1.1.10 运行 Vulkan 推理

Vulkan 推理的主要计算由 GPU 承担,但 CPU 仍负责 Tokenizer、任务调度、内存管理以及部分未卸载至 GPU 的计算。

Vulkan 推理默认将宿主进程绑定至当前最高频的 4 个 Cortex-A720 核心:

GPU_CPU_LIST=0,1,10,11
GPU_THREAD_NUM=4

检查设置:

echo "GPU_CPU_LIST=$GPU_CPU_LIST"
echo "GPU_THREAD_NUM=$GPU_THREAD_NUM"
taskset -c "$GPU_CPU_LIST" true

执行 Vulkan 推理:

cd ~/local-llm-test/llama.cpp
taskset -c "$GPU_CPU_LIST" \
./build-vulkan/bin/llama-cli \
-m "$MODEL" \
-p "请用一句话介绍你自己。" \
-t "$GPU_THREAD_NUM" \
-n 128 \
-ngl 99 \
2>&1 | tee ~/local-llm-test/llama-vulkan.log

其中:

  • taskset -c “$GPU_CPU_LIST” 将宿主进程绑定至 CPU 0、1、10、11;

  • -t “$GPU_THREAD_NUM” 将 llama.cpp CPU 线程数设置为 4;

  • -ngl 99 表示尝试将尽可能多的模型层卸载至 GPU。

实际能够卸载的模型层数取决于:

  • 模型结构和模型大小;

  • 可用内存;

  • GPU 驱动版本;

  • Vulkan Runtime;

  • llama.cpp Commit;

  • Vulkan 后端算子支持情况。

运行时重点检查:

  1. vulkaninfo 能够识别板载 GPU;

  2. CMakeCache.txt 中显示 GGML_VULKAN=ON;

  3. 执行的是 build-vulkan/bin/llama-cli;

  4. 日志中出现 Vulkan 设备信息;

  5. 日志中显示模型层被卸载至 GPU;

  6. 模型能够正常生成文本;

  7. 未出现 Vulkan 初始化失败或完全回退至 CPU。

检查关键日志:

grep -Ei \
"vulkan|gpu|offload|layer|fallback|error" \
~/local-llm-test/llama-vulkan.log

满足以下条件,可以认为 Vulkan 推理路径基本跑通:

  • Vulkan Runtime 能够识别 GPU;

  • llama.cpp 已启用 Vulkan 后端;

  • 运行日志显示 Vulkan 设备和模型层卸载信息;

  • 模型能够完成文本生成。

如果出现:

warning: no usable GPU found

或者:

--gpu-layers option will be ignored

依次检查:

grep "GGML_VULKAN" \
~/local-llm-test/llama.cpp/build-vulkan/CMakeCache.txt
vulkaninfo --summary
ls -lh \
~/local-llm-test/llama.cpp/build-vulkan/bin/llama-cli

并确认实际执行的是:

./build-vulkan/bin/llama-cli

而不是:

./build-cpu/bin/llama-cli

如果 Vulkan 能够识别 GPU,但模型运行时因内存不足退出,可以降低卸载层数,例如:

taskset -c "$GPU_CPU_LIST" \
./build-vulkan/bin/llama-cli \
-m "$MODEL" \
-p "请用一句话介绍你自己。" \
-t "$GPU_THREAD_NUM" \
-n 128 \
-ngl 10

随后逐步提高 -ngl,观察内存占用、模型层卸载数量和运行结果。

3.1.1.11 CPU 与 Vulkan 性能对比

CPU / KleidiAI 测试使用全部 8 个 Cortex-A720 核心:

CPU_LIST=0,1,6-11
THREAD_NUM=8

执行 CPU 测试:

taskset -c "$CPU_LIST" \
./build-cpu/bin/llama-bench \
-m "$MODEL" \
-p 512 \
-n 128 \
-t "$THREAD_NUM" \
-r 3 \
| tee ~/local-llm-test/llama-cpu-bench.log

Vulkan 测试将宿主进程绑定至当前最高频的 4 个 Cortex-A720 核心:

GPU_CPU_LIST=0,1,10,11
GPU_THREAD_NUM=4

执行 Vulkan 测试:

taskset -c "$GPU_CPU_LIST" \
./build-vulkan/bin/llama-bench \
-m "$MODEL" \
-p 512 \
-n 128 \
-t "$GPU_THREAD_NUM" \
-ngl 99 \
-r 3 \
| tee ~/local-llm-test/llama-vulkan-bench.log

两次测试应使用相同的:

  • 模型文件;

  • 模型量化方式;

  • Prompt Token 数;

  • 生成 Token 数;

  • 测试重复次数;

  • 系统镜像;

  • 系统电源状态;

  • 散热状态;

  • 后台进程负载。

CPU 和 Vulkan 使用不同的 CPU 绑定配置:

推理路径

CPU 绑定范围

线程参数

CPU / KleidiAI

0,1,6-11

-t 8

Vulkan

0,1,10,11

-t 4

该测试用于比较两条推荐部署路径的实际性能。由于两条路径承担主要计算的硬件不同,CPU 线程数和 CPU 绑定范围不要求完全一致,但必须在测试结果中分别记录。

如需严格控制 CPU 资源进行后端对比,可以额外将两条路径都绑定至相同的 4 个 Cortex-A720 核心:

CPU_LIST=0,1,10,11
THREAD_NUM=4

但正式发布的默认测试配置仍建议使用:

  • CPU / KleidiAI:8 个 Cortex-A720;

  • Vulkan:4 个最高频 Cortex-A720。

llama-bench 的结果主要用于比较模型推理后端和参数配置,不等同于完整应用的端到端响应时间。

测试完成后,建议记录:

git rev-parse HEAD
uname -a
lscpu
vulkaninfo --summary

同时记录:

  • 模型名称;

  • 量化类型;

  • CPU 推理线程数;

  • CPU 绑定范围;

  • -ngl 数值;

  • Prompt Processing 性能;

  • Text Generation 性能;

  • 系统温度;

  • 后台负载;

  • 异常日志。

3.1.1.12 llama.cpp 常见问题

问题现象

可能原因

建议处理方式

找不到 llama-cli

编译失败或构建路径错误

检查编译日志及 build-cpu/bin

模型文件只有几 KB

模型未完整下载

删除异常文件后重新下载

模型加载失败

模型路径错误、文件损坏或不是 GGUF

检查 -m、文件大小和模型格式

输出乱码或大量重复

模型不完整、Chat Template 不匹配或参数异常

检查模型、模板和当前版本帮助

日志中没有 CPU_KLEIDIAI

未启用或日志格式变化

检查 CMake 配置、Commit 和完整日志

taskset 执行失败

CPU 编号不存在或 CPU 不在线

使用 lscpu -e 检查 CPU 范围

线程数大于绑定核心数

配置不一致

调整 -t 或扩大 CPU_LIST

vulkaninfo 找不到 GPU

GPU 驱动或 Vulkan Runtime 异常

检查 GO 图形引擎和系统 Release

-ngl 被忽略

未启用 Vulkan 或执行了错误二进制

检查构建参数和执行路径

Vulkan 内存不足

模型或卸载层数过大

使用更小模型或降低 -ngl

编译进程被终止

可用内存不足

将编译并行数从 -j4 降至 -j2

多次测试波动较大

调度、温度或后台负载不同

固定绑核、线程数和测试环境

3.1.2 MNN

MNN 是面向移动端、嵌入式设备和边缘设备的轻量级推理引擎,可运行经过转换的 MNN 大语言模型,并支持 CPU、OpenCL 等推理后端。

MNN LLM 使用包含模型结构、模型权重、Tokenizer 和运行配置的完整模型目录,不能直接加载 llama.cpp 使用的 GGUF 模型。

常见 MNN LLM 模型目录中可能包含:

config.json
llm_config.json
llm.mnn
llm.mnn.weight
tokenizer.mtok
embeddings_bf16.bin

不同模型和不同 MNN 版本的文件组成可能存在差异,应以模型仓库中的 README 和 config.json 为准。

本节介绍:

  • MNN 社区源码获取和编译;

  • MNN 格式模型下载;

  • CPU / KleidiAI 推理;

  • taskset 绑核;

  • OpenCL GPU 推理;

  • CPU 与 OpenCL 性能测试。

3.1.2.1 准备环境并获取 MNN 源码

安装基础依赖:

sudo apt update
sudo apt install -y \
git \
git-lfs \
cmake \
build-essential \
wget \
ca-certificates \
clinfo \
ocl-icd-opencl-dev \
opencl-headers \
util-linux

初始化 Git LFS:

git lfs install

创建工作目录:

mkdir -p ~/mnn
cd ~/mnn

获取 MNN 社区源码:

git clone https://github.com/alibaba/MNN.git

进入源码目录:

cd ~/mnn/MNN

记录当前 Commit:

git rev-parse HEAD \
| tee ~/mnn/mnn-commit.txt

如果源码目录已经存在,可以更新:

cd ~/mnn/MNN
git pull --ff-only

更新后应重新记录 Commit,并重新编译。

3.1.2.2 编译 MNN CPU / KleidiAI 版本

清理旧构建目录:

cd ~/mnn/MNN
rm -rf build-cpu

配置 CPU、LLM 和 KleidiAI 构建:

cmake -S . -B build-cpu \
-DCMAKE_BUILD_TYPE=Release \
-DMNN_BUILD_SHARED_LIBS=ON \
-DMNN_BUILD_LLM=ON \
-DMNN_LOW_MEMORY=ON \
-DMNN_SUPPORT_TRANSFORMER_FUSE=ON \
-DMNN_KLEIDIAI=ON \
-DMNN_KLEIDIAI_DEFAULT_ON=ON

主要构建选项如下:

构建选项

作用

MNN_BUILD_SHARED_LIBS=ON

构建动态库

MNN_BUILD_LLM=ON

构建 MNN LLM 引擎、llm_demo 和相关工具

MNN_LOW_MEMORY=ON

启用低内存相关能力

MNN_SUPPORT_TRANSFORMER_FUSE=ON

启用 Transformer 融合优化

MNN_KLEIDIAI=ON

集成 Arm KleidiAI

MNN_KLEIDIAI_DEFAULT_ON=ON

默认优先使用 KleidiAI Kernel

检查配置:

grep -E \
"^MNN_(BUILD_SHARED_LIBS|BUILD_LLM|LOW_MEMORY|SUPPORT_TRANSFORMER_FUSE|KLEIDIAI|KLEIDIAI_DEFAULT_ON):" \
build-cpu/CMakeCache.txt

开始编译:

cmake --build build-cpu -j4

如果编译时出现内存不足,可以降低并行数:

cmake --build build-cpu -j2

检查编译产物:

find ~/mnn/MNN/build-cpu \
-maxdepth 3 \
-type f \
\( -name "llm_demo" -o -name "llm_bench" \)

设置程序路径:

LLM_DEMO="$(find ~/mnn/MNN/build-cpu \
-type f -name llm_demo \
| head -n 1)"
LLM_BENCH="$(find ~/mnn/MNN/build-cpu \
-type f -name llm_bench \
| head -n 1)"

检查:

echo "$LLM_DEMO"
echo "$LLM_BENCH"
test -x "$LLM_DEMO"
test -x "$LLM_BENCH"

查看帮助:

"$LLM_DEMO" --help || true
"$LLM_BENCH" --help || true

3.1.2.3 检查动态库加载

如果系统中已经安装其他版本的 MNN,源码编译的 llm_demo 可能错误加载系统中的旧版 libMNN.so,从而出现:

  • undefined symbol;

  • 程序异常退出;

  • 模型加载失败;

  • OpenCL 后端缺失;

  • 运行结果与构建配置不一致。

检查动态库:

ldd "$LLM_DEMO" \
| grep -E "libMNN|libllm|Express|not found"

MNN 相关动态库应优先指向当前 build-cpu 目录。

如果出现 not found,或者加载了系统中其他版本的 MNN 库,可以在当前终端设置:

export LD_LIBRARY_PATH="$HOME/mnn/MNN/build-cpu:$HOME/mnn/MNN/build-cpu/express:${LD_LIBRARY_PATH:-}"

再次检查:

ldd "$LLM_DEMO" \
| grep -E "libMNN|libllm|Express|not found"

不建议在没有确认影响范围的情况下永久写入 ~/.bashrc,避免影响系统中其他依赖 MNN 的程序。

3.1.2.4 准备 MNN 模型

本节以 Qwen2.5-0.5B-Instruct-MNN 作为基础验证模型。

如果赛事资源包已经提供经过验证的 MNN 模型,建议优先使用赛事模型,并将后续路径替换为实际路径。

创建工作目录并下载模型:

mkdir -p ~/mnn
cd ~/mnn
git clone https://www.modelscope.cn/MNN/Qwen2.5-0.5B-Instruct-MNN.git

进入模型目录并下载完整权重:

cd ~/mnn/Qwen2.5-0.5B-Instruct-MNN
git lfs pull

检查 Git LFS 文件:

git lfs ls-files

查看模型目录:

ls -lh

检查是否仍存在 Git LFS 指针文件:

grep -Il \
"^version https://git-lfs.github.com/spec/v1" \
./* 2>/dev/null

如果该命令输出了模型文件名称,说明实际权重没有完整下载,应重新执行:

git lfs pull

设置模型目录:

MODEL_DIR=~/mnn/Qwen2.5-0.5B-Instruct-MNN

确认主要配置文件:

ls -lh "$MODEL_DIR/config.json"

3.1.2.5 创建 MNN CPU 配置

MNN CPU / KleidiAI 推理默认绑定全部 8 个 Cortex-A720 核心:

CPU_LIST=0,1,6-11
THREAD_NUM=8

其中,实际绑定的逻辑 CPU 为:

0,1,6,7,8,9,10,11

CPU 2、3、4、5 为 Cortex-A520 小核,不纳入默认 CPU / KleidiAI 推理的绑核范围。

如需测试 4 核配置,应绑定当前最高频的 4 个 Cortex-A720 核心:

CPU_LIST=0,1,10,11
THREAD_NUM=4

检查当前设置:

echo "CPU_LIST=$CPU_LIST"
echo "THREAD_NUM=$THREAD_NUM"
taskset -c "$CPU_LIST" true

本节后续默认使用 8 个 Cortex-A720 核心:

CPU_LIST=0,1,6-11
THREAD_NUM=8

为避免直接修改原始配置,复制 CPU 配置:

cd "$MODEL_DIR"
cp config.json config-cpu.json

根据 THREAD_NUM 修改配置:

python3 - "$THREAD_NUM" <<'PY'
import json
import sys
from pathlib import Path
thread_num = int(sys.argv[1])
path = Path("config-cpu.json")
with path.open("r", encoding="utf-8") as file:
config = json.load(file)
config["backend_type"] = "cpu"
config["thread_num"] = thread_num
config.setdefault("precision", "low")
config.setdefault("memory", "low")
with path.open("w", encoding="utf-8") as file:
json.dump(config, file, indent=4, ensure_ascii=False)
print(json.dumps(config, indent=4, ensure_ascii=False))
PY

检查配置:

grep -nE \
'"backend_type"|"thread_num"|"precision"|"memory"' \
"$MODEL_DIR/config-cpu.json"

使用默认 8 核配置时,预期包含:

"backend_type": "cpu",
"thread_num": 8

创建测试 Prompt:

cat > ~/mnn/text_baseline_prompt.txt <<'EOF'

请用一句话介绍你自己。

EOF

进行 MNN CPU / KleidiAI 推理时,应确保以下三项一致:

  • CPU_LIST 中包含的 CPU 数量;

  • config-cpu.json 中的 thread_num;

  • llm_bench 命令中的 -t 参数。

默认配置为:

taskset CPU 数量:8
config-cpu.json thread_num:8
llm_bench -t:8

3.1.2.6 运行 MNN CPU / KleidiAI 推理

设置当前构建目录的动态库路径:

export LD_LIBRARY_PATH="$HOME/mnn/MNN/build-cpu:$HOME/mnn/MNN/build-cpu/express:${LD_LIBRARY_PATH:-}"

执行绑核后的 CPU 推理:

taskset -c "$CPU_LIST" \
"$LLM_DEMO" \
"$MODEL_DIR/config-cpu.json" \
~/mnn/text_baseline_prompt.txt

保存完整日志:

taskset -c "$CPU_LIST" \
"$LLM_DEMO" \
"$MODEL_DIR/config-cpu.json" \
~/mnn/text_baseline_prompt.txt \
2>&1 | tee ~/mnn/mnn-cpu.log

如果模型能够正常加载并输出文本,说明 MNN CPU 推理链路基本可用。

检查 KleidiAI 信息:

grep -i "kleidiai" \
~/mnn/mnn-cpu.log

同时检查构建配置:

grep -E \
"^MNN_KLEIDIAI(:|_DEFAULT_ON:)" \
~/mnn/MNN/build-cpu/CMakeCache.txt

其中:

MNN_KLEIDIAI=ON

表示构建时集成了 KleidiAI;

MNN_KLEIDIAI_DEFAULT_ON=ON

表示运行时默认优先使用 KleidiAI Kernel。

但不能仅凭模型能够输出文本判断 KleidiAI 已经生效。正式测试时,应结合:

  • MNN Commit;

  • CMake 配置;

  • 运行日志;

  • CPU 指令能力;

  • 线程数和绑核范围;

  • 相同模型下的性能数据。

3.1.2.7 MNN CPU 性能测试

先查看当前版本参数:

"$LLM_BENCH" --help

使用相同 CPU 绑定范围执行测试:

taskset -c "$CPU_LIST" \
"$LLM_BENCH" \
-m "$MODEL_DIR/config-cpu.json" \
-a cpu \
-t "$THREAD_NUM" \
-p 32,128 \
-n 32 \
-rep 3 \
-kv true \
-fp ~/mnn/mnn-cpu-bench.md

主要参数说明如下:

参数

说明

-m

模型目录中的 config.json 路径

-a cpu

使用 CPU 后端

-t

CPU 推理线程数

-p

Prompt 长度

-n

生成长度

-rep

每项重复测试次数

-kv true

Decode 阶段考虑历史 KV Cache

-fp

将结果写入指定文件

如果当前社区版本不支持其中某个参数,应删除该参数,并以:

“$LLM_BENCH” –help

的实际输出为准。

正式测试时,config-cpu.json 中的 thread_num、llm_bench -t 和 taskset 绑定的 CPU 数量应保持一致。

3.1.2.8 可选:编译 MNN OpenCL 版本

MNN OpenCL 后端依赖 CIX GO 图形引擎提供的 GPU 驱动和 OpenCL Runtime。

检查 OpenCL Runtime:

clinfo

过滤关键信息:

clinfo | grep -E \
"Platform Name|Platform Version|Device Name|Device Version|Driver Version"

如果能够识别 OpenCL Platform 和板载 GPU Device,且未出现 Loader 或设备初始化错误,说明 OpenCL Runtime 基本可用。

清理构建目录:

cd ~/mnn/MNN
rm -rf build-opencl

配置 OpenCL、LLM 和 KleidiAI 构建:

cmake -S . -B build-opencl \
-DCMAKE_BUILD_TYPE=Release \
-DMNN_BUILD_SHARED_LIBS=ON \
-DMNN_BUILD_LLM=ON \
-DMNN_OPENCL=ON \
-DMNN_USE_SYSTEM_LIB=ON \
-DMNN_SEP_BUILD=OFF \
-DMNN_LOW_MEMORY=ON \
-DMNN_SUPPORT_TRANSFORMER_FUSE=ON \
-DMNN_KLEIDIAI=ON \
-DMNN_KLEIDIAI_DEFAULT_ON=ON

检查配置:

grep -E \
"^MNN_(BUILD_LLM|OPENCL|USE_SYSTEM_LIB|SEP_BUILD|KLEIDIAI|KLEIDIAI_DEFAULT_ON):" \
build-opencl/CMakeCache.txt

预期至少包含:

MNN_BUILD_LLM:BOOL=ON

MNN_OPENCL:BOOL=ON

MNN_USE_SYSTEM_LIB:BOOL=ON

MNN_SEP_BUILD:BOOL=OFF

开始编译:

cmake --build build-opencl -j4

如果内存不足:

cmake --build build-opencl -j2

查找程序:

LLM_DEMO_OPENCL="$(find ~/mnn/MNN/build-opencl \
-type f -name llm_demo \
| head -n 1)"
LLM_BENCH_OPENCL="$(find ~/mnn/MNN/build-opencl \
-type f -name llm_bench \
| head -n 1)"

检查:

echo "$LLM_DEMO_OPENCL"
echo "$LLM_BENCH_OPENCL"
test -x "$LLM_DEMO_OPENCL"
test -x "$LLM_BENCH_OPENCL"

设置动态库路径:

export LD_LIBRARY_PATH="$HOME/mnn/MNN/build-opencl:$HOME/mnn/MNN/build-opencl/express:${LD_LIBRARY_PATH:-}"

检查动态库:

ldd "$LLM_DEMO_OPENCL" \
| grep -E "libMNN|libllm|OpenCL|not found"

3.1.2.9 创建 OpenCL 配置

复制原始模型配置:

cd "$MODEL_DIR"
cp config.json config-opencl.json

修改 OpenCL 配置:

python3 - <<'PY'
import json
from pathlib import Path
path = Path("config-opencl.json")
with path.open("r", encoding="utf-8") as file:
config = json.load(file)
config["backend_type"] = "opencl"
config["thread_num"] = 4
config.setdefault("precision", "low")
config.setdefault("memory", "low")
with path.open("w", encoding="utf-8") as file:
json.dump(config, file, indent=4, ensure_ascii=False)
print(json.dumps(config, indent=4, ensure_ascii=False))
PY

检查配置:

grep -nE \
'"backend_type"|"thread_num"|"precision"|"memory"' \
"$MODEL_DIR/config-opencl.json"

预期包含:

"backend_type": "opencl"

需要注意:

在 MNN OpenCL LLM 测试中,thread_num=4 或 llm_bench -t 4 不应简单理解为使用四个 CPU 线程。对于 OpenCL 后端,该参数与 GPU Mode 有关,当前社区版本通常推荐使用 4,但应以当前版本文档和 llm_bench –help 为准。

3.1.2.10 运行 MNN OpenCL 推理

OpenCL 推理的主要计算由 GPU 承担,但 CPU 仍可能负责:

  • Tokenizer;

  • 模型调度;

  • 内存管理;

  • 数据搬运;

  • 部分 OpenCL 不支持的算子。

OpenCL 推理默认将宿主进程绑定至当前最高频的 4 个 Cortex-A720 核心:

GPU_CPU_LIST=0,1,10,11

检查绑定范围:

echo "GPU_CPU_LIST=$GPU_CPU_LIST"
taskset -c "$GPU_CPU_LIST" true

执行 OpenCL 推理并保存日志:

taskset -c "$GPU_CPU_LIST" \
"$LLM_DEMO_OPENCL" \
"$MODEL_DIR/config-opencl.json" \
~/mnn/text_baseline_prompt.txt \
2>&1 | tee ~/mnn/mnn-opencl.log

检查日志:

grep -Ei \
"opencl|backend|device|fallback|error" \
~/mnn/mnn-opencl.log

模型能够输出文本,只能说明推理流程能够运行,不能单独证明 OpenCL 已实际参与计算。

满足以下条件,可以认为 MNN OpenCL 推理路径基本跑通:

  1. clinfo 能够识别 OpenCL Platform 和 GPU Device;

  2. 构建配置中显示 MNN_OPENCL=ON;

  3. 使用的是 build-opencl 中编译的程序;

  4. config-opencl.json 中设置了 backend_type=opencl;

  5. 运行日志显示 OpenCL 后端或 GPU Device 已初始化;

  6. 未出现 OpenCL 初始化失败或完全回退至 CPU;

  7. 模型能够正常生成文本。

OpenCL 推理过程中,日志中出现 CPU 信息不一定表示 OpenCL 失败。重点应确认是否出现 OpenCL 初始化失败,或者整个模型完全回退至 CPU。

需要注意:

"thread_num": 4

以及 llm_bench 命令中的:

-t 4

在当前 MNN OpenCL LLM 路径中与 OpenCL GPU Mode 有关,不能简单理解为使用 4 个 CPU 推理线程。

真正限制宿主进程 CPU 运行范围的是:

taskset -c "$GPU_CPU_LIST"

即:

taskset -c 0,1,10,11

OpenCL 首次运行时可能执行 Kernel Tuning 并生成缓存,因此首次运行结果通常不适合作为正式性能数据。建议先运行一次预热,再执行正式测试。

3.1.2.11 CPU 与 OpenCL 性能对比

MNN CPU / KleidiAI 测试使用全部 8 个 Cortex-A720 核心:

CPU_LIST=0,1,6-11
THREAD_NUM=8

执行 CPU 测试:

taskset -c "$CPU_LIST" \
"$LLM_BENCH" \
-m "$MODEL_DIR/config-cpu.json" \
-a cpu \
-t "$THREAD_NUM" \
-p 32,128 \
-n 32 \
-rep 3 \
-kv true \
-fp ~/mnn/mnn-cpu-bench.md

OpenCL 测试将宿主进程绑定至当前最高频的 4 个 Cortex-A720 核心:

GPU_CPU_LIST=0,1,10,11

OpenCL 预热:

taskset -c "$GPU_CPU_LIST" \
"$LLM_BENCH_OPENCL" \
-m "$MODEL_DIR/config-opencl.json" \
-a opencl \
-t 4 \
-p 32 \
-n 16 \
-rep 1 \
-kv true

OpenCL 正式测试:

taskset -c "$GPU_CPU_LIST" \
"$LLM_BENCH_OPENCL" \
-m "$MODEL_DIR/config-opencl.json" \
-a opencl \
-t 4 \
-p 32,128 \
-n 32 \
-rep 3 \
-kv true \
-fp ~/mnn/mnn-opencl-bench.md

正式对比时,应固定:

  • 模型及量化配置;

  • Prompt 长度;

  • 生成长度;

  • 测试重复次数;

  • MNN Commit;

  • 系统镜像;

  • GPU Runtime;

  • 系统电源状态;

  • 系统温度和散热状态;

  • 后台进程负载。

CPU 和 OpenCL 使用不同的 CPU 绑定配置:

推理路径

CPU 绑定范围

线程或模式参数

CPU / KleidiAI

0,1,6-11

thread_num=8、-t 8

OpenCL

0,1,10,11

thread_num=4、-t 4

CPU 测试中的 -t 表示 CPU 推理线程数,应与以下两项保持一致:

  • config-cpu.json 中的 thread_num;

  • taskset 绑定的 CPU 数量。

即默认 CPU 配置为:

CPU_LIST=0,1,6-11
THREAD_NUM=8
config-cpu.json thread_num=8
llm_bench -t 8

OpenCL 测试中的 -t 4 与 OpenCL GPU Mode 有关,不能直接与 CPU 测试中的 4 个或 8 个 CPU 推理线程作等价解释。

OpenCL 宿主进程实际绑定的 CPU 范围由以下命令决定:

taskset -c "$GPU_CPU_LIST"

默认对应:

CPU 0、1、10、11

该测试用于比较两条推荐部署路径的实际性能。由于 CPU 和 OpenCL 路径承担主要计算的硬件不同,CPU 线程数和 CPU 绑定范围不要求完全一致,但必须分别记录。

如需严格控制 CPU 资源进行后端对比,可以额外将 CPU 和 OpenCL 路径都绑定至相同的 4 个 Cortex-A720 核心:

CPU_LIST=0,1,10,11
THREAD_NUM=4
GPU_CPU_LIST=0,1,10,11

此时还需要将 config-cpu.json 中的:

"thread_num": 8

修改为:

"thread_num": 4

并将 CPU Benchmark 命令中的:

-t 8

修改为:

-t 4

正式发布的默认测试配置仍建议使用:

  • MNN CPU / KleidiAI:全部 8 个 Cortex-A720;

  • MNN OpenCL:4 个最高频 Cortex-A720。

测试结果用于比较当前设备和当前环境下不同后端的相对性能,不应直接作为不同开发板、不同模型或不同软件版本之间的统一性能基准。

3.1.2.12 MNN 常见问题

问题现象

可能原因

处理建议

找不到 llm_demo

未启用 MNN_BUILD_LLM 或编译失败

检查 CMake 配置和编译日志

找不到 llm_bench

当前版本未生成或构建失败

使用 find 查找并检查编译日志

模型文件只有几百字节

下载到 Git LFS 指针

在模型目录执行 git lfs pull

MNN 无法加载 GGUF

模型格式不兼容

使用完整 MNN 模型目录

找不到配置或权重

模型目录不完整或相对路径失效

保持配置文件位于模型原目录

动态库显示 not found

当前构建库不在搜索路径

临时设置 LD_LIBRARY_PATH

出现 undefined symbol

程序与 libMNN.so 版本不一致

使用 ldd 检查实际库路径

CPU 可以运行但无法确认 KleidiAI

仅编译了支持,日志未说明实际使用

检查构建配置、日志和性能

taskset 失败

CPU 编号不存在或 CPU 不在线

使用 lscpu -e 检查范围

CPU 配置线程数不一致

thread_num、-t 和绑核数量不同

统一三者配置

clinfo 找不到设备

OpenCL Runtime、ICD 或驱动异常

检查 GO 图形引擎和系统版本

OpenCL 初始化失败

未构建 OpenCL 或 Runtime 异常

检查 MNN_OPENCL、clinfo 和日志

OpenCL 回退至 CPU

后端初始化失败或算子不支持

检查完整日志和后端信息

OpenCL 首次运行较慢

正在执行 Kernel Tuning

预热一次后重新测试

性能波动较大

温度、调度、绑核或负载不同

固定测试条件并重复测试