2026 年 10 月 20 日,DeepSeek 开源了 DeepInfer(推理引擎)和 deepseek-recipe(协议编解码库)。
这两个东西不是两个独立的玩具,而是同一条请求链路的上下游。
本文按「先讲清楚它们在链路里的位置 → 各自解什么问题 → 怎么上手 → 你该不该用」来写,全程用后端能听懂的话。

0. 三十秒版本

DeepInferdeepseek-recipe
一句话DeepSeek 线上推理引擎的开源版,负责 把 token 算出来协议编解码库,负责 把 HTTP 请求翻译成 token,再把 token 翻译成 HTTP 响应
类比Netty + 线程池 + 连接池 + 本地缓存 + 负载均衡Spring MVC 的 HttpMessageConverter + Jackson
语言Python + C++/CUDA/AscendC kernelRust(核心)+ Python 绑定
形态一个能被 import 的库,不自带 HTTP 服务一个能被 import 的库,不自带模型
硬件要求NVIDIA SM100 或华为昇腾 950,最小高效部署 64+64 卡笔记本就能跑(纯 CPU)
你现在能用吗基本不能(除非你在管 GPU 集群)能,pip install deepseek-recipe
许可证Apache 2.0MIT

最重要的一句话:DeepInfer 里没有 OpenAI 兼容接口,deepseek-recipe 里没有模型推理。这两个仓库加在一起,才是一个「能对外提供 API 的模型服务」。 这不是巧合,是刻意切的。


1. 它们在同一条链路的什么位置

先看一次 POST /v1/chat/completions 完整走完的路径:

客户端 SDK
   |  {"model":"deepseek-flash","messages":[...],"stream":true}
   v
+---------------------------------------------------+
|  ① 协议层:deepseek-recipe                       |
|     · 解析 Messages / Chat Completions /         |  ← 三种「方言」
|       Responses 三种请求格式                      |
|     · 统一成内部 Conversation 对象               |
|     · 按模型版本渲染 chat template               |
|     · 分词 → token_ids                          |
|     · 算出这次该给多少 max_tokens                |
+---------------------------------------------------+
   |  prompt + token_ids + InferenceOptions
   v
+---------------------------------------------------+
|  ② 调度层:你自己写(或 DeepSeek 没开源的那部分)|
|     负载均衡 / KV cache 路由 / 扩缩容 / 鉴权      |
+---------------------------------------------------+
   |
   v
+---------------------------------------------------+
|  ③ 计算层:DeepInfer                             |
|     · prefill:把 prompt 算成 KV cache           |
|     · decode:一个一个往外吐 token               |
|     · 投机解码 / 结构化输出 / 确定性采样         |
+---------------------------------------------------+
   |  token 流
   v
+---------------------------------------------------+
|  ④ 又是 recipe:StreamProcessor                  |
|     · token → 文本(增量 UTF-8 解码)           |
|     · 拆 reasoning / 正文 / 工具调用参数         |
|     · 组装成 SSE chunk,按客户端要的方言输出     |
+---------------------------------------------------+
   |  data: {"choices":[{"delta":{"content":"你"}}]}
   v
客户端

后端视角翻译一下:

  • recipe = 序列化层 / 协议适配层。它无状态、纯 CPU、可水平扩,跟你的业务代码一样随便加机器。
  • DeepInfer = 计算层。它是有状态的、吃 GPU 的、昂贵的那一层。
  • 中间那层(调度、KV cache 路由、扩缩容)这次没开源,DeepSeek 在 Q&A 里说明了原因:跟内部基础设施耦合太紧。

这个切法本身就有信息量:DeepSeek 把「跟硬件绑定的」和「跟协议绑定的」彻底分开了。前者换硬件才需要动,后者换 API 标准就要动。放在一个进程里,你会天天为了兼容一个新协议去重启 GPU 集群。


2. DeepInfer:把「算 token」这件事做到极致

2.1 它要解的问题:一段文本生成,其实是两种完全不同的负载

大模型生成回答分两个阶段,硬件瓶颈完全相反:

阶段干什么瓶颈优化方向
Prefill把整段 prompt 一次性算完,产出 KV cache算力(矩阵乘法打满)堆算力、切大 chunk、减少重复计算
Decode一次只生成 1 个 token,循环 N 次显存带宽(每个 token 都要把所有专家权重读一遍)减少搬运、加大并发、投机解码

