The reproducible harness: measuring cost, performance and energy in a single auditable experiment

Contents

Notation: amounts in euros (N €), decimals with a point, thousands with a thin space. The dollar sign is not used (on this site it is a formula delimiter).

TL;DR

The “data” series has produced three independent measurement axes: cost per million tokens (OpenCost + LiteLLM), performance under an SLO (GuideLLM + AIPerf) and energy per token (DCGM + Kepler). The problem is that the three have been measured in different articles, with different loads and at different times: they are not comparable with each other. This closing article describes the integrated harness that runs the three axes in the same experiment, on the same node (4×H100 SXM, a generic reference), with all the metadata fixed, the output in versioned JSON/CSV and an idempotent Kubernetes Job. The result is the 3-axis scorecard (€/1M tok, Wh/token, TTFT/ITL P99) that allows configurations to be compared on a multi-objective Pareto frontier and any figure to be audited with the bench to reproduce it.


Why the three axes must be measured together

The series’ opening post (The three axes) established the identity:

$$\text{CPM} = \frac{\text{cost/h}}{\text{throughput (tok/s)} \times 3.6 \times 10^{-3}}$$ $$\text{energy/token (Wh)} = \frac{\text{mean power (W)}}{\text{throughput (tok/s)} \times 3\,600}$$

Throughput is the common denominator. If it is measured in different experiments, different time, different load, different GPU temperature, then CPM and energy/token do not share a denominator: they are three anecdotes, not a scorecard. The harness captures them in the same time window, over the same load, with Prometheus windows aligned to the second. Only then is the scorecard row coherent by construction.

The second reason is reproducibility. The post Bias and reproducibility in benchmarking listed twelve biases that invalidate comparisons: an unpinned engine, an undeclared tokeniser, unfixed input/output lengths, missing warmup, a client outside the cluster. The harness removes all of them because the metadata are part of the Job, not of the documentation.


Architecture of the integrated bench

The harness has four layers. Each one is OSS, exports to Prometheus and lives in the same Kubernetes namespace:

LayerTool(s)Primary metricExport protocol
PerformanceGuideLLM (SLO sweep) + AIPerfTTFT P99, ITL P99, goodput (tok/s)native JSON/CSV + /metrics OpenMetrics
CostOpenCost + LiteLLM proxyCPM (€/1M tok), cost/requestREST API + Prometheus scrape
Energy (GPU)DCGM ExporterDCGM_FI_DEV_POWER_USAGE (W), DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION (mJ)Prometheus DaemonSet
Energy (pod)Keplerkepler_container_joules_total, energy per podPrometheus DaemonSet

MLPerf Power is used as an external comparability reference: its published results, with hardware documented in detail, allow the harness figures to be calibrated for plausibility. The in-house bench does not aim to be an MLPerf submission, but to be reproducible on your own cluster.

Namespace: benchmarkJob benchmark-runGuideLLM sweep (SLO)AIPerf fixed concurrencyLiteLLM proxytoken counting + CPMDCGM DaemonSetPOWER_USAGE, ENERGYKepler DaemonSetInference endpointvLLM / SGLangpinned model, FP8/FP16OpenCost€/GPU-h per pod/nsPrometheus15 s scrape30-day retentionScorecard exporterPromQL → JSON/CSVversioned in gitone row per (model, config, hardware)

The Kubernetes Job: the full YAML

The experiment runs as a versioned Kubernetes Job. All the relevant metadata are environment variables declared in the manifest: not in ad-hoc scripts, not in external documentation. The Job is idempotent (same name = same run) and leaves traces in the pod log and in the output volume.

