之前的方案是 FastAPI 调用 Ollama,把 qwen2:0.5b 打进 Ollama 镜像做离线部署。问题在于 Ollama 镜像太大 (基础镜像约 2.6G;上一篇里打包好的 llm-serving-ollama:0.1.0 为 4.58GB),而实际只需要"加载一个 0.5B 的 GGUF 模型并提供推理接口"。
llama.cpp 自带的 llama-server 正好满足这个需求:
- 直接加载 GGUF 文件,不需要 Ollama 的模型库、拉取、管理那一整套机制;
- 原生提供 OpenAI 兼容接口(
/v1/chat/completions、/v1/models)和/health; - 模型文件复制进镜像即可离线运行。
这一次:下载模型、准备基础镜像、编写 Dockerfile、构建 llm-serving-llama:0.1.0。镜像的运行验证、Helm 配置和 Kubernetes 部署后面更新。
过程概览
| 步骤 | 操作 | 关键内容 |
|---|---|---|
| 1 | 下载 GGUF 模型 | 下载 Qwen2-0.5B-Instruct 的 Q4_K_M 模型至 llama/models/ |
| 2 | 下载基础镜像 | docker pull ghcr.io/ggml-org/llama.cpp:server |
| 3 | 编写 Dockerfile | 配置模型复制、llama-server 启动参数和监听端口 8080 |
| 4 | 构建本地镜像 | 构建 llm-serving-llama:0.1.0,使用 --pull=false |
步骤 4 有原始日志;步骤 1~3 在日志中没有直接输出,下文按"补充"处理。
验证结果
- 镜像
llm-serving-llama:0.1.0构建成功,首次构建 96.5s(含拉取基础镜像),镜像 IDbb93e849f185...。 - 基础镜像已在本地时,用
--pull=false重新构建只需 1.4s,所有层命中缓存。 - 模型文件复制进镜像仅用 1.0s,构建上下文约 397.90MB,即模型文件的大小。
环境信息(来自日志)
- 宿主机:MacBook,Docker Desktop(
docker:desktop-linux) - 项目目录:
llm-serving - 基础镜像:
ghcr.io/ggml-org/llama.cpp:server(digestsha256:fc9b506e...8ed2f6) - 模型文件:
qwen2-0_5b-instruct-q4_k_m.gguf - 目标镜像:
llm-serving-llama:0.1.0
一、准备模型与基础镜像
1.1 下载 GGUF 模型(补充)
日志中没有记录下载过程,只有构建时的结果:模型文件位于 llama/models/qwen2-0_5b-instruct-q4_k_m.gguf,构建上下文传输了约 397.90MB。该文件通常来自 Hugging Face 上 Qwen 官方发布的 GGUF 仓库(例如 Qwen/Qwen2-0.5B-Instruct-GGUF)。下面是一个示例做法,请按你实际的下载方式调整:
bash
mkdir -p llama/models
huggingface-cli download Qwen/Qwen2-0.5B-Instruct-GGUF \
qwen2-0_5b-instruct-q4_k_m.gguf \
--local-dir llama/models
ls -lh llama/models
选择 Q4_K_M 量化是体积与质量的折中:约 390MB 的模型文件,CPU 上就能运行。
1.2 下载基础镜像
bash
docker pull ghcr.io/ggml-org/llama.cpp:server
这是 llama.cpp 官方发布在 GitHub Container Registry 上的 server 镜像(只含 llama-server)。从日志看,如果本地没有这个镜像,docker build 会在构建过程中自动拉取(见 2.2 的构建输出)。
二、构建 llama.cpp 离线镜像
2.1 编写 Dockerfile(补充)
日志中没有贴出 llama/Dockerfile。根据构建步骤(WORKDIR /app、COPY models/... /models/...,大小 322B),以及后续容器日志中的信息(模型路径 /models/qwen2-0_5b-instruct-q4_k_m.gguf、监听 0.0.0.0:8080、启动命令 /app/llama-server、模型 id 为 qwen2:0.5b),可以推断它大致如下,仅供参考:
dockerfile
FROM ghcr.io/ggml-org/llama.cpp:server
WORKDIR /app
COPY models/qwen2-0_5b-instruct-q4_k_m.gguf /models/qwen2-0_5b-instruct-q4_k_m.gguf
EXPOSE 8080
ENTRYPOINT ["/app/llama-server"]
CMD ["-m", "/models/qwen2-0_5b-instruct-q4_k_m.gguf", \
"--host", "0.0.0.0", \
"--port", "8080", \
"--alias", "qwen2:0.5b"]
几点说明:
- 基础镜像里预设了
LLAMA_ARG_HOST、LLAMA_ARG_PORT环境变量,而启动命令里显式传了--host和--port,以命令行参数为准(容器日志里有对应的warn提示)。 - 监听端口定为 8080,后续容器端口映射和 Kubernetes Service 都要与它对应。
- 用
--alias把模型 id 设为qwen2:0.5b,与之前 Ollama 方案中的模型名一致,上层 FastAPI 的DEFAULT_MODEL不用改。
2.2 构建镜像
bash
(.venv) (base) igwanglyang@MacbookProdahouzicn llm-serving % docker build \
-t llm-serving-llama:0.1.0 \
-f llama/Dockerfile \
llama
[+] Building 96.5s (9/9) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 322B 0.0s
=> [internal] load metadata for ghcr.io/ggml-org/llama.cpp:server 3.3s
=> [auth] ggml-org/llama.cpp:pull token for ghcr.io 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/3] FROM ghcr.io/ggml-org/llama.cpp:server@sha256:fc9b506e89c1a1cfdd7bc056480f54dc657c1c04d76839a08642dc280b8ed2f6 91.4s
=> => resolve ghcr.io/ggml-org/llama.cpp:server@sha256:fc9b506e89c1a1cfdd7bc056480f54dc657c1c04d76839a08642dc280b8ed2f6 0.0s
=> => sha256:8cd4a0822fc4b025faa5ad678bc5076727907e00997ba8fcf5ead1a40a587723 27.59kB / 27.59kB 1.1s
=> => sha256:fc9b506e89c1a1cfdd7bc056480f54dc657c1c04d76839a08642dc280b8ed2f6 1.42kB / 1.42kB 0.0s
=> => sha256:16fc96974cd6c0e7c8f9a2a5085446279dff2c00dda73a7b64a4ef66ab2eb379 1.75kB / 1.75kB 0.0s
=> => sha256:147880ee65ff2c9b6ddff83c26f4ec71dd4eb191b1c269eec4d5dbf7af8a17ea 4.89kB / 4.89kB 0.0s
=> => sha256:3dd7229bb5739a4d9511ab52141929d26651f52c5e9edb82e2af9c8d33e88d8a 255.44MB / 255.44MB 86.5s
=> => sha256:b3b8bb43fdfa2740e7c7310376ed90a66f6898d8654c2430b597bd655fd434f6 13.41MB / 13.41MB 9.0s
=> => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B 1.9s
=> => extracting sha256:3dd7229bb5739a4d9511ab52141929d26651f52c5e9edb82e2af9c8d33e88d8a 4.6s
=> => extracting sha256:b3b8bb43fdfa2740e7c7310376ed90a66f6898d8654c2430b597bd655fd434f6 0.1s
=> => extracting sha256:8cd4a0822fc4b025faa5ad678bc5076727907e00997ba8fcf5ead1a40a587723 0.0s
=> => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 0.0s
=> [internal] load build context 3.3s
=> => transferring context: 397.90MB 3.1s
=> [2/3] WORKDIR /app 0.2s
=> [3/3] COPY models/qwen2-0_5b-instruct-q4_k_m.gguf /models/qwen2-0_5b-instruct-q4_k_m.gguf 1.0s
=> exporting to image 0.5s
=> => exporting layers 0.5s
=> => writing image sha256:bb93e849f185ccdaecf908cb67cbe7e667d4ce44872992e1a8c9a5408d4f336b 0.0s
=> => naming to docker.io/library/llm-serving-llama:0.1.0 0.0s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/wxxd6lzdc6ne72uzbi344w0mm
原始输出的列间距很宽,这里压缩了空白,内容没有改动。
构建输出里的要点:
- 基础镜像拉取占了大头 :主要层是一个 255.44MB 的层加一个 13.41MB 的层,
[1/3] FROM一步就耗时 91.4s。 - 模型复制很快 :
COPY models/...gguf只用了 1.0s;构建上下文397.90MB传输用了 3.1s。 - 镜像最终命名为
docker.io/library/llm-serving-llama:0.1.0。
2.3 基础镜像已在本地时,用 --pull=false 重新构建
基础镜像已经下载过,再次构建时可以加 --pull=false,避免构建时去检查/拉取基础镜像(在网络受限、镜像源不稳定的环境下尤其有用):
bash
(.venv) (base) igwanglyang@MacbookProdahouzicn llm-serving % docker build \
--pull=false \
-t llm-serving-llama:0.1.0 \
-f llama/Dockerfile \
llama
[+] Building 1.4s (9/9) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 322B 0.0s
=> [internal] load metadata for ghcr.io/ggml-org/llama.cpp:server 1.3s
=> [auth] ggml-org/llama.cpp:pull token for ghcr.io 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/3] FROM ghcr.io/ggml-org/llama.cpp:server@sha256:fc9b506e89c1a1cfdd7bc056480f54dc657c1c04d76839a08642dc280b8ed2f6 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 89B 0.0s
=> CACHED [2/3] WORKDIR /app 0.0s
=> CACHED [3/3] COPY models/qwen2-0_5b-instruct-q4_k_m.gguf /models/qwen2-0_5b-instruct-q4_k_m.gguf 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:0c9f67dd3312101f8d1eab7bf083a764e1f5f184453c2909a63aa45bb31ef46b 0.0s
=> => naming to docker.io/library/llm-serving-llama:0.1.0 0.0s
这次构建只花了 1.4s:所有层命中缓存,构建上下文只有 89B(Docker 判断模型文件没有变化)。
注意 :
FROM要写完整的镜像地址ghcr.io/ggml-org/llama.cpp:server。如果只写成llama.cpp:server,Docker 会把它当作 Docker Hub 上的library/llama.cpp,解析失败。
三、确认镜像(补充)
日志里没有列出镜像大小。建议在构建完成后执行下面的命令,确认镜像已生成并记录体积,用来和之前的 Ollama 离线镜像对比:
bash
docker images | grep llm-serving
对比参考:上一篇日志中 llm-serving-ollama:0.1.0 为 4.58GB 。llama.cpp 镜像由约 270MB 的基础镜像层(255.44MB + 13.41MB)加上约 390MB 的模型文件组成,体积预期会明显小于 Ollama 方案;具体数字以你机器上 docker images 的实际输出为准,请补进文章。
总结
把 llama.cpp 打包成离线镜像的完整路径:
bash
# 1. 准备模型和基础镜像
huggingface-cli download Qwen/Qwen2-0.5B-Instruct-GGUF qwen2-0_5b-instruct-q4_k_m.gguf --local-dir llama/models
docker pull ghcr.io/ggml-org/llama.cpp:server
# 2. 构建镜像(首次)
docker build -t llm-serving-llama:0.1.0 -f llama/Dockerfile llama
# 3. 基础镜像已在本地时重建
docker build --pull=false -t llm-serving-llama:0.1.0 -f llama/Dockerfile llama
# 4. 确认镜像
docker images | grep llm-serving
经验点:
- 按需选型 :只需要"加载 GGUF 并提供推理接口"时,
llama-server比 Ollama 精简得多,不用带上模型管理那一套。 - 模型直接
COPY进镜像:一次构建,处处离线可跑;模型复制只需约 1 秒,耗时主要在首次拉取基础镜像。 --pull=false与完整镜像地址 :基础镜像在本地时用--pull=false加快构建;FROM始终写完整的ghcr.io/...地址。- 提前对齐端口和模型名 :Dockerfile 里固定监听 8080、模型别名
qwen2:0.5b,方便后续与 FastAPI、Kubernetes 配置对接。