资源 / 技术指南

把一次请求的时间拆开。

路由打分、完整决策、首 token 与完整回答不是同一个指标。先确定计时边界,才能知道该优化哪一层。

每个指标测到了什么

不要把最小组件的测量值放到整个请求旁边。

encode + scores
编码路由文本并计算候选得分,是微基准;不包含完整的模式解析、成本策略和供应商调用。
Router.decide
完整路由调用还包含模式选择、会话文本构造、描述向量组合、成本估计与候选排序。它选择模型,但不执行模型生成。
首 token 与完整响应
在客户端测量请求发出到首个有效内容、以及请求完成的时间;包含网络和上游服务行为,与路由器微基准分别报告。

建立可比较的测量

记录环境与负载分布,避免短输入和预热状态掩盖实际体验。

  1. 固定环境

    记录检查点、模型池、编码器、精度和实际加载设备。不能只记录传入的 device 参数,应核对模型最终所在设备。

  2. 分开冷启动与热态

    单列进程首次调用,并在说明预热方式后统计热态。检查点加载、编码器初始化与请求决策也应分开计时。

  3. 覆盖实际输入与负载

    测试代表性的会话长度与候选池,保留分位数和极值。组件测试之后,再增加并发、到达模式与客户端超时的服务测试。

从症状回到测量层

先增加能区分原因的观测,再决定改配置或改代码。

长输入明显变慢
对照编码时间与完整 decide 时间,检查路由文本长度和截断策略。缩短路由输入可能改变选模质量,应同时评测。
首个请求慢,后续正常
检查初始化、权重载入、缓存与设备预热。把冷启动单独展示,不要用热态中位数代表首次访问。
组件正常,在线尾延迟高
补测网关排队、上游首 token、重试与网络。控制并发和请求到达模式,保留失败和超时样本。

复跑完整决策计时

在项目环境中使用已存在、与模型池匹配的检查点。下方使用文档中留档的 CPU 路径;脚本不调用供应商。

保留比较条件
归档输出 JSON、命令、软件版本和设备信息。当前公开计时来自 crb-gen1,不能写成 crb-gen2 的重新测量结果。
python3 scripts/bench_decide.py \
  --pool configs/pool.coderouterbench.yaml \
  --checkpoint ~/.enthalpy/checkpoints/crb-gen1 \
  --device cpu --out reports/router-latency.cpu.json

路由延迟

一次选择是一次真实的 prefill,所以它随 prompt 长度增长。下面是原始测量,条件写在表下面,你自己判断它对不对得起一次上游调用。

prompt tokens字符p50(热)p95(热)min–max首次调用(冷)
156071 ms77 ms67 ms – 80 ms180 ms
2561,293264 ms334 ms247 ms – 369 ms251 ms
1,0245,229944 ms998 ms917 ms – 1029 ms963 ms

短 prompt(15 tokens)上是 71 ms,到 1,024 tokens 的截断上限时是 944 ms。所以 ENTHALPY_ROUTER_MAX_TOKENS 是一个延迟旋钮,不只是显存旋钮;路由信号在任务陈述里,而任务陈述靠近 prompt 开头,截断是从左边截的。

冷热那两列必须一起看。进程里的第一次决策要 180 ms,稳态之后才是 71 ms —— 后端在建它的 kernel。把新副本挂到负载均衡后面之前,先打一个热身请求。另外,加载权重本身还要 2.7 s,那是进程启动的一次性成本。

测量条件:backbone Qwen/Qwen3-0.6B,device cpu,dtype float328 个候选,25 次迭代 + 5 次热身,router_max_tokens 1,024。checkpoint sft-2e9f,pool 3520b40d —— 请注意这不是上面那些准确率数字所用的 checkpoint。两次测量来自两个产物,就不共用一行出处。

4ROUTER / 系统研究

状态、复用与调度:解释延迟从哪里来

KV cache、RadixAttention、LMCache 与分块调度共同决定请求等待的哪一段。下面的模型、图和实验步骤用于拆分关键路径。