apiVersion: batch/v1
kind: Job
metadata:
  name: bench-llama3-70b-fp8-h100x4-20260616
  namespace: benchmark
  labels:
    bench/model: llama3-70b
    bench/precision: fp8
    bench/engine: vllm-0.9.1
    bench/hardware: h100x4-sxm
    bench/isl: "1024"
    bench/osl: "256"
    bench/concurrency: "32"
    bench/tokenizer: meta-llama-3-tokenizer-v3
    bench/run-id: "20260616T1200"
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      serviceAccountName: bench-runner
      volumes:
        - name: results
          persistentVolumeClaim:
            claimName: bench-results-pvc
      initContainers:
        # Warmup: 60 s of traffic before the experiment
        - name: warmup
          image: ghcr.io/vllm-project/guidellm:0.4.2
          command:
            - guidellm
            - benchmark
            - --target
            - http://vllm-svc.inference.svc.cluster.local:8000
            - --rate-type
            - concurrent
            - --rate
            - "4"
            - --max-seconds
            - "60"
            - --data
            - prompt_tokens=1024,output_tokens=256
          env:
            - name: GUIDELLM_ENV
              value: production
      containers:
        - name: guidellm-sweep
          image: ghcr.io/vllm-project/guidellm:0.4.2
          command:
            - guidellm
            - benchmark
            - --target
            - http://vllm-svc.inference.svc.cluster.local:8000
            - --rate-type
            - sweep
            - --max-seconds
            - "120"
            - --data
            - prompt_tokens=1024,output_tokens=256
            - --output-path
            - /results/guidellm-$(BENCH_RUN_ID).json
          env:
            - name: BENCH_RUN_ID
              valueFrom:
                fieldRef:
                  fieldPath: metadata.labels['bench/run-id']
          volumeMounts:
            - name: results
              mountPath: /results

        - name: aiperf-concurrent
          image: nvcr.io/nvidia/aiperf:0.2.0
          command:
            - aiperf
            - profile
            - --url
            - http://vllm-svc.inference.svc.cluster.local:8000/v1
            - --model
            - meta-llama/Meta-Llama-3-70B-Instruct
            - --concurrency
            - "4,8,16,32"
            - --input-tokens
            - "1024"
            - --output-tokens
            - "256"
            - --num-requests
            - "200"
            - --output
            - /results/aiperf-$(BENCH_RUN_ID).json
          volumeMounts:
            - name: results
              mountPath: /results

        - name: scorecard-exporter
          image: python:3.12-slim
          command:
            - python
            - /scripts/export_scorecard.py
            - --run-id
            - "$(BENCH_RUN_ID)"
            - --prometheus
            - http://prometheus.monitoring.svc.cluster.local:9090
            - --output
            - /results/scorecard-$(BENCH_RUN_ID).json
          env:
            - name: BENCH_RUN_ID
              valueFrom:
                fieldRef:
                  fieldPath: metadata.labels['bench/run-id']
          volumeMounts:
            - name: results
              mountPath: /results

The Job name (bench-llama3-70b-fp8-h100x4-20260616) is the unique identifier of the run. Changing it is enough to register a variant. The labels are the metadata that the scorecard-exporter reads to enrich the output JSON.


Performance layer: GuideLLM and AIPerf

GuideLLM — the SLO-driven sweep

GuideLLM (a vLLM project) generates realistic traffic patterns, synchronous, concurrent, poisson, throughput, sweep, and captures full TTFT and ITL distributions (Red Hat Developer). The sweep mode ramps from idle to saturation over 10 rounds and identifies the knee: the maximum load where goodput ≈ throughput under the declared SLO. The output is JSON/CSV with all the percentiles per round, ready to be versioned.

For the harness, the bench’s reference SLO is:

MetricThresholdPercentile
TTFT500 msP99
ITL (TPOT)50 ms/tokP95
Error rate0.5 %

The Job command already appears in the YAML above. The JSON output includes, per round: rate (req/s), TTFT (P50/P95/P99), ITL (P50/P95/P99), throughput (tok/s) and goodput (tok/s). The goodput under the SLO at the knee is the value that enters the scorecard.

For the cross-verification layer of the data and comparability with published results, see the post GuideLLM in depth.

AIPerf — NVIDIA’s concurrency profiler

AIPerf (the successor to GenAI-Perf, repository ai-dynamo/aiperf) measures TTFT, ITL, throughput and latency distributions at fixed concurrencies (GitHub ai-dynamo/aiperf). Where GuideLLM gives the automatic sweep up to the knee, AIPerf gives the detailed profile at specific concurrencies (4, 8, 16, 32 in the example): it allows the throughput-latency curve to be characterised point by point.

The two are complementary: GuideLLM finds the knee automatically; AIPerf confirms it and characterises the behaviour in the neighbourhood of that knee. Both outputs go to the results volume with the same BENCH_RUN_ID. The deep analysis of AIPerf/GenAI-Perf is in GenAI-Perf in depth.

Metrics the harness extracts from AIPerf for the scorecard:

# TTFT P99 at concurrency 16 (the closest to the sweep's knee):
aiperf profile ... --concurrency 16 \
  | jq '.results[] | select(.concurrency==16) | .ttft_ms.p99'

# Goodput (tok/s) at concurrency 16:
aiperf profile ... --concurrency 16 \
  | jq '.results[] | select(.concurrency==16) | .output_token_throughput'

