From 916ddbbbf9286a0c025a1cc0a77dad4d8fb639b7 Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Thu, 1 Oct 2026 13:44:14 -0700 Subject: [PATCH] fix: remove unavailable estimate lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 中文:移除无法估算配置列表。 --- docs/dashboard-readonly-views.md | 7 +- docs/powerx-system-power.md | 11 ++- docs/powerx-system-power.zh.md | 9 +- docs/tco-calculator.md | 6 +- .../app/cypress/e2e/profit-estimator.cy.ts | 83 +++---------------- .../calculator/ProfitEstimatorDisplay.tsx | 81 ------------------ 6 files changed, 27 insertions(+), 170 deletions(-) diff --git a/docs/dashboard-readonly-views.md b/docs/dashboard-readonly-views.md index 3f98dd8eb..572f233b6 100644 --- a/docs/dashboard-readonly-views.md +++ b/docs/dashboard-readonly-views.md @@ -66,7 +66,7 @@ state. GPU interactive downsampling does not alter returned raw data or statisti Power boundary labels are GPU Level Measured, GPU Level Provisioned (TDP), All in Provisioned, and All in Measured. The last combines measured GPU power with modeled unmeasured components and PUE; it is not a wall-meter measurement. These labels and -collapsed power-assumption/availability notes do not change metric IDs, API selectors, +power-assumption notes do not change metric IDs, API selectors, or calculations. Profit comparison `powerLabel` display text follows the same names. All in Measured watts and energy accept validated 8K/1K and AgentX rows through the shared chart/API transform, including historical and unofficial rows. AgentX reuses @@ -78,9 +78,8 @@ Dense profit charts reserve readable space per bar and scroll within the plot on screens; captions and controls stay fixed. This is presentation-only: API selectors, calculations, source identities and CSV rows are unchanged. PNG export includes the full plot regardless of its current scroll position, so no API or skills contract change is needed. -Unavailable-estimate notices distinguish unpriced SKUs from missing measured-plus-modeled -estimates using the retained provisioned result identity. This explanatory grouping preserves -the API's existing rows, skip reasons, selectors and calculations. +Profit charts omit the per-configuration unavailable-estimate list. This is presentation-only; +the API retains skipped rows and reasons, and calculations and CSV exports are unchanged. The GPU statistics table includes startup and warmup for all chips in the selected series, regardless of chip visibility. It is separate from serving-window power, J/token and selected-time-window calculations. Run telemetry is DB-first with an diff --git a/docs/powerx-system-power.md b/docs/powerx-system-power.md index 0808b0ce8..2367c7852 100644 --- a/docs/powerx-system-power.md +++ b/docs/powerx-system-power.md @@ -45,8 +45,8 @@ experimental power controls with ↑↑↓↓ if they are hidden. 4. For a capacity comparison, open `/profit-estimator-per-gigawatt`, choose the model and a supported interactivity target, then select **Compare both** in Benchmark Config. Match each result's workload, engine and precision to the - inference selection. Expand **Unavailable estimates** for missing results; - hover or select a bar for its power basis and read the formula notes below. + inference selection. Hover or select a bar for its power basis and read the + formula notes below. **Expected result:** the inference table includes rows with a valid selected metric, including supported B200/H200 multi-node deployments. Selecting All in @@ -175,10 +175,9 @@ Chart/table rows and target-based profit estimates answer different questions. | `workload`, `hardware`, `model-domain` | Use a supported workload/profile and in-domain input; do not replace the missing estimate with zero or TDP. | In **Compare both**, a valid provisioned result remains when its measured estimate -is unavailable. Configurations that cannot be priced at all are listed separately -from missing measured estimates. Measured-only mode never substitutes provisioned -watts. See [persistence and recovery](./powerx-persistence-recovery.md) for retained -telemetry and targeted repair; new power readings cannot be attached to old +is unavailable. Measured-only mode never substitutes provisioned watts. See +[persistence and recovery](./powerx-persistence-recovery.md) for retained telemetry +and targeted repair; new power readings cannot be attached to old throughput results. ## Provenance and reproducible exports diff --git a/docs/powerx-system-power.zh.md b/docs/powerx-system-power.zh.md index 64b96e14f..d10225fe0 100644 --- a/docs/powerx-system-power.zh.md +++ b/docs/powerx-system-power.zh.md @@ -36,8 +36,8 @@ AgentX 估算属于容量规划预览,不代表已完成 AgentX 校准,也 AgentX 预览;独立的 **Modeled Chassis AC** 指标仍仅支持 8K/1K 单轮结果。 4. 要比较容量,打开 `/profit-estimator-per-gigawatt`,选择模型和曲线支持的 交互性目标,再在 Benchmark Config 中选择 **Compare both**。逐项核对结果的 - 工作负载、引擎和精度是否与推理页面所选一致。展开 **Unavailable estimates** - 查看缺失原因;悬停或选中柱形查看功耗依据,并阅读图表下方的公式说明。 + 工作负载、引擎和精度是否与推理页面所选一致。悬停或选中柱形查看功耗依据, + 并阅读图表下方的公式说明。 **预期结果:**推理表格列出所选指标有效的记录,包括满足要求的 B200/H200 多节点 部署。All in Measured 不会包含所有 GPU 功耗有效的记录,还需满足下述输入要求。 @@ -151,9 +151,8 @@ NVL72 记录。利润规划始终要求 schema 2。 | 缺少成本、token 组成或预配功率 | 检查财务输入;即使功耗有效,也可能无法计算两种利润结果。 | | `workload`、`hardware`、`model-domain` | 使用受支持的工作负载/profile 和定义域内的输入,不以零值或 TDP 替代缺失估算。 | -**Compare both** 在实测估算不可用时仍保留有效预配结果。完全无法定价的配置,与 -仅缺少实测估算的配置分开列出;仅实测模式不会用预配功率代替。保留遥测和定向修复 -方式见[持久化与恢复](./powerx-persistence-recovery.md);新采集的功率不能附到旧吞吐量上。 +**Compare both** 在实测估算不可用时仍保留有效预配结果。仅实测模式不会用预配 +功率代替。保留遥测和定向修复方式见[持久化与恢复](./powerx-persistence-recovery.md);新采集的功率不能附到旧吞吐量上。 ## 来源与可复现导出 diff --git a/docs/tco-calculator.md b/docs/tco-calculator.md index 756ccf4d8..4b03e3393 100644 --- a/docs/tco-calculator.md +++ b/docs/tco-calculator.md @@ -1135,7 +1135,7 @@ and the two can be collapsed into one once both are on master. input / $0.06 cached / $1.20 output per M tok, the permanent 50%-off rate on its pay-as-you-go page); the OpenRouter aggregate also sits below it. At 83 tok/s/user the B200, B300, GB200, and MI355X agentic curves are priced, and the - H100, H200, MI300X, and MI325X curves top out below it and list as not priced. + H100, H200, MI300X, and MI325X curves top out below it and are omitted at that target. GLM 5.2/5.3 opens on a 10% model license fee and MiniMax M3 on 20%; Kimi K3 opens on the 30% `DEFAULT_LAB_CUT_PCT`. DeepSeek V4 Pro opens on 24 tok/s/user, the speed DeepSeek's own API serves at, DeepSeek's peak-hour list price for @@ -1144,14 +1144,14 @@ and the two can be collapsed into one once both are on master. and a 0% model license fee, since the weights ship under the MIT license. At 24 tok/s/user the B200, B300, and MI355X agentic curves are priced; the GB200, GB300, and H200 curves bottom out above it (their lowest measured - points sit at roughly 40, 30, and 27 tok/s/user) and list as not priced until + points sit at roughly 40, 30, and 27 tok/s/user) and are omitted until a lower-interactivity run lands. DeepSeek V4.1 Flash opens on 125 tok/s/user, the speed DeepSeek's own API serves the Flash tier at, DeepSeek's peak-hour list price for `deepseek-flash` ($0.30 input / $0.006 cached / $1.20 output per M tok; off-peak is half that), and a 0% model license fee, since the weights ship under the MIT license. It entered the fleet on AgentX only (InferenceX#2961), so the page is wired ahead of the first published rows; SKUs whose agentic curves stop short - of 125 tok/s/user list as not priced rather than extrapolated. A + of 125 tok/s/user are omitted rather than extrapolated. A model with a list price gets a third Token Price option, ` list price`, next to OpenRouter and Custom; the caption names the source in force and links the lab's pricing page when the list price is used. Switching to Custom diff --git a/packages/app/cypress/e2e/profit-estimator.cy.ts b/packages/app/cypress/e2e/profit-estimator.cy.ts index 4e7aef39a..dfb6648ed 100644 --- a/packages/app/cypress/e2e/profit-estimator.cy.ts +++ b/packages/app/cypress/e2e/profit-estimator.cy.ts @@ -95,22 +95,10 @@ const chart = () => cy.get('[data-testid="profit-estimator-chart"]'); const chartSvg = () => chart().find('svg').filter(':has(.chart-root)').first(); const bars = () => chart().find('rect.bar'); -function assertDisclosureOpen(testId: string, open: boolean) { - cy.get(`[data-testid="${testId}"]`).should(($details) => { - expect($details[0].open, `${testId} native disclosure state`).to.equal(open); - const content = $details[0].querySelector('p'); - expect(content, `${testId} content`).not.to.equal(null); - // Cypress visibility omits native closed-details rendering in some browsers. - if (content && typeof content.checkVisibility === 'function') { - expect(content.checkVisibility(), `${testId} browser visibility`).to.equal(open); - } - }); -} - // Clear the preceding chart before each case changes the viewport. describe('Profit estimator power option', { testIsolation: true }, () => { for (const locale of ['en', 'zh'] as const) { - it(`distinguishes unpriced SKUs from missing measured-power estimates (${locale})`, () => { + it(`keeps available estimates without a per-configuration warning list (${locale})`, () => { stubOpenRouter(); const width = locale === 'en' ? 1280 : 390; cy.viewport(width, 900); @@ -135,34 +123,12 @@ describe('Profit estimator power option', { testIsolation: true }, () => { .should('not.contain', 'H200') .and('contain', 'B300') .and('contain', 'GB300'); - const unpriced = locale === 'en' ? 'Not priced:' : '未定价:'; - const measured = - locale === 'en' ? 'Measured + modeled unavailable:' : '实测加建模估算不可用:'; - cy.get('[data-testid="profit-power-unavailable"] > summary').click(); - cy.get('[data-testid="profit-power-unavailable"] > p') - .should('be.visible') - .should(($notice) => { - const [baseline, measurement] = $notice.text().split(measured); - expect(baseline).to.contain(unpriced).and.to.contain('H200'); - expect(baseline).not.to.contain('B300'); - expect(measurement).to.contain('B300').and.to.contain('GB300'); - expect(measurement).not.to.contain('H200'); - expect(measurement).to.contain( - locale === 'en' ? 'no usable measured power' : '同一组基准测试数据点缺少有效功耗', - ); - expect(measurement).to.contain( - locale === 'en' - ? 'missing complete Grace or module power' - : '缺少完整的 Grace 或 module 功耗', - ); - const bounds = $notice[0].getBoundingClientRect(); - expect(bounds.left).to.be.at.least(0); - expect(bounds.right).to.be.at.most(width); - }); - cy.get('[data-testid="profit-power-unavailable"]').scrollIntoView({ - offset: { top: -70, left: 0 }, + cy.get('[data-testid="profit-power-unavailable"]').should('not.exist'); + chart().scrollIntoView(); + cy.screenshot(`profit-no-unavailable-list-${locale}`, { + capture: 'viewport', + overwrite: true, }); - cy.screenshot(`profit-unavailable-basis-${locale}`, { capture: 'viewport', overwrite: true }); }); it(`prices DeepSeek Flash partial chassis with a one-line power note and CSV labels (${locale})`, () => { @@ -202,10 +168,6 @@ describe('Profit estimator power option', { testIsolation: true }, () => { }, ); const label = locale === 'en' ? 'Full-chassis extrapolation' : '整机外推'; - const cpuReason = - locale === 'en' - ? 'missing complete Grace or module power' - : '缺少完整的 Grace 或 module 功耗'; cy.get('#profit-target').should('have.value', '125'); chart().find('text.revenue-label').should('have.length', 3); chart() @@ -213,19 +175,11 @@ describe('Profit estimator power option', { testIsolation: true }, () => { .and('contain', 'B300') .and('contain', 'MI355X') .and('contain', label); - cy.get('[data-testid="profit-power-unavailable"]') - .should('contain', 'GB300') - .and('contain', cpuReason); - assertDisclosureOpen('profit-power-unavailable', false); - cy.get('[data-testid="profit-power-unavailable"] > summary').click(); - assertDisclosureOpen('profit-power-unavailable', true); - cy.get('[data-testid="profit-power-unavailable"] > p').should('be.visible'); + cy.get('[data-testid="profit-power-unavailable"]').should('not.exist'); cy.get('[data-testid="profit-power-note"]') .should('contain', locale === 'en' ? 'All in Measured' : '整体实测功耗') .and('not.contain', locale === 'en' ? 'unmeasured components' : '未实测的组件'); cy.get('[data-testid="profit-power-assumptions"]').should('not.exist'); - cy.get('[data-testid="profit-power-unavailable"] > summary').click(); - assertDisclosureOpen('profit-power-unavailable', false); cy.get('[data-testid="profit-power-note"]').then(($note) => { const box = $note[0].getBoundingClientRect(); expect(box.left).to.be.at.least(0); @@ -293,7 +247,7 @@ describe('Profit estimator power option', { testIsolation: true }, () => { }); for (const currentValid of [true, false]) { - it(`dates historical power skips without current hardware metadata (${currentValid ? 'with current bars' : 'provisioned bars only'})`, () => { + it(`keeps historical provisioned bars when measured power is unavailable (${currentValid ? 'with current bars' : 'provisioned bars only'})`, () => { stubOpenRouter(); cy.intercept('GET', '/api/v1/benchmarks*', (req) => { const historical = req.query['date'] === PROFIT_HISTORY_DATE; @@ -318,14 +272,6 @@ describe('Profit estimator power option', { testIsolation: true }, () => { `/profit-estimator-per-gigawatt?c_power=compare&i_gpus=b200_sglang,b300_vllm&i_dstart=${PROFIT_HISTORY_DATE}&i_dend=${PROFIT_HISTORY_DATE}`, { onBeforeLoad: unlockPowerGate }, ); - cy.get('[data-testid="profit-power-unavailable"]').should( - 'contain', - `B300 (vLLM) (FP4) • ${PROFIT_HISTORY_DATE}`, - ); - cy.get('[data-testid="profit-power-unavailable"]').should( - 'contain', - `B200 (SGLang) (FP4) • ${PROFIT_HISTORY_DATE}`, - ); chart() .find('text.revenue-label') .should('have.length', currentValid ? 4 : 3); @@ -361,7 +307,7 @@ describe('Profit estimator power option', { testIsolation: true }, () => { chart().find('text.revenue-label').should('have.length', 7); chart().should('contain', 'B200').and('contain', 'B300').and('contain', 'MI355X'); chart().should('contain', 'All in Measured').and('contain', 'All in Provisioned'); - cy.get('[data-testid="profit-power-unavailable"]').should('contain', 'GB300'); + cy.get('[data-testid="profit-power-unavailable"]').should('not.exist'); }); it('prices a GB200 NVL72 tray on its measured compute module and names the basis', () => { @@ -421,10 +367,7 @@ describe('Profit estimator power option', { testIsolation: true }, () => { .and('contain', 'DLC PUE 1.1'); cy.get('body').type('{esc}'); cy.get('[data-testid="option-help-content-profit-power"]').should('not.exist'); - // Only GB300's measured estimate is unavailable; its provisioned estimate remains visible. - cy.get('[data-testid="profit-power-unavailable"]') - .should('contain', 'GB300') - .and('not.contain', 'GB200'); + cy.get('[data-testid="profit-power-unavailable"]').should('not.exist'); chart().scrollIntoView(); cy.screenshot('profit-nvl72-compare-desktop', { capture: 'viewport', overwrite: true }); cy.viewport(393, 900); @@ -611,10 +554,8 @@ describe('Profit estimator power option', { testIsolation: true }, () => { cy.get('#profit-power').click(); cy.get('[role="option"]').contains('All in Measured').click(); // These existing fixtures intentionally have throughput but no validated power. - cy.get('[data-testid="profit-power-unavailable"]').should( - 'contain', - 'no usable measured power', - ); + cy.get('[data-testid="profit-power-unavailable"]').should('not.exist'); + cy.contains('No SKU can be priced for the current selection.').should('be.visible'); cy.get('#profit-target').should('have.value', '45'); cy.get('[data-testid="profit-model-selector"]').should('contain', 'Kimi K3'); cy.get('[data-testid="profit-price-source-selector"]').should('contain', 'Moonshot'); diff --git a/packages/app/src/components/calculator/ProfitEstimatorDisplay.tsx b/packages/app/src/components/calculator/ProfitEstimatorDisplay.tsx index ea8122428..c59d3ae10 100644 --- a/packages/app/src/components/calculator/ProfitEstimatorDisplay.tsx +++ b/packages/app/src/components/calculator/ProfitEstimatorDisplay.tsx @@ -91,7 +91,6 @@ import { profitModelDefaults, type ProfitBasis, type ProfitEstimatorRow, - type ProfitEstimatorSkipReason, } from './profit-estimator'; import { powerBasisLabel, profitEstimatorChartStrings, rowLabel } from './ProfitEstimatorChart'; import { estimateProfitByPower, powerSourceKey, type ProfitPowerBasis } from './profit-power'; @@ -220,7 +219,6 @@ const STRINGS = { powerPreview: `${ALL_IN_MEASURED_NOTE.en} AgentX system power is not yet qualified.`, powerDetails: 'GPU power is interpolated between the same throughput points. Includes PUE 1.3 for air-cooled chassis or 1.1 for NVL72, and 10% headroom. Aggregate multinode hosts use the measured deployment mean. Full-chassis extrapolation fills an eight-GPU server with replicas of the measured 1/2/4-GPU workload at the same per-GPU power and throughput; it does not measure a partly idle server.', - unavailableEstimates: (count: number) => `Unavailable estimates (${count})`, powerNvl72Note: (hardware: string, basis: string, pue: number) => `${hardware}: ${basis}. Modeled: NVSwitch trays, NICs/DPUs, NVMe, power shelves, DLC PUE ${pue}.`, csvPowerHeaders: ['Power basis', 'Power sensor', 'System power profile'], @@ -300,21 +298,6 @@ const STRINGS = { 'Revenue ($/GPU/hr, 100% util)', ], }, - skipped: (entries: string) => `Not priced: ${entries}.`, - modeledUnavailable: (entries: string) => `Measured + modeled unavailable: ${entries}.`, - skipReason: { - 'outside-measured-range': 'no measured point at the target interactivity', - 'no-power': 'no all-in power figure', - 'no-measured-power': 'no usable measured power for these benchmark points', - 'no-cpu-power': 'missing complete Grace or module power for these benchmark points', - 'incompatible-power-basis': 'bounding points use different power measurement bases', - 'unsupported-power-hardware': 'no system power model for this hardware', - 'unsupported-power-topology': - 'GPU counts, physical hosts or role power do not support this system model', - 'outside-power-model': 'these benchmark points are outside the supported power model', - 'no-cost': 'no TCO for this tier', - 'no-token-mix': 'no input/output token mix recorded', - } satisfies Record, compareHistory: 'Compare history', gpuConfig: 'Chip Config', gpuConfigTooltip: `Select up to ${PROFIT_HISTORY_MAX_GPUS} chip configurations to compare how their estimated revenue and profit have moved over time. Each config is priced again on every compared date (the ends of the date range, plus any date or run added from the Config Changelog below) using the run measured then, so software updates show up as a change in the bar.`, @@ -350,7 +333,6 @@ const STRINGS = { powerPreview: `${ALL_IN_MEASURED_NOTE.zh} AgentX 系统功耗模型尚未完成验证。`, powerDetails: 'GPU 功耗在相同的吞吐量数据点间插值,风冷机箱 PUE 为 1.3,NVL72 为 1.1,另加 10% 功耗余量。聚合多节点按部署平均功耗估算各台服务器。整机外推假设八卡服务器部署多个相同的实测单卡、双卡或四卡实例,每卡功耗和吞吐量保持不变;它不代表部分 GPU 闲置时的整机实测功耗。', - unavailableEstimates: (count: number) => `无法估算(${count} 项)`, powerNvl72Note: (hardware: string, basis: string, pue: number) => `${hardware}:${basis}。建模部分:NVSwitch tray、网卡/DPU、NVMe、电源架,液冷 PUE ${pue}。`, csvPowerHeaders: ['功耗口径', '功耗传感器', '系统功耗 profile'], @@ -430,20 +412,6 @@ const STRINGS = { '收入($/GPU/hr,100% 利用率)', ], }, - skipped: (entries: string) => `未定价:${entries}。`, - modeledUnavailable: (entries: string) => `实测加建模估算不可用:${entries}。`, - skipReason: { - 'outside-measured-range': '未在该交互性下实测', - 'no-power': '缺少全电源配置功率数据', - 'no-measured-power': '同一组基准测试数据点缺少有效功耗', - 'no-cpu-power': '同一组基准测试数据点缺少完整的 Grace 或 module 功耗', - 'incompatible-power-basis': '插值两端的功耗测量口径不同', - 'unsupported-power-hardware': '该硬件暂无适用的系统功耗模型', - 'unsupported-power-topology': 'GPU 数量、物理主机或各角色功耗不满足系统模型要求', - 'outside-power-model': '这些基准测试数据点超出功耗模型的适用范围', - 'no-cost': '该层级无 TCO 数据', - 'no-token-mix': '未记录输入/输出 token 比例', - } satisfies Record, compareHistory: '对比历史趋势', gpuConfig: '芯片配置', gpuConfigTooltip: `最多选择 ${PROFIT_HISTORY_MAX_GPUS} 个芯片配置,对比其收入与利润估算随时间的变化。每个配置都会用当日实测的运行结果,在每个对比日期(日期范围的起止两端,以及从下方配置变更日志中添加的日期或运行)重新估价,软件更新带来的差异会直接体现在柱形上。`, @@ -1371,29 +1339,6 @@ function ProfitEstimatorInner({ historyCurrentRunIds, ]); - const powerUnavailable = useMemo(() => { - const unpriced: string[] = []; - const measuredUnavailable: string[] = []; - for (const row of fullEstimate.skipped) { - const label = rowLabel( - { ...row, dateLabel: row.date ? historyEntryLabel(row.date) : undefined }, - hardwareConfig, - ); - const entries = - powerBasis === 'compare' && - fullEstimate.rows.some((priced) => priced.resultKey === `${row.resultKey}__provisioned`) - ? measuredUnavailable - : unpriced; - entries.push(`${label}: ${t.skipReason[row.reason]}`); - } - return [ - unpriced.length > 0 ? t.skipped(unpriced.join('; ')) : '', - measuredUnavailable.length > 0 ? t.modeledUnavailable(measuredUnavailable.join('; ')) : '', - ] - .filter(Boolean) - .join(' '); - }, [fullEstimate, hardwareConfig, historyEntryLabel, powerBasis, t]); - const powerBasisNotes = useMemo(() => { const notes = new Map(); for (const row of estimate.rows) { @@ -1434,20 +1379,6 @@ function ProfitEstimatorInner({ {t.powerLabel}: {t.powerOptions[powerBasis]}

)} - {basis === 'gw-year' && powerBasis !== 'provisioned' && fullEstimate.skipped.length > 0 && ( -
- track('profit_estimator_power_unavailable_toggled')} - > - {t.unavailableEstimates(fullEstimate.skipped.length)} - -

{powerUnavailable}

-
- )} - {basis === 'gw-year' && - estimate.rows.length === 0 && - powerBasis !== 'provisioned' && ( -

- {t.powerPreview} {powerUnavailable} -

- )}