news 2026/9/12 4:35:34

Qwen Code `/stats` 会话生成时序指标(TTFT / Generation Time / TPS)设计与实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qwen Code `/stats` 会话生成时序指标(TTFT / Generation Time / TPS)设计与实现解析

Qwen Code/stats会话生成时序指标(TTFT / Generation Time / TPS)设计与实现解析

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

/stats是 Qwen Code 终端内置的会话统计入口,它同时支持交互式 Session 面板与非交互式文本输出。本文围绕设计文档 2026-07-24-generation-stats.md 展开,讲解 TTFT(首字延迟)、Generation Time(生成耗时)与 TPS(吞吐)三类指标的语义定义、从底层流式生成器到/stats界面的完整数据链路,并结合仓库源码给出可验证的实现依据。读完本文,你将掌握 Qwen Code 中生成时序指标的计算口径、排除规则与兼容性边界,并能在实际使用中正确解读/stats的 Generation Metrics 输出。

背景:为什么需要独立的生成时序指标

Issue #4252 提出一个明确诉求:/stats需要把“生成时序”与两类既有指标区分展示:

  • 会话墙钟时间(session wall time):会话从开始到结束的整体耗时,受用户思考、工具执行等环节影响;
  • 端到端 API 延迟(end-to-end API latency):一次模型请求从发起到完成的总时长,包含网络排队、首字等待与后续生成。

在这项改动之前,底层时序数据其实已经存在,只是没有通向/stats的“最后一公里”:

已有能力位置说明
ttftMs首字时序loggingContentGenerator.ts从请求派发到首个用户可见流式 chunk 的耗时
sampling_ms/output_tokens_per_secondsession-tracing.tsendLLMRequestSpan推导出的生成阶段耗时与吞吐
ApiResponseEvent请求级事件types.ts已携带请求时长、模型、prompt id 与输出 token 数,进入UiTelemetryService

本次改动补上的正是缺失环节:把已有的 TTFT 值接入/stats所使用的“无内容会话指标”(content-free session metrics),让生成时序在交互与非交互两种模式下都能呈现。

指标语义与计算公式

对于每一个成功且带 TTFT 的流式响应,定义三个指标:

  • TTFT(Time To First Token):即已有的ttftMs测量值,表示从请求派发到首个用户可见流式内容出现的时间。源码中ttftMs只在首个包含用户可见内容的 chunk 上捕获(见下文),排除了仅含 role / usageMetadata 的中间 chunk。
  • Generation Time(生成耗时)max(0, duration_ms - ttft_ms),从首个用户可见流式内容出现到流结束。
  • TPS(tokens per second)output_token_count / generation_time_seconds,仅在生成耗时为 0 时不可用。

会话级聚合口径

SessionMetrics.generation(见 uiTelemetry.ts)按需惰性创建(lazily created),包含两类数据:

  1. 最近一次已完成的请求样本last):模型名、TTFT、生成耗时、输出 token 数;
  2. 聚合计数
    • timedRequests:已计时的请求总数;
    • totalTtftMs:所有计时请求的 TTFT 之和;
    • totalGenerationDurationMs:吞吐合格(throughput-eligible)请求的生成耗时之和;
    • totalThroughputOutputTokens:吞吐合格请求的输出 token 之和。

对应的会话级派生指标为:

  • 会话平均 TTFT=totalTtftMs / timedRequests(所有计时请求的算术平均);
  • 会话 TPS=totalThroughputOutputTokens / (totalGenerationDurationMs / 1000)(加权吞吐,而非各请求 TPS 的均值)。

零生成耗时(generationDurationMs === 0)的请求只计入 TTFT 统计,不进入 TPS 计算的两侧(分子分母都不含),以此避免除零并防止短请求被过度加权。这一口径在 uiTelemetry.ts 的聚合逻辑中完整实现:先校验ttft_ms存在、有限且非负,再计算generationDurationMs = Math.max(0, event.duration_ms - event.ttft_ms),仅当generationDurationMs > 0时才累加生成耗时与输出 token。

与端到端延迟、采样耗时的关系

