Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/app/src/components/calculator/interpolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,34 @@
* Shared by both server-side (compare SSR) and client-side (calculator, trends).
*/

import { frontierCacheUnavailableReason } from '@/lib/calculator/cache-pricing';

import type { CostProvider, GPUDataPoint, InterpolatedResult } from './types';

/**
* Frontier-level cache verdict and provenance. Empty for points priced by the
* legacy rule, which carry no source, so legacy results are unchanged.
*/
function frontierCache(
points: readonly GPUDataPoint[],
): Pick<
InterpolatedResult,
'cacheHitUnavailable' | 'cacheHitUnavailableReason' | 'cacheHitSources'
> {
const reason = frontierCacheUnavailableReason(
points.map((p) => ({
rate: p.cacheHitRate,
source: p.cacheHitSource ?? (p.cacheHitUnavailable ? 'unavailable' : undefined),
reason: p.cacheHitReason,
})),
);
const sources = [...new Set(points.flatMap((p) => (p.cacheHitSource ? [p.cacheHitSource] : [])))];
return {
...(reason ? { cacheHitUnavailable: true, cacheHitUnavailableReason: reason } : {}),
...(sources.length > 0 ? { cacheHitSources: sources.toSorted() } : {}),
};
}

/**
* Compute the upper-left pareto front for GPUDataPoints.
* This is the same algorithm used by the main inference charts for the
Expand Down Expand Up @@ -390,6 +416,7 @@ export function interpolateForGPU(
inputTpPerMw: sorted[0].inputTpPerMw,
outputTpPerMw: sorted[0].outputTpPerMw,
cacheHitRate: sorted[0].cacheHitRate,
...frontierCache(sorted),
theoreticalCacheHitRate: sorted[0].theoreticalCacheHitRate,
inputTokenShare: sorted[0].inputTokenShare,
concurrency: sorted[0].concurrency,
Expand Down Expand Up @@ -456,6 +483,9 @@ export function interpolateForGPU(
const cacheHitRate = sorted.every((p) => typeof p.cacheHitRate === 'number')
? buildMetric((p) => p.cacheHitRate!)
: undefined;
// One unproven knot, or a verified knot beside one without a rate, leaves the
// operating point without an actual rate rather than a full-price one.
const cache = frontierCache(sorted);

// Same all-or-nothing logic for the theoretical (infinite-cache) hit rate.
const theoreticalCacheHitRate = sorted.every((p) => typeof p.theoreticalCacheHitRate === 'number')
Expand Down Expand Up @@ -495,6 +525,7 @@ export function interpolateForGPU(
inputTpPerMw,
outputTpPerMw,
cacheHitRate,
...cache,
theoreticalCacheHitRate,
inputTokenShare,
concurrency,
Expand Down
49 changes: 39 additions & 10 deletions packages/app/src/components/calculator/profit-estimator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
* eats the revenue; the operator's profit can therefore go negative.
*/

import { tokenRevenueFromRatesPerGpuHour } from '@/components/inference/token-revenue';
import {
cacheAffectsPrice,
tokenRevenueFromRatesPerGpuHour,
} from '@/components/inference/token-revenue';
import type { TokenRevenuePricing } from '@/components/inference/types';
import { Model } from '@/lib/catalog/data-mappings';

Expand Down Expand Up @@ -252,7 +255,8 @@ export type ProfitEstimatorSkipReason =
| 'outside-measured-range'
| 'no-power'
| 'no-cost'
| 'no-token-mix';
| 'no-token-mix'
| 'cache-hit-unavailable';

