先说结论:一台十年前的机器,能跑、能对外提供服务,但整个过程不是"装完就完事"------这代硬件(AMD Polaris 显卡 + 没有 AVX2 的 Ivy Bridge CPU)逼出了三个反直觉的决定:关掉 flash attention、接受 CPU 当主力、用固定别名把参数钉死。
环境:Intel Xeon E5-2670 v2 / 32 GB DDR3L / AMD RX 580 2048SP 8 GB / Windows 10 专业版 19045,跑 Ollama 0.40.14 + 千问 3.5 4B(Q4_K_M),给局域网内的 WorkBuddy、Dify 等平台提供 OpenAI 兼容 API。
一、硬件直接决定了方案边界
| 项目 | 实际配置 | 对方案的影响 |
|---|---|---|
| CPU | Xeon E5-2670 v2,10 核 20 线程,Ivy Bridge,仅 AVX 无 AVX2 | 只能当兜底算力,预填充约 54~74 tok/s |
| 内存 | 32 GB DDR3L-1600 | 撑得住 9.6 GB 的模型 + KV 占用 |
| 显卡 | RX 580 2048SP / 8 GB,Polaris gfx803 | ROCm 不支持(只支持 RX 7600 起的 RDNA3),只能走 Vulkan |
| 系统 | Windows 10 19045 | Ollama 0.40.x 默认启用 Vulkan 后端,Polaris 可用 |
三条硬边界:
- 不能上 ROCm,只能 Vulkan。 Vulkan 在 Polaris 上对"非标准注意力结构"支持不全,这是后面所有取舍的根源。
- 8 GB 显存决定模型上限。 4B Q4(3.3 GB)宽裕,8B Q4(约 5 GB)临界,14B 以上必须 CPU 混合、速度崩到 1~2 tok/s。
- 模型选型定死。 能用的就是 Qwen3.5 4B(混合架构)。换纯稠密模型(如 qwen3:4b-instruct-2507)虽然更上卡,但实测会随机输出整串 0 的静默缺陷,已放弃并删除。
顺带一提:本机还有个向日葵虚拟显示器 OrayIddDriver Device,没卸载------它不参与 Vulkan 计算,删了没收益,还可能影响远程连接。
二、最终配置长什么样
对外只有一个名字:qwen-api。所有平台都填这个模型名,底层换模型只重建别名,调用方代码不用改。
dockerfile
FROM qwen3.5:4b-q4_K_M
PARAMETER num_ctx 16384
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER repeat_penalty 1.05
PARAMETER num_predict 2048
SYSTEM "你是运行在这台 Windows 电脑上的本地大模型助手。回答简洁、准确,默认使用中文;不确定时直接说明不确定,不要编造事实。"
用户级环境变量(Ollama 只在启动时读一次,改完必须重启服务):
| 变量 | 值 | 作用 |
|---|---|---|
| OLLAMA_HOST | 0.0.0.0:11434 | 监听所有网卡,否则局域网其他机器连不上 |
| OLLAMA_KEEP_ALIVE | -1 | 模型永久常驻,不主动释放 |
| OLLAMA_MAX_LOADED_MODELS | 1 | 8 GB 显存只容得下一个模型 |
| OLLAMA_NUM_PARALLEL | 2 | 名义 2 路并发,实测被压到 1 槽 |
| OLLAMA_FLASH_ATTENTION | false | 关键反向设置 |
| OLLAMA_CONTEXT_LENGTH | 16384 | 兜底值,实际以 Modelfile 的 num_ctx 为准 |
服务用启动脚本拉起(脚本里先设好变量再启动),避免"改了环境变量却没生效"这个高频坑。没做开机自启,重启后手动跑一次。
三、七次调参,每一次都为什么
1. flash attention:从开到关(本次部署最重要的决定)
默认开启时看着很完美:33/33 层全上卡、100% GPU、占 3.4 GB、生成 8.6 tok/s。
但实测发现长提示会静默输出垃圾(一整串 0),而且稳定复现:
| 提示 token | 输出 |
|---|---|
| 1633 | 正常 |
| 1708 | 垃圾(全 0) |
| 1783 | 垃圾 |
| 1858 | 垃圾(连测两次均复现) |
关掉 FA 后,同样 1858 token 的提示完全正常。根因是 Vulkan 后端在 Polaris 上的 flash attention 在长序列存在数值缺陷,不是模型损坏、也不是参数配错,调参救不回来。
代价照实说:
| FA 开 | FA 关(当前) | |
|---|---|---|
| 上卡层数 | 33/33 | 1/33(16384 上下文时 0/33) |
| 占用 | 3.4 GB | 8.7 → 9.6 GB |
| 生成速度 | 8.6 tok/s | 5.5 tok/s |
| 预填充 | 55 tok/s | 72~74 tok/s(8192 时) |
| 长提示 >1650 token | 输出垃圾 | 正常 |
取舍逻辑只有一句:可预期的慢,优于不可预期的错。 给程序用的 API,正文突然变成一串 0 且不报错,比慢危险得多。
2. num_ctx:8192 → 16384
最初按"够用就好"定 8192,后来要接 Dify 这类会累积对话历史的多平台,才提到 16384。同样 4098 token 的提示,代价如下:
| 配置 | 预填充 | 每轮固定开销 | 占用 | 上卡 |
|---|---|---|---|---|
| 8192 | 55 |
约 56 s | 8.7 GB | 1/33 层 |
| 16384(当前) | 约 76 s(54 tok/s) | 约 76 s | 9.6 GB | 0/33 层 |
每轮多花约 20 秒,原因是 KV 缓存翻倍 + 显卡装不下层,退化成接近纯 CPU 推理。
但要分清场景:WorkBuddy 每轮提示固定 4098 token 且不带历史,4098 + 2048 = 6146 本来就在 8192 之内。只给它用,8192 更划算;多平台共用才需要 16384。
3. num_predict:1024 → 2048
第一次接 WorkBuddy 就踩到:回答生成到 1024 token 正好撞上限被截断,客户端自动发起第二次请求续写,两段合计 7 分 38 秒;续写时模型还编造了一段"Token 溢出恢复模式"的自述文字。