在 session-tracing.ts 的 span 属性推导中,存在同源的sampling_ms = Math.max(0, duration - ttftMs),与本文的 Generation Time 公式一致;当sampling_ms > 0且有输出 token 数时还会写入output_tokens_per_second。值得注意的是,该处代码注释记录了一次 Phase 4a 的缺陷修复:旧公式duration - ttft - setup会重复扣除requestSetupMs(Phase 4b 起开始填充),导致重试请求的采样耗时被错误钳制为 0——这印证了“生成时序 = 总时长 − 首字耗时”这一口径在工程上需要小心维护。

数据流:从流式生成器到/stats

设计文档给出了端到端数据流:

LoggingContentGenerator.loggingStreamWrapper -> ApiResponseEvent(ttft_ms) -> logApiResponse -> UiTelemetryService -> SessionMetrics.generation -> SessionContext -> /stats

第一跳:loggingStreamWrapper捕获 TTFT

LoggingContentGenerator是包裹底层ContentGenerator的装饰器(loggingContentGenerator.ts)。在loggingStreamWrapper的迭代循环中:

// Capture TTFT on the first stream chunk that contains user-visible // content. hasUserVisibleContent skips role-only / usageMetadata-only // chunks, so TTFT reflects "model produced something the operator can // attribute to user-perceived latency." if (ttftMs === undefined && hasUserVisibleContent(response)) { ttftMs = Date.now() - startTime; }

关键实现细节:

  • ttftMs方法内闭包局部变量而非实例字段,因为LoggingContentGenerator被多个并发的generateContentStream调用共享(每个ContentGenerator一个实例),实例字段会造成并发串扰;
  • 判定函数hasUserVisibleContent跳过仅含 role / usageMetadata 的 chunk,保证 TTFT 反映“模型产出可感知内容”的时刻;
  • 流结束后,ttftMs通过safelyLogApiResponse传给ApiResponseEvent构造函数(loggingContentGenerator.ts),事件定义在 types.ts,其中ttft_ms字段注释为 "Time from stream dispatch to first user-visible content."。

第二跳:endLLMRequestSpan的 Span 属性

同一份ttftMs还会在 span 收尾时写入 trace 属性:endLLMRequestSpan设置ttft_mssampling_msoutput_tokens_per_second(session-tracing.ts),供可观测性链路使用。设计文档同时说明,后续的 GenAI 对齐补充了标准化的gen_ai.response.time_to_first_chunk属性(见 loggingContentGenerator.ts 中performance.now() - requestIssuedAtMs的首 chunk 计时),它独立于私有的ttft_ms/stats并不消费该标准属性,ApiResponseEvent.ttft_ms的数据流与“首个用户可见输出”语义保持不变。

第三跳:UiTelemetryService聚合进SessionMetrics

UiTelemetryService收到ApiResponseEvent后执行聚合(uiTelemetry.ts):

if ( event.ttft_ms === undefined || !Number.isFinite(event.ttft_ms) || event.ttft_ms < 0 || ... ) { /* 跳过:无 TTFT 的请求不产生生成样本 */ } const generation = metrics.generation ?? (metrics.generation = createInitialGenerationMetrics()); const generationDurationMs = Math.max(0, event.duration_ms - event.ttft_ms); generation.timedRequests++; generation.totalTtftMs += event.ttft_ms; if (generationDurationMs > 0) { generation.totalGenerationDurationMs += generationDurationMs; generation.totalThroughputOutputTokens += event.output_token_count; } generation.last = { model, ttftMs, generationDurationMs, outputTokens };

GenerationMetricsGenerationTimingSample的类型定义位于 uiTelemetry.ts,是SessionMetrics上的可选字段(generation?: GenerationMetrics)。随后SessionContext的 clone/equality 逻辑会复制并比较该可选对象,确保交互式面板在每个完成计时的请求后都能刷新(对应设计文档 Compatibility 一节的描述)。

排除规则:内部辅助提示词

生成指标会排除内部辅助提示词(internal helper prompts)对应的请求,原因有二:

  1. 这些请求不会写入可恢复的会话记录(resumable transcript);
  2. 若计入,既会让用户困惑,也会造成“实时会话”与“恢复后会话”的数值不一致。

主对话与子代理(subagent)请求保持计入,与既有会话级模型统计口径一致。源码侧,LoggingContentGenerator全程用isInternalPromptId(userPromptId)判别内部请求(loggingContentGenerator.ts),内部请求不采集响应文本,也不产生api_response日志,自然不进入生成样本。

