Skip to content

feat(provider): MTP policy lives in the weights — auto activates embedded Qwen heads only - #777

Merged
Gajesh2007 merged 9 commits into
masterfrom
mtp-auto-qwen35-family
Aug 31, 2026
Merged

Gajesh2007 merged 9 commits into
masterfrom
mtp-auto-qwen35-family

Conversation

@Gajesh2007

@Gajesh2007 Gajesh2007 commented Aug 29, 2026 •

Copy link
Copy Markdown
Member

What

The weights ship the MTP policy. Eight commits (three feature, four review fixups, one consolidated report) that converge on one rule: a checkpoint drafts under default config iff it embeds its own head (mtplx_mtp in config.json) and is Qwen-family (hardcoded for now):

  1. fix(provider): restore + extend auto MTP to the Qwen 3.5 family — .auto re-broadened from feat(provider): integrate Qwen3.8-27B full VLM #700's single pinned id to the qwen3_5/qwen3_5_moe family.
  2. refactor(provider): embedded MTP only — Qwen3.8-27B moves from the separately published revision-pinned external head to the embedded artifact (same shape as 3.6-35B and the 9B). Deleted: StandaloneQwen38MTPResolver, the automaticTargetModelID pin, the resolver test suite (−429/+62).
  3. docs(provider): beta mtp feature text + test(provider): extraction suite colocates the metallib — review fixups: darkbloom beta now describes the embedded-gated policy, and QwenVLMTargetExtractionTests self-heals its metallib — QwenVLMTargetExtractionTests was the one MLX-touching suite without LiveInferenceFixtures.ensureMetallibColocated() (verified by deleting the manual copy, 17/17).
  4. fix(provider): tri-state inline probe (4299d78) — review fixup: inlineDeclarationProbe distinguishes declared / absent / undeterminable so a declared-but-uninspectable head surfaces inline_artifact_invalid instead of config_disabled; operator guides updated for the embedded policy (0b8a3b5).
  5. docs(reports) (da41944) — the Qwen3.5-9B validation + MTP report consolidated from docs(reports): Qwen3.5-9B dense validation + native inline-MTP head (1.51x decode) #774 (closed in favor of this PR), including the OPEN greedy-token-parity validation item that gates registry rollout of the embedded artifacts.
  6. feat(provider): auto MTP activates only embedded Qwen heads — .auto requires the embedded declaration, not just the family: enablesMTP(forModelType:embeddedArtifactDeclared:). A Qwen checkpoint without an embedded head is never asked about (no catalog lookup, no prefetch — plain config-disabled). Separately published assistants and mtp_drafter_path overrides require explicit mtp_mode = "on"; catalog prewarm runs only under "on" (auto never consults the catalog).

Net effect: publishing a checkpoint with an embedded head is what turns MTP on for it — no per-model code, no registry spec-dec metadata, no id pins. The funnel keeps full authority (inline inspection, kill switch); a declared-but-invalid head still falls back target-only with a recorded reason.

Verification (live, M4 Max, default config — no mtp_mode key — on the final binary)

