<aside>
本文依旧由人类手动撰写
</aside>
厂内一般会基于 SGLang 的某个时间点上的 commit,作为 base,开始开发新的模型的适配,比如 KIMI K3 发布了,我们就会从当时最新的一个社区分支上,拉取到厂内,我们共同开发,比如 Encoder 开发啦,内部链路适配啦,内部指标监控适配啦,乃至后面的极致优化,比如权重的极致优化,算子的融合等等等等。那么我们适配了一段时间,比如 1 个月后,这个模型适配的差不多了,下一个新的模型又来了,此时你有两种选择:1. 就地新开分支新开代码,我们忘掉之前干的哪些活,直接原地适配,重来一遍;2. 将过去的代码中挑选有意义的部分,挑挑拣拣,合并到上游最新的分支。那么本文就是在处理第二条路下的坑。
初步来想,好像这个也不是个很难的事啊:粗暴一点,直接两条分支 merge,解决完冲突,诶不就好了吗。恭喜你,掉入了第一个坑:冲突不是最可怕的,最可怕的是没冲突自动帮你合并的地方。就拿我最近一周干的活来说吧,说白了就是在 merge 两条分支,带着最新最强的 Claude 的 Fable 模型,一路火花带闪电,很快啊,4 小时之后,他跟我汇报:我干好了!可以验收!
之后发生的故事大家也都了解了,就是——启动不了,直接报错。很好,AI 干的 AI 担当,我们进化 Skill,我们开启 Loop。在艰难跋涉地干了一星期精度 debug 后,终于勉勉强强把代码跑起来了,但是呢,精度还是差点意思:能回答对,但是长上下文下,还是掉点厉害;于是我们只能这样:继续提供干净代码做对照组,来回对比精度,逐步复现问题,最后告诉我:

如图所示,我是真没招了,自己 merge 还能被注释带偏,说是注释里写了:「为了性能我们做了什么选择」云云,然后 AI 就被带走了,自己重写一个函数入口,试图弥补上下游之间的函数裂缝。
且,暴力 merge 带来的副作用是巨大的:1. 这次合并,带来了 390 K 行新增和 190 K 行删除。差点别给我们内部 git 仓库打崩了。更别提人类来 review 了。