把这两种负载塞在同一批卡上,一定是浪费:prefill 在打满算力的时候,decode 的带宽是闲的,反之亦然。

所以 DeepInfer 的第一条架构决策是:拆。

2.2 架构:EPD 三分离

              +------------+
      请求→  | Entrypoint | ← 前缀匹配、算命中长度、路由
              +-----+------+
                    |
        +-----------+-----------+
        v           v           v
   +---------+ +---------+ +---------+     VE = Vision Encoder
   |   VE    | |   PE    | |   CE    |     PE = Prefill Engine
   |  视觉   | | Prefill | | Decode  |     CE = Decode Engine
   +---------+ +----+----+ +----^----+
                    |           |
                    +-----------+ KV cache 传输
                    |
             +------v------+
             |  3FS 存储   |  ← KV cache 落盘,100 PiB 级
             +-------------+
  • VE / PE / CE 各自独立部署,可以按阶段单独扩缩容,TTFT(首字延迟)和 TBT(字间延迟)就能分开调。
  • 并行策略不对称,这点很关键,Q&A 里说得很直白:
阶段并行配置为什么
PrefillEP 2~8 / DP 2~8用 HBM 允许的最小并行度,少通信
DecodeEP 64~128 / DP 64~128(Wide-EP)用 GPU 允许的最大并行度,把 MoE 权重摊薄

为什么 decode 要 Wide-EP?因为 decode 是带宽瓶颈——每个 token 都要把全量专家权重从 HBM 搬到 SM。如果 64 张卡各放 1/64 的专家,每张卡每步只搬 1/64 的权重,带宽压力直接除以 64。代价是卡间要 all-to-all 交换 token,但如果都在同一个 supernode 内,网络比 HBM 便宜得多。

这是「用网络换带宽」的经典操作。跟分库分表一个思路:单表扛不住,就拆开,让每次查询只碰一小片。

2.3 六个值得单独看的设计

① KV cache 当外部存储用,不是当内存缓存用

一般做法是把 KV cache 放显存/内存,命中不了就重算。DeepSeek 的做法是落到 3FS 分布式文件系统上:

  • 容量约 100 PiB SSD
  • 每台 KV 存储服务器 20×30.73 TB SSD,读 ≥40 GB/s、写 5 GB/s
  • TTL:agent 流量 3 天,chatbot 流量 7 天
  • 逻辑上每 GPU 约 14.4 TB

为什么不用本地内存分层缓存?Q&A 给了理由,而且是后端最该记的一条:

用专用存储集群,可以让任意一台 prefill 机器命中任意前缀,避免「前缀亲和性」和「负载均衡路由策略」互相打架。

翻译成人话:如果 KV cache 存在机器本地,为了命中率你就得把同一用户的请求粘到同一台机器(会话粘滞),但为了负载均衡你又想随便路由。这两个目标天然冲突。把缓存外置,两个问题一起解决 —— 这是把「有状态」变成「无状态」的老办法,和把 session 从内存挪到 Redis 一模一样。

附带好处:存储节点扩容和计算节点扩容解耦;计算节点故障率比存储节点高一个数量级,缓存不受影响。

② SWA bounded replay:重算只要 128 个 token

这是我认为整个仓库里最巧的一处。

问题背景:V4.1 用了滑动窗口注意力(SWA),只有最近若干 token 的 KV 会被长期保留。缓存命中时,block KV cache 有了,但 SWA 那部分的 KV 可能缺。精确恢复要把最后 2560 个 token 重跑 20 层 decoder,成本很高。

DeepInfer 的做法:只重算最近的 128 个 token,并且把注意力窗口截断在重放段内——位置 i 的 query 只跟 max(s, i-127) 到 i 的 key 算。这样得到的 SWA KV 是近似值,但计算量变成常数,跟上下文长度、层数都无关。

效果(Q&A 原文):decoder 侧这么干之后,prefill 总计算量几乎减半。

这个取舍很值得学:用一点点精度换掉一个 O(n) 的开销。类似你用近似算法替换精确 Top-K,或者用 HyperLogLog 换精确去重。