Checkpoint .auto control
Qwen3.5-9B embedded 79–83 tok/s 47.9–48.5 kill-switch
Qwen3.6-35B-A3B embedded 44.4–51.9 tok/s 26.4–26.5 kill-switch
Qwen3.8-27B embedded (built from the validated pair's bytes) 28.0–31.5 tok/s 20.3–20.6 kill-switch
mlx-community 9B (no embedded head) 40 tok/s target-only, no MTP attempt —

Unit tests: 74/74 across the MTP suites; targetPolicy pins the full (mode × family × embedded) matrix incl. normalization and non-family exclusion.

Before / After

flowchart LR
  subgraph Before["Before — policy in code"]
    A1[slot load] --> B1{"id == pinned Qwen3.8?"}
    B1 -->|yes| C1[external head @ exact revision<br/>StandaloneQwen38MTPResolver]
    B1 -->|no| D1[target-only<br/>every new model = code change]
  end
  subgraph After["After — policy in the weights"]
    A2[slot load] --> B2{"Qwen family AND<br/>checkpoint declares mtplx_mtp?"}
    B2 -->|yes| C2[funnel inspects embedded head → drafts<br/>invalid head → target-only + reason]
    B2 -->|no| D2[target-only, no catalog/prefetch<br/>external assistants need mtp_mode = on]
  end
Loading
flowchart LR
  subgraph Code
    M["MTPMode.enablesMTP(modelType, embeddedArtifactDeclared)"] --> P[ProviderLoop+MTP prepare<br/>declares check via SpecDecStore]
    M --> S[StandaloneServer+MTP prepare<br/>funnel-only]
    PW[prewarmSpecDecCatalog<br/>now on-mode only] -.-> F
    P --> F[SpecDecArtifactFunnel<br/>inline inspection · kill switch]
    S --> F
    F --> E[CBv2 engine + drafter]
  end
Loading

Rollout

  • Embedded artifacts are published on HF: EigenLabs/Qwen3.5-9B-MLX-4bit-mtp and EigenLabs/Qwen3.8-27B-4bit-mtp (27B = target @301e9e27 + head @329261c5, byte-identical tensors, verified by round-trip download + serve). The 27B was never registered on the official darkbloom registry, so this PR lands cleanly first; register the embedded repos after.
  • Migration note (standalone, old external pair): a box serving the old EigenLabs/Qwen3.8-27B-4bit with the cached external head no longer auto-drafts — switch to the embedded repo, or pin mtp_drafter_path + mtp_mode = "on".
  • Fleet boxes on .auto draft exactly for the embedded checkpoints they serve; mtp_mode = "off" untouched; "on" keeps catalog/external-assistant behavior for operators who need it.

Related: #774 (9B validation + embedded-artifact build conventions).

#752 (v0.8.14) made MTPMode.auto architecture-based: any qwen3_5_moe
checkpoint drafted. #700 narrowed it to the single pinned
EigenLabs/Qwen3.8-27B-4bit, silently dropping auto-MTP for the
Qwen3.5/3.6 35B MoE fleet on upgrade — and dense qwen3_5 checkpoints
(the 9B class) never had it.

.auto now enables MTP for the pinned Qwen3.8 target plus, by
config.json model_type, the qwen3_5 / qwen3_5_moe family — the same
set SpecDecArtifactFunnel.isQwen35Target already resolves. The funnel
stays the authority: a family checkpoint without an MTP artifact still
falls back target-only with a recorded reason, and the kill switch is
unchanged. enablesMTP now takes the model type; callers that only know
an id keep the pinned-id decision.

Verified live on an M4 Max, default config (no mtp_mode key):
- Qwen3.5-9B inline MTP: 80.7 tok/s vs 47.6 target-only (1.7x)
- Qwen3.6-35B inline MTP: 44-52 tok/s vs 26.5 kill-switch (1.9x)
- Qwen3.8-27B pinned path: unchanged (canary + resolver tests)
@vercel

vercel Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
d-inference Ready Ready Preview Aug 31, 2026 5:59am
d-inference-console-ui-dev Ready Ready Preview Aug 31, 2026 5:59am
d-inference-landing Ready Ready Preview Aug 31, 2026 5:59am

Request Review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-08-31T06:02:27.193930Z 4cfdf93 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…ead path

Qwen 3.8-27B moves from the separately published, revision-pinned
external assistant (EigenLabs/Qwen3.8-27B-MTP-4bit) to the embedded
inline artifact (mtplx_mtp in the target checkpoint), the same shape the
3.6-35B and the 9B ship. The pairing now travels inside the weights, so
the machinery that existed only to pin it outside is scrap:

- delete StandaloneQwen38MTPResolver (exact-revision cache staging,
  samePayload verification, recordingSourceRevision binding) and its
  branch in StandaloneServer.specDecPreparation — the funnel is the one
  mechanism
- delete MTPMode.automaticTargetModelID and collapse enablesMTP to the
  model-type-only family rule (the pin gated the external pair and
  nothing else)
- delete the 'Standalone exact Qwen3.8 MTP cache' test suite with the
  resolver; MTPConfigTests now pin the family rule directly

Fleet (coordinator) mode is untouched: catalog spec-dec resolution
keeps serving the external head until the registry switches the 27B
entry to the embedded artifact.

Verified live on an M4 Max, default config (no mtp_mode key), on the
final binary: embedded 27B artifact drafts at 28-31 tok/s vs 20.3-20.6
with the kill switch; 9B 80.7 vs 47.9; 3.6-35B 44-52 vs 26.5.
@Gajesh2007 Gajesh2007 changed the title fix(provider): restore + extend auto MTP to the Qwen 3.5 family (9B dense + 3.5/3.6 MoE) feat(provider): embedded MTP by default for the Qwen 3.5 family + delete the external 27B head path Aug 29, 2026
@blacksmith-sh

blacksmith-sh Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
github.com/eigeninference/d-inference/coordinator/api/
TestAcceptedDoesNotStopSpeculativeFirstContentRace
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e95206d1ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread provider-swift/Sources/ProviderCore/Server/StandaloneServer+MTP.swift Outdated
Comment thread provider-swift/Sources/ProviderCore/Config/ProviderConfig.swift Outdated
.auto now requires BOTH the hardcoded Qwen family gate (qwen3_5,
qwen3_5_moe) AND an embedded-head declaration (mtplx_mtp.included=true
in the checkpoint's config.json). A Qwen checkpoint without an embedded
head is not asked about at all — no catalog lookup, no prefetch, plain
config-disabled fallback. Separately published assistants and
mtp_drafter_path overrides now require an explicit mtp_mode="on", and
catalog prewarm runs only under "on" (auto never consults the catalog).

The weights ship the policy: publishing a checkpoint with an embedded
head is what turns MTP on for it, with no per-model code or registry
spec-dec metadata. Verified live under a default config on the gated
binary: embedded 9B drafts at 79-83 tok/s; the bare mlx-community 9B
serves target-only. 74/74 MTP suite tests.
@Gajesh2007 Gajesh2007 changed the title feat(provider): embedded MTP by default for the Qwen 3.5 family + delete the external 27B head path feat(provider): MTP policy lives in the weights — auto activates embedded Qwen heads only Aug 31, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6daf57c79f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread provider-swift/Sources/ProviderCore/ProviderLoop+MTP.swift Outdated
Codex review: 'darkbloom beta list' still said automatic mode enables
only the exact Qwen3.8 target. It now describes the embedded-gated
family rule, and notes that catalog assistants and mtp_drafter_path
overrides require the explicit on override.
@Gajesh2007

Copy link
Copy Markdown
Member Author

Codex review dispositions:

  1. BetaFeatures.swift stale policy text (P2) — real miss, fixed in 135dae1: darkbloom beta list / beta status mtp now describe the embedded-gated family rule and note that catalog assistants + mtp_drafter_path overrides require the explicit on override.

  2. "Preserve catalog MTP for the pinned 27B" (P1) — intentional, and moot in production: the 27B was never registered on the official registry (the Go fixture referenced is registry-format test data, not prod state), so no fleet box loses drafting. The design here is exactly that the weights ship the policy: the embedded replacement is now published at EigenLabs/Qwen3.8-27B-4bit-mtp (target @301e9e27 + head @329261c5, byte-identical tensors, one manifest). Catalog resolution of separately published assistants stays reachable under mtp_mode = "on".

  3. "Preserve the cached Qwen3.8 assistant fallback" (P2) — intentional retirement. A standalone box with the old target + cached external head migrates by switching checkpoints to the embedded repo above; the manual escape (mtp_drafter_path + mtp_mode = "on") still works for pinned local assistants. Resurrecting the resolver would keep the exact id-pin machinery this PR exists to delete.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 135dae1450

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread provider-swift/Sources/ProviderCore/Config/BetaFeatures.swift
Comment thread provider-swift/Sources/ProviderCore/ProviderLoop+MTP.swift Outdated
…her MLX suite

QwenVLMTargetExtractionTests evaluates MLX arrays through tiny synthetic
modules but was the one MLX-touching suite without
LiveInferenceFixtures.ensureMetallibColocated(); a fresh worktree needed
the metallib manually copied beside the test runner. The suite init now
self-heals: verified by deleting the manual copy and re-running 17/17.
…iagnostics

Codex review: the boolean declaresInlineArtifact applied the inspection's
strict 1 MiB config cap, so a valid-but-oversized config.json that DECLARES
an embedded head read as 'absent' — the auto pre-gate then reported
config_disabled and the funnel never surfaced inline_artifact_invalid.

SpecDecStore.inlineDeclarationProbe now distinguishes declared / absent /
undeterminable with a deliberately looser probe bound (64 MiB): declared and
undeterminable configs reach inspectInlineArtifact, whose strict cap rejects
loudly with the named reason; only a parsed config without a declaration
falls through to the catalog resolver silently, as designed. All three
callers (funnel inline branch + both auto pre-gates) migrated; boolean
discriminator deleted. New InlineDeclarationProbeTests cover the tri-state,
the oversized-declared end-to-end inline_artifact_invalid, and the
oversized-undeclared catalog fallthrough.
…olicy

Codex review: beta-features.md and cli-reference.md still described MTP as
a default-off Gemma-only feature that installing a release never enables.
Both now document auto's embedded-Qwen activation, the explicit-off
rollback, and that catalog spec_dec assistants and mtp_drafter_path
overrides require the explicit on override.
Consolidated from #774 (closed in its favor): the validation record behind
this PR's policy change, including the embedded-head build conventions, the
measured MTP results, and the OPEN greedy-token-parity validation item that
gates registry rollout of the embedded artifacts.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da41944ab0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread provider-swift/Sources/ProviderCore/ProviderLoop+MTP.swift Outdated
Comment thread provider-swift/Sources/ProviderCore/Config/ProviderConfig.swift
Comment thread provider-swift/Sources/ProviderCore/Server/StandaloneServer+MTP.swift Outdated
1. Single-probe inline decision (TOCTOU): the funnel re-probed
   config.json after the auto pre-gate; a config change between the two
   reads could flip an embedded-only decision into a catalog resolution
   auto forbids. SpecDecArtifactFunnel.Request now carries the caller's
   one authoritative InlineDeclarationProbe and the funnel never
   re-probes. Pinned by SingleProbeInlineDecisionTests.

2. Inline assistants no longer charge twice: ModelScanner already counts
   the embedded mtp shard in the target's sizeBytes/estimatedMemoryGb,
   yet admission and the pending-load reservation added residentBytes on
   top — near the cap that misreported assistant_memory_unavailable for
   a fitting target+assistant. SpecDecArtifact.additionalWeightBytes is
   0 for .inline and residentBytes otherwise; all three charge sites
   (ProviderLoop admission, pending-load reservation, standalone) use it.

3. EigenLabs/Qwen3.8-27B-4bit-mtp inherits the target's hardware
   invariant: ModelRuntimeRequirements now gates every concrete Qwen3.8
   build id (M5 + MLX NAX), so the embedded publication cannot dodge
   eligibility by renaming. Pinned by EmbeddedQwen38RuntimeRequirementsTests.

107/107 across the MTP, funnel, and runtime-requirements suites.

@antojoseph antojoseph left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lgtm

@Gajesh2007
Gajesh2007 merged commit a8d0cf7 into master Aug 31, 2026
36 of 38 checks passed
@Gajesh2007
Gajesh2007 deleted the mtp-auto-qwen35-family branch August 31, 2026 06:43
Gajesh2007 added a commit that referenced this pull request Aug 31, 2026
…60822

Brings the test-suite refactor up to date with the 22 commits master
gained since the last sync (#700-#790: Qwen3-VL and Qwen3.8 serving,
first-content deadline admission, MTP-in-the-weights, serial-number
privacy, MLX core 0.32.2, usage-checkpoint fixes).

Every master-side test added or modified inside files this branch
deletes, splits, or rewrites was re-homed into the refactor's successor
suites and lanes rather than dropped, including:

- PrefillDeadlineProductionConfigTests and the Qwen required-tool-choice
  tests into the slimmed EngineV2ProductionWiringTests
- bridgeDeadlineRefusalReleasesRealStaging into
  SSDPrefixCacheReservationTests
- excludesInferenceControls into WatchdogRecoveryTests
- the rewritten #777 MTP config/beta suites into ConfigTests and
  BetaFeaturesTests (MTPConfigTests stays deleted)
- vision/DeepStack/media additions into the split EngineV2Vision* and
  MediaIngest suites
- the #704 writeDeferred provider-writer tests, first-content-budget
  wire test, #730 serial-privacy subtests, and #752 benchmark-control
  tests into their branch successors

Master-added test files were audited against the lane architecture:
the Qwen3.8 production canary trio moved to Live/ (with a #filePath
depth fix), SchedulerPrefillDecision's real-weights suite split out to
Probes/ behind its per-probe gates, and hermetic additions stay in the
default lane. docs/developer/test.md updated to match.

Production-code unions of note: registry.go keeps both the branch's
PrivateOnly/catalog public-listing gates and master's per-model
capability gating; provider_writer.go carries both determinism-seam
families; LaunchAgent forwards the union of both env allowlists;
postgres_model_registry.go extends nil-canonicalization to master's new
required_provider_capabilities column. The branch-side prefill-keepalive
tests were dropped with the feature master removed in #704.

Two master-branch interaction defects fixed so the merged tree is green:
the store contract harness now re-seeds the usage_totals counter row its
truncate wipes (the #665 migration guard treats the bare marker as
corruption), and the #730 log-report migration test restores the pooled
connection's search_path before release.

Submodules: mlx-swift and mlx-swift-lm test branches merged with
master's 0.32.2-era pins; relocated suites verified present.

Verified: go vet + full coordinator/testbed tests green; store contracts
green twice consecutively against real Postgres 16; swift build
--build-tests clean; default Swift lane 2,264 tests in 248 suites all
passing.
Gajesh2007 added a commit that referenced this pull request Sep 1, 2026
master now serves qwen3_vl_moe directly (#752, vision off M5 #781) and
auto-activates embedded MTP heads (#777): the "dark fleet-wide / no CBv2
adapter" claims become a bench-side measurement gap (LLM-factory load
cannot construct the wrapper; floor stays at the 5.5 default, which fits its
32 GB tier), and the qwen3.6 production route now includes MTP k=2 — the
B=1-measured +0.4 GiB allowance applies to the live route (floors remain
deferred for the vision reason).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6
Gajesh2007 added a commit that referenced this pull request Sep 2, 2026
master now serves qwen3_vl_moe directly (#752, vision off M5 #781) and
auto-activates embedded MTP heads (#777): the "dark fleet-wide / no CBv2
adapter" claims become a bench-side measurement gap (LLM-factory load
cannot construct the wrapper; floor stays at the 5.5 default, which fits its
32 GB tier), and the qwen3.6 production route now includes MTP k=2 — the
B=1-measured +0.4 GiB allowance applies to the live route (floors remain
deferred for the vision reason).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6
Gajesh2007 added a commit that referenced this pull request Sep 2, 2026
master now serves qwen3_vl_moe directly (#752, vision off M5 #781) and
auto-activates embedded MTP heads (#777): the "dark fleet-wide / no CBv2
adapter" claims become a bench-side measurement gap (LLM-factory load
cannot construct the wrapper; floor stays at the 5.5 default, which fits its
32 GB tier), and the qwen3.6 production route now includes MTP k=2 — the
B=1-measured +0.4 GiB allowance applies to the live route (floors remain
deferred for the vision reason).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6
Gajesh2007 added a commit that referenced this pull request Sep 3, 2026
…rdened, rebased for v0.8.16 (supersedes #654, addresses #653) (#791)

* Resolve the activation reserve per serving set (measured per-model floors)

The flat 5.5 GiB activation reserve (sized for gemma-4-26b's measured
B=8 peak) is charged to every model, so gpt-oss-20b needs 20.0 GB to
load — arithmetically unreachable on the catalog's own min_ram_gb=24
tier, which shows online and 429s every request (#653).

Provider: add UnifiedMemoryCap.measuredActivationFloorsBytes
(gpt-oss-20b: 3.5 GiB = worst measured B=8 peak, 3.20 compiled, plus
slack), resolved max-over-set (advertised UNION resident), env
raise-only above the resolved floor, threaded through the load gate,
startup preload, doctor (serving-set verdict; per-alternative solo
floors), free_for_load reporting, the measured post-load/post-bridge
guards, runtime KV admission (GlobalKVCacheBudget actor setter,
re-pushed on serving-set mutations), and engine KV grants plus the
heartbeat budget clamp.

Coordinator: mirror via servabilityModelActivationFloorsGB and a third
version regime in servabilityActivationFloor(version, modelID);
servabilityPerModelFloorMinVersion is a placeholder for the shipping
release.

needs on the 24 GB tier drops 20.0 -> 18.0 GB.

Closes #653.

* docs: activation reserve overhaul plan + 2026-08-30 measurement sweep

The sweep re-baselines the July activation-peak measurements on the current
engine: the 5.5 GiB default's basis (gemma qat-4bit B=8 = 5.05) now measures
2.28 GiB (saturated long-prompt envelope ~3.6); gpt-oss measures 2.63
(envelope ~2.7); the padded-weights estimate overshoots real residency by
~6 GiB on the 26B 8bit artifact. Plan covers per-model floors (PR #683 base),
the qwen3.6 B=8 measurement, measured resident weights, and the default
retune.

* fix(coordinator): pin per-model floor gate to the 0.8.16 release train

The cherry-picked #683 carried servabilityPerModelFloorMinVersion = "0.8.11"
as a self-declared placeholder; 0.8.12–0.8.15 have since shipped holding the
flat 5.5 GiB reserve, so as-is the coordinator would charge those binaries a
3.5 GiB gpt-oss floor they don't hold (fail-open, but wrong). Gate at 0.8.16
with the release-coupling contract documented at the constant, and pin 0.8.15
as the last flat-floor release in the regime test.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* docs: Phase 2 measurement results — qwen3.6 floor 4.0 candidate + 32 GB tier re-tier recommendation

* fix(provider): serving-set reserve for the paged capacity decision + hybrid-safe paged cache mapping

Two paged-path gaps behind the per-model floors (review findings):

1. The paged physical-capacity decision measured live headroom with the FLAT
   reserve while every other gate on the load path carves the serving-set
   floor — on a measured-only box there is a band where the flat-input
   liveLimit dips under the 1 GiB minimum pool and an explicit-paged slot
   503s a load the relaxed gate admitted (admit-then-fail). Thread the
   caller's resolved reserve through makeProductionEngine/Build/
   prepareProductionBackend and the slot factory; StandaloneServer keeps
   the flat default (nil).

2. The paged cache wiring subscripted the dense per-storage cache array
   with newCacheV2's MODEL layer index. Identity for Gemma/GPT-OSS; on a
   hybrid trunk (Qwen3.5/3.6: 40 model layers, 10 attending) it is an
   Index-out-of-range at engine build — latent today only because
   supportsPagedKV=false vetoes paged for recurrent models upstream. Map
   model index → storage slot so the flag can flip without a crash.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(provider): close the review races in serving-set reserve resolution

Four adversarial-review findings on the per-model floor machinery:

1. In-flight loads join the reserve basis (advertised ∪ resident ∪
   modelsLoading): a hard-swap could drop a high-floor model from
   advertisedModels while its load was still running — in neither set, a
   relax computed in that window sized admission and grants below the
   reserve the model decodes under once installed (OOM direction).

2. Reserve pushes are epoch-stamped and the budget discards stale ones:
   cross-actor jobs from different tasks are not FIFO, so a relax computed
   before a raise could be DELIVERED after it and stand. Epochs increment
   under ProviderLoop's isolation = true mutation order. Regression test
   added.

3. Shrink paths regrow survivors: failed-self-test retirement now
   un-advertises BEFORE unloading (so unload's refresh-then-regrow runs
   against the shrunken set) and regrows after a cold retirement;
   dropAdvertisedBuild regrows after its relax. Grant clamps are
   min(granted, current), so nothing else ever hands the difference back
   (under-grant direction — capacity loss, not OOM).

4. Doctor's serving-set basis mirrors the daemon's advertise chokepoints
   (memory-filtered scan + supported families) instead of the raw scan, so
   a cached too-large or unadapted model cannot falsely FAIL a tier the
   daemon serves at a lower floor. The unfiltered list stays for target
   diagnosis.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(provider): reserve re-push at load transitions + retirement tombstone

Round-2 adversarial-review findings:

1. ensureModelLoaded re-pushes the reserve right after modelsLoading.insert
   — the insert sits after the specDecPreparation suspension, so a mid-await
   hard swap could drop the model from advertisedModels and push a relax
   while it was in neither basis set; the budget then admitted against a
   reserve the resuming load re-pins (OOM direction). The failed-load path
   symmetrically relaxes and regrows survivors — a dead high-floor id
   otherwise pinned the budget and grants forever (capacity-loss direction).

2. Failed-self-test retirement holds a retiringModels tombstone across its
   unload drain: with the slot still resident, a concurrent same-id
   prefetch reports .alreadyAvailable and its verified-insert re-advertised
   the failed build, undoing the fail-closed removal. The coordinator
   un-advertise also moves BEFORE the drain — leaving it after kept routing
   new requests at a slot fastAdmissionReject accepts, parking them behind
   the drain for a post-accept 404.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(provider): hold the load gate through failed-load cleanup + seal the retirement/prefetch races

Round-3 adversarial-review findings:

1. The failed-load catch released isLoadingAny/modelsLoading before its
   awaits — a new same-id loader could start mid-cleanup and place its own
   pending-load reservation (shared key, unconditional overwrite), which
   the old catch's release then DELETED, blinding KV admission to the
   incoming weights; the early unlock also let another model's weights go
   resident before the survivor regrow (sized from modelSlots only).
   Cleanup now runs release → clearCache → marker removal → refresh →
   regrow entirely under the held gate, unlocking last.

2. applyVerifiedPrefetch re-checks the retirement tombstone after each
   suspension (the pre-insert reserve push and the post-insert refresh) —
   retirement interleaving in either await could otherwise be undone by
   the resumed insert or the client announcement. The refused pre-insert
   path also re-refreshes so its phantom raise doesn't dangle.

3. Retirement waits out a drain another task owns (waitForModelUnload)
   before dropping its tombstone, and the forceReconnect moves back AFTER
   the drain: reconnecting first cancels every in-flight request on every
   model (cancelAllInflight on the disconnect event). The client store
   removal stays pre-drain so any reconnect during it registers without
   the failed model; the residual routed-404 window is bounded and
   absorbed by dispatch retries.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* feat(provider,coordinator): measured activation floor for qwen3.6-35b (4.0 GiB)

Adds qwen3.6-35b-a3b-vl-mtp-mxfp8 → 4.0 GiB to both mirrored floor tables
(one commit, per the contract), from the 2026-08-30 sweep on the served
artifact (hub 73a03825, stock v2 = production's live route): non-KV
transient saturates ~3.3 GiB across 500→8k-token B=8 cells (the raw
peak-over-resident growth is contiguous-cell KV, separately accounted by
the KV budget) + ~0.4 inline-MTP allowance + slack. Basis notes sit on
both table entries because gpt-oss's 3.5 uses the July raw-500 convention.

Effect: qwen3.6 needs drop 30.3 → 28.8 GB. That widens the 36 GB tier
comfortably; the catalog's 32 GB tier remains physically marginal (<4 GiB
left for macOS at the cap) — recommend min_ram_gb 32 → 36 as a separate
ops/catalog change, per docs/reports/2026-08-30-activation-floor-measurements.md.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(provider): ABA-proof fail-closed retirement against prefetch verification

Round-4 adversarial-review finding (wrong-results direction): the prefetch's
detached scan/hash suspension is long enough for an ENTIRE retirement —
tombstone insert AND removal — to complete inside it, after which the
resumed prefetch observed no tombstone, re-advertised the self-test-failed
build, and a routed request could reload and serve it without re-passing
the self-test.

The durable mark is the failed WEIGHT HASH (failedSelfTestHashes, set at
retirement): the same bytes that failed are refused at verification no
matter how the suspensions interleave; a build with different bytes clears
the record and gets its chance. The announce block also re-validates after
its two client awaits and UNDOES the client-store add if retirement removed
the local advertisement mid-announce — otherwise the post-drain reconnect
registered a build the loop no longer serves (persistent false routing).

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(provider): durable self-test record at every prefetch guard + registration convergence on undo

Round-5 adversarial-review findings:

1. The durable failed-self-test record was checked only at the FIRST guard
   — a retirement completing entirely inside the reserve-push suspension
   set the record after that check had passed, and the later guards
   consulted only the ephemeral tombstone. A shared helper
   (selfTestRecordRefuses) now runs at all three guards. The record is
   also ALWAYS set at retirement ("" sentinel when no reliable hash is
   known — the scanner keeps a previous hash on recompute failure, so a
   stale H1 could be recorded while H2 actually failed; the sentinel
   refuses every same-id build until restart, fail-closed), and a
   different-hash clear happens only at the actual advertise point.

2. The announce-undo could not retract a registration the reconnect loop
   had already encoded from the transiently-stale client store — the
   coordinator kept routing the retired id until an unrelated reconnect.
   The undo path now forces one more reconnect so registered inventory
   converges on the corrected store (rare double-race; bounded,
   correctness-restoring).

Also aligns the measurement report's MTP allowance wording (~0.4) with the
floor-table comment.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(provider): slot-bound hash for the failed-self-test record

Round-6 residue: the scanner maps (liveModelHashes/modelHashes) keep a
previous value when recomputation fails, so retirement could record a
stale H1 while H2's bytes actually loaded and failed — and a later
verified H2 cleared the record and re-advertised the failed bytes. Record
the slot's own verified binding (cacheEligibleWeightHash — the bytes the
slot actually loaded) or the refuse-all sentinel; never the maps.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* feat(provider,coordinator): measured resident weights + qwen3.5 floor (Phase 3)

Second over-reservation lever from the sweep: the scanner's disk×1.2
padding overshoots real MLX residency by ~6 GiB on the 26B 8bit artifact —
enough that gemma-8bit's 36 GB tier was arithmetically unserveable
(padded 31.3 + floor + 1 > 32.4 cap; measured 25.5 fits with margin).

Adds the twin measured-residency tables (measuredResidentWeightsBytes /
servabilityMeasuredResidentGiB, same 0.8.16 gate, same move-together
mirror contract as the floors), threaded through:
- provider: load gate, pending-load reservation, startup preload plan, doctor
- coordinator: cold token-budget estimate AND the cold-load admit gate
  (reportedFreeForLoadAdmits + its four callers — without this, routing
  never offers the tier a cold load its providers can now take)

Text-only models only (gpt-oss 11.5, gemma-4-26b/-8bit 25.5 GiB measured);
vision-capable models keep the padded estimate until a vision-inclusive
residency measurement exists.

Also: qwen3.5-35b-a3b → 4.0 GiB activation floor in both tables (measured
2026-08-31; byte-identical B=8 profile to qwen3.6, pre-covers the family
MTP default), and the plan records two deliberate resolutions — the flat
default retune (5.5→~4.5) is DEFERRED until vision-inclusive measurement
(its only beneficiaries are exactly the models a text-only bench cannot
vouch for), and qwen3-vl-30b's real blocker is a missing CBv2 adapter
(qwen3_vl_moe unsupported — the catalog entry is dark fleet-wide; ops
decision, not memory arithmetic).

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(review): vision-gate the measured tables, padded pending-load, canonical docs

Codex PR review (5 findings), all addressed:

1. qwen3.5/3.6 floor entries REMOVED (back to the 5.5 default): they are
   vision-capable and the tower transient rides the activation reserve —
   VisionMemoryGate charges decoded-media buffers + KV, and
   VisionTowerBudget only bounds the tower's N² allocation, so text-decode
   evidence alone must not lower the reserve. Their measured ~3.3 GiB
   text envelopes stand in the report as the baseline; entries return with
   a vision-inclusive measurement.

2. gemma-4-26b/-8bit residency entries REMOVED: the artifact carries
   vision_config (model_type gemma4), production loads the tower via
   VLMModelFactory, and the bench's forced-LLM 24.97 GiB under-counts it.
   The 36 GB tier unblock is gated on a provider-path residency
   measurement. Residency table = gpt-oss only (model_type gpt_oss — no
   VLM wrapper; the bench load IS the production path).

3. Pending-load reservation reverted to the PADDED estimate even for
   measured models — it guards the load transient, which steady post-load
   residency does not cover.

4. AGENTS.md memory-model section rewritten: the canonical instructions
   described the flat-reserve design and warned against exactly the
   per-model contract this branch ships; they now document the serving-set
   resolution, both mirror tables, the move-together rule, the vision
   gate, and the ≥4k-cell measurement convention.

5. Measurement report conclusions/follow-ups updated to final state
   (qwen3.6/3.5 measured; harness fix landed as mlx-swift-lm#129;
   qwen3-vl's blocker is the missing CBv2 adapter).

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* chore(release): pin mlx-swift-lm to main (incl. bench fix #129) + bump version to 0.8.16

Submodule fe01df9 → 30da946: brings the merged bench-harness fix
(mlx-swift-lm#129, paged-cache index mapping + supportsPagedKV veto) plus
the engine work that landed on main since the old pin — #125 Qwen3-VL CBv2
DeepStack (engine side; provider wiring still gated off in
EngineV2SupportedModels), #126 sorted-hint lane, #127 vision batch, #128
serving-correctness batch, #118 dense Qwen3.8 MTP artifacts.

ProviderCore.version 0.8.13 → 0.8.16, honoring the release coupling
documented at servabilityPerModelFloorMinVersion (this tree is the release
train that ships the per-model tables). LatestProviderVersion moves with it
— TestLatestProviderVersionMatchesProviderCore enforces the pairing; the
constant only reaches operators when the coordinator deploys at release.

Also hardens PagedKernelPreflightTests' hung-child deadline 0.5s → 2s:
sibling tests run runtimeSmoke on real Metal kernels in parallel and the
new engine's heavier compile load could pre-empt the child's startup past
half a second (pid file never written → the kill assertion misread as a
missing file). A rarer residue of the same timing class remains under
tight isolation looping (~1-in-3 at 2s) — known-flake, same family as the
KV-reclaimer wall-clock tests; full suite is green (2246/233).

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(review): retirement inference gate + 0.8.16 changelog + canonical citations

Codex round-3 findings:

1. (P1) New inference is now rejected on BOTH resident-slot fast paths
   while a failed-self-test retirement drains: fastAdmissionReject returns
   an immediate reject (coordinator reroutes) and ensureModelLoaded throws
   a 503-mapped retiring error before the resident fast-return — the
   promised 404 only lands after the drain, and the fast paths were
   accepting work against a build that failed its serving-path self-test.
   Regression test + test seam added (RetirementGateTests).

2. (P2) CHANGELOG gains the v0.8.16 release-candidate entry: per-model
   floors + measured resident weights with the coordinator mirrors, the
   reserve race-hardening series, and the mlx-swift-lm pin advance to
   30da946 with its engine PRs enumerated.

3. (P1) The plan's qwen3-vl provider-gate claims now cite the canonical
   code (EngineV2SupportedModels.swift:41 predicate;
   EngineV2Factory+Production.swift:656-region family switch).

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(review): retirement re-checks after every suspension + standalone measured weights + plan truth

Codex round-4 findings:

1. (P1) The retirement gate is re-checked after EVERY actor suspension that
   precedes a resident-slot return, via a shared throwIfRetiring helper:
   ensureModelLoaded re-checks after the unloading-wait loop and after the
   loading-waiter resume (a parked request could otherwise resume past a
   tombstone inserted while it waited and submit to the failed build);
   fastAdmissionReject re-checks after both availableMemoryGb suspensions
   (no inference_accepted for a retiring model).

2. (P1) StandaloneServer's load gate uses the measured-residency weights
   figure too (loadGateWeightsGb) — darkbloom local's 24 GiB gpt-oss case
   was still being charged the disk×1.2 estimate. The FLOOR deliberately
   stays flat in standalone; the weights constant has no serving-set
   semantics to differ on.

3. (P2) The plan's Phase-3 status block now states the truth: both qwen
   floors deferred (tables ship gpt-oss only), qwen3.6's 32 GB tier does
   NOT fit at the 5.5 default (30.3 > 28.8 cap — re-tier to 36 REQUIRED),
   and gemma-8bit's 36 GB tier stays blocked pending the VLM-path
   residency measurement.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(review): remaining retirement windows + doctor live basis + live preload requirements

Codex round-5 findings:

1. (P1) throwIfRetiring at the two remaining post-suspension resident
   returns in ensureModelLoaded: after the specDecPreparation resume and
   at the load-gate (isLoadingAny) wait's resident return.

2. (P2) Doctor derives its serving-set floor basis from the daemon's OWN
   advertised list when the daemon is up and fresh — new optional
   advertised_models field in daemon-state (decode-tolerant), written from
   the live advertisedModels keys. CLI selection overrides (--all
   bypassing enabled_models, --model restricting) made any config-derived
   reconstruction wrong in both directions; the config-derived basis stays
   as the offline fallback.

3. (P2) Startup preload recomputes each candidate's requirement at its
   admission step (Dependencies.currentRequiredGb hook → live
   loadGateWeightsGb + current loadHeadroomGb): a fail-closed retirement
   mid-preload relaxes the serving-set floor, and a later candidate held
   its stale plan-time figure — skipping a model the refreshed gate
   admits.

4. (P2) The plan's Phase-2 step 3 is rewritten as resolved: the 4.0 qwen
   floor is a rejected-for-now candidate behind vision-inclusive
   measurement; the 32→36 re-tier is REQUIRED regardless.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(review): padded admits everywhere — measured residency is post-load-only

Codex round-6 findings:

1. (P1) Measured steady residency leaves EVERY admit-time gate: the load
   transient (shard staging) exceeds the post-load figure, which is exactly
   what the disk×1.2 padding was sized for — admitting a co-resident load
   against 11.5 GiB steady while staging peaks higher is the OOM direction.
   The provider-side measured-weights table/helper and all its wirings
   (load gate, startup preload, standalone, doctor) are REMOVED; the
   canonical measured values live in servabilityMeasuredResidentGiB
   (coordinator) and feed ONLY coldTokenBudgetEstimate — the POST-load
   token-budget arithmetic, where steady residency is physically correct
   (it converges to warm active_token_budget_max).
   reportedFreeForLoadAdmits reverts to the padded conversion for every
   binary and model, with a regression test pinning both directions
   (admit refuses at measured-fits/padded-doesn't; admits at padded-fits).
   The gemma-8bit 36 GB tier follow-up now requires a measured LOAD-PEAK
   through the provider's VLM path, not just steady residency.

2. (P2) fastAdmissionReject recomputes its requirement after the
   availableMemoryGb suspension — a concurrent verified prefetch can raise
   the serving-set floor during the hop (accepted-then-503 otherwise).

3+4. (P2) Measurement report finalized: the tier verdict marks the qwen
   floor entry deferred (shipped tables carry gpt-oss only), and the
   release-gate conclusion records the SHIPPED 0.8.16 state instead of the
   0.8.11-placeholder/0.8.15-train caveat. CHANGELOG, AGENTS.md, and the
   plan reframed to the post-load-only design.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* fix(review): empty live advertised set is authoritative in doctor

Codex round-7 (single P2): a fresh daemon that retired every model after
failed startup self-tests legitimately advertises [] — the !isEmpty guard
discarded that live value and reconstructed the serving set from config,
crediting floors for models the daemon is not serving. Non-nil is now
authoritative including empty.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>

* docs: reconcile plan and report with master post-rebase

master now serves qwen3_vl_moe directly (#752, vision off M5 #781) and
auto-activates embedded MTP heads (#777): the "dark fleet-wide / no CBv2
adapter" claims become a bench-side measurement gap (LLM-factory load
cannot construct the wrapper; floor stays at the 5.5 default, which fits its
32 GB tier), and the qwen3.6 production route now includes MTP k=2 — the
B=1-measured +0.4 GiB allowance applies to the live route (floors remain
deferred for the vision reason).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): live headroom through eviction/retry waits, authoritative empty doctor set, capacity publish after reserve raise

Codex review of the rebased head (f591ce7), six findings:

1. evictUntilAvailable takes the weights figure and re-resolves the
   requirement on every iteration (and after its clearCache retry) instead
   of comparing against a requirement captured before its suspensions — a
   concurrent verified prefetch can raise the serving-set floor mid-wait,
   and the stale lower figure admitted a load the post-load guard then
   rejected (accepted-then-503). The MTP admission after the loop resolves
   its target requirement live too.

2. fastAdmissionReject re-resolves the requirement after its retry memory
   sample as well (the second suspension was outside the earlier fix).

3. Doctor keeps a LIVE empty advertised set authoritative all the way into
   the verdict: DoctorRunner passes [] through (only the offline
   reconstruction maps empty to nil), and ModelFitDiagnostic resolves an
   empty declared set at the default floor — the target cannot be joining a
   set the daemon does not advertise — instead of crediting the retired
   target its solo floor. Regression test added.

4. applyVerifiedPrefetch re-slices resident engines and refreshes aggregate
   capacity after the reserve raise and BEFORE announcing the enlarged set,
   so the coordinator never routes against a token budget the tightened
   shared KV gate rejects until the next periodic tick.

5. Plan: canonical file:line citations restored for the Qwen3-VL production
   path (supported-family predicate, wrapper extraction, construction
   switch), and the Phase-1 release-coupling item marked completed
   (0.8.16 in-tree, pairing test).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): resolve the floor after every admission suspension, preflight advertise-only reserve raises under the re-slice gate

- evictUntilAvailable samples memory first and resolves the requirement
  after that suspension: its zero-iteration exit compared against a floor
  captured before the await, so a concurrent raise could admit a load the
  post-load guard then rejects.
- admitSpecDecIfMemoryAllows takes the target's weight basis and resolves
  the headroom after its own memory sample (same class).
- applyVerifiedPrefetch preflights the serving-set reserve raise against
  the resident slots' serviceability floor and refuses the advertisement
  when the raise would strand a survivor below it, mirroring the load
  path's newcomer refusal. The preflight -> push -> insert -> re-slice ->
  capacity-publish sequence holds the re-slice gate like a load does, so
  a load cannot size itself against the pre-raise budget in between.
- Capacity republished after the drop and cold-retire relaxes so
  free_for_load_gb never lags the floor by a capacity tick; retiring
  rejects tagged slot_state rather than memory_cap; doctor's offline
  serving-set basis mirrors the daemon's runtime-eligibility filter.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): retry reserve-refused desired builds, drain the box before the retirement reconnect

- A verified prefetch refused by the reserve-raise preflight now schedules
  a retry through the desired-build backoff policy (the re-run is a hash
  pass — the bytes are on disk). `.verified` still goes out for the
  attempt and the coordinator deduplicates unchanged desired_models
  pushes, so without a local retry nothing would ever re-offer the build,
  even after an unload frees the room. The scheduler is factored out of
  the transient-failure handler and shared.
- The failed-self-test retirement waits for box-wide in-flight work to
  drain (bounded by the shutdown drain budget) before forcing the
  reconnect that communicates the removal: closing the socket cancels
  every in-flight request on the box, not just the retired model's.
- Plan doc: the paged-cache subscript is fixed on this branch (cite the
  mapping, not the removed line); the vision-tower transient rides the
  activation reserve — VisionMemoryGate covers media buffers and KV, not
  activations — which is the reason vision-capable floors stay deferred.
- Regression test: a scripted resident gpt-oss slot on a 32 GiB box where
  the resident floor leaves a serviceable grant but an unmeasured build's
  raise would not; the desired build is refused, one retry is pending,
  and it advertises once the box has room.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* docs(plan): gemma residency stays deferred behind the VLM-path measurement

The sequencing bullet still listed gemma-qat4 as a text-only artifact
ready for measured residency, contradicting the plan's own vision gate:
its checkpoint declares vision_config, the provider loads it through the
VLM factory, and the text-only 13.48 GiB figure undercounts the tower.
Only gpt-oss is ready for Phase 3a.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): detach and coalesce the retirement reconnect, harden the preflight test against slow runners

- The startup preloader awaits retirement inline, so the box-wide drain
  wait added for the reconnect would have stalled every remaining preload
  candidate behind a busy box for up to the drain budget. The reconnect is
  now a single detached task: un-advertise, unload, and the tombstone stay
  inline; one background re-registration per burst of retirements fires
  after in-flight work drains (the client store already excludes every
  retired id by then), skipped on shutdown.
- Load-failure rejects capture the retiring flag before the cleanup
  awaits, so a retirement completing during them cannot misfile the reject
  as memory_cap.
- ReserveRaisePreflightTests asserted an exact prefetch count that a slow
  CI runner violated (the first retry fired before the observation).
  Assert the refusal invariants that hold at any point of the retry cycle
  and use a retry budget the tight phase cannot exhaust.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): discard capacity snapshots built across a reserve move, barrier admission across the retirement reconnect

- updateAggregateCapacity captures the activation-reserve epoch at entry
  and discards its snapshot if a push landed across its actor hops: a
  refresh suspended inside the engine summary could otherwise resume after
  a verified prefetch raised the floor and overwrite the newer publish
  with pre-raise slot budgets and free_for_load_gb.
- The post-retirement reconnect raises an admission barrier on the actor
  before closing the socket and re-checks in-flight work after the drain's
  last observation, so a routed request cannot be admitted in the hop only
  to be cancelled by the disconnect; the barrier lifts on the new
  session's .connected. Shutdown cancels a pending reconnect task.
- A retired, un-advertised id with a standing failed-hash record is
  fast-rejected (slot_state) until the register lands, instead of
  accept-then-404 at the advertised guard; the same predicate tags both
  rejection sites.
- Tests: the prefetch advertise test waits on the client store (the local
  insert now precedes the client add by the re-slice hops); the preflight
  test requires the reserve env override to be unset.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): re-validate the load gate before allocation, serialize advertise-raises behind in-flight loads, consult the durable record in the shared guard

- ensureModelLoaded re-runs the padded-weight gate after the last
  suspension before allocation (pending-load reservation, weight hashing,
  the pre-load hook): the serving-set floor can move across any of them
  and the pending reservation only fences competing KV grants. Refuse
  before shard staging rather than let the post-load guard unload an
  already-overcommitted transient.
- applyVerifiedPrefetch defers an advertise-only reserve raise while any
  load sits between admission and install (modelsLoading is set before
  the gate), through the desired-build backoff — the primary defense; the
  allocation-time gate is the backstop.
- throwIfRetiring and fastAdmissionReject's post-sample checks consult
  isRefusedByRetirement (tombstone OR durable failed-hash record): a
  suspension spanning an entire retirement could otherwise resume into
  the failed build with its pre-retirement modelInfo.
- updateAggregateCapacity recomputes (bounded, three attempts) when the
  reserve epoch moves across its hops instead of returning without a
  publish — not every push site publishes a replacement snapshot.
- Measurements report: paged-cache mapping marked fixed with its current
  citation; the text-only caveat states that the vision-tower transient
  rides the activation reserve and is not contained by VisionMemoryGate.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): add the load's own reservation back in the allocation gate, pin pending advertisements into the reserve basis

- The allocation-time backstop compared the free-for-load figure — which
  nets out every ledger entry, including this load's own pending
  reservation placed just above — against the full padded requirement, a
  double count that refused every load whose padded weights exceed the
  headroom (the 24 GB gpt-oss-20b tier: 18.0 required, ~4.6 netted). The
  comparison now goes through ModelLoadAdmission.fitsAtAllocation, which
  adds the load's own reservation back; regression test pins the tier
  arithmetic in both directions.
- applyVerifiedPrefetch re-checks modelsLoading AFTER the survivor
  preflight (it awaits each bridge's grant, and a load admitted during
  those hops is in neither modelSlots nor the preflight) and pins the id
  into pendingAdvertise before the reserve push, so a load admitted during
  the push's own suspension already resolves its gate and fleet budget
  against the raised floor. Cleared when the id joins advertisedModels or
  on the tombstone refusal.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): cover the retained MTP assistant in the allocation-time requirement

The pending reservation added back by the allocation gate covers the
padded target PLUS a separately staged assistant, but the requirement it
was compared against covered the target alone, so the assistant's bytes
dropped out of the check: a box where the target fits but target +
assistant no longer does would pass and let the later assistant
allocation cross the unified-memory cap. The requirement now uses the
gate's weight basis with extraWeightBytes included — the same basis as
the reservation. Regression test pins the asymmetry.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): re-offer capacity-deferred desired builds on the capacity change itself

A verified prefetch deferred for a capacity reason (the reserve raise
would strand a resident slot, or a load was in flight) was retried only
through the desired-build backoff — about 18 minutes — while the idle
unload that typically frees the room defaults to 60 minutes; once the
budget expired the provider stayed on the superseded build until the
next desired_models push (the coordinator deduplicates unchanged state).

Deferred ids are now remembered (reserveDeferredPrefetches) and re-offered
with a fresh budget by the capacity-change events themselves: a slot
unload and a load finishing (installed or failed). The backoff stays as
the near-term retry; a still-tight box simply defers again. Cleared when
the build advertises or leaves the desired set.

Regression test: refused → re-offered by the resident slot's unload
inside seconds with a 600 s backoff configured.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): carve the per-model reserve in standalone serving, re-offer explicit prefetches deferred for capacity

- StandaloneServer resolves the activation reserve over its serving set
  (configured models ∪ resident slots ∪ in-flight loads) and passes it to
  every consumer the network provider threads it through: the KV budget
  actor, the fleet KV budget behind engine grants, the load gate's
  headroom, the paged capacity decision, and both post-load probes. A
  tight box the coordinator path admits (24 GB gpt-oss) is admitted by
  `darkbloom local` too. Regression test pins the gpt-oss grant against
  the measured floor rather than the flat default; the existing re-slice
  test now expects the lone survivor's own floor after an eviction.
- Capacity-deferred prefetches are remembered whether desired OR explicit
  (`prefetch_model`): the coordinator ignores the `.verified` status the
  refused attempt emits, so the capacity-change re-offer is the only path
  that ever advertises the build. Ids are forgotten before each re-run —
  a re-refusal re-remembers, an unwanted build drops out.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): re-push the standalone reserve on setModels, pass it to the MTP fallback probe

- StandaloneServer.setModels changes the serving set, which is part of
  the activation-reserve basis: it now re-pushes the resolved reserve
  into the KV budget actor and re-slices the resident grants under the
  new floor before a higher-floor model can be exposed (a load in flight
  re-slices at its own install, so the regrow is skipped then). Regression
  test: a gpt-oss-only server's grant shrinks to the raised-floor budget
  when an unmeasured model is added.
- The target-only fallback probe after a failed MTP build now takes the
  resolved reserve like the first probe (the earlier pattern edit missed
  its indentation), so a tight box is not unloaded against the flat
  default it just cleared.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): run the setModels re-slice when the in-flight load finishes, including on failure

setModels skipped the survivors' re-slice while a load was in flight,
assuming the load's install would re-slice under the updated reserve. A
load that fails before install restores the PREVIOUS grants (sized under
the old floor) and installs nothing, so nothing re-sized them while the
KV budget actor already enforced the new floor. The re-slice is now
deferred (resliceDeferredBehindLoad) and run from both completion paths
of the standalone load — after a successful install (idempotent) and
after the failure cleanup — via runDeferredResliceIfNeeded. Regression
test drives the deferral through the load-marker seams and asserts the
resident grant ends under the raised floor.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): serialize and preflight standalone setModels, re-validate the standalone load gate before allocation

