Skip to content

Per-model token rates, weighted variant routing, and a model availability status API - #6

Merged
CMGS merged 11 commits into
mainfrom
feat/pricing-canary-status
Jul 18, 2026
Merged

Per-model token rates, weighted variant routing, and a model availability status API#6
CMGS merged 11 commits into
mainfrom
feat/pricing-canary-status

Conversation

@CMGS

@CMGS CMGS commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three additions to the serving pipeline, converged through two review rounds:

Per-model token_rate billing weights — each token component (prompt / cache read / cache write / completion / reasoning) can carry its own weight relative to the model's unit prices (e.g. cache reads at 0.1, cache writes at 1.25). Cost and quota consumption use the weighted counts on every billing path — the request pipeline, aborted-stream and missing-usage estimates, and realtime turns all share one model_token_rate helper, so no surface can price differently. Ledger prompt_tokens/completion_tokens stay vendor-reported; total_tokens is the weighted platform total that quota metering consumed (documented on the record and pinned by tests). Weights are validated finite and non-negative at load.

Weighted variant routing (canary/gray) — a public model name may declare a weighted split across other declared same-protocol models. Selection hashes the effective user with FNV-1a, so every fleet instance maps a user to the same variant with no shared state; anonymous requests spread per request. The swap reuses the fallback seam: entitlement and the per-(AK, model) daily counter judge the public name, billing prices the served variant, the response echoes the requested name, and each variant caches separately. Load-time validation rejects unknown/cross-protocol/nested/duplicate/zero-weight targets — and variants on realtime models, which pin their model at handshake and never traverse the selector.

GET /admin/models/status — per-model availability classified over a configurable window (available / unstable / unavailable / no_data) from minute-bucketed success/error counts. Samples record the client-visible terminal outcome only (a failover-recovered attempt is account-health business, not a model error; pool-exhaustion 503s count, so a sustained outage converges to unavailable instead of sitting below min_samples), attributed to the requested public name so a variant split doesn't scatter a model's health. The claim path never gains a network hop: the in-process store writes its ring directly and the Redis store buffers until a background flush. Realtime models are excluded (never sampled). Tenant-scoped like the other admin reads.

Notes

  • Realtime turns bill through the same token-rate weighting; realtime canary and realtime availability sampling are deliberate non-goals here (config rejects the former loudly).
  • stability.availability_window_minutes is capped at 60 to match the store's one-hour retention.
  • Also fixes a pre-existing flaky test (sqlite_erase_roundtrip used a 5ms wall-clock margin) and an asymmetry where a stream aborted during a failover retry recorded as a success.
  • Docs: docs/api.md, docs/configuration.md, and conf/gateway.yaml cover the new knobs.

Testing

cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test --workspace green on macOS and in a rust:1 Linux container. New coverage includes: weighted cost math and ledger columns end-to-end (cached-usage transport), realtime turn weighting, variant stickiness/distribution/attribution, sustained-outage convergence to unavailable, failover-recovery single-sample accounting, and load-time validation of every new config rule. Redis-backed availability has a GW_TEST_REDIS_URL-gated test matching the existing pattern.

CMGS added 11 commits July 17, 2026 21:35
Cost and quota consumption multiply the served model's configured component
weights (cache reads/writes, reasoning); ledger token columns keep the
vendor-reported counts. Weights validated finite and non-negative at load.
CallEngine outcomes (upstream 5xx vs success; 4xx and client aborts say
nothing about model health) accumulate in an in-process buffer, flushed to
minute buckets every 2s — memory ring locally, shared Redis keys fleet-wide —
so the claim path never gains a network hop. GET /admin/models/status
classifies the configured window into available/unstable/unavailable/no_data;
tenant admins see only entitled models.
A public model name may declare a weighted split across other declared
models (same protocol, one level, validated at load). Selection hashes the
effective user (request id when anonymous) with FNV-1a so every fleet
instance maps a user to the same variant with no shared state. The swap
reuses the fallback seam: entitlement and the (AK, model) counter judge the
public name, billing prices the served variant, the response echoes the
requested name, and each variant caches separately.
Round findings applied: realtime turns and the estimate/no-usage branches
now weight through the same model_token_rate helper as the pipeline (a cut
stream or WS turn must price like a completed one); availability records
the client-visible terminal outcome instead of per-attempt (failover
recovery is account-health business); availability_window_minutes capped
at the store's one-hour retention; duplicate variant targets rejected;
status endpoint fans out with join_all; small cleanups from the simplify
lenses. total_tokens documented as the weighted platform total.
Regression tests from the verification pass: a failover-recovered request
samples exactly one availability success; BadStability rejects window
bounds and inverted/out-of-range rates. Plus a WHY note on the e2e
wire-vs-ledger total assertion (equal only without token_rate).
External review found two fail-silent gaps: legal variants config on a
realtime model routed 100% to the parent (sessions never traverse
VariantSelect) — now rejected at load; and availability attributed samples
to the served backend name, leaving a tenant's entitled public name at
permanent no_data under a variant split — samples now attribute to the
requested public name, matching the client-visible-outcome semantic.
Realtime models are excluded from /admin/models/status (never sampled;
listing them read as a permanent no_data). Docs catch up: the status
endpoint in api.md, token_rate/variants/availability in configuration.md.
Also deflakes sqlite_erase_roundtrip (5ms marker margin lost races under
parallel-suite load).
All avail.record sites sat in CallEngine, so once an account tripped into
cooldown every subsequent 503 came from SelectAccount unsampled — a
sustained outage held ~failure_threshold samples per window, below
min_samples, reading no_data forever (zero-account models: permanently).
SelectAccount now records one failure against the requested public name
before erroring; cache hits are unaffected (the executor short-circuits
them before this node).
Applied: weighted_pair hoisted to gw_models (the dag estimate paths and
realtime shared a hand-built TokenInput pattern); platform_total deleted
(no production caller survived the BillTokens refactor); served_model/
requested_model narrowed to take the model param so bill() reuses them
instead of re-deriving; token_rate validation shares the finite/non-negative
predicate with the qps checks; fallback_from doc names both writers.
Skipped with reasons: per-request ModelConf memo (tens of ns vs a stale-memo
hazard at two rewrite points; rides the perf pass), spawn-loop dedup
(touches three pre-existing tasks; loc-justify adjudicates), realtime
pre-clamp removal (would desync user-budget consumption from the DAG's
clamped totals). Test comments trimmed to the zero-budget, multi-line WHYs
compressed.
The retry-success arm recorded availability and account health without the
first attempt's aborted-stream guard, so a client cutting the stream during
a failover retry read as a model success — against CallEngine's own
documented exclusion.
AvailTracker dissolved into the AvailStore impls per the RedisHealth
embedded-cache precedent: MemoryAvail records straight into its ring (no
buffer between an in-process write and an in-process store), RedisAvail
owns the claim-path buffer its flush drains, and the pure-passthrough
window wrapper is gone. The buffer/ring bumps reuse slot_mut instead of
re-rolling its miss-path idiom; fnv1a folds; the Redis window keys build
via flat_map. Adds the GW_TEST_REDIS_URL-gated RedisAvail test matching
the health/governance pattern. prompt_includes_cache kept (vendor-
inclusive prompt accounting is a plausible near-term need).
@CMGS
CMGS merged commit ec95a21 into main Jul 18, 2026
7 checks passed
@CMGS
CMGS deleted the feat/pricing-canary-status branch July 18, 2026 02:08
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