提到 2048 后该现象消失,代价是长回答最坏情况耗时翻倍。
4. OLLAMA_HOST:127.0.0.1 → 0.0.0.0
默认只监听本机,另一台电脑的 WorkBuddy 根本连不上。改成 0.0.0.0 后监听变为 :::11434,局域网访问 /api/version 实测返回 0.40.14。
安全提醒:这等于同网段任何设备都能免鉴权调用本机显卡。只建议在可信网络开;要收紧就只绑具体网卡 IP 并限定来源 IP。
5. keep_alive=-1 与 max_loaded=1
这台机器就是用来跑模型的,所以让模型永久占住显存(ps 里 UNTIL 显示 Forever),避免每次请求重新加载(首次加载约 20 秒)。同时限死只加载一个模型,防止 8 GB 显存被第二个模型撑爆。
6. NUM_PARALLEL=2:留着但没收益
实测 2 路并发:单路 8.7 s,两路分别 8.3 s / 16.6 s,总墙钟 16.6 s,相对串行 17.4 s 只有 1.05x。单路请求已经吃满显存带宽,加并发数不能提吞吐,只能排队。
更关键的是:上下文提到 16384 后,日志显示 n_slots = 1,Ollama 自己就把并发槽压回 1 了。
7. 别名固化参数
把上下文、采样、输出上限、系统提示全钉进 qwen-api 的 Modelfile,好处是任何客户端接进来都自动继承正确参数,不用每个平台各配一遍。
四、接第三方 Agent 客户端才知道的事
以 WorkBuddy 为例,配置很简单:

Base URL 填 http://本机IP:11434/v1(必须带 /v1),Key 随便非空即可,模型名必须与 ollama list 一致,思考模式不勾选。
接通之后暴露出来的三个事实,比模型本身更值得记:
- 每轮提示固定 4098 token,且每轮从头重算。 日志连续四次请求都是 4098,一个 token 不差------因为它不带对话历史,缓存无从命中。这就是"问什么都得先等一分钟"的原因,与问题长短无关。顺便说,这也是它记不住上一轮聊了什么的原因,不是模型健忘。
- 关掉"工具调用"并不能缩短提示。 取消勾选后仍是 4098,大头是客户端自己注入的项目记忆与系统提示。
- 一轮对话的真实耗时构成:提示 4098 token(预填充 56 s)+ 生成 544 token(97 s)= 服务端 3 分钟,界面显示 3 分 54 秒,多出来的是它自己启动运行环境的时间。
实测截图(一句话解释缓存,生成 544 token、正文完整、无思考残留):

简单算术题也验证过准确性与耗时(2+3*4 返回 14,9 的三次方返回 729):

五、另外两条坑
5.1 思考模式:两个接口两种关法,服务端关不掉
Qwen3.5 默认开思考,对程序调用是灾难------一句话回答烧掉 500+ token,正文可能为空。
| 接口 | 关闭写法 |
|---|---|
| 原生 /api/chat、/api/generate | "think": false |
| OpenAI 兼容 /v1/chat/completions | "reasoning_effort": "none"(think:false 在此无效) |
实测(同一问题,上限 150 token):
| 传参 | 耗时 | token | 正文 |
|---|---|---|---|
| 什么都不传 | 26.2 s | 150(烧满) | 空 |
| think: false(走 /v1) | 25.9 s | 150 | 空(参数被忽略) |
| reasoning_effort: "none" | 6.2 s | 34 | 65 字正常 |
服务端试过三种固化方式,全部无效:Modelfile 里 PARAMETER think false(直接报 unknown parameter)、系统提示词强令禁止思考、提示词加 /no_think。结论:思考只能在调用方关。
例外是 WorkBuddy:界面里"思考模式"不勾选时输出是干净正文,说明它内部已经处理了。
5.2 1650 token 的红线
FA 关闭是当前配置,这条红线在 FA 开时才会触发;当前配置下长提示正常。但一旦有人为了提速把 FA 打开,就必须同步约束每次输入不超过 1500 token(约 1000 汉字),否则静默输出垃圾。
六、维护与排查
powershell
& "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" ps
& "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" list
Invoke-RestMethod -Uri "http://127.0.0.1:11434/api/version"
改参数:改 Modelfile → create qwen-api -f Modelfile → 下次请求自动生效。
看日志定位问题:server.log,重点看 prompt eval time(每轮真实提示长度与速度)、offloaded N/M layers(上卡层数)、flash_attn(FA 状态)。
| 现象 | 先查什么 |
|---|---|
| 回答为空 | 思考模式没关(/v1 用 reasoning_effort: none) |
| 输出一整串 0 | 提示过长且 FA 被打开了(>1650 token) |
| 慢到几分钟 | 预填充在 CPU 上跑,看 ps 的 PROCESSOR 占比 |
| 别的电脑连不上 | OLLAMA_HOST 没生效或防火墙拦了 |
| 改了环境变量没反应 | 没用启动脚本,服务继承了旧环境 |
回滚:停进程 → unins000.exe /VERYSILENT → 删 .ollama 目录 → 清 6 个 OLLAMA_* 变量。
七、这套配置适合谁
适合:单机、少量客户端、能接受"一分钟级"响应、要求结果正确不能出垃圾的场景。
不适合:高并发、亚秒级响应、需要显卡吃满速率的场景。这代硬件(Polaris + Ivy Bridge)的天花板就在这里,软件层面能调的已经调完------再提速只剩换 N 卡走 CUDA,或换 RDNA3 走 ROCm 两条硬件路。