Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4e243b4
fix(summaries): discard long excerpt lines with bounded streaming reads
morluto Aug 11, 2026
4252a68
fix(oracles): derive validation status from coherent receipt evidence
morluto Aug 11, 2026
dd9148c
fix(inference): compare protocol facets structurally instead of delim…
morluto Aug 11, 2026
f2133ac
fix(inference): record effective quantization instead of rewriting un…
morluto Aug 11, 2026
02646ca
fix(release): pin twine through lockfile and make final artifacts imm…
morluto Aug 11, 2026
0486592
fix(vllm): preserve exact percentile identity and reject incoherent a…
morluto Aug 11, 2026
17e945a
chore: exclude workflow file from release commit (pushable separately)
morluto Aug 11, 2026
b4d6734
fix(faults): randomize or counterbalance treatment order instead of a…
morluto Aug 11, 2026
39cb8cc
fix(workloads): validate scalar choices by exact JSON type and value …
morluto Aug 11, 2026
529aefb
fix: address oracle parsing review feedback
morluto Aug 11, 2026
7d3ef1b
fix(workloads): preserve typed scaling values
morluto Aug 11, 2026
9cf4a3f
test: update collection receipt for scaling regression
morluto Aug 11, 2026
cd57b18
fix(experiments): support explicit factor baselines
morluto Aug 11, 2026
0a221bf
ci(release): run twine from the locked environment
morluto Aug 11, 2026
7aeb3c6
fix(faults): record randomized block orders
morluto Aug 11, 2026
d79dd8e
fix(inference): require bound quantization for validity
morluto Aug 11, 2026
e6ba710
test(inference): bind complete protocol quantization
morluto Aug 11, 2026
6c19bc1
fix(experiments): preserve explicit baseline identities
morluto Aug 11, 2026
6ebc992
fix(scaling): retain numeric input kinds
morluto Aug 11, 2026
4762de6
fix(scaling): reject mixed numeric input fits
morluto Aug 11, 2026
2ee72e6
fix(domain): bound variant label length
morluto Aug 11, 2026
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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ dev = [
"pyarrow-stubs>=20.0.0.20260625",
"ruff>=0.11",
"scipy-stubs>=1.18.0.1,<1.19",
"twine>=6.1",
Comment thread
morluto marked this conversation as resolved.
"vulture>=2.14",
]

Expand Down
26 changes: 24 additions & 2 deletions src/flameox/adapters/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,17 @@ class AIPerfCorrelationSummary(ContractModel):
# vLLM aggregate benchmark-result JSON normalization
# ---------------------------------------------------------------------------
#
def _percentile_label(percentile: int | float) -> str:
"""Return a canonical label for a percentile rank, preserving fractional precision.

``int()`` truncation made p99.1 and p99.9 both produce ``p99``, losing
the exact percentile identity in the metric name.
"""
if float(percentile).is_integer():
return str(int(percentile))
return str(float(percentile))


# vLLM's ``benchmark_serving.BenchmarkMetrics`` dataclass is serialized by the
# Mooncake replayer (and other vLLM benchmark scripts) as a JSON object whose
# percentile fields are lists of ``[percentile, value_ms]`` pairs. The parser
Expand Down Expand Up @@ -907,6 +918,10 @@ def non_negative_latency(self) -> VllmAggregateMetrics:
"median_itl_ms",
"mean_e2el_ms",
"median_e2el_ms",
"std_ttft_ms",
"std_tpot_ms",
"std_itl_ms",
"std_e2el_ms",
):
if getattr(self, name) < 0:
raise ValueError(f"{name} must be non-negative")
Expand Down Expand Up @@ -945,6 +960,13 @@ class VllmResultDocument(ContractModel):
actual_duration: Annotated[float, Field(ge=0)]
time_scale: Annotated[float, Field(gt=0)] = 1.0

