第 2 课
llama serve:OpenAI 兼容的本地 API
用 llama serve 起服务并用 /health 判断就绪,用 curl 发请求时看懂思考模型回答为空、finish_reason 为 length 的原因,读 usage、timings 与提示词缓存,用 OpenAI Python SDK 调用和流式输出,设 --api-key 并对照 401,再用 Anthropic /v1/messages 和 /tokenize、/props 原生接口调试。
课程目录第 2 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课依据:Llama 官方文档「Running a server」https://llama.app/docs/serve 、「API server」https://llama.app/docs/api 、「Web UI」https://llama.app/docs/webui (2026-10-27 核对,llama build b11852)。
这一课做完你会得到什么
用 llama serve 在本机起一个 OpenAI 兼容的 API 服务,用 curl 和 OpenAI 官方 Python SDK 调用它,学会流式输出、看用量和速度、设访问密钥,再用 Anthropic 格式调一次同一个模型。以后任何支持「自定义 Base URL」的应用都能接上你的本地模型。
本课继续用第 1 课下载的 ggml-org/Qwen3.5-0.8B-GGUF:Q8_0,内存够的话换成官方示例的 ggml-org/gemma-4-e4b-it-GGUF:Q4_0 效果更好。
第 1 步:启动服务
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF:Q8_0 \
-c 8192 -a qwen3.5-0.8b
三个参数的意思:
| 参数 | 作用 |
|---|---|
-c 8192 | 上下文 8192 token;-c 0 用模型支持的最大值 |
-a qwen3.5-0.8b | API 里报告的模型名,调用时好写 |
--port | 换端口,默认 9931 |
看到 listening on http://127.0.0.1:9931 就是起来了。服务默认只监听本机(127.0.0.1),想让局域网其他设备访问才加 --host 0.0.0.0,这时务必同时设第 5 步的 API Key。
另开一个终端,检查服务是否就绪:
curl http://localhost:9931/health
对照结果(2026-10-27 实测):
{"status":"ok"}
模型还在加载时这个接口返回 HTTP 503,加载完返回 200。写启动脚本时可以循环等到 200 再发请求:
until curl -sf http://localhost:9931/health; do sleep 1; done
第 2 步:用 curl 发第一个请求
把请求体存成 ask.json:
{
"model": "qwen3.5-0.8b",
"messages": [
{"role": "system", "content": "你是简洁的助手。"},
{"role": "user", "content": "列出本地跑大模型的 3 个好处。"}
],
"max_tokens": 200
}
发送:
curl -s http://localhost:9931/v1/chat/completions \
-H "Content-Type: application/json" \
-d @ask.json
对照结果(2026-10-27 实测,只摘关键字段):
finish_reason: length
content: ''
reasoning_content: Thinking Process: 1. **Analyze the Request:** ...
回答是空的。原因:Qwen 3.5 默认先思考,思考内容放在 message.reasoning_content 里,和最终回答 message.content 分开返回;200 个 token 全花在思考上,还没开始回答就到上限了(finish_reason: length)。
两种解决办法:调大 max_tokens,或者对不需要思考的任务直接关掉思考。按 Ctrl+C 停掉服务,加上 -rea off 重启:
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF:Q8_0 \
-c 8192 -a qwen3.5-0.8b -rea off
再发一次同样的请求,对照结果(2026-10-27 实测,节选):
finish_reason: stop
本地跑大模型能带来三大核心价值:
1. **数据隐私与安全性** ……
2. **算力成本与延迟更低** ……
3. **可控性与定制化** ……
usage: {'prompt_tokens': 33, 'completion_tokens': 152,
'total_tokens': 185}
timings: {'cache_n': 0, 'prompt_per_second': 229.1,
'predicted_per_second': 44.4}
usage 是标准的 OpenAI 用量字段;timings 是 llama.cpp 额外给的速度统计。把同一个请求再发一次,cache_n 变成 29:服务端默认开启提示词缓存,前缀相同的部分(比如固定的系统提示词)不用重新计算。
cache_n: 29
第 3 步:用 OpenAI Python SDK 调用
官方 SDK 只需要改 base_url。没设密钥时 api_key 随便填一个非空字符串。新建 hello_local.py:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:9931/v1",
api_key="no-key-required",
)
resp = client.chat.completions.create(
model="qwen3.5-0.8b",
messages=[{"role": "user", "content": "用一句话介绍北京。"}],
max_tokens=80,
)
print(resp.choices[0].message.content)
print(resp.usage)
用 uv 临时装上 openai 包运行(也可以先 pip install openai 再 python hello_local.py):
uv run --with openai python hello_local.py
对照结果(2026-10-27 实测,usage 一行有删节):
北京是中国的首都、政治、经济、文化、历史和科技创新中心,……
CompletionUsage(completion_tokens=40, prompt_tokens=17,
total_tokens=57, ...)
查看服务里有哪些模型,返回的 id 就是 -a 设的名字:
curl -s http://localhost:9931/v1/models
对照结果(2026-10-27 实测,只取 id):
['qwen3.5-0.8b']
第 4 步:流式输出
加 stream=True,一边生成一边打印。新建 stream.py:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:9931/v1", api_key="x")
stream = client.chat.completions.create(
model="qwen3.5-0.8b",
messages=[{"role": "user", "content": "从 1 数到 5,用逗号分隔。"}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
uv run --with openai python stream.py
对照结果(2026-10-27 实测):
1, 2, 3, 4, 5
第 5 步:设访问密钥
服务对外开放前一定要加密钥。停掉服务,加上 --api-key 重启(多个密钥用逗号分隔):
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF:Q8_0 \
-a qwen3.5-0.8b -rea off --api-key sk-local-123
不带密钥请求会被拒绝:
curl -s http://localhost:9931/v1/models
对照结果(2026-10-27 实测,HTTP 401,为排版折成两行):
{"error":{"message":"Invalid API Key",
"type":"authentication_error","code":401}}
带上 Bearer 头就正常返回 200:
curl -s http://localhost:9931/v1/models \
-H "Authorization: Bearer sk-local-123"
Python 里把 api_key="sk-local-123" 传给 OpenAI(...) 即可。
注意:这次没写 -c,启动日志里每个槽位的上下文变成了 n_ctx_slot = 262144,即模型支持的最大长度。llama.cpp 会按可用内存自动调整,但长上下文占的内存更多,内存紧张的机器建议明确写 -c。
第 6 步:Anthropic 格式和原生接口
同一个服务还提供 Anthropic Messages 格式的 /v1/messages,用 Anthropic SDK 写的应用也能直接接:
curl -s http://localhost:9931/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: any" \
-d '{"model": "qwen3.5-0.8b", "max_tokens": 200,
"messages": [{"role": "user",
"content": "用一句话介绍 llama.cpp。"}]}'
对照结果(2026-10-27 实测,未设 --api-key 的服务,节选):
type: message stop_reason: end_turn
usage: {'input_tokens': 18, 'output_tokens': 45}
Llama.cpp 是用于部署 Llama 3 模型的开源 C++ 版本,……
返回格式是 Anthropic 的(type: message、stop_reason: end_turn)。回答内容不准确:llama.cpp 并不只服务 Llama 3。这又是小模型的事实性问题,不是接口问题。
llama.cpp 还有一组原生接口,调试时很有用:
# 看一段文字会被切成几个 token
curl -s http://localhost:9931/tokenize \
-H "Content-Type: application/json" \
-d '{"content": "你好,llama.cpp"}'
# 查看服务和模型属性
curl -s http://localhost:9931/props
对照结果(2026-10-27 实测,/props 只摘 4 个字段):
{"tokens":[109266,3709,636,2933,6982]}
total_slots: 4 model_alias: qwen3.5-0.8b n_ctx: 8192
modalities: {'vision': False, 'video': False, 'audio': False}
total_slots: 4 表示能同时处理 4 个请求;要调整用 -np,例如 -np 2。
第 7 步:浏览器里的聊天界面
本节按官方 Web UI 文档描述,未在云主机的浏览器里实测。服务起来后,用浏览器打开 http://localhost:9931 就是内置的聊天界面:支持 Markdown、公式、代码高亮,思考型模型的思考过程会折叠显示;可以拖入图片、PDF 或音频(要模型本身支持);可以编辑历史消息分叉对话,导入导出 JSON。只想要 API 不要网页,启动时加 --no-webui。
小结
llama serve -hf 仓库:量化 -a 别名起服务,/health返回 200 再发请求;- 回答为空先看
finish_reason和reasoning_content,用-rea off或调大max_tokens; - OpenAI SDK 只改
base_url;对外开放必须加--api-key; - 同一服务还能用 Anthropic
/v1/messages格式调用。
下一课做结构化输出、工具调用、向量检索,并把本地模型接到编程智能体上。