这里我们依旧第一性原理:代码 merge,我们要的是个啥?如果是一团浆糊无法清晰地拆分,那还不如不合并;我们需要的是:尽量减少冲突的前提下,将我们的代码,吸收掉社区新的代码。那么好,开始思考一下,我们的代码也是社区代码来的,直接划分成两部分:社区的代码,就让社区自己吸收,逐步 rebase,肯定能达到社区当前 main 分支的状态对吧。剩下的就是我们自己的一堆 PR 了;这些 PR 里,以 KIMI K3 吸收 GLM5-Flash 分支为例,有如下表格:
| 分组 | 提交 | 行数 | 说明 |
|---|---|---|---|
| core-link | 5 | 595 | 链路适配:tokenize 接口、health_forward、PD 传输核心 |
| core-metrics | 6 | 1,788 | 监控:日志 schema / Trace+内容检测 / 指标机器与钩子 |
| core-model-opt | 6 | 254 | 通用模型优化:DCP 传输、fp8 KV、TokenSpeed MLA |
| core-bugfix | 23 | 986 | 通用行为修复 |
| feat-cache | 16 | 14,801 | 特性 · AttentionStore/asradix(交付开 AS 才迁) |
| feat-epd-mm | 66 | 24,300 | 特性 · 多模态 EPD(默认不迁移,除非明确要求) |
| feat-dspark | 5 | 2,476 | 特性 · DSpark 投机(含 PPxPDxDSpark 组合提交) |
| feat-vl-kernel | 1 | 55 | 特性 · VL kernel(有 VL 模型才迁) |
| model-K2-fix | 1 | 9 | 交付该模型时才迁移 |
| model-K2.6-opt | 7 | 293 | 交付该模型时才迁移 |
| model-K3+KDA-opt | 1 | 57 | 交付该模型时才迁移 |
| model-K3-fix | 5 | 690 | 交付该模型时才迁移 |
| model-K3-opt | 26 | 14,832 | 交付该模型时才迁移 |
| model-V4-fix | 2 | 18 | 交付该模型时才迁移 |
| absorbed | 34 | 63,836 | 社区已收录的 PR,丢弃 |
| engineering | 17 | 2,768 | 工程/CI,跟镜像与流水线重建 |
可以看到,如果这么拆分的话,复杂度一下就降下来了:只需要迁移前四类 PR 到最新的代码分支即可。也就几千行代码,稍作 PR 拆分,人类阅读起来就很容易了。
下面将按照实际的细节开始叙述。综合考虑厂内目前不断迭代维护的三个主研发分支为例.
目标:梳理完毕三个研发分支上全部的指标,做到 Day0 rebase 时,不重不漏地保证可以全部接入线上监控,并且适配好线上链路的特殊 Token ID 要求,如多模态下图片哈希计算,打印 Req_ID 方便和线上链路日志对齐等。
| 指标 | 含义 | 社区现状 / 迁移要点 |
|---|---|---|
sglang:all_gather_latency_us |
DP 准备阶段 all-gather 耗时(μs) | 无。计时只包住集合通信调用,跳过 all-gather 时报 0 |
sglang:decode_bs_util |
每个 DP worker 的 decode 活跃请求占比 | 无。分母用 max(1, …) 兜零 |
sglang:prefill_chunk_util |
每个 DP worker 的 prefill token 占用比 | 无 |
sglang:idle_batch_ratio |
上一批中空闲 DP worker 的比例 | 无 |
sglang:dp_balance |
DP worker 间负载均衡分数 | 社区有 DPBalanceStats 无此族名,对齐口径后补 |
sglang:num_max_batchs |
最大运行请求数 | 无 |
sglang:generation_time |
上一轮调度迭代墙钟耗时(s) | 无。首 tick 前起点为 0,上报前 clamp 到非负 |
sglang:run_batch_time |
上一轮迭代内前向耗时(s) | 无 |
sglang:iter_token_process_time |
上一轮迭代前向之外的调度耗时(s) | 无 |
sglang:detokenizer_time |
一批 scheduler 输出的 detokenize 耗时 | 无 |
sglang:detokenizer_to_tokenizer_time |
detokenizer→tokenizer IPC 时延 | 无。跨进程量用Wall Clock,不用 perf_counter |
sglang:request_time_to_tokenizer_ready_seconds |
HTTP 收到请求到 tokenize 完成 | 无 |
sglang:request_zmq_time_seconds |
请求在 ZMQ 上的传输耗时 | 无 |
sglang:request_waiting_time |
请求进入前向前的等待时间 | 无。时间戳 helper 写全 NULL/PREFILL/DECODE 分支,默认返回 0.0 |
sglang:request_first_token_forward_time |
入队到首次前向完成 | 无。DECODE 分支漏写会 observe(None) 打崩主循环 |
sglang:prompt_calculation_time |
prompt 全部 chunk 的计算时间 | 无 |
sglang:kvcache_transfer_time |
PD KV cache 传输耗时 | 无。P、D 两侧都要观测,面板按 decode pod 过滤,只挂 P 侧面板为空 |
sglang:prealloc_token_usage |
decode prealloc 队列预分配 token 占用(0-100) | 社区族名 pending_prealloc_token_usage,口径一致后加别名 |
sglang:device_cache_hit_rate |
prefill token 中 device 缓存命中率 | 社区只有总 cache_hit_rate。mostrecent 模式 |
sglang:host_cache_hit_rate |
prefill token 中 host 缓存命中率 | 无。只在开 HiCache 时有数 |
sglang:engine_startup_time |
引擎启动总耗时 | 社区有 startup_time_seconds{phase}。在 init_startup_timing_summary 取真值,K3 版硬编码 0 不要照抄 |
sglang:engine_load_weights_time |
权重加载耗时 | 同上,取加载权重阶段的值 |
sglang:num_mm_requests_total |
多模态请求数 | 社区只有 num_requests_total。构造时 inc(0) 预热 |
sglang:num_aborted_requests_total |
被 abort 的请求数 | 社区已有,缺预热:构造时 inc(0);计数点放在 abort 实际生效处 |
sglang:num_bootstrap_failed_reqs_total |
PD bootstrap 失败的请求数 | 社区已有,缺预热,构造时 inc(0) |
sglang:num_transfer_failed_reqs_total |
PD KV 传输失败的请求数 | 社区已有,缺预热,构造时 inc(0) |
sglang:evicted_tokens_total |
radix cache 驱逐的 token 数 | 社区有定义无预热,补 inc(0) |
sglang:encoder_num_aborted_requests_total |
EPD encoder 侧被 abort 的请求数(预热项) | 社区无。构造时 inc(0) |
sglang:cached_tokens_total |
命中缓存的 token 数,按 cache_source 分 |
社区 cache_source 扁平为 device / host / storage;L3 取值改为 storage_{backend},面板过滤 storage_MooncakeStore |
sglang:func_latency_seconds |
被 @time_func_latency 装饰的函数耗时 |
社区已有,挂接厂内打点位置 |
sglang:mm_receiver_embedding_transfer_time |
prefill receiver 端到端 embedding 传输时间 | 无。只在 EPD 参数下创建 |
sglang:mm_receiver_waiting_requests |
正在等 embedding 的多模态请求数 | 无。EPD 下创建 |
sglang:mm_receiver_failed_requests_total |
等 embedding 失败的多模态请求数 | 无。EPD 下创建 |
sglang:mm_receiver_timeout_requests_total |
等 embedding 超时的多模态请求数 | 无。EPD 下创建 |
sglang:gpu_buffer_pool_used_bytes |
prefill 侧 embedding buffer 池已用字节 | 无。EPD 下创建 |
sglang:gpu_buffer_pool_total_bytes |
prefill 侧 embedding buffer 池总字节 | 无。EPD 下创建 |
sglang:encoder_num_requests_total |
encode 请求数 | 社区近似 encoder_requests_total,在社区 EncoderMetricsCollector 上补厂内族名 |
sglang:encoder_num_failed_requests_total |
encode 失败的请求数 | 无 |
sglang:encoder_cache_hit_requests_total |
encoder cache 命中的请求数,label source∈{workflow, transfer} |
社区按文件计数 encoder_cache_hit_files_total。构造时两种 source 都 inc(0) |
sglang:encoder_cache_total_requests_total |
查询 encoder cache 的请求数,label source |
社区近似 encoder_cache_total_files_total |
sglang:encoder_cache_hit_bytes_total |
命中的 embedding 字节数,label source |
社区按 token 计数 encoder_cache_hit_tokens_total |
sglang:encoder_cache_total_bytes_total |
查询的 embedding 字节数,label source |
社区近似 encoder_cache_total_tokens_total |
sglang:encoder_cache_req_hit_rate |
请求级命中率,label source |
无。transfer 口径的提交同时改了 keep_on_gpu 的 CPU 副本行为,单独评审 |
sglang:encoder_cache_byte_hit_rate |
字节级命中率,label source |
无。同上 |
sglang:encoder_cache_used_bytes |
mm_cache 当前占用字节 | 社区近似 encoder_cache_size_mb。ds 没有,DSv4.1 契约要求,从 kimi 补 |
sglang:encoder_free_tokens |
encode 剩余 token 预算(背压信号) | 无 |
sglang:epd_transfer_requests_total |
/epd/transfer 请求数,label status |
无 |
sglang:epd_transfer_total_seconds |
/epd/transfer 端到端时延 |
社区近似 encoder_transfer_seconds |
sglang:vit_fetch_tasks_count |
每次从 vit-server 拉到的任务数 | 无 |
sglang:vit_fetch_duration_seconds |
从 vit-server 拉任务的时延 | 无 |
sglang:{stage}_run_batch_seconds |
EPD 某阶段一次 run_batch 的执行时间。{stage} 即阶段名:ds 有 mm_loader / mm_process / mm_embed / mooncake_transfer,kimi 另有 mm_decode / video_decode / mm_gpu_process / vit_task |
社区 0 命中。BatchExecutorCollector(stage) 按阶段名生成 6 族,整套迁 |
sglang:{stage}_batch_size |
该阶段每批请求数 | 社区 0 命中,同上 |
sglang:{stage}_batch_num_tokens |
该阶段每次 serve_once 处理的 token 数 | 社区 0 命中,同上 |
sglang:{stage}_queue_wait_seconds |
每个请求在该阶段队列里的等待时间 | 社区只有不分阶段的 encoder_queue_wait_seconds,按阶段名补 |
sglang:{stage}_queue_wait_num_reqs |
该阶段当前排队请求数 | 社区 0 命中。配 SGLANG_ENCODER_PREPROC_WORKERS |
sglang:{stage}_sched_bubble_seconds |
每个请求在该阶段的调度空泡(上一阶段完成到本阶段开始) | 社区 0 命中,同上 |
sglang:num_prefill_prealloc_queue_reqs |
prefill bootstrap 队列请求数(仅 ds) | 社区名 num_prefill_bootstrap_queue_reqs,面板仍用旧名时加 deprecated 别名 |
sglang:per_prompt_len |
每请求 prompt 长度分布(仅 ds) | 社区名 prompt_tokens_histogram,按面板加别名 |
sglang:per_prompt_gen_len |
每请求生成长度分布(仅 ds) | 社区名 generation_tokens_histogram,按面板加别名 |
sglang:num_retractions |
每请求被 retract 的次数(仅 kimi) | 无。只有 K3 面板使用,随 kimi 交付迁 |
sglang:num_running_reqs_offline_batch |
离线批运行请求数(kimi、glm) | 两边恒为 0,契约中列为「结构性不发」,不迁 |
| 日志 schema | 每行日志带 levelname 和组件前缀(DPController / Detokenizer / Tokenizer),线上日志采集按此切分 | 社区无 levelname |
| P 侧 TTFT | PD 分离时在 prefill 侧记录首 token 时延 | 社区对 P 侧跳过 |
| req_id trace | 请求全链路 14 条带 req_id 的日志,与线上链路日志对齐 | 社区无 |
| token id dump | 按开关 dump tokenized / padded token ids,核对线上特殊 token 与多模态图片哈希 | 社区无,ds、kimi 均有 |
| 多模态图片 / 视频 hash ID 对齐 | 每个图片 / 视频项的 hash 取 md5(model_id + url),视频再拼 f"{fps:.2f}";EPD 路径直接用 task_id 的十六进制值;pad 值为 1_000_000 + hash % 2^30 单值填充,线上链路、P/D、EPD 算出同一组 token id |
社区改成身份指纹派生 4 个值轮流填充,token id 对不上;按 fork 改回单值公式,社区指纹只留给缓存键。多模态 pad 改写 input_ids 后要刷新 AS block hash |
| 空闲期归零与心跳 | 无请求时 Gauge 归零,并按心跳周期刷新 | 社区无。开关 SGLANG_IDLE_METRIC_HEARTBEAT 默认关 |