- setModels is serialized behind any in-flight load: that load passed
  its gate against the current floor, so the whole list update waits
  (pendingModelsUpdate) and applies on either completion path of the
  load — failure included, where the restore-on-throw has just put the
  old grants back.
- The update is preflighted against the resident slots' serviceability
  floor (reserveKeepsSurvivorsServiceable, the load path's newcomer
  check applied to a raise with no newcomer); a stranding raise is
  refused and the current list kept. setModels returns whether it was
  accepted.
- The standalone load re-validates the padded gate after its last
  suspension before allocation, through the same fitsAtAllocation
  helper (own reservation added back, retained assistant included) —
  the backstop behind the serialization.
- Tests: deferral behind a load (updated), refusal of a stranding raise
  on a single tight slot (new).

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): hold the standalone load gate across setModels, epoch-stamp its reserve pushes

- applyModels now runs under the standalone load gate (isLoadingAny),
  held from the serviceability preflight through the reserve push and
  the re-slice: the preflight suspends on each resident bridge, and a
  cold load entering meanwhile would pass its own gate under the old
  floor and install a slot the preflight never saw. Loads and further
  setModels calls park on the gate exactly as they park behind a load;
  a list parked while an apply held the gate is applied right after it.
- Every standalone reserve push carries a monotonic epoch (as the
  provider's do), so a stale lower-floor push can never land after a
  newer one at the KV budget actor even if delivery reorders.
- Test seams hold and release the same gate the load path does.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): keep a deferral whose attempt is still finishing, drain parked setModels, re-push the standalone reserve at every basis change