命中策略也很清楚,一句话:「命中长度由 block KV cache 决定,最后一块要不要重算由 SWA KV cache 决定」。YOCO 截断下,长度 L 的输入最大命中 L-134(128 窗口 + 6 个投机回溯位)。

③ DSpark:投机解码,但验证长度是变的

投机解码(speculative decoding)的基本玩法是小模型先猜 5 个 token,大模型一次验证,猜对的就白赚。

问题是:每个 draft token 的接受率不一样。第 5 个 token 大概率被拒,你还是在为它付验证算力。固定验证 5 个,等于固定浪费。

DeepInfer 的 DSpark 加了个 Confidence Head,预测每个 draft token 的接受率,然后按边际收益排序,只验证最划算的前缀子集。

实测数据(EP64/DP64):

并发 (seqs/rank)固定验证 5 个的吞吐变长验证的吞吐增益
164891 tok/s4816 tok/s-1.5%
3289649111+1.6%
60(生产工作点)1373014466+5.4%
961681718660+11.0%

注意低并发时是负收益——省下的验证算力抵不过额外调度开销。这类优化从来不是无条件生效的,生产工作点在哪,优化就在哪生效。

④ 异步调度:让 GPU 别等 CPU

decode 每一步是:schedule(N) → forward(N) → sample(N) → post-process(N)。

其中 schedule 和 post-process 是纯 CPU 逻辑,而且 post-process(N) 之前必须做一次 GPU→CPU 同步拿 sample(N) 的结果。于是每步都有那么几毫秒,GPU 在空转等 CPU。

解法跟后端里的流水线重叠完全同构:GPU 跑 forward(N) 的时候,CPU 并行去跑 schedule(N+1) 和 post-process(N-1)。Q&A 说参照了 SGLang 和 NanoFlow 的做法。

这个思路你天天在用:日志异步刷盘、双缓冲、预取下一条。

⑤ batch-invariant:同一句话,不管你旁边是谁,结果都一样

这个特性很多人会跳过,但对做评测、做 RL、做回归测试的人是刚需。

连续批处理(continuous batching)下,你的请求会和别人的请求拼成同一个 batch。不同 batch 组成会导致算子切分方式、归约顺序不同 → 浮点误差 → 同样输入不同输出。你在本地测出一个答案,上线后用户拿到另一个答案,还没法复现。

DEEPINFER_BATCH_INVARIANT=1 就是让结果跟 batch 组成无关。再叠加确定性采样:

export DEEPINFER_BATCH_INVARIANT=1
export DEEPINFER_ENABLE_MTP_VARLEN=0
# 代码里给 SamplingParams 传固定的 seq_seed

注意这两个开关都是 NVIDIA only。

⑥ 结构化输出:编译期和运行期分离

要模型严格输出某个 JSON Schema / 正则 / EBNF,标准做法是维护一个语法自动机,每步 decode 时算出合法 token 集合,生成 mask 压到采样上。

DeepInfer 多做了一件事:把「编译」和「推理」解耦。编译在专用的 SO 机器上异步做,请求进引擎之前编译结果已经取回来了。理由很简单——编译是 CPU 密集的长尾操作,不能让它插在 GPU 的 decode 循环里。

又是一个后端常识:慢操作从热路径上挪走。

2.4 真要跑的话:命令长什么样

环境要求(README 明确):

  • Linux
  • GCC ≥ 13
  • Python 3.12(必须是空环境)
  • CUDA 13(NVIDIA)或 CANN 9.2(昇腾)
# 1. 建一个干净的 Python 3.12 环境,然后:
export DEEPINFER_ACCELERATOR=nvidia   # 昇腾用 ascend,这个变量要一直留着
./build.sh

# 2. 跑一次离线推理(默认会去 HuggingFace 拉 DeepSeek-V4.1-Flash-1017)
python examples/offline_inference.py --ep 8

# 3. 换并行方式
python examples/offline_inference.py --ep 2 --dp 8
python examples/offline_inference.py --ep 2 --cp 8

# 4. 跑 benchmark(单机要先设好拓扑)
export WORLD_SIZE=1 RANK=0
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
python examples/benchmark.py --decode_num_requests 1        # 低延迟:单请求解码
python examples/benchmark.py --cp 8 --prefill_batch_size 65536
python examples/benchmark.py --context_length 131072 --yoco_clip   # 128k 长上下文

