Skip to content

Add fastermodels.measurement: latency rows and quality rows, validated - #10

Merged
nathanhubens merged 3 commits into
mainfrom
feat/measurement-rows
Sep 29, 2026
Merged

nathanhubens merged 3 commits into
mainfrom
feat/measurement-rows

Conversation

@nathanhubens

@nathanhubens nathanhubens commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Adds fastermodels.measurement: the published definition of an on-device measurement, and its validator. Two row kinds, because speed and accuracy do not describe the same object.

  • Latency row — one engine timed on one device. Identified by the model's graph without its weights (graph_hash), the numeric precision, the batch size, the device and its software stack, and whether the weights were trained or random: speed depends on the graph, not on the weights, so latency can be measured (and a latency model trained) on random-weight models. Carries p50/p90/p99, the spread over repeats, the device conditions (power mode, locked clocks, temperature, throttling) and what the engine ran (layer count, reformatting layers, per-layer precisions).
  • Quality row — one artifact's accuracy on one eval set, measured through the deployed engine: accuracy with its confidence interval, argmax parity against the reference model, licences.

API: FIELDS (per kind), validate(row) (every problem, by field), status(row) (VALID / INVALID with reasons — measurement validity, never a verdict against a budget), key(row) (per kind; a software update on the device gives a new key), read_rows / write_row (JSON Lines, append-only, refusing malformed and duplicate rows), join(latency_rows, quality_rows) (VALID rows paired on the engine), throughput(row) (derived), to_parquet (one kind per file, optional parquet extra), graph_hash(onnx_path) (optional onnx extra). Reached as fastermodels.measurement; nothing is added to the package top level.

Tests

nbdev_test on the full suite: Success, including a real Parquet round trip and graph_hash on real exports (same shape with other weights → same hash; another width, a renamed initializer or another element type → a different one). Export and clean are idempotent; __version__ is unchanged.

A measurement row records one session of one model variant on one device
and software stack, at one precision and one batch size, on one date:
latency percentiles and their spread over repeats, device conditions
(power mode, clock locking, temperature, throttling), the accuracy with
its confidence interval measured through the deployed engine, and argmax
parity against the reference model. Rows are stored as JSON Lines.

- FIELDS: the schema as data (type, required, rules); SCHEMA_VERSION = 1.
- validate(row): every problem, by field name.
- status(row): whether the measurement is VALID and why not — parity
  below its floor, throttling, latency spread above 5 %, accuracy not
  measured through the deployed engine, latency not measured on the
  device, or any of these not recorded. It is a validity status, never a
  verdict against a latency or accuracy budget.
- key(row): the identity a row is unique on; a software update on the
  device gives a new key.
- read_rows / write_row: JSON Lines, append-only, refusing malformed and
  duplicate rows. to_parquet for publishing (optional `parquet` extra).
- throughput(row) is derived, never stored.

Reached as `fastermodels.measurement`; the generic names stay off the
package top level.
How fast an engine runs depends on its graph (architecture, widths,
numeric precision) and on the device and software stack, not on the
values of its weights; how accurate it is depends on the weights and the
evaluation set. A single row forced every speed measurement to carry an
accuracy. Each row now has a `kind`:

- latency: one engine timed on one device. It needs no accuracy, so an
  engine built from random weights (`weights='random'`) is measured like
  any other, which is what a latency predictor trains on. Graph fields
  (`graph_hash`, widths through macs/params, recipe), the engine's layer
  counts and the precisions it actually ran, device, conditions and
  latency percentiles.
- quality: one artifact's accuracy on one evaluation set, through the
  engine it names (`engine_hash`, None only for the development model).

The identity and provenance fields are defined once and shared.
`FIELDS[kind]` is the schema; validate, status and key dispatch on the
kind, a missing or unknown kind is one clear problem, and key uniqueness
in a file is per kind. `join` pairs the VALID rows of both kinds that
share an engine_hash, keeping identity per side (latency_run_id,
quality_run_id, ...) and refusing two sides that disagree on the same
engine. `to_parquet` writes one kind per file.

`graph_hash(onnx_path)` hashes the ONNX graph with every initializer's
values removed and its name, shape and type kept: two models of the same
shape share it, another width or a renamed weight does not. It needs the
new optional `onnx` extra.
@nathanhubens nathanhubens changed the title Add fastermodels.measurement: one on-device measurement row, validated Add fastermodels.measurement: latency rows and quality rows, validated Sep 29, 2026
to_parquet went through a pandas DataFrame, so the column types of the
published file were whatever pandas inferred: a string column with a
missing value read back as NaN under pandas 3 and as None under pandas 2,
and a column nobody recorded had no type at all. A published file must
not depend on the reader.

The schema is now derived from FIELDS[kind]: str is a string column, int
int64, float float64, bool bool, a dict a string holding canonical JSON,
and a field that is not required is nullable. The table is built and
written with pyarrow alone; pandas is no longer used, and the `parquet`
extra is pyarrow only.

The round-trip test reads the file back with pyarrow and compares it to
the rows, None preserved, and pins the type and nullability of the
columns.
@nathanhubens
nathanhubens merged commit c5df9df into main Sep 29, 2026
2 checks passed
@nathanhubens
nathanhubens deleted the feat/measurement-rows branch September 29, 2026 09:59
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