NOTE / 03STATE / CAPACITY

KV cache:从张量形状到容量预算

上下文长度是一个随并发增长的状态预算。

自回归解码保存历史 token 的键和值,避免为每一步重新计算这些历史状态。缓存规模取决于层数、KV 头数、头维度、存储精度和保留的 token 数。本页把逻辑张量预算与实际 GPU 分配区分开,并提供可复核的容量计算器。

FIG. 01STATE / CAPACITY
L × H_kv × d_headK + V× bytes × tokens
模型维度 × 保留 token示意图,未表示实测比例或具体引擎布局。

缓存存的是什么

在常规 decoder attention 中,每层为已处理 token 保存 K 与 V。新 token 仍需生成自己的查询并读取相关历史状态,因此有 KV cache 不等于 attention 不再消耗带宽。为容量建模时,应明确保留窗口与各层类型,而不是只引用模型的最大上下文。 [1]

GQA 使用 KV 头数,不使用查询头数

MHA、MQA 与 GQA 的区别之一是查询头共享多少组 K/V。GQA 的容量项应使用 KV 头数 H_kv,而不是查询头数 H_q。这个结构来自模型本身;不能把一个已有 MHA 检查点的头数配置直接改小,就当作无损压缩。 [2]

逻辑字节与实际分配之间还有距离

块取整、缓存元数据、量化 scale、临时 workspace、模型权重与框架预留会改变实际显存。张量并行时还需检查 KV 头的切分或复制,不能总是用卡数直接相除。共享前缀可能减少物理副本,但必须按照真实共享与引用关系计数。 [3]

模型与记号

B_KV = 2 × L × H_kv × d_head × b × Σ Tᵢ

适用于各层具有相同 KV 形状的常规全注意力模型:2 表示 K 和 V;L 为层数,H_kv 为 KV 头数,d_head 为头维度,b 为每元素字节数,Tᵢ 为各序列保留 token 数。忽略共享、分片、块取整与额外开销。

INTERACTIVE / CAPACITY

KV 容量工作台

教学参数,不对应特定模型。计算逻辑 KV 张量总量,不含权重、分片、共享与引擎开销。

KV 容量工作台
逻辑 KV 总量4.00 GiB
每 token · 全部层
128.00 KiB
每序列
1.00 GiB

2 × 32 × 8 × 128 × 2 × 8192 × 4 = 4294967296 bytes

按整数相乘计算字节;KiB = 2¹⁰ bytes,GiB = 2³⁰ bytes。展示保留两位小数。

参数来源:上方可编辑教学输入;计算:本页整数公式。

如何设计实验

  1. 用模型配置填表

    核对层数、KV 头数、head dimension 与实际缓存 dtype;不要把权重量化位宽当作 KV 的存储位宽。

  2. 记录长度分布

    分别记录输入长度、输出长度和同时存活的请求数。按实际保留状态建模,并为长尾请求和增长留出预算。

  3. 用运行时反查

    将逻辑估算与引擎报告的可用块和实际峰值显存对照;记录差额的来源,而不是把全部差额视为浪费。

NOTE / 04PREFIX / REUSE

RadixAttention:复用前缀,不是复用答案

复用的单位是具有一致计算历史的前缀状态。

多轮对话、共享 system prompt 与分支生成会反复计算相同前缀。SGLang 的 RadixAttention 用基数树组织 token 前缀与对应 KV 状态,使匹配、分裂、引用与淘汰成为运行时的一部分。关键是可复用的状态边界,而不是文本看起来相似。

FIG. 01PREFIX / REUSE
共享前缀system / document
query Aquery Bquery C

独立后缀

匹配公共前缀,再计算后缀示意图,未表示实测比例或具体引擎布局。

压缩前缀树如何定位公共部分

树边可以承载一段 token 序列,而不必每个 token 一个节点。新请求沿树匹配已有前缀,在分叉处拆分节点,再为未命中的后缀计算 KV。多个请求引用同一前缀时,缓存管理还要保护正在使用的状态,并在容量压力下选择可淘汰部分。 [1]

