12GB 显存跑 35B,听起来像标题党。我第一眼看到这套数字,先把权重、上下文和内存三笔账分开算了一遍。

算完发现确实不是魔法:26 层 MoE 被丢给 CPU,权重用 Q4_K_M,KV cache 压到 Q8,剩下的靠 llama.cpp 在显卡和内存之间来回搬。能跑,50 tok/s 也是真的;但这套数字只回答「跑得动吗」,不回答「能不能当编码 Agent 用」。

这套配置到底把什么放进了 12GB

先看清单:

  • 显卡:RTX 3060,12GB 显存
  • 系统内存:16GB
  • 权重:Q4_K_M,35B 总参,按 4bit 出头算,光权重就接近 20GB
  • 注意力:Flash Attention 打开,KV cache 用 Q8
  • 分工:26 层 MoE 交给 CPU,其余层留在 GPU
  • 运行时:llama.cpp 的 llama-server

20GB 权重塞进 12GB 显存,唯一的路是拆开。26 层去 CPU,等于把一部分专家权重放进系统内存,GPU 只留放得下的部分。这不是「模型变小了」,是「模型被分成了两半」。

A3B 省的是算力,不是显存

35B 总参、3B 激活,意思是每个 token 只走 3B 左右的计算路径。decode 本来就是逐 token、吃显存带宽,活跃参数少,单步就轻,50 tok/s 不奇怪。

但「只激活 3B」不等于「只需要 3B 的显存」。全部专家还得存着,路由到哪个专家,哪个专家的权重就得在。把 A3B 理解成 3B 模型,是这类配置最容易踩的坑。省下来的是每一步的乘加,不是权重占的地方。

26 层下放 CPU,代价是每步都要过一趟 PCIe

GPU 算完自己的层,激活值要经 PCIe 交给 CPU;CPU 算完 26 层,再交回来。每生成一个 token 都要走一遍。层数越多、来回越频繁,瓶颈就从算力变成传输和内存带宽。

16GB 系统内存也不宽裕:一部分装下放的专家,一部分留给 KV cache 和运行时,剩下给系统的就不多了。浏览器、IDE、Docker 一开,数字会变。

一个 token 在 GPU 和 CPU 之间的往返
  1. 1路由选专家每层只激活少数专家,整轮约 3B 激活
  2. 2GPU 层计算显存放得下的层先在卡上算完
  3. 3激活值过 PCIe交给系统内存里的 26 层
  4. 4CPU 层计算用内存带宽换显存空间
  5. 5汇总采样各层算完后汇总 logits,采样出下一个 token
26 层在 CPU 上,搬运本身就是成本

262K 是配置值,不是可用值

配置里写 262K,只说明 n_ctx 能开到这么大,不代表这个长度下还能保持 50 tok/s,也不代表内存装得下。

KV cache 随 token 数线性增长。35B 级别的模型,层数和 KV 头数摆在那,Q8 已经压过一轮,26 万 token 粗算也是十几 GB 起步。12GB 显存加 16GB 内存,不可能全放显存,只能继续往 CPU 内存里挤。

实际能用的上下文,要一点点往上加:8K、16K、32K,每档看显存占用、系统内存、decode 速度,以及有没有开始 swap。掉到 swap 那一刻,速度不是打折,是换了一个数量级。

长上下文还有质量问题。能塞进去,不等于模型还能用住中间的信息。拿一段长文档做定位测试,比看参数表有用。

550 tok/s 的 prompt 和 50 tok/s 的 decode,不是一回事

prompt processing 是 prefill:整段提示一起进,矩阵乘能并行,550 tok/s 说的是这个阶段。decode 是一个 token 一个 token 往外蹦,每步都要读权重、走一遍 GPU 和 CPU 之间的往返,50 tok/s 说的是这个阶段。两个数字差十倍,不是测错了,是两种负载。

做编码 Agent 时,这个差别很要命。工具调用会把上下文越滚越长,每次追加结果都要重新 prefill 新增的部分;缓存命中做得好,只算增量,做不好就整段重算。聊天的体感是 decode,Agent 的体感往往是 prefill 加排队。只看 50 tok/s 去买机器,很容易失望。

真正该测的是活能不能干完

这套配置下一步该对比的,不是又一个 tok/s 数字,而是四件事:能不能写出可运行的代码、工具调用参数对不对、报错后能不能自己定位、多步任务会不会中途跑偏。

对本地编码 Agent 来说,一次任务重试三次,省下来的 token 成本全吐回去,人还得盯着。建议用同一组任务做 A/B:固定提示、固定工具集、固定上下文预算,记录完成率、平均重试次数、人工接管次数和总耗时。速度只作为其中一列。

50 tok/s 在交互式补全里很舒服,在长任务里可能被一次失败的 prefill 抵消。

想复现,先按这个顺序调

  1. 先确认显存余量。12GB 卡别一上来就开大上下文,先把 n_ctx 设到 8K,看模型加载后还剩多少。
  2. 用 Q4_K_M 起步,别急着上更低的档位。量化越低,权重越省,质量掉得也越明显;编码任务对量化很敏感。
  3. 用 `-ngl` 控制上卡层数,用 `-ot` 把专家张量单独指到 CPU。先让模型能起来,再谈速度。
  4. KV cache 先用 Q8,观察内存曲线;不够再降,别一开始就压到 Q4。
  5. 上下文逐档加,记录每一档的 decode 速度和内存占用,找到开始 swap 的那一档,往前退一档用。
  6. 最后再测任务完成率。跑分好看和能干活,是两件事。

12GB 显存跑 35B,成立的前提是把一半模型放进内存,再用 MoE 的稀疏激活把单步计算量压下来。这套思路值得学,但别把它读成「小显卡也能随便跑大模型」。最后决定能不能天天用的,是上下文开到多大、内存够不够,以及 Agent 任务能不能一次跑完。

50 tok/s 只是入场券。