Prhub

#35125 [Rust Server] Add e2e latency metadata and fix Sarashina import

原始 PR 作者 merrymercy 合并时间 2026-08-18 17:08 文件变更 2 提交数 3 评论 0 代码增减 +253 / -29

执行摘要

Rust /generate 增加 e2e_latency 元数据,修 Sarashina 导入

PR body 明确要求“add Python-compatible meta_info.e2e_latency to terminal Rust /generate responses”,并“preserve per-request latency semantics for unary, batch, cumulative streaming, and incremental streaming”。Rust server 作为 Python 后端的数据面替代,需要在对齐响应结构的同时补齐端到端延迟指标;Sarashina2 Vision 部分则是因为多模态请求类型已从 mm_utils 迁往 schedule_batch,旧导入路径已失效。

值得精读 rust/sglang-server/src/api_server/native_api.rs,重点看 RequestTiming 的设计——把 API 层计时与 scheduler 内部消息解耦,并利用 join_all 在保持顺序的同时并发观察终端输出。这个模式对后续 Rust server 增加 TTFT、ITL 等指标有直接参考价值。建议后续补充针对 e2e_latency 的端到端测试,覆盖 unary、batch、流式终止帧三种形态。

讨论亮点

该 PR 没有任何 review 评论或讨论线程,作者 merrymercy 以 3 个连续 commit 完成变更并自行合并。因此没有可提炼的争议点或已解决/未解决疑虑。

实现拆解

实现分三步完成:

  1. 新增 API 层计时结构:在 rust/sglang-server/src/api_server/native_api.rs 中引入 RequestTiming,记录 created_attime_to_first_tokene2e_latency 三个字段,并提供 observe_first_outputfinishterminal_latencies 方法。generateinto_requests 完成 body 归一化后、prefetch 之前创建计时实例,与 Python 的 APIServerReqTimeStats 起始点对齐,注释中明确这是为避免把 API 专属时间戳塞入 scheduler 消息。

  2. 把计时贯穿 unary 与 batch 两条响应路径generate_singlegenerate_batchdrain_unary 均接收 RequestTimingdrain_unary 在收到首个 FrameDone 时调用 observe_first_output,在 DoneError 时调用 finish,并在正常终态通过 add_e2e_latency 把延迟写入 meta_info。batch unary 从原来的“按序逐个 drain”改为 futures::future::join_all 并发 drain,join_all 保持返回顺序,同时让每个请求的终端输出能被及时观察,保证 e2e_latency 准确。流式路径则通过 generation_event_stream 携带 timing,在终端帧的 terminal_stream_frame_string 中注入延迟字段。

  3. 修复 Sarashina2 多模态导入python/sglang/srt/models/sarashina2_vision.pyMultimodalDataItemMultimodalInputs 的导入从 sglang.srt.managers.mm_utils 改到 sglang.srt.managers.schedule_batch,与类型新归属地保持一致。

测试配套:PR 仅依赖 cargo test(241 passed)与 cargo clippy 验证,没有新增独立测试文件对 e2e_latency 做显式断言,属于本次变更的覆盖缺口。

文件 模块 状态 重要度
rust/sglang-server/src/api_server/native_api.rs API 入口 modified 8.86
python/sglang/srt/models/sarashina2_vision.py 模型层 modified 4.4

关键符号

new observe_first_output finish terminal_latencies generate_single generate_batch drain_unary add_e2e_latency terminal_stream_frame_string timed_receiver

关键源码片段

rust/sglang-server/src/api_server/native_api.rs entrypoint

Rust native API 入口的核心改动文件:新增 RequestTiming 计时结构、贯穿 generate_single/generate_batch/drain_unary 全部响应路径,把 e2e_latency 写入 meta_info,并将 batch unary 改为 join_all 并发 drain。这是该 PR 的技术主体。