命中率应该按 token 和时间解读

请求级命中标志无法表达只复用了短 system prompt 还是长文档。应记录复用 token、跳过的 prefill 工作与 lookup 开销。前缀缓存主要减少可跳过的 prefill;它不会让后续输出 token 的自回归生成消失。高命中率不自动意味着相同幅度的端到端加速。 [2]

兼容性、隔离与调度一起考虑

前缀相同还不够:模型版本、adapter、token 化及位置语义也需要兼容。工程上应明确缓存命名空间与租户边界。把请求集中到命中最多的实例可能提高复用,也可能增加排队;需要将命中收益与等待时间一起评估,而不是仅最大化缓存指标。 [1]

模型与记号

T_cached ≈ T_lookup + T_prefill(suffix | prefix KV) + T_decode

这是拆分成本的概念模型。被复用前缀省去自身重新计算,但后缀仍会访问前缀 KV;排队、传输与调度开销需要在完整请求中另行记录。

如何设计实验

  1. 构造命中与未命中对照

    固定模型与输出设置,分别测试共享前缀、仅语义相似但 token 不同的输入,以及完全不同的输入。

  2. 让缓存承受压力

    预热后加入足以触发淘汰的其他请求,再测复用和恢复;记录引用保护、淘汰和等待情况。

  3. 用业务指标验收

    同时比较 TTFT、逐 token 延迟、有效吞吐与队列等待。对跨租户复用采取明确的隔离与授权规则。

NOTE / 06SCHEDULING / SLO

从 Prefill 到 Decode:调度的实验设计

提升吞吐时,仍要知道谁在等待。

推理系统既处理一次性 prompt 计算,也处理逐步生成。持续批处理、分块 prefill 和 P/D 分离改变请求在这些阶段的相互影响。本笔记以延迟与吞吐的共同约束组织实验,而不把某个调度开关当作普遍最优方案。

FIG. 01SCHEDULING / SLO
提示词计算逐步生成完成 / 释放
APDDD···
B·PDDD··
C··PDDD·
迭代之间让请求进入或退出示意图,未表示实测比例或具体引擎布局。

持续批处理改变调度粒度

Orca 的迭代级调度将作业推进到生成迭代的粒度,使已完成请求离开、待处理请求进入,不必等待静态批次全部结束。它减少了长短请求混合时的部分空转,但实际批量还受 KV 容量、token 预算及执行策略限制。 [1]

长 Prefill 会怎样影响 Decode

较长 prompt 可能占用一次较长的执行时间,影响正在逐 token 生成的请求。Sarathi-Serve 研究分块 prefill 与批处理设计,在吞吐和生成延迟之间控制干扰。块大小不能只看首 token:缩小块可能增加调度开销,也可能改变 GPU 的执行效率。 [2]

P/D 分离把干扰换成传输与容量规划

分开 prefill 与 decode 实例可以独立规划资源,但 KV 必须跨实例到达,负载也必须匹配两侧容量。缓存复用、传输重叠和队列调度决定实际关键路径。若某侧积压,孤立测得的阶段吞吐无法描述用户体验。 [3]

模型与记号

TTFT = T_queue + T_prompt_path + T_first_content

这是客户端边界下的分解记号,不是引擎内部计时字段的通用定义。逐 token 延迟、完整响应时间与达标吞吐应另外报告;明确首事件是否真的包含可见内容。

如何设计实验

  1. 固定工作负载轨迹

    记录请求到达时间、输入输出长度分布、共享前缀、采样设置与超时。不要只用闭环固定并发替代所有线上流量。

  2. 把失败计入结果

    对相同硬件与策略扫描负载,报告延迟分布、错误、拒绝、抢占及队列增长;明确达标吞吐的判定规则。

  3. 分离冷态与稳态

    记录预热、缓存填充、测量区间和结束时未完成请求。不要只选最稳定的片段而隐藏过载与恢复阶段。

机制图为原创示意;公式用于解释或容量建模。论文与官方文档中的结果不等同于 4Router 实测。