Skip to content

[Port][CI] MFU metric, FLOPs oracle and metric-history gate (miles F10) - #494

Draft
zhou9402 wants to merge 8 commits into
vllm-project:mainfrom
zhou9402:miles-port/f10-mfu-metric-gate
Draft

zhou9402 wants to merge 8 commits into
vllm-project:mainfrom
zhou9402:miles-port/f10-mfu-metric-gate

Conversation

@zhou9402

Copy link
Copy Markdown

Ports miles' FLOPs model / MFU metric and the metric-history CI gate (SQLite store + declarative register_ci_gate).

Source: radixark/miles@ba28991834. Port is additive (new modules + new add_*_arguments() groups); slime-origin files are not restructured so future [Sync] Mirror Slime PRs stay clean. One commit per sub-PR; review each commit separately.

Commits:

  • 89ddd2a F10-0 (V5): FLOPs oracle verifier
  • 2cea872 F10-1 (A1a): full HF-config FLOPs model in vime
  • add5737 F10-2 (A1b): MFU metric + --mfu-peak-tflops + device table
  • fa0ef42 F10-3: metric-history SQLite storage layer
  • 8b75e2c F10-4: metric-history gate core (selection/constraints/evaluate_gate)
  • fc8fd9a F10-5: register_ci_gate declaration parsing
  • 87c5a98 F10-6 (V1): metric capture backend + gate runner + e2e verifier
  • 06e4cb4 F10-7: Buildkite wiring for the FLOPs oracle and metric-history gate

Verification: tests/ci/flops_oracle/ (V5) checks the FLOPs model against a hand-computed oracle; tests/ci/metric_history/ (V1) runs the gate end to end on a synthetic history. GPU suite baseline persistence still needs one real Buildkite run.

Merge order of the miles-port series: F10 → F02 → F06 → F11a → F11b → F05 → F09 → F04 → F03 → F07 → F08 → F01. train.py / vime/utils/arguments.py are touched by several branches; later ones are rebased after each merge.

🤖 Generated with Claude Code

zhou and others added 8 commits October 27, 2026 18:22
Port of miles@ba28991834 miles/utils/flops_utils.py (vendored as oracle).

Vendored miles flops_utils.py as the ground-truth oracle plus an 8-config
verifier (dense/GQA/MoE/MLA/GDN/KDA/DSA-indexer/mamba2/compressed-attention)
that fails on relative error >= 1e-6. Auto-skips until the vime
implementation lands in F10-1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of miles@ba28991834 miles/utils/flops_utils.py.

Adds vime/utils/flops_model.py: the miles analytical FLOPs model
(dense/GQA/MLA/MoE/GDN/KDA/DSA+QSA indexers/compressed attention/mamba2)
plus a model_flops_from_args adapter that loads the HF config from
args.hf_checkpoint and degrades to None on failure. Passes the V5 oracle
verifier (72 checks, 8 configs, rel err < 1e-6). MTP layers stay excluded,
matching miles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of miles@ba28991834 miles/utils/device_flops.py and the MFU block of
miles/backends/training_utils/metrics/perf.py.

Adds vime/utils/device_flops.py (peak BF16 TFLOPS table incl. AMD
MI300X/MI325X/MI355X with source URLs, detection via vime.utils.accelerator),
logs perf/actor_train_mfu + perf/mfu_peak_tflops + perf/actor_train_tflops_hf
in log_perf_data from the new HF-config FLOPs model, and adds the
--mfu-peak-tflops override flag. The slime-origin perf/actor_train_tflops
path is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of miles@ba28991834 tests/ci/metric_history/storage/{store,sqlite_store}.py.

Adds the MetricHistoryStore contract (runs + metric_values, trusted run flag,
baseline query on (test_path, backend, suite, metric_key, step)) and its
SQLite backend under tests/ci/metric_history/, with CI-neutral provenance
columns (ci_run_id/ci_run_attempt/pipeline replacing github_*/event_name).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of miles@ba28991834 tests/ci/metric_history/{selection,constraints,gate}.py.

selection.py and constraints.py are verbatim; gate.py drops the miles
CIRegistry dependency -- gate identity (test_path/backend) is passed
explicitly and register_ci_gate spec parsing is a lazy import (lands in
F10-5). Gate semantics unchanged: cold start INACTIVE, band constraints
against the mean of up to 20 trusted baselines, TRACKED for constraint-less
specs, ERROR on unselectable metrics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of miles@ba28991834 tests/ci/metric_history/register.py.

