实测 Ollama 本地部署:我用 Python 搓了个离线问答机器人,Qwen4 27B 一到换个模型名就能用

先把话说清楚:这篇没有测到 Qwen4。Qwen4 的 27B 本地版权重到今天还没放出来,没有权重、没有模型卡、没有价格、没有发布时间,拿不到就没法测。这篇测的是 qwen2.5:0.5b,一个 397MB 的小模型,跑在本地 CPU 上。机器人搭好了,检索、问答、不知道就直说,全都跑通了。等 Qwen4 27B 的权重一到,代码里 MODEL 那行改个名字就能换。这篇就是来验证这条路走不走得通,顺带把踩到的坑都记下来。

先说清楚这套东西是什么。Ollama 是本地跑大模型的工具,模型下载到本机,推理也在本机,不用联网,不用 API key。离线问答机器人是三件套:本地知识库(几行笔记)、检索(TF-IDF 余弦相似度,第 13 期 ML 篇刚讲过)、本地模型(qwen2.5:0.5b)。适合谁:想在自己机器上跑个能问答的小东西、不想把数据发给云端 API 的人。做完长什么样:命令行里输入"问答机器人怎么加记忆?",机器人先从知识库里找出最相关的两行,再让模型照着资料回答;问知识库里没有的东西,它直说答不了,不编。

三步走。第一步装 Ollama,拉模型,确认模型真能出字。第二步搭检索,知识库 9 行笔记,查得到才往下走。第三步把检索和模型接起来,跑通三问:两问答得上,一问老实说不知道。

第一步,目标是 Ollama 装好、模型拉下来、生成跑通。Ollama 0.40.15,我从 GitHub release 下的 linux-amd64 包,完整大小 1472MB。装完启动服务,拉模型:

bash 复制代码
ollama serve
ollama pull qwen2.5:0.5b
ollama list

验证:ollama list 显示 qwen2.5:0.5b,397MB。第一句生成我用 API 直接测的:

python 复制代码
import json, urllib.request