- retryReserveDeferredPrefetches skips an id whose prefetch attempt is
  still in flight in the coordinator (ModelPrefetchCoordinator.isInFlight:
  the tail after onVerified returns, before the terminal status emits) —
  a re-run then would coalesce into that attempt and never reach
  applyVerifiedPrefetch again. The deferral and the desired backoff are
  kept for the next capacity change instead.
- setModels drains a list parked behind its own apply (the load-completion
  drains cannot be relied on: no load may ever come).
- The standalone eviction path pushes the relaxed reserve before the
  survivors grow into it; both standalone load-marker transitions push
  the reserve (the marker joins/leaves the basis); the standalone load
  re-validates its model's membership right before marking itself
  loading, so a stale modelInfo removed by setModels cannot re-enter the
  basis.

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* docs(reserve): correct the 24 GB tier claim, derive the tier arithmetic in a test, fix stale standalone comments

The CHANGELOG, the floors-table comment and the PR body claimed the
measured gpt-oss floor makes the catalog's 24 GB tier reachable. The
live gate resolves usable = free-plus-inactive − max(memory_reserve_gb,
total − 0.9·total); with the shipped 4 GiB reserve default the 24 GB
tier still needs ~22 GB free of 24 at the 18.0 GB requirement, and the
#653 reporters measured 12.3 and 16.1 GB usable. What the floor does
close is the 32 GB flap band (the gate needs ~22 GB free instead of
~24). The remaining 24 GB levers — the admit-time disk×1.2 padding and
the small-box reserve default — are recorded as follow-ups.