想从本地加载权重,别让它联网:

export DEEPINFER_HF_MODEL_DIR=/path/to/DeepSeek-V4.1-Flash-1017

常用的开关(都是环境变量,没有配置文件):

变量作用默认
DEEPINFER_ENABLE_MTP_VARLEN开启 DSpark 变长验证关
DEEPINFER_USE_NEXTN_DECODEdraft 最大长度,0 = 关掉投机解码5
DEEPINFER_YOCO_PREFILL_TRUNCATION_MODE设为 compact 开启 SWA 有界重放-
DEEPINFER_YOCO_PREFILL_KEEP_TOKENS保留的 suffix token 数140
DEEPINFER_BATCH_INVARIANTbatch 无关 + 确定性(NVIDIA only)关
DEEPINFER_OVERRIDE_NUM_EXTRA_EXPERTSEPLB 冗余专家数下限32

看 benchmark 输出时注意三个指标都是 per GPU 的:prefill 吞吐(tok/s/GPU)、decode 的 avg_SPS(steps/s)、decode 吞吐(tok/s/GPU,按投机解码全接受口径算,会比实际高)。

2.5 现实门槛:先看完这一节再决定要不要 clone

① 它只支持一个模型。

README 写得很清楚:Supported models: DeepSeek-V4.1-Flash。就这一个。

这跟 vLLM 完全是两类东西。DeepInfer 的 README 自己也说了,它最初主要基于 vLLM 构建,借鉴了 SGLang 和 TensorRT-LLM —— 但它被特化到了单一模型、单一硬件组合上,为的是把每一滴性能榨出来。通用性换性能,这是明确的取舍。

所以别指望拿它跑 Qwen 或者 Llama。

② 硬件门槛卡死了 99% 的人。

  • NVIDIA:SM100(Blackwell 那一代),不是 A100/H100
  • 昇腾:950
  • Benchmark 用的配置:prefill 4 张 SM100,decode 64 张 SM100

③ 最小「高效」部署单元是 128 张卡。

Q&A 原话:1P + 1D 技术上能跑起来,但 decode 推荐 EP64 以上,所以最小高效单元是 64 卡 prefill + 64 卡 decode,P:D 大约 1:1。

④ 有几块关键拼图没开源。

README 里带了一句 "Required for a complete inference service (not included in this release)":

  • KV cache 系统
  • VE/Prefill/Decode 的分离调度
  • DP 副本之间的负载均衡

Q&A 补充了原因:这几块跟内部基础设施耦合太紧。所以 DeepInfer 给你的是「一个离线推理引擎」,不是一个「能直接对外开张的服务」。 examples/offline_inference.py 是脚本,不是 server——仓库里根本没有 HTTP 层。

这也正好解释了为什么 recipe 必须单独存在于另一个仓库。

⑤ 开源动机里有一组数字值得看(Q&A 原文,我不做解读,你自己判断):

单个集群目前每天服务 155 万亿 token,整体收入 $38 / GPU-hour(谷时定价)。
另:FlashMLA 开源版缺一处手调 SASS 优化,decode kernel 慢 5~10%;整体端到端差距最多 5%。

意思是:开源版和内部版差距不大(≤5%),这是它敢开源的前提。同时它也告诉你,剩下那 5% 就是「工程细节 + 硬件特调」的护城河。


3. deepseek-recipe:把三种 API 方言翻译成一种

3.1 它要解的问题,比看起来大

现在的局面是:客户端只认三种协议,而每个推理框架各写一套转换逻辑。

OpenAI Chat Completions  +
OpenAI Responses         +--→  ???  --→ 各家自己的 chat template / tokenizer
Anthropic Messages       +

每家都自己拼 chat template、自己解析 tool call、自己处理流式增量。结果是行为各不相同:

  • 同一句 prompt,在 A 家部署和在 B 家部署,模型看到的 token 序列不一样
  • 工具调用的流式分片规则不一样,SDK 一会儿能解析一会儿不能
  • 用户在消息里手打一个 <|end▁of▁sentence|>,有的实现会当成真的结束符

deepseek-recipe 的定位就是把这一层抽出来,做成一个可复用的库,并且明确承诺:

