DwarfStar
DwarfStar 是一个小型原生推理引擎,首先针对 DeepSeek V4 Flash 进行了优化,并在超高内存机器上支持 DeepSeek V4 PRO。它有意保持狭窄定位:不是通用的 GGUF 运行器,也不是对其他运行时的封装——它是完全自包含的。除了以正确且快速的方式运行模型外,该项目目标还在于提供 DeepSeek 专属的加载、提示词渲染、工具调用、KV 状态处理(内存和磁盘)、服务器 API 以及集成的编码智能体,所有这些都准备好与编码智能体或提供的 CLI 界面配合使用。此外,还提供了用于生成 GGUF 和 imatrix 以及进行质量和速度测试的工具。
我们支持以下后端:
- Metal 是我们的主要目标平台。从配备 96GB 内存(或更少,使用 SSD 流式传输)的 MacBook 开始。
- NVIDIA CUDA / DGX Spark,针对 DGX Spark 进行了特别优化的 CUDA。
- Strix Halo(ROCm),适用于 Framework Desktop 等系统以及基于相同 GPU 和统一 RAM 设计的其他系统。
如果没有 llama.cpp 和 GGML,这个项目就不会存在,请务必阅读致谢部分,非常感谢 Georgi Gerganov 和所有其他贡献者。
请注意,DeepSeek v4 并非我们的唯一目标。目前,Flash 和 PRO 因其能力、规模、KV 缓存效率而成为完美选择。如果明天有更好的开放权重模型针对 128GB 规模发布,我们可能会切换,对于其他重要的规模类别(如 512GB 内存)也是如此。该项目严格根据特定时刻存在的开放权重模型而灵活调整。如果支持了新模型,旧模型可能会被完全移除并不再支持,除非存在某种能力上的重叠。
动机
- 功能非常强大的开放权重模型终于出现了。DeepSeek v4 Flash 感觉接近前沿水平。PRO 版本甚至更好。两者都能很好地抵抗 2 位量化。
- 像 MacBook、DGX Spark 这样功能非常强大的计算机现在也已存在。
- DeepSeek v4 的 KV 缓存设计使得运行非常大的上下文窗口变得实用。其他厂商也在采用这种方法。
- 这些数千亿参数的模型严格优于较小的模型(即使是稠密模型),无论基准测试结果如何。
话虽如此,关于这个项目有几点重要说明:
- 本地推理领域有许多优秀的项目,但新模型不断发布,注意力很快就会被下一个需要实现的模型所吸引。本项目刻意采取了一种狭窄的押注策略:一次只针对一个模型,进行官方向量验证(使用官方实现获取 logits)、长上下文测试,以及足够的智能体集成来确认它是否真的有效。具体模型可能会随着领域发展而变化,但约束条件始终不变:在高性能个人电脑或 Mac Studio 上实现可信的本地推理,起始内存为 96/128GB。
- 本软件是在 GPT 5.5 的强力辅助下开发的,由人类主导创意、测试和调试。我们公开说明这一点,因为它塑造了项目的构建方式。如果你对 AI 开发的代码感到不满,那么这个软件不适合你。下面的致谢同样重要:没有主要由手工编写的 llama.cpp 和 GGML,这个项目就不会存在。
- 本实现的核心理念是:像 DeepSeek v4 那样的压缩 KV 缓存,以及现代 MacBook 的快速 SSD 磁盘,应该改变我们认为 KV 缓存属于 RAM 的观念。KV 缓存实际上是一等磁盘公民。快速 SSD 磁盘还从“模型需要适配 RAM”的角度改变了推理游戏:虽然拥有比模型大小更多的 RAM 仍然是首选,但 SSD 流式传输使得可用 RAM 量从一个硬性门槛(我能否运行这个模型?)变成了一个连续的速度等级谱系。
- 我们的愿景是,本地推理应该是一套开箱即用、协同良好的三要素组合:A) 带 HTTP API 的推理引擎 + B) 经过特别定制、能在给定引擎和假设条件下良好运行的 GGUF 格式文件 + C) 结合编码智能体的测试与验证。D) 针对特定模型和执行环境构建的专用智能体。DwarfStar 仅能运行所提供的 GGUF 文件。它会在不同的上下文窗口大小下,根据官方获取的 logits 进行测试。这个项目的存在,是因为我们希望让一个本地模型感觉上是端到端完整的,而不仅仅是可运行。不过,这是测试版质量的代码,所以我们可能还没达到那个目标,尤其是最近我们引入了大型新功能:分布式推理、SSD 流式传输以及其他一些小的改进。
- 优化的图路径针对 macOS 上的 Metal 和 Linux 上的 CUDA。CPU 路径仅用于正确性检查以及模型/分词器诊断。对于仅限 CPU 的 Linux 构建,请使用 `make cpu`;它会构建普通的 `./ds4` 和 `./ds4-server` 二进制文件,不包含 CUDA 或 Metal。在 macOS 上,警告:当前 macOS 版本在虚拟内存实现中存在一个 bug,如果你尝试运行 CPU 代码,会导致内核崩溃。还记得吗?软件就是如此糟糕。无法修复 CPU 推理以避免崩溃,因为每次你都必须重启电脑,这并不有趣。如果你有胆量,请帮助我们。
致谢 llama.cpp 和 GGML
ds4.c 并未链接到 GGML,但它的存在要归功于 llama.cpp 项目开辟的道路,以及该项目中开发的内核、量化格式、GGUF 生态系统和来之不易的工程知识。我们非常感谢并感激 llama.cpp 及其贡献者。他们的实现、内核、测试和设计选择,在构建这个 DeepSeek V4 专用推理路径时,是至关重要的参考。部分源代码级别的片段根据 MIT 许可协议在此保留或改编:GGUF 量化布局和表格、CPU 量化/点积逻辑以及某些内核。出于这个原因,也因为我们由衷的感激,我们在 LICENSE 文件中保留了 GGML 作者的版权声明。
状态
代码和 GGUF 文件应被视为测试版质量,因为模型推理和服务是一项复杂的事务,而这一切仅存在了几天。需要数月时间才能达到更稳定的形态。不过,我们努力让项目保持可用状态,并且正在取得进展。如果你遇到问题,请务必使用 `--trace` 记录会话,并在提交问题时附上完整的追踪日志。
ds4-agent 是 Alpha 质量等级,该项目是后来添加的。
更多文档
如果你在寻找非常具体的内容,我们还有其他子 README 文件。否则,对于常规使用,请继续阅读后续章节。
- CONTRIBUTING.md:面向贡献者的正确性与速度回归测试指南。在提交拉取请求前请先阅读此文件。
- gguf-tools/README.md:离线 GGUF 生成、imatrix 收集、量化工具以及质量检查。
- gguf-tools/imatrix/README.md:如何收集和使用路由 MoE 的 imatrix。
- gguf-tools/imatrix/dataset/README.md:如何生成校准提示词语料库。
- gguf-tools/quality-testing/README.md:如何将本地 GGUF 与官方 DeepSeek V4 Flash/PRO 的续写结果进行评分对比。
- dir-steering/README.md:方向性引导数据、向量生成及使用方法。
- speed-bench/README.md:基准测试命令、图表及 CSV 生成。
- tests/test-vectors/README.md:用于回归检查的官方续写向量。
模型权重
此实现仅适用于为此项目发布的 DeepSeek V4 Flash 和 PRO 的 GGUF 文件。它不是一个通用的 GGUF 加载器,任意的 DeepSeek/GGUF 文件将不具备引擎所期望的张量布局、量化混合、元数据或可选的 MTP 状态。这里提供的 2 比特量化并非玩笑:它们表现良好,能在编码智能体下工作,并以可靠的方式调用工具。2 比特量化使用了非常不对称的量化方式:仅对路由 MoE 专家进行量化,up/gate 使用 IQ2_XXS,down 使用 Q2_K。它们占据了模型空间的大部分:其他组件(共享专家、投影、路由)保持不变以保证质量。
下载一个主模型。优先选择 imatrix 版本。
./download_model.sh q2-imatrix # 96/128 GB RAM machines, imatrix-tuned q2 ./download_model.sh q2-q4-imatrix # 96/128 GB RAM machines, q2 with last 6 layers q4 ./download_model.sh q4-imatrix # >= 256 GB RAM machines, imatrix-tuned q4 ./download_model.sh pro-q2-imatrix # 512 GB RAM machines, PRO q2 imatrix quant
对于完整的 PRO Q4 分布式运行,请在每台机器上下载一半。
./download_model.sh pro-q4-layers00-30 # first half of PRO Q4 split ./download_model.sh pro-q4-layers31-output # second half of PRO Q4 split
该脚本从 https://huggingface.co/antirez/deepseek-v4-gguf 下载文件,将文件存储在 ./gguf/ 目录下,使用 curl -C - 恢复部分下载,并更新 ./ds4flash.gguf 以指向所选的主模型。pro-q4-layers00-30、pro-q4-layers31-output 和 pro-q4-split 目标会下载分布式的 PRO Q4 分片,但不会更新 ./ds4flash.gguf。对于公开下载,身份验证是可选的,但如果提供了 --token TOKEN、HF_TOKEN 或本地 Hugging Face token 缓存,则会使用这些信息。
如果你想重新生成 GGUF 文件或收集新的 imatrix,请参阅 gguf-tools/README.md。这些工具适用于离线模型构建工作,在完整的 DeepSeek V4 Flash 权重上可能需要很长时间。本地工具支持 Flash GGUF 的生成。PRO GGUF 的生成目前仍然依赖于基于 llama.cpp 的外部工作流程;原生工具支持可以在后续添加。
./download_model.sh mtp 会获取 Flash 的可选推测解码支持 GGUF。它可以与 q2-imatrix、q2-q4-imatrix 和 q4-imatrix 一起使用,但必须通过 --mtp 显式启用。当前的 MTP/推测解码路径仍处于实验阶段:它受正确性门控,目前最多只能提供轻微的速度提升,并非有意义的生成速度增益。
然后构建:
make # macOS Metal make cuda-spark # Linux CUDA, DGX Spark / GB10 make cuda-generic # Linux CUDA, other local CUDA GPUs make cpu # CPU-only diagnostics build
./ds4flash.gguf 是两个二进制文件使用的默认模型路径。使用 -m 参数可从 ./gguf/ 目录中选择其他受支持的 GGUF 文件。运行 ./ds4 --help 和 ./ds4-server --help 可查看完整的标志列表。
速度
以下是单次运行的 Metal CLI 数据,参数设置为 --ctx 32768、--nothink、贪婪解码和 -n 256。短提示词是一个普通的意大利语小故事提示词。长提示词则用于测试分块预填充加长上下文解码。Q4 需要更大内存的机器类别,因此 M3 Max 的 Q4 数据为 N/A。
| 机器 | 量化 | 提示词 | 预填充 | 生成 |
|---|---|---|---|---|
| MacBook Pro M3 Max, 128 GB | q2 | 短 | 58.52 t/s | 26.68 t/s |
| MacBook Pro M3 Max, 128 GB | q2 | 11709 tokens | 250.11 t/s | 21.47 t/s |
| MacBook Pro M3 Max, 128 GB | q4 | 短 | N/A | N/A |
| MacBook Pro M3 Max, 128 GB | q4 | 长 | N/A | N/A |
| MacBook Pro M5 Max, 128 GB | q2 | 短 | 87.25 t/s | 34.27 t/s |
| MacBook Pro M5 Max, 128 GB | q2 | 11707 tokens | 463.44 t/s | 25.90 t/s |
| Mac Studio M3 Ultra, 512 GB | q2 | 短 | 84.43 t/s | 36.86 t/s |
| Mac Studio M3 Ultra, 512 GB | q2 | 11709 tokens | 468.03 t/s | 27.39 t/s |
| Mac Studio M3 Ultra,512 GB | q4 | 短 | 78.95 t/s | 35.50 t/s |
| Mac Studio M3 Ultra,512 GB | q4 | 12018 tokens | 448.82 t/s | 26.62 t/s |
| Mac Studio M3 Ultra,512 GB | PRO q2 | 32768 tokens | 138.82 t/s | 9.56 t/s |
| DGX Spark GB10,128 GB | q2 | 7047 tokens | 343.81 t/s | 13.75 t/s |
运行大于 RAM 的模型
常规 Metal 路径会尝试让模型驻留在 GPU 可寻址内存中。这是最快的路径,当模型能容纳时应保持为默认选项。当模型无法容纳时,DwarfStar 还有一种仅限 Metal 的 SSD 流式传输容量模式。在此模式下,非路由模型权重保持驻留,而路由 MoE 专家则保存在内存缓存中,并在缓存未命中时从 GGUF 文件加载。
流式传输不如将完整模型放入 RAM 快。它仍然需要内存来存放非路由权重、KV 缓存、图计算暂存区、激活值以及路由专家缓存。它的用处在于路由专家占据了模型大小的主导地位,而现代 Mac 的 SSD 速度足够快,使得缓存未命中可以容忍。长预填充仍然可以很快;生成过程对缓存未命中更敏感,因为每个新 token 都会再次通过专家进行路由。
从自动缓存预算开始:
./ds4 -m ./ds4flash.gguf --ssd-streaming
如果启动时报告专家缓存过大,或者你想为上下文保留更多内存,请显式设置路由专家缓存:
./ds4 -m ./ds4flash.gguf --ssd-streaming --ssd-streaming-cache-experts 32GB
32GB 这个值是完整路由专家的内存预算,而不是通用的字节缓存。DwarfStar 会将其转换为当前 GGUF 能容纳的完整专家数量。非路由权重、KV 缓存、图计算暂存区和激活值需要额外的内存。只有自动缓存预算会为你做减法:它取 Metal 推荐工作集的 80%,减去非路由权重,然后将剩余部分用于路由专家。正常使用时保持热专家预加载启用;仅在测量时使用 `--ssd-streaming-cold` 和 `--ssd-streaming-preload-experts N`。
实用的 SSD 流式传输示例
在 64GB MacBook 上,从 2-bit Flash GGUF 和一个适中的专家缓存开始:
./download_model.sh q2-imatrix ./ds4 \ -m ./ds4flash.gguf \ --ssd-streaming \ --ssd-streaming-cache-experts 32GB \ --ctx 32768 \ --nothink
在 128GB MacBook 上,PRO q2 流式传输是实验性的,但可用于检查以及在你接受慢速生成时的偶尔工作。从 `--nothink` 开始:
./download_model.sh pro-q2-imatrix ./ds4 \ -m gguf/DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix.gguf \ --ssd-streaming \ --ctx 32768 \ --nothink
在配备 128GB 内存的 M5 Max 芯片上,一次简短的 PRO q2 流式解码基准测试发现,自动预算方案表现最佳:它选择了约 59GB 的路由专家缓存。在该机器上,手动设置 64GB 到 75GB 的缓存效果接近。更大的显式 NGB 请求在推理前会被限制,以确保专家缓冲区保持可锁定状态,而不会落入 macOS 的页面交换机制。如果系统面临额外内存压力且 mlock 仍然失败,ds4 会拒绝安装可分页的专家缓存条目,并在继续使用已测得的可锁定缓存大小之前,释放一部分已锁定的缓存余量。建议优先使用自动预算方案;如果在这类机器上手动设置缓存,可从 48GB 到 64GB 左右开始,然后仅在启动日志报告可锁定缓存时再增加。待机器稳定后,以保守的生成限制重新启用思考功能:
./ds4 \ -m gguf/DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix.gguf \ --ssd-streaming \ --ctx 32768 \ --think \ --tokens 1500
重要的启动日志行是缓存报告。先保守设置,如果机器有富余空间,再增加缓存。
分布式推理
分布式推理让 DwarfStar 能够运行单台机器无法承载的大模型,其方法是将 Transformer 层拆分到多台机器上。主要示例是在两台 128GB MacBook 上运行完整的 4-bit Flash 量化模型:每个进程只映射自己的层切片,激活值通过 TCP 传输,协调器则保持正常的 CLI/API 行为。
分布式推理还可以通过同时使用多个 GPU,像流水线一样在不同层上处理不同的微批次,从而加速预填充阶段。只有预填充能以这种方式加速。生成过程纯粹是自回归的:每个 token 必须完成整个路由路径,下一个 token 才能开始。模型的计算量与单进程相同,再加上协调延迟,因此分布式生成的速度会更慢。
为了建立一个初步的心智模型,以下是几个高层概念:
- 你将 GGUF 文件放在每台机器上,但每台机器只加载其中的一个子集。`--layers` 参数控制映射哪些张量,因此一个使用 `--layers 20:output` 的工作节点不会加载前面的层。
- 层的范围是包含端点的:`10:20` 表示第 10 层、第 11 层……直到第 20 层。`N:output` 表示从第 N 层到最后一层,再加上输出头。
- 你将其中一台机器指定为协调节点,其余机器作为工作节点。工作节点会连接到协调节点,告知其自身存在以及能够处理的网络层。
- 每个工作节点都保存着自己负责的那部分 KV 缓存。
- 通信是工作节点之间的直接通信,无需通过协调节点进行中继。因此,如果你的协调节点是 A,并且你发起一个请求,激活值将按照 A -> B -> C -> 回到 A 的顺序流动。
工作原理及配置方法
预填充路径是流水线式的(这也是它能比单机更快的原因)。对于大型提示词,协调节点可以在工作节点处理第 N 个数据块的同时,运行其负责的第 N+1 个数据块。下面的分布式数据行是通过两台由 Thunderbolt 5 连接的 M5 Max 128 GB MacBook 测量得到的,使用了 Q4 Flash GGUF 格式和默认的 4096 token 分布式预填充数据块。单进程列是在单台机器上使用 Q2 GGUF 格式运行的参考数据,因此实际上速度更快,因为路由 MoE 的规模更小。
| 提示词 | 单进程参考 | 两台 MacBook | 加速比 |
|---|---|---|---|
| 9421 tokens | 421.70 t/s | 582.22 t/s | 1.38x |
| 28684 tokens | 405.30 t/s | 674.16 t/s | 1.66x |
| 63819 tokens | 353.62 t/s | 654.79 t/s | 1.85x |
生成阶段则不同。它是严格自回归的:token N+1 必须等到 token N 产生 logits 并且采样过程选出下一个 token 之后才能开始。这意味着分布式生成无法利用长预填充流水线。每个生成的 token 至少需要一次跨机器的激活值传输,因此生成速度比单机本地进程要慢。在同样的两台 Mac Thunderbolt 设置下,一个 12k 上下文的控制运行,使用 91 GB Flash 量化格式,从单进程的 30.59 t/s 降到了分布式的 24.67 t/s,性能损失了 19.4%。因此,分布式推理主要用于适配更大的模型和加速长序列的预填充,而不是为了加快解码速度。
在两台 Mac Studio 上运行完整的 DeepSeek V4 PRO Q4
完整尺寸的 PRO Q4 GGUF 模型可以通过将协调节点分配层 0:30、工作节点分配层 31:output 的方式,在两台 512 GB Mac Studio M3 Ultra 机器上运行。使用分片的 GGUF 文件,这样每一方只映射其所需的张量:
# Coordinator machine. ./download_model.sh pro-q4-layers00-30 # Worker machine. ./download_model.sh pro-q4-layers31-output
这两个文件是:
gguf/DeepSeek-V4-Pro-Q4K-Layers00-30.gguf
gguf/DeepSeek-V4-Pro-Q4K-Layers-31-output.gguf
这是一个容量使用场景:每个进程只映射模型自身的那一半,而工作进程拥有输出头并返回 logits。
当前的 PRO Q4 Metal 路径为大型路由专家使用了队列驻留的精确专家表。这避免了早期分布式 PRO Q4 尝试中出现的宽范围多 GiB 路由张量绑定,那些绑定要么导致运行极慢,要么触及 Metal 内存会计限制。在通过直连的 192.168.0.182 / 192.168.0.183 链路进行的简短贪婪冒烟测试中,模型生成了连贯的文本,启动后测得生成速度为 11.47 t/s。每个 token 的遥测数据是均衡的:本地层大约 39-43 毫秒,远程层大约 44-49 毫秒,总 token 时间约为 84-92 毫秒。启动时预计会较慢,因为两侧需要映射并驻留各自那一半的模型。长上下文 PRO Q4 的预填充和解码性能仍需单独进行基准测试。
上述测量使用的是 Thunderbolt 5 线缆。该实现基于纯 TCP,也适用于较慢的链路(包括 WiFi),但强烈建议使用快速以太网或 Thunderbolt 网络。慢速链路主要影响生成延迟和短预填充;当层拆分均衡时,大型预填充仍能受益。在正常性能路径中,最后一个工作进程拥有输出头并直接返回 logits。
最小双主机配置:
# Machine A: coordinator, owns tokenization, sampling, the prompt, and layers 0..30. ./ds4 \ -m gguf/DeepSeek-V4-Pro-Q4K-Layers00-30.gguf \ --role coordinator \ --layers 0:30 \ --listen 169.254.43.68 1234 # Machine B: worker, connects to A and owns layers 31..output. ./ds4 \ -m gguf/DeepSeek-V4-Pro-Q4K-Layers-31-output.gguf \ --role worker \ --layers 31:output \ --coordinator 169.254.43.68 1234
通常,最后一个工作进程也应拥有输出头,例如 `--layers 20:output`。这避免了在预填充后返回完整的最终隐藏状态批次,并让最后一个工作进程直接生成 logits。在非常慢或按流量计费的链路上,也支持 `--layers 20:42`:协调器将加载输出头并在本地计算 logits,用额外的协调器工作换取更小的逐 token 回复。
网络链路对比
下表展示了相同的两台 M5 Max 主机、相同的 91 GB Flash 量化、协调器 `--layers 0:19`、工作进程 `--layers 20:output`、来自 speed-bench/promessi_sposi.txt 的 8192 token 提示词,以及 128 个生成的 token。WiFi 和互联网数据会随本地条件变化,但趋势是关键部分:高延迟直接损害生成速度,而较低的带宽也会拖慢长预填充的速度。
| 链路 | 地址 | 平均 Ping | 预填充 | 生成 |
|---|---|---|---|---|
| 雷电 5 | 169.254.43.68 -> 169.254.12.245 | 0.45 毫秒 | 582.99 token/秒 | 25.09 token/秒 |
| 无线网络 | 192.168.1.57 -> 192.168.1.95 | 77.20 毫秒 | 250.70 token/秒 | 10.70 token/秒 |
| 互联网 / VPN | 10.77.0.4 -> 10.77.0.3 | 152.10 毫秒 | 114.88 token/秒 | 3.63 token/秒 |
互联网/VPN 场景并非旨在提供良好的交互体验。它对于集体测试仍然有用:多人可以临时组合机器来运行一个在任何单台主机上都放不下的更大模型,以接受缓慢解码为代价,换取能够检查该模型本身。
像使用普通的 `./ds4` 一样使用协调器:交互式聊天、`/read` 和普通生成都通过相同的高级会话 API 进行。相同的分布式选项也集成到了 ds4-agent、ds4-eval 和 ds4-bench 中。对于基准测试,工作节点应已处于运行状态;ds4-bench 会等待直到有完整的路由可用。
有用的调优和诊断:
./ds4-bench \ -m gguf/DeepSeek-V4-Flash-Q4KExperts-F16HC-F16Compressor-F16Indexer-Q8Attn-Q8Shared-Q8Out-chat-v2.gguf \ --prompt-file speed-bench/promessi_sposi.txt \ --ctx-start 32768 \ --ctx-max 65536 \ --step-incr 32768 \ --gen-tokens 0 \ --role coordinator \ --layers 0:19 \ --listen 169.254.43.68 1234 \ --debug
在协调器上使用 `--debug` 会打印路由形成过程和每跳遥测数据:层范围、token 跨度、本地评估时间、下游等待时间、套接字发送时间以及输入/输出字节数。这是当前用于判断切分是否均衡的性能分析工具。`--dist-prefill-window N` 控制端到端可以同时进行的预填充块数量;默认值是保守且有界的。`--dist-prefill-chunk N` 用于实验,但默认的 4096 token 块是标准设置,除非你正在明确验证不同的块大小,否则应使用此设置。
默认情况下,DwarfStar 以 32 位浮点数发送隐藏状态激活值。为减少流量,可在协调器上传递 `--dist-activation-bits 16` 或 `--dist-activation-bits 8`。这仅改变机器之间的传输格式,不影响模型权重或 KV 缓存。16 位传输可将激活值流量减半,是在以太网或 WiFi 上首选的尝试选项。8 位传输更为激进,应视为近似/实验性模式,除非你已针对自己的用例验证了输出结果。不过,实验表明减小激活值尺寸并未带来显著改善,因此该选项未来可能会被移除。
如果某个工作节点断开连接,协调器会将该工作节点从当前活跃路由中移除。正在处理中的请求可能会失败,后续调用会报告路由不完整,直到有兼容的工作节点重新连接并发送新的注册信息。对于实时会话,协调器会保留 token 历史记录,并能在路由恢复时通过重放前缀来重建工作节点的 KV 状态。工作节点还会对每个工作项验证一个滚动的 64 位 token 前缀哈希,因此位置 0 处重启的工作节点无法静默地接受位置 N 的工作;它会报告不匹配,协调器则会重放当前的转录内容。CLI 和智能体中的 Ctrl+C 是协作式的:DwarfStar 会等待当前的分布式 token 或预填充块处理完毕后再交还控制权,从而避免协调器引发 KV 分裂。保存的智能体/服务器会话使用与单机会话相同的 KV 文件格式:保存时,协调器获取工作节点拥有的层张量,并序列化一个正常的负载;加载时,它会将该负载拆分到当前已注册的路由上。
分布式协议概述
在协议层面存在两种连接。工作节点会向协调器保持一条控制 TCP 连接,并发送一条 HELLO 消息,其中包含其模型 ID、模型族、量化配置、层切片、上下文容量以及数据端口。协调器利用这些注册信息构建一条覆盖所有层的路由。随后,工作通过低延迟 TCP 数据连接进行:协调器计算第一个切片,发送一条 WORK 帧,其中包含会话 ID、token 位置、跨度前后的滚动 token 前缀哈希值、路由信息以及隐藏状态负载,每个工作节点计算自己的切片。中间工作节点可以直接将数据转发给下一个工作节点。最后一个工作节点将 logits 返回给协调器,或者对非最终的预填充块返回 ACK,以便预填充流水线保持满载。RESULT 帧会回显请求 ID 和跨度后哈希值。工作节点状态错误的处理方式与套接字故障不同:KV/哈希不匹配可通过在同一路由上重放 token 历史来恢复,而传输故障则会丢弃该路由并等待替换的工作节点。对于持久化 KV,协调器会打开工作节点数据连接,并为每个工作节点所拥有的层范围发送快照保存/加载消息;磁盘负载保持为单个智能体/服务器缓存文件。该协议没有加密或认证,且尚未达到发布稳定版本;协调器和工作节点应使用同一代码提交构建,并在受信任的机器和受信任的网络上使用。
减少热量、功耗和风扇噪音
长时间的本地推理运行可能会让 GPU 持续忙碌。如果你更关心热量、风扇噪音、MacBook 的电池续航,或减少硬件热应力,而不是追求最大吞吐量,请使用 `--power N`。
`--power 100` 是默认值,表示全速运行。较低的值会让 DwarfStar 以该百分比的 GPU 使用率为目标:`--power 70` 目标约为 70%,`--power 50` 目标约为一半使用率,依此类推。DwarfStar 通过测量 GPU 工作时间并在工作单元之间插入短暂休眠来实现这一点:在预填充阶段,它在层之间休眠;在生成阶段,它在解码出的 token 之间休眠。这在不改变模型输出的情况下降低了持续负载。
该选项可在 CLI、服务器、智能体、评测和基准测试工具中使用,例如:
./ds4 --power 50 ./ds4-agent --power 70 ./ds4-server --power 40 --ctx 100000
原生智能体
DwarfStar 配备了一个原生编码智能体,其工作方式与大多数其他系统不同:推理过程由智能体自身内部控制,无需通过 socket/API 边界,因此会话由磁盘上的 KV 缓存本身表示。此外,工具和系统提示词均为 DeepSeek v4 Flash 和 PRO 进行了垂直设计。这带来了几个优势:
- 低延迟体验,主要受预填充速度限制。生成文本、调用工具、启动新会话始终是即时完成的。
- 预填充期间显示实时进度条。
- 无需进行 DSML 工具调用转换,工具以 LLM 原生格式处理。
- 从架构上杜绝了 KV 缓存不匹配的可能性,当前状态始终是真实状态。
- 所有内容均针对此模型进行了调优。
- 能够通过 `/list` 和 `/switch` 切换已保存的会话;完整的 KV 会话无需预填充阶段即可恢复。
智能体会话存储在 `~/.ds4/kvcache` 中。使用 `/save` 持久化当前会话,使用 `/list` 按最近更新时间排序显示已保存的会话,使用 `/switch <sha>` 恢复其中一个会话。会话 ID 在后续保存中保持稳定,由第一条用户提示词和创建时间派生而来。`/del <sha>` 删除一个已保存的会话。`/strip <sha>` 保留渲染后的对话文本和标题,但移除占用大量空间的 KV 负载;切换到已精简的会话时,会通过预填充已保存的文本来重建 KV 缓存。
从其他目录启动 ds4-agent 时,使用 `--chdir /path/to/ds4`,以便 `metal/*.metal` 等相对路径的运行时文件能够从项目树中正确解析。
不过,尽管该系统目前已经可以运行,但要使其达到成熟可用的状态,仍有大量工作要做。当智能体最终达到理想形态时,我们很可能会将服务器和客户端分离,创建一个基于有状态会话的协议,以客户端-服务器的方式重建所有这些功能。
基准测试
ds4-bench 测量的是上下文边界处的即时预填充和生成吞吐量,而不是报告整个运行过程的平均值。它只加载模型一次,沿着固定的 token 序列遍历至 2048、4096、6144 等边界,并使用增量预填充,因此每一行仅测量新添加的 token 区间。在每个边界之后,它将当前的 KV 状态保存到内存中,生成一个固定的贪婪非 EOS 探测序列,恢复内存快照,然后继续预填充。
./ds4-bench \ -m ds4flash.gguf \ --prompt-file speed-bench/promessi_sposi.txt \ --ctx-start 2048 \ --ctx-max 65536 \ --step-incr 2048 \ --gen-tokens 128
示例文件是 Alessandro Manzoni 的《约婚夫妇》(I Promessi Sposi)经过清理的公有领域 Project Gutenberg 文本(电子书 #45334),已移除 Gutenberg 的页眉和页脚:https://www.gutenberg.org/ebooks/45334。
使用 `--step-incr N` 可设置不同的线性间隔,或使用 `--step-mul F` 进行指数扫描。输出为 CSV 格式,每个边界对应一行:最新的预填充区间 token/秒、该边界处的生成 token/秒,以及 kvcache_bytes。
会话默认以 4096 token 的块大小预填充长提示词。设置 `DS4_METAL_PREFILL_CHUNK=N` 可比较其他块大小,例如 2048 以匹配严格的官方向量检查点路径,或设置 `DS4_METAL_PREFILL_CHUNK=0` 以在内存允许时将提示词作为一个完整批次进行预填充。更改块大小会改变 KV 检查点/逻辑路径,因此应将其作为明确的运行配置进行比较。分块 Metal 预填充为每个块重用相同的、支持范围感知的按层主图,在保留绝对压缩器/索引器边界的同时,避免了旧的逐层分块调度路径。
能力评估
ds4-eval 是一个小型的真实模型集成基准测试。它不是排行榜运行程序,也不应作为官方的 GPQA、SuperGPQA、AIME 或安全基准测试分数进行报告:其中的问题是一个嵌入式的 92 项子集,旨在使本地回归测试有用且可直观检查。该程序加载真实的 GGUF 文件,渲染 DeepSeek 聊天提示词,在分屏 TUI 中流式输出采样的 token,对最终答案进行评分,并打印一份逐题报告,其中包含提示词 token 数、生成 token 数、通过/失败状态、模型答案以及正确答案。
./ds4-eval -m ds4flash.gguf --trace /tmp/ds4-eval.txt
默认运行使用 `--tokens 16000`,启用思考模式,并设置软/硬 `</think>` 预算截断,以便模型有空间生成可见答案。ds4-eval 会根据所选最大提示词加上生成预算,在内部确定上下文大小,并拒绝需要超过 100 万上下文 token 的运行。按 p 暂停,按 q 退出并打印报告,按上/下键检查或选择另一个问题,按回车键运行所选的下一个问题。`--plain` 可禁用 TUI。
使用 `--regrade-trace /path/to/trace.txt` 可针对之前的 `--trace` 文件重放当前的答案提取器和评分器,而无需加载模型或重新生成 token。这在审计评估器变更时非常有用:它会显示哪些案例发生了变化、旧的选定答案、新的选定答案,以及通过/失败的摘要。
对于可能影响生成漂移的推理变更,请在测试计划中保留这个确定性的 q1..q4 token 计数门控:
./ds4-eval \ -m ds4flash.gguf \ --plain \ --questions 4 \ --tokens 2048 \ --temp 0 \ --seed 1
生成的 token 计数必须与基线保持一致:
| 问题 | 预期状态 | 预期生成的 token 数 | 预期给出/正确 |
|---|---|---|---|
| 1 | 通过 | 2048 | B / B |
| 2 | 通过 | 438 | C / C |
| 3 | 通过 | 666 | 70 / 70 |
| 4 | 失败 | 2048 | A / C |
前 75 个嵌入问题交错排列为 25 个 GPQA Diamond、25 个经过审计的 SuperGPQA 和 25 个 AIME 2025 问题。最后 17 个是经过审计的 COMPSEC 子集,包含简化的单功能 C/C++ 漏洞定位问题。模型需要给出单个最佳源代码行,或者仅在无法将错误定位到一行时给出最小的精确行集;评分器仅当相邻行是同一错误的等效位置时才接受经过审计的小范围。顺序有意设置为渐进式:早期问题是有用的冒烟测试,而后期问题难度足够大,即使是强大的推理模型也应该会遗漏其中一些。SuperGPQA 切片是经过筛选而非盲选的:上游数据中键值错误、缺少图表或提示词不明确的行会被替换为更清晰的行。
该测试集应被视为一项硬能力回归测试套件,而非通过/失败的单元测试。
- GPQA Diamond 提供了研究生级别的科学问题,并配有选择题答案。DeepSeek 的模型卡报告显示,在思考模式下,该模型在完整的 GPQA Diamond 上取得了强劲的成绩,但单个题目仍需仔细的物理、化学或生物学推理,并且很容易因提示词/渲染方式的小改动或采样回归而出错。
- SuperGPQA 贡献了广泛的专家知识和跨领域问题。模型卡上的 SuperGPQA 分数远低于 GPQA Diamond,因此这些题目的难度预计会参差不齐:有些看起来普通,另一些则需要小众的专业知识或对翻译风格考题的精确解读。
- AIME 2025 贡献了需要精确答案的竞赛数学题。这些通常是整个测试集中最不留情面的题目:没有选择题的先验知识,没有步骤分,任何一次算术或代数错误都会改变最终成绩。
- COMPSEC 贡献了从公开 CVE 分析报告中提炼出的单函数 C/C++ 安全推理题。这些不是漏洞利用提示词:任务是识别引入防御性代码缺陷的最佳源代码行,如果函数是安全的则返回 0。
实际上,这意味着不应期望 ds4-eval 能完美地跑出 92/92 的成绩。它的目的是回答一个更有用的工程问题:在内核、量化、提示词渲染、KV 缓存或工具流发生变更后,DeepSeek V4 Flash 是否仍能在用户实际使用的相同推理路径下,解决一组具有代表性的难题,涵盖硬科学、广泛知识、精确数学和安全代码问题?
命令行界面
单次提示词:
./ds4 -p "Explain Redis streams in one paragraph." 不使用 -p 参数将启动交互式提示词:
./ds4
ds4> 交互式 CLI 是一个真正的多轮对话。它会保留渲染后的对话记录和实时的图 KV 检查点,因此每一轮都会延续之前的对话。有用的命令包括 /help、/think、/think-max、/nothink、/ctx N、/read FILE 和 /quit。按 Ctrl+C 可中断当前生成并返回 ds4> 提示符。
CLI 默认使用思考模式。使用 `/nothink` 或 `--nothink` 可获取直接回答。`--mtp MTP.gguf --mtp-draft 2` 可启用可选的 MTP 推测路径;该路径仅对贪心解码有效,目前使用置信度门控(`--mtp-margin`)来避免缓慢的部分接受,应视为实验性的轻微加速路径。
服务器
启动一个兼容 OpenAI/Anthropic 的本地服务器:
./ds4-server --ctx 100000 --kv-disk-dir /tmp/ds4-kv --kv-disk-space-mb 8192
从其他目录启动 `ds4-server` 时,请使用 `--chdir /path/to/ds4`,以便 `metal/*.metal` 等相对运行时文件能从项目树中正确解析。
服务器在内存中保留一个可变的后端/KV 检查点,因此重新发送同一提示词更长版本的无状态客户端可以复用共享前缀,而无需从零号 token 开始预填充。
请求解析和套接字在客户端线程中运行,但推理本身通过一个图工作线程串行化。当前服务器不会将多个独立请求批量处理;并发请求需在单个活动图/会话上排队等待。
支持的端点:
- GET /v1/models
- GET /v1/models/deepseek-v4-flash
- GET /v1/models/deepseek-v4-pro
- POST /v1/chat/completions
- POST /v1/responses
- POST /v1/completions
- POST /v1/messages
Flash 和 PRO 模型端点是兼容性别名。它们都报告当前通过 `-m` 参数传入的 GGUF 文件所加载的模型;端点名称不会选择不同的模型。
`/v1/chat/completions` 接受标准的 OpenAI 风格消息、`max_tokens`/`max_completion_tokens`、`temperature`、`top_p`、`top_k`、`min_p`、`seed`、`stream`、`stream_options.include_usage`、`tools` 和 `tool_choice`。工具模式会被渲染为 DeepSeek 的 DSML 工具格式,生成的 DSML 工具调用会被映射回 OpenAI 工具调用。
`/v1/responses` 接受 OpenAI Responses 风格的输入、`instructions`、`tools`、`tool_choice`、`max_output_tokens`、`temperature`、`top_p`、`stream` 和 `reasoning`。这是 Codex CLI 的首选端点。服务器会尽可能将 Responses 的连续请求绑定到活动状态,并可回退到与聊天补全相同的 DSML 渲染和 KV 前缀复用机制。
/v1/messages 是兼容 Anthropic 的端点,供 Claude Code 风格客户端使用。它接受 system、messages、tools、tool_choice、max_tokens、temperature、top_p、top_k、stream、stop_sequences 以及 thinking 控制参数。工具调用以 Anthropic 的 tool_use 块形式返回。
默认采样 API 生成使用 temperature=1、top_p=1 和 min_p=0.05,因此默认过滤器基于相对概率而非核采样质量。在思考模式下,DwarfStar 使用这些固定的采样默认值,并忽略客户端的采样旋钮,这与 DeepSeek 的固定思考 API 行为一致。
chat、Responses 和 Anthropic 端点支持 SSE 流式传输。在思考模式下,推理内容以原生 API 形状流式传输,而非混入最终文本中。OpenAI 聊天流式传输在识别到 DSML 调用时也会立即流式传输工具调用:先发送工具头部,然后在生成过程中将参数字节作为 tool_calls[].function.arguments 增量转发。Anthropic 端点实时流式传输思考内容和文本,然后在生成的工具块完成时发出结构化的 tool_use 块。Responses 端点流式传输 Codex 所期望的 Responses 事件生命周期,包括 response.output_text.delta、函数调用参数事件,以及最终的 response.completed / response.incomplete / response.failed 事件。
对于来自其他源的浏览器 JavaScript 客户端,请使用 --cors 启动服务器以发出 Access-Control-Allow-* 标头。这仅更改 HTTP 标头,不会将服务器暴露在局域网中。当需要远程机器能够连接时,请显式使用 --host 0.0.0.0。
工具调用处理与规范化
DeepSeek V4 以 DSML 文本形式发出工具调用。智能体客户端不会在下一个请求中将相同的文本发送回来:它们会发送规范化的 OpenAI/Anthropic JSON 工具调用对象。如果服务器以略微不同的方式重新渲染这些对象,渲染后的字节前缀将不再匹配实时的 KV 检查点,下一个轮次将不得不重新构建。
第一道防线是精确回放。每次工具调用都会获得一个不可猜测的 API 工具 ID,服务器会记住工具 ID 到精确采样的 DSML 块的映射关系,该映射存储在一个基于基数树的有界内存映射中。当客户端后续回传该工具 ID 时,提示词渲染器会使用模型采样时的精确 DSML 字节,而不是重新格式化的近似内容。这个映射也可以保存在 KV 缓存文件中,因此对于已缓存的历史记录,精确回放能够在服务器重启后依然生效。
规范化只是备用路径。如果精确的 DSML 块缺失,或者通过 `--disable-exact-dsml-tool-replay` 禁用了精确回放,服务器会根据 JSON 工具对象渲染出一个确定性的 DSML 形式。在一次工具调用轮次之后,它会将实时采样的 token 流与下一个客户端请求将要渲染的提示词进行比较。如有必要,它会重写实时检查点,或者回退到磁盘上较旧的 KV 快照,并仅重放后缀部分。这确保了模型的续写内容与无状态 API 记录保持一致。
在生成过程中,服务器还会区别对待 DSML 语法和负载内容。当模型正在生成稳定的协议结构(例如 DSML 标签、参数头部、JSON 标点符号或结束标记)时,采样会被强制设为 temperature=0,以确保工具调用保持可解析状态。这种贪婪模式不适用于参数负载:字符串类型的参数体和 JSON 字符串值(包括文件内容和编辑文本)会使用请求的正常采样设置。这种区分很重要:确定性解码有助于处理语法,但应用于长代码或文件正文时可能会产生重复文本。
最小的 OpenAI 示例:
curl http://127.0.0.1:8000/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{ "model":"deepseek-v4-flash", "messages":[{"role":"user","content":"List three Redis design principles."}], "stream":true }'
智能体客户端用法
ds4-server 可供使用兼容 OpenAI 的聊天补全接口的本地编程智能体使用。首先启动服务器,并将客户端上下文限制设置为你启动服务器时使用的 `--ctx` 值,不得超过该值。
./ds4-server --ctx 100000 --kv-disk-dir /tmp/ds4-kv --kv-disk-space-mb 8192
如果你愿意,可以使用更大的上下文和更大的缓存。完整的 100 万 token 上下文大约会占用 26GB 内存(仅压缩索引器就约 22GB),因此请根据你的系统配置一个合理的上下文。如果拥有 128GB 内存,你可以运行 2-bit 量化模型,其本身已占用 81GB,再增加 26GB 可能负担过重,因此将上下文窗口设为 10 万到 30 万 token 更为明智。不过,有用户报告称,在仅配备 96GB 系统内存的 Mac 上,也能以 250k 上下文窗口运行 2-bit 量化模型:如果你也打算这样做,请务必关闭占用过多内存的进程 ;)
下面设置的 384000 输出限制是为了避免 token 上限,因为该模型能够生成非常长的回复(最多可达 38.4 万 token)。当配置的上下文窗口已满时,服务器仍会停止生成。
对于 opencode,请在 ~/.config/opencode/opencode.json 中添加一个 provider 和 agent 条目:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"ds4": {
"name": "ds4.c (local)",
"npm": "@ai-sdk/openai-compatible",
"options": {
"baseURL": "http://127.0.0.1:8000/v1",
"apiKey": "dsv4-local"
},
"models": {
"deepseek-v4-flash": {
"name": "DeepSeek V4 Flash (ds4.c local)",
"limit": {
"context": 100000,
"output": 384000
}
}
}
}
},
"agent": {
"ds4": {
"description": "DeepSeek V4 Flash served by local ds4-server",
"model": "ds4/deepseek-v4-flash",
"temperature": 0
}
}
} 对于 Pi,请在 ~/.pi/agent/models.json 中添加一个 provider:
{
"providers": {
"ds4": {
"name": "ds4.c local",
"baseUrl": "http://127.0.0.1:8000/v1",
"api": "openai-completions",
"apiKey": "dsv4-local",
"compat": {
"supportsStore": false,
"supportsDeveloperRole": false,
"supportsReasoningEffort": true,
"supportsUsageInStreaming": true,
"maxTokensField": "max_tokens",
"supportsStrictMode": false,
"thinkingFormat": "deepseek",
"requiresReasoningContentOnAssistantMessages": true
},
"models": [
{
"id": "deepseek-v4-flash",
"name": "DeepSeek V4 Flash (ds4.c local)",
"reasoning": true,
"thinkingLevelMap": {
"off": null,
"minimal": "low",
"low": "low",
"medium": "medium",
"high": "high",
"xhigh": "xhigh"
},
"input": ["text"],
"contextWindow": 100000,
"maxTokens": 384000,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
}
]
}
}
} 可选地,在 ~/.pi/agent/settings.json 中将其设为默认的 Pi 模型:
{
"defaultProvider": "ds4",
"defaultModel": "deepseek-v4-flash"
} 对于 Codex CLI,请使用 Responses wire API:
[model_providers.ds4] name = "DS4" base_url = "http://127.0.0.1:8000/v1" wire_api = "responses" stream_idle_timeout_ms = 1000000
然后运行:
codex --model deepseek-v4-flash -c model_provider=ds4
对于 Claude Code,请使用兼容 Anthropic 的端点。像这样的包装器与本地 ~/bin/claude-ds4 设置相匹配:
#!/bin/sh unset ANTHROPIC_API_KEY export ANTHROPIC_BASE_URL="${DS4_ANTHROPIC_BASE_URL:-http://127.0.0.1:8000}" export ANTHROPIC_AUTH_TOKEN="${DS4_API_KEY:-dsv4-local}" export ANTHROPIC_MODEL="deepseek-v4-flash" export ANTHROPIC_CUSTOM_MODEL_OPTION="deepseek-v4-flash" export ANTHROPIC_CUSTOM_MODEL_OPTION_NAME="DeepSeek V4 Flash local ds4" export ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION="ds4.c local GGUF" export ANTHROPIC_DEFAULT_SONNET_MODEL="deepseek-v4-flash" export ANTHROPIC_DEFAULT_HAIKU_MODEL="deepseek-v4-flash" export ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-flash" export CLAUDE_CODE_SUBAGENT_MODEL="deepseek-v4-flash" export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 export CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK=1 export CLAUDE_STREAM_IDLE_TIMEOUT_MS=600000 exec "$HOME/.local/bin/claude" "$@"
Claude Code 在开始执行有用工作之前,可能会发送一个较大的初始提示词,通常约为 25000 个 token。请保持启用 --kv-disk-dir:在首次昂贵的预填充之后,磁盘 KV 缓存允许后续的延续或重启的会话重用已保存的前缀,而无需再次处理整个提示词。
思考模式
DeepSeek V4 Flash 具有不同的非思考、思考和 Think Max 模式。服务器默认使用思考模式。reasoning_effort=max 会请求 Think Max 模式,但仅在上下文大小足够满足模型卡推荐时才会应用;较小的上下文会回退到普通思考模式。OpenAI 的 reasoning_effort=xhigh 仍映射到普通思考模式,而非 Think Max。
对于直接回复,请使用 thinking: {"type":"disabled"}、think:false 或非思考模型别名,例如 deepseek-chat。
磁盘 KV 缓存
Chat/completion API 是无状态的:智能体客户端通常会在每次请求时重新发送整个对话。ds4-server 首先尝试成本较低的精确 token 前缀检查,如果失败,则回退到将渲染后的提示词字节与解码后的检查点字节进行比较。内存中的实时检查点覆盖当前会话;磁盘 KV 缓存则让有用的前缀能够在会话切换和服务器重启后仍然可用。
出于内存原因,当前内存中只有一个实时 KV 缓存。当一个新的无关会话将其替换时,旧的检查点只有被写入磁盘 KV 缓存后,才能在不重新处理的情况下恢复。换句话说,内存缓存处理当前活跃的会话;磁盘缓存则是不同会话的恢复机制。
通过以下方式启用它:
./ds4-server --kv-disk-dir /tmp/ds4-kv --kv-disk-space-mb 8192
缓存键是渲染后字节前缀的 SHA1 值,文件命名为 `<sha1>.kv`。DS4 负载仍然存储该前缀对应的精确 token ID 和图状态。这对于持续对话很重要:模型可能生成了一个 token,其解码后的文本稍后被客户端作为两个规范提示词 token 发送回来。如果命中渲染后的字节前缀,仍然可以复用该检查点,仅对新后缀进行 token 化。该文件特意使用普通的读/写 I/O(而非 mmap)写入,这样在恢复缓存条目时,不会给已经映射了模型的进程增加更多的 VM 映射。
工具调用还维护了一个有界的精确 DSML 重放映射,其键是不可猜测的工具 ID,这样客户端 JSON 历史记录可以被渲染回精确的采样文本。默认情况下,RAM 映射最多保留 100000 个 ID;可通过 `--tool-memory-max-ids` 进行调整。使用 `--disable-exact-dsml-tool-replay` 可以禁用此功能,并回退到标准的 JSON 到 DSML 渲染。
在磁盘上,缓存文件的结构是:
KVC fixed header, 48 bytes
u32 rendered_text_bytes
rendered_text_bytes of UTF-8-ish token text
DS4 session payload, payload_bytes from the KVC header
optional tool-id map section
固定头部采用小端序:
0 u8[3] magic = "KVC"
3 u8 version = 1
4 u8 routed expert quant bits, currently 2 or 4
5 u8 save reason: 0 unknown, 1 cold, 2 continued, 3 evict, 4 shutdown
6 u8 extension flags, bit 0 = appended tool-id map
7 u8 reserved
8 u32 cached token count
12 u32 hit count
16 u32 context size the snapshot was written for
20 u8[4] reserved
24 u64 creation Unix time
32 u64 last-used Unix time
40 u64 DS4 session payload byte count
渲染文本是缓存 token 前缀经过分词器解码后的文本。它既是可供人工检查的前缀,也是查找标识:其 SHA1 值就是文件名,只有当这些字节是传入提示词的前缀时,该文件才可复用。加载后,来自 DS4 负载的精确检查点 token 仍然具有权威性,并且仅对缓存字节之后传入的文本后缀进行 token 化。
可选的工具 ID 映射仅在头部扩展位 0 置位时存在。追加的段使用固定的位顺序,因此未来的扩展位可以无歧义地添加字段。该映射将不可猜测的 API 工具调用 ID 回溯到模型采样的确切 DSML 块。仅存储其 DSML 块存在于渲染缓存文本中的映射。这使得重启后的服务器能够像原始模型输出一样,逐字节地渲染后续的客户端历史记录,即使客户端对 JSON 参数进行了重新排序。
当前的工具 ID 映射段为:
0 u8[3] magic = "KTM"
3 u8 version = 1
4 u32 entry count
For each entry:
0 u32 tool id byte length
4 u32 sampled DSML byte length
8 bytes tool id
... bytes exact sampled DSML block
该段是辅助性的重放记忆,而非模型状态。缓存命中会先恢复会话载荷,然后(如果存在)加载映射。在处理请求之前,服务器还可以扫描缓存文件,查找客户端历史记录中存在的工具 ID,并仅加载那些映射,因此即使匹配的 KV 快照并非最终用于渲染前缀命中的那个,精确的 DSML 重放也能在服务器重启后幸存。
DS4 会话载荷以十三个小端序 u32 字段开头:
0 magic = "DSV4"
1 payload version = 2
2 saved context size
3 prefill chunk size
4 raw KV ring capacity
5 raw sliding-window length
6 compressed KV capacity
7 checkpoint token count
8 layer count
9 raw/head KV dimension
10 indexer head dimension
11 vocabulary size
12 live raw rows serialized below
然后存储:
- u32[token_count] 检查点 token ID。
- float32[vocab_size] 该检查点之后下一个 token 的 logits。
- u32[layer_count] 压缩注意力行数。
- u32[layer_count] 比率-4 索引器行数。
- 对于每一层:实时的原始滑动窗口 KV 行,按逻辑位置顺序而非物理环形顺序写入。
- 对于压缩层:实时的压缩 KV 行和压缩器前沿张量。
- 对于比率-4 压缩层:实时的索引器压缩行和索引器前沿张量。
logits 是来自主机 ds4_session 缓冲区的原始 IEEE-754 float32 值。它们在检查点 token 之后立即保存,以便加载的快照可以从确切的下一个 token 分布进行采样或继续,而无需多运行一个解码步骤。MTP 草案 logits/状态不会被持久化;加载磁盘检查点后,草案状态会失效,并通过正常生成过程重建。
分布式协调器会话使用相同的 DSV4 载荷。在保存时,拉取工作节点拥有的层张量,并将其合并到正常的按层排序的张量流中;在加载时,协调器将该张量流拆分为当前路由,并将相关的层张量推送回各工作节点。保存的文件不保留分布式拓扑结构。
张量载荷是 DS4 专用的 KV/会话状态,而非通用的推理图转储。它预期仅能在针对此模型布局的兼容 ds4.c 构建版本之间移植。
缓存会在四个时刻保存检查点:
- 冷启动:在较长的初始提示词达到稳定前缀之后、生成开始之前。
- 续写:当预填充或生成过程达到下一个绝对对齐边界时。
- 驱逐:在无关请求替换内存中的活跃会话之前。
- 关闭:当服务器正常退出时。
冷启动保存会特意裁剪掉少量尾部 token,并向下对齐到预填充块边界。这可以避免未来请求向同一提示词追加文本时,常见的 BPE 边界重新分词错误。默认设置较为保守:存储至少 512 个 token 的前缀,冷启动保存的提示词最多 30000 个 token,裁剪掉末尾 32 个 token,并对齐到 2048 个 token 的块。重要的调节参数如下:
续写保存使用相同的对齐方式,并且仅在活跃图自然达到绝对边界时写入。使用默认设置时,这意味着大约每 10000 个 token 保存一次,与第一个冷启动检查点的位置无关,这样长文本生成过程会留下重启点,而无需持久化脆弱的最后几个 token。
- --kv-cache-min-tokens
- --kv-cache-cold-max-tokens
- --kv-cache-continued-interval-tokens
- --kv-cache-boundary-trim-tokens
- --kv-cache-boundary-align-tokens
- --tool-memory-max-ids
- --disable-exact-dsml-tool-replay
默认情况下,如果渲染出的前缀匹配,检查点可以在 2 位和 4 位路由专家变体之间复用。当你希望严格限定仅复用相同量化位数的检查点时,请使用 --kv-cache-reject-different-quant。
缓存目录是可丢弃的。如果行为看起来可疑,请停止服务器并删除该目录。你可以使用 hexdump 检查缓存内容,因为 KV 缓存文件包含所缓存的原始提示词。
后端
默认的图后端在 macOS 上是 Metal,在 CUDA 构建中是 CUDA:
./ds4 -p "Hello" --metal ./ds4 -p "Hello" --cuda
在 Linux 上,直接运行 `make` 会列出可用的构建目标,而不是隐式选择 CUDA 目标。使用 `make cuda-spark` 针对 DGX Spark / GB10 构建。该命令省略了显式的 `nvcc -arch` 参数,因为这是目前在 GB10 上最快的路径。使用 `make cuda-generic` 进行常规的本地 CUDA 构建,或者在交叉编译或需要指定已知目标时,显式设置 `CUDA_ARCH`:
make cuda CUDA_ARCH=sm_120 make cuda CUDA_ARCH=native
此外还有一个 CPU 参考/调试路径:
./ds4 -p "Hello" --cpu make cpu ./ds4 ./ds4 -p "Hello"
不要将 CPU 路径视为生产目标。CLI 和 ds4-server 支持 CPU 后端用于参考/调试用途,并且与 Metal 和 CUDA 共享相同的 KV 会话和快照格式,但正常的推理应使用 Metal 或 CUDA。
引导(Steering)
本项目支持使用单向量激活方向进行引导;更多信息请参见 `dir-steering` 目录。这遵循了《语言模型中的拒绝行为由单一方向介导》论文的核心思想。你可以用它来让模型变得更啰嗦或更简洁,或者如果你有一个用于汽车租赁网站的聊天机器人,可以降低它回答编程问题的倾向,等等,这比微调快得多。这对于希望降低模型提供双重用途或攻击性安全指导意愿的网络安全研究人员也很有用。
测试向量
`tests/test-vectors` 包含从官方 DeepSeek V4 Flash API 捕获的短上下文和长上下文续写向量。这些请求使用 `deepseek-v4-flash`,贪婪解码,禁用思考,以及 API 暴露的最大 `top_logprobs` 切片。本地向量通过 `./ds4 --dump-logprobs` 生成,并按 token 字节进行比较,因此分词器/模板或注意力机制的回归问题会在演变为长文本生成失败之前就被发现。C 运行器在此严格的 API 向量比较中固定 `DS4_METAL_PREFILL_CHUNK=2048`。
所有项目测试均由 C 运行器驱动,首先运行一个小的 ds4-eval 提取器自检:
make test # ./ds4-eval --self-test-extractors && ./ds4_test --all ./ds4_test --logprob-vectors ./ds4_test --server
调试说明
当生成结果看起来有问题时,通常三个小工具就足以获得初步答案:
./ds4 --dump-tokens -p "..." ./ds4 --dump-logprobs /tmp/out.json --logprobs-top-k 20 --temp 0 -p "..." ./ds4 --dump-logits /tmp/logits.json --metal --nothink --prompt-file prompt.txt ./ds4-server --trace /tmp/ds4-trace.txt ...
- `--dump-tokens` 会按原样对 `-p` 或 `--prompt-file` 字符串进行 token 化,识别 DS4 协议特殊标记,然后在推理开始前退出。例如,DSML 工具关闭标记以两个 token 开头:`</` 和 `|DSML|`。
- `--dump-logprobs` 会存储一个贪婪续写结果,并在每一步附带最高概率的局部替代选项,这有助于将采样选择与 logit/模型问题区分开来。
- `ds4-server --trace` 会记录整个智能体会话中渲染后的提示词、缓存决策、生成的文本以及工具解析器事件。
关于
面向 Metal、CUDA 和 ROCm 的 DeepSeek 4 Flash 和 PRO 本地推理引擎
资源
许可证
贡献指南
星标
14.7k
关注者
复刻
1.3k
发布版本
软件包
贡献者
编程语言
- C 49.6%
- Cuda 21.4%
- Objective-C 17.1%
- Metal 6.3%
- C++ 2.9%
- Python 2.4%
- 其他 0.3%