本地模型不是「更便宜的 Claude」,它是一类有自己工程约束的部件。
2026 年,一台 32GB 的 Mac 已经能跑得动 Qwen / Llama / gpt-oss 这一档的开放权重模型。于是很多人把本地模型当成「免费版 Claude」直接塞进 agent——然后撞上一堆莫名其妙的退化:长 context 突然失忆、tool call 格式崩、同一个模型在 Ollama 里和 MLX 里表现不一样。问题不在模型,在于本地推理是一套和调云端 API 完全不同的工程:你要自己背负量化决策、显存预算、运行时选择、约束解码。这一期不讲「量化是什么」或「怎么装 Ollama」(那是入门),而是讲四个决定本地方案成败的工程判断:量化到底损失了什么、显存够不够用怎么算、四个运行时差在哪、以及小模型做 agent 的现实边界与 hybrid 路由。目标是让你在「这个任务该本地还是上云」这个问题上,给出有数字支撑的答案。
显存占用第一性公式:权重字节数 = 参数量 × 每参数字节。FP16 是 2 bytes/param,所以 7B 模型裸权重 14GB;量化到 4-bit(约 0.5 byte/param 含 scale 开销)压到 ~4GB——这就是本地能跑起来的前提。GGUF 的 K-quants(Q4_K_M、Q5_K_M)是现在的实际标准:它们对不同 tensor 用不同精度(attention 和 feed_forward 的关键层给更高 bit),Q4_K_M 被公认为体积/质量的甜点。再叠加 imatrix(importance matrix):用一份校准语料统计「哪些权重一动输出就大变」,量化时优先保护它们,能进一步压低困惑度损失。
但真正的陷阱是评估指标。社区习惯用 perplexity 衡量量化损失,而 PPL 是 next-token 的平均对数似然——它对「偶尔一个关键 token 选错」极不敏感。可恰恰是这种偶发错误,会让一段代码编译不过、让一条 reasoning 链断掉。所以你会看到「Q4 的 PPL 只涨了 1%」但实际跑 coding / 数学任务掉了一个档。多步推理和精确生成对量化的敏感度,被平均化的 PPL 系统性低估了。
# 别只信 PPL——用你自己任务的样本做 A/B
# 同一个模型拉两个量化档,跑你真实的 20 条 eval
ollama pull qwen3:8b # 默认通常是 Q4_K_M
ollama pull qwen3:8b-q8_0 # 高精度对照组
# 自己带 imatrix 量化(最大化保真):
# 1) 用贴近你领域的语料算 importance matrix
./llama-imatrix -m model-f16.gguf -f calib.txt -o imat.dat
# 2) 量化时挂上 --imatrix,关键权重被优先保护
./llama-quantize --imatrix imat.dat model-f16.gguf model-Q4_K_M.gguf Q4_K_M
核心纪律:量化档位是按任务选的,不是按「能跑就行」选的。抽取 / 分类这类宽容任务,Q4 甚至更低都行;代码 / 数学 / 长链推理,宁可上 Q5_K_M / Q8 或换更小但满血的模型。
「这个模型能不能在我机器上跑」不是查表,是算账。预算由三块组成:
2 × layers × kv_heads × head_dim × seq_len × bytes。这就是被低估的大头——一个 70B 模型开到 32K context,FP16 的 KV cache 能再吃掉 10GB+。GQA(分组注意力)把 kv_heads 砍小,是现代模型能撑长 context 的关键。把这三块加起来对比你的显存(或 Apple Silicon 的统一内存),才知道「装得下吗」。更重要的是第二个判定:装得下 ≠ 该用。本地模型有明确的「够用区」和「别碰区」:
# 30 秒估算:模型装得下吗?
weights_GB = params_B * bytes_per_param # Q4≈0.5, Q8≈1.0, FP16≈2.0
kv_GB = 2 * layers * kv_heads * head_dim * ctx * 2 / 1e9 # FP16 KV
need_GB = weights_GB + kv_GB + 1.5 # +overhead 余量
# 例:Llama-70B Q4, 32K context, GQA(kv_heads=8)
# weights ≈ 40GB, kv ≈ 10GB+ → need ≈ 52GB
# → 48GB 机器「号称跑得动 70B」其实开不到 32K context
「本地跑模型」其实是一个分层的栈,每层解决不同的事:
Modelfile 配置、以及一个 OpenAI 兼容的本地 server(localhost:11434)——这是它最大的价值,让你一行改 base_url 就把云端代码切到本地。Ollama 最隐蔽的坑:默认 num_ctx 很小(历史上常是 2048/4096)。你以为在用模型的 128K 窗口,实际它只看到几千 token,超出部分被静默丢弃——长 context 任务「无故失忆」十有八九是这个。用 Modelfile 显式拉大:
# Modelfile:显式设置 context,别吃默认值的暗亏
FROM qwen3:8b
PARAMETER num_ctx 32768
# 创建并起服务
ollama create qwen3-32k -f Modelfile
# 然后用 OpenAI SDK 直连本地——代码几乎不用改
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
r = client.chat.completions.create(model="qwen3-32k",
messages=[{"role":"user","content":"..."}])
num_ctx,把锅甩给「模型长 context 不行」。
把 8B 本地模型塞进 agentic loop,最常见的崩法是 tool call 格式不稳:少个括号、JSON 截断、字段名拼错——一个解析错误整个 loop 就死。云端大模型把这件事做得很好让你忘了它有多难,小模型没这福气。解法不是「再求模型守规矩」,而是 约束解码(constrained decoding):llama.cpp 的 GBNF 语法 / JSON Schema 在采样层直接把不合语法的 token 概率清零,物理保证输出是合法 JSON。这比 prompt 里写「请输出 JSON」可靠一个量级。
但要记住约束解码的边界:它只保语法,不保语义。模型仍可能填进一个格式合法但内容错误的值。而且 llama.cpp 的实现里,schema 只用于约束输出、不会被注入进 prompt——模型「看不到」schema,你想让它理解字段含义,仍要在 prompt 里描述清楚。
真正的工程答案往往是 hybrid:本地模型干高频、宽容、隐私敏感的「脏活」(路由分类、PII 脱敏、初筛、draft),把难的、低频的、要最强质量的步骤交给云端前沿模型。本地作为 router 和 fallback,而不是全程主力。
# 用 JSON Schema 约束本地输出(llama.cpp server / llama-cpp-python)
# 采样层强制合法 JSON——小模型也不会再吐坏格式
schema = {"type":"object",
"properties":{"intent":{"enum":["search","refund","other"]},
"urgent":{"type":"boolean"}},
"required":["intent","urgent"]}
r = client.chat.completions.create(model="qwen3-8k",
messages=[{"role":"user","content": ticket}],
extra_body={"response_format":{"type":"json_schema",
"json_schema":{"schema": schema}}})
# intent=search → 本地直接处理;intent=refund 且 urgent → 升级云端
{"urgent":true} 是合法 JSON,不保证 true 是对的判断;语义错误照样发生。(2)指望本地 8B 跑完整多 tool 的 agentic loop——步数一多,累积错误率把成功率拖到不可用,这种就该上云。(3)hybrid 里本地分类器自己就不准——路由层用错模型,下游全错,分类器要么够准要么也得是云端便宜档。
把这四点串成一个能省钱又护隐私的周末项目:在你现有的某个云端调用前面,加一层本地分诊。
Modelfile 里显式写够 num_ctx。{intent, urgent};简单/隐私的就地处理,复杂/高价值的才升级云端前沿模型。做完这一套,你对「本地 vs 云」就不再是感觉,而是一条有数字的判据线——这正是超级个体该有的成本直觉。