Prhub

#47435 [Rust Frontend] Improve scheduler stats logging parity

原始 PR 作者 BugenZhao 合并时间 2026-07-02 22:55 文件变更 6 提交数 5 评论 4 代码增减 +521 / -30

执行摘要

补齐 Rust 前端调度器日志统计对标 Python

来自 PR body:先前 Rust 前端的 stats logger 从未直接观测 scheduler stats,仅靠定期抓取全局 Prometheus registry。但 per-position speculation acceptance 和 CUDAGraph sample buckets 等信息无法用 Prometheus 数据表达。因此引入非 Prometheus 的 per-engine SchedulerLogStatsAccumulator,挂载到全局 registry 以便跨 engine 捕获。此外修复 engine index 始终为 0 的问题。

建议阅读此 PR,尤其是 SchedulerLogStatsAccumulator 的设计:它展示了如何在 Prometheus 之外累积无法直接表达的数据,并通过 drain 模式定期消费。这种模式可推广到其他需要 interval 聚合的统计场景。关于 CUDAGraph 日志暂未输出的问题,团队应在收集反馈后做出取舍。

讨论亮点

Codex 在 rust/src/llm/src/log_stats.rs:302 评论指出:record_scheduler_stats 调用了 observe_cudagraph,但日志 drain 后未读取 cudagraph_counts,导致 CUDAGraph 样本被丢弃,用户看不到对应日志(P2)。作者 BugenZhao 回复:此地已知未完成,因担心打印 Markdown 表格过于冗长,后续根据反馈再考虑添加。

实现拆解

  1. 定义 SchedulerLogStatsAccumulator 及关联类型rust/src/metrics/src/scheduler.rs):新增 SchedulerLogStatsInterval 存储投机解码草稿数、per-position 接受 token 数、以及 CUDAGraph 采样计数(以 CudagraphLogKey 为键的 map)。SchedulerLogStatsAccumulator 使用 Arc<Mutex<SchedulerLogStatsInterval>> 允许并发观测,并提供 observe_spec_decodeobserve_cudagraphdrain 方法。

  2. 扩展记录函数 record_scheduler_statsrust/src/engine-core-client/src/metrics.rs):在原有的 Prometheus counter 更新之后,额外调用 log_stats.observe_spec_decodelog_stats.observe_cudagraph,将原始调度器数据灌入 accumulator。

  3. 扩展 EngineMetricsCounterSnapshotrust/src/llm/src/log_stats.rs):新增 external prefix cache 查询/命中、预占次数、投机解码相关计数器、MFU 计数器以及 scheduler_deferred gauge。CounterSnapshot 同步增加对应字段以计算 delta。

  4. 实现日志计算与格式化函数rust/src/llm/src/log_stats.rs):新增 read_deferred_waitingcache_hit_ratespec_decoding_log_statsmfu_log_statsdrain_scheduler_log_statsformat_position_rates 等函数。在 run_stats_logger 的主循环中定期 drain accumulator,计算衍生指标(平均接受长度、吞吐、位置接受率等)并按 Python 风格输出日志字符串。

  5. 修复 engine index 硬编码并改用真实索引rust/src/engine-core-client/src/client.rsrust/src/llm/src/lib.rs):新增 engine_indices() 方法返回各 engine 的实际 u32 索引;StatsLogger::start 参数从 engine_count: usize 改为 engine_indices: Vec<u32>,确保在混合 DP 下各 engine 的 Prometheus 标签使用正确索引。

配套改动:CudagraphStat 重命名为 CudagraphStatsprotocol/stats.rs),语义更清晰。

文件 模块 状态 重要度
rust/src/llm/src/log_stats.rs 日志统计 modified 8.65
rust/src/metrics/src/scheduler.rs 指标框架 modified 8.65
rust/src/engine-core-client/src/client.rs 客户端层 modified 5.71
rust/src/engine-core-client/src/metrics.rs 指标收集 modified 5.64
rust/src/engine-core-client/src/protocol/stats.rs 协议定义 modified 4.31
rust/src/llm/src/lib.rs 入口 modified 3.83

关键符号

resolve_engine_metrics run_stats_logger read_deferred_waiting cache_hit_rate spec_decoding_log_stats mfu_log_stats drain_scheduler_log_stats format_position_rates CudagraphLogKey SchedulerLogStatsInterval::merge SchedulerLogStatsAccumulator::observe_spec_decode SchedulerLogStatsAccumulator::observe_cudagraph SchedulerLogStatsAccumulator::drain engine_indices record_scheduler_stats

关键源码片段

rust/src/metrics/src/scheduler.rs core-logic

新增 SchedulerLogStatsAccumulator 及相关类型,定义非 Prometheus 累积器基础设施。