AST-parsed register_ci_gate marker with literal-only keyword validation,
GATE_DEFAULTS for the 8 whitelisted metrics (kept verbatim, incl. the two
tito keys reserved for the C1 port), and byte-stable canonical JSON
provenance keys. zip(strict=True) relaxed to plain zip for 3.9 compat
(vacuous: AST dict keys/values are always equal length).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of miles@ba28991834 miles/utils/tracking_utils/ci_history.py and the
gate-hook semantics of miles tests/ci/ci_utils.py.

Adds vime/observability/ci_metric_history.py (whitelist JSONL capture hooked
into logging_utils.log, gated on VIME_CI_GATE_RECORD_DIR), the run_gate.py
CLI (merge, evaluate, MIN_ENFORCED_BASELINES=10 enforcement, --write-baseline,
--mark-untrusted-sha, Buildkite env provenance), the --metric-history-path
flag with VIME_METRIC_HISTORY_PATH fallback, and verify_gate_e2e.py (V1):
12 seeded baselines, 2nd-run history read, steady PASS, tampered value FAIL,
tampered threshold FAIL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the V5 FLOPs oracle verifier, the metric_history pytest suite, and the
V1 gate e2e verifier to the plugin-contracts CPU step of
.buildkite/pipeline.yml; updates .buildkite/README.md command count; documents
the GPU opt-in path (record dir + artifact-uploaded SQLite store, no hostPath
assumption).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request ports the MFU metric and metric-history regression gate tooling from the miles repository to vime, including the analytical FLOPs model, SQLite-backed metric-history store, and Buildkite CI integration. Feedback on these changes highlights three key improvements: moving the transformers import inside the exception handler in train_metric_utils.py to prevent training crashes, coercing step counters to standard Python integers in ci_metric_history.py to support numpy and PyTorch types, and automatically creating the parent directory for the SQLite database in sqlite_store.py to avoid operational errors.

Comment on lines +22 to +27
def _mfu_model_flops(hf_checkpoint: str):
"""HF-config FLOPs model for MFU accounting, cached per checkpoint; None when unsizable."""
from transformers import AutoConfig

try:
hf_config = AutoConfig.from_pretrained(hf_checkpoint, trust_remote_code=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The from transformers import AutoConfig import is placed outside the try...except block. If transformers is not installed in the environment, this will raise an ImportError and crash the training process, violating the goal of the exception handler (accounting must never break training). Additionally, if hf_checkpoint is empty or not set, calling AutoConfig.from_pretrained will fail and log a noisy warning. Checking for an empty hf_checkpoint and moving the import inside the try block resolves both issues.

def _mfu_model_flops(hf_checkpoint: str):
    \"\"\"HF-config FLOPs model for MFU accounting, cached per checkpoint; None when unsizable.\"\"\"
    if not hf_checkpoint:
        return None

    try:
        from transformers import AutoConfig
        hf_config = AutoConfig.from_pretrained(hf_checkpoint, trust_remote_code=True)

Comment on lines +160 to +161
step = metrics.get(step_key)
recorder.log(metrics, step if isinstance(step, int) and not isinstance(step, bool) else None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The check isinstance(step, int) will evaluate to False if step is a numpy integer (e.g., numpy.int64) or a PyTorch 0-dimensional tensor, which are very common types for step counters in training loops. This causes the step to be recorded as None (and serialized as null in JSONL), which subsequently triggers a SelectionError in the CI gate when evaluating steps: \"all\". Coercing the step to a standard Python integer using int() with a fallback to None ensures robust step capture.

Suggested change
step = metrics.get(step_key)
recorder.log(metrics, step if isinstance(step, int) and not isinstance(step, bool) else None)
step = metrics.get(step_key)
if step is not None and not isinstance(step, bool):
try:
step = int(step)
except (TypeError, ValueError, RuntimeError):
step = None
else:
step = None
recorder.log(metrics, step)

Comment on lines +74 to +77
def __init__(self, database: str = ":memory:"):
# check_same_thread is left at its default; the store is meant for the
# single-threaded CI gate process and the test suite.
self._conn = sqlite3.connect(database)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If the parent directory of the SQLite database file does not exist, sqlite3.connect will raise a sqlite3.OperationalError: unable to open database file and crash the CI gate. Automatically creating the parent directory if it does not exist makes the store initialization much more robust when custom paths are configured.

    def __init__(self, database: str = \":memory:\"):
        # check_same_thread is left at its default; the store is meant for the
        # single-threaded CI gate process and the test suite.
        if database != \":memory:\":
            import os
            parent = os.path.dirname(database)
            if parent:
                os.makedirs(parent, exist_ok=True)
        self._conn = sqlite3.connect(database)

@read-the-docs-community

Copy link
Copy Markdown

@zhou9402
zhou9402 marked this pull request as draft October 28, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant