开头那段话我憋了挺久。8GB显存跑35B模型,乍一听就是个标题党:35B权重就算压到4bit也得20GB左右,一张8GB显卡连零头都不够。但我实际跑起来之后发现,这里的关键不是“显卡能不能装下”,而是“推理时能不能让显卡和内存协作把模型拆开算”。我用的配置很普通:一张RTX 3070 8GB、64GB内存,系统是Windows 11加WSL2,模型选的是Qwen2.5-32B-Instruct的GGUF Q4_K_M量化版。折腾了几个星期,模型跑起来了,速度也确实能用,只是完全不是“秒回”的体验。这篇文章把我踩过的坑、调过的参数、实测过的速度全记下来,给想低成本玩本地大模型的人一个参考。先说结论:能跑,能干活,但你必须接受1秒出1~2个token的速度,以及一系列围绕显存和内存的取舍。
1. 为什么8GB显存跑35B这事值得折腾
1.1 显存账本:35B到底要吃多少内存
先把账算清楚。一个35B参数的模型,如果以FP16精度存储,每个参数占2字节,权重文件大约70GB。INT8量化能压到35GB左右,4bit量化(比如GGUF的Q4_K_M)能压到20GB上下。看起来跟8GB显存是天壤之别,但绝大多数人忽略了一个事实:本地推理不是非得把整个模型塞进显存不可。显卡负责的只是部分计算,模型可以按层拆开,一部分层放在GPU上,另一部分层放在CPU内存里,算完GPU上的层再把中间结果传给CPU继续算剩下的层。这就是为什么“8GB显存跑35B”在技术上可行。
我用生活里的例子解释一下。显存是桌面,内存是储物间,模型是一套需要按顺序翻阅的百科全书。桌面太小放不下整套书,但你不需要同时打开所有书,只需要把当前要读的那几本放桌上,读完一批再从储物间换下一批。推理过程是串行的,模型一层一层算,所以同一时刻只需要驻留“正在算的这几层”,而不是全部70GB。8GB显存只要能装下一部分层,剩下的交给内存,整台电脑就还能把活干完。
当然代价是速度。显卡算完一部分后,要把中间结果通过PCIe总线传给CPU,这部分数据交换非常频繁;再加上CPU本身算大模型就慢,最终生成的token速度会低到让人怀疑人生。后面我会给实测数据,这里先不卖惨。
1.2 三条路线:量化、按层拆分、控制上下文
我实测过程中试过三条路线,各有各的适用场景:
- Ollama默认加载:最简单,装好之后一条
ollama run命令就能跑。它会自动判断显存余量,把能放的层放到GPU上,剩下的留在CPU。适合只想快速验证“能不能跑”的人。 - llama.cpp手动指定GPU层数:用
-ngl参数控制到底放多少层到GPU,精度最高,适合需要反复微调显存占用的人。我最后长期用的是这条路。 - Ollama自定义Modelfile:在Ollama里通过
num_gpu参数实现类似-ngl的控制,兼顾了Ollama的便利和llama.cpp的灵活性。
三条路线的底层原理都一样:量化缩小模型体积,按层拆分降低峰值显存,控制上下文长度来压住KV Cache。后面每一章都会围绕这三个点展开。
1.3 适合谁、不适合谁
说点实在话,这套方案适合下面几类人:
- 有隐私需求,不想把对话内容传到云端的人;
- 需要批量处理长文本,比如翻译文档、做摘要、跑本地知识库,对响应速度不敏感的人;
- 单纯想研究大模型部署原理,把它当实验平台的人;
- 想在Dify这类工具里接一个免费、私有、不限量的本地模型,做企业内部知识库原型的人。
不适合的人是那些追求ChatGPT式交互体验的:你问一句话,等一两分钟才看到第一个字开始蹦,再等几分钟才能收到完整回答,普通用户大概率受不了。还有,如果你需要高并发,比如让几十个人同时用,消费级单卡加CPU的方案会直接卡死。真到那个规模,要么用云端API,要么上多卡服务器,这不是本文的讨论范围。
2. 实操前的准备:硬件、软件与模型选型
2.1 我的实测环境
先交代一下跑这套东西的环境,方便你对照:
| 部件 | 配置 | 备注 |
|---|---|---|
| CPU | AMD Ryzen 7 5700X,8核16线程 | 内存带宽很重要,尽量别用太老的CPU |
| 显卡 | NVIDIA RTX 3070 8GB | 8GB显存是这次折腾的核心限制 |
| 内存 | 64GB DDR4-3200(双通道) | 建议至少32GB,目标模型Q4_K_M约20GB,内存太小跑不动 |
| 硬盘 | NVMe SSD 1TB | 模型加载要从硬盘读,SSD能减少加载时间 |
| 系统 | Windows 11 22H2 + WSL2 Ubuntu 22.04 | llama.cpp跑在WSL2里,Ollama用Windows原生版 |
| CUDA | 12.1 | 确保显卡驱动能识别CUDA |
这里最容易被忽略的是内存。很多人看到“8GB显存跑35B”就以为8GB是全部需求,实际上40GB内存才是这套方案的真正门槛。20GB模型权重、Windows系统占用、浏览器、再加上推理过程中的中间激活值,32GB内存都很紧张。我把内存从16GB加到64GB之后,Ollama才不再随机崩溃,这是我这套方案里最值的一笔投资。
2.2 模型选型:别下safetensors,要下GGUF
35B这个称呼在开源社区没那么严格,我实测用的是Qwen2.5-32B-Instruct的GGUF量化版。为什么选GGUF而不是HuggingFace默认的safetensors?因为Ollama和llama.cpp都原生支持GGUF格式,整个文件就是为分块加载设计的,支持把部分层留在磁盘、部分加载进内存、部分发到GPU。safetensors是训练和微调用的格式,推理时加载起来非常笨重,不适合这种低显存场景。
各量化等级的体积和体验差异,我的体感如下:
| 量化等级 | 体积(约) | 实际体验 |
|---|---|---|
| Q2_K | 13GB左右 | 速度快一些,但中文效果明显下降,经常语序混乱 |
| Q3_K | 16GB左右 | 能跑,但明显感觉模型“有点傻” |
| Q4_K_M | 20GB左右 | 我长期使用的版本,中文能力、逻辑能力都可接受 |
| Q5_K_M | 24GB左右 | 质量更好,但8GB显存下内存压力太大,加载都费劲 |
| Q6_K/Q8 | 26GB以上 | 不建议这个场景尝试,你会被卡到怀疑人生 |
Q4_K_M是我综合体积、速度和效果后的甜点区。量化带来的损失是真实存在的,但Qwen2.5-32B这种大模型本身冗余很高,压缩到4bit后日常写作、翻译、代码补全依然够用。优先级排序:先保证模型能跑起来,再考虑质量。
2.3 安装Ollama和llama.cpp
如果你只是想快速跑起来,先装Ollama。去官网下载Windows版本,装好之后打开终端,拉模型:
ollama pull qwen2.5:32b ollama run qwen2.5:32b第一次会下载模型文件,之后每次加载会有一段时间的等待。Ollama启动时会自动分配GPU层数,但默认策略在8GB显存下不一定最优,所以后面我会教你创建自定义Modelfile。
如果你想像我一样精细控制,需要在WSL2里编译llama.cpp。步骤很简单:
git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DGGML_CUDA=ON cmake --build build --config Release -j编译好之后,用它加载GGUF模型:
./build/bin/llama-cli -m /mnt/d/models/qwen2.5-32b-q4_k_m.gguf -ngl 20 -c 8192 --temp 0.7-ngl是number of GPU layers,意思是把多少层放在GPU上。这个是整个调参过程中最重要的参数,后面我会单独讲怎么确定它的值。
3. 跑起来的三条路线与实测参数
3.1 第一次用Ollama跑,先看默认策略翻不翻车
我第一次跑的时候直接ollama run qwen2.5:32b,等模型加载完,输入“你好,介绍一下你自己”,屏幕卡了大约四十秒才开始吐字。当时心里还是挺激动的,毕竟8GB显存真的把32B模型拉起来了。但接着就发现问题:我用nvidia-smi看显存占用,发现GPU显存几乎被占满,而任务管理器的内存也在疯狂上涨。ollama ps显示模型加载在GPU和CPU各占了一部分,但具体分了哪些层看不到。
如果只是简单聊几句,默认策略能跑。但一旦对话变长、上下文累积,显存里的KV Cache会一路增长,很快就能把剩余显存挤爆。我的经验是,用默认策略跑10轮左右的对话后,报错概率急剧上升,有时候是CUDA out of memory,有时候是内存爆掉导致Windows卡死。所以,想要稳定跑,必须自己做显存规划。
解决办法是创建自定义Modelfile:
FROM qwen2.5:32b PARAMETER num_gpu 20 PARAMETER num_ctx 8192 PARAMETER temperature 0.7保存为Modelfile,然后执行:
ollama create qwen32b-8g -f ./Modelfile ollama run qwen32b-8g这样创建的新模型ID叫qwen32b-8g,它会严格按照20层GPU来加载。这个数字怎么看出来的?往下看。
3.2 llama.cpp如何确定GPU层数,以及实测速度表
用llama.cpp跑的时候,启动日志会打印每一层放在哪个设备上。关键信息类似:
llm_load_tensors: offloading 20 repeating layers to GPU llm_load_tensors: offloaded 20/65 layers to GPUQwen2.5-32B总层数大约64层(加embedding等)。offloaded 20/65 layers表示有20层在GPU、45层在CPU。怎么确定20这个数?方法很简单:先设置一个较小的-ngl值,比如10,跑起来后看nvidia-smi里这个进程的显存占用,记下来;再停掉,把-ngl加到15,再看显存增量。增量大约就是每多放一层的显存开销。我的环境里每层大约0.3GB,算上CUDA基础占用和KV Cache,20层对应6.5GB左右,给GPU留了1.5GB余量,比较稳。
我记录了一组实测速度,生成任务都设置同样的-c 8192:
| 配置 | 显存占用 | 实际速度(tokens/s) | 说明 |
|---|---|---|---|
| -ngl 0 | 约0.5GB | 0.42 | 纯CPU推理,慢到怀疑人生 |
| -ngl 10 | 约3.5GB | 0.87 | GPU开始帮忙,但帮助有限 |
| -ngl 20 | 约6.8GB | 1.32 | 我长期使用的配置 |
| -ngl 25 | 约7.6GB | 1.15 | 显存接近极限,性能反而下降 |
| -ngl 28 | 报错 | - | CUDA out of memory,直接崩溃 |
从表格能看出两个关键点:增加GPU层数并不总是提速,因为当显存快满时,KV Cache和中间激活值会被挤到内存里,反而增加交换开销。另外,推理速度不是由GPU层单独决定的,CPU负责的那几十层才是最大瓶颈。整条流水线的速度就取决于最慢的一环,跟生产线一个道理,你没设备扩展那个慢环节,光给前面的环节加人就白搭。
这里有个细节:为什么-ngl 25时显存占用只比-ngl 20多了0.8GB,速度却下降了?因为上下文长度的KV Cache原本由GPU做,但显存紧张后部分KV被放到了内存,每一轮生成都要在GPU和CPU之间搬运KV数据。搬运比计算还慢,所以提速变减速。
3.3 上下文长度与KV Cache:别把显存掏空
很多人跑大模型喜欢把上下文设置得很大,比如32K。但在8GB显存的约束下,这等于自杀。KV Cache的大小跟上下文长度线性相关,Qwen2.5-32B在8K上下文下大约需要2~3GB空间,这还是在开启flash attention之后的数字。
Ollama里可以通过Modelfile设置:
PARAMETER flash_attn on PARAMETER num_ctx 8192llama.cpp里对应:
./build/bin/llama-cli -m /mnt/d/models/qwen2.5-32b-q4_k_m.gguf -ngl 20 -c 8192 --flash-attn --temp 0.7--flash-attn能显著减少KV Cache的显存占用,效果非常明显。实测开启后,同样的8K上下文,显存占用能省下800MB到1GB,这笔账必须算。
如果你需要处理长文档,我的建议是不要把长文档一次性塞进去。先让模型做分段摘要,再把摘要拼起来,这样能把上下文控制在4K以内,既保证速度,又避免OOM。后面我会详细说这个工作流。
3.4 不同任务的实测体验
光看速度数字不够直观,我挑三个真实任务说说体验。
第一个是代码生成。让它写一个Python脚本,大概200行,模型输出大约900个token,1.32 tokens/s的速度意味着大约11分钟。这个速度说实话有点折磨人,但写出来的代码基本能用,简单的算法和API调用都正确。我觉得适合“你正在写代码,突然需要一个不常用函数的实现”这种场景,丢给模型慢慢生成,自己先去干别的。
第二个是翻译。把一篇5000字的英文技术文档翻译成中文,这个场景最推荐。翻译任务对交互速度要求低,你只需要等它把整段翻译完。我实测的速度虽然慢,但翻译质量相当不错,术语处理比很多在线翻译工具都专业。这算是本地大模型最适合的落地方案之一。
第三个是长文写作。让它写一篇3000字的行业分析,大约4500个token,算下来要跑接近一个小时。这就不适合实时等待了,我是写进脚本里让它后台跑,跑完再去看。如果你只是想快速生成一篇短文,还是用云端API更实际。但如果你在意数据隐私,或者就是不想为API付费,那等一小时也不算完全不能接受。
4. 让它干活:服务化接入与日常使用
4.1 把模型变成API服务,给Dify和知识库用
跑通Chat界面只是第一步。真正让它有价值的做法,是把Ollama变成后台API服务,这样Dify、WebUI、甚至自己的Python脚本都能调它。
Ollama装好后默认会监听11434端口。你可以启动服务:
ollama serve然后验证接口是否可用:
curl http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen32b-8g", "messages": [{"role": "user", "content": "你好"}], "stream": false }'注意返回里的model字段必须跟ollama list里的模型ID完全一致,我自己就曾因为在Dify里填了qwen2.5:32b而不是自定义的qwen32b-8g,导致连接一直报错。
4.2 Dify接入本地大模型的完整配置
Dify接入Ollama的步骤不复杂,但有几个坑必须提前知道。
在Dify后台进入“设置 → 模型供应商”,选择Ollama,填写模型名称和Base URL。这里的Base URL有个经典的坑:Dify如果是用Docker启动的,它访问不了宿主机的localhost。你在Dify容器里填http://localhost:11434,它指向的是容器自己,而不是你装Ollama的Windows宿主机。正确写法要区分场景:
- Dify以Docker方式运行在Windows:填
http://host.docker.internal:11434 - Dify以Docker方式运行在Linux:填
http://172.17.0.1:11434 - Dify直接跑在裸机:填
http://localhost:11434
填完后点测试,如果能通,就把模型ID填成qwen32b-8g。之后你在Dify的应用编排里,LLM节点就能选到这个本地模型。配合Dify的知识库,你就拥有了一套完全私有化的RAG问答系统,企业里做内部文档问答、客服助手,数据都留在本地,这一点对很多公司来说比模型精度还重要。
不过要注意,Dify默认的请求超时可能比较短。本地模型生成一个回答需要几十秒到几分钟,如果超时设置太小,Dify会直接报“请求超时”错误。我建议把请求超时调到120秒以上,或者尽量让模型用短回答模式。在Dify的LLM节点里可以设置temperature低一点,比如0.2,并让System Prompt强调“回答尽量简洁”,这样能显著减少等待时间。
4.3 让本地35B干活的几个调参心得
跑了大半个月,我总结出几条非常实用的心得:
- 提问越短,出结果越快。本地大模型的prefill阶段(读入你的prompt)也很慢,你把背景信息写一大堆,光是“阅读理解”就要卡半分钟。尽量用简洁的prompt。
- 一次只问一件事。如果一个问题里带了三四个子问题,模型输出会很长,速度会拖到无法接受。拆成多个请求,分段获取结果,体感反而好。
- 设定输出长度。Ollama的Modelfile里可以加
PARAMETER num_predict 512,llama.cpp里对应-n 512,限制最大输出长度,防止模型失控一直写下去。 - 温度别调太高。Q4量化后模型本身有一定随机性,温度太高容易跑偏和重复。一般对话用0.7,任务型场景用0.2。
- 对话别太长。每多一轮对话,KV Cache就变大,速度也会下降。遇到长任务,我会直接开新会话,只把必要的背景塞进去,而不是在一个会话里连续聊几十轮。
这些心得的核心都是同一个原则:本地跑大模型,你要把token视为稀缺资源,尽量用最少的输入输出把活干完。云端API可能不在乎你多传几千个token,本地8GB显卡很在乎。
5. 常见问题与排查实录
5.1 CUDA out of memory:显存爆了的自救清单
这是我最开始遇到最多的错误。典型场景是加载模型后没跑几句,直接报CUDA out of memory。排查步骤按顺序来:
- 先看
ollama ps,确认当前加载的模型数量。Ollama会保留最近用过的模型在显存里,如果之前加载了另一个模型,会占掉不少显存。 - 看
nvidia-smi,确认有没有其他程序占用显存。浏览器开几十个标签页、3D渲染软件、甚至某些桌面特效都可能吃显存。 - 降低
num_gpu,从20降到15,给KV Cache留出空间。 - 真到万不得已,把量化等级从Q4_K_M降到Q3_K。这会牺牲质量,但至少能跑。
我遇到过最隐蔽的坑是:Windows的桌面窗口管理器会占用几百MB显存,WSL2里跑的CUDA进程有时会把这部分也算进去。所以给GPU留1.5GB余量不是保守,而是必须。
5.2 内存和虚拟内存:为什么建议至少64GB
我一开始用32GB内存跑,系统经常变得特别卡,鼠标都移不动。原因是20GB模型权重占掉大部分内存,Windows、浏览器、后台进程再占10GB,内存马上见底。系统就开始疯狂用虚拟内存,把数据往硬盘上倒腾,这比CPU慢速推理还致命。
排查方法很简单:任务管理器里看“内存使用率”,如果长期在95%以上,就说明内存不够。解决办法两选一:加物理内存,或者把虚拟内存(页面文件)扩大到32GB以上。我后来加了内存条,世界清净了。
还要提醒一个细节:如果开了虚拟内存,Windows会把不常用的模型层交换到页面文件里,你可能发现第一次加载模型特别慢,但生成时偶尔也会卡住好几秒。页面文件能防止崩溃,但不能防止慢。所以预算允许的话,物理内存尽量给到64GB。
5.3 速度慢得离谱,到底有没有用到GPU
很多人跑完第一反应是“这也太慢了”,然后怀疑模型根本没用到显卡。其实80%的情况下,你的GPU确实在干活,只是CPU层拖了后腿。但为了排查,可以用这几个方法确认:
ollama ps会输出PROCESSOR列,显示模型是100% CPU还是包含了GPU。nvidia-smi看GPU利用率。如果能跑到40%~60%,说明GPU在计算;如果一直是0%,说明没分配GPU层。- 看llama.cpp的启动日志,里面有
offloaded 20/65 layers to GPU之类的信息。
如果确认GPU层数为0,通常是两个原因:一是Ollama版本太老,没识别到CUDA;二是WSL2里没安装正确的CUDA驱动。重新装驱动、升级Ollama就能解决。
比较反直觉的一点是:即使GPU利用率达到100%,整体速度也可能只有1 tokens/s。因为CPU层是瓶颈,GPU算完一批数据后要等CPU算完才能继续。这就像一条流水线,一个环节慢,其他环节再快也没用。想提速的唯一办法是让GPU多包揽一些层,但显存用完后就到头了。这时候如果CPU内存带宽更高、CPU核心更强,速度会有一点点提升,但不会有质变。
5.4 常见问题速查表
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 输入prompt后很久才出第一个字 | 模型在做prefill,加上CPU计算慢 | 缩短prompt;换更强CPU;开启flash attention |
| 生成到一半报CUDA out of memory | KV Cache随对话增长占满显存 | 清空会话;降低num_ctx;增大num_ctx的余量 |
| 模型加载后再次打开还要重新加载 | keep_alive时间太短 | Ollama设置OLLAMA_KEEP_ALIVE=10m或更长 |
| Dify测试连接失败 | 容器访问宿主机地址不对 | 按Dify运行方式正确填写Host地址 |
| API请求总是超时 | 本地模型响应太慢 | 调大客户端超时到120秒以上,并限制输出长度 |
| 对话内容重复、混乱 | 量化后模型质量下降 | 调高temperature;换Q5_K_M;清上下文重开 |
5.5 关于“企业搭建本地大模型”的运维成本
网上经常看到“花二三十万买硬件搭本地大模型”的讨论。我的看法是,预算高的方案运维量同样不小:多卡服务器的驱动、CUDA版本、容器编排、监控告警,每一项都够运维忙一阵。但我这套消费级方案几乎是零运维,开机后启动Ollama,模型文件躺在硬盘里,平时就是占点内存和硬盘空间,不用管它。真要说运维,也就是定期看一眼模型版本、清理一下没用的模型文件。这也是消费级方案的一个隐藏优势:够简单,坏了也没那么心疼。
6. 实操留一句自己的体会
折腾这段时间,我最大的体会是:跑不跑得动,跟显存有关;跑得舒不舒服,跟内存和预期有关。8GB显存跑35B,技术上早就可行,难的是接受1秒1个多token的现实。可一旦把它定位成后台任务、离线批处理、私有知识库的底座,这种速度反而没那么重要。如果你是刚入门,我建议先不用看那么多理论,直接把Ollama装好,下个Q4_K_M的GGUF模型,创建一份带num_gpu 20的Modelfile,跑两句体会一下。然后你就会明白,为什么我会说“显存不够不是死路,但内存一定要管够”。