Ollama 部署 Qwen2.5-0.5B 实验总结¶
〇. 全局思路:用 Docker 类比理解 Ollama¶
先看这段,建立整体认知再往下读细节。
Ollama ≈ 管理模型的 Docker:Docker 管理"镜像",Ollama 管理"模型"——都是"从仓库拉到本地 → 按名字管理 → 跑起来对外服务"。
| Docker 步骤 | Ollama 对应步骤 | 本实验实际做法 |
|---|---|---|
| 装 Docker | 装 Ollama | 官方脚本下载慢 → gh-proxy.com 代理下载 1.4G 包,bin+lib 都要装 |
| 起服务 | ollama serve(前台) |
默认前台跑 |
| Docker Hub 拉镜像 | Ollama 官方源拉模型 | 海外源拉不动 |
| 国内镜像站下 tar 包 | 魔搭直链下 GGUF | wget 魔搭直链,469MB |
docker load 导入 |
ollama create 导入 |
写 Modelfile → ollama create qwen05 |
docker run 启动 |
ollama run qwen05 |
对话 / API 调用 |
| systemd 托管 Docker | systemd 托管 Ollama | 写 service 文件 → systemctl enable --now |
完整链路一句话:装 Ollama → 下 GGUF → ollama create 导入 → ollama run 启动 → 写 systemd 转后台常驻。
唯一差异点:Ollama 模型没有 Docker 那种"分层镜像"概念,模型就是一堆文件 + 一个 Modelfile 描述,没有构建复杂度。这套"下文件 → 起服务 → 暴露 API"的心智模型以后学 vLLM / SGLang 同样适用。
一. 实验说明¶
1. 实验目的¶
在纯 CPU 的阿里云服务器上把"本地部署大模型"整条链路走通:装推理框架 → 下载模型 → 导入 → 对话 → API 调用。选择 0.5B 模型是因为它体积小(约 500MB)、CPU 就能跑,适合当练手材料。
注:0.5B 是玩具级模型,做正经翻译/推理质量很差。本实验的价值是低成本把部署链路跑通,为以后部署 7B/13B 或 GPU 推理(vLLM)打基础。
2. 实验环境¶
| 项目 | 值 |
|---|---|
| 服务器 | 阿里云 ECS(主机名 my-yun),纯 CPU 无 GPU |
| 系统 | RockyLinux 系,包管理 dnf |
| Python | 3.6(过老,导致 modelscope 工具不可用,见 四.1) |
| 模型 | Qwen2.5-0.5B-Instruct-GGUF(q4_k_m 量化,约 469MB) |
| 推理框架 | Ollama 0.33.3 |
3. 结论¶
- ✅ 整条链路跑通:模型能对话、API 能调用、systemd 托管成功
- ✅ 模型下载走魔搭直链,阿里云服务器上速度飞快,无墙问题
- ⚠️ 全程踩了 6 个坑(海外源慢、缺 zstd、Python 过老、段错误……),见 九. 踩坑记录汇总
二. 关键概念(先搞懂再动手)¶
| 名词 | 解释 |
|---|---|
| 推理框架 | 把模型"跑起来"并提供接口的软件,如 Ollama / llama.cpp / vLLM |
| Ollama | LLM 界的 "Docker":负责加载模型、对外提供 OpenAI 兼容 API,本身不生产模型 |
| 魔搭 ModelScope | LLM 界的 "Docker Hub":模型仓库,Qwen/DeepSeek 都有,只负责存和下载 |
| GGUF | 适合 CPU 加载的模型文件格式,支持量化 |
| 量化(Q4_K_M) | 把权重从 16bit 压到 4bit,体积小、速度快,精度略降 |
| OpenAI 兼容 API | 各框架统一模仿 OpenAI 的 /v1/chat/completions 接口,客户端可通用对接 |
一句话流程:魔搭(仓库)下载 GGUF → Ollama(运行时)加载 → 11434 端口提供 API。
用 Docker 类比:Docker Hub 拉不动 → 从国内镜像下 tar 包 → docker load 导入再跑,ollama create 干的就是 docker load 的活。
三. 安装 Ollama¶
1. 坑①:官方脚本报缺 zstd¶
[root@my-yun ~]# curl -fsSL https://ollama.com/install.sh | sh
>>> Installing ollama to /usr/local
ERROR: This version requires zstd for extraction. Please install zstd and try again:
- RHEL/CentOS/Fedora: sudo dnf install zstd
原因:安装包是 zstd 压缩格式,系统缺解压工具。解决:装 zstd 后重跑(以下为纯执行命令,可整段复制粘贴):
2. 坑②:官方脚本卡死在海外下载源¶
[root@my-yun ~]# curl -fsSL https://ollama.com/install.sh | sh
>>> Cleaning up old version at /usr/local/lib/ollama
>>> Installing ollama to /usr/local
>>> Downloading ollama-linux-amd64.tar.zst
0.1%
注:阿里云服务器访问 ollama.com/GitHub 走国际出口,速度约 0.1%~25KB/s(下载包 1.4GB 要十几个小时且随时断)。跟本地网络无关,服务器能通也下不动。解决:走 GitHub 加速代理。
3. 代理测速,挑最快的¶
[root@my-yun ~]# for p in ghfast.top gh-proxy.com mirror.ghproxy.com ghproxy.net gh.llkk.cc; do
echo "== $p =="
curl -sL -o /dev/null --max-time 8 -w '速度: %{speed_download} B/s\n' \
"https://$p/https://github.com/ollama/ollama/releases/latest/download/ollama-linux-amd64.tar.zst"
done
测速结果汇总:
| 代理 | 速度 | 结论 |
|---|---|---|
| gh-proxy.com | 24.4 MB/s | ✅ 选它 |
| ghfast.top | 41 KB/s | 太慢 |
| ghproxy.net | 26 KB/s | 太慢 |
| mirror.ghproxy.com | 0 | 不通 |
| gh.llkk.cc | 0 | 不通 |
4. 用 gh-proxy.com 下载安装包(24MB/s,约 1 分钟下完)¶
cd /tmp
wget -c "https://gh-proxy.com/https://github.com/ollama/ollama/releases/latest/download/ollama-linux-amd64.tar.zst" -O ollama.tar.zst
注:
-c断点续传,中断后重跑同一条命令接着下。GitHub 加速代理是社区公益的,域名常换,失效就重新测速。
下载完校验是"真包"不是错误页:
[root@my-yun tmp]# ls -lh ollama.tar.zst
-rw-r--r-- 1 root root 1.4G Sep 4 01:15 /tmp/ollama.tar.zst
[root@my-yun tmp]# file ollama.tar.zst
/tmp/ollama.tar.zst: Zstandard compressed data (v0.8+), Dictionary ID: None
5. 坑③:系统 tar 不支持 --zstd,分两步解压¶
zstd -d /tmp/ollama.tar.zst -o /tmp/ollama.tar
mkdir -p /opt/ollama-pkg
tar -xf /tmp/ollama.tar -C /opt/ollama-pkg
zstd 解压会打印解压后大小(2.2GB 正常,解压后比压缩包大):
[root@my-yun tmp]# zstd -d /tmp/ollama.tar.zst -o /tmp/ollama.tar
/tmp/ollama.tar.zst : 2262210560 bytes
6. 坑④:只装 bin 导致 Segfault(段错误)¶
第一次只把 bin/ollama(误以为 12M 残缺文件就是全部)install 到系统,结果 ollama create / ollama list 全部段错误:
排查 file 发现是残缺的 ELF:
[root@my-yun tmp]# file /usr/local/bin/ollama
/usr/local/bin/ollama: ELF 64-bit LSB shared object, x86-64, ..., missing section headers
原因:Ollama 是"外壳 + 引擎库"结构——bin/ollama 只是主程序(38M),真正干推理的引擎在 lib/ollama/ 目录下几百 MB 的 .so 共享库。只装 bin 不装 lib,程序运行到一半找不到自己的代码就崩。
正确安装:bin 和 lib 都要装(纯执行命令):
install -m 755 /opt/ollama-pkg/bin/ollama /usr/local/bin/ollama
mkdir -p /usr/local/lib/ollama
cp -a /opt/ollama-pkg/lib/ollama/. /usr/local/lib/ollama/
验证版本(第一条 Warning 只是提示服务没启动,能出版本号 = 装好了):
[root@my-yun tmp]# ollama --version
Warning: could not connect to a running Ollama instance
Warning: client version is 0.33.3
四. 下载模型(魔搭直链,国内飞快)¶
1. 坑⑤:modelscope 工具跑不起来(Python 3.6 太老)¶
魔搭官方下载工具装上了但报语法错误:
[root@my-yun ~]# modelscope download --model Qwen/Qwen2.5-0.5B-Instruct-GGUF
File "/usr/local/lib/python3.6/site-packages/modelscope/utils/file_utils.py", line 238
while byte_chunk := f.read(buffer_size):
^
SyntaxError: invalid syntax
注:
:=叫海象运算符,Python 3.8 才引入,系统的 Python 3.6 直接语法报错。不要为了它升级系统 Python(容易搞坏系统),绕开它用 wget 直链下载。
2. 用 wget 下魔搭直链¶
魔搭每个文件都有直链,格式:仓库地址/resolve/master/文件名(纯执行命令):
mkdir -p /data/models/qwen05
cd /data/models/qwen05
wget -c "https://modelscope.cn/models/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/master/qwen2.5-0.5b-instruct-q4_k_m.gguf" -O qwen2.5-0.5b-instruct-q4_k_m.gguf
魔搭是阿里系,服务器上速度飞快,几十秒下完。校验大小(几 KB = 下到错误页,几百 MB 才对):
[root@my-yun qwen05]# ls -lh qwen2.5-0.5b-instruct-q4_k_m.gguf
-rw-r--r-- 1 root root 469M May 21 09:01 qwen2.5-0.5b-instruct-q4_k_m.gguf
注:模型仓库页面文件列表里给的是字节数(如 428730208 ≈ 409MB)而不是"409MB"这种写法;不同量化版本(q2_k/q4_0/q4_k_m/q8_0/fp16)体积差异大,下载前在网页核对文件名,别下错版本也别下整个仓库。
五. 导入模型并对话¶
1. 写 Modelfile 并导入(ollama create 等价于 docker load)¶
cat > /data/models/qwen05/Modelfile <<'EOF'
FROM /data/models/qwen05/qwen2.5-0.5b-instruct-q4_k_m.gguf
EOF
导入前需要 ollama 服务在运行,另开终端执行:
[root@my-yun ~]# ollama create qwen05 -f /data/models/qwen05/Modelfile
gathering model components
copying file sha256:74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db 100%
parsing GGUF
verifying conversion
creating new layer sha256:969a560cd5d289a304ed169d293a9c6687c088ac5390077690d7e567cc938ee7
writing manifest
success
[root@my-yun ~]# ollama list
NAME ID SIZE MODIFIED
qwen05:latest 42660c488ef5 491 MB 3 minutes ago
注:
ollama create里的qwen05是自定义的本地名字,以后 run/API 调用都用它,与官方命名无关。模型数据默认存在/root/.ollama下。
2. 对话测试¶
坑⑥:从网页/聊天窗口复制命令会带上隐藏的控制字符(如
11;rgb:2828/2a2a/3636),粘贴后要么被当成命令执行(-bash: 11: command not found),要么混进输入内容。对策:粘贴命令先放到纯文本编辑器过一遍;终端被弄乱执行reset恢复。
六. API 验证(部署实验的核心)¶
Ollama 自动提供 OpenAI 兼容接口,这是以后接一切客户端(OpenWebUI、翻译工具等)的入口:
[root@my-yun ~]# curl -s http://127.0.0.1:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen05","messages":[{"role":"user","content":"1+1等于几"}],"stream":false}'
返回 JSON(标准 OpenAI 格式):
{
"id": "chatcmpl-950",
"object": "chat.completion",
"created": 1788915799,
"model": "qwen05",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "1+1等于2。" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 34, "completion_tokens": 7, "total_tokens": 41 }
}
注:关键字段——
choices[0].message.content是模型回答;finish_reason: stop表示正常结束;usage是 token 统计。返回这个格式 = 任何 OpenAI 客户端把 base_url 指向http://<服务器IP>:11434/v1即可对接。
1. 这一步到底在做什么¶
本质是亲手扮演一次"客户端程序",向模型服务发了一个 HTTP 请求并收到回复。
用 Docker 类比:容器起来后 curl http://localhost:8080 验证网站通不通,那个 curl 是在模拟浏览器访问。这里的 curl 就是模拟任何 AI 客户端(OpenWebUI/翻译工具/agent 内部替你发的都是这种请求)。
请求里带了三样东西:
| 参数 | 含义 |
|---|---|
model |
调哪个模型(qwen05) |
messages |
对话内容(role=user 表示用户说的话) |
stream |
是否流式返回(false = 一次给完整回答) |
意义:证明模型已不是"能聊天的程序",而是"按标准协议对外提供服务的接口"——这是部署与"自己跑着玩"的分水岭。
2. 需要生成 API Key 吗?——不需要¶
Ollama 不生成 API Key,也不校验 Key。 原因:
| OpenAI 官网 | 本地 Ollama | |
|---|---|---|
| 为什么有 Key | 用别人的服务器,要验证身份 + 计费 | 服务只监听 127.0.0.1(自己的机器) |
| Key 的作用 | 你是谁、给你算钱 | 不需要——本地已经信任你 |
在 agent / AI 客户端里对接本地模型时,配置项长这样——api_key 填任意占位符即可:
base_url = http://<服务器IP>:11434/v1 # 指向谁(关键)
model = qwen05 # 用哪个模型(关键)
api_key = ollama # 任意占位符,Ollama 不校验
什么时候才真的需要 Key:Key = 身份验证 + 配额管理,只有"多人/跨网络使用"才需要。以后要把模型开放给别人用,正确姿势是在前面加一层 API 网关(如开源 new-api/one-api,负责发 Key、限流、审计),再转发给后端 Ollama——思路跟 nginx 反代 + 鉴权是一路的。
七. systemd 托管(后台常驻 + 开机自启)¶
手动安装的 ollama 没有注册 systemd,前台 ollama serve 一关终端就没了。写服务文件并启用(纯执行命令):
pkill -f "ollama serve"
cat > /etc/systemd/system/ollama.service <<'EOF'
[Unit]
Description=Ollama Service
After=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/ollama serve
Restart=always
RestartSec=3
Environment="OLLAMA_HOST=127.0.0.1:11434"
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now ollama
验证服务状态,应看到 Active: active (running):
再测一次 API 确认 systemd 托管后一切正常:
[root@my-yun ~]# curl -s http://127.0.0.1:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen05","messages":[{"role":"user","content":"你好"}],"stream":false}'
{"id":"chatcmpl-863","object":"chat.completion","created":1788915920,"model":"qwen05","system_fingerprint":"fp_ollama","choices":[{"index":0,"message":{"role":"assistant","content":"你好! 我是中国阿里云开发的一个AI助手。有什么我可以帮你的好吗?"},"finish_reason":"stop"}],"usage":{"prompt_tokens":30,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens":19,"total_tokens":49}}
⚠️ 两个注意点: ① 实验用了
User=root,因为模型存在/root/.ollama下,换用户会找不到模型。个人实验机无所谓,生产环境应建低权限专用用户并迁移模型目录。 ②OLLAMA_HOST保持127.0.0.1(只本机访问)。11434 端口没有鉴权,开0.0.0.0等于裸奔——云服务器对外提供必须加安全组白名单或 nginx 反代鉴权。
八. 清理临时文件¶
九. 踩坑记录汇总¶
| # | 坑 | 现象 | 原因 | 解决 |
|---|---|---|---|---|
| 1 | 缺 zstd | 官方脚本报错退出 | 系统没有 zstd 解压工具 | dnf -y install zstd |
| 2 | 海外源下载慢 | 0.1%~25KB/s,十几个小时 | ollama.com/GitHub 走国际出口 | gh-proxy.com 代理 24MB/s |
| 3 | tar 不认 zstd | tar --zstd 报 unrecognized option |
系统 tar 版本老 | zstd -d 解出 .tar 再 tar -xf |
| 4 | 段错误 | ollama 命令 Segmentation fault | 只装了 bin 缺 lib/ollama 引擎库 | 补装 /usr/local/lib/ollama |
| 5 | modelscope 用不了 | Python 语法报错 | 系统 Python 3.6 < 3.8(海象运算符) | 放弃工具,用 wget 魔搭直链 |
| 6 | 粘贴乱码 | 终端出现 11;rgb:... 或 command not found |
复制带隐藏 ANSI 控制字符 | 纯文本中转;reset 恢复 |
十. 推理框架全家桶(先认清各自定位)¶
本实验用的 Ollama 只是推理框架的一种,完整的生态是:
| 框架 | 定位 | 底层 | 硬件 | 难度 | 同类关系 |
|---|---|---|---|---|---|
| Ollama | 开箱即用,个人/玩具 | llama.cpp | CPU/单卡 | 一条命令 | Docker 之于容器 |
| llama.cpp | Ollama 的底层引擎 | 自研 C++ | CPU/GPU | 中 | containerd 之于 Docker |
| vLLM | 生产主流,高并发 | 自研调度 | 必须 GPU | 高 | SGLang 的竞品 |
| SGLang | 生产新锐,R1 部署出名 | 自研调度 + RadixAttention | 必须 GPU | 高 | vLLM 的竞品 |
易混点:Ollama 和 SGLang 不是 Docker/containerd 那种"高层包底层"的分层关系,而是同级竞争——都是独立的"跑模型的服务"。Docker/containerd 的分层类比只适用于 Ollama/llama.cpp。 通俗理解:Ollama 是傻瓜相机(举起就拍),vLLM/SGLang 是专业电影机(画质天花板但要会调参)。SGLang 因 DeepSeek 官方用它部署 R1 而火,适合 GPU + 高并发生产场景,0.5B/CPU 场景用不上。
1. Ollama vs SGLang:不是 minikube vs K8s¶
常见误解:把 Ollama/SGLang 类比成 minikube/K8s(同一个软件的单机版 vs 集群版)。不对:
| minikube vs K8s | Ollama vs SGLang | |
|---|---|---|
| 关系 | 同一个软件,单节点版 vs 集群版 | 两个独立软件,不同团队开发 |
| 本质 | 规模不同 | 定位、引擎、目标硬件都不同 |
两者是两套独立引擎,不存在"Ollama 是 SGLang 的单机版"。更像 MySQL vs PostgreSQL:都是数据库,但不是同一款的大小号。
真正区别是天花板不同:Ollama 只能单机跑;SGLang/vLLM 单卡能跑、单机多卡能跑、多机集群也能跑——它们"支持到集群",不是"必须集群"。
2. 大参数模型需要集群吗?——分三档看¶
不一定。显存需求 ≈ 参数量 × 每参数字节数(fp16 = 2 字节),模型能否放下算一笔账就知道:
| 模型规模 | 显存需求(约) | 部署形态 | 例子 |
|---|---|---|---|
| ≤ 7B / 14B | 量化后 < 24GB | 单卡就够 | 租一张 T4/4090 |
| 32B / 70B | 140GB+(fp16) | 单机多卡(2~8 张 80GB 卡) | 8×H100 跑 Llama-70B |
| 千亿级 | 1.3TB+ | 多机集群(几十张卡以上) | DeepSeek R1(671B) |
三个容易误解的点:
- 单机插 8 张显卡 ≠ 集群:那叫"单机多卡",vLLM/SGLang 用张量并行把它当"一张大卡"用。多机集群只有千亿级模型才真需要——R1 当时轰动的原因之一就是它连单机都放不下。
- 验证 70B 为什么单卡不行:700亿 × 2 字节 = 140GB,一张 80GB 卡放不下 → 需要 2 张。而 0.5B 量化后 ≈ 5亿 × 0.5 字节 ≈ 250MB,CPU 内存都绰绰有余。
- 部署规模路线图:
0.5B(CPU) → 7B(单张24G卡) → 14B(单张24G卡) → 70B(单机多卡) → 千亿(集群)。集群是终点不是起点,绝大多数公司生产场景(7B~70B)用单机多卡就覆盖了。
3. SGLang 需要 Ollama 当底层吗?——不需要,它是"一体机"¶
SGLang 自带引擎 + API 服务,不经过 Ollama。 类比错误的根源在于:K8s 是纯"编排层",自己不跑容器所以必须调 containerd;而 SGLang 不是纯编排层——它把"引擎(执行)+ API 服务"做在了一起,扮演的是 containerd + Docker 合体的角色。
容器 vs 推理世界的完整分层对照:
| 容器世界 | AI 推理世界 | 说明 |
|---|---|---|
| 容器镜像 | 模型文件(GGUF / safetensors) | 都是"数据/产物" |
| containerd(引擎) | llama.cpp / SGLang / vLLM 的引擎 | 真正把模型跑起来的计算核心 |
| Docker(封装层) | Ollama | 把引擎包成"一条命令" |
| K8s(编排层) | 多机调度(SGLang 支持的多节点) | 大规模时才需要 |
由此得出三句结论:
- Ollama 站在"封装层",内部偷偷用 llama.cpp 当引擎(Ollama 的 containerd = llama.cpp)
- SGLang 站在"引擎层",自己就是干活的那层,不需要任何人包它,只要喂模型文件
- 模型文件格式也不同:Ollama 吃 GGUF(为 CPU/llama.cpp 优化),SGLang 吃 HuggingFace 原始权重 safetensors(为 GPU/自研引擎优化)——再次印证两者是"两个物种"
实际使用对比:
# 风格一:Ollama(封装层,面向小白)——命令简单,引擎细节全隐藏
ollama run qwen05
# 风格二:SGLang(引擎层,面向工程师)——直接给模型路径,自带 OpenAI 兼容服务
python -m sglang.launch_server \
--model-path /data/models/Qwen2.5-7B-Instruct \
--port 30000
4. 通识三件套:这次实验顺带搞懂了什么¶
部署本身是一次性知识,真正值钱的是顺带搞懂的三个底层通识——剥掉 AI 外壳,它们都是运维老基本功的"换皮":
| 搞懂的 | 其实是 | 在哪见过 |
|---|---|---|
| 显存算账 | 容量规划 | 磁盘/内存/机器数量预估 |
| Key 不需要 | 信任边界与鉴权 | 端口绑定、防火墙、密码策略 |
| 框架分层 | 抽象分层 | Docker/containerd/K8s |
① 显存算账 = 容量规划。模型就是一张巨大的数表(权重):fp16 每个数 2 字节 → 70B = 700亿 × 2 = 140GB,一张 80G 卡放不下 → 要多卡。任何系统的第一问都是"资源够不够":数据库问磁盘、模型问显存,同一个本事。
显存算账的完整公式(换任何卡直接套):
| 精度 | 每参数字节 | 6G 卡理论能装 | 说明 |
|---|---|---|---|
| fp16 | 2 字节 | 3B | "÷2"只对这一行成立 |
| int8 | 1 字节 | 6B | 很少用 |
| Q4_K_M | ~0.6 字节 | ~10B | 实际部署主力(÷0.6 ≈ ×1.6) |
注意 "能吃满"是伪命题,三个吃不满的因素:
- 驱动固定开销:一启动先吃约 0.5~1GB(图形驱动、上下文),是"房租"
- KV Cache:上下文越长额外显存越多(长对话比短对话更吃显存)
- 必须留余量:满载会 OOM 崩溃,实际留 10~20%
实用预算:模型文件 ≤ 总显存 − 1~1.5GB。以 1060 Ti 6G 为例(实际可用约 4.5~5GB):
| 模型 | 量化 | 文件大小 | 6G 卡结论 |
|---|---|---|---|
| Qwen2.5-3B | Q4 | ~1.9GB | ✅ 舒服,上下文能开大 |
| Qwen2.5-7B | Q4_K_M | ~4.7GB | ⚠️ 极限:能跑但上下文别超 4K,再长 OOM |
| Qwen2.5-14B | Q4 | ~9GB | ❌ 放不下 |
老卡特有提醒(Pascal 架构如 1060 Ti 没有 Tensor Core,fp16 性能很弱):别跑 fp16,用 Q4 量化最合适(走 CUDA core 绕开短板)。验证 GPU 真在干活:ollama run 起来后另开终端 nvidia-smi,显存被占、利用率跳动 = 在用 GPU;显示 0% = 在偷偷用 CPU 跑(老卡驱动/支持问题)。
② 本地不需要 Key = 信任边界。Key 存在的唯一理由是"区分陌生人和自己人":OpenAI 全世界都能连 → 必须 Key 计费鉴权;Ollama 只听 127.0.0.1,已经确定是你 → 再要 Key 多此一举(跟 Redis 绑 127.0.0.1 不需要密码一个道理)。运维天天在判断"哪些资源要鉴权",AI 只是把同一问题又问了一遍。
③ 框架分层 = 抽象分层。llama.cpp/SGLang 是引擎层、Ollama 是封装层、多机调度是编排层——AI 把"容器生态的每一层"都重演了一遍。遇到新框架先问"它站在哪一层"(引擎?封装?编排?),这一问能省 80% 的学习弯路。
十一. 后续方向¶
- 换大模型对比:同样流程导入 1.5B/3B(7B 需内存 ≥ 8GB),感受"质量 / 速度 / 内存"三角关系。
- 学 llama.cpp:用
llama-server直接跑同一个 GGUF,理解它和 Ollama 的差异(顺便搞懂 Ollama 的底层引擎)。 - 上 GPU 学 vLLM 或 SGLang:按需租抢占式 GPU 实例(T4),两者定位相同二选一。建议先 vLLM(生态成熟、岗位需求大),SGLang 作为 R1 热点可后补。
- 接应用闭环:OpenWebUI 或翻译工具把 base_url 指向本机 11434,完成"部署 → 服务 → 应用"。
十二. 附:生产环境认知延伸¶
这一节是实验之外的延伸话题,从"怎么做"上升到"为什么这么做"。内容独立成块,可按需取用。
1. 隔离谱系:conda / Docker / KVM 是同一招的三个层次¶
隔离不是只有"容器"一种做法,而是一条从轻到重的谱系——本质都是"让 A 和 B 互不干扰",区别只在颗粒度和代价:
| 层级 | 隔离什么 | 手段 | 例子 |
|---|---|---|---|
| 最轻 | 环境变量 | PATH、LD_LIBRARY_PATH |
Redis 加 PATH |
| ↓ | 语言级依赖 | 独立目录 + 版本管理 | conda / venv / pyenv |
| ↓ | 系统库依赖 | chroot、独立 rootfs | 早期隔离 |
| ↓ | 进程/网络/文件全隔离 | namespace + cgroup | Docker 容器 |
| ↓ | 独立内核 | 虚拟化 | KVM 虚拟机 |
| 最重 | 物理隔离 | 独立硬件 | 独立服务器 |
conda vs Docker:
| conda | Docker | |
|---|---|---|
| 隔离对象 | Python 解释器 + 包 | 整个用户态环境 |
| 隔离强度 | 弱(同内核、同系统库、共享网络) | 强(独立文件系统、网络、进程) |
| 能否装 MySQL/nginx | ❌ 只能管语言层 | ✅ |
| 一句话 | "Python 界的容器",不是容器 | 容器 |
判断标准:隔离需求往上一层够用,就别往下走(两个 Python 项目冲突用 conda 就够,上 Docker 是过度设计)。
为什么要"修改 bash 配置文件"(装 conda 时常见疑惑):
- 不是改 bash 程序,而是改它的配置文件(
/etc/profile、~/.bashrc等),bash 启动时按顺序读它们 - 你的 Redis 文档里
echo 'export PATH=...' >> /etc/profile就是同一类操作 - conda init 的真正必要性:Linux 规矩是"子进程无法修改父进程的环境变量"。
conda activate要改当前 shell 的 PATH,所以 conda 必须被注入成 shell 函数(跑在当下 shell 里),而不能是普通可执行文件(那只会开子进程,改完就退,白改) conda init往~/.bashrc追加一段conda initialize块,核心是. .../conda.sh(读入脚本定义函数)- 改完要
source ~/.bashrc或重登 SSH 才生效
为什么提示符前面多了 (base):那段脚本末尾会自动 conda activate base(默认开 auto_activate_base)。(base) 只是提示符前缀,不是命令输出,用来标记"当前在哪个环境"。base 是 conda 自带的基础环境(相当于"官方基础镜像"),别往里装东西,项目都建独立环境:
2. 程序 vs 数据:什么进镜像,什么该挂载¶
一切文件本质上都是数据,"程序 vs 数据"不是两种实体,而是两种角色:程序回答"怎么算",数据回答"算什么"。
模型权重的双重身份——它有程序的血统(训练 ≈ 编译,权重 ≈ 字节码,推理引擎 ≈ 虚拟机),但工程上按数据处理。因为它完全符合"数据"的工程特征:只读、巨大、独立发布、重建成本极高。
模型和数据库结构居然同构:
| 数据库 | 大模型 | 角色 |
|---|---|---|
| DBMS 软件(mysqld) | 推理引擎(vLLM/Ollama) | 程序 |
| 数据文件(.ibd) | 模型权重(.gguf) | 数据 |
| SQL 语句 | Prompt | 指令 |
核心区分(最容易混的一点):
| 数据库数据 | 模型权重 | |
|---|---|---|
| 挂载属于 | 必须(技术上不该烘焙) | 偏好(技术上能烘焙,但不划算) |
| 决定性因素 | 可变性 + 不可重建 | 体积 + 分发成本 |
| 一句话 | 因为会变、丢了没救 | 因为太大、搬着费劲 |
检验方法:假设库只有 1MB 且一年不改,依然必须挂载(它是可写的唯一状态);模型哪怕 140GB,烘焙依然"安全"(只是不划算)。数据库是"不能",模型是"不划算"。
补充:模型不烘焙的主因是冷启动拉取 + 仓库存储/带宽,而不是"重建不方便"——镜像层是内容寻址的,模型单独占一层的话,升级运行时那层时模型层会被复用、不会重传。
判定四问(遇到任何东西都能自己判断):
- 删了能重建吗?能 → 镜像;不能 → 挂载
- 谁写它?多久变一次?高频写 → 挂载;只读 → 随意
- 多大?巨大 → 别塞镜像
- 要活过容器吗?要 → 挂载
| 东西 | 可写? | 能重建? | 结论 |
|---|---|---|---|
| 引擎/运行时 | 只读 | ✅ | 进镜像 |
| 模型权重 | 只读 | 极贵 | 挂载(不划算) |
| 数据库数据 | 可写 | 不能 | 挂载(必须) |
| 缓存/会话 | 可写 | 无所谓 | emptyDir/内存,随便丢 |
| 日志 | 追加写 | 无所谓 | 挂载或采集 |
3. 生产套路:环境进镜像 + 权重做挂载¶
这就是当前的标准做法(厂商官方文档即如此),本质是数据库"程序与数据分离"的原样平移:
| 组成 | 放哪 | 理由 |
|---|---|---|
| 运行时(CUDA + PyTorch + vLLM/SGLang) | 进镜像 | 只读、可重建、层可缓存复用(5~16GB) |
| 模型权重 | 挂载(:ro) |
只读、巨大(4.7GB~140GB)、独立更新 |
| 配置/参数 | 挂载或环境变量 | 按环境变,不该重构建 |
做法 A:官方镜像 + 挂载权重(最主流)
docker run -d --gpus all \
-v /data/models:/models:ro \
-p 8000:8000 \
vllm/vllm-openai:latest \
--model /models/Qwen2.5-7B-Instruct
做法 B:自建环境镜像 + 挂载权重(需要额外依赖/私有包/自定义脚本时)
# Dockerfile —— 绝不 COPY 权重进来
FROM vllm/vllm-openai:v0.6.3
RUN pip install --no-cache-dir prometheus-client 你的私有包
COPY entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
K8s 形态(完全一致,readOnly: true 是关键):
containers:
- name: vllm
image: registry.example.com/vllm-custom:1.0
args: ["--model", "/models/Qwen2.5-7B-Instruct"]
volumeMounts:
- name: models
mountPath: /models
readOnly: true
volumes:
- name: models
persistentVolumeClaim:
claimName: models-pvc
五种权重来源:PVC(NFS/Ceph)、hostPath/本地 NVMe(最快)、OSS CSI、initContainer 预下载、KServe 直接声明 storageUri(自动下载缓存)。
五个实操坑:
| 坑 | 现象 | 对策 |
|---|---|---|
| UID/权限不匹配 | 容器非 root 跑,挂载目录属 root → Permission denied | 挂载前 chown 对齐,或 --user 指定 |
忘加 :ro |
容器意外写坏权重 | 权重一律只读挂载 |
| 路径/环境变量不对 | 容器里找不到模型 | Ollama 用 OLLAMA_MODELS,HF 系用 HF_HOME,或直接给绝对路径 |
| 冷启动下载 | 首次启动卡几分钟拉模型 | 预置到卷 / initContainer 预热,评估 RTO |
| 权重进了构建上下文 | docker build 卡死 |
.dockerignore 排除 models/、*.gguf |
4. GPU 与 CUDA:什么时候需要,容器里怎么用¶
三者分层(记住这条就不会踩版本坑):
CUDA 程序(vLLM / llama.cpp 的 GPU 版)
↓ 需要
CUDA Toolkit(编译器 + 运行时库)
↓ 需要
显卡驱动(NVIDIA driver)
↓ 需要
NVIDIA 显卡(物理硬件)
判断标准不是"要不要 CUDA",而是"这次计算要不要用 NVIDIA 显卡":
| 场景 | 需要 CUDA? |
|---|---|
| GPU 跑推理(vLLM/SGLang/TensorRT-LLM) | ✅ 必须 |
| Ollama/llama.cpp 用 GPU 加速 | ✅ 需要(编译时开 CUDA 后端) |
| 训练/微调 | ✅ 必须 |
| 多卡并行(张量并行) | ✅ 必须(NCCL 走 CUDA) |
| AMD 显卡 | ❌ 用 ROCm |
| 国产卡(昇腾等) | ❌ 用厂商自家栈(CANN) |
| 纯 CPU 跑模型 | ❌ 不需要(本实验即此类) |
| 调云端 API | ❌ 不需要 |
本实验为什么不需要 CUDA:没有显卡,CPU 推理与 CUDA 无关。附带两个有趣结论:
- Ollama 官方包是全后端打包(CPU/NVIDIA/AMD 引擎全塞进去,运行时自动探测)——这解释了为什么
bin/ollama只 38M,解压后 2.2G(多出来的多是 GPU 库) - 同一份 Ollama 拿到有 N 卡的机器上会自动启用 CUDA,不用重编译
# 看它当前在用 CPU 还是 GPU
[root@my-yun ~]# ollama ps
NAME ID SIZE PROCESSOR UNTIL
qwen05:latest xxx 491 MB 100% CPU ...
PROCESSOR列显示100% CPU;在 GPU 机器上会变成100% GPU或xx%/yy% CPU/GPU(部分层跑 GPU)。
Docker 里怎么给显卡(能声明,但要先装桥):
docker run --gpus all nvidia/cuda:12.4.0-base nvidia-smi # 全部卡
docker run --gpus '"device=0"' ... # 只用 0 号卡
docker run --gpus '"device=2,3"' ... # 用 2、3 号卡
# 前提:装 NVIDIA Container Toolkit(Docker 原生不认识 GPU)
dnf -y install nvidia-container-toolkit
nvidia-ctk runtime configure --runtime=docker
systemctl restart docker
关键认知——两种"指定"是两个层次:
| 层面 | 手段 | 作用 |
|---|---|---|
| 资源分配(Docker 层) | --gpus all / device=0 |
哪些卡对容器可见 |
| 应用选择(应用层) | CUDA_VISIBLE_DEVICES、启动参数 |
应用用可见卡中的哪张 |
细节:
CUDA_VISIBLE_DEVICES=0在容器内指"容器内视角的第 0 张",不是宿主机物理 0 号卡。--gpus '"device=2,3"'+CUDA_VISIBLE_DEVICES=0= 实际用宿主机 2 号卡。
K8s 里换了个说法,本质一样:
区别是 K8s 不用你写 --gpus——由 NVIDIA GPU Operator 自动装驱动/Toolkit/device plugin,你只声明数量。
铁律:容器里不装显卡驱动,驱动永远由宿主机提供、由 Toolkit 注入;镜像里只带 CUDA 运行时。所以镜像 CUDA 版本必须和宿主驱动兼容——这是容器化 GPU 最常见的翻车点。
5. 数据库为什么通常不上 K8s(附"供奉等级"谱系)¶
先修正前提:不是"很少有人用",而是"不是默认选择"。现状是争议中成熟——已有 CloudNativePG、KubeBlocks、Crunchy PGO 等成熟 Operator,同时也有大量讨论在讲"什么时候托管 DBaaS 仍更划算":Postgres on Kubernetes 2026: Operator 对比与 DBaaS 何时胜出、 为什么数据库可以放在 K8s 上(成本/控制/扩展对比)、主流 PostgreSQL Operator 生态对比。
| 场景 | 主流做法 |
|---|---|
| 开发/测试/小项目 | Docker Compose 起 MySQL,非常普遍 |
| 中小规模生产 | 云托管 RDS / 数据库跑 VM(最大多数) |
| 大规模云原生公司 | K8s + Operator 真实存在(Vitess、TiDB Operator、CloudNativePG) |
| 单实例核心 OLTP、延迟极敏感 | 物理机/VM |
两种"稳定观"根本不同(这是冲突的根源):
| 无状态服务(容器主场) | 数据库 | |
|---|---|---|
| 稳定的定义 | 任何实例随时可挂,整体可用 | 这一个实例的数据绝对不能错 |
| 实现方式 | 多副本 + 快速重建(牛群模式) | 单点数据的正确性 + 持久性(宠物模式) |
| 对失败的假设 | 假设一定会死,死了就换 | 假设不能死,死了是灾难 |
| K8s 提供什么 | 完整保障 | 只提供机制(PV 会重挂),不保证一致性/failover/性能 |
关键:容器不是"做不到稳定",而是"被允许不稳定"。同一个"实例消失",在无状态服务里是常态(还有 19 个副本),在数据库里是灾难。
真正原因,按重要性排:
- 有更省事的替代品(最大原因):云托管 RDS。你承担全部复杂度和风险,收益却是别人已商品化的东西——投入产出比问题
- K8s 的强项数据库用不上:K8s 价值在"海量无状态副本 + 弹性伸缩",而数据库副本数固定、扩副本是主从而非 HPA、还不希望 Pod 乱跑。用不上强项,却要承担复杂度
- 数据库对底层苛刻:吃本地 NVMe(网络存储延迟差一个数量级)、需要内核级调参(NUMA/huge pages)、Pod 重调度要经历 PV 解绑→attach→挂载(分钟级),比 VM HA 切换更慢
什么时候上 K8s 是对的:有成熟平台团队 + Operator + 拓扑感知存储;需要大量数据库实例(SaaS 多租户);云原生分布式数据库(TiDB/CockroachDB 天生适合)。
"供奉等级"谱系(数据库该被供着,区别只在供在哪座庙):
| 等级 | 形态 | 谁在用 |
|---|---|---|
| 🏛️ 神殿级 | 裸金属 + 专线 + 独立机柜 + 门禁 | 银行核心账务、电信计费、交易所 |
| 🏢 自建机房级 | 自建/托管机房物理机 | 大厂核心库、有历史包袱的企业 |
| ☁️ 云主机级 | 云 VM(RDS 底层) | 当前最主流 |
| 📦 容器级 | K8s + Operator | 云原生公司、SaaS 多租户、分布式数据库 |
| 🧪 玩具级 | docker compose up |
开发测试 |
神殿级为什么没被取代:合规审计(数据物理可控)、极致性能(微秒级不接受虚拟化开销)、超大规模时自建更便宜、迁移风险大于收益。庙可以换,香火(数据)不能断。
对运维的意义:核心能力不是记住"数据库该装物理机",而是判断一个场景该用哪一档——核心账务 vs 内部工具,合规要求如何,规模多大。面试被问"数据库该不该上云/上 K8s",能答出这层判断就比背答案高一档。