/// Internal, non-Prometheus accumulator for periodic text logs that need raw
/// scheduler DTOs.
#[derive(Clone, Default)]
pub struct SchedulerLogStatsAccumulator {
    inner: Arc<Mutex<SchedulerLogStatsInterval>>,
}impl SchedulerLogStatsAccumulator {
    /// Observe spec-decoding fields needed for per-position text-log rates.
    pub fn observe_spec_decode(&self, num_drafts: u64, accepted_tokens_per_pos: &[u64]) {
        // 加锁获取内部 interval,然后累加草稿数和 per-position 接受 token 数
        let mut inner = self.inner.lock().expect("scheduler log stats accumulator poisoned");
        inner.spec_num_drafts += num_drafts;        // 确保 vector 长度足够,逐位置累加
        if inner.spec_accepted_tokens_per_pos.len() < accepted_tokens_per_pos.len() {
            inner.spec_accepted_tokens_per_pos
                .resize(accepted_tokens_per_pos.len(), 0);
        }
        for (position, accepted_tokens) in accepted_tokens_per_pos.iter().copied().enumerate() {
            inner.spec_accepted_tokens_per_pos[position] += accepted_tokens;
        }
    }    /// Observe one CUDA graph runtime sample for the interval table.
    pub fn observe_cudagraph(
        &self,
        num_unpadded_tokens: u64,
        num_padded_tokens: u64,
        num_paddings: u64,
        runtime_mode: &str,
    ) {
        // 构建 CudagraphLogKey,在 map 中计数
        let mut inner = self.inner.lock().expect("scheduler log stats accumulator poisoned");
        let key = CudagraphLogKey {
            num_unpadded_tokens,
            num_padded_tokens,
            num_paddings,
            runtime_mode: runtime_mode.to_string(),
        };
        *inner.cudagraph_counts.entry(key).or_default() += 1;
    }    /// Drain and reset the current text-log interval.
    pub fn drain(&self) -> SchedulerLogStatsInterval {
        // 用 take 替换出当前 interval,重置为空
        let mut inner = self.inner.lock().expect("scheduler log stats accumulator poisoned");
        std::mem::take(&mut *inner)
    }
}
rust/src/engine-core-client/src/client.rs core-logic

新增 engine_indices 方法,修复 engine index 硬编码问题。

/// Return the engine-side indices connected to this client.
pub fn engine_indices(&self) -> Vec<u32> {
    // 从每个 engine 的 connection 中提取 engine_index,确保支持混合 DP 下非连续索引
    self.engines
        .iter()
        .map(|engine| {
            engine
                .engine_id
                .engine_index()
                .expect("engine id must encode as u16")
        })
        .collect()
}

评论区精华

CUDAGraph 样本记录但未在日志输出 设计

Codex 指出 record_scheduler_stats 调用 observe_cudagraph 后,日志 drain 时未读取 cudagraph_counts,导致 CUDAGraph 样本被丢弃。

结论:作者 BugenZhao 确认已知,因担心打印 Markdown 表格过于冗长,暂不输出,待收集更多反馈后再决定。 · addressed

风险与影响

  • 数据丢失风险:CUDAGraph 样本被 accumulator 记录但未在日志中输出(drain_scheduler_log_stats 函数未读取 raw_log_stats.cudagraph_counts),这是 P2 级别的问题,但作者有意暂缓,不会造成程序错误。
  • 并发风险SchedulerLogStatsAccumulator 使用 Mutex 保护,但仅在定期日志循环中 drain(每 10 秒一次),观测频率极低,锁竞争概率可忽略。
  • 兼容性:所有新计数器均为可选字段,不影响已有 Prometheus 指标;StatsLogger::start 签名变更但仅限内部调用,已同步更新调用点。
  • 回归风险:无新增测试文件,但现有单元测试全部通过;engine index 修复可能影响 DP 场景,但之前硬编码 0 本身就是错误的,修复后应正确。
  • 用户:Rust 前端用户现在能够从日志中看到丰富的调度器指标,包括 deferred 请求数、预占次数、外部缓存命中率、投机解码吞吐/接受率、MFU 等,与 Python 版保持一致,便于调试和性能分析。
  • 系统:无运行时影响;内存和 CPU 开销仅限于每 10 秒一次的 accumulator drain 和日志格式化。
  • 团队:建立了可扩展的非 Prometheus accumulator 模式,未来可复用于其他需要复杂聚合的指标。CUDAGraph 日志输出为已知缺失,需后续跟进。
CUDA Graph 样本日志暂未输出 无新增测试覆盖 引擎索引修复可能影响 DP 场景(预期正确)

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论