Cost layer: OpenCost and LiteLLM

OpenCost — the denominator in euros per GPU-hour

OpenCost (CNCF incubating, opencost.io) allocates Kubernetes cost to namespace, label, pod and container in real time (GitHub opencost/opencost). For the harness, OpenCost answers the question: how much does the vllm-svc pod cost in euros per hour during the experiment’s window?

The harness calls the OpenCost REST API when the experiment finishes:

# Cost of the inference namespace over the experiment window (1 hour)
curl -s "http://opencost.monitoring.svc.cluster.local:9003/allocation" \
  --data-urlencode 'window=2026-06-16T12:00:00Z,2026-06-16T13:00:00Z' \
  --data-urlencode 'aggregate=namespace' \
  --data-urlencode 'namespace=inference' \
  | jq '.data[0].inference.totalCost'

The returned cost (in EUR, configured with the node’s real prices) is divided by the throughput measured in that same window to obtain the CPM:

$$\text{CPM} = \frac{\text{cost}_\text{namespace/h} \times 10^6}{\text{goodput (tok/s)} \times 3\,600}$$

The post OpenCost: cost allocation in Kubernetes details the price configuration and the allocation by GPU label.

LiteLLM — the per-request token counter

LiteLLM (litellm.ai, GitHub BerriAI/litellm) acts as an OpenAI-compatible proxy with token accounting per request, model and team. In the harness, GuideLLM and AIPerf point at the LiteLLM endpoint (which in turn forwards to vLLM): every request is recorded with prompt_tokens, completion_tokens and cost (using the custom pricing configured for the on-prem node).

# litellm-config.yaml (fragment of custom on-prem pricing)
model_list:
  - model_name: llama3-70b-fp8
    litellm_params:
      model: openai/meta-llama/Meta-Llama-3-70B-Instruct
      api_base: http://vllm-svc.inference.svc.cluster.local:8000/v1
      api_key: sk-dummy
      input_cost_per_token: 0.00000109   # 1.09 EUR/1M tok on-prem
      output_cost_per_token: 0.00000109

The LiteLLM records are exported to Prometheus as litellm_request_total_tokens and litellm_spend_metric_total, which the scorecard exporter consumes to compute the real CPM per token type (input vs output).


Energy layer: DCGM and Kepler

DCGM Exporter — GPU power and energy

DCGM Exporter (GitHub NVIDIA/dcgm-exporter, docs) exposes GPU metrics on /metrics for Prometheus as a DaemonSet on the GPU nodes. The two energy metrics the harness uses:

DCGM metricTypeUnitUse in the harness
DCGM_FI_DEV_POWER_USAGEgaugeWinstantaneous power per GPU
DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTIONcountermJenergy accumulated since boot

To compute the energy consumed during the experiment’s window (without the idle baseline), the harness takes the difference of the counter before and after the sweep:

# Deployment as a DaemonSet (fragment of the official chart)
helm repo add gpu-helm-charts \
  https://nvidia.github.io/dcgm-exporter/helm-charts
helm install dcgm-exporter gpu-helm-charts/dcgm-exporter \
  --namespace monitoring \
  --set serviceMonitor.enabled=true \
  --set serviceMonitor.interval=15s

The DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION field (in mJ) allows the experiment’s energy to be computed with hardware-counter accuracy, not with an estimate:

$$\text{experiment energy (J)} = \left(\text{counter}_\text{end} - \text{counter}_\text{start}\right) \times 10^{-3}$$ $$\text{Wh/token} = \frac{\text{experiment energy (J)}}{3\,600 \times \text{generated tokens}}$$

Kepler — pod-level energy

Kepler (CNCF sandbox, GitHub sustainable-computing-io/kepler) uses eBPF to estimate energy consumption at container and pod level, exporting kepler_container_joules_total to Prometheus (Red Hat Emerging Technologies). It combines RAPL (CPU/DRAM), NVML (GPU) and regression models when no sensors are available.

In the harness, Kepler complements DCGM: DCGM gives the hardware measurement of the GPU (more precise for GPU-intensive loads such as LLM inference), while Kepler attributes the energy to the specific vLLM pod (including the node’s CPU contribution). The main metric the harness consumes:

# Energy of the vllm-svc pod during the experiment (J)
increase(
  kepler_container_joules_total{
    container_namespace="inference",
    container_name="vllm"
  }[${BENCH_DURATION}]
)

