Skip to content
Closed
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
27 changes: 27 additions & 0 deletions packages/app/src/components/calculator/interpolation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import { frontierCacheUnavailableReason } from '@/lib/calculator/cache-pricing';

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

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() } : {}),
};
}

export function paretoFrontUpperLeft<T>(
points: T[],
getX: (p: T) => number,
Expand Down Expand Up @@ -274,6 +296,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 @@ -326,6 +349,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);

const theoreticalCacheHitRate = sorted.every((p) => typeof p.theoreticalCacheHitRate === 'number')
? buildMetric((p) => p.theoreticalCacheHitRate!)
Expand Down Expand Up @@ -362,6 +388,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 @@ -256,7 +259,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 @@ -302,7 +306,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 @@ -319,6 +330,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 @@ -431,17 +445,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
22 changes: 19 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,7 @@ export function buildGpuGroups<M extends GroupMeta>(
/** Token basis selected by the consumer; applies to official and overlay rows. */
tokenType?: CostType;
tcoBasis?: TcoBasis;
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 +199,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 +226,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 +261,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
7 changes: 7 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 All @@ -19,6 +20,9 @@ export interface GPUDataPoint {
outputThroughput: number;
inputThroughput: number;
cacheHitRate?: number;
cacheHitUnavailable?: boolean;
cacheHitSource?: PricingCacheSource;
cacheHitReason?: string;
theoreticalCacheHitRate?: number;
inputTokenShare?: number;
concurrency: number;
Expand Down Expand Up @@ -53,6 +57,9 @@ export interface InterpolatedResult {
inputTpPerMw: number;
outputTpPerMw: number;
cacheHitRate?: number;
cacheHitUnavailable?: boolean;
cacheHitUnavailableReason?: string;
cacheHitSources?: PricingCacheSource[];
theoreticalCacheHitRate?: number;
inputTokenShare?: number;
concurrency: number;
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 @@ -1218,6 +1219,44 @@ describe('buildGpuGroups', () => {

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 Down Expand Up @@ -60,6 +66,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 @@ -117,11 +124,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 @@ -211,6 +228,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 @@ -270,7 +288,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
Loading