「其处理逻辑与 DeepSeek 官方 API 完全一致,因此第三方部署可以获得与官方一致的 API 行为。」

这是它最有分量的卖点 —— 不是「能跑」,是「跟官方一模一样」。

3.2 它的流水线

HTTP body (三种方言之一)
   |  ChatCompletionRequest / ResponseRequest / MessageRequest
   v  .convert(ConversionOptions)
Conversation            ← 内部统一表示:消息、图片、工具、生成参数
   |  Encoding::render_conversation()
RenderedPrompt {
    prompt: String,          // 渲染好的模板文本
    special_ranges: [...],   // 哪些字节区间是「真正的控制 token」
    image_sources: [...],
}
   |  Encoding::encode()  (附加 Tokenizer 后)
Vec<u32>  token_ids
   |  --→ 你的推理后端 --→  token 流
   v
StreamProcessor::push(InferenceChunk)
   |
   v
SSE chunk (回到客户端想要的那种方言)

两个细节值得展开。

入门方式(Python,真的能跑):

python3 -m pip install deepseek-recipe
from deepseek_recipe import ChatCompletionRequest, ConversionOptions, DeepseekV41Encoding

request = ChatCompletionRequest({
    "model": "DeepSeek-V4.1-Flash",
    "messages": [{"role": "user", "content": "Hello"}],
})
converted = request.convert(ConversionOptions())
rendered = DeepseekV41Encoding().render_conversation(converted.conversation)
print(rendered.prompt)      # ← 你能直接看到模型真正吃到的文本

这一行 print 是理解 chat template 的最快路径。 你想知道 tool call 是怎么拼进 prompt 的、thinking 模式多出了哪些 token,看一眼就明白了。

加 tokenizer,看模型真正吃到的 token:

from deepseek_recipe import Tokenizer

tokenizer = Tokenizer.from_file("static/tokenizers/v41/tokenizer.json")
encoding = DeepseekV41Encoding().with_tokenizer(tokenizer)
print(encoding.encode(converted.conversation))   # [151644, 872, 198, ...]

接流式:

from deepseek_recipe import (
    InferenceChunk, InferenceFinishReason, StreamProcessor, PromptUsage,
)

processor = StreamProcessor(
    ChatCompletionRequest.chunk_generator(converted, "id-1", "DeepSeek-V4.1-Flash"),
    converted.parsing_options,
    tokenizer,                      # 可选:后端吐 token ID 而不是文本
)

chunks = []
for c in [
    InferenceChunk.ready(system_fingerprint="fp-1", prompt_usage=PromptUsage(prompt_tokens=4)),
    InferenceChunk.text("Hello!", token_count=2),
    InferenceChunk.finish(InferenceFinishReason.Stop),
]:
    chunks.extend(processor.push(c))
chunks.extend(processor.finish())

for c in chunks:
    print(c.to_json())
processor.close()

3.3 三个「真功夫」

这三个地方,是你自己写协议转换一定会写错、而且很难发现写错的地方。

① 特殊 token 用「区间映射」,不用字符串匹配

标准的偷懒做法是:把模板拼成一个字符串,然后交给 tokenizer 去分词,指望它把 <|User|> 识别成特殊 token。

这个做法有两个坑:

  1. 用户可以注入。用户在消息里手打 <|User|>,他就伪造了一个对话轮次边界,模型被劫持。
  2. 分词器不一定认得。特殊 token 必须在 tokenizer 的 added vocabulary 里精确拼写,稍有偏差就退化成普通字符序列。

recipe 的做法:渲染时就把「哪些区间是控制 token」记下来(special_ranges),分词时这些区间直接映射成 fixed token ID,区间之间的普通文本才走词表编码。

结果很干净:

  • 用户写的 <|User|> 落在普通文本区间 → 编码成普通 token 序列 → 模型只当它是几个字符
  • 模板插入的控制 token → 直接给 ID → 一定是控制语义

这是白名单,不是黑名单。 别在文本里搜关键字过滤,那是堵不住的。

② 分段并行分词

关键洞察:BPE 分词器在特殊 token 边界处天然可以断开。既然渲染时已经知道所有特殊 token 的位置,那些中间的普通文本段就可以并行编码,最后按序拼接。

