DwarfStar 4 深度实战:当 Redis 之父用 C 语言「重新发明」本地推理——从磁盘 KV 一等公民到 284B 模型跑进 MacBook 的工程完全指南(2026)
引子:antirez 又一次「重新发明」
2026 年 5 月,GitHub 上一个 C 语言项目在 4 天内斩获 7000+ Star——DwarfStar 4(ds4),作者栏写着 antirez。
如果你在后端领域摸爬滚打过,这个名字不需要介绍:Salvatore Sanfilippo,Redis 的创造者,那个用 3 万行 C 代码改变了全球数据缓存格局的人。他写代码的风格一贯如此——不追热点,不赶时髦,找到一个问题,用最直白的方式干掉它。
这次他瞄准的问题听起来离谱:在一台 MacBook 上跑 DeepSeek V4 Flash,一个 284B 参数的 MoE 大模型。
"离谱"是因为——284B 参数,全精度 500GB+,就算 2-bit 量化也需要 90GB+,而一台 MacBook Pro 最多 128GB 统一内存。传统观点认为这不可能:显存不够、推理太慢、KV 缓存占满内存、长上下文完全没法做。
antirez 的回答是:那就别用传统方式做。
ds4 不是 llama.cpp 的竞品,不是通用 GGUF 运行器,不是任何现有推理框架的 wrapper。它是一个完全自包含的、只服务 DeepSeek V4 Flash 一个模型的专用推理引擎。用他自己的话说:
"这项目故意押一个很窄的赌注:一次只做一个模型,做官方 logits 回归,做长上下文测试,做足够的 Agent 集成来确认它真的能工作。模型可能随着时间换,但约束不变——在高端个人机上做可信的本地推理。"
这篇文章会带你深入 ds4 的每一个核心设计决策——不是泛泛而谈的"它有多快",而是它为什么快、凭什么能在 128GB 内存里装下 284B 模型、磁盘 KV 缓存到底怎么工作、非对称量化为什么有效。我会拆解源码、分析架构、给出实战配置,让你真正理解这个项目背后的工程思维。
一、核心问题:284B 模型凭什么跑进个人电脑?
1.1 MoE:大部分参数是"睡着的"
DeepSeek V4 Flash 是一个 Mixture-of-Experts(MoE)模型,总参数量 284B,但每次推理只激活约 30B 参数。这是 MoE 架构的核心特性——模型有上百个"专家"(Expert),每个 token 经过路由器(Router)选择 4-8 个专家执行,其余专家的权重根本不参与计算。
这意味着什么?推理的计算量和显存带宽需求,远低于 284B 这个数字暗示的水平。
传统 Dense 模型(如 LLaMA 70B)每个 token 都要读全部 70B 权重。MoE 模型只需要读 ~30B。这就是 ds4 能跑的根本前提——如果 DeepSeek V4 Flash 是 Dense 284B,任何个人电脑都跑不动。
1.2 非对称 2-bit 量化:只量化"可以被牺牲"的部分
但即使只激活 30B,模型权重仍然有 284B 需要存储在内存中(推理时需要读取路由结果对应的 expert 权重)。90GB+ 的内存需求对于 128GB MacBook 来说还是紧张——操作系统要占一部分,应用要占一部分,KV 缓存还要占一部分。
ds4 的解法是非对称量化——不是所有层都用同一个 bit-width,而是根据每层对输出质量的敏感度分配不同的精度:
| 模型组件 | 量化方式 | 理由 |
|---|---|---|
| Routed MoE Experts | IQ2_XXS / Q2_K(2-bit) | 参数最多(~70-80%),每个 token 只激活少数,整体质量可通过 imatrix 修正 |
| Up / Gate 投影 | IQ2_XXS | MoE 内部线性变换,对精度敏感度较低 |
| Down 投影 | Q2_K | 略高于 IQ2_XXS |
| Shared Experts | 保留高精度(4-bit+) | 对所有 token 都执行,质量影响大 |
| Attention / Projections / Routing | 不量化 | "骨架"部分,精度不可妥协 |
这个策略的关键洞察是:2-bit 量化不是对所有层都一样"伤"。MoE 的路由专家每个 token 只用几个,个别专家量化误差大,对整体输出的影响被其他保持精度的组件稀释了。而 shared expert 和 attention 层是每 token 都要用的,这些地方一旦量化出错,每个 token 都会受影响。
配合 imatrix(重要性矩阵)——在模型校准集上测量每层权重对输出 logits 的敏感度——ds4 对"更重要"的权重分配更多比特,对"不重要"的分配更少。结果是 Flash q2-imatrix 版本在 96-128GB 机器上跑,coding agent 场景下质量几乎不退化。
1.3 实测数据:它到底有多快?
以下数据来自官方 README,CLI 单次跑数,--ctx 32768 --nothink,greedy 采样,-n 256:
| 机器 | 量化 | Prompt 长度 | Prefill 速度 | 生成速度 |
|---|---|---|---|---|
| M3 Max 128GB | q2 | short | 58.52 t/s | 26.68 t/s |
| M3 Max 128GB | q2 | 11,709 tokens | 250.11 t/s | 21.47 t/s |
| M5 Max 128GB | q2 | short | 87.25 t/s | 34.27 t/s |
| M5 Max 128GB | q2 | 11,707 tokens | 463.44 t/s | 25.90 t/s |
| M3 Ultra 512GB | q2 | 12,018 tokens | 468.03 t/s | 27.39 t/s |
| M3 Ultra 512GB | q4 | 12,018 tokens | 448.82 t/s | 26.62 t/s |
| M3 Ultra 512GB | PRO q2 | 32,768 tokens | 138.82 t/s | 9.56 t/s |
| DGX Spark GB10 128GB | q2 | 7,047 tokens | 343.81 t/s | 13.75 t/s |
几个值得细品的观察:
- 长 prompt 的 prefill 速度远高于短 prompt(58→250 t/s)。这不是魔法,而是图执行引擎在大批量输入时 GPU 利用率更高——短 prompt 的计算粒度太小,GPU 大量时间在等数据。
- M5 Max 几乎是 M3 Max 的两倍。Apple 新架构对 FP16/矩阵运算有实质提升,不是挤牙膏。
- q4 相比 q2 生成速度几乎一样(27.39 vs 26.62 t/s)。因为生成阶段的瓶颈是激活/路由计算,不是权重读取——你把权重读快了,但算力没变。
- DGX Spark 生成速度反而比 Apple Silicon 慢(13.75 vs 25+ t/s)。这说明 Metal 路径对这个特定模型的量化方案做了更深度的优化——ds4 的 Metal kernel 是为 DeepSeek V4 Flash 的张量形态量身写的。
26 t/s 是什么概念?人类阅读英文的速度大约是 250 词/分钟 ≈ 4 词/秒。大模型一个 token 大约 0.75 个英文单词,26 t/s ≈ 19.5 词/秒——模型生成速度是人类阅读速度的近 5 倍。对于编程辅助场景,这个速度完全够用。
二、磁盘 KV 缓存:整个项目最颠覆的设计
2.1 传统做法的困境
KV 缓存(Key-Value Cache)是 Transformer 推理的核心数据结构——每个 token 经过 attention 层时产生的 Key 和 Value 向量,用于后续 token 的 attention 计算。没有 KV 缓存,每生成一个新 token 都要重新计算所有历史 token,复杂度从 O(n) 退化为 O(n²)。
问题是:KV 缓存很大。
DeepSeek V4 Flash 有 1M token 的上下文窗口。假设每个 token 的 KV 缓存占 1KB(GQA 压缩后),1M token 就是 1GB。实际上 DeepSeek V4 的 KV 缓存经过了极致压缩,但长对话仍然会消耗大量内存。
传统做法:KV 缓存全部放在 RAM/VRAM 中。对话越长,缓存越大,内存越紧。当缓存占满了可用内存,要么截断上下文,要么 OOM。
2.2 antirez 的反直觉洞察
antirez 的想法是这样的:
"DeepSeek V4 的 KV 缓存经过极致压缩,每个 token 的缓存非常小。加上现代 MacBook 的 SSD 读速是 5-7 GB/s(M3/M4 系列),PCIe 4.0 NVMe 更是 7+ GB/s。磁盘应该被视为 KV 缓存的一等公民。"
"一等公民"不是"缓存满了才溢出到磁盘"——那是传统的 offloading,效果很差因为访问模式是随机的。"一等公民"的意思是:KV 缓存从诞生之初就设计为磁盘友好的持久化结构。
这带来的变革是根本性的:
- 服务器重启不需要重跑 prompt——KV 缓存在磁盘上,加载就能用
- 长对话不需要常驻内存——冷门部分的 KV 缓存放在磁盘,用到时再加载
- Agent 会话可以"暂停/恢复"——整个推理状态(KV 缓存 + 元数据 + 工具调用记录)序列化到磁盘,下次启动从断点继续
2.3 磁盘 KV 缓存的文件格式
ds4_kvstore.h 定义了完整的磁盘 KV 存储结构:
文件头(48 字节固定):
┌──────────┬──────────┬───────────┬───────────┬──────────┐
│ magic │ version │ model_id │ quant_bits│ reason │
│ "DSV4" │ uint32 │ uint32 │ uint32 │ uint32 │
├──────────┼──────────┼───────────┼───────────┼──────────┤
│ token_count│ hits │ ctx_size │ timestamp │ payload │
│ uint64 │ uint64 │ uint64 │ uint64 │ bytes │
├──────────┼──────────┼───────────┼───────────┼──────────┤
│ text_bytes│reserved │ │ │ │
│ uint64 │ │ │ │ │
└──────────┴──────────┴───────────┴───────────┴──────────┘
后续负载:
- KV 缓存的二进制序列化(所有层的 Key/Value 张量)
- 文本内容(token ID 序列 + 渲染后的文本前缀)
- 扩展字段:thinking 是否可见、session title、tool map
文件名使用 SHA1 哈希(按 prompt 前缀字节计算),便于快速查询"这个 prompt 我之前跑过没有"。
2.4 四种保存时机
KV 缓存不是"用完了才存"——ds4 定义了四种保存触发条件:
| 时机 | 触发条件 | 作用 |
|---|---|---|
| cold | 首次处理一个新的 prompt 前缀 | 冷启动存档,后续相同前缀直接恢复 |
| continued | 会话继续,token 数超过阈值 | 增量更新,避免丢失长对话进度 |
| evict | 内存压力需要淘汰旧会话 | 被淘汰前先落盘,下次需要时从磁盘恢复 |
| shutdown | 服务器关闭 | 优雅退出时保存所有活跃会话 |
2.5 LRU 淘汰策略:半生命周期 6 小时
磁盘空间也不是无限的。ds4 使用 频率衰减 LRU 算法决定保留/淘汰哪些 checkpoint:
score = hits * decay(elapsed_time)
decay(t) = 2^(-t / half_life)
half_life = 6 hours
一个 checkpoint 在 6 小时内被访问 2 次,得分是 2 * 2^0 = 2。6 小时后没被访问,得分衰减到 2 * 0.5 = 1。24 小时后衰减到 2 * 0.0625 = 0.125。
当磁盘 KV 缓存超过预算(默认 4096 MB),score 最低的 checkpoint 被淘汰。这个策略保证了高频使用的 prompt 前缀(如系统提示 + 常用工具定义)始终保留,而一次性对话会被自动清理。
2.6 实战:启用磁盘 KV 缓存
# 启动 ds4-server 并启用磁盘 KV 缓存
./ds4-server -m ds4flash.gguf \
--host 127.0.0.1 --port 8080 \
--kv-disk-dir /tmp/ds4-kv \
--kv-disk-space-mb 8192 # 分配 8GB 磁盘空间给 KV 缓存
# 服务器会自动:
# 1. 接收 prompt 时先在 kv-cache 目录找相同前缀的 checkpoint
# 2. 找到 → 从磁盘恢复 KV 状态,只跑新增的 suffix
# 3. 没找到 → 正常 prefill,然后保存 checkpoint
# 4. 超预算 → LRU 淘汰最久未用的 checkpoint
这对 Agent 场景非常关键——假设你的 coding agent 已经读了 10 个文件并做了 5 轮工具调用,下次对话不需要从头跑这 10 个文件。KV 缓存直接从磁盘恢复,省掉大量 prefill 时间。
三、Session 抽象:把推理变成可回放的时间线
3.1 Engine 与 Session 的分离
ds4.h 是整个项目最重要的文件之一——虽然只有约 400 行,它定义了"引擎的对外契约"。antirez 在文件开头写了一句关键注释:
"The CLI and server should treat ds4_engine as the loaded model and ds4_session as one mutable inference timeline. Keep this header narrow so HTTP/CLI code does not depend on tensor internals."
| ds4_engine(只读,多 session 共享) | ds4_session(可变,每个用户一个) |
|---|---|
| GGUF 文件 mmap + 元数据 | 活跃 KV 缓存(RAM 中) |
| Tokenizer 权重 | 当前 logits 向量 |
| 模型形状信息 | 当前位置 pos |
| Metal/CUDA 图模板(编译一次复用) | 采样 RNG 状态 |
| 量化反量化内核 | 与磁盘 KV 交互的句柄 |
| MTP draft 模型(如果启用) | 会话级配置(温度、top-p 等) |
这个分离的设计收益是:
- 一个 engine 实例可以服务多个 session——模型只加载一次,多个用户/对话共享,节省内存
- 上层代码(CLI/Server/Agent)不需要知道张量细节——通过 ds4.h 的窄 API 操作,换模型只改 ds4.c + GPU 后端
- API 可以独立版本化——通过 model_id 检查 KV 兼容性
3.2 核心 API:session_sync 的妙用
ds4 最精巧的 API 设计是 ds4_session_sync():
// 核心 API 列表
ds4_engine* ds4_engine_open(const char* model_path); // 加载模型
ds4_session* ds4_session_create(ds4_engine* engine); // 创建会话
int ds4_session_sync(ds4_session* s, // 关键函数!
const ds4_tokens* prompt,
...);
ds4_token ds4_session_sample(ds4_session* s); // 采样下一个 token
int ds4_session_eval(ds4_session* s, ds4_token t); // 前向传播一个 token
int ds4_session_save_payload(ds4_session* s, FILE* fp); // 保存到磁盘
int ds4_session_load_payload(ds4_session* s, FILE* fp, ...); // 从磁盘恢复
session_sync 的行为是:将会话同步到给定的完整 prompt token 前缀。它不是"追加 token",而是"保证当前会话状态与给定的完整 prompt 一致"。内部实现会:
- 找到当前 KV 缓存与目标 prompt 的最长公共前缀
- 如果当前 checkpoint 是 prompt 的前缀,只跑后缀部分(增量 prefill)
- 如果不是前缀(用户修改了中间的消息),找到最长公共前缀,只重建后面的部分
这个设计让上层代码极其简单——Server / Agent 不需要关心"上下文增量更新"的逻辑,只需要把完整的 prompt 传给 session_sync,engine 自己 diff 出需要重跑的部分。
// Agent 循环的伪代码——注意上层完全不需要管理 KV 缓存
ds4_engine* engine = ds4_engine_open("ds4flash.gguf");
ds4_session* session = ds4_session_create(engine);
while (true) {
// 1. 构建完整 prompt(系统提示 + 对话历史 + 新消息)
ds4_tokens prompt = build_prompt(chat_history, new_message);
// 2. sync 到目标状态——自动增量,自动磁盘恢复
ds4_session_sync(session, &prompt, NULL);
// 3. 生成回复
while (!is_stop_token(token)) {
token = ds4_session_sample(session);
output(token);
ds4_session_eval(session, token);
}
// 4. 对话继续——sync 会自动处理增量
chat_history.append(new_message, reply);
}
对比 llama.cpp 的做法——上层需要自己管理 llama_kv_cache_seq_rm、llama_kv_cache_seq_add、llama_decode 等底层操作,稍有不慎就会出错。ds4 的 session_sync 把所有复杂度封装在内部,上层只需要"给它完整 prompt"。
四、纯 mmap 模型加载:不解压、不拷贝、不预加载
4.1 为什么不用传统文件 I/O?
大多数推理框架(包括早期 llama.cpp)加载模型的方式是:打开文件 → malloc 一块大内存 → read 整个文件到内存 → 解析 GGUF header → 建立张量索引。
对于 ds4 来说,模型文件 ~90GB。malloc + read 90GB 数据意味着:
- 启动时需要等待磁盘读取 90GB 数据
- 进程 RSS 立即占满 90GB+ 内存
- 如果多个进程使用同一模型,内存不共享
4.2 mmap 的魔法
ds4 使用 mmap() 把 GGUF 文件直接映射进进程虚拟地址空间:
// 简化的模型加载逻辑
void* base = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
// base 现在是虚拟地址,指向 GGUF 文件的每一字节
// 不需要 read(),不需要 malloc,不需要拷贝
// 访问某个张量:
float* tensor_data = (float*)(base + tensor_offset);
// 第一次访问触发 page fault,OS 从磁盘加载对应的 16KB page
// 后续访问直接走 page cache,速度接近内存
关键优势:
- 启动速度极快——mmap 只建立虚拟地址映射,不实际读取数据。90GB 的模型,冷启动几秒钟内就绪
- 实际 RSS 远小于模型大小——只有 Metal/CUDA 实际需要的部分才被加载到物理内存,"工作集"随上下文增长逐渐增加
- OS 管理 page cache——常用的 layer 留在物理内存,不常用的被 OS 自动换出到 disk cache。不需要自己实现 LRU
- 多进程共享——
MAP_PRIVATE的 page 在写入前是共享的(copy-on-write),多个 worker 读同一模型不重复占内存
4.3 与 Metal 的配合
Metal GPU 不直接访问 mmap 的虚拟地址——需要通过 MTLBuffer 把数据上传到 GPU 显存。ds4 的策略是:
- mmap 整个模型到虚拟地址空间
- Metal 图执行时,按需把当前 layer 的权重从 mmap 区域拷贝到
MTLBuffer - 由于 MoE 架构每次只激活部分 expert,实际需要拷贝的权重远小于模型总大小
- Metal 图模板在 engine 创建时编译一次,后续 session 复用——避免重复编译 kernel
这种"按需加载 + 图模板复用"的组合,使得 ds4 的 Metal 后端在推理时只需要在 GPU 显存中保持当前 layer 的权重,而不需要把整个模型都放进 VRAM。
五、内置 HTTP 服务器与 Agent 模式
5.1 OpenAI / Anthropic API 兼容
ds4_server.c(598 KB)实现了一个完整的 HTTP 服务器,兼容 OpenAI 和 Anthropic 的 API 格式:
# 启动服务器
./ds4-server -m ds4flash.gguf --host 127.0.0.1 --port 8080 \
--kv-disk-dir /tmp/ds4-kv
# 用 curl 调用(OpenAI 格式)
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "ds4",
"messages": [
{"role": "system", "content": "你是一个编程助手"},
{"role": "user", "content": "用 Rust 写一个线程安全的 LRU 缓存"}
],
"stream": true
}'
# 或者直接替换 Claude Code 的后端
export ANTHROPIC_BASE_URL="http://127.0.0.1:8080"
export ANTHROPIC_MODEL="deepseek-v4-flash"
claude # Claude Code 的请求会发到本地 ds4
5.2 Thinking 模式控制
DeepSeek V4 Flash 有三种推理模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
Non-thinking(--nothink) | 直接回复,最快 | 简单问答、格式转换 |
| Thinking(默认) | 生成思考过程,推理质量高 | 编程、数学、复杂推理 |
Think Max(--think-max) | 最长思考链,需 ≥32K 上下文 | 困难问题、算法设计 |
在 API 层面通过 reasoning_effort 参数控制:
{
"model": "ds4",
"messages": [...],
"reasoning_effort": "high" // "none" / "high" / "max"
}
5.3 工具调用与 Agent 状态机
ds4_agent.c(387 KB)实现了一个完整的 Coding Agent:
- DeepSeek V4 Flash 原生工具调用语法(DSML tool-call 格式),包括 fast path 和 exact path 两种发射模式
- 工具定义:文件读写、shell 执行、代码编辑
- 多轮推理循环:Agent → 工具调用 → 结果回注 → 继续推理,整个循环在同一个 KV 会话内
- 状态持久化:整个 Agent 会话(KV 缓存 + 工具调用记录 + 元数据)可以序列化到磁盘,下次恢复继续
这意味着你可以关机、第二天回来、继续上次的编码任务——Agent 状态完好无损。这在当前所有本地推理引擎中是独一无二的。
六、分布式推理:两台 MacBook 拼成一台超级 Mac
6.1 为什么需要分布式?
M3 Ultra 512GB 可以跑 q4 量化版,但 128GB 机器只能跑 q2。如果想跑 PRO 版本(更大的模型),单机 512GB 也不够。
ds4 的分布式方案(ds4_distributed.c + .h)允许多台机器联合推理:
┌─────────────┐ TCP ┌─────────────┐
│ Worker A │─────────────│ Worker B │
│ Layer 0-31 │─────────────│ Layer 32-63│
│ M5 Max 128G│─────────────│ M5 Max 128G│
└─────────────┘ └─────────────┘
↑ │
│ 激活张量通过 TCP 传递 │
└──────────────────────────────┘
工作方式:
- 每台机器用
--layers N:M只加载自己负责的 transformer layer 子集 - 激活张量通过 TCP 在 worker 之间流动(A → B → 回到 A),不经过 coordinator 转发
- 长 prefill 可以流水线化:worker N 处理 chunk K 的同时 worker N+1 处理 chunk K+1
6.2 实测性能
两台 M5 Max 128GB 通过 Thunderbolt 5 连接,跑 Q4 Flash:
| Prompt 长度 | 单机参考 | 双机分布式 | 加速比 |
|---|---|---|---|
| 9,421 tokens | 421.70 t/s | 582.22 t/s | 1.38× |
| 28,684 tokens | 405.30 t/s | 674.16 t/s | 1.66× |
| 63,819 tokens | 353.62 t/s | 654.79 t/s | 1.85× |
注意:生成阶段分布式反而更慢约 20%——因为生成是纯自回归的,每个 token 都要跨机器传递激活,网络延迟成为瓶颈。分布式的价值在于:(a) 跑单机放不下的大模型;(b) 加速长 prefill。
6.3 分布式实战配置
# 机器 A(加载 layer 0-31)
./ds4-server -m ds4flash.gguf \
--layers 0:31 \
--distributed-coordinator 192.168.1.100:9000 \
--distributed-workers 192.168.1.101:9001
# 机器 B(加载 layer 32-63 + output)
./ds4-server -m ds4flash.gguf \
--layers 32:output \
--distributed-coordinator 192.168.1.100:9000 \
--distributed-workers 192.168.1.101:9001
七、方向引导(Directional Steering):给模型加方向盘
7.1 不用 Prompt Engineering,直接操控激活
ds4 的 dir-steering/ 目录实现了一个非常前沿的功能:通过注入方向向量来控制模型的生成倾向。
这基于 2024 年的论文《Refusal in Language Models Is Mediated by a Single Direction》的核心发现——大模型的行为倾向(如"是否拒绝回答"、"是否啰嗦")可以用激活空间中的一个方向向量来表示。
ds4 的实现步骤:
- 收集两组 prompt:一组"希望的行为"(如简洁回答),一组"不希望的行为"(如啰嗦回答)
- 在相同的中间层收集两组的激活向量
- 计算差值向量(direction vector),存为
.f32文件(约 700KB / 176K float32) - 推理时,在指定层的输出上加上
steering_attn × direction_vector
# 使用内置的 verbosity 方向向量
./ds4 -m ds4flash.gguf \
--directional-steering-file dir-steering/out/verbosity.f32 \
--directional-steering-attn 0.5 \
--directional-steering-ffn 0.2
# attn=0.5 表示对 attention 层的输出施加 50% 强度的方向引导
# ffn=0.2 表示对 FFN 层的输出施加 20% 强度的方向引导
这个功能的价值在于:不需要改 prompt、不需要微调、不需要重新量化——只需要一个 700KB 的方向向量文件,就能在推理时实时调整模型的行为倾向。
对于 DeepSeek V4 Flash 这种"对齐过度"的模型(过度礼貌、拒绝某些编程问题的回答),方向引导可以有效地反转这些倾向,而不损失模型的其他能力。
7.2 自定义方向向量
# dir-steering/tools/build_direction.py 的工作原理(简化)
import torch
def build_direction_vector(model, positive_prompts, negative_prompts, layer=16):
"""构建方向向量"""
positive_activations = []
negative_activations = []
for prompt in positive_prompts:
acts = model.forward_with_hooks(prompt, layers=[layer])
positive_activations.append(acts[layer].mean(dim=0))
for prompt in negative_prompts:
acts = model.forward_with_hooks(prompt, layers=[layer])
negative_activations.append(acts[layer].mean(dim=0))
# 方向向量 = 正向均值 - 负向均值
pos_mean = torch.stack(positive_activations).mean(dim=0)
neg_mean = torch.stack(negative_activations).mean(dim=0)
direction = pos_mean - neg_mean
# 归一化
direction = direction / direction.norm()
# 保存为 .f32 文件
direction.numpy().tofile("my_direction.f32")
return direction
八、从零搭建:完整实战指南
8.1 环境准备
# 硬件要求
# - 最低:MacBook Pro M3 Max 96GB(q2-imatrix,勉强可用)
# - 推荐:MacBook Pro M3/M5 Max 128GB(q2-imatrix,流畅运行)
# - 高配:Mac Studio M3 Ultra 256GB+(q4-imatrix,高质量推理)
# - 土豪:Mac Studio M3 Ultra 512GB(PRO q2,最强大模型)
# 软件要求
# macOS Sonoma 14+(Metal 3+)
# Xcode Command Line Tools
xcode-select --install
# 确认内存
sysctl -n hw.memsize # 输出字节数,128GB = 137438953472
8.2 编译安装
# 克隆仓库
git clone https://github.com/antirez/ds4.git
cd ds4
# macOS Metal 编译(默认 target)
make -j$(sysctl -n hw.ncpu)
# 编译完成后会生成:
# - ./ds4 (CLI 推理工具)
# - ./ds4-server (HTTP API 服务器)
# - ./ds4-bench (基准测试工具)
8.3 下载模型
# 下载 q2-imatrix 版本(推荐,约 90GB)
./download_model.sh q2-imatrix
# 下载 q2-q4-imatrix 版本(最后 6 层 q4,约 100GB,质量更好)
./download_model.sh q2-q4-imatrix
# 下载 q4-imatrix 版本(需要 256GB+ 内存)
./download_model.sh q4-imatrix
# 下载 PRO q2-imatrix 版本(需要 512GB 内存)
./download_model.sh pro-q2-imatrix
8.4 CLI 交互式使用
# 基本使用
./ds4 -m ds4flash.gguf --ctx 32768
# 关闭思考模式(更快,适合简单问答)
./ds4 -m ds4flash.gguf --ctx 32768 --nothink
# 单次问答
./ds4 -m ds4flash.gguf -p "用 Python 写一个快速排序" --temp 0
# 长上下文对话
./ds4 -m ds4flash.gguf --ctx 65536 # 64K 上下文
# 调整采样参数
./ds4 -m ds4flash.gguf --ctx 32768 \
--temperature 0.7 \
--top-p 0.9 \
--min-p 0.05
8.5 作为 API 服务器运行
# 基本服务器
./ds4-server -m ds4flash.gguf --host 127.0.0.1 --port 8080
# 带磁盘 KV 缓存的服务器(推荐)
./ds4-server -m ds4flash.gguf \
--host 127.0.0.1 --port 8080 \
--kv-disk-dir ~/.ds4/kv-cache \
--kv-disk-space-mb 8192
# 限制功率(降低发热,适合笔记本)
./ds4-server -m ds4flash.gguf \
--host 127.0.0.1 --port 8080 \
--power-percent 75
8.6 接入 Claude Code / Cursor 等 AI 编程工具
# Claude Code
export ANTHROPIC_BASE_URL="http://127.0.0.1:8080"
export ANTHROPIC_MODEL="deepseek-v4-flash"
claude
# OpenAI 兼容客户端
export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
export OPENAI_API_KEY="not-needed" # ds4 不验证 API key
8.7 性能调优清单
| 调优项 | 方法 | 效果 |
|---|---|---|
| 降低上下文窗口 | --ctx 16384 而非 --ctx 32768 | 减少 KV 缓存内存占用 |
| 关闭思考模式 | --nothink | 生成速度提升 2-3×,适合简单任务 |
| 限制功率 | --power-percent 50 | 降低发热和风扇噪音,牺牲 ~20% 速度 |
| 启用磁盘 KV | --kv-disk-dir | 长对话内存占用大幅降低 |
| 选择合适量化 | q2-imatrix vs q4-imatrix | q2 速度一样但内存占用低一半 |
九、回归测试:为什么你该信任这个引擎
9.1 官方 Logits 回归
ds4 的测试策略非常严谨——不是"跑几个示例看看输出像不像",而是逐 token 对比本地推理与 DeepSeek 官方 API 的 logits 向量:
# 运行回归测试
make test
./ds4_test --logprob-vectors # 使用官方 continuation vectors
./ds4_test --server # 测试 HTTP API 的正确性
测试覆盖:
- 短上下文:数百 token 的 prompt
- 长上下文:最高验证到 250K tokens 的回溯一致性
- 工具调用:DSML tool-call 格式的正确性
- 思考模式:thinking token 的生成和隐藏
9.2 imatrix 校准
量化质量的保证来自 imatrix(重要性矩阵)的校准流程:
- 构建校准数据集(
gguf-tools/imatrix/dataset/build_ds4_imatrix_dataset.py,~3K 行 Python) - 生成校准 prompt(
prompts.jsonl,~21MB 语料) - 在全精度模型上收集每层权重对输出 logits 的敏感度
- 敏感度高的权重分配更多量化比特
这个过程不是"拍脑袋决定哪些层用 2-bit"——它是基于数据驱动的敏感性分析,确保 2-bit 量化只在"安全"的地方使用。
十、与 llama.cpp 的对比:不是替代,是不同路线
| 维度 | llama.cpp + llama-server | DwarfStar 4 |
|---|---|---|
| 定位 | 通用推理引擎,支持 100+ 模型 | 单模型深度优化 |
| KV 磁盘缓存 | 基础支持 | 一等公民,持久化 + 精确恢复 |
| 模型支持 | 广泛 | 仅 DeepSeek V4 Flash/PRO |
| 量化策略 | 统一量化方案 | 非对称 expert 精确量化 |
| Agent 集成 | 通用 API | 原生工具调用 + Agent 状态机 |
| 项目风格 | 社区化,多人维护 | 个人主导 + GPT 5.5 辅助 |
| Metal 优化 | 通用 kernel | 为 DSV4 张量形态定制 kernel |
| 方向引导 | 无 | 内置 directional steering |
| 分布式推理 | llama-mpi / rpc-server | 内置,流水线化 prefill |
ds4 不打算替代 llama.cpp。如果你需要跑多种模型,llama.cpp 仍然是唯一选择。但如果你只需要跑 DeepSeek V4 Flash——而且你想跑得更快、用更少内存、有更好的 Agent 体验——ds4 是更优解。
antirez 自己在 README 中也明确写了:ds4 不链接 GGML,但 acknowledge llama.cpp 的开创性工作。这不是竞争,是不同的工程选择。
十一、局限性与风险
客观地说,ds4 目前存在以下局限:
- Alpha 质量——项目仅存在几周,稳定性有待验证。antirez 用 GPT 5.5 辅助编码虽然快,但 AI 生成的代码可能存在边界条件的 bug
- 硬件门槛高——最低 96GB RAM(q2),推荐 128GB+。对于大多数 MacBook 用户来说,这意味着需要 Max 或 Ultra 芯片
- 仅一个模型——不支持其他模型,包括未来的 DSV4 更新版也需要适配
- macOS CPU 路径有内核 Bug——Apple 的虚拟内存实现问题会导致内核崩溃,CPU 路径在 macOS 上不可用
- GGUF 文件格式特定——不是通用 GGUF loader,必须使用项目提供的量化文件
- 生成速度的天花板——26 t/s 对编程够用,但对需要实时交互的场景(如对话式 AI)仍然偏慢
十二、工程启示:antirez 的"窄而深"哲学
ds4 给我最大的启示不是技术细节,而是工程哲学。
12.1 约束即自由
antirez 刻意选择了"只做一个模型"的约束。这个约束看起来是限制,实际上是解放:
- 不需要抽象层——直接写 DSV4 的 Metal kernel,不需要"适配多种模型形状"的通用框架
- 不需要兼容矩阵——只测一个模型的正确性,可以做到逐 token 回归
- 不需要参数爆炸——CLI 参数只有十几个,而不是 llama.cpp 的上百个
- 可以做 llama.cpp 不敢做的事——比如磁盘 KV 一等公民,因为只需要保证 DSV4 的 KV 格式兼容
12.2 用"慢"的存储做"快"的事
磁盘比内存慢,这是常识。但 antirez 的洞察是:"慢"是相对的。7GB/s 的 SSD 读速对于 DeepSeek V4 极度压缩的 KV 缓存来说,已经够快了。关键不是绝对速度,而是访问模式是否匹配存储介质特性——KV 缓存是大块顺序读写(一次加载整个 layer 的 KV),不是随机访问,SSD 在这个模式下性能接近内存。
这让人想起 Redis 本身的设计——"内存很快,但磁盘更便宜"。antirez 用同样的思维重新思考了推理引擎的存储层次。
12.3 API 设计的"窄接口"原则
ds4.h 只有约 400 行,暴露的 API 只有十几个函数。但就是这十几个函数,支撑了 CLI、HTTP Server、Agent、分布式推理四个上层应用。
关键在于 session_sync——一个函数把"增量更新、KV 恢复、prompt diff"三种复杂逻辑全部封装。上层只需要"给完整 prompt",不需要知道内部怎么 diff、怎么恢复、怎么重建。
这是好的 API 设计:不是暴露更多能力,而是隐藏更多复杂度。
总结:本地推理的范式转移
DwarfStar 4 不是一个"更快的 llama.cpp"。它代表了一种新的本地推理范式:
- 专用化——不做通用引擎,做单模型的极致优化
- 存储层次重新设计——磁盘 KV 缓存是一等公民,不只是溢出区
- 会话可持久化——推理状态可以"存盘/读档",Agent 会话可以跨重启恢复
- 行为可控——方向引导提供了一种不修改模型就能控制输出的机制
- 窄接口——400 行 API 支撑完整应用栈
对于 Mac 开发者来说,ds4 意味着你可以在本地运行一个 284B 参数的思考模型,速度 26 t/s,1M 上下文窗口,配合磁盘 KV 缓存,体验接近云端 API——但代码不离开你的电脑。
antirez 又一次证明了他最擅长的事:找到一个被忽视的约束,用最少的代码做出最有效的解法。Redis 如此,ds4 亦如此。
GitHub: https://github.com/antirez/ds4
License: MIT
Stars: 13,000+ (截至 2026 年 6 月)