The comparative analysis of DCGM vs Kepler vs Zeus is in Energy tools: deployment, precision and overhead.

The MLPerf Power reference

MLPerf Power (MLCommons, IEEE HPCA 2025 paper) establishes the protocol for measuring the energy efficiency of ML systems with high-precision external measurement. The in-house bench is not an MLPerf submission (that requires external power meters and committee review), but its published results are the calibration reference: if the harness gives a result of the same order as the MLPerf submission for the same hardware with the same model, the measurement is plausible. If it differs by more than a factor of 2, there is a methodological problem. See the post MLPerf Power: energy efficiency for the current reference data.


Unifying the metrics: the PromQL queries

The scorecard-exporter is the Job container that, once GuideLLM and AIPerf have finished, collects all the metrics from Prometheus and builds the JSON of the scorecard row. The key queries:

# Mean GPU power during the sweep (W) — 4×H100 node
avg_over_time(
  sum(DCGM_FI_DEV_POWER_USAGE{Hostname=~"gpu-node-.*"})[${BENCH_DURATION}:15s]
)

# Total GPU energy during the sweep (mJ → convert to J)
(
  sum(DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION{Hostname=~"gpu-node-.*"}) offset 0
  -
  sum(DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION{Hostname=~"gpu-node-.*"}) offset ${BENCH_DURATION}
) * 0.001

# Tokens generated during the sweep (from LiteLLM)
increase(
  litellm_request_total_tokens{model="llama3-70b-fp8", token_type="completion"}[${BENCH_DURATION}]
)

# Cost of the inference namespace over the window (from the OpenCost API)
# → REST call at the start and at the end of the Job, difference of accrual

The ${BENCH_DURATION} variable is the real duration of the sweep (in Prometheus format, e.g. 22m), which the exporter computes as end_ts - start_ts and substitutes into every query.

The output JSON of each run has this structure:

{
  "run_id": "20260616T1200",
  "metadata": {
    "model": "meta-llama/Meta-Llama-3-70B-Instruct",
    "engine": "vllm-0.9.1",
    "precision": "fp8",
    "hardware": "4xH100-SXM-80GB",
    "isl_tokens": 1024,
    "osl_tokens": 256,
    "tokenizer": "meta-llama-3-tokenizer-v3",
    "slo_ttft_p99_ms": 500,
    "slo_itl_p95_ms": 50,
    "bench_tool_guidellm": "0.4.2",
    "bench_tool_aiperf": "0.2.0",
    "dcgm_exporter": "3.3.9",
    "kepler": "0.10.2"
  },
  "performance": {
    "goodput_tok_s": 3120,
    "ttft_p99_ms": 487,
    "itl_p95_ms": 42,
    "throughput_peak_tok_s": 3890,
    "elbow_concurrency": 28
  },
  "cost": {
    "cpm_eur_1m": 0.97,
    "gpu_cost_eur_h": 10.8,
    "cost_window_eur": 3.24
  },
  "energy": {
    "wh_per_token": 0.00044,
    "j_per_token": 1.58,
    "power_mean_w": 4924,
    "energy_total_kwh": 0.287,
    "pue": 1.4,
    "wh_per_token_pue_adjusted": 0.000616
  },
  "carbon": {
    "grid_intensity_gco2_kwh": 40,
    "co2_per_1m_tokens_g": 24.6
  }
}

All the fields are computed from the same time windows. The JSON is versioned in git alongside the harness code. Reproducing any scorecard row is: git checkout <run-id> && kubectl apply -f job.yaml.


The 3-axis scorecard: table and interpretation

The following table illustrates how five configurations of the same model (Llama 3 70B) on the same reference node (4×H100 SXM) compare with the harness. The figures are illustrative of the order of magnitude (the bench fills them in with real measurements):

ConfigEnginePrecisionCPM (€/1M)Goodput (tok/s)Wh/tokTTFT P99 (ms)ITL P95 (ms)CO2 /1M (g, FR)
AvLLM 0.9FP161.641,8900.000744984929.6
BvLLM 0.9FP80.973,1200.000444874217.6
CSGLang 0.4FP80.883,4100.000414213816.4
DvLLM 0.9FP8, ISL 5121.312,2400.000562943122.4
EvLLM 0.9FP8, max-batch 1280.843,5900.000407215516.0