兼容性边界

本次改动刻意保持最小侵入:

  • ApiResponseEvent.ttft_msSessionMetrics.generation均为增量且可选字段,已有记录事件与调用方保持有效;
  • 不新增第二个计时器,不把时序持久化到日/月 token 用量文件,不改变导出内容,也不改动 daemon / Web Shell 的 stats schema;
  • 既有日/月记录继续只含 token 与 API 时长数据,保持 issue-4479-token-usage-stats-coordination.md 中记录的归属边界;
  • 非流式响应、以及流式但从未产出用户可见内容的响应,保持原行为,不创建生成样本。

输出呈现:交互式与非交互式

非交互式文本输出

statsCommand.tsformatGenerationMetrics(statsCommand.ts)按“最新请求 + 会话聚合”两个区块渲染:

Generation Metrics (Latest Request) Model: <model> TTFT: <duration> Generation Time: <duration> Output Tokens: <count> TPS: <x.x> tok/s Requests: <count> Average TTFT: <duration> Session TPS: <x.x> tok/s

其中:

  • 最新请求 TPS:last.outputTokens / (last.generationDurationMs / 1000),生成耗时为 0 时显示
  • 平均 TTFT:totalTtftMs / timedRequests
  • 会话 TPS:totalThroughputOutputTokens / (totalGenerationDurationMs / 1000)

交互式 Session 面板

交互式/stats的 Session 标签页(StatsSessionTab)渲染同一份SessionContext中的SessionMetrics.generation数据,并在每次完成计时的请求后随 context 更新而刷新。新引入的高可见性标签(如 “Generation Time”“Average TTFT”“Session TPS” 等)覆盖了所有内置语言环境(i18n)。

验证与测试

设计文档列出的验证策略在仓库中均有对应测试支撑:

验证目标测试位置
聚合、内部提示词排除、零生成耗时处理、会话隔离与重置uiTelemetry.test.ts、SessionContext.test.tsx
TTFT 到达ApiResponseEvent且非可见流不携带 TTFTloggingContentGenerator.test.ts
非交互输出与交互 Session 标签渲染statsCommand.test.ts、StatsSessionTab.test.tsx
新标签的 i18n 覆盖stats 相关测试中的多语言断言(如'生成耗时(TTFT/TPS)归属于生成指标'

例如 statsCommand.test.ts 验证了“存在流式响应时包含实时生成时序”:构造ttftMs: 250generationDurationMs: 1250、输出 50 token 的样本后,断言输出包含TPS: 40.0 tok/sSession TPS: 40.0 tok/s,与公式50 / 1.25 = 40完全吻合。

小结

/stats的生成时序指标是一套“复用既有底层测量、补齐汇聚链路”的增量设计:TTFT 在LoggingContentGenerator流式包装层捕获,经ApiResponseEvent进入UiTelemetryService,惰性聚合为SessionMetrics.generation,最终由SessionContext驱动交互式 Session 面板与非交互式文本输出。理解max(0, duration_ms - ttft_ms)的生成耗时口径、加权吞吐 = 总输出 token / 总生成耗时的会话 TPS 口径、以及内部提示词排除规则,即可准确解读/stats中每个数字背后的真实含义。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 4:35:18

CSGO饰品交易避坑指南:识别高风险饰品与市场陷阱

1. 项目概述CSGO饰品交易市场就像一个充满机遇与陷阱的丛林。作为在这个领域摸爬滚打多年的老玩家&#xff0c;我见过太多新手因为不了解市场规律而血本无归。今天这篇指南&#xff0c;就是要帮你避开那些看似诱人实则危险的"毒饰品"。饰品搬砖本质上是通过低买高卖赚…

作者头像 李华
网站建设 2026/9/12 4:34:51

Java路径操作:从基础Path到跨平台实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:34:48

Python Flask+Django混合架构开发药品仓库管理系统

1. 项目背景与核心需求药品仓库管理是医疗机构运营中至关重要的环节。传统的手工记录方式效率低下且容易出错&#xff0c;而市面上的通用仓库管理系统往往无法满足诊所特有的药品管理需求。这正是我们选择用Python开发轻量级药品仓库管理系统的原因。Flask和Django作为Python两…

作者头像 李华