A DiagnosticsTests case now derives usable from total, the reserve
default and a free-plus-inactive figure through the real gate
arithmetic instead of taking usable as an input, and pins both tiers'
verdicts. Two comments that still described StandaloneServer as
keeping the flat default are corrected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(review): wake a kept deferral once its prefetch attempt finishes; re-validate the gpt-oss floor on the shipped pins

- A capacity-deferred prefetch whose attempt was still finishing inside
  the coordinator kept its deferral but had no wake-up: explicit
  prefetch_model builds have no backoff timer and the capacity-change
  callers may never fire again. A one-shot wake-up per id now waits (off
  the actor) for the coordinator's attempt to reach terminal cleanup and
  re-offers the deferred builds.
- The measurements report's 32 GB conclusion assumed the 0.9×physical
  cap; the effective ceiling is min(0.9×physical, physical −
  memory_reserve_gb) = 28.0 GiB under the 4 GiB default, so a qwen3.6
  floor near 3.5 GiB does not fit either — the 36 GB re-tier is required
  regardless of any floor change.
- The gpt-oss-20b B=8 cells were re-run with BenchCBv2 built at exactly
  the pins this PR ships (mlx-swift-lm 30da946 incl. the #126 MoE
  dispatch change, mlx-swift 6b0505c): 4k-prompt peak−active 4.17 GiB
  (identical to August), 500-token 2.27 GiB; the non-KV envelope stays
  ~2.7 GiB, so the 3.5 GiB floor holds with ~0.8 GiB margin. The raw
  reports are committed under docs/reports/raw/ (the August raw files
  were lost with their worktree).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

