Merge nucleic/eager-glass-wren-mrb5 into dev

This commit is contained in:
2026-07-29 18:58:02 -07:00
parent 34a808befc
commit 11a566fb00
+81 -25
View File
@@ -43,10 +43,12 @@ is a classic small-model problem.
classifier is exactly the energy bill this policy exists to avoid. Both shipped models
are *designed for accelerator eligibility from day one* (fixed shapes, fp16-friendly
ops — §4.2), and scheduling is verified, not assumed (§8).
6. **Two model sizes, one contract:** a **light** classifier every machine can run, and a
**deep** classifier (§4.3) that meaningfully exploits ANE/NPU headroom for higher
accuracy on the ambiguous prompts the light model gets wrong. Same labels, same
calibration semantics, same wire — machines differ only in which artifact answers.
6. **A model ladder, one contract:** a **light** classifier every machine can run, a
**deep** classifier (§4.3) that exploits ANE/NPU headroom for higher accuracy on the
ambiguous prompts the light model gets wrong, and a **max** classifier (§4.4) that
spends big-GPU headroom (RTX/Radeon-class, AC power only) on the best result the
hardware can deliver. Same labels, same calibration semantics, same wire — machines
differ only in which artifact answers.
7. **No architectural churn:** the classifier slots into the existing layer stack behind
`PurposeVerdict.Source`; routing, session plumbing, UI, and wire stay as shipped.
@@ -178,11 +180,12 @@ runs quantized **7B** LLMs at usable rates ([NPU comparison](https://localaimast
the ANE side routinely serves Apple's ~3B foundation model. So the deep model is sized by
*task saturation*, not by hardware: past roughly half a billion parameters, **encoder**
accuracy on a fixed 8-way task flattens, and the next meaningful step up is a 13B
generative decoder — rejected, because it reintroduces exactly what this plan removes
(uncalibrated generation, hundreds of ms, GB-scale memory residency) for negligible
classification gain. Target ship size: **large-class encoder, ~400M params**, decided by
ablation (below), with the older base size kept as a pipeline ablation rung rather than a
shipping artifact.
generative decoder — rejected *as the default path*, because it reintroduces exactly what
this plan removes (uncalibrated generation, hundreds of ms, GB-scale memory residency)
for negligible classification gain on ordinary machines. (§4.4 revisits the decoder under
strict constraints as the optional GPU-max tier.) Target ship size for deep: **large-class
encoder, ~400M params**, decided by ablation (below), with the older base size kept as a
pipeline ablation rung rather than a shipping artifact.
- **Architecture:** ModernBERT-class encoder, **large size (~400M params; ~150M base
variant trained as an ablation rung)**, **512-token window** (the hard cases are long —
@@ -212,16 +215,59 @@ shipping artifact.
- **Same export discipline as 4.2:** fixed 1×512-token shape, fp16 (+ int8-QDQ variant),
both ONNX and Core ML artifacts, same tokenizer spec, same threshold JSON schema.
- **Model selection at runtime** (per machine, decided once per launch and re-checked on
asset/hardware change):
- deep model present **and** verified accelerator-resident (§5/§6 checks) → **deep is
the L1 classifier**; lite stays loaded as the instant-answer for the first keystrokes
if deep's asset is still warming;
asset/hardware/power change):
- max model present **and** its §4.4 eligibility holds (verified GPU rung + memory
floor + AC power) → **max is the L1 classifier**;
- else deep model present **and** verified accelerator-resident (§5/§6 checks) →
**deep is L1**; lite stays loaded as the instant-answer for the first keystrokes
if the bigger asset is still warming;
- otherwise → lite is L1 (which is already ≥ the current AFM path).
Power transitions re-run this ladder live: unplugging mid-session steps max → deep
seamlessly (next debounce tick answers on the smaller model).
A cascade variant (lite always answers; deep re-scores only low-margin drafts) is the
fallback posture if deep-everywhere measures worse on energy than expected — the
layering makes either wiring a one-line change in `PromptPurposeService`.
### 4.4 Artifacts, conversion, versioning
### 4.4 The GPU-max classifier (`purpose-max`) — spending real GPU headroom
§4.3's sizing argument stands: past ~0.5B parameters, a *bigger encoder* buys latency,
not accuracy. So the tier above deep-large is not a larger encoder — it is a **different
kind of model** that only RTX/Radeon-class (or big-unified-memory Apple GPU) headroom
makes viable: a small **instruction-tuned decoder used as a constrained classifier**.
This deliberately *revisits* §2's decoder rejection, which was a rejection for the
*default* path (uncalibrated free generation, hundreds of ms, GB-scale residency on
every machine). Under the max tier's constraints, each objection is answered rather than
waved off:
- **Architecture:** a 1.54B instruction-tuned decoder (Qwen/Phi-class), LoRA-fine-tuned
on the same dataset and merged. Classification is **not free generation**: one prefill
over the 512-token head + a single constrained step with the logits masked to the 8
label tokens — the masked-label distribution is the output, temperature-scaled on the
validation split exactly like the encoders (this restores the calibration story the §2
rejection was about). Secondary-purpose and difficulty ride the same masked readout.
- **What it buys beyond accuracy:** a short *generated* rationale (one clause, capped
tokens) for the chip tooltip — a per-prompt "why", where the encoders can only attach
canned per-cell copy. Generation of the rationale is optional, async after the label,
and never on any routing path.
- **Eligibility (all required):** a verified GPU rung per §5/§6 (NvTensorRT-RTX /
MIGraphX / OpenVINO-GPU on Windows, Apple GPU on macOS) **and** a memory floor
(≥ 6 GB dedicated VRAM, or ≥ 24 GB unified memory on Apple silicon) **and AC power**.
On battery the ladder drops to deep on the ANE/NPU automatically — max is a
wall-power luxury by definition, and the power-transition listener swaps tiers live
(mid-draft transitions just change which model answers the next debounce tick).
- **Artifacts:** int4/int8 quantized, ~12.5 GB — a separately downloaded optional asset
("best on this machine" offer in Settings Intelligence, never auto-downloaded).
Export uses the generative stacks (ONNX Runtime GenAI format for Windows ML's runtime;
a stateful Core ML model on macOS) — meaningfully more export complexity than the
encoders, which is why max is its own late work item and not on deep's critical path.
- **Budgets & gates:** label p95 ≤ 250 ms on the eligibility-floor GPU (still well inside
the debounce window; rationale generation excluded from the budget); ships only on a
**≥ +2-point hard-slice win over deep-large** — if constrained decoding can't beat the
large encoder where it matters, max doesn't exist and the doc's saturation argument was
simply right; idle-unload is mandatory (a multi-GB resident model must never outlive
composer attention, and must never keep a dGPU awake).
### 4.5 Artifacts, conversion, versioning
- **Canonical artifact: ONNX** (opset ≥ 17), one file, one tokenizer spec (exported
vocab + normalization rules — no runtime HuggingFace dependency).
@@ -231,9 +277,10 @@ shipping artifact.
the frozen test set; label agreement ≥ 99.5% and identical accuracy-to-the-gate, or the
conversion is rejected (this is what makes "one model, two runtimes" honest).
- **Windows runtime:** the ONNX artifact as-is, via Windows ML (§6).
- **Naming:** `purpose-lite-v<N>` / `purpose-deep-v<N>` with `.onnx` (canonical, per-EP
quantization variants) and `.mlmodelc` (converted) suffixes.
- **Versioning:** artifacts named `purpose-<lite|deep>-v<N>.<onnx|mlmodelc>`; the version
- **Naming:** `purpose-lite-v<N>` / `purpose-deep-v<N>` / `purpose-max-v<N>` with `.onnx`
(canonical, per-EP quantization variants; GenAI-format for max) and `.mlmodelc`
(converted) suffixes.
- **Versioning:** artifacts named `purpose-<lite|deep|max>-v<N>.<…>`; the version
string rides `PurposeVerdict.reason` provenance and the session routing note, so field
behavior is attributable to a model version. Old artifact kept for one release for a fast
revert (`nucleic.purposeClassifierVersion` defaults key as the pin/kill switch).
@@ -396,11 +443,16 @@ path to the slider later, for free.
2. **Tier-drift invariant** re-run over *model* predictions (not just heuristics), for
both models: every test-set misroute within one cost tier at all levels/lanes — and for
deep's mixed-intent path, the primary+secondary blend rule proven to stay in-bound.
3. **Parity gates:** Core ML↔ONNX label agreement ≥ 99.5% per model (§4.4); C#↔Swift
threshold config is one shared JSON so acceptance semantics can't fork.
4. **Latency:** lite p95 ≤ 20 ms; deep p95 ≤ 100 ms *on the accelerator it's gated to*
(still several debounce ticks under the 700 ms window, and never on the send path).
Measured in eval.py and asserted loosely in a perf smoke test.
3. **Parity gates:** Core ML↔ONNX label agreement ≥ 99.5% per model (§4.5); C#↔Swift
threshold config is one shared JSON so acceptance semantics can't fork. For max, the
masked-label readout must additionally pass a calibration bound (ECE ≤ lite's on the
validation split) — a decoder that classifies better but *confides* worse would poison
the accept/corroborate/escalate contract.
4. **Latency:** lite p95 ≤ 20 ms; deep p95 ≤ 100 ms *on the accelerator it's gated to*;
max label-readout p95 ≤ 250 ms on the eligibility-floor GPU (rationale generation
excluded — async, off every routing path). All still several debounce ticks under the
700 ms window, and never on the send path. Measured in eval.py and asserted loosely in
a perf smoke test.
5. **Accelerator-residency gates** (goal #5 made falsifiable):
- macOS: `MLComputePlan` ANE-op share ≥ the per-artifact floor emitted at conversion,
checked at load on-device (rung fallbacks per §5 — the GPU retry must itself pass a
@@ -419,7 +471,9 @@ path to the slider later, for free.
cycle with A/B logging *locally* (both verdicts recorded in the routing note's reason);
flip default on when disagreement analysis shows the model ≥ AFM on real drafts; remove
the AFM layer (L2 demotion, §5) after two clean releases. Deep rolls out behind the
same telemetry one cycle after lite.
same telemetry one cycle after lite; max one cycle after deep, and only if its §4.4
hard-slice-over-deep-large gate held on the frozen set (a tier that can't beat the one
below it is deleted, not shipped).
---
@@ -476,8 +530,10 @@ path to the slider later, for free.
| 7 | Windows ML runtime (with the port's §7 renderer) | `PurposeClassifier.cs` (Windows App SDK 1.8.1+, power-source-aware policy ladder — `MAX_EFFICIENCY` on battery; NPU→GPU (NvTensorRT-RTX/MIGraphX/OpenVINO-GPU)→CPU on AC — explicit-EP validation path, AOT compile-at-first-use), shared threshold JSON, degradation ladder |
| 8 | `purpose-deep` training + heads | ModernBERT-class multi-task checkpoints at base (~150M) *and* large (~400M), 512-token window, ≤ 850 MB fp16 / ≤ 450 MB int8 artifacts, hard-slice +5pt-over-lite and large-vs-base ablation gates, mixed-intent blend rule in router + tests |
| 9 | Deep-model distribution + selection | on-demand asset download/verify/pin (macOS-VM base-image pattern), §4.3 runtime selection ladder, accelerator-residency gating on both platforms, Settings download affordance |
| 10 | Rollout | dark-ship + local A/B logging, lite default flip, deep one cycle later, AFM layer retirement |
| 10 | Rollout | dark-ship + local A/B logging, lite default flip, deep one cycle later, max one after that, AFM layer retirement |
| 11 | `purpose-max` (optional tier, after 89 prove out) | 1.54B LoRA-fine-tuned decoder, masked-label constrained readout + calibration, GenAI-format ONNX + stateful Core ML exports, GPU+memory+AC eligibility gating with live power-transition stepping, async rationale generation, idle-unload |
Items 15 are independent of the Windows port's schedule; 6 can land any time (additive);
7 rides the port's renderer milestone; 89 start once item 5's hard slice exists (deep's
gate is defined against it).
gate is defined against it); 11 starts only after deep ships and holds its gates — max is
an *optional* crown, not a dependency of anything.