body = json.dumps({"model": "qwen2.5:0.5b",
"prompt": "用一句话回答:滑动窗口是什么?",
"stream": False}).encode()
req = urllib.request.Request("http://127.0.0.1:11434/api/generate",
data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=300) as r:
data = json.load(r)
print(data["response"])
print("总耗时 ms:", data["total_duration"] // 1000000)

验证:回答是"滑动窗口是一种算法,用于在数据流中过滤和处理数据流中的特定块。"总耗时 8.5 秒,其中模型加载占了大头,真正生成 21 个 token 只用了 2.1 秒。0.5b 的回答很笼统,但格式对了,服务通了。坑有两个,都是这篇实打实踩到的。第一个坑在下载:我第一次下载只拿到 466MB 就断了,/tmp 只有 512MB,装满了。换到 home 目录重新下,断点续传补到 1472MB 才完整。第二个坑更隐蔽:截断的包解压出来能启动服务、能拉模型,但调生成直接报错,llama-server 二进制文件不存在。服务看着正常,生成一步就崩。补齐完整包、重启服务,生成才通。装完先跑一句生成,别只看服务起来了就以为完事。

第二步,目标是检索跑通,查得到、查得准、查不到能认出来。知识库就是第 13 期 MCP 篇里那 9 行笔记,分三个文件放着:rag_notes.txt、deploy_notes.txt、faq.txt。检索用第 13 期 ML 篇的余弦相似度,词表从知识库里现建。代码:

python 复制代码
import os, re
import numpy as np

TOKEN_RE = re.compile(r"[\u4e00-\u9fff]|[a-zA-Z0-9]+")
MIN_SIM = 0.3

def tokenize(text):
return TOKEN_RE.findall(text.lower())

docs, sources = [], []
for fname in sorted(os.listdir("kb")):
if not fname.endswith(".txt"):
continue
with open(os.path.join("kb", fname), encoding="utf-8") as f:
for i, line in enumerate(f, 1):
line = line.strip()
if line:
docs.append(line)
sources.append(f"{fname} 第 {i} 行")

vocab = sorted({tok for d in docs for tok in tokenize(d)})

def vectorize(text):
v = np.zeros(len(vocab))
for tok in tokenize(text):
if tok in vocab:
v[vocab.index(tok)] += 1
return v

doc_vecs = np.array([vectorize(d) for d in docs])

def retrieve(query, top_k=2):
qv = vectorize(query)
qn = np.linalg.norm(qv)
if qn == 0:
return []
sims = doc_vecs @ qv / (np.linalg.norm(doc_vecs, axis=1) * qn)
order = np.argsort(-sims)[:top_k]
return [(float(sims[i]), docs[i], sources[i])
for i in order if sims[i] >= MIN_SIM]

验证:我本地跑出来的三问,问"问答机器人怎么加记忆?"命中 0.5976,faq.txt 第 1 行,讲滑动窗口留最近 5 轮的那行;第二命中 0.3371,deploy_notes.txt 第 1 行。问"令牌桶怎么限流?"命中 0.4336,rag_notes.txt 第 3 行,讲令牌桶的那行。问"量子纠缠是什么?"无命中,相似度最高的才 0.2182,被 0.3 的门槛拦住了,机器人会老实说不知道。坑在分词:第一版我用的正则把连续中文当成一个词,"令牌桶怎么限流"整个成了一个词,知识库里只有"令牌桶限流"这个词,对不上,检索直接返回空。改成按字切分,一个汉字一个 token,"令牌桶怎么限流"才命中 0.4336。代价是常用字会带来低分误命中,"量子纠缠是什么"里的"是、什么"和很多文档都沾边,最高 0.2182。门槛 0.3 就是用来挡这种的:宁可答不知道,也不硬凑一个低分答案。0.3 这个数是在这个 9 行小库上试出来的,换个库得重新试。

第三步,目标是检索加模型接起来,跑通三问。主流程就几十行:检索出资料,拼进提示词,调 Ollama 生成。提示词里写死两条:只用资料回答,不要编造;用两三句话回答。回答限长 60 个 token,防着小模型没完没了往下写。代码:

python 复制代码
import json, time, urllib.request

MODEL = "qwen2.5:0.5b"
OLLAMA_URL = "http://127.0.0.1:11434/api/generate"

def ask_ollama(prompt):
t0 = time.time()
body = json.dumps({"model": MODEL, "prompt": prompt, "stream": False,
"options": {"num_predict": 60}}).encode()
req = urllib.request.Request(OLLAMA_URL, data=body,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=300) as r:
data = json.load(r)
return data["response"].strip(), time.time() - t0

def answer(question):
hits = retrieve(question)
if not hits:
return "知识库里没找到相关内容,这个问题我答不了。", hits, 0.0
context = "\n".join(f"- {text}" for _, text, _ in hits)
prompt = (f"只用下面这些资料回答问题,不要编造资料里没有的内容,用两三句话回答。\n"
f"资料:\n{context}\n问题:{question}\n回答:")
resp, secs = ask_ollama(prompt)
return resp, hits, secs

验证:我本地跑出来的三问,第一问"问答机器人怎么加记忆?"检索命中 0.5976,faq.txt 第 1 行,第二命中 0.3371,deploy_notes.txt 第 1 行。模型回答用了 53.2 秒,原文是:"问答机器人可以通过使用滑动窗口算法,将对话内容进行滚动和压缩,以减少信息量,优化存储空间,提高模型的识别和预测能力。"回答用上了检索到的滑动窗口和压缩,意思基本对,后半句"优化存储空间,提高模型的识别和预测能力"是模型自己补的套话,资料里没有这句,但也不算编造事实。第二问"令牌桶怎么限流?"检索命中 0.4336,rag_notes.txt 第 3 行。模型回答用了 60.9 秒,原文是:"令牌桶限流的原理是在一定时间内(60行代码)请求的最大调用次数被拒绝,如果请求次数超出了这个限制,流量将被拒绝。"这句答糊涂了:它把资料里的"60 行代码"理解成了一个时间单位,逻辑也绕晕了。检索是对的,模型把资料用错了。0.5b 就是这个水平,检索能保证它不凭空编,但保证不了它把资料读对。第三问"量子纠缠是什么?"检索无命中,0.0 秒直接返回:"知识库里没找到相关内容,这个问题我答不了。"不知道就直说,这句比第二问的糊涂答案强。

Qwen4 的事单独说一句,免得有人以为这篇测到了。11 月 6 日官方预热图"北冥有鱼",四个档位 Max、Plus、Flash、27B,27B 是本地部署的开源权重档。截至 11 月 8 日,权重没放出来,没有模型卡、没有许可证、没有价格、没有发布时间。社区的普遍做法是先用 Qwen3.8-27B 把本地这套跑通,等 Qwen4-27B 一到换个模型名。这篇的 MODEL 就是一行配置,换成"qwen4:27b"就能用,检索和提示词都不用动。27B 跑本地要 20GB 左右显存(32K 上下文,Q4_K_M 量化),24GB 显卡或者 32GB 以上内存的 Mac 才够,0.5b 这种小模型才适合 CPU 随便跑。

避坑清单单独列一遍。第一条,下载先看完整大小,1472MB 的包只下到 466MB,解压出来看着正常,生成一步就崩,llama-server 缺失。装完先跑一句生成确认。第二条,/tmp 只有 512MB,大文件别往里下,换 home 目录。第三条,中文分词别拿连续中文当一个词,按字切分,召回才对;代价是常用字误命中,拿相似度门槛挡。第四条,小模型在忙机器上慢得离谱,这篇实测生成只有 0.46 token/秒,第一问 53.2 秒,第二问 60.9 秒,原因是同机在跑论文批处理,内存只剩 400 多 MB,模型被挤进交换分区。本地跑模型之前先看机器有多忙。第五条,num_predict 限长会截断回答,第一版没写"用两三句话回答",60 个 token 用了 144.5 秒还被截断在半句上;提示词里写死"用两三句话回答"之后,两问都在 61 秒内答完,句子完整。第六条,检索不到就直说不知道,门槛 0.3 是底线,低分硬凑比不知道更糟。第七条,0.5b 会把资料读错,"60 行代码"能被它理解成时间单位,检索对了不等于答案对了,小模型的答案要逐句验,不能直接上生产。

照着打勾的清单:Ollama 0.40.15 装好,qwen2.5:0.5b 拉下来 397MB,第一句生成 8.5 秒、21 个 token;检索三问,0.5976、0.4336、无命中,门槛 0.3 挡住了 0.2182 的误命中;分词 bug 复现过、修好了;三问最终结果,第一问 53.2 秒答对大意,第二问 60.9 秒把"60 行代码"读错了,第三问 0.0 秒老实说不知道。Qwen4 没测到,权重没出,MODEL 一行配置等着换。

下期预告:下期把第 13 期 Python 篇的手写 MCP Server 接进这个机器人,模型自己决定什么时候查本地文件,什么时候直接答。这个系列在专栏《AI话题解读》里连着更新,每篇都是一个真实跑通的东西,关注后专栏里能连着看。

这篇建议收藏。Ollama 本地部署的坑都在这了,下载、缺 runner、分词、门槛、限长,收藏这篇,下次自己搭离线机器人直接照着抄,Qwen4 权重一到改个模型名就能用。

你本地跑过最小的模型是哪个?在什么机器上,速度怎么样?

署名:Cyrus

相关推荐
美团技术团队14 分钟前
美团 LongCat-2.5 开源:从看见,到抵达
人工智能
aifannihao21 分钟前
AI 学习第 8 课:1200 列纯噪声 CV 出 0.47,泄漏在筛特征那步
人工智能·机器学习·scikit-learn·学习路线·数据泄漏
老金带你玩AI38 分钟前
我宣布!Magpie就是版本答案!
人工智能
Rocky Ding*1 小时前
REPA-E技术深度解析:解冻 VAE 之后,扩散模型为什么需要选择梯度
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·repa-e
2601_967980111 小时前
Python中flatten( )函数及函数用法详解
python·numpy·数组操作·flatten函数·列表处理
xbzb1 小时前
自定义 yum 软件源:Linux 上 createrepo,Windows 上纯 Python 也能造
linux·运维·windows·python·yum·createrepo
武雄(小星Ai)1 小时前
Ollama报llama-server binary not found排坑实录:服务活着引擎没了
人工智能·windows·大模型·ollama·排坑
bryant_meng1 小时前
【TNT】《Transformer in Transformer》
人工智能·深度学习·transformer·word embedding·tnt·sentence embed
jay神2 小时前
基于 YOLOv8 的轴承缺陷检测(Web 版)
前端·人工智能·yolo