How to read the table:

  • B vs A: FP8 over FP16 cuts CPM by 41 % and energy per token by 41 % thanks to the same higher throughput. Both meet the SLO (TTFT P99 < 500 ms, ITL P95 < 50 ms). B dominates A on all three axes: it is Pareto-superior.
  • C vs B: SGLang gives 9 % more goodput and 9 % lower CPM with FP8. Both meet the SLO. C is Pareto-superior to B (if the engine is indifferent for the stack).
  • D vs B: a shorter ISL (512 tok) lowers latency (TTFT P99 294 ms vs 487 ms) but lowers goodput and raises CPM. If the use case demands TTFT < 300 ms, D is the candidate; if TTFT < 500 ms is enough, B is better on cost and energy.
  • E vs C: max-batch 128 raises goodput (+5 %) and lowers CPM, but the TTFT P99 breaks the SLO (721 ms > 500 ms) and so does the ITL P95 (55 ms > 50 ms). E has the best raw throughput but is outside the SLO: it is not a valid candidate for the interactive chat use case.

The scorecard’s Pareto frontier, under the declared SLO, includes only A, B, C and D (E falls out for breaking the SLO). Of those four, C dominates B which dominates A. D only enters the frontier if the use case demands TTFT P99 < 300 ms. The decision is not a number; it is the whole row plus the SLO.


The multi-objective Pareto frontier

With three minimisation metrics, CPM (€/1M tok), Wh/tok and TTFT P99 (ms), the Pareto frontier is defined as the set of configurations where none dominates another on all three axes simultaneously. Formally, config \( i \) dominates config \( j \) if:

$$\text{CPM}_i \leq \text{CPM}_j \;\land\; \text{Wh/tok}_i \leq \text{Wh/tok}_j \;\land\; \text{TTFT}_{i} \leq \text{TTFT}_{j}$$

with at least one strict inequality. The scorecard allows it to be computed over the set of runs with a simple vector operation. In the table’s example, the frontier under the SLO (with E excluded) is \(\{C, D\}\): C dominates on cost/energy/goodput; D dominates on latency. Between C and D the choice depends on the TTFT SLO of the specific use case.

CPM (EUR/1M tok) ↑ worsegoodput (tok/s) → better1.701.401.100.900.841,8002,2003,1003,4003,600A (FP16)B (FP8)C (SGLang FP8) ★D (ISL 512)E (SLO broken)Pareto frontier(A dominates only on latency; C+D on the frontier under the SLO)

Reproducible design: the twelve mandatory metadata

The checklist that closes the bias dossier (Bias and reproducibility). For the harness JSON to be auditable, the twelve fields must be present in every run:

#FieldExampleWhy it is mandatory
1Serving engine versionvllm-0.9.1the same model can perform differently across versions
2Model version (commit/tag)meta-llama/Meta-Llama-3-70B-Instruct@sha256:abcavoids ambiguity between variants of the same name
3Precisionfp8FP16 vs FP8 changes throughput and latency
4Tokeniser and versionmeta-llama-3-tokenizer-v3the ISL/OSL in tokens depends on the tokeniser
5ISL (input sequence length)1024 tokchanges the prefill time and the knee
6OSL (output sequence length)256 tokchanges the decode time
7Hardware (model and count)4×H100-SXM-80GBthe basis of any comparison
8Maximum concurrency tested32defines the sweep’s range
9Bench tool versionguidellm-0.4.2, aiperf-0.2.0different versions can give different metrics
10Declared SLOTTFT P99 < 500 ms, ITL P95 < 50 msthe knee depends on the SLO; without it there is no goodput
11Warmup duration60 swithout warmup the KV cache is cold and the first rounds are biased
12Harness version / run-id20260616T1200identifies the run so it can be reproduced with git checkout

A scorecard missing any of these twelve fields is an anecdote. With all twelve, it is an auditable datum. The run-id in the Job name (bench-llama3-70b-fp8-h100x4-20260616) implicitly incorporates fields 1-3 and 7, forcing the Job name to change with every variant, which makes it impossible to overlap runs in the same namespace.


Idempotence and versioning of results

The Kubernetes Job is idempotent: if the same manifest is applied again (same name), the Job already exists and Kubernetes does not relaunch it (backoffLimit: 0 policy). This avoids accidental duplicate runs. To relaunch, the Job has to be deleted (kubectl delete job <name>) or the run-id changed.

Results are versioned with the following directory convention in the harness’s git repository:

results/
  20260616T1200/
    guidellm-20260616T1200.json
    aiperf-20260616T1200.json
    scorecard-20260616T1200.json
  20260617T0900/
    guidellm-20260617T0900.json
    aiperf-20260617T0900.json
    scorecard-20260617T0900.json