* fix(load): refuse hopeless evictions up front; fold in the design review's pre-merge corrections

- Eviction feasibility (#653's 32 GB report: "a request for a model I
  can't serve killed the one I could"): before the first eviction, the
  load path asks whether evicting EVERY idle model plus the reclaimable
  buffer cache could reach the requirement at all; when it cannot, the
  load is refused without evicting. The fast pre-accept check applies the
  same bound so the coordinator reroutes instead of accepting a request
  that would only fail after the same check. Pure decision
  ModelLoadAdmission.evictionCanReach, unit-tested on the report's numbers.
- Wording: the floor narrows the 32 GB flap band by ~2 GB (both #653
  samples get the same verdict under either floor) rather than closing
  it; the plan no longer claims the 24 GB tier is unblocked; the consumer
  hardware-requirements doc describes the reserve instead of a 2 GB
  headroom; an orphaned qwen basis comment (no such table entry) is
  removed from the coordinator floors table.
- Re-validation: the 4k batch-8 cell re-run with production's prefill
  construction (chunk 512, solo stripe 2048) measures the same 4.17 GiB
  peak-over-active; raw report committed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxThXGrq5VyXfUzcahTVw6

---------

Co-authored-by: Jeremy Joplin <jeremykjoplin@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

This branch had an error being deployed

1 failed and 3 active deployments
Preview – d-inference-console-ui-dev — 4cfdf93a Deployed Aug 31, 2026 by vercel[bot]
Preview – d-inference — 4cfdf93a Deployed Aug 31, 2026 by vercel[bot]
Preview – d-inference-landing — 4cfdf93a Deployed Aug 31, 2026 by vercel[bot]
benchmarks — 4cfdf93a Deployed Aug 31, 2026 by Gajesh2007 via E2E Benchmarks #566
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.

2 participants