Prhub

#43965 [Rust Frontend] Support continuous_usage_stats stream option

原始 PR 作者 ricky-chaoju 合并时间 2026-06-12 01:51 文件变更 9 提交数 6 评论 8 代码增减 +335 / -51

执行摘要

为 Rust 前端新增 continuous_usage_stats 流式选项

Rust 前端已经支持 stream_options.include_usage 但缺少 continuous_usage_stats 支持,导致无法在流式响应中连续返回累计用量,与 Python vLLM 行为不符。最初有 CLI 默认选项的尝试,但 reviewer 建议先只支持 per-request 选项以降低复杂性。

该 PR 清晰实现了所需功能,设计决策合理(去除 CLI 默认值、使用宏简化),测试覆盖充分。推荐合并,可作为 Rust 前端后续流式选项实现的参考模式。

讨论亮点
  • CLI 默认值 vs per-request 选项:BugenZhao 认为应先支持 per-request 选项,CLI 默认值并非必需,因为使用场景有限。作者据此移除 CLI 相关代码,只保留 per-request 处理。
  • 流式用法附加的抽象方式:BugenZhao 建议将每次 yield 前的 usage 附加逻辑抽象为 stream adaptor 或宏,以避免重复代码。最终采用 yield_chunk! 宏实现,既简洁又避免了引入复杂抽象。

实现拆解

  1. rust/src/server/src/routes/openai/utils/usage.rs 中新增 ContinuousUsage 结构体,提供 set_prompt_tokensadd_output_tokensset_final_countsto_usage 方法,用于在流式响应过程中累计并输出 token 用量。
  2. completions/convert.rschat_completions/convert.rsResponseOptions 中加入 include_continuous_usage 字段,在 prepare_completion_requestprepare_chat_request 函数中根据请求中的 continuous_usage_statsinclude_usage 同时为真时设置该字段。
  3. completions.rschat_completions.rs 的流式处理函数 completion_chunk_streamchat_completion_chunk_stream 中引入 ContinuousUsage 实例,在收到 Start 事件时记录 prompt tokens,在 TextDelta/BlockDelta 事件时累加 output tokens,并通过 yield_chunk! 宏在每个分块中附加 usage 字段(如果 include_continuous_usage 为真)。
  4. 移除之前 validate.rs 中对 continuous_usage_stats 的拒绝检查,确保该选项可以被正确传递。
  5. tests.rs 中添加两个集成测试(stream_continuous_usage_stats_adds_usage_to_chat_chunkscompletions_stream_continuous_usage_stats_adds_usage_to_chunks),验证每个分块均包含 usage 且最终用法块正确;同时在 completions/convert.rschat_completions/convert.rs 的模块测试中添加单元测试验证选项映射和门控逻辑(连续用法仅在 include_usage=true 时启用)。
文件 模块 状态 重要度
rust/src/server/src/routes/tests.rs 集成测试 modified 8.16
rust/src/server/src/routes/openai/completions/convert.rs 请求转换 modified 7.24
rust/src/server/src/routes/openai/chat_completions/convert.rs 请求转换 modified 7.21
rust/src/server/src/routes/openai/chat_completions.rs 流式端点 modified 7.02
rust/src/server/src/routes/openai/utils/usage.rs 工具结构 added 6.85
rust/src/server/src/routes/openai/completions.rs 流式端点 modified 6.49

关键符号

ContinuousUsage::set_prompt_tokens ContinuousUsage::add_output_tokens ContinuousUsage::set_final_counts ContinuousUsage::to_usage prepare_completion_request (added include_continuous_usage) prepare_chat_request (added include_continuous_usage) stream_continuous_usage_stats_adds_usage_to_chat_chunks (test) completions_stream_continuous_usage_stats_adds_usage_to_chunks (test)

关键源码片段

rust/src/server/src/routes/openai/chat_completions.rs core-logic

流式响应生成核心文件,引入 `ContinuousUsage` 和 `yield_chunk!` 宏,在每个流式分块中附加用法数据,是功能实现的关键。

