<aside>

本文依旧由人类手动撰写

</aside>

01. 背景

厂内一般会基于 SGLang 的某个时间点上的 commit,作为 base,开始开发新的模型的适配,比如 KIMI K3 发布了,我们就会从当时最新的一个社区分支上,拉取到厂内,我们共同开发,比如 Encoder 开发啦,内部链路适配啦,内部指标监控适配啦,乃至后面的极致优化,比如权重的极致优化,算子的融合等等等等。那么我们适配了一段时间,比如 1 个月后,这个模型适配的差不多了,下一个新的模型又来了,此时你有两种选择:1. 就地新开分支新开代码,我们忘掉之前干的哪些活,直接原地适配,重来一遍;2. 将过去的代码中挑选有意义的部分,挑挑拣拣,合并到上游最新的分支。那么本文就是在处理第二条路下的坑。

02. 望山跑死马

初步来想,好像这个也不是个很难的事啊:粗暴一点,直接两条分支 merge,解决完冲突,诶不就好了吗。恭喜你,掉入了第一个坑:冲突不是最可怕的,最可怕的是没冲突自动帮你合并的地方。就拿我最近一周干的活来说吧,说白了就是在 merge 两条分支,带着最新最强的 Claude 的 Fable 模型,一路火花带闪电,很快啊,4 小时之后,他跟我汇报:我干好了!可以验收!

之后发生的故事大家也都了解了,就是——启动不了,直接报错。很好,AI 干的 AI 担当,我们进化 Skill,我们开启 Loop。在艰难跋涉地干了一星期精度 debug 后,终于勉勉强强把代码跑起来了,但是呢,精度还是差点意思:能回答对,但是长上下文下,还是掉点厉害;于是我们只能这样:继续提供干净代码做对照组,来回对比精度,逐步复现问题,最后告诉我:

image.png

如图所示,我是真没招了,自己 merge 还能被注释带偏,说是注释里写了:「为了性能我们做了什么选择」云云,然后 AI 就被带走了,自己重写一个函数入口,试图弥补上下游之间的函数裂缝。

且,暴力 merge 带来的副作用是巨大的:1. 这次合并,带来了 390 K 行新增和 190 K 行删除。差点别给我们内部 git 仓库打崩了。更别提人类来 review 了。

03. 分而治之

Timeline.png

这里我们依旧第一性原理:代码 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 拆分,人类阅读起来就很容易了。

04. 实战分析

下面将按照实际的细节开始叙述。综合考虑厂内目前不断迭代维护的三个主研发分支为例.

4.1 全部监控指标和日志追加

目标:梳理完毕三个研发分支上全部的指标,做到 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 默认关

4.2 可吸收的框架侧优化