¶
这是 llama.cpp 学习系列的第二篇。我们将从零开始,在 MacBook Pro (Apple Silicon) 上构建一个真正可运行的聊天程序。
目标¶
在本文结束时,你将: 1. 在 MacBook Pro 上编译 llama.cpp 源码 2. 理解 llama.cpp 的基本 API 调用流程 3. 使用 CMake 构建自己的第一个 LLM 推理程序 4. 看到 Metal GPU 加速的效果
前置要求¶
macOS 13+ (Apple Silicon: M1/M2/M3/M4/M5) Xcode Command Line Tools: xcode-select --installCMake 3.14+: brew install cmake至少 4GB 可用内存
第一步:获取 llama.cpp 源码¶
# 克隆仓库
gitclonehttps://github.com/ggerganov/llama.cpp.git
cdllama.cpp
你会看到这样的结构:
llama.cpp/
├── include/ # 头文件(我们主要用这里)
│ ├── llama.h # 核心 API
│ └── ggml.h # 计算图库
├── src/ # 源码实现
│ ├── llama.cpp # 推理引擎
│ └── ggml-metal.m # Metal GPU 后端
├── ggml/ # ggml 计算库
├── examples/ # 官方示例
└── CMakeLists.txt # 构建配置
第二步:编译 llama.cpp¶
# 配置构建(自动检测 Metal)
cmake-Bbuild-DCMAKE_BUILD_TYPE=Release
# 查看构建配置,确认 Metal 已启用
cmake-Bbuild-DCMAKE_BUILD_TYPE=Release2>&1|grep-imetal
# 你应该看到: -- Metal framework found
# 编译(使用所有 CPU 核心)
cmake--buildbuild--configRelease-j$(sysctl-nhw.ncpu)
编译完成后,你会看到:
[100%] Built target llama-cli
[100%] Built target llama-server
验证编译结果:
# 查看生成的库文件
ls-labuild/bin/
# 你应该看到 libllama.dylib, llama-cli 等
# 测试运行
./build/bin/llama-cli--version
第三步:下载一个小模型¶
推荐使用 Qwen3-0.6B,只有 0.6B 参数,非常小巧(约 500MB),适合学习:
方式一:ModelScope 下载(国内推荐)¶
pipinstallmodelscope
# 创建模型目录
mkdir-pmodels
# 下载 Q4_K_M 量化版本(推荐)
modelscopedownload--modelQwen/Qwen3-0.6B-GGUF--local_dir./models
方式二:HuggingFace 下载¶
pipinstallhuggingface_hub
mkdir-pmodels
huggingface-clidownload\
ggml-org/Qwen3-0.6B-GGUF\
qwen3-0.6b-q4_k_m.gguf\
--local-dirmodels/
方式三:直接 curl¶
curl-L-omodels/qwen3-0.6b-q4_k_m.gguf\
"https://huggingface.co/ggml-org/Qwen3-0.6B-GGUF/resolve/main/qwen3-0.6b-q4_k_m.gguf"
Qwen3-0.6B 量化版本参考¶
| 推荐 | ||
第四步:获取 Demo 源码¶
# 克隆 demo 仓库
gitclonegit@gitee.com:frankobsidian/llamacppdemos.git
cdllamacppdemos/01-minimal-chat
项目结构:
01-minimal-chat/
├── minimal-chat.cpp # 主程序源码
├── CMakeLists.txt # CMake 构建文件
└── build.sh # 一键构建脚本
第五步:理解代码¶
API 调用流程¶
┌─────────────────────────────────────────────────────────┐
│ 程序启动 │
├─────────────────────────────────────────────────────────┤
│ 1. ggml_backend_load_all() # 加载所有后端(Metal等) │
├─────────────────────────────────────────────────────────┤
│ 2. llama_model_load_from_file() # 加载 GGUF 模型 │
├─────────────────────────────────────────────────────────┤
│ 3. llama_init_from_model() # 创建推理上下文 │
├─────────────────────────────────────────────────────────┤
│ 4. llama_tokenize() # 将文本转为 token │
├─────────────────────────────────────────────────────────┤
│ 5. llama_decode() # 预填充(处理输入) │
├─────────────────────────────────────────────────────────┤
│ 6. llama_sampler_sample() # 逐个采样生成 token │
│ ↓ 循环执行 │
│ llama_decode() # 更新 KV 缓存 │
│ llama_token_to_piece() # token 转文本输出 │
├─────────────────────────────────────────────────────────┤
│ 7. llama_model_free() # 释放资源 │
└─────────────────────────────────────────────────────────┘
核心代码解析¶
1. 初始化后端
ggml_backend_load_all();
自动加载所有可用后端:CPU (SIMD)、Metal (Apple GPU)、CUDA (NVIDIA GPU)
2. 加载模型
llama_model_paramsmodel_params=llama_model_default_params();
model_params.n_gpu_layers=99;// 全部卸载到 GPU
llama_model*model=llama_model_load_from_file("model.gguf",model_params);
n_gpu_layers = 99:将所有模型层放到 GPU(Metal 加速) 设为 0 则纯 CPU 推理
3. 创建上下文
llama_context_paramsctx_params=llama_context_default_params();
ctx_params.n_ctx=2048;// 上下文窗口大小
ctx_params.n_threads=8;// CPU 线程数
llama_context*ctx=llama_init_from_model(model,ctx_params);
4. Tokenize 输入
// 第一次调用:获取 token 数量
// tokens=NULL 时返回负数,绝对值为所需 token 数
intn=llama_tokenize(vocab,text,text_len,nullptr,0,true,true);
if(n==0){
return{};
}
if(n<0){
n=-n;// 负数表示所需 token 数,取绝对值
}
// 第二次调用:实际执行
std::vector<llama_token>tokens(n);
llama_tokenize(vocab,text,text_len,tokens.data(),n,true,true);
5. 推理与采样
// 创建采样器链
llama_sampler*smpl=llama_sampler_chain_init(sparams);
// 添加采样策略:repeat_penalty -> top_k -> top_p -> temp -> dist
llama_sampler_chain_add(smpl,llama_sampler_init_penalties(64,1.1f,0.0f,0.0f));
llama_sampler_chain_add(smpl,llama_sampler_init_top_k(40));
llama_sampler_chain_add(smpl,llama_sampler_init_top_p(0.9f,1));
llama_sampler_chain_add(smpl,llama_sampler_init_temp(temperature));
llama_sampler_chain_add(smpl,llama_sampler_init_dist(0));
// 预填充
llama_batchbatch=llama_batch_get_one(tokens.data(),n_prompt);
llama_decode(ctx,batch);
// 自回归生成
for(inti=0;i<n_predict;i++){
llama_tokentoken=llama_sampler_sample(smpl,ctx,-1);
if(llama_vocab_is_eog(vocab,token))break;
// token 转文本输出
charbuf[256];
intn=llama_token_to_piece(vocab,token,buf,sizeof(buf),0,true);
printf("%.*s",n,buf);
// 继续推理
batch=llama_batch_get_one(&token,1);
llama_decode(ctx,batch);
}
第六步:构建并运行¶
使用构建脚本(推荐)¶
cd 01-minimal-chat
# 指定 llama.cpp 路径并构建
./build.sh/path/to/llama.cpp
手动 CMake 构建¶
cd 01-minimal-chat
# 配置
cmake-Bbuild\
-DLLAMA_DIR=/path/to/llama.cpp\
-DCMAKE_BUILD_TYPE=Release
# 编译
cmake--buildbuild--configRelease
运行¶
./build/minimal-chat -m ../../../models/Qwen3-0.6B-Q8_0.gguf "讲个笑话"输出¶
第七步:Metal GPU 加速说明¶在 MacBook Pro 上,llama.cpp 会自动使用 Metal 后端。
验证 Metal 加速¶
# 运行时观察日志
./build/minimal-chat-mmodel.gguf"你好"2>&1|grep-imetal
# 你应该看到类似:
# ggml_metal_init: loaded Metal library
调整 GPU 卸载层数¶
# 全部卸载到 GPU(最快)
./build/minimal-chat-mmodel.gguf -ngl 99 "你好"
# 部分卸载(显存不够时)
./build/minimal-chat-mmodel.gguf -ngl 20 "你好"
# 纯 CPU 推理
./build/minimal-chat-mmodel.gguf -ngl 0 "你好"
性能参考¶
在不同 MacBook Pro 上的预期性能(Qwen3-0.6B Q4_K_M):
API 版本注意事项¶
重要:llama.cpp API 更新频繁,以下字段名和返回值语义可能随版本变化。本文档基于最新版本编写。
1. llama_perf_context_data 字段变更¶
t_eval | t_eval_ms | |
n_eval | n_eval |
// 旧写法(已废弃)
if(perf_data.t_eval>0&&n_generated>0){
doublebuiltin_speed=n_generated/(perf_data.t_eval/1e6);
}
// 新写法
if(perf_data.t_eval_ms>0&&perf_data.n_eval>0){
doublebuiltin_speed=perf_data.n_eval/(perf_data.t_eval_ms/1000.0);
}
2. llama_tokenize 返回值语义¶
- 成功时
:返回正数,表示 token 数量 - 缓冲区不足时
:返回负数,其绝对值为所需的 token 数量
intn=llama_tokenize(vocab,text,text_len,nullptr,0,true,true);
if(n==0){
return{};// 真正无 token
}
if(n<0){
n=-n;// 负数表示所需 token 数,取绝对值
}
3. 采样策略配置¶
避免混用互斥的采样器(如 dist + greedy)。推荐配置:
// 重复惩罚 -> top-k -> top-p -> 温度 -> 分布采样
llama_sampler_chain_add(smpl,llama_sampler_init_penalties(64,1.1f,0.0f,0.0f));
llama_sampler_chain_add(smpl,llama_sampler_init_top_k(40));
llama_sampler_chain_add(smpl,llama_sampler_init_top_p(0.9f,1));
llama_sampler_chain_add(smpl,llama_sampler_init_temp(temperature));
llama_sampler_chain_add(smpl,llama_sampler_init_dist(0));
详细调试记录见 debugging-notes.md。
常见问题¶
1. 编译错误:找不到 llama.h¶
# 检查路径是否正确
ls/path/to/llama.cpp/include/llama.h
# CMake 时指定正确路径
cmake-Bbuild-DLLAMA_DIR=/path/to/llama.cpp
2. 运行时错误:找不到 dylib¶
# 设置动态库路径
export DYLD_LIBRARY_PATH=/path/to/llama.cpp/build/bin:$DYLD_LIBRARY_PATH
# 或者使用 build.sh,它会自动设置 rpath
3. Metal 相关错误¶
# 检查是否支持 Metal
system_profiler SPDisplaysDataType|grepMetal
# 如果不支持,使用 CPU 模式
./build/minimal-chat-mmodel.gguf -ngl 0 "你好"
快速上手(M5 MacBook Pro)¶
# 1. 编译 llama.cpp
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(sysctl-nhw.ncpu)
cd ..
# 2. 获取 demo
git clonegit@gitee.com:frankobsidian/llamacppdemos.git
cd llamacppdemos/01-minimal-chat
# 3. 构建
./build.sh../../llama.cpp
# 4. 下载模型(国内用 ModelScope)
pip install modelscope
modelscope download --model Qwen/Qwen3-0.6B-GGUF --local_dir ./models
# 5. 运行
./build/minimal-chat-mmodels/Qwen3-0.6B-Q8_0.gguf "你好"
M5 预期性能:~110 tokens/s(Metal GPU 加速)
夜雨聆风