scorecard-aggregate.csv   # all rows, for analysis and charts

The scorecard-aggregate.csv is the scorecard table in flat format: each row is a run, each column a field of the JSON. It is generated with:

# Generates the aggregate CSV from all the JSON files in results/
python scripts/aggregate_scorecards.py results/ > scorecard-aggregate.csv

The CSV is what feeds the Pareto charts, the reports and the architecture decisions. It is versioned in git; its diff is the configuration change.


The harness is not a standalone article: it is the synthesis of the twenty-eight articles. Each tool has its deep dive:

The harness closes the series’ dossier because it makes the opening article’s promise possible: “when someone disputes a number in the proposal, the answer is not ‘a blog says so’, but ’this is the bench, this is the methodology, reproduce it’”. With the run-id and the git repo, reproducing is one command.


Full flow of a benchmarking session

The harness’s standard operating procedure, from start to finish:

# 1. Make sure the inference endpoint is up
kubectl get svc vllm-svc -n inference

# 2. Verify that DCGM and Kepler are scraping
curl -s http://prometheus.monitoring.svc.cluster.local:9090/api/v1/query \
  --data-urlencode 'query=DCGM_FI_DEV_POWER_USAGE' | jq '.data.result | length'

# 3. Record the energy counter BEFORE the experiment
PRE_ENERGY=$(kubectl exec -n monitoring dcgm-exporter-xxx -- \
  curl -s localhost:9400/metrics \
  | grep DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION | awk '{sum+=$2} END{print sum}')

# 4. Launch the Job (unique name per run)
kubectl apply -f jobs/bench-llama3-70b-fp8-h100x4-20260616.yaml

# 5. Wait for it to finish
kubectl wait --for=condition=complete job/bench-llama3-70b-fp8-h100x4-20260616 \
  -n benchmark --timeout=3600s

# 6. Record the energy counter AFTER
POST_ENERGY=$(kubectl exec -n monitoring dcgm-exporter-xxx -- \
  curl -s localhost:9400/metrics \
  | grep DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION | awk '{sum+=$2} END{print sum}')

# 7. Collect the results from the volume
kubectl cp benchmark/bench-run-pod:/results/ ./results/20260616T1200/

# 8. Compute the experiment's energy (mJ → kWh)
python scripts/calc_energy.py \
  --pre $PRE_ENERGY --post $POST_ENERGY \
  --run-id 20260616T1200

# 9. Add a row to the aggregate CSV
python scripts/aggregate_scorecards.py results/ > scorecard-aggregate.csv

# 10. Version it
git add results/20260616T1200/ scorecard-aggregate.csv
git commit -m "bench: llama3-70b fp8 4xH100 ISL1024 OSL256 (20260616T1200)"

Steps 6-9 are candidates for automation as a second post-processing Job launched when the main one completes (with ownerReferences or a clean-up CronJob). In the harness’s basic state, the manual steps are precisely the ones that force a review of the result before versioning it.


Cost of the experiment

A GuideLLM sweep of 10 rounds at 120 seconds per round occupies the node for ~22-26 minutes. The AIPerf profile at 4 concurrencies (200 requests each) adds ~8-12 minutes. Total per run: ~35-40 minutes of a 4×H100 node.

At the reference amortised cost (~10.8 €/h for 4×H100 on-prem):

$$\text{cost per run} \approx 10.8 \times \frac{38}{60} \approx 6.84 \text{ EUR}$$

At the reference European cloud price (4 × 2.73 = 10.92 €/h):

$$\text{cost per run (cloud)} \approx 10.92 \times \frac{38}{60} \approx 6.92 \text{ EUR}$$

A continuous benchmarking programme (10 configurations × 3 models × 2 sweeps/week) adds up to ~415 EUR/week. That is why the harness includes short sweeps (5 rounds, ~15 minutes) for CI and full sweeps for releases.


What the harness does not measure (and why)

Missing dimensionReasonWhere it is covered
Output quality (MMLU, HumanEval)requires a quality evaluation bench, not a serving oneLLM quality benchmarks
Training throughputthe harness is exclusively for inferenceoutside the scope of the “data” series
Network and storage costOpenCost can attribute it, but it requires additional configurationOpenCost: cost allocation
Hourly carbon intensityuse the ElectricityMaps API + the measured energyFrom the watt to carbon: PUE and grid mix
Client-server network latencythe Job runs inside the cluster; WAN latency requires an external clientdocument as additional metadata

Sources