在自己的电脑或小主机上跑 DeepSeek-R1 蒸馏模型,数据不出本机。
ollama pull不通时(公司代理、DNS 拦截等),改用“从 Hugging Face 下载 GGUF、校验 sha256、导入 Ollama”的办法。再测一下本机速度,最后用 Open WebUI 给家人或同事一个网页界面。
先看清成品
在 box 上实测(8 核 Intel Xeon 虚拟机,没有 GPU,16GB 内存;Ollama 0.40.7):
ollama pull失败(见调试示例),改用import_gguf.sh导入成功,sha256 校验通过。- 故意截断文件后再导入,被 sha256 校验拦下。
速度:
模型 生成速度 读入提示 deepseek-r1-distill:1.5b 43.6 tok/s 646.4 tok/s qwen2.5 1.5b 48.8 tok/s 1853.3 tok/s qwen3 0.6b 123.3 tok/s 1950.7 tok/s (之前另一次运行的生成速度是 41.0 / 40.6 / 143.2,说明每次结果都有波动)
没有测过: Open WebUI 的 compose 文件只做了
docker compose config校验,没有启动;GPU 部分也没有测(box 没有 GPU)。
说明:“DeepSeek-R1 蒸馏版”是用 R1 的输出训练出来的小模型(基座是 Qwen),能力和官方 API 背后的大模型差距很大。本机跑的是 1.5B 参数、Q4_K_M 量化的版本。许可证(Hugging Face 标签):deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B 是 MIT,unsloth 的 GGUF 转换仓库是 apache-2.0。
合规提醒:API Key 只放在环境变量里,不要写进代码或提交到 Git。不要把身份证号、客户资料、公司机密发给云端 API;这类数据用 B 篇的本地部署处理。如果要把它做成面向公众的服务,需要按《生成式人工智能服务管理暂行办法》《人工智能生成合成内容标识办法》等规定履行相应义务。API 价格以 DeepSeek 官网“模型 & 价格”页为准,本文不列价格。
安全提醒:Ollama 的 11434 端口没有鉴权。不要映射到公网,也不要在路由器上开端口。局域网共享请通过 Open WebUI(它有账号体系),远程使用参考 38-jellyfin-C 的 Tailscale 方案。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
import_gguf.sh |
用 HF API 取 sha256 → 下载 → 校验 → ollama create |
bench.py |
用 Ollama 返回的 eval_count / eval_duration 计算 tok/s,第一次运行(包含加载时间)不计入 |
docker-compose.yml |
Ollama + Open WebUI,Ollama 不对外开放端口 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| 内存 | 1.5B Q4 模型文件约 1.1GB;建议 8GB 以上内存 | 7B 以上的模型需要更多内存,或者用显卡 |
| Ollama | Linux 用官方安装脚本;macOS / Windows 用安装包 | 安装脚本需要 zstd(box 上缺少时安装脚本会报错提示) |
| 工具 | curl、python3、sha256sum |
文件结构
41-deepseek-B-src/
├── import_gguf.sh
├── bench.py
└── docker-compose.yml
第 1 步:导入模型
import_gguf.sh:
#!/bin/sh
# 从 Hugging Face 下载 GGUF,核对 sha256,再导入 Ollama。
# 适用场景:ollama pull 不通(公司代理、内网镜像、DNS 拦截等),或者想用指定的量化版本。
# 用法:sh import_gguf.sh 仓库 文件名 本地模型名
# sh import_gguf.sh unsloth/DeepSeek-R1-Distill-Qwen-1.5B-GGUF DeepSeek-R1-Distill-Qwen-1.5B-Q4_K_M.gguf deepseek-r1-distill:1.5b
set -eu
REPO=$1; FILE=$2; NAME=$3; DIR=${GGUF_DIR:-$HOME/gguf}
mkdir -p "$DIR"; cd "$DIR"
# Hugging Face 的 API 会给出 LFS 文件的 sha256
WANT=$(curl -fsSL "https://huggingface.co/api/models/$REPO/tree/main" |
python3 -c "import sys,json; print(next(f['lfs']['oid'] for f in json.load(sys.stdin) if f['path']=='$FILE'))")
[ -f "$FILE" ] || curl -fL --retry 3 -C - -o "$FILE" "https://huggingface.co/$REPO/resolve/main/$FILE"
GOT=$(sha256sum "$FILE" | cut -d' ' -f1)
[ "$GOT" = "$WANT" ] || { echo "✗ sha256 不一致:$GOT ≠ $WANT,删除后重新下载"; exit 1; }
echo "✓ sha256 校验通过 $GOT"
printf 'FROM ./%s\n' "$FILE" > "Modelfile.$NAME"
ollama create "$NAME" -f "Modelfile.$NAME"
ollama show "$NAME" | sed -n '1,12p'
$ sh import_gguf.sh unsloth/DeepSeek-R1-Distill-Qwen-1.5B-GGUF DeepSeek-R1-Distill-Qwen-1.5B-Q4_K_M.gguf deepseek-r1-distill:1.5b
✓ sha256 校验通过 f3bdf9cf31dee4b57ae4e455a1cb0d01b5c2c1b50d72d3112141c195506c2840
using existing layer sha256:f3bdf9cf… writing manifest success
Model
architecture qwen2
parameters 1.8B
context length 131072
quantization Q4_K_M
template source gguf_chat_template
Capabilities
tools
thinking
completion
template source: gguf_chat_template 表示 Ollama 直接使用了 GGUF 文件里自带的对话模板,所以 Modelfile 只需要一行 FROM。截断文件后的校验结果:
✗ sha256 不一致:<截断文件的哈希> ≠ f3bdf9cf…2840,删除后重新下载
第 2 步:测速度
bench.py:
"""在本机测本地模型的速度:用 Ollama 原生接口返回的 eval_count / eval_duration 计算生成速度(tokens/s)。
用法:python bench.py 模型1 模型2 ... 环境变量 OLLAMA_URL 默认 http://127.0.0.1:11434"""
import os, sys, requests
URL = os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434")
PROMPT = "用三句话解释什么是二分查找。"
def run(model, think=None):
body = {"model": model, "prompt": PROMPT, "stream": False, "options": {"num_predict": 256, "temperature": 0}}
if think is not None: body["think"] = think
d = requests.post(f"{URL}/api/generate", json=body, timeout=600).json()
if "error" in d: raise SystemExit(f"{model}: {d['error']}")
return d
for m in sys.argv[1:]:
run(m) # 第一次包含加载时间,丢掉
d = run(m)
gen = d["eval_count"] / d["eval_duration"] * 1e9
pp = d["prompt_eval_count"] / max(d["prompt_eval_duration"], 1) * 1e9
print(f"{m:28} 生成 {gen:5.1f} tok/s 读入提示 {pp:6.1f} tok/s 本次输出 {d['eval_count']} tokens")
$ python bench.py deepseek-r1-distill:1.5b qwen2.5-local:1.5b qwen3-local:0.6b
deepseek-r1-distill:1.5b 生成 43.6 tok/s 读入提示 646.4 tok/s 本次输出 256 tokens
qwen2.5-local:1.5b 生成 48.8 tok/s 读入提示 1853.3 tok/s 本次输出 29 tokens
qwen3-local:0.6b 生成 123.3 tok/s 读入提示 1950.7 tok/s 本次输出 256 tokens
这是 box 上的实测数字,你的机器会不一样,请在自己的机器上跑一遍。
第 3 步:网页界面(未在 box 上启动)
docker-compose.yml:
# Ollama + Open WebUI:在局域网里给家人或同事一个网页聊天界面
# 注意:Ollama 的 11434 端口没有鉴权,不要映射到公网;这里只让 Open WebUI 在容器网络内访问它
services:
ollama:
image: ollama/ollama
volumes:
- ./ollama:/root/.ollama
- ./gguf:/gguf:ro # 放 import_gguf.sh 下载好的文件,在容器里 ollama create
restart: unless-stopped
# 有 NVIDIA 显卡时取消注释(需要先安装 NVIDIA Container Toolkit)
# deploy:
# resources:
# reservations:
# devices: [{driver: nvidia, count: all, capabilities: [gpu]}]
open-webui:
image: ghcr.io/open-webui/open-webui:main
environment:
OLLAMA_BASE_URL: http://ollama:11434
volumes:
- ./open-webui:/app/backend/data
ports:
- "3000:8080"
depends_on: [ollama]
restart: unless-stopped
启动后打开 http://主机:3000,第一个注册的账号就是管理员。
调试示例:ollama pull 报“non-public”
在 box 上直接 ollama pull deepseek-r1:1.5b,会长时间卡在 “pulling manifest”,等不到结果时会报错。报错的关键信息是(节选并整理):Ollama 下载模型时会跳转到 *.r2.cloudflarestorage.com,而这个域名被解析成了 198.18.0.1;Ollama 判定这是非公网地址,拒绝跳转。
原因是 box 的网络把这个下载域名解析到了 198.18.0.1(198.18.0.0/15 是保留地址段,常见于代理或 DNS 拦截环境)。Ollama 认为这是非公网地址,拒绝跳转。这不是 Ollama 坏了,而是网络环境的问题。解决办法有两个:换一个网络;或者像本文这样从 Hugging Face 下载 GGUF 再导入。导入时一定要校验 sha256,避免文件下载不完整或被篡改。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 安装脚本提示缺 zstd | 新版安装包是 zstd 压缩的 | apt-get install zstd |
| 速度很慢 | 内存不够,模型被换到了磁盘上 | 换更小的模型或更低的量化 |
| 局域网其他电脑连不上 11434 | 默认只监听本机 | 用 Open WebUI 访问,不要直接开放 11434 |
模板规则
- 导入的模型文件一律校验 sha256。
- 速度以自己机器的实测为准。
- 无鉴权的推理端口不对外开放。
项目资源
| 用途 | 地址 |
|---|---|
| Ollama 导入模型 | https://docs.ollama.com/import |
| Ollama Linux 安装 | https://docs.ollama.com/linux |
| DeepSeek-R1-Distill-Qwen-1.5B(Hugging Face) | https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B |
| GGUF 量化版(unsloth) | https://huggingface.co/unsloth/DeepSeek-R1-Distill-Qwen-1.5B-GGUF |
| Open WebUI 文档 | https://docs.openwebui.com/ |
本地部署的价值在于数据不出门。代价是模型小、能力有限,所以要先测清楚它能做什么,再决定哪些任务交给它。