export interface ProfitEstimatorSkipped {
hwKey: string;
Expand Down Expand Up @@ -298,7 +302,14 @@ export interface ProfitEstimatorSpecs {
export function estimateSkuProfit(
result: Pick<
InterpolatedResult,
'hwKey' | 'resultKey' | 'precision' | 'value' | 'inputTokenShare' | 'cacheHitRate' | 'clamped'
| 'hwKey'
| 'resultKey'
| 'precision'
| 'value'
| 'inputTokenShare'
| 'cacheHitRate'
| 'cacheHitUnavailable'
| 'clamped'
> & { date?: string },
specs: ProfitEstimatorSpecs,
pricing: TokenRevenuePricing,
Expand All @@ -315,6 +326,9 @@ export function estimateSkuProfit(
const gpuHours = assumptions.basis === 'chip-hour' ? 1 : gpuHoursPerGwYear(specs.powerKwPerGpu);
if (gpuHours === null) return { ...base, reason: 'no-power' };
if (!(specs.costPerGpuHour > 0)) return { ...base, reason: 'no-cost' };
if (result.cacheHitUnavailable && cacheAffectsPrice(pricing)) {
return { ...base, reason: 'cache-hit-unavailable' };
}

const revenuePerGpuHour = tokenRevenueFromRatesPerGpuHour(
result.value,
Expand Down Expand Up @@ -427,17 +441,32 @@ export function modelsWithAgenticData<M extends string>(
/**
* Remap interpolated results to use the theoretical cache hit rate in place of
* the measured one. When the theoretical rate is unavailable, the measured rate
* is kept — the intent is an upper-bound estimate, not a dropout.
* is kept — the intent is an upper-bound estimate, not a dropout. An unproven
* measured rate stays unavailable rather than passing for a theoretical one.
*/
export function applyCacheHitMode<
T extends { cacheHitRate?: number; theoreticalCacheHitRate?: number },
T extends Pick<
InterpolatedResult,
| 'cacheHitRate'
| 'theoreticalCacheHitRate'
| 'cacheHitUnavailable'
| 'cacheHitUnavailableReason'
| 'cacheHitSources'
>,
>(results: readonly T[], mode: CacheHitMode): T[] {
if (mode === 'actual') return results as T[];
return results.map((r) =>
typeof r.theoreticalCacheHitRate === 'number'
? { ...r, cacheHitRate: r.theoreticalCacheHitRate }
: r,
);
return results.map((r) => {
if (typeof r.theoreticalCacheHitRate !== 'number') return r;
const next = {
...r,
cacheHitRate: r.theoreticalCacheHitRate,
// Recorded provenance must name the theoretical rate, not the actual one it replaced.
...(r.cacheHitSources ? { cacheHitSources: ['theoretical' as const] } : {}),
};
delete next.cacheHitUnavailable;
delete next.cacheHitUnavailableReason;
return next;
});
}

/**
Expand Down
23 changes: 20 additions & 3 deletions packages/app/src/components/calculator/throughput-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { rowToSequence } from '@semianalysisai/inferencex-constants';
import type { AggDataEntry, HardwareConfig } from '@/components/inference/types';
import type { BenchmarkRow } from '@/lib/api/api';
import { rowToAggDataEntry } from '@/lib/benchmarks/benchmark-transform';
import { pricingCacheHitRate } from '@/lib/calculator/cache-pricing';
import {
type CacheRatePolicy,
DEFAULT_CACHE_RATE_POLICY,
selectPricingCacheBasis,
} from '@/lib/calculator/cache-pricing';
import { getHardwareKey } from '@/lib/charts/chart-utils';
import {
DEFAULT_TCO_BASIS,
Expand Down Expand Up @@ -180,6 +184,8 @@ export function buildGpuGroups<M extends GroupMeta>(
/** Token basis selected by the consumer; applies to official and overlay rows. */
tokenType?: CostType;
tcoBasis?: TcoBasis;
/** How strictly actual cache pricing follows the producer's validity core. */
cacheRatePolicy?: CacheRatePolicy;
/** Derive a row's group key + metadata. Return null to drop the row. */
classify: (hwKey: string, row: BenchmarkRow) => { key: string; meta: M } | null;
},
Expand All @@ -194,6 +200,7 @@ export function buildGpuGroups<M extends GroupMeta>(
percentile = Percentile.P90,
tokenType = 'total',
tcoBasis = DEFAULT_TCO_BASIS,
cacheRatePolicy = DEFAULT_CACHE_RATE_POLICY,
classify,
} = options;
const grouped: Record<string, GPUDataPoint[]> = {};
Expand All @@ -220,7 +227,10 @@ export function buildGpuGroups<M extends GroupMeta>(
const tput = m.tput_per_gpu ?? 0;
const outputTput = m.output_tput_per_gpu ?? tput;
const inputTput = m.input_tput_per_gpu ?? 0;
const cacheHitRate = pricingCacheHitRate({ ...m, hw: row.hardware });
const cache = selectPricingCacheBasis(
{ ...m, hw: row.hardware, benchmark_type: row.benchmark_type },
cacheRatePolicy,
);
const theoreticalRate =
typeof m.theoretical_cache_hit_rate === 'number' &&
Number.isFinite(m.theoretical_cache_hit_rate)
Expand Down Expand Up @@ -252,7 +262,14 @@ export function buildGpuGroups<M extends GroupMeta>(
throughput: tput,
outputThroughput: outputTput,
inputThroughput: inputTput,
...(cacheHitRate === null ? {} : { cacheHitRate }),
...(cache.rate === null ? {} : { cacheHitRate: cache.rate }),
...(cache.source === 'unavailable' ? { cacheHitUnavailable: true } : {}),
...(cacheRatePolicy === 'legacy'
? {}
: {
cacheHitSource: cache.source,
...(cache.reason ? { cacheHitReason: cache.reason } : {}),
}),
...(theoreticalRate === null ? {} : { theoreticalCacheHitRate: theoreticalRate }),
...(tokenShare === null ? {} : { inputTokenShare: tokenShare }),
concurrency: row.conc,
Expand Down
20 changes: 20 additions & 0 deletions packages/app/src/components/calculator/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BenchmarkRow } from '@/lib/api/api';
import type { PricingCacheSource } from '@/lib/calculator/cache-pricing';

export type CalculatorMode = 'interactivity_to_throughput' | 'throughput_to_interactivity';

Expand Down Expand Up @@ -44,6 +45,15 @@ export interface GPUDataPoint {
* absence is what makes cached-input billing a no-op outside agentic traces.
*/
cacheHitRate?: number;
/**
* The validity policy left this point's actual cache hit rate unproven. Not
* the same as an absent `cacheHitRate`, which bills no cache discount.
*/
cacheHitUnavailable?: boolean;
/** Where the rate came from; recorded only when a validity policy is enforced. */
cacheHitSource?: PricingCacheSource;
/** Why the actual rate is unavailable, when it is. */
cacheHitReason?: string;
/**
* Infinite-cache theoretical hit rate (0..1) computed from the trace itself,
* representing the maximum reuse potential of the workload with unbounded
Expand Down Expand Up @@ -100,6 +110,16 @@ export interface InterpolatedResult {
* fixed-sequence frontier). See {@link GPUDataPoint.cacheHitRate}.
*/
cacheHitRate?: number;
/**
* Some frontier point's actual cache hit rate is unproven, or a verified
* point sits next to one without a rate, so actual pricing at this operating
* point is unavailable. See {@link GPUDataPoint.cacheHitUnavailable}.
*/
cacheHitUnavailable?: boolean;
/** Why actual pricing is unavailable at this operating point. */
cacheHitUnavailableReason?: string;
/** Distinct sources of the frontier points' rates, when a validity policy is enforced. */
cacheHitSources?: PricingCacheSource[];
/**
* Infinite-cache theoretical hit rate at that operating point, or undefined
* when the frontier did not carry a theoretical rate on every point.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { BenchmarkRow } from '@/lib/api/api';
import { Percentile, Sequence } from '@/lib/catalog/data-mappings';
import { overlayRunIndex } from '@/lib/charts/overlay-run-style';
import { SUPPLEMENTAL_BENCHMARK_ROWS } from '@/lib/benchmarks/supplemental-benchmarks';
import { toCalculatorBenchmarkRows } from '@/lib/benchmarks/benchmark-api-view';

import type { GPUDataPoint } from './types';
import {
Expand Down Expand Up @@ -1396,6 +1397,44 @@ describe('buildGpuGroups', () => {
// tokens instead of inventing an 80% input mix.
expect(Object.values(grouped)[0][0].inputTokenShare).toBeUndefined();
});

it.each([true, false])(
'preserves the agentic P/D token mix through the calculator projection (totals=%s)',
(hasTotals) => {
const row = makeRow({
disagg: true,
is_multinode: true,
prefill_tp: 4,
prefill_ep: 1,
decode_tp: 16,
decode_ep: 1,
num_prefill_gpu: 4,
num_decode_gpu: 16,
benchmark_type: 'agentic_traces',
isl: null,
osl: null,
metrics: {
p90_itl: 1 / 50,
tput_per_gpu: 300,
input_tput_per_gpu: 750,
output_tput_per_gpu: 187.5,
...(hasTotals ? { total_prompt_tokens: 1000, total_generation_tokens: 1000 } : {}),
},
});
const options = {
sequence: Sequence.AgenticTraces,
precisions: ['fp4'],
classify: singlePrecisionClassify,
};
const raw = Object.values(buildGpuGroups([row], options).grouped)[0][0];
const compact = Object.values(
buildGpuGroups(toCalculatorBenchmarkRows([row], 'agentic-traces'), options).grouped,
)[0][0];
expect(compact.inputTokenShare).toBe(raw.inputTokenShare);
if (hasTotals) expect(compact.inputTokenShare).toBeCloseTo(0.5, 9);
else expect(compact.inputTokenShare).toBeUndefined();
},
);
});

it('applies Internal TPUv7 owner cost to every calculator token basis', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@/components/inference/metric-registry';
import {
applyTokenRevenuePricing,
cacheAffectsPrice,
inputTokenShareForRevenue,
NORMALIZED_TOKEN_REVENUE_PRICING,
tokenRevenueFromRatesPerGpuHour,
Expand All @@ -27,7 +28,12 @@ import {
dedupeAgenticHistoryRuns,
} from '@/lib/benchmarks/benchmark-run-selection';
import { rowToAggDataEntry } from '@/lib/benchmarks/benchmark-transform';
import { pricingCacheHitRate } from '@/lib/calculator/cache-pricing';
import {
type CacheRatePolicy,
DEFAULT_CACHE_RATE_POLICY,
frontierCacheUnavailableReason,
selectPricingCacheBasis,
} from '@/lib/calculator/cache-pricing';
import {
buildDerivedChartFields,
getHardwareKey,
Expand All @@ -51,6 +57,7 @@ export function rowToLightweightPoint(
requestedMetrics: readonly DerivedMetricKey[],
tokenRevenuePricing: TokenRevenuePricing | null = NORMALIZED_TOKEN_REVENUE_PRICING,
tcoBasis: TcoBasis = DEFAULT_TCO_BASIS,
cacheRatePolicy: CacheRatePolicy = DEFAULT_CACHE_RATE_POLICY,
): InferenceData | null {
const entry = rowToAggDataEntry(row);
const hwKey = getHardwareKey(entry);
Expand Down Expand Up @@ -85,11 +92,21 @@ export function rowToLightweightPoint(
server_external_cache_hit_rate: entry.server_external_cache_hit_rate,
server_cpu_cache_hit_rate: entry.server_cpu_cache_hit_rate,
theoretical_cache_hit_rate: entry.theoretical_cache_hit_rate,
benchmark_type: entry.benchmark_type,
server_overall_cache_hit_rate: entry.server_overall_cache_hit_rate,
server_cache_core_schema_version: entry.server_cache_core_schema_version,
server_cache_detail_schema_code: entry.server_cache_detail_schema_code,
server_cache_status_class_code: entry.server_cache_status_class_code,
server_cache_published: entry.server_cache_published,
server_cache_pricing_eligible: entry.server_cache_pricing_eligible,
server_cache_basis_code: entry.server_cache_basis_code,
server_cache_overall_source_code: entry.server_cache_overall_source_code,
server_cache_invalid_rate_fields: entry.server_cache_invalid_rate_fields,
...buildDerivedChartFields(derivedEntry, hwKey, requestedMetrics, tcoBasis),
} as InferenceData;

return requestedMetrics.includes('tokenRevenuePerGpuHour')
? applyTokenRevenuePricing([point], tokenRevenuePricing)[0]!
? applyTokenRevenuePricing([point], tokenRevenuePricing, cacheRatePolicy)[0]!
: point;
}

Expand Down Expand Up @@ -179,6 +196,7 @@ export function interpolateMetricAtInteractivity(
targetInteractivity: number,
metricKey: YAxisMetricKey,
tokenRevenuePricing?: TokenRevenuePricing | null,
cacheRatePolicy: CacheRatePolicy = DEFAULT_CACHE_RATE_POLICY,
): number | null {
if (points.length === 0) return null;

Expand Down Expand Up @@ -238,7 +256,16 @@ export function interpolateMetricAtInteractivity(
};
const throughputYs = sorted.map((p) => extractMetric(p, 'tpPerGpu')!);
const inputShares = sorted.map(inputTokenShareForRevenue);
const cacheHitRates = sorted.map(pricingCacheHitRate);
const cacheBases = sorted.map((p) => selectPricingCacheBasis(p, cacheRatePolicy));
// An unproven knot, or a verified knot beside one without a rate, makes the
// operating point unpriceable; splining around it would bill hits as zero.
if (
frontierCacheUnavailableReason(cacheBases) !== undefined &&
cacheAffectsPrice(tokenRevenuePricing)
) {
return null;
}
const cacheHitRates = cacheBases.map((cache) => cache.rate);
const inputShare = inputShares.every((share): share is number => share !== null)
? interpolateBounded(inputShares)
: null;
Expand Down
Loading