@field_validator("actual_duration", "time_scale")
@classmethod
def finite_duration_and_scale(cls, value: float) -> float:
if not math.isfinite(value):
raise ValueError("duration and time_scale must be finite")
return value

@model_validator(mode="after")
def totals_match(self) -> VllmResultDocument:
if self.successful_requests + self.failed_requests != self.total_requests:
Expand Down Expand Up @@ -1038,7 +1060,7 @@ def _normalize_document(payload: dict[str, Any]) -> dict[str, Any]:
"successful_requests": completed,
"failed_requests": max(0, total_requests - completed),
"total_requests": total_requests,
"actual_duration": payload.get("duration", 0.0),
"actual_duration": payload.get("duration"),
"time_scale": 1.0,
}

Expand Down Expand Up @@ -1163,7 +1185,7 @@ def add(
add(f"vllm.{label}.std_ms", std, unit="ms", aggregation="std", extra={"stat": "std"})
for percentile, value in getattr(metrics, f"percentiles_{metric}_ms"):
add(
f"vllm.{label}.p{int(percentile)}_ms",
f"vllm.{label}.p{_percentile_label(percentile)}_ms",
float(value),
unit="ms",
aggregation="percentile",
Expand Down
10 changes: 8 additions & 2 deletions src/flameox/analysis/inference_protocol.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import json
from collections.abc import Callable
from enum import StrEnum
from typing import Annotated, Literal
Expand Down Expand Up @@ -405,9 +406,14 @@ def _facets() -> tuple[_FacetGetter, ...]:


def _normalize(value: object) -> str:
"""Return a display string for a protocol facet value.

For dict values, use canonical JSON serialization (sorted keys) so that
keys/values containing commas or equals signs cannot produce colliding
normal forms. Plain scalar values use ``str()`` as before.
"""
if isinstance(value, dict):
items = sorted(value.items(), key=lambda item: item[0])
return ",".join(f"{k}={v}" for k, v in items)
return json.dumps(value, sort_keys=True, ensure_ascii=False)
if isinstance(value, bool):
return "true" if value else "false"
if value is None:
Expand Down
5 changes: 5 additions & 0 deletions src/flameox/application/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@
"parse_inference_tool_discovery",
"probe_existing_vllm_server",
"render_evidence_summary_markdown",
"scalar_contains",
"scalar_equal",
"scalar_identity",
"scalar_identity_set",
"scalar_subset",
"workspace_status",
]

Expand Down
53 changes: 43 additions & 10 deletions src/flameox/application/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
_FactorExperimentConfig,
_OutcomeExperimentConfig,
_ScaledLegacyExperimentConfig,
scalar_contains,
scalar_equal,
scalar_identity_set,
scalar_subset,
)
from flameox.catalog import Catalog
from flameox.domain import (
Expand Down Expand Up @@ -139,6 +143,7 @@ class ExperimentPlan(ContractModel):
execution_policy: ExecutionPolicy
variant_parameter: str
variants: tuple[str, ...]
baseline_variant: str | None = None
factors: dict[str, tuple[JsonValue, ...]] = Field(default_factory=dict)
parameter_overrides: dict[str, JsonValue]
blocks: tuple[ExperimentBlock, ...]
Expand Down Expand Up @@ -660,6 +665,11 @@ async def plan(
execution_policy=execution_policy,
variant_parameter=variant_parameter,
variants=variants,
baseline_variant=(
self._factor_label(config.baseline_value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve typed identity in baseline selection

When a treatment factor contains type-distinct values with the same display label, such as integer 1 and string "1", converting baseline_value through _factor_label() collapses both to "1". The plan then cannot distinguish the declared baseline: trials and run-set selections are grouped under the same label, causing automatic comparison to be skipped or outcome failures to be attributed to the wrong side. Store the typed scalar identity rather than only its display label.

AGENTS.md reference: AGENTS.md:L46-L49

Useful? React with 👍 / 👎.

if isinstance(config, _FactorExperimentConfig) and config.baseline_value is not None
else None
),
factors={
name: tuple(cast(JsonValue, value) for value in values)
for name, values in factors.items()
Expand Down Expand Up @@ -903,12 +913,26 @@ async def report(message: str) -> None:
"Automatic experiment comparison currently requires pyperf measurements."
)
else:
comparison_run_sets: tuple[RunSet, ...] = run_sets
if not plan.baseline_variant:
Comment thread
morluto marked this conversation as resolved.
Outdated
limitations.append(
"Baseline was determined by list position, not an explicit "
"baseline_value. Reordering the treatment list reverses the "
"comparison direction."
)
else:
comparison_run_sets = tuple(
sorted(
run_sets,
key=lambda run_set: run_set.selection["variant"] != plan.baseline_variant,
Comment thread
morluto marked this conversation as resolved.
Outdated
)
)
comparison = await run_atomic_thread(
lambda: ComparisonService(self.workspace).record(
parse_compare_run_sets_request(
{
"baseline_run_set_id": run_sets[0].run_set_id,
"candidate_run_set_id": run_sets[1].run_set_id,
"baseline_run_set_id": comparison_run_sets[0].run_set_id,
"candidate_run_set_id": comparison_run_sets[1].run_set_id,
"experiment_id": plan.experiment.experiment_id,
"metric": plan.experiment.primary_metric,
"unit": (
Expand Down Expand Up @@ -988,7 +1012,7 @@ def _materialize_combinations(
matches = [
name
for name, choices in workload_parameters.items()
if set(config.variants).issubset(set(choices))
if scalar_subset(list(config.variants), list(choices))
]
if not matches:
raise DomainError(
Expand Down Expand Up @@ -1018,7 +1042,9 @@ def _materialize_combinations(
ErrorCode.WORKSPACE_INVALID,
f"Experiment factor {name!r} is not a workload parameter.",
)
if len(set(values)) != len(values) or not set(values).issubset(set(allowed)):
if len(scalar_identity_set(list(values))) != len(values) or not scalar_subset(
list(values), list(allowed)
):
Comment thread
morluto marked this conversation as resolved.
raise DomainError(
ErrorCode.WORKSPACE_INVALID,
f"Experiment factor {name!r} contains duplicate or undeclared values.",
Expand All @@ -1040,7 +1066,7 @@ def _materialize_combinations(
ErrorCode.WORKSPACE_INVALID,
"Explicit combinations must contain every declared factor exactly once.",
)
if any(combination[name] not in factors[name] for name in factor_names):
if any(not scalar_contains(combination[name], factors[name]) for name in factor_names):
raise DomainError(
ErrorCode.WORKSPACE_INVALID,
"Explicit combination contains an undeclared factor value.",
Expand All @@ -1060,7 +1086,7 @@ def _materialize_combinations(
ErrorCode.WORKSPACE_INVALID,
"Every exclusion must name at least one declared factor.",
)
if any(value not in factors[name] for name, value in rule.items()):
if any(not scalar_contains(value, factors[name]) for name, value in rule.items()):
raise DomainError(
ErrorCode.WORKSPACE_INVALID,
"Exclusion contains an undeclared factor value.",
Expand All @@ -1069,7 +1095,7 @@ def _materialize_combinations(
combination
for combination in combinations
if not any(
all(combination[name] == value for name, value in rule.items())
all(scalar_equal(combination[name], value) for name, value in rule.items())
for rule in config.exclude
)
)
Expand Down Expand Up @@ -1102,7 +1128,10 @@ def _outcome_result(
selected = [
trial
for trial in trials
if trial.factors.get(plan.variant_parameter) == treatment_value
if scalar_equal(
cast(Scalar, trial.factors.get(plan.variant_parameter)),
cast(Scalar, treatment_value),
)
]
attempted = sum(trial.outcome is not TrialOutcome.UNATTEMPTED for trial in selected)
eligible = sum(
Expand Down Expand Up @@ -1211,9 +1240,13 @@ def _outcome_result(
disposition = ExperimentOutcomeDisposition.INSUFFICIENT_EVIDENCE
elif not failures:
disposition = ExperimentOutcomeDisposition.ALL_CLEAN
elif len(plan.variants) == 2 and failed_treatments == {plan.variants[0]}:
elif len(plan.variants) == 2 and failed_treatments == {
plan.baseline_variant or plan.variants[0]
}:
disposition = ExperimentOutcomeDisposition.BASE_ONLY_FAILURE
elif len(plan.variants) == 2 and failed_treatments == {plan.variants[1]}:
elif len(plan.variants) == 2 and failed_treatments == {
v for v in plan.variants if v != (plan.baseline_variant or plan.variants[0])
}:
disposition = ExperimentOutcomeDisposition.CANDIDATE_ONLY_FAILURE
else:
disposition = ExperimentOutcomeDisposition.MIXED
Expand Down
8 changes: 6 additions & 2 deletions src/flameox/application/faults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import json
import random
import shutil
import socket
from collections.abc import Awaitable, Callable
Expand Down Expand Up @@ -250,6 +251,9 @@ async def plan(
)
blocks: list[ExperimentBlock] = []
for block_number in range(1, config.blocks * config.repetitions + 1):
cell_treatments = list(treatments)
generator = random.Random(f"{config.random_seed}:{block_number}")
generator.shuffle(cell_treatments)
Comment thread
morluto marked this conversation as resolved.
cells = tuple(
ExperimentCell(
trial_id=digest_model(
Expand All @@ -260,12 +264,12 @@ async def plan(
factors={"scenario": treatment},
parameters={},
)
for treatment in treatments
for treatment in cell_treatments
)
blocks.append(
ExperimentBlock(
block_id=f"fault-block-{block_number:04d}",
order=treatments,
order=tuple(cell_treatments),
cells=cells,
)
)
Expand Down
2 changes: 1 addition & 1 deletion src/flameox/application/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -1556,7 +1556,7 @@ def _protocol_identity(
model_revision=plan.model_revision,
tokenizer_id=plan.tokenizer or plan.model,
tokenizer_revision=plan.tokenizer_revision or plan.model_revision,
quantization=plan.quantization or "none",
quantization=plan.quantization,
Comment thread
morluto marked this conversation as resolved.
),
server=ServerConfigIdentity(
backend=plan.server_provider.value,
Expand Down
21 changes: 19 additions & 2 deletions src/flameox/application/summaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import re
from enum import StrEnum
from typing import Annotated, Literal, cast
from typing import Annotated, Literal, TextIO, cast

from pydantic import Field, JsonValue, model_validator

Expand All @@ -28,6 +28,23 @@
from flameox.models import ContractModel
from flameox.storage import ArtifactStore, JsonRecordStore, RunStore, Workspace

_DISCARD_CHUNK_SIZE = 4096


def _discard_rest_of_line(stream: TextIO) -> None:
"""Discard the remainder of an overlong line without materializing it.

Instead of calling unbounded ``stream.readline()`` which loads the
entire remaining line into memory, read fixed-size chunks until a
newline or EOF is encountered. Peak memory is bounded by
``_DISCARD_CHUNK_SIZE``, not by the physical line length.
"""
while True:
chunk = stream.readline(_DISCARD_CHUNK_SIZE)
if not chunk or chunk.endswith("\n"):
break


_ANSI_ESCAPE = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))")
_CONTROL = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")

Expand Down Expand Up @@ -566,7 +583,7 @@ def _excerpt(self, artifact_id: str) -> tuple[tuple[str, ...], bool]:
break
if len(line) == 201 and not line.endswith("\n"):
truncated = True
stream.readline()
_discard_rest_of_line(stream)
selected.append(_CONTROL.sub("", _ANSI_ESCAPE.sub("", line.rstrip("\r\n"))))
if stream.readline(1):
truncated = True
Expand Down
Loading
Loading