/// API 本地单请求计时。
///
/// Python 在首个输出 batch 记录 time-to-first-token,在请求结束时记录
/// end-to-end 延迟。这里保留两个测量值,即使 `/generate` 目前只暴露
/// `e2e_latency`——避免把 API 专属时间戳塞进 scheduler 消息。
#[derive(Clone, Debug)]
struct RequestTiming {
    // TODO: 将请求生命周期计时抽到独立的 tracing/metrics 模块,
    // 并与 Python 的 APIServerReqTimeStats 对齐设计。
    created_at: Instant,
    time_to_first_token: Option<Duration>,
    e2e_latency: Option<Duration>,
}impl RequestTiming {
    fn new() -> Self {
        Self {
            created_at: Instant::now(),
            time_to_first_token: None,
            e2e_latency: None,
        }
    }    // 只在首个输出到达时记录一次 TTFT,后续帧不会覆盖。
    fn observe_first_output(&mut self) {
        self.time_to_first_token
            .get_or_insert_with(|| self.created_at.elapsed());
    }    // 请求结束时记录 e2e,同样只保留第一次值。
    fn finish(&mut self) {
        self.e2e_latency
            .get_or_insert_with(|| self.created_at.elapsed());
    }    // 两个指标都齐了才返回,供终端帧写入 meta_info。
    fn terminal_latencies(&self) -> Option<(Duration, Duration)> {
        Some((self.time_to_first_token?, self.e2e_latency?))
    }
}
// drain_unary 中关键的计时挂载点:
while let Some(item) = rx.recv().await {
    match item {
        EgressItem::Frame(out) => {
            // 首个帧触发 TTFT 观测,与 Python 在首个输出 batch 记录一致。
            timing.observe_first_output();
            acc.fold(&out);
        }
        EgressItem::Done(out) => {
            // 终帧也算首个输出(单 token 请求场景),并结束 e2e 计时。
            timing.observe_first_output();
            timing.finish();
            acc.fold(&out);
            let final_out = acc.into_output();
            // 校验类中止会携带自己的 HTTP 状态码与诊断信息。
            if let Some((code, message)) = final_out
                .finish_reason
                .as_ref()
                .and_then(|f| f.abort_status())
            {
                let status = StatusCode::from_u16(code)
                    .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
                return (status, error_value(code, message), true);
            }
            // 正常终态:把计时写入 meta_info.e2e_latency 后返回。
            let mut value = frame_value(&final_out, rid_str);
            add_e2e_latency(&mut value, &timing);
            return (StatusCode::OK, value, true);
        }
        EgressItem::Error(e) => {
            // 错误路径也结束计时,避免 e2e_latency 悬挂为 None。
            timing.finish();
            let code = e.http_status();
            let status = StatusCode::from_u16(code)
                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
            return (status, error_value(code, e.message()), true);
        }
    }
}
python/sglang/srt/models/sarashina2_vision.py data-contract

修复 Sarashina2Vision 的多模态类型导入:MultimodalDataItem 与 MultimodalInputs 改从 schedule_batch 导入,避免因 mm_utils 中类型迁移导致的导入失败。

# 多模态请求类型已从 mm_utils 迁往 schedule_batch,
# 这里同步更新导入来源,避免 ImportError。
from sglang.srt.managers.mm_utils import (
    MultiModalityDataPaddingPatternMultimodalTokens,
    general_mm_embed_routine,
)
from sglang.srt.managers.schedule_batch import MultimodalDataItem, MultimodalInputs

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 响应契约变化/generate 的终端响应 meta_info 新增 e2e_latency 字段,对严格 schema 校验的客户端是兼容的增量,但依赖字段枚举的客户端可能告警;native_api.rs 是 Rust server 的入口层,属于数据面核心路径。
  2. batch 并发 drain 的资源开销join_all 会同时 poll 所有 receiver,大批量请求(例如数百个)会同时持有多个 drain_unary 任务,虽不阻塞调度,但会在 API 层增加并发 task 和内存占用。
  3. 计时语义对齐风险created_at 在 prefetch 之前开始,因此 e2e_latency 包含媒体下载/文件读取等 I/O 时间,与 Python 起点的对齐依赖注释中的约定,若后续调整 generate 入口顺序可能破坏一致性。
  4. 测试缺口:本次没有新增任何测试文件,cargo test 仅能保证编译与既有行为不回归,无法验证 e2e_latency 数值正确性和四种响应形态下的字段存在性。
  5. Sarashina 导入修复风险低:仅调整 import 来源,但若 schedule_batchmm_utils 之间存在循环依赖,未来重构时需留意该文件的导入稳定性。

影响面集中在 Rust server 数据面:所有 /generate 调用方都会在终端响应的 meta_info 中看到新增的 e2e_latency,与 Python 端行为对齐;batch unary 的并发 drain 对大 batch 场景能缩短整体响应延迟,同时保持输出顺序不变。对 Sarashina2Vision 用户,修复了因类型迁移导致的加载/推理报错。对团队而言,该 PR 进一步加固了 Rust server 与 Python 后端在响应元数据上的兼容性,为后续运维指标采集提供了基础。影响程度中等,主要是入口层契约增强与一个模型文件的导入修正。

Rust 入口层变更 无新增测试文件 batch 并发 drain 资源开销 响应 schema 新增字段

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论