6.13 LLM 自托管完整方案——ollama、Open WebUI 与本地推理
预计阅读时间:19 分钟
📖 目录
6.12:AI 基础设施 介绍了 GPU 驱动安装和 vLLM/Triton 等高性能推理服务。对于个人使用或小规模部署,ollama + Open WebUI 提供了更简单的本地 LLM 运行方案——一行命令启动模型,浏览器即用。
学习目标
- 理解本地 LLM 推理的技术路径:模型量化、显存需求与推理速度的权衡
- 用 ollama 完成模型安装、运行与 OpenAI 兼容 API 调用
- 部署 Open WebUI 提供浏览器交互界面,配置多用户与多模型
- 掌握 systemd 服务化、HTTPS 反向代理、访问控制等生产配置
- 根据硬件条件(CPU/GPU/显存)选择正确的模型与量化级别
- 评估自托管 LLM 与云 API 的适用边界(成本、隐私、性能)
前置知识
- 6.12:AI 基础设施 AI 基础设施——GPU 驱动、CUDA 与 vLLM 概念(本文是其简化替代方案)
- 3.1:Docker 容器入门 Docker 容器入门——Open WebUI 通过容器部署
- 3.4:Nginx Web服务器 Nginx Web 服务器——反向代理与 HTTPS 配置
- 2.12:systemd 深入 systemd 深入——服务化运行与资源限制
方案对比
| 方案 | GPU 要求 | 适合场景 | 复杂度 |
|---|---|---|---|
| ollama | 可选(CPU 可跑小模型) | 个人/开发环境 | 极简 |
| Open WebUI + ollama | 同上 | 团队共享 Web 界面 | |
| vLLM | 需要 GPU | 生产 API 服务 | 中等 |
| Triton Inference Server | 需要 GPU | 多模型/多框架 | 高 |
1. ollama 安装与使用
# 一行安装(Linux)
curl -fsSL https://ollama.com/install.sh | sh
# 启动服务
ollama serve
# 下载并运行模型(首次下载约 4-7GB)
ollama run llama3.1:8b # Meta Llama 3.1 8B(推荐入门)
ollama run qwen2.5:14b # 通义千问 14B(中文优秀)
ollama run deepseek-r1:7b # DeepSeek R1 7B(推理能力强)
ollama run codellama:13b # Code Llama 13B(代码专用)
# 交互式对话
ollama run llama3.1:8b
>>> 你好,请介绍一下 Linux 的文件系统层次结构
常用命令
# 列出已下载的模型
ollama list
# 查看模型详情(参数量、量化格式)
ollama show llama3.1:8b
# 删除模型
ollama rm llama3.1:8b
# 停止运行中的模型
ollama stop
# API 调用(兼容 OpenAI 格式)
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "llama3.1:8b",
"messages": [{"role": "user", "content": "什么是 Docker?"}]
}'
模型选择指南
| 模型 | 参数量 | 内存需求 | 特点 |
|---|---|---|---|
| qwen2.5:3b | 3B | 4GB | 轻量,中文支持好 |
| llama3.1:8b | 8B | 8GB | 通用,英文优秀 |
| qwen2.5:14b | 14B | 16GB | 中文最佳,推理能力强 |
| codellama:13b | 13B | 16GB | 代码生成专用 |
| deepseek-r1:14b | 14B | 16GB | 深度推理、数学、逻辑 |
:q8_0(8-bit)格式,精度更高但速度更慢。2. Open WebUI——浏览器界面
Open WebUI 提供类似 ChatGPT 的 Web 界面,支持多用户、文件上传、知识库(RAG)。
# Docker 一键部署(连接本机 ollama)
docker run -d \
--name open-webui \
-p 3000:8080 \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
-v open-webui:/app/backend/data \
--restart unless-stopped \
ghcr.io/open-webui/open-webui:main
# 首次访问 http://localhost:3000
# 管理员账户自动创建,后续可添加用户
Open WebUI 核心功能:
- 多模型切换:侧边栏选择不同 ollama 模型
- 文件上传:上传 PDF/TXT/图片,自动提取内容作为上下文
- 知识库(RAG):上传文档建立向量索引,模型基于文档回答
- 多用户管理:独立会话、角色权限、使用统计
- 对话历史:完整保存,支持导出和搜索
3. 生产环境配置
# ollama 系统服务
# /etc/systemd/system/ollama.service
[Unit]
Description=Ollama LLM Server
After=network.target
[Service]
User=ollama
Group=ollama
Environment="OLLAMA_HOST=0.0.0.0:11434"
Environment="OLLAMA_MODELS=/data/ollama/models"
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=2"
ExecStart=/usr/local/bin/ollama serve
Restart=always
[Install]
WantedBy=multi-user.target
# 启动
sudo systemctl enable --now ollama
资源限制
# 限制 ollama 使用 16GB 内存
# /etc/systemd/system/ollama.service.d/override.conf
[Service]
MemoryMax=16G
CPUQuota=400%
# 重启生效
sudo systemctl daemon-reload && sudo systemctl restart ollama
4. 网络部署与远程访问
# 仅内网访问(推荐)
OLLAMA_HOST=192.168.1.100:11434
# 通过 Nginx 反向代理 + HTTPS
# /etc/nginx/conf.d/ollama.conf
server {
listen 443 ssl;
server_name llm.example.com;
location / {
proxy_pass http://127.0.0.1:11434;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 300s; # LLM 响应慢,超时放长
}
}
# Open WebUI 部署(带 HTTPS)
docker run -d \
--name open-webui \
-p 127.0.0.1:3000:8080 \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
-e WEBUI_AUTH=true \
-v open-webui:/app/backend/data \
--restart unless-stopped \
ghcr.io/open-webui/open-webui:main
Nginx 网关加认证与限流
# ollama API 无认证,反向代理层必须兜底:
# 1. Basic Auth(个人简单场景)
# 生成密码哈希:openssl passwd -apr1
auth_basic "LLM API";
auth_basic_user_file /etc/nginx/htpasswd-llm;
# 2. 或 API Key 校验(OpenResty / lua-resty 实现)
# 3. 团队场景推荐直接走 Open WebUI 的用户认证
# 反向代理层统一做限流,防止 API Key 被滥用
limit_req_zone $binary_remote_addr zone=llm_api:10m rate=10r/m;
location /v1/chat/completions {
limit_req zone=llm_api burst=5;
proxy_pass http://127.0.0.1:11434;
}
5. 安全注意事项
- 不要暴露 ollama API 到公网——无认证机制,任何人都能调用
- 通过 Open WebUI(有认证)或 Nginx 反向代理 + mTLS 暴露
- 模型文件存放在
/data/ollama/models,注意磁盘空间(每个模型 4-20GB) - GPU 服务器建议物理隔离或 VPC 网络
6. 与 6.12:AI 基础设施 AI 基础设施的关系
| 维度 | 本章(ollama + Open WebUI) | 6.12:AI 基础设施(vLLM + Triton) |
|---|---|---|
| 适合 | 个人、小团队、开发测试 | 生产 API、高并发 |
| 性能 | 单请求,响应式 | 批处理、高吞吐 |
| GPU 利用率 | 低(模型按需加载) | 高(持续推理) |
| API 格式 | 兼容 OpenAI API | 自定义 / gRPC |
7. 模型量化原理
量化(Quantization)把 FP32/FP16 的模型权重用更低精度表示,换取显存下降与推理加速。看懂量化,才能理解 ollama 模型标签里 :q4_K_M、:q8_0 的含义,以及为什么同一模型不同 tag 显存差好几倍。
7.1 精度格式对比
| 格式 | 每权重位数 | 相对 FP16 显存 | 精度损失 | 推理速度 | 典型场景 |
|---|---|---|---|---|---|
| FP32 | 32 bit | 200% | 无 | 慢 | 训练 |
| FP16 | 16 bit | 100% | 几乎无 | 基准 | 服务基准(vLLM 默认) |
| BF16 | 16 bit | 100% | 几乎无(动态范围更大) | ≈FP16 | A100/H100 训练与推理 |
| INT8 | 8 bit | 50% | 轻微 | 快 20-40% | 性价比均衡 |
| INT4 | 4 bit | 25% | 可感知(复杂任务降级) | 最快 | 个人本地部署 |
量化只压缩权重部分,KV cache 与激活值仍需另算(见第 8 节)。以 7B 模型为例:FP16 权重约 14GB,INT8 约 7GB,INT4 约 3.5GB。质量上 INT8 对多数任务不可感知,INT4 在代码生成、数学等复杂任务上偶有降级。
7.2 量化格式:GPTQ / AWQ / GGUF
| 格式 | 适用推理后端 | 特点 | 选择建议 |
|---|---|---|---|
| GPTQ | GPU(vLLM、TGI) | 离线量化,推理时开销低,生态成熟 | GPU 上 vLLM 服务首选 |
| AWQ | GPU(vLLM、TGI) | 按权重重要性保护关键通道,同精度下质量优于 GPTQ | 追求质量时选 AWQ |
| GGUF | llama.cpp / ollama | 支持 CPU 运行与 KV cache 量化,单文件易分发 | ollama/无 GPU 环境必选 |
ollama 直接使用 GGUF 格式,ollama run llama3.1:8b-q8_0 指定位宽;vLLM 更常用 GPTQ/AWQ 量化后的 safetensors 权重。同一模型不同量化版本的显存差可达 4 倍,先小后大验证效果是标准流程。
7.3 KV cache 量化
KV cache 是长上下文场景显存的大头(公式见 8.1),llama.cpp 支持把它从 FP16 量化到 8/4 bit,代价是长上下文下精度略降,对多数对话场景不可感知:
# llama.cpp 服务端:KV cache 量化到 8bit,32K 上下文的显存可降 60%+
llama-server -m qwen2.5-7b-q4_k_m.gguf -c 32768 \
--cache-type-k q8_0 --cache-type-v q8_0
# 不指定时默认 f16(精度最高),-c 32768 表示 32K 上下文窗口
8. GPU 内存规划
8.1 显存计算示例
总显存 = 模型权重 + KV cache + 激活值 + CUDA 开销。以 7B 模型、FP16、4K 上下文、batch=8 为例:
# 权重:70 亿参数 × 2 字节 = 约 14GB
# KV cache:2(K/V) × 层数(32) × KV 头数(8) × 头维度(128) × seq_len(4096) × batch(8) × 2 字节
# = 2×32×8×128×4096×8×2 ≈ 4.3GB
# 激活值:与隐藏层维度、batch 相关,约占权重 5-10% ≈ 1GB
# CUDA 上下文 + 运行时开销:0.5-1GB
# 合计 ≈ 14 + 4.3 + 1 + 1 ≈ 20GB(RTX 3090/4090 24G 可跑,12G 卡必须 INT4)
8.2 不同规模模型的显存需求
| 模型 | FP16 | INT8 | INT4 | 推荐硬件 |
|---|---|---|---|---|
| 7B(Qwen2.5-7B、Llama3-8B) | 16-18GB | 12-13GB | 8-9GB | RTX 4070 12G 起步(INT4) |
| 13B(CodeLlama-13B) | 28-30GB | 20-22GB | 13-15GB | RTX 4090 24G |
| 70B(Llama3-70B、Qwen2.5-72B) | 140-150GB | 75-85GB | 42-45GB | 2×A100 80G 或 4×A100 40G |
表中数值含 4K 上下文 KV cache 与运行开销,实际以 nvidia-smi 观测为准。个人场景的黄金公式:INT4 下 7B 是 12G 卡的甜点,14B 需要 24G,70B 与单机个人部署基本无缘。
8.3 多卡并行:张量并行 vs 流水线并行
| 维度 | 张量并行(Tensor Parallel) | 流水线并行(Pipeline Parallel) |
|---|---|---|
| 原理 | 单层内切分权重到多卡,每层结束后卡间同步 | 按层分段,段边界传输中间结果 |
| 通信量 | 大(每层 all-reduce),需要 NVLink 高速互联 | 小(只在段边界),万兆网络即可 |
| 适合 | 单机多卡(同机内互联) | 跨机集群 |
| vLLM 配置 | --tensor-parallel-size 2/4/8 | --pipeline-parallel-size(vLLM 2.x 支持) |
8.4 CPU offload 场景
显存不足时可以把部分层加载到内存。llama.cpp 用 --n-gpu-layers 控制"前 N 层上 GPU";ollama 通过 Modelfile 的 num_gpu 参数设置。代价是每跨层一次 PCIe 拷贝,速度可能跌到 2-5 token/s,只适合"能跑就行"的非交互任务(批量离线处理、测试)。
# llama.cpp:40 层模型中前 30 层上 GPU,其余跑 CPU
llama-server -m qwen2.5-14b-q4_k_m.gguf -ngl 30
# ollama 方式:为单个模型定制加载策略
cat > Modelfile <<EOF
FROM qwen2.5:14b
PARAMETER num_gpu 30
EOF
ollama create qwen2.5-14b-offload -f Modelfile
8.5 显存不足时的决策顺序
模型放不下显存时,按"成本从低到高"依次尝试,不要一上来就换小模型:
| 顺序 | 手段 | 效果 | 代价 |
|---|---|---|---|
| ① | KV cache 量化(--cache-type-k q8_0)并收紧上下文长度 | 省 30-60% KV 显存 | 长上下文精度略降 |
| ② | 降量化位宽(FP16 → INT8 → INT4) | 权重显存减半再减半 | INT4 复杂任务质量降级 |
| ③ | 限制并发(--max-num-seqs 减半) | 省多份 KV cache | 吞吐下降 |
| ④ | CPU offload 部分层(--n-gpu-layers) | 能跑但慢(2-5 token/s) | 交互体验差,仅批处理 |
| ⑤ | 换小模型或减上下文 | 彻底解决 | 能力下降,需重新验证 |
判断顺序的价值:大多数情况做到 ①③ 就够——先看 nvidia-smi 确认到底是权重占满还是 KV cache 占满,再对症下药。
9. OpenAI 兼容 API 详解
ollama、vLLM、LocalAI 都实现了 OpenAI 的 /v1/chat/completions 接口,应用层只需改 base_url 即可切换后端,这是自托管生态能互通的关键。
9.1 三个后端端点对比
| 后端 | 默认端点 | 流式输出 | function calling | embedding |
|---|---|---|---|---|
| ollama | /v1/chat/completions | 支持 | 支持(tools 参数) | /v1/embeddings 支持 |
| vLLM | /v1/chat/completions | 支持 | 支持(受模型能力限制) | --task embed 启动 |
| LocalAI | /v1/chat/completions | 支持 | 支持 | /v1/embeddings 支持 |
9.2 流式输出
# curl 流式请求:stream=true,模型逐字返回 SSE 事件
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"llama3.1:8b","stream":true,
"messages":[{"role":"user","content":"写一首五言绝句"}]}' \
--no-buffer | head -30
# Python 客户端:openai SDK 只需改 base_url 与 api_key
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
stream = client.chat.completions.create(
model="llama3.1:8b",
messages=[{"role": "user", "content": "你好"}],
stream=True)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)
9.3 function calling
# 声明工具,模型返回 tool_calls 而不是直接回答
resp = client.chat.completions.create(model="llama3.1:8b", messages=[
{"role": "user", "content": "北京现在温度多少?"}
], tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询城市当前温度",
"parameters": {"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]}
}}])
# 模型返回:tool_calls=[{function:{name:"get_weather",
# arguments:{"city":"北京"}}}]
# 应用执行函数后,把结果以 role="tool" 消息发回,模型给出最终回答
9.4 embedding 模型部署
# 拉取嵌入模型(RAG 向量检索用)
ollama pull nomic-embed-text
ollama pull qwen3-embedding:0.6b # 中文场景可选
# 调用:返回 768/1024 维向量,与 chat 接口完全同构
curl http://localhost:11434/v1/embeddings \
-H "Content-Type: application/json" \
-d '{"model":"nomic-embed-text","input":"Kafka 与 RabbitMQ 的区别"}'
10. 推理优化——vLLM 生产部署
追求吞吐的 API 服务应使用 vLLM 而不是 ollama(同卡吞吐高 3-5 倍)。三大特性让它成为生产标准:
- PagedAttention:KV cache 按页管理,消除显存碎片,是吞吐翻倍的根源
- Continuous Batching:请求到达即插入批次,GPU 持续满载,不再等"凑满一批"
- 前缀缓存:相同 system prompt 的计算结果复用,多轮对话与 Agent 场景命中率极高
# 安装并启动(需 NVIDIA GPU + CUDA)
pip install vllm==0.6.0
vllm serve Qwen/Qwen2.5-7B-Instruct \
--gpu-memory-utilization 0.9 \
--max-model-len 8192 \
--max-num-seqs 32 \
--enable-prefix-caching \
--served-model-name qwen25 \
--port 8000
# 验证(与 ollama 同一套 OpenAI 接口)
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen25","messages":[{"role":"user","content":"你好"}]}'
10.1 关键调优参数
| 参数 | 作用 | 建议 |
|---|---|---|
| --gpu-memory-utilization | 显存可用比例(权重之外余量全给 KV cache) | 0.85-0.95,留 5-10% 给 CUDA 上下文 |
| --max-num-seqs | 单 batch 最大并发序列数 | 8-64,越大吞吐越高但首 token 延迟上升 |
| --max-model-len | 最大上下文长度 | 按业务需要,过长会浪费 KV cache 显存 |
| --enable-prefix-caching | 前缀缓存复用 | 有固定 system prompt 必开 |
| --served-model-name | 对外暴露的模型名 | 固定别名,避免应用随权重路径变动 |
# 压测验证(wrk 打 60 秒,观察 TPS 与 P95)
wrk -t8 -c32 -d60s -s post.lua http://localhost:8000/v1/chat/completions
# 关注指标:吞吐 token/s、排队时间、P95 首 token 延迟
# 报错 "CUDA out of memory":调低 --max-num-seqs 或 --max-model-len
# vLLM 自带 /metrics 端点(Prometheus 格式),直接接入 Grafana
10.2 常见瓶颈与排查
| 症状 | 原因 | 排查与解决 |
|---|---|---|
| GPU 利用率低(< 40%) | 请求太少,batch 不满 | 看 vllm:num_requests_running;低并发场景本就不该用 vLLM,回退 ollama |
| 首 token 延迟高 | prefill 阶段处理长提示词 | 缩短 system prompt;开启前缀缓存命中固定开头;长文档用 RAG 摘要代替全量塞入 |
| CUDA OOM | KV cache 超限 | 调低 --max-num-seqs / --max-model-len;确认 --gpu-memory-utilization 留了余量 |
| 吞吐上不去但显存有剩余 | 连续批处理被长序列阻塞 | 长请求改流式分块;提高 --max-num-seqs 让更多短请求并行 |
排查口诀:先看 vllm 自带 /metrics(gpu_cache_usage_perc、num_requests_running、prompt/completion token 吞吐),再动参数——凭感觉调参是最贵的排障方式。
11. 安全与成本评估
11.1 本地 vs API 成本对比
| 维度 | 本地自托管 | 云 API(豆包/OpenAI 等) |
|---|---|---|
| 前期成本 | GPU 服务器 2-8 万元(24G 单卡约 1-1.5 万) | 0 |
| 运行成本 | 电费+折旧:24G 卡满载约 0.5-1 元/小时 | 按 token:7B 级模型约 0.5-2 元/百万 token |
| 计费模型 | 无论用不用都在烧钱 | 用多少付多少 |
| 盈亏平衡点 | 日用量约 500 万-2000 万 token 时与 API 打平 | 低用量(<100 万 token/天)更便宜 |
11.2 数据隐私场景
- 必须本地:病历、代码仓库、客户 PII、涉密文档——数据不出内网是合规硬要求
- 可选本地:内部知识库(可脱敏后走 API,成本更低、模型能力更强)
- 建议 API:个人玩具项目、一次性实验、需要最强模型能力的业务
11.3 模型许可证速查
| 模型 | 许可证 | 商用限制 |
|---|---|---|
| Llama 3.x(Meta) | Llama Community License | 月活 7 亿以下可商用 |
| Mistral 7B / 8x7B | Apache 2.0 | 无限制 |
| Qwen 2.5(阿里) | Apache 2.0 | 无限制 |
| DeepSeek V3 / R1 | MIT | 无限制 |
| GLM-4(智谱) | GLM 许可(需报备) | 月活过亿需书面授权 |
11.4 内容安全过滤
自托管模型没有云厂商的内容审核管线,对外服务必须自建过滤层:
# 最低要求组合:
# 1. Nginx/网关层关键词与正则过滤(OpenResty lua 脚本或 lua-resty-waf)
# 2. 按用户限流(令牌桶),防止服务被批量滥用
# 3. 全量对话日志审计,保留 180 天,供合规排查
# 4. 敏感场景可在网关后串一个开源审核模型做二次过滤
12. 生产案例——从下载到上线
以"给公司内网做一个代码问答助手"为例,完整走一遍:下载 -> 验证 -> 量化 -> vLLM 部署 -> 接入应用 -> 监控。
# ① 选型与下载:Qwen2.5-7B(Apache 2.0 可商用、代码能力好),先拉 GGUF 试效果
ollama pull qwen2.5:7b
ollama run qwen2.5:7b "用 Python 写一个协程池下载器"
# ② 质量确认后,换 AWQ 量化版跑 vLLM(吞吐比 ollama 高 3-5 倍)
git clone https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-AWQ
pip install vllm==0.6.0
# ③ vLLM 部署(systemd 托管,开机自启 + 崩溃重启)
cat > /etc/systemd/system/vllm.service <<EOF
[Unit]
Description=vLLM Inference Server
After=nvidia-persistenced.service
[Service]
ExecStart=/opt/venv/bin/vllm serve /data/models/Qwen2.5-7B-Instruct-AWQ \
--gpu-memory-utilization 0.9 --max-num-seqs 16 \
--enable-prefix-caching --served-model-name qwen-coder
Restart=always
Environment=CUDA_VISIBLE_DEVICES=0,1
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now vllm
# ④ 应用接入:内部工具统一指向 OpenAI 兼容端点
OPENAI_BASE_URL=http://10.0.0.5:8000/v1
OPENAI_API_KEY=local-only-key
# ⑤ 监控:vLLM 自带 /metrics(Prometheus 格式)
curl -s http://10.0.0.5:8000/metrics | grep vllm:gpu_cache_usage_perc
# 告警规则:gpu_cache_usage_perc > 0.9 时降并发或扩容
# 配合 node_exporter 监控 GPU 温度、显存、功耗
上线后观察一周:GPU 利用率、显存占用、P95 时延三张图稳定后,再决定是否加 Redis 缓存层(缓存重复问题)或扩容第二台。整个链路中模型选型与量化级别决定了 80% 的效果,部署本身只占 20%。
常见错误
- ollama 启动失败:端口 11434 已被占用——使用
lsof -i :11434查看占用进程并终止,或修改OLLAMA_HOST指定其他端口。 - 模型下载卡住/超时——国内网络环境下从 ollama.com 下载速度慢,可配置代理(
HTTPS_PROXY)或使用国内镜像源拉取。 - Open WebUI 无法连接 ollama——Docker 容器内访问宿主机需使用
host.docker.internal(Linux 需添加--add-host=host.docker.internal:host-gateway),不要用127.0.0.1。 - GPU 显存不足导致推理报错——减小模型参数量(如用 8B 替代 14B),或降低
OLLAMA_NUM_PARALLEL减少并发加载。使用nvidia-smi监控显存占用。 - 模型输出乱码/中文质量差——优先选择中文优化模型(qwen2.5 系列),确保模型下载完整(
ollama show检查),避免使用过低量化格式(Q4 以下)。 - 显存规划错误导致 OOM——只算了权重没算 KV cache,长上下文场景直接 CUDA out of memory。用第 8 节公式估算总量,并设置
OLLAMA_NUM_PARALLEL或--max-num-seqs限制并发。 - vLLM 启动失败/显存利用率异常——多卡环境未设
CUDA_VISIBLE_DEVICES导致卡冲突;或--gpu-memory-utilization设置过高(>0.95)挤占 CUDA 上下文空间。 - function calling 返回非法 JSON——小模型工具调用不可靠,解析失败应重试或回退为普通回答,不要直接抛 500 给用户。
- 并发调太高导致逐个请求都慢——OLLAMA_NUM_PARALLEL / --max-num-seqs 过大时,单请求被批量排队拖慢。低延迟优先的业务调小并发,高吞吐优先的业务调大。
- 切换模型前忘记清显存——同一 GPU 上换模型前先
ollama stop或重启 vLLM,否则旧模型权重残留导致新模型 OOM。
最佳实践
- 模型存储路径独立挂载——将
OLLAMA_MODELS指向独立磁盘分区,模型文件动辄数十 GB,避免占用系统盘空间。 - 生产环境务必加认证——ollama API 本身无任何认证机制,必须通过 Open WebUI、Nginx 反向代理 + mTLS 或 API Key 网关暴露服务。
- 使用 systemd 托管 ollama——避免手动
ollama serve后台运行不可靠的问题,systemd 自动重启、日志管理、开机启动一并解决。 - 按需加载模型,及时卸载——
OLLAMA_MAX_LOADED_MODELS控制常驻内存的模型数量,避免多模型同时加载耗尽 GPU 显存。 - 定期清理旧版本模型——
ollama pull更新模型后旧版本仍占用磁盘,用ollama list检查并ollama rm删除不再使用的版本。 - 先 GGUF 验证效果,再上 vLLM 生产——用 ollama 快速验证模型质量与量化位宽,确认后再用 AWQ/GPTQ 部署 vLLM,避免在错误选型上浪费调优时间。
- 固定 system prompt 并开启前缀缓存——vLLM 的
--enable-prefix-caching在 Agent/固定提示词场景可省 30-50% 算力。 - 许可证合规先行——商用前核对模型许可证(Llama 月活限制、GLM 报备要求),并把许可证声明写进项目 README。
- 用 nvidia-smi 观测而非估算——显存规划表只是起点,上线后以
nvidia-smi实测为准,把实测值回填到文档形成团队基线。 - 显存不足按 ①⑤ 顺序决策——先量化与限并发,再 offload,最后才换小模型,每一步都用压测数据验证。
练习题
- 在本地安装 ollama 并运行 qwen2.5:7b,通过
curl调用 OpenAI 兼容 API 发送一个中文问题,记录响应内容和延迟时间。 - 使用 Docker 部署 Open WebUI 并连接到本机 ollama,配置 Nginx 反向代理实现 HTTPS 访问,截图展示多模型切换功能。
- 编写一个 systemd override 文件,限制 ollama 进程最多使用 8GB 内存和 2 个 CPU 核心,并在 ollama 运行大模型时用
systemd-cgtop观察资源消耗。 - 用第 8 节公式为"14B 模型、INT4、8K 上下文、batch=16"计算总显存,据此判断 RTX 3090 24G 单卡能否部署,并用
nvidia-smi实测验证。 - 分别用 ollama 与 vLLM 部署同一个 7B 模型,用 wrk 压测对比吞吐(token/s)与 P95 延迟,解释 vLLM 高吞吐的来源(continuous batching、PagedAttention)。
- 用 nvidia-smi 记录同一模型在 2K/8K/16K 上下文下的显存占用,画出"上下文长度 vs KV cache 显存"曲线,验证第 8 节公式。
- 模拟一次显存 OOM:把 vLLM 的 --max-num-seqs 调到显存放不下,观察报错信息,按 10.2 排查表给出处置方案。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释 LLM 推理引擎的架构和模型量化原理 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成 vLLM/Ollama 部署和模型加载 | 在终端实际执行 |
| 原理掌握 | 能说出 LLM 推理的 KV Cache 和批处理优化原理 | 画出流程图 |
| 故障排查 | 能独立排查 LLM 服务 GPU 显存不足或推理延迟高的问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么需要为 LLM 服务配置请求限流和超时 | 对比不同方案 |
本章总结
自托管 LLM 的门槛比想象中低:ollama 一行命令即可运行中小模型,Open WebUI 提供企业级界面,配合 systemd 与 Nginx 就能变成可对外服务的应用。但选型要清醒:本地推理适合隐私敏感、成本敏感、离线场景;追求模型天花板性能与规模化时,云 API 仍是最优解。量化级别与显存的匹配是性能的关键杠杆,先测再调。
延伸阅读
- 6.12:AI 基础设施 AI 基础设施基础——GPU、CUDA 与模型服务
- 4.7:压力测试实战 压力测试实战——用 wrk 测试 LLM API 吞吐