fix(experimental): keep totals cumulative and switch token units to M
This commit is contained in:
1 parent
6df1a2c1e3
commit
8dec878ecf
10 files changed
+36
-41
No files matched your search
@@ -24,8 +24,8 @@
|
||||
en: e001601f2f9779b1
|
||||
zh: 077d3703b8c82774
|
||||
/deepseek-ai-dsh-experimental-client-ui-perf/understand-the-implementation:
|
||||
en: 4d14d791cd6dd206
|
||||
zh: e7d54702502d8459
|
||||
en: c68d4c69e3820208
|
||||
zh: 58b9c35c9d49b512
|
||||
/deepseek-ai-dsh-experimental-client-ui-perf/further-exploration:
|
||||
en: 285cfe1d7a99af34
|
||||
zh: fbcee76e0c2f20fb
|
||||
|
||||
@@ -59,7 +59,7 @@ Four Conversation Definitions fold the event window into nodes for one view targ
|
||||
|
||||
The footer switches control the totals row and the context basis, and the footer stays pinned above the composer while the table scrolls. An expanded step row fills the Trigger column with its start clock and the gap since the previous step.
|
||||
|
||||
The footer switches control the totals row and the context basis. `Context basis: Latest` shows each turn's final prompt size; `Cumulative` sums that turn's step prompt sizes, so a row shows the tokens processed rather than the final window. The totals row applies the same basis to every loaded turn and sums the numeric columns (cache stays token-weighted, speed stays output over decode time).
|
||||
The footer switches control the totals row and the context basis. `Context basis` changes only how a turn row aggregates its steps: `Latest` shows the turn's final prompt size, while `Cumulative` sums that turn's step prompt sizes. The totals row is not affected by this switch: its context cell is always the cumulative step context of the loaded turns, and the other numeric columns are sums (cache stays token-weighted, speed stays output over decode time). Token cells switch from `K` to `M` at ten million.
|
||||
|
||||
The turn cache-hit cell is token-weighted across the steps that reported usage (summed cache reads ÷ summed prompt tokens), so one transient cold step is not hidden by a warmer neighbour; the tooltip shows the summed read/input/write breakdown. A step row keeps its own measured share.
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ kind: "package-reference"
|
||||
|
||||
页脚的开关控制合计行与上下文口径,滚表格时页脚钉在输入框上方。展开的步骤行在「触发」列显示该步开始时刻和距上一步的间隔。
|
||||
|
||||
页脚的开关控制合计行与上下文口径。「上下文口径:最新」显示每轮最后一步的提示词大小;「累计」显示该轮各步提示词大小之和,即这一轮实际处理过的 token 量。合计行对已加载的轮次套用同一口径并汇总数值列(缓存仍按 token 加权,速度仍按输出 ÷ 生成时长)。
|
||||
页脚的开关控制合计行与上下文口径。「上下文口径」只改变轮次行对其各步的聚合方式:「最新」显示该轮最后一步的提示词大小,「累计」显示该轮各步提示词大小之和。合计行不受这个开关影响:上下文列始终是已加载轮次的步骤累计值,其余数值列是求和(缓存仍按 token 加权,速度仍按输出 ÷ 生成时长)。token 超过一千万时从 K 切换为 M。
|
||||
|
||||
轮次的缓存命中率按报了 usage 的各步做 token 加权(缓存读之和 ÷ 提示词 token 之和),不会因为相邻步命中高就把某一次冷启动盖掉;tooltip 展示汇总后的读/输入/写明细。步骤行仍显示该步自己的实测比例。
|
||||
|
||||
|
||||
@@ -1614,7 +1614,7 @@ export function PerfView(props: PerfViewProps): ReactElement {
|
||||
return () => { clearInterval(timer) }
|
||||
}, [hasRunning])
|
||||
// Turns compare only with the other turns in the window.
|
||||
const totals = view.showTotals && turns.length > 0 ? aggregateTotals(turns, view.contextMode) : null
|
||||
const totals = view.showTotals && turns.length > 0 ? aggregateTotals(turns) : null
|
||||
const scaleTurns = visibleTurns.size === 0 ? turns : turns.filter(turn => visibleTurns.has(turn.turn))
|
||||
const turnScale = scaleTurns.reduce((max, turn) => {
|
||||
const span = totalSpan(turn, now)
|
||||
|
||||
@@ -62,11 +62,11 @@ export const zh = {
|
||||
'table.showTotals': '显示合计行',
|
||||
'contextMode.label': '上下文口径',
|
||||
'contextMode.latest': '最新',
|
||||
'contextMode.latest.title': '每轮最后一步的提示词大小(最终上下文)',
|
||||
'contextMode.latest.title': '轮次行显示最后一步的提示词大小(最终上下文);合计行始终为累计值',
|
||||
'contextMode.cumulative': '累计',
|
||||
'contextMode.cumulative.title': '该轮各步提示词大小之和(累计处理量)',
|
||||
'contextMode.cumulative.title': '轮次行显示该轮各步提示词大小之和(累计处理量);合计行始终为累计值',
|
||||
'totals.label': '合计',
|
||||
'totals.title': '当前已加载轮次的合计',
|
||||
'totals.title': '当前已加载轮次的合计;上下文为各步累计值',
|
||||
|
||||
'expand.all': '展开全部',
|
||||
'collapse.all': '折叠全部',
|
||||
@@ -206,11 +206,11 @@ export const en: Record<PerfKey, string> = {
|
||||
'table.showTotals': 'Show total row',
|
||||
'contextMode.label': 'Context basis',
|
||||
'contextMode.latest': 'Latest',
|
||||
'contextMode.latest.title': "Prompt size of the turn's last measured step (final context)",
|
||||
'contextMode.latest.title': "Turn rows show the last measured step's prompt size (final context); the total row stays cumulative",
|
||||
'contextMode.cumulative': 'Cumulative',
|
||||
'contextMode.cumulative.title': "Sum of prompt sizes across the turn's steps (cumulative processing)",
|
||||
'contextMode.cumulative.title': "Turn rows show the sum of prompt sizes across the turn's steps (cumulative processing); the total row stays cumulative",
|
||||
'totals.label': 'Total',
|
||||
'totals.title': 'Totals for the turns loaded in this window',
|
||||
'totals.title': 'Totals for the turns loaded in this window; the context cell is cumulative across steps',
|
||||
|
||||
'expand.all': 'Expand all',
|
||||
'collapse.all': 'Collapse all',
|
||||
@@ -337,11 +337,11 @@ export const ja: Record<PerfKey, string> = {
|
||||
'table.showTotals': '合計行を表示',
|
||||
'contextMode.label': 'コンテキスト基準',
|
||||
'contextMode.latest': '最新',
|
||||
'contextMode.latest.title': '各ターンの最後に測定したステップのプロンプトサイズ(最終コンテキスト)',
|
||||
'contextMode.latest.title': 'ターン行は最後に測定したステップのプロンプトサイズ(最終コンテキスト)。合計行は常に累計',
|
||||
'contextMode.cumulative': '累計',
|
||||
'contextMode.cumulative.title': 'ターン内の各ステップのプロンプトサイズの合計(累計処理量)',
|
||||
'contextMode.cumulative.title': 'ターン行はターン内の各ステップのプロンプトサイズの合計(累計処理量)。合計行は常に累計',
|
||||
'totals.label': '合計',
|
||||
'totals.title': '読み込み済みターンの合計',
|
||||
'totals.title': '読み込み済みターンの合計。コンテキストは各ステップの累計',
|
||||
|
||||
'expand.all': 'すべて展開',
|
||||
'collapse.all': 'すべて折りたたむ',
|
||||
|
||||
@@ -363,11 +363,9 @@ export function turnContext(turn: PerfTurnMetrics, mode: ContextMode): number |
|
||||
/**
|
||||
* Aggregate the loaded turns into one totals row.
|
||||
* @param turns - aggregated turns in display order.
|
||||
* @param mode - context basis shared with the turn rows.
|
||||
* @returns summed metrics and the context value for the selected basis.
|
||||
* @returns summed metrics; the context column is always the cumulative step context.
|
||||
*/
|
||||
export function aggregateTotals(turns: readonly PerfTurnMetrics[], mode: ContextMode): PerfTotals {
|
||||
let context: number | null = null
|
||||
export function aggregateTotals(turns: readonly PerfTurnMetrics[]): PerfTotals {
|
||||
let stepCount = 0
|
||||
let outputSum = 0
|
||||
let outputMeasured = false
|
||||
@@ -391,7 +389,6 @@ export function aggregateTotals(turns: readonly PerfTurnMetrics[], mode: Context
|
||||
let wallMeasured = false
|
||||
for (const turn of turns) {
|
||||
stepCount += turn.steps.length
|
||||
if (isNumber(turn.context)) context = turn.context
|
||||
if (isNumber(turn.output)) {
|
||||
outputSum += turn.output
|
||||
outputMeasured = true
|
||||
@@ -429,19 +426,17 @@ export function aggregateTotals(turns: readonly PerfTurnMetrics[], mode: Context
|
||||
}
|
||||
let cumulative = 0
|
||||
let cumulativeMeasured = false
|
||||
if (mode === 'cumulative') {
|
||||
for (const turn of turns) {
|
||||
for (const step of turn.steps) {
|
||||
if (!isNumber(step.context)) continue
|
||||
cumulative += step.context
|
||||
cumulativeMeasured = true
|
||||
}
|
||||
for (const turn of turns) {
|
||||
for (const step of turn.steps) {
|
||||
if (!isNumber(step.context)) continue
|
||||
cumulative += step.context
|
||||
cumulativeMeasured = true
|
||||
}
|
||||
}
|
||||
return {
|
||||
turnCount: turns.length,
|
||||
stepCount,
|
||||
context: mode === 'cumulative' ? (cumulativeMeasured ? cumulative : null) : context,
|
||||
context: cumulativeMeasured ? cumulative : null,
|
||||
output: outputMeasured ? outputSum : null,
|
||||
cacheHit: cacheMeasured && contextSum > 0 ? Math.min(1, read / contextSum) : null,
|
||||
cacheRead: cacheMeasured ? read : null,
|
||||
|
||||
@@ -30,13 +30,13 @@ export function formatTokens(value: number | null): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Token count in K: one decimal at or above 1K, two decimals below so a
|
||||
* short reply does not print as 0.0K.
|
||||
* Token count in K, switching to M once the value reaches ten million.
|
||||
* @param value - token count, or null when unrecorded.
|
||||
* @returns the K-suffixed count, or the dash placeholder.
|
||||
* @returns the K/M-suffixed count, or the dash placeholder.
|
||||
*/
|
||||
export function formatTokensShort(value: number | null): string {
|
||||
if (!isNumber(value)) return DASH
|
||||
if (value >= 10_000_000) return `${(value / 1_000_000).toFixed(1)}M`
|
||||
return value >= 1_000 ? `${(value / 1_000).toFixed(1)}K` : `${(value / 1_000).toFixed(2)}K`
|
||||
}
|
||||
|
||||
|
||||
@@ -328,6 +328,9 @@ describe('display formatting boundaries', () => {
|
||||
expect(formatTps(Number.NaN)).toBe(DASH)
|
||||
expect(formatTps(0)).toBe('0.0 tok/s')
|
||||
expect(formatTps(268.94)).toBe('268.9 tok/s')
|
||||
expect(formatTokensShort(9_999_999)).toBe('10000.0K')
|
||||
expect(formatTokensShort(10_000_000)).toBe('10.0M')
|
||||
expect(formatTokensShort(65_300_000)).toBe('65.3M')
|
||||
expect(formatPercent(null)).toBe(DASH)
|
||||
expect(formatPercent(0)).toBe('0.00%')
|
||||
expect(formatPercent(0.962)).toBe('96.20%')
|
||||
|
||||
@@ -239,7 +239,7 @@ describe('turn aggregation', () => {
|
||||
const bare = aggregateTurn(3, [metricsOf({ step: 1 })])
|
||||
expect(turnContext(bare, 'cumulative')).toBeNull()
|
||||
expect(turnContext(bare, 'latest')).toBeNull()
|
||||
const totals = aggregateTotals([bare], 'cumulative')
|
||||
const totals = aggregateTotals([bare])
|
||||
expect(totals.context).toBeNull()
|
||||
expect(totals.output).toBeNull()
|
||||
expect(totals.cacheHit).toBeNull()
|
||||
@@ -252,14 +252,14 @@ describe('turn aggregation', () => {
|
||||
|
||||
const partial = aggregateTurn(4, [metricsOf({ step: 1, context: 100, cacheRead: 40 })])
|
||||
expect(turnContext(partial, 'cumulative')).toBe(100)
|
||||
const mixed = aggregateTotals([partial], 'cumulative')
|
||||
const mixed = aggregateTotals([partial])
|
||||
expect(mixed.cacheWrite).toBe(0)
|
||||
expect(mixed.cacheHit).toBe(0.4)
|
||||
const forged = { ...bare, cacheRead: 10, cacheContext: 100, cacheWrite: null }
|
||||
expect(aggregateTotals([forged], 'latest').cacheWrite).toBe(0)
|
||||
expect(aggregateTotals([forged]).cacheWrite).toBe(0)
|
||||
})
|
||||
|
||||
it('aggregates loaded turns into totals and switches the context basis', () => {
|
||||
it('aggregates loaded turns into totals with cumulative context', () => {
|
||||
const first = aggregateTurn(1, [
|
||||
metricsOf({
|
||||
step: 1, startTime: 0, endTime: 300, context: 100, output: 10, decode: 1_000,
|
||||
@@ -276,10 +276,10 @@ describe('turn aggregation', () => {
|
||||
cacheRead: 250, cacheWrite: 10, tool: 75, toolWall: 60, wall: 500,
|
||||
}),
|
||||
])
|
||||
const latest = aggregateTotals([first, second], 'latest')
|
||||
const latest = aggregateTotals([first, second])
|
||||
expect(latest.turnCount).toBe(2)
|
||||
expect(latest.stepCount).toBe(3)
|
||||
expect(latest.context).toBe(300)
|
||||
expect(latest.context).toBe(600)
|
||||
expect(turnContext(first, 'latest')).toBe(200)
|
||||
expect(turnContext(first, 'cumulative')).toBe(300)
|
||||
expect(latest.output).toBe(60)
|
||||
@@ -297,10 +297,7 @@ describe('turn aggregation', () => {
|
||||
expect(latest.toolWall).toBe(100)
|
||||
expect(latest.wall).toBe(1_200)
|
||||
|
||||
const cumulative = aggregateTotals([first, second], 'cumulative')
|
||||
expect(cumulative.context).toBe(600)
|
||||
|
||||
const empty = aggregateTotals([], 'cumulative')
|
||||
const empty = aggregateTotals([])
|
||||
expect(empty).toEqual({
|
||||
turnCount: 0,
|
||||
stepCount: 0,
|
||||
|
||||
@@ -780,7 +780,7 @@ describe('turn rows', () => {
|
||||
const totalLabel = screen.getByText(say('totals.label'))
|
||||
expect(totalLabel.parentElement?.textContent).toContain(say('summary', { turns: 2, steps: 4 }))
|
||||
const totalRow = totalLabel.closest('tr') as HTMLTableRowElement
|
||||
expect(cellsOf(totalRow)[2]?.textContent).toBe(formatTokensShort(300))
|
||||
expect(cellsOf(totalRow)[2]?.textContent).toBe(formatTokensShort(600))
|
||||
|
||||
const first = rowWithPrefix(say('turn.label', { turn: 1 }))
|
||||
expect(cellsOf(first)[3]?.textContent).toBe(formatTokensShort(200))
|
||||
|
||||
Reference in new issue
Block a user