收益在长上下文:一个 128k 的 prompt,串行分词是实打实的 CPU 时间,会直接加到 TTFT 上。分段并行把这个常数压下去。

顺手还消掉了一个隐患:不用再在普通文本里跑特殊 token 匹配逻辑。

③ 流式状态机:跨 chunk 的半截东西

推理后端是按 token 粒度吐的,于是你会遇到:

  • 一个 UTF-8 汉字被拆成 3 个 chunk(前两个是残缺字节)
  • 被拆成 <、/thi、nk>
  • 工具调用参数 JSON 的 {"city": "北 只到了半个字符串

recipe 的处理:

  • 不完整 UTF-8:先缓冲 token,补齐再交给解析器
  • 标记匹配:增量 KMP,chunk 在标记中间断掉就保留匹配进度,下个 chunk 接着认
  • 已经确定的内容立刻吐出去,不重扫前文
  • 工具参数:工具名一解析完就返回;参数按 DSML 边界直接生成 JSON 片段,字符串逐段转义。不构造完整参数对象,所以参数再长,首片段也不用等全部生成完

最后一条尤其重要。很多实现在这里等「参数全部生成完 → parse 成 dict → dump 成 JSON 再发」,导致工具调用的首字节延迟等于整个参数生成时间。recipe 是边生成边发。

3.4 更省事的用法:当一台「协议层 mock server」

仓库里有个 server-py 示例,用 mock 推理,不需要 GPU、不需要权重、不需要 API key:

python3 -m venv .venv && . .venv/bin/activate
pip install ./deepseek-recipe-python -r server-py/requirements.txt
python3 -m uvicorn main:app --app-dir server-py --host 127.0.0.1 --port 7777

起来之后三个端点都在:

POST /v1/chat/completions
POST /v1/responses
POST /v1/messages

stream=false 返 JSON,stream=true 返 SSE,回答固定是 Hello world!。

后端视角的用法:你在写 AI 网关 / SDK / 前端联调的时候,最烦的是「协议细节不对但没人报错」。用这个 mock server,你可以先把你自己的客户端、SSE 解析、重试逻辑、超时逻辑全部对齐,再去接真模型。

换成真后端只要改一个回调:

from main import create_app

app = create_app(infer=my_infer)   # my_infer 返回 AsyncIterator[InferenceChunk]

注意一个坑:server-py 的 stream processor 没挂 tokenizer,所以你的 infer 必须吐 text chunk,吐 tokens chunk 会直接让响应失败。而且这种情况下 usage 里的 reasoning_tokens 不会统计。

3.5 边界:它明确不管什么

README 一句话讲完了:「调用方必须提供模型推理、工具执行和 HTTP 传输。」

翻成 checklist:

它管它不管
三种请求格式 → 统一 ConversationHTTP 服务 / 路由 / 中间件
Conversation → prompt / token_ids模型推理
生成参数归一(含 max_tokens 默认值)工具的实际执行
流式 token → 协议 SSE鉴权、限流、计费
reasoning / tool call / JSON / stop 解析KV cache、调度、GPU
结构化输出的约束选择概率计算(logprob 由后端给)

还有一个容易忽略的坑:max_tokens 的默认值是按模式分的。

场景默认
非思考模式8192
思考模式65536
思考模式 + reasoning_effort=max131072

而且它不会无脑用这个值,会用 resolve_max_tokens(prompt_tokens, context_limit) 检查:请求没指定就取默认,然后压到 prompt 之后剩余的上下文以内;如果 prompt + max_tokens 超了上下文,直接返回错误。

你如果自己算 max_tokens,很容易在这里和官方 API 行为不一致——这正是 recipe 存在的意义。


4. 拼起来:一个完整的自建 AI 网关

客户端 SDK(OpenAI / Anthropic)
   |
   v
网关(鉴权 · 限流 · 计费)
   |
   v
recipe(协议转换 + 分词)
   |
   v
调度层(KV 路由 · 负载均衡 · 扩缩容)
   |
   v
DeepInfer(EPD 分离推理)
   |
   v
recipe StreamProcessor(流式组装)
   |
   v
网关 → 客户端

三层各自的伸缩性完全不同,这也回答了「为什么要拆」:

层伸缩维度扩容动作成本
recipe无状态,QPS加 CPU 机器,秒级便宜
调度层有状态(缓存位置),请求数要配合 KV cache 位置中等
DeepInfer有状态(权重+KV),GPU 数加机器、重新定 EP size,分钟级重启极贵

Q&A 里提到:容错不是靠缩短单引擎重启时间(重启是分钟级),而是靠配额冗余 + 请求级重路由。配额利用率超过 0.8 就禁止缩容。这话说的是:GPU 集群的扩缩容不是 K8s 那种秒级弹性,架构上必须假设「机器随时会走」。


5. 你该不该碰

该不该用 DeepInfer

你的情况结论
做业务后端,调 API 或跑开源模型不用。看它的设计取思路就行
做私有化部署,客户给的是 H100/A100用不了。要 SM100 或昇腾 950
做私有化部署,客户有昇腾 950 集群值得评估,但要补 KV cache + 调度层,工作量不小
做大模型平台 / 推理性能优化必读。这是目前公开的、最完整的一份「超大规模推理系统」实现
只想省钱调 API别折腾,$38/GPU-hour 那套规模效应你复制不了

该不该用 deepseek-recipe

你的情况结论
在做 AI 网关 / 多协议兼容层强烈推荐。这是它的主场景
自建推理服务,想和官方 API 行为一致推荐。省掉 chat template 对齐的无底洞
写 SDK / 客户端,需要稳定的协议行为推荐。可以拿 mock server 当靶子
只想调官方 API不需要
后端是 Go / Java目前只有 Rust 和 Python 绑定,要么起个 sidecar,要么自己重写

三条可以直接偷的设计

  1. 把「协议转换」从「计算」里剥出来,做成无状态服务。 recipe 独立成库不是洁癖——协议变得越来越快(Chat Completions → Responses → 各家自定义),而 GPU 集群重启是分钟级。别让兼容性改动去碰最贵的那层。
  2. 缓存外置,消灭「缓存亲和 vs 负载均衡」的矛盾。 KV cache 落 3FS,跟 session 挪到 Redis 是同一个解法。任何「本地缓存 + 会话粘滞 + 负载均衡」三方打架的场景,都可以想想能不能把缓存抽出来。
  3. CPU 逻辑和 GPU 计算重叠,别让贵的等便宜的。 异步调度的收益来自「post-process 和 schedule 期间 GPU 在空转」。你的系统里,凡是「贵的资源在等便宜的流程」,都值得画一次时序图看看空转有多少。

6. 几个容易被误读的点

  • 「DeepInfer 是国产 vLLM」——不对。 它建在 vLLM 之上,但只支持一个模型、两种硬件。它是「特化到极致」,不是「通用替代」。
  • 「$38/GPU-hour 是成本」——不对。 原文是 revenue(收入),而且是谷时定价。别拿它算成本模型。
  • 「recipe 是 DeepInfer 的附属品」——不对。 recipe 仓库 9 月就存在了,10 月 19 日发的 0.2.0,跟 DeepInfer 是并列开源的两个东西。它也能接 vLLM / SGLang / 你自己的玩具后端。
  • 「开源了就能一键起服务」——不对。 KV cache 系统、分离调度、DP 负载均衡都没开源,DeepInfer 也没有 HTTP 层。
  • 「许可证一样」——不一样。 DeepInfer 是 Apache 2.0,recipe 是 MIT。商用都友好,但 Apache 2.0 多了一条专利授权条款。

参考资料

  • DeepInfer: (Apache 2.0)
  • DeepInfer 的部署 Q&A(本文数字主要来源): doc/inference_deployment_qa.md
  • DeepInfer 示例: examples/README.md(offline / benchmark / sweep)
  • deepseek-recipe: (MIT)
  • recipe 中文文档: README.zh.md、docs/streaming.zh.md、docs/tokenizer.zh.md
  • recipe mock 服务示例: server-py/README.md
  • PyPI: pip install deepseek-recipe(Python 3.10+)
  • crates.io: deepseek-recipe@0.2、deepseek-recipe-encoding@0.2、deepseek-recipe-core 等 6 个包

本文所有数字与结论均出自上述仓库的 README 与 Q&A 文档,未做外推。凡是「我的判断」都在文中明确标注为类比或推论。