use crate::routes::openai::utils::usage::ContinuousUsage;// 在 chat_completion_chunk_stream 函数中:
let mut continuous_usage = ContinuousUsage::default();// 宏:在 yield 前根据 include_continuous_usage 追加 usage
macro_rules! yield_chunk {
    ($chunk:expr) => {{
        let mut chunk = $chunk;
        if include_continuous_usage {
            chunk.usage = Some(continuous_usage.to_usage());
        }
        y.yield_ok(chunk).await;
    }};
}// 处理 Start 事件:记录 prompt token,yield 第一块
Ok(ChatEvent::Start { prompt_token_ids, .. }) => {
    continuous_usage.set_prompt_tokens(prompt_token_ids.len());
    let mut chunk = start_chunk(&request_id, &response_model, created);
    if return_token_ids {
        chunk.prompt_token_ids = Some(prompt_token_ids.to_vec());
    }
    yield_chunk!(chunk); // 使用宏,可能附加连续用法
    // 当 echo=true 时,发射最后助手消息内容作为 delta 块
    if let Some(echo_text) = &echo {
        yield_chunk!(block_delta_chunk(
            &request_id,
            &response_model,
            created,
            AssistantBlockKind::Text,
            echo_text.clone(),
        ));
    }
}// 处理 BlockDelta 事件:累加 output token 后 yield
Ok(ChatEvent::BlockDelta { kind, delta, .. }) => {
    // ... 构造 chunk ...
    let delta_token_count = /* 计算 delta token 数 */;
    continuous_usage.add_output_tokens(delta_token_count);
    yield_chunk!(chunk);
}
rust/src/server/src/routes/openai/utils/usage.rs data-structure

新增的 `ContinuousUsage` 结构体,是连续用法统计的核心工具,提供累计计数和转换方法。

// 累计 prompt 与 output token 计数,供流式分块使用
#[derive(Debug, Clone, Default)]
pub(crate) struct ContinuousUsage {
    prompt_tokens: usize,
    output_tokens: usize,
}impl ContinuousUsage {
    /// 记录 stream 开始时报告的 prompt token 计数
    pub(crate) fn set_prompt_tokens(&mut self, prompt_tokens: usize) {
        self.prompt_tokens = prompt_tokens;
    }    /// 累加新解码的 output token 到运行 completion 计数
    pub(crate) fn add_output_tokens(&mut self, output_tokens: usize) {
        // 使用 saturating_add 防止溢出
        self.output_tokens = self.output_tokens.saturating_add(output_tokens);
    }    /// 用生成结束时报告的最终计数替换运行计数
    pub(crate) fn set_final_counts(&mut self, prompt_tokens: usize, output_tokens: usize) {
        self.prompt_tokens = prompt_tokens;
        self.output_tokens = output_tokens;
    }    /// 构建流式用法快照,不含 prompt cache 细节
    pub(crate) fn to_usage(&self) -> Usage {
        Usage::from_counts(self.prompt_tokens, self.output_tokens, None)
    }
}

评论区精华

CLI 默认值 vs per-request 选项 设计

BugenZhao 认为应先支持 per-request 选项,CLI 默认值并非必需,因为使用场景有限。

结论:作者移除 CLI 相关代码,只保留 per-request 处理。 · 已解决

流式用法附加的抽象方式 设计

BugenZhao 建议将每次 yield 前的 usage 附加逻辑抽象为 stream adaptor 或宏,以避免重复代码。

结论:最终采用 `yield_chunk!` 宏实现,既简洁又避免了引入复杂抽象。 · 已解决

风险与影响

  • 流式响应路径核心变更chat_completion_chunk_streamcompletion_chunk_stream 被修改,可能影响非 continuous_usage_stats 请求的响应格式。测试已覆盖常规情况,风险可控。
  • ContinuousUsage 仅含累计计数:当前结构体不包含 prompt cache 细节(如 cached_tokens),若未来需要此类信息,需扩展结构。
  • saturating_add 保护:使用 saturating_add 防止溢出,但极端 token 数下累计计数可能不精确,实际场景不太可能溢出。
  • 用户影响:使用 Rust 前端的用户现在可以设置 stream_options.continuous_usage_stats: true,获得与 Python vLLM 一致的连续用法统计。
  • 系统影响:每个流式分块多一次 to_usage 调用和字段赋值,但开销可忽略。
  • 团队影响:代码结构清晰,ContinuousUsage 可复用,宏减少了样板代码,维护性良好。
流式路径核心变更 ContinuousUsage 仅含累计计数 测试覆盖增强

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论