Commit Graph

88 Commits

Author SHA1 Message Date
vh b8435ceb6f Set mog-sec's context to a measured ceiling so it refuses instead of dying
Operator: "loading up the context killed sec again." That reproducer is what
finally made the failure legible, and it showed the previous four fixes had all
been aimed at the wrong quantity.

What the KV pool can hold and what the card can process at depth are different
numbers. Cutting context 420k to 384k to 320k, pinning the KV in bytes, and
dropping the prefill chunk from 16384 to 4096 all sized the pool. The crashes were
governed by the transient needed to process a prefill chunk against a quarter
million tokens of resident KV, which scales with depth and not with pool size.
Each change helped and none fixed it.

Bisected against the real reproducer, with a non-repeating prompt because prefix
caching would let a repeated one hash to cached blocks and never prefill deep:

  113,247 prompt tokens  SURVIVED (27 s)
  200,088 prompt tokens  SURVIVED (174 s)
  ~285,000 prompt tokens ENGINE DIED, HTTP 500, container restarted

The sustainable ceiling therefore sits between 200k and 285k with gen idle, and
gen shares the card with its load uncontrolled, so 163,840 takes about 20% margin
under the proven-good depth rather than sitting at the measured edge.

The ceiling's purpose is the refusal. Verified after the change: a 149,073-token
request serves in 41 s, and requests at both 200k and the ~285k depth that killed
the engine now return a clean 400 naming the limit in under a second with the seat
untouched. A seat that refuses what it cannot serve is strictly better than one
that dies trying.

Concurrency went 1.03x to 2.09x. The compose header's "served at native 262K" was
never actually deliverable on a shared card; it had simply not been exercised at
depth until today.

The probe is committed rather than described, so the ceiling can be re-measured
when the card's tenancy changes.
2026-09-10 16:04:16 -07:00
vh 9a916a759f Swap the MeroMero A4B onto the erp-seat seat as char-rp-fast, retire the Pfish-6 alias
Operator: "replace that a4b moe over pfish-6 -- remove the pfish-6 alias and
create an alias for char-rp-fast."

G4-MeroMero-26B-A4B-it-uncensored-heretic-NVFP4A16 is live on ana-ml2 :8021 under
its own served name, behind the new gateway alias char-rp-fast. Pfish-6 is gone
from the gateway and now returns an explicit 400 rather than a substitution; 0 of
17 LiteLLM keys scoped it, so nothing was orphaned. The compose project name stays
erp-seat because asset-engine derives seat liveness from it.

The first quant of that A4B served NaN and passed its healthcheck doing it. It was
built with the dense v2-31B recipe, whose ignore list has no router regex, so all
30 MoE routers were quantized to 4 bits -- and a 4-bit router changes which experts
run rather than degrading them. Quant rc=0, healthcheck green, correct KV pool,
correct served name, and every completion returned finish_reason=length with the
full token count and content: null. The model was emitting a full budget of tokens
that decoded to the empty string. Raw /v1/completions was empty too, ruling out the
chat template and the reasoning parser. The signal that named it was logprobs:
vLLM refused to serialize the response, "Out of range float values are not JSON
compliant: nan".

The lesson is about the control rather than the router. That tree had already been
structurally diffed and passed -- against a verified-good DENSE quant of the same
Gemma-4 family. A dense model has no routers, so the single thing that was wrong
was the single thing the control could not distinguish. Diffing instead against
Pfish-6, a known-good quant of the same 26B-A4B MoE, gave it in one line: 222
ignore entries against 252, the 30 missing being layers.N.router.proj. A positive
control is only worth what it can distinguish, and "same family" is not "same
architecture class".

Re-quantized with the MoE recipe, whose dry-run asserts 11,520 expert Linears and
refuses a router in the quantize set before any GPU time. The live seat then passed
prose with no channel-prefix leak, a solid-colour image read correctly, an auto
tool call parsed, finite logprobs, and KV 534,649 tokens / 2.04x carried over from
Pfish-6 unchanged. The broken tree is parked on ana-ml2 as
...-NVFP4A16.BROKEN-routers-quantized-20260910.

Section 4.4's temp port was not reachable: 15.9 GiB of weights plus KV plus
multimodal encoder-cache profiling does not fit in the ~19 GiB free beside GPU1's
six other tenants -- 0.20 utilization refused admission, 0.185 OOM'd in encoder
profiling. The substitute was reversibility and ordering: named .env backup, prove
the seat on its real port while no alias points at it, move the alias last. That is
why a NaN-serving seat never reached a consumer. The seat was down about 16 minutes
across two attempts; no consumer saw a broken alias.

Playbook gains the router-quant failure signature and the control-class rule in
3.15, and a logprobs check in 4.4. seat_verify.py carries that check as check 6.

Quality is NOT established: no RP eval, no long-context check, no A/B against
Pfish-6 or char-rp. Samplers are the author's card values, untuned here.
2026-09-10 11:34:05 -07:00
vh 1a5bc2ddf1 Land the MeroMero v2-31B NVFP4A16 quant and record the pinned-transformers trap
The v2 dense quant had failed four times. Attempt 5 lands it at 19 G.

The blocker was not what it looked like. `AmbiguousGlobalPerLayerAttributeError`
on `head_dim` read as a malformed upload -- DogOnKeyboard's config carries a
`per_layer_config` key zerofata's canonical one lacks -- and the standing fix was
to force `allow_global_per_layer_attribute_access=True`. Both halves were wrong.

`pip install llmcompressor==0.13.0` downgrades transformers 5.16.1 -> 5.14.1. The
config was serialized by 5.16.1, which materializes `per_layer_config` from
`global_head_dim` + `layer_types`; 5.14.1 carries the heterogeneity guard but not
the gemma4 resolver. Under the image's own transformers the same config loads
fine. `:latest` was also re-pulled during attempt 4 and no earlier run, so the
toolchain moved mid-diagnosis. Two things separated "malformed upload" from
"moved toolchain": reproducing the real failing call (a bare AutoConfig load does
not reproduce it; the trigger is reached through AutoTokenizer) and keeping
zerofata's canonical tree, quantized cleanly on 2026-08-21, as a positive control.

The fix drops `per_layer_config` rather than forcing global access. It is exactly
redundant -- keys are precisely the ten full_attention layer indices, sole value
(512, 4), verbatim the global fields -- and forcing instead would make
`config.head_dim` answer 256 to the callers building the 512-wide layers.
patch_perlayer.py re-proves that redundancy at apply time and refuses if it ever
stops holding.

Verified on the tensor table rather than the exit code: the output is identical
family-for-family and count-for-count to the August canonical quant, with 356
BF16 vision-tower tensors preserved and input_activations=None. A GPU-free load
leaves 0 tensors on meta and generates coherent prose. The section 4.4 serve test
has NOT run -- GPU1 has 19.9 GB free against 19.5 GB of weights, so it needs a
live seat displaced.

Also fixes the A4B output, which had a truncation cap baked into its tokenizer
(max_length 8192) from being quantized with the calibration corpus.

Playbook gains section 3.17 for the pinned-transformers class and sharpens 3.16
to say drop the dataset outright for any A16 scheme.
2026-09-10 10:56:16 -07:00
vh eb75713c1b Wire Beszel fleet filesystems, GPU telemetry, dashboard and alerts 2026-09-10 08:35:28 -07:00
vh 6972e7ef7f feat(erp-seat): run 7 quantized to NVFP4A16 and serving as the trial seat on ana-ml2
- services/erp-seat-quant/run_quant_erp_v7.sh: v6 runner retargeted; dry-run gate
  passed identically (11,725 targets, 11,520 experts = 30x128x3, routers+vision BF16)
- 49 GiB bf16 relayed gx10 -> ana-ml2 (no key path either way; nh3-dev relays),
  checksums verified against source; quant 49 -> 16 GiB, all post-steps clean
- stacks/erp-seat: .env-driven swap to erp-tune-v7-nvfp4a16, served under its TRUE
  name; homepage labels + README updated, v6 rollback path recorded
- stacks/litellm: trial -> erp-tune-v7-nvfp4a16 (config-file alias; /model/update
  refuses a config model, so this is an edit + restart)
2026-09-09 15:30:15 -07:00
vh c335c38c19 fix(booth): a partial ask answer is recorded, not refused
Operator: the form failed when a question was left blank. Refusing the whole
submission over one blank threw away the picks that were made, and the HTML
`required` on the radios blocked it in the browser before the server saw it.

- answered questions recorded; blank ones land in `unanswered`; `complete` says
  whether the set is finished; a blank question carrying a note keeps the note
- `required` dropped from both templates so the browser cannot block a partial
- refused only when there is no pick anywhere AND no notes (a 400 — that would
  flip an open ask to answered with no decision recorded); a choice outside the
  option list is still an error
- new ◐ partial state with an n/N count; skipped questions render as skipped
- README + global CLAUDE.md tell reading sessions to check `complete`
- 154 tests; v0.1.15
2026-09-09 15:05:18 -07:00
vh 784c555dbd docs(booth): record WHEN inline ask placement earns its markup — the artifact-in-working-memory test, not just the placement rule (framing from tts-dev) 2026-09-09 14:23:19 -07:00
vh 6e0b85ba27 docs: refresh what today's work made stale — booth asks (inline placement promoted to its own section), ana-ml2 nvme7 settled by the scrub result, nh3-dev booth entry + the CLI-on-PATH fix, run-07 runbook outcome + serving state 2026-09-09 14:18:34 -07:00
vh 78c3a7c170 feat(booth): asks render INLINE in a verbatim report, placed by the author
Operator verdict on the separate /asks page: the question belongs with the
artifact it is about. A four-voice audition wants each voice's radio group
under that voice's audio, and one submit for the lot.

- booth/inline.py: data-booth-ask="stem" | "stem:key" | data-booth-ask-submit,
  plus <!-- booth:ask ... --> comments; unknown stem left alone, not blanked
- _ask_inline.html: self-contained fragments (own scoped styles, no JS), per-question
  groups bound to one form via the HTML5 form= attribute so a scattered
  multi-question ask still POSTs once
- unplaced questions and a missing submit block are appended, so a partially
  marked-up page can never produce an unsubmittable 400
- chip becomes a jump link to the first open ask; /asks page kept as a fallback
- 6 tests (one caught the partial-placement drop); v0.1.14
2026-09-09 14:16:03 -07:00
vh a56743ade3 fix(booth): asks were invisible in a booth serving its own index.html
A custom index.html is returned verbatim, so booth.html's asks panel never
rendered there — a valid ask (emmie-anchor/anchor.ask.json) was listed by the
CLI and shown nowhere, with nothing to say so.

- panel extracted to _asks.html; new GET /b/<name>/asks standalone page
- verbatim pages get an amber '? N open asks' chip beside the back chip
- POST /answer honours back=asks so answering returns to that page
- single-question asks now keep an optional 'title' (was silently dropped)
- README + routes table; 8 regression tests; v0.1.12
2026-09-09 10:39:18 -07:00
vh 2a186e4762 fix(booth): CLI resolves its source through the ~/.local/bin symlink (readlink -f), so ask/asks/answer/unlink work from any cwd 2026-09-09 09:21:34 -07:00
vh c85a700141 feat(booth): multi-question asks — a questions list renders one form with a radio group per question and lands as one answer sidecar keyed by question
- asks.py: single {prompt, options} and multi {title, questions:[{key, prompt, options, notes?}]} both normalise to questions[]; per-question notes; every question required on submit
- /answer reads choice.<key> / notes.<key> / notes for multi; single shape unchanged
- booth asks prints per-question picks; README + CLI header; install step symlinks the CLI to ~/.local/bin; v0.1.10; 135 tests
2026-09-09 09:20:35 -07:00
vh 3fe01225a9 feat(booth): asks — a multiple-choice question a session poses in a booth, answered by the operator as a radio form + notes, written back as an answer sidecar
- booth/asks.py (stdlib): <stem>.ask.json question / <stem>.answer.json answer; normalise+validate, atomic write, list with answer folded in, broken asks surfaced not hidden
- POST /b/<name>/answer: validates choice against the ask (400), unknown stem 404, re-answer overwrites
- booth.html asks panel above the gallery; amber open / green answered; JS-off form POST; index card + booth header badge for open asks
- CLI: booth ask / asks / answer [--wait [SECS]]; remote sessions poll <stem>.answer.json over HTTP
- ask/answer files excluded from gallery items and item counts; 23 tests; v0.1.9
2026-09-09 07:24:16 -07:00
vh 911ff20356 feat(erp-seat): NVFP4A16 quant pipeline for the Gemma-4 26B-A4B MoE ERP tune + ana-ml2 GPU1 serve stack
- services/erp-seat-quant/quant_nvfp4a16_gemma4_moe.py: linearize_moe first (playbook §3.15),
  asserts the expert Linear count, routers/vision/audio/norms/lm_head ignored, W4A16 for RP
  long-session fidelity, post-steps restore processor configs + template and reset the
  tokenizer truncation cap (§3.14); --dry-run proves targets before GPU time
- services/erp-seat-quant/run_quant_erp_v6.sh: detached container on GPU1 (vllm-llmcompressor)
- stacks/erp-seat: serve recipe copied from gemma4-charrp, true served name only, port 8021
2026-09-08 21:55:28 -07:00
vh 76fdf45925 feat(booth): pin/favorite, multi-select delete, newest-first link board
The standing link board grew from a flat oldest-first list with a per-row
× into a manageable board: newest links lead, favorites stay on top, and
several dead links can go in one pass.

- Ordering: order_for_display() renders pinned rows first, then newest-first
  within each group (the board is an append log, so newest = most recently
  posted — the row you usually came to grab).
- Pin/favorite: a per-row ★ toggles pinned state via POST /b/<name>/pin.
  State lives in a .pins sidecar dotfile (one content id per line), NOT
  inline in links.md — so links.md stays a pure atomic-append log (many
  sessions post concurrently) and a row's content id never changes just
  because it was pinned. remove_link_entry drops a removed row's pin;
  orphaned pins are inert (renderer only stars a live id).
- Multi-select delete: checkboxes feed POST /b/<name>/unlink-many (repeated
  'sel' content ids), with a select-all box and a live count. The per-row ×
  stays for single removal.
- One <form> with formaction buttons, so checkboxes, ×, ★, and bulk delete
  coexist without nested forms AND all work with JS off; JS only adds
  select-all and the live count. Per-row × confirm reads desc/url from
  data-* attrs, so an arbitrary posted description can't break into the JS.
- Every action is keyed by content id, never row position — same race-safety
  the existing × has, extended to the bulk path.
- Fixed pre-existing undefined --fg/--bg CSS refs in the board styles.

Tests: +19 (pins round-trip, ordering, orphan-inert, remove-unpins, /pin
and /unlink-many endpoints, board render + order). Full suite 102 passing.
Deployed to nh3-dev booth.service; verified live (newest-first, pin
round-trip, bulk delete) against the real 31-row board with no data loss.
2026-09-06 02:29:22 -07:00
vh 0f748ea54e feat(searxng): move to nh3-docker, update, and expose as an MCP tool
The ana-docker instance was returning zero results for every query while
reporting healthy — 4.5 months stale (2026.4.17 against a current 2026.9.3),
its engine scrapers rotted against sites that had changed. /healthz proves
the web app answers and says nothing about whether search works, so seven
days of green sat on top of a search box that found nothing.

Moved to nh3-docker rather than updated in place, because the colo egress is
the other half of the problem: 38.120.12.42 is a datacenter address that
DuckDuckGo and Startpage CAPTCHA, while nh3-docker egresses residentially at
70.230.226.88. Same reasoning as the fleet's residential proxy for yt-dlp,
applied at the source instead of around it.

Config corrected along the way: base_url said searxng.pfi.local, a name
retired on 2026-08-19, while the environment said something else — the env
won so nothing broke and the file quietly lied. The karmasearch.videos
removal key never matched, because the engine's real name has a space.

scripts/searxng-health.sh asserts results > 0 across three unrelated
queries. That is the check that would have caught this, and the only kind
that can: the mechanism was healthy throughout.

services/searxng-mcp exposes it as `web_search` at user scope, so every
Claude Code session has it. Zero results raise rather than returning an
empty list — an empty list is indistinguishable from a broken aggregator,
which is precisely how this hid.

Old instance stopped and removed; DNS alias repointed to searxng.nh3.internal.
2026-09-03 14:07:06 -07:00
vh 0ad332bb4a feat(booth): per-row link removal + render the link board as real UI
The standing link board is the one MULTI-WRITER booth -- every agent session
appends operator-facing URLs to it. "Delete the folder" was the only
granularity available, so removing one dead link meant hand-editing markdown.
It is 32 rows and only grows.

  booth links                 row number, entry id, raw row
  booth unlink 3              by row number
  booth unlink 8b40e0a5       by entry id (what the UI's x posts)
  POST /b/<name>/unlink       form field `entry` = content id

ROWS ARE ADDRESSED BY CONTENT ID, NEVER BY POSITION. The board is append-only
and multi-writer: another session can post between listing it and clicking x,
and an index would then delete a neighbour. An id either matches the row you
saw or matches nothing. A row number typed at the CLI is resolved to its id
BEFORE anything is deleted. Appends and prunes now take the same flock on
.links.lock, so a post cannot be lost inside a prune's read-modify-write.

UI: a booth carrying links.md renders as rows -- description, URL, provenance,
copy button, per-row x -- instead of a markdown blob. links.md is filtered out
of the gallery so it does not appear twice; the header counts LINKS not files;
the empty-state and the one-click "Wipe now" both stand down for a board (same
rule as the kept lane: nothing durable is one click from gone).

booth/links.py extracted, STDLIB ONLY. The CLI needs this logic and must not
require the service venv -- importing app.py drags in FastAPI, so deleting a
line from a text file would have needed a web framework installed.

THREE BUGS FOUND BY TESTING, all in the shell wrapper while the module was
correct throughout -- module-only tests would have caught none of them:

- `[ "$n" -eq 0 ] && echo ...` as the LAST statement made `booth links` exit 1
  whenever the board had rows. `unlink`'s index lookup calls it inside $( )
  under `set -e`, so a successful listing killed the caller and the removal
  silently did nothing while reporting success.
- ids are 8 hex chars and roughly one in forty is ALL DIGITS; those were read
  as row numbers, resolved to nothing, and removed nothing. Now disambiguated
  by the id's actual shape, not by "is it numeric".
- filtering links.md out of the gallery left `items` empty, so a full board
  rendered "This booth is empty" and an empty <div class="gallery"> under 32
  visible rows.

87 tests (was 76): parser tolerance of hand-written prose, content-id
stability across concurrent appends, removal precision, UI branch behaviour
for board/normal/empty booths, and subprocess CLI tests pinning the two shell
bugs. Deployed to nh3-dev and verified against the live 32-row board
read-only; board file byte-identical afterwards.
2026-08-23 12:55:33 -07:00
vh 4be880f36c feat(booth): kept boards can be deleted from the UI; document the TTL-reset trap
Kept boards had no delete path in the UI at all. The kept lane deliberately
omits the wipe control -- a one-click wipe next to the durable stuff is a
footgun -- but "deliberate" had been implemented as "impossible": the only
routes out were ssh or a hand-written API call.

Now it is two deliberate acts. A `release` control on kept cards drops the
sentinel, the board moves to the ephemeral lane, and the existing x wipes it
from there. Release is reversible -- POST /b/<name>/keep pins it again.

  POST /b/<name>/unkeep   release the pin
  POST /b/<name>/keep     pin it (round-trip, so release is not a one-way door)

FOUND WHILE TESTING, and it invalidates the previously-documented workaround:
removing the sentinel BUMPS the booth directory's mtime, and booth age is the
newest mtime in the tree -- so a released board's clock RESETS from 10,000s to
0s and it survives another full TTL. The old comment said "remove the sentinel
first (it rejoins the sweep)", which is true but means the board lives another
24h, not that it gets reaped. Unkeep-and-wait is a delay, not a delete.

test_releasing_a_board_RESETS_its_ttl_clock pins that behaviour deliberately so
nobody re-derives the workaround. Release is what unlocks the x; the x is what
deletes.

CLI: `booth rm` already worked on kept boards but said nothing about it. It now
announces "(was KEPT -- durable board)" so wiping something durable can never
look identical to wiping run output. Not a block -- a CLI user naming a booth
is being explicit.

5 new tests (67 pass). Verified live on nh3-dev: release renders on all four
kept boards, the ephemeral lane keeps its x, and the links board is untouched
with its sentinel intact.
2026-08-23 10:42:32 -07:00
vh 0755ba7d00 fix(quant): stop baking the calibration truncation cap into the shipped tokenizer
load_calib tokenizes with tok(..., truncation=True, max_length=seqlen). For a
fast tokenizer that mutates the Rust backend's truncation state in place, and
the subsequent tok.save_pretrained() persisted it, so every mixed-NVFP4 build
shipped a tokenizer.json carrying

  "truncation": {"direction": "Right", "max_length": 2048, ...}

against a source whose value is null. Every prompt was clamped at the
calibration length, permanently.

It hid because older transformers does not enforce the text-vs-ids count
check. On a newer one the seat dies at startup with a message that names
images and never mentions tokenizers:

  ValueError: Mismatch in `image` token count between text and `input_ids`.
  Got ids=[2047] and text=[16384].

The cap also silently limited image resolution well before it killed
anything -- at 2048 the largest servable image is about 1448x1448, since
(edge/patch)^2 / merge^2 image tokens have to fit under it.

Fix saves a pristine tokenizer re-read from the source rather than the
mutated calibration object, and then asserts truncation is null so the
defect fails the build instead of shipping again.

Playbook gains section 3.14 with the symptom, the cause, the audit one-liner
and a table of which builds were affected, plus a fourth mandatory post-step.
The transferable lesson is called out: this is the third case of an artifact
carrying config authored against an older transformers that a newer one
begins enforcing, so an image bump is a config-compatibility event rather
than just a version change.
2026-08-22 00:32:21 -07:00
vh 36c173c6a1 feat(mog-sec): quant + serve M.O.G.-SEC pen-test seat; PPL on gen; retire fable
Autonomous overnight run under the operator's full-autonomy grant. End state:
fleet up, gen seat untouched, a new verified pen-test seat serving where fable was.

PPL on the orcarouter gen seat (fable downed to free GPU1 for a nospec probe,
probe torn down after): mean 7.07 / median 5.76, within noise of heresy 6.910 /
5.625 and identical to our recipe's usual 7.059. The gen-seat search is settled.

M.O.G.-SEC: chose Blackfrost-Research/M.O.G.-SEC-27B-1M-CTX-BF16 (rev deede677)
over the pre-made ModelOpt NVFP4, which was disqualified on W4A4 4-bit activations
(the AEON degradation mode, catastrophic on a 1M-context model), zero MTP tensors,
and ModelOpt format. Pulled, format-screened (P(<think>) 1.11e-05, clean), quanted
in-house to mixed NVFP4+FP8 (23.4 GB, MTP + vision preserved), and served in the
retired fable slot.

  stacks/mog-sec        ana-ml2 GPU1 :8019, KV 418,218 tok / 1.60x @ 262K
  aliases               mog-sec (non-thinking), mog-sec-reasoning (thinking)
  gates                 surface 6/6, MTP 55.3%, format 0/15 leak, vision 7/3/1,
                        capability 4/4 (delivers offensive-security content)

Served at native 262K, NOT the card's 1M -- the 1M needs YaRN (absent from the
weights' config) plus the SGLang/DFlash2 path the repo ships a deployment kit for,
neither of which is our vLLM surface. A real 1M seat is a separate SGLang project.

Retired char-rp-reasoning + char-rp-fable (zero traffic, pointed at the downed
fable :8019; now 404 cleanly, not repointed -- a security model is not an RP model).
char-rp (meromero) untouched. Vision preprocessor built from the model's own
image_processor block, same trick as the MeroMero seat.

GPU0 seats (gen, meromero) were untouched and healthy throughout. The quant ran in
GPU1 free space with no production seat stopped except fable, which was replaced.
2026-08-21 02:47:18 -07:00
vh e4576f0989 test(gen-seat): PPL on orcarouter — mean 7.07 / median 5.76, within noise of heresy
Measured against a spec-decode-free probe on GPU1 (fable downed to free the VRAM,
probe torn down after). eval_quality.py aborts PPL under --speculative-config, so
a nospec probe is the only way to read it.

  orcarouter  mean 7.0655  median 5.758
  heresy      mean 6.910   median 5.625   (+2.2% mean)
  our recipe's usual mixed-quant PPL: 7.059 -- orcarouter is identical to it

So orcarouter matches heresy on fidelity and wins on every other axis: MTP
acceptance 58.4% vs 47.2%, zero think-leak, vision 7/8. The gen-seat search that
ran through Cold-Fusion, heresy, and preetpatel is settled on orcarouter.
2026-08-21 02:06:52 -07:00
vh ce09ac4fa6 test(gen-seat): add a real vision battery — orcarouter scores 7/8
surface_test.py's vision check is one image and one word. It proves the tower
loads; it does not prove the tower works. This battery uses generated images with
known ground truth so every answer is objectively gradeable.

Against orcarouter NVFP4-mixed on the `gen` alias:

  T1  OCR, 5 lines incl. one at 18px          PASS  all 5 exact
  T2  counting + attribute binding            PASS  7 circles / 3 triangles / 1 square
  T3  bar chart, 6 values + max/min           PASS  6/6 exact
  T4b occlusion, star behind rectangle        PASS
  T4c aspect ratio of a 160x140 rectangle     FAIL  called it taller than wide
  T5  two images, which has text              PASS
  T6  four images, the seat's cap             PASS  all four named
  T7  five images, one over the cap           PASS  rejected with HTTP 400

No <think> leak on any vision call.

The single miss is fine-grained relative-dimension estimation on a near-square
shape, and it reproduced across two runs (the longer T4 called the same rectangle
"equal width and height"). Counting, OCR, chart values and occlusion ordering are
all solid, so this is a precise-geometry weakness, not a broken tower. Recorded so
nobody builds a feature on this model judging relative sizes.

T7 earns its place separately: it confirms the per-prompt image cap fails loudly
with a 400 rather than silently dropping the extra image.
2026-08-21 01:52:33 -07:00
vh f85d102813 test(gen-seat): orcarouter passes every gate — in-band MTP head delivers +11 points
Gates run against the live seat while the operator tested in parallel.

  <think> leak (n=30, 4 prompt types + multi-turn)   0/30, 0 empty
  MTP acceptance                                     58.4% @ 117.11 tok/s median
  surface                                            6/6
  abliteration survival                              4/4 compliance
  deterministic quality gens                         coherent and correct
  PPL                                                still blocked

For scale on the leak gate, the abandoned h300 build scored 8/30 on this exact
instrument, and its abliteration-survival samples had 2 of 4 open with
"<think>Ok, let's figure this out:". Orcarouter has none.

The headline is MTP acceptance. 58.4% against heresy's byte-identical base head
at 47.2% is +11 points, and it sits level with our own in-band L35 at 59.1%. That
is the additive in-band-vs-graft delta the entire Cold-Fusion experiment was built
to measure and never cleanly delivered -- orcarouter handed it over for free
because the author had already done the Robinson edit on the head.

Surface 6/6 covers plain chat, vision, tool calling, the thinking split, a
36,042-token long-context retrieval, and streaming.

PPL remains blocked on a spec-decode-free probe seat: it needs ~22 GB and GPU1 has
~16 GB free. Comparison target is heresy at 6.910.
2026-08-21 01:48:09 -07:00
vh c8f128bdff feat(gen-seat): quant orcarouter — its MTP head is already Robinson-abliterated in-band
Pulled orcarouter/Qwen3.8-27B-Uncensored at rev 9878936b (55.5 GB, gated, our
token has access) and built /tank/aimodels/qwen38-27b-orcarouter-nvfp4-mixed
(23.4 GB, mixed NVFP4+FP8). Verified, not yet cut over.

The operator asked whether we could apply the Robinson path to the MTP head. We
cannot, because the author already did. compare_mtp_head.py against the verbatim
base graft: 13 of 15 tensors byte-identical, exactly 2 differ --
mtp.layers.0.self_attn.o_proj.weight and mtp.layers.0.mlp.down_proj.weight, which
are precisely the two residual writers our own abliterate.py targets
(EXPECT_MTP_WRITERS = 2).

Reverse-engineered the edit from the weights alone (mtp_delta.py, added here):

  sigma2/sigma1 = 0.0164 on BOTH tensors    rank-1, a single-direction projection
  |cos| between the two recovered dirs = 1.0000   ONE shared direction
  ||delta||/||W|| = 1.42% and 1.41%         a gentle, consistent projection
  sink energy dim 3994 = 0.0000%            sink-clean; Heretic's was 6.18%

That is the Robinson in-band MTP abliteration, already applied, with a direction
that passes our sink screen outright. Nothing to do but preserve it, and the quant
carries it byte-identically. This is the configuration the entire Cold-Fusion
experiment was designed to test and never cleanly delivered.

The new format screen paid for itself on its first real use: think_prior.py on the
bf16 BEFORE any GPU time gave P(<think>) = 1.23e-06 at rank 52, against
Cold-Fusion stock 0.1850 and h300 0.2216. Roughly 150,000x cleaner.

Two durable findings about the pipeline itself:

The quant needs ~17 GB, not a whole card. It ran entirely in GPU1's spare 16 GB
with ZERO production seats stopped -- the h300 run's "stop BOTH GPU0 seats" was
never necessary, it simply had a free card by coincidence. The first attempt OOM'd
by 2.37 GiB at layer 64 of 65 with 3.57 GiB reserved-but-unallocated, which is
fragmentation, and PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True closed it.

post_quant.py now builds a missing output index from the safetensors headers.
A sub-23 GB quant saves one bare shard with no index, and post_quant needs one;
this has broken three separate rounds and been hand-fixed every time. The header
is read by struct-unpacking the u64 length and parsing the JSON -- never
safe_open, which mmaps the whole 22 GB shard and ENOMEMs on ZFS.

Artifact verified: mixed-precision, 1968 tensors, 15 mtp, 333 visual, re:^mtp.*
present in the ignore list (llm-compressor pruned it as always), preproc restored.
Imatrix deferred per operator; the log confirms the usual uniform-MSE fallback, so
this build stays apples-to-apples with heresy's PPL 6.910.
2026-08-21 01:25:37 -07:00
vh 37e9e1ca7f revert(gen-seat): abandon Cold-Fusion, roll back to heresy — the leak is in the base
Operator directive, given before the result was in: if it's the base, abandon
h300 and the base too. The dose-response said base (18.5% of 22.2%), so it fired.

Live gen seat is /tank/aimodels/qwen38-27b-heresy-nvfp4-mixed again, restored
from .env.bak-coldfusion-L35-20260820. The h300 env is preserved at
.env.bak-h300-abandoned-20260821.

The clincher, same probe pointed at heresy:

  Cold-Fusion stock          P(<think>) 0.1850
  Cold-Fusion L35                       0.2048
  Cold-Fusion h300                      0.2216
  heresy (restored)          not in the top 20, <0.002

A >100x gap between the families, which is why no rollback inside Cold-Fusion
would have helped -- stock and L35 leak at nearly the h300 rate.

Verified after rollback: 0/30 leaks and 0 empty on the same instrument that
scored h300 at 8/30, with the EXISTING enable_thinking:false config; KV pool
403,065 tok / 1.54x, heresy's exact documented baseline; 7/7 aliases; vision
intact. No LiteLLM change was needed, so the chat_template_kwargs fix is left
unapplied -- it worked, but it was a workaround for a base we no longer serve.

Cost, stated plainly: 8/100 refusals becomes 29/100, a 3.6x regression on the
axis the whole Heretic-300 run existed to move. Accepted deliberately.

What carries forward is the methodology, none of which lived in the Cold-Fusion
weights: direction_scope=0 beating per-layer on a merged base, aggression not
being the lever, PR #317 silently dropping the MTP head on save, the MPOA and
sink-screen reasoning, the graft/KL/catatonia/export harnesses, and the finding
that a pristine MTP graft accepts as well as an in-band edit.

New acceptance gate earned here: run think_prior.py on a candidate's STOCK
weights before committing GPU time. It is a ~10s CPU measurement and it would
have disqualified Cold-Fusion before the 300-trial study ever started. Heretic's
objective has no format-compliance term at all -- the same blindness that removed
the self-harm guardrail.

Nothing deleted. Every Cold-Fusion artifact, the 300-trial Optuna journal and
catatonia-T260.json remain on disk. Abandon means stop serving, not rm.
2026-08-21 00:40:23 -07:00
vh 5ee2325820 feat(coldfusion-abliteration): dose-response settles the <think> leak — base 83%, our abliteration 17%
Answers "how likely is it that our abliteration caused this?" with a measurement
instead of a prior. P(<think>) at the first generated token, template rendered
enable_thinking=false so the prompt already carries a CLOSED think pair -- the
exact event behind the leak. Raw softmax, bf16, CPU-only, one process per model.
Deterministic: stock reproduced to 17 significant figures across two runs.

  coldfusion-bf16                  none (stock)          0.1850   rank 3
  coldfusion-abliterated-L35-bf16  Robinson L35, mild    0.2048   rank 2
  coldfusion-h300-mtp-bf16         Heretic-300, heavy    0.2216   rank 2

The stock, untouched base already puts 18.5% of first-token mass on opening a
think block the template had closed. Abliteration adds a real, monotonic,
dose-dependent +3.7 points -- a nudge on a pre-existing base, not the cause.
Cold-Fusion is a reasoning-token-compression finetune, i.e. a model trained to
think briefly, and the leak's text shape agrees: a compact correct trace with a
trained transition marker, which is trained behavior rather than damage.

This changes the options. Rolling back to L35 or stock does NOT fix the leak --
at 18.5% under temp 0.7 / top_p 0.8 they leak at nearly the h300 rate. Only
leaving the Cold-Fusion family escapes it, at the cost of the 8/100 refusal
result. The chat_template_kwargs fix is the correct lever.

Durable methodology point: a forward-KL budget cannot catch this. Heretic
minimizes forward KL(stock||abliterated), which is near-blind to the model
putting new mass on tokens stock barely used -- that is reverse KL's job, and we
measured exactly that asymmetry on L35 (reverse 1.43 vs forward 0.70). h300's KL
of 0.0136 is not evidence of innocence. For any "did the abliteration break
behavior X" question, measure P(token) directly.

Ran CPU-only deliberately: 96 EPYC cores and 265 GB of RAM make a 27B forward
pass cheap, so this cost no GPU window and no seat downtime, where the obvious
route was stopping both GPU0 seats.

Also normalizes two more abliteration output dirs from root-owned 0600 to
llmuser 0664. The unreadable-model failure surfaces as FileNotFoundError rather
than a permission error, which is worth knowing before it wastes a run.
2026-08-21 00:22:11 -07:00
vh 91f4cf22e1 fix(gen-seat): diagnose the unterminated-<think> leak — model defect, temp-triggered
Operator reported the new Heretic-300 gen seat "sends CoT but never completes
the turn" through Lobe. Diagnosed; not yet fixed (the fix changes gen's
semantics, so it is the operator's call).

The Qwen3.8 chat template appends a pre-closed <think>\n\n</think>\n\n when
enable_thinking is false. The h300 model opens a fresh <think> anyway and never
closes it. Because the prompt already closed the block, vLLM's qwen3 reasoning
parser is not in reasoning state, so the tag passes through as ordinary text --
reasoning_content empty, reasoning_tokens 0, and the whole reasoning-plus-answer
blob lands in content. Lobe then correctly treats the unterminated tag as
still-thinking and renders no answer. The client and the serving stack are both
behaving correctly; the model is not.

The trigger is TEMPERATURE, not presence_penalty (n=12 per arm):

  temp 0.7, pp 1.5  (current gen)   4/12
  temp 0.7, pp 0.0                  4/12
  temp 0.7, pp 0.5                  3/12
  temp 0,   pp 1.5                  0/12

That falsifies the standing hypothesis, recorded in the litellm config comment
and in the operator's own 2026-08-16 note, that presence_penalty 1.5 is the
first dial to move. It is not this bug's cause.

It also explains the blast radius: only the two temp-0.7 aliases leak, `gen`
and `summarizer-large`. summarizer, classifier, image-judge and qwen-image-bench
all run at temp 0 and are clean, so nevermore's summarizer path is unaffected.

Candidate fix, validated n=30 over 4 prompt types plus a 3-turn conversation:
chat_template_kwargs {enable_thinking: true, reasoning_effort: low} takes 8/30
leaks to 0/30, at ~+27% completion tokens and a ~3% empty-content residual.

The tell appears in eval_coldfusion_h300.json and in none of the aeon, heresy,
mixed or w4a16 evals, so it is new with this build -- but L35 was never evaled,
so this does not separate a Cold-Fusion base trait from a Heretic-300
abliteration artifact.

Reproducers and the full method land in bench/think-leak/. Note in particular
that the 7/7 alias smoke test run at cutover structurally could not catch this:
trivial prompts never invite reasoning, so they never sample the leaking token.
2026-08-21 00:12:30 -07:00
vh e3ce713f7f feat(gen-seat): cut over to Heretic-300 — 7/7 aliases, vision intact, MTP 59.7%
Live GEN_MODEL is now qwen38-27b-coldfusion-h300-nvfp4-mixed (ana-ml2 GPU0
:8015). Served-name left unchanged so all 7 LiteLLM aliases route without a
gateway edit.

Verification:
  KV pool            401,550 tok / 1.53x   (baseline 403k / 1.54x)
  LiteLLM aliases    7/7 green
  vision             3/3 shapes, colour+form+position correct
  MTP acceptance     59.7% median @ 118.37 tok/s
  quality gens       4/4 correct
  abliteration       4/4 compliance
  PPL                NOT measured (see below)

The roadmap predicted ~47% acceptance for a pristine MTP graft versus L35's
59.1% in-band edit. Measured 59.7% on the same harness: there is no acceptance
penalty, which removes the throughput argument for reimplementing MPOA.

A single long-prose generation read 47.5% off the same counters -- below the
8-run minimum of 49.0% -- and would have "confirmed" the prediction by
coincidence. Acceptance must be read from quickbench.py, never one sample.

PPL is blocked on VRAM, not on the model: eval_quality.py aborts with
"prompt_logprobs look uniform" under --speculative-config, and the probe-seat
workaround needs ~22 GB while both cards sit at ~96% committed.

Also normalizes the quant dir from root:0600 to llmuser:llmuser 0664 to match
every other model dir, and records that config.json sha256 is byte-identical
across the h300 and L35 quants and is therefore useless for confirming which
weights are mounted (mtime and a head-hash are the discriminating views).

Rollback is one line to .env.bak-pre-h300-20260820.
2026-08-20 23:13:15 -07:00
vh f90a5025de feat(coldfusion-abliteration): Heretic-300 — 8/100 refusals at KL 0.0136, beats the heresy bar 3.6x
Ran Heretic v1.4.0's 300-trial TPE search on Cold-Fusion. Best trial scores
8/100 refusals at KL 0.0136 against a 98/100 base, versus absolute-heresy at
29/100 and our hand-tuned Robinson L35 at 72/100 / KL 0.0116 — i.e. 64 fewer
refusals for the same damage. Hand-verified coherent: correct arithmetic with
shown working, clean code, 66-167 word prose across nine probes.

Durable findings:

- direction_scope=0 (single shared direction) is decisive on this merged base:
  n=129, best 8/100. Per-layer directions n=131 never beat 52/100 despite a
  better median. Points against the multi-direction intuition for a diffuse
  direction (our two-template |cos| is 0.62 vs Robinson's 0.99 on stock).
- Aggression is not the lever. r(KL, refusals) = -0.561 over 261 trials; the
  KL<0.02 band contains both the worst results (median 87/100) and the single
  best. A KL 0.3554 trial scored worse than one at 0.0193.
- PR #317 confirmed: Heretic silently drops the MTP head on save. Source 1199
  tensors -> export 1184, all 15 mtp.* gone, vision 333/333 intact, exit 0, no
  warning. This is also why absolute-heresy ships a byte-identical MTP head —
  a bug, not a design choice. Always diff tensor keys after a Heretic export.
- Heretic's recovered direction carries 6.18% of its energy in sink dim 3994,
  versus 0.094% for our L35 and 1.97% for the L39 we rejected as brick-inducing.
  It survives that only because of magnitude-preserving ablation
  (row_normalization=FULL); our plain projection has no such protection, so the
  sink screen correctly refused the in-band MTP graft. Same direction, different
  operation. MPOA is the prerequisite for in-band MTP on a Heretic trunk.
- Heretic's edit is recoverable from weights: delta is rank-1 (s2/s1 ~ 0.010),
  SVD gives the direction, norms give per-layer weights (1.08 -> 1.34, i.e.
  over-projection). Cross-layer |cos| agreement 0.9903 independently confirms
  the single-direction result.

New tooling in services/coldfusion-abliteration/:
  kl_divergence.py    first-token KL, class-split, zero noise floor
  catatonia_gate.py   12 probes x 220 tokens, prints every completion
  heretic_export.py   PTY driver; selects by measured value, never by menu
                      position — Heretic's resume prompt puts "delete the
                      checkpoint and all results" one arrow-key from the target
  graft_mtp.py        recovers the trunk direction by SVD; --pristine for the
                      safe path when the sink screen refuses

Also adds quant playbook 3.13: the NVFP4 recipe sets observer="imatrix_mse" but
llm-compressor has always silently fallen back to uniform MSE for want of
importance data — on this build and on the incumbent. Existing A/B comparisons
stay valid since every build shares the fallback. Parked as id 42.

Guardrail note: this build has lost the self-harm guardrail that the Robinson
L35 build retained. Restoration is the operator's own work item.
2026-08-20 22:51:56 -07:00
vh a9d73dad41 fix(coldfusion-abliteration): GPU0 seat restore order is load-bearing — correct the claim and the runbook
Restoring the two GPU0 seats with `start meromero; sleep 10; start gen` put
meromero into a 7-restart crash-loop:

  ValueError: Free memory on device cuda:0 (35.3/94.97 GiB) on startup is less
  than desired GPU memory utilization (0.52, 49.38 GiB).

The previous commit's README claimed restore order "is not actually load-bearing"
on the grounds that both seats pass --gpu-memory-utilization as a fraction of
total VRAM. That is half right and the wrong half mattered: the fraction sets the
target, but vLLM gates startup on FREE VRAM and refuses to start unless the whole
target is available. GPU0 runs at ~96.4/97.9 GB with roughly 0.4 GiB of slack, so
the seats coexist only in the order they were originally brought up, and meromero
is the one that does not fit in the remainder. The pre-existing auto-memory note
("gen takes a fraction of free VRAM at startup and will starve meromero") was
pointing at the real effect.

Also: "first" means healthy, not ten seconds earlier. A sleep 10 against a
two-to-three minute weight load is simultaneity, not ordering — gate on observed
state.

Recovery applied: stop gen, wait for meromero healthy, start gen. Verified
against the pre-window baseline rather than against "both green":

  gen KV       14.36 GiB / 403,065 tok / 1.54x  ->  14.34 GiB / 401,550 tok / 1.53x
  meromero KV  542,202 tok                      ->  542,202 tok
  RestartCount 0 on both; summarizer smoke-tested through LiteLLM

Note for the next reader: raw nvidia-smi used-MiB is the wrong check here. It
reads 89,503 now vs 96,376 before, which looks like a 6.9 GB regression and is
allocator slack — serving capacity is unchanged. The anomalous boots were the
high ones (34.95 GiB KV), where gen came up on an empty card mid-window.
2026-08-20 13:08:56 -07:00
vh 1b3fb270e7 feat(coldfusion-abliteration): first-token KL measured — 28.4x selectivity, harmless median 0.0211
Adds `kl_divergence.py`: first-token KL(stock || abliterated) over the full
248,320-token vocabulary, bf16 vs bf16, scored separately for held-out harmless
and reserved-harmful prompts.

Result (L35, 256 harmless / 104 harmful, answer mode):

  harmless  median 0.0211  mean 0.0364  top-1 agreement 89.8%
  harmful   median 0.5996  mean 0.6992  top-1 agreement 55.8%
  selectivity 28.4x (72.8x in think mode)

Self-KL noise floor is exactly 0.0, and all 720 per-prompt values are
bit-identical between a single-process and a two-process run, so the figures are
signal rather than bf16 jitter. Reverse KL on harmful/answer is 1.43 vs forward
0.70 — the mass-where-stock-had-none asymmetry expected of a refusal-direction
removal. Against the Heretic reference figures (0.1191 prior seat, 0.0759 the
live absolute-heresy seat) this is materially gentler, but those are the other
tool's optimizer output on a different base with its own harmless set and
template — order-of-magnitude, not head-to-head. KL remains a fidelity number;
the viability gate is still MTP acceptance (59.1%).

Method notes:
- Prompt classes are reported separately by design. A single averaged KL over a
  mixed corpus is close to meaningless, since the metric is meant to be large on
  harmful prompts and small on benign ones; the ratio carries the information.
- The harmless evaluation set is drawn from the alpaca pool minus calibration's
  own draw, reconstructed by replaying that draw rather than remembered, and
  asserted disjoint on text. The harmful set is the reserved test split.
- `render` is imported from abliterate.py rather than copied, so the measurement
  cannot drift from the rendering the direction was captured against.
- Batch size 1 with logits_to_keep=1: no padding semantics, ~0.6 MB of logits.

Three corrections to the runbook, each of which cost time:
- "bf16 is 50 GB, only gen must go" was 50.10 GiB mislabelled. Text-only weights
  are 51,300 MiB; freeing either GPU0 seat alone leaves ~50,933 MiB. Both must
  stop. VRAM is now sized from the safetensors headers at run time.
- A 27B model cannot be released in-process: `del` + gc + empty_cache left free
  VRAM at 45,287 MiB, and so did confining the model to an inner frame that
  exits. Only process exit returned the card (96,689 MiB). The first run
  completed only because the allocator hit OOM, collected, and retried. Each
  model now gets its own process, handing log-probs to disk between stages.
- The residency gate read hf_device_map, which transformers leaves empty when the
  model fits on one device — it reported "(unsharded)" whether or not anything
  was wrong, so it could never fail. It now reads parameter devices directly.

Model-agnostic lessons promoted to the quant playbook (new 3.12).
2026-08-20 13:00:39 -07:00
vh 725c8fdf9e feat(coldfusion-abliteration): THESIS PROVEN — in-band-abliterated MTP head accepts 59.1% (beats incumbent ~47%)
Quantized the L35 abliterated model to mixed NVFP4 and measured MTP acceptance
end to end. The experiment's whole premise: Heretic (the incumbent gen seat)
leaves the MTP head a byte-identical base graft its wrapper never loads, whereas
Robinson abliterates the MTP head in-band — the question was whether that in-band
edit survives well enough to spec-decode. It does, better than the graft:

  MTP acceptance  59.1% median (51-65%, 8 cache-busted topics)  vs incumbent ~47%
  decode          118.7 tok/s median (faster; image-confounded, read as not-worse)
  abliteration    survives quant (creative refusals drop, self-harm guardrail
                  intact, coherent)

Output at /tank/aimodels/qwen38-27b-coldfusion-L35-nvfp4-mixed (22.5 GB). Result
JSON in bench/. NOT cut over — the incumbent seat is untouched; making L35 the gen
seat is a separate decision needing the full Stage-3 gate + real multi-turn hold.

Two env foot-guns hardened along the way:
- quant_mixed_nvfp4.py now promotes text_config attention fields
  (num_attention_heads etc.) to the top-level config for the oneshot, then
  restores. transformers 5.10 / llmcompressor 0.12 (this venv moved under us
  since the Aug-15 heresy quant) no longer delegate the top-level lookup, so
  oneshot raised "Cannot determine num_attention_heads". Same "the fight is the
  environment" pattern as the abliteration capture.
- a sub-~23GB quant saves as a single model.safetensors with no index, so the
  post_quant MTP graft needed an index built first — from the safetensors header,
  not safe_open (which mmaps the whole shard and ENOMEMs on ZFS).

post_quant grafted the abliterated MTP (15 tensors, 849 MB) and re-injected
re:^mtp.* into quantization_config.ignore (llm-compressor pruned it again — the
two-rounds-lost 0%-MTP bug, fired and repaired as designed). Probe served on the
pinned nightly (#51113 qwen3_5_mtp fix) to match the live seat's vLLM.
2026-08-20 10:18:48 -07:00
vh e9dbc8660b feat(coldfusion-abliteration): abliteration LANDS at layer 35 — separation selector, shard-surgery write, three false diagnoses corrected
The abliterated model works. A/B vs stock on a matched greedy battery: explicit
sexual + graphic torture (the measured stock refusal surface) go from refused to
complied/engaged, held-out AdvBench prompts loosen, the self-harm guardrail
survives, coherence intact — the Robinson design point exactly. Output at
/tank/aimodels/qwen38-27b-coldfusion-abliterated-L35-bf16, verified bitwise:
131/131 targets changed, 333/333 vision byte-identical (delta 0.0), 735/735
others untouched.

Getting there corrected three diagnoses the prior session had backwards.

1. The layer-selection metric was wrong, and that was the whole ballgame. The
   recipe picks the abliteration layer by peak two-template |cos| agreement. On
   this heavily-merged base that metric is anti-correlated with efficacy: its
   argmax (layer 18) is the WORST-separating layer in the window (Cohen's d 5.51
   vs 9.89 at the peak), and abliterating there was a measured behavioral no-op —
   stock and "abliterated" refused all six probes identically. Cause: the two
   renderings end in different generative modes (</think> vs <think>), so |cos|
   scores answer-vs-reason mode, not refusal, and on a merge the mode term
   dominates. Replaced selection with harmful/harmless SEPARATION (Cohen's d /
   AUC of the direction's projection), gated on the sink screen since separation
   and sink-energy both climb with depth. Picks layer 35 (d 9.35, AUC 0.9997,
   sink 0.094%). Agreement is kept as a printed diagnostic.

2. The "bf16 NaNs, use fp32" rule was a misdiagnosis. The NaN was never
   precision — it was multi-GPU sharding (the residual stream zeroes two layers
   past the GPU0->GPU1 boundary; the first capture's layer 22 happened to sit in
   the healthy region, which is why it looked fine) plus
   PYTORCH_CUDA_ALLOC_CONF=expandable_segments (corrupts retained tensors; the
   corruption MOVED between bit-identical forwards, the tell that it was memory
   not math). On one GPU with a plain allocator, bf16 full-64-layer is exactly
   deterministic and coherent, at 50 GB and 4.3x the throughput of the 111 GB
   fp32 it replaced. Both defects are now hard gates (residency exit 8, allocator
   exit 9); capture pins CUDA_VISIBLE_DEVICES=0.

3. The corpus-size hypothesis was falsified. 52x more calibration data (8->416,
   mlabonne/harmful_behaviors = the recipe's actual AdvBench split, already on the
   box) moved agreement 0.594->0.624 — nothing. Kept the 416/416 corpus anyway
   (calibration.py); it gives the clean separation signal. The held-out 104-prompt
   test split is reserved and asserted disjoint.

Also: the --out write is now shard-level surgery (reads/writes the 18 safetensors
directly, no model object, no GPU). This is correctness, not thrift —
AutoModelForCausalLM resolves to the TEXT model, so save_pretrained would drop all
333 vision tensors AND skip the MTP head (the in-band MTP edit is the entire point
of the Robinson formula). Neither failure raises. Shard surgery makes vision and
the other 1068 tensors byte-identical by construction.

Batched capture with a dtype-aware equivalence gate; hidden states captured via
forward pre-hook (reading output_hidden_states off the returned object is unsafe
here — buffers get recycled). Sharding/allocator lessons promoted to the
quantization playbook (model-agnostic, sections 3.9-3.11 + superseded table); the
selection-metric lesson added to the recipe doc.

The dead layer-18 no-op checkpoint was removed (52 GB, confirmed identical to
stock). Incumbent gen seat untouched. Full canonical refusal-probe re-profile and
MTP-acceptance-on-quant still owed before this becomes a gen-seat candidate.
2026-08-20 08:46:21 -07:00
vh f714f28195 feat(coldfusion-abliteration): Robinson's real 416-prompt corpus, batched capture, two new gates
The 8/8 calibration set gave |cos| agreement 0.594 against the recipe's 0.9925.
This wires in the corpus the recipe actually used and makes a capture at that
scale affordable.

Corpus (calibration.py, new). The recipe's "held-out train/test split of 416/104
with overlap 0" names mlabonne/harmful_behaviors exactly — 416 train / 104 test,
AdvBench-derived — and it plus harmless_alpaca were already staged in ana-ml2's
HF dataset cache. Read via pyarrow, no datasets dependency, no hub access.
Harmful is order-deterministic (no seed), so a re-capture is reproducible from
the flags alone. The 104-prompt test split is reserved as the held-out
generalization probe and asserted disjoint, so the post-write re-profile cannot
silently become in-distribution. --calib builtin reproduces the legacy run.

Batched capture. 832 prompts x 2 templates = 1664 forwards. Padding is on the
RIGHT: in a causal stack nothing after position t reaches position t, so
trailing pads cannot touch the token read, whereas left padding feeds pads into
the DeltaNet recurrence ahead of the prompt — the path whose torch fallback
already NaN'd once here. Means accumulate in float64; the direction is a
difference of means, which is where cancellation lives on this model.

Gates added, both protecting numbers rather than tensors:
- batch-equivalence: proves padded-batch == single-prompt (rel 1e-3) before
  spending the capture window.
- surgery pre-check: aborts if any of the 131 targets is absent or on the meta
  device. orthogonalize_ edits in place, and an in-place write to an
  accelerate-offloaded tensor is a silent no-op — that ships a half-abliterated
  model past a smoke test.

Fixed a reporting bug: the agreement line printed the global agree.max() beside
the window's argmax layer, so the first capture read as 0.8538 when the real
in-window number was 0.5944. The global peak sits in the early layers where the
dim-3994 massive activation inflates agreement for reasons unrelated to refusal.
Now prints window max, a top-5, and labels the global figure informational.

--max-layer truncates the decoder for capture. Exact, not approximate: a causal
stack's layer-N state cannot depend on layers above N, so any value above the
window top leaves the direction bit-identical while cutting fp32 residency and
forward cost. 46 drops 18 of 64 layers and is what keeps fp32 off CPU offload.
Refused on the write path, where it would emit a truncated checkpoint.

Verified on ana-ml2 without the GPU: dry-run still 1:1 (131 tensors, all
coverage gates), calibration loads 416/416 deterministically with its guards
firing, both --max-layer guards exit as designed. Also confirmed against
chat_template.jinja that enable_thinking=True does resolve reasoning_effort to
xhigh, so the two renderings are the recipe's — template selection was not the
cause of the low agreement.

The re-capture itself is unrun: it needs the fp32 VRAM window and therefore
production seat downtime.
2026-08-20 07:52:37 -07:00
vh 7abd3011f7 fix(coldfusion-abliteration): capture works — fp32 forward + finite-gate
The --capture forward NaN'd repeatedly. Root cause: transformers' Qwen3.5
DeltaNet linear-attention needs the causal-conv1d fast-path kernel, which
can't be built here (no nvcc, no prebuilt wheel). Its torch fallback produces
nondeterministic all-NaN hidden states in bf16 -- same 11-token input finite
on one forward, NaN at layer 4 on the next. bf16 and fp32 share exponent
range, so it's precision-driven catastrophic cancellation, not overflow, and
fp32 resolves it.

Fixes:
- --capture now loads fp32 (the write/surgery path stays bf16 -- no forward,
  no NaN). attn_implementation=sdpa pinned.
- A finite-gate aborts on a non-finite direction. The sink screen alone can't
  catch this: nan > threshold is False, so a NaN direction "passed" it and got
  saved silently on the first run.

Capture result (fp32, full GPU): refusal direction finite, unit-normed, layer
22, sink energy 0.0008% in dim 3994 -- clean, not sink-dominated. Saved.

Caveat recorded: two-template |cos| agreement is 0.59 at layer 22 vs Robinson's
0.99, almost certainly the small 8/8 calibration set vs their 416/104. Valid
but noisier than ideal; the README flags expanding the sets before the write.

README documents the three environment gotchas (fp32-for-capture, the seats
that must be stopped for the 110GB fp32 VRAM and how to restore them, and the
fla side-dir PYTHONPATH) so the next run doesn't rediscover them.
2026-08-20 07:20:38 -07:00
vh b56cb0db13 docs(coldfusion-abliteration): dry-run passed — recipe maps 1:1 (131 tensors)
Dry-run against the fully-staged bf16 confirms the Robinson recipe transfers
onto the DavidAU Cold-Fusion checkpoint with no name drift: 1199 tensors, 333
vision preserved, down_proj=64/o_proj=16/linear_out=48/mtp=2/embed=1, coverage
gate 6/6, exactly 131 tensors to orthogonalize. Harness verified-ready; the
destructive write still gates on operator go.
2026-08-20 06:53:59 -07:00
vh 1857a8eb81 feat(coldfusion-abliteration): Robinson-formula harness, gated, staged
Harness to abliterate DavidAU/Qwen3.8-27B-Cold-Fusion-GAIN-V1.1 using the
MTP-aware, vision-preserving recipe in docs/pfi/abliteration-recipe-qwen38.md.

Motivation is measured, not assumed: the stock model's refusal profile
(probed 2026-08-19, hand-verified) is ~33% on creative content, concentrated
on explicit-sexual and graphic-torture, with 4/5 hard-harm refused, self-harm
guardrails intact, and zero benign over-refusal. So there is a real
creative-content refusal surface. The Robinson formula is chosen specifically
because it abliterates the MTP head IN-BAND -- which the current gen seat's
Heretic pass does not (its MTP head is a byte-identical base graft the
Qwen3_5 wrapper never loads). That in-band MTP edit is the additive delta.

The script refuses to brick the model. Two hard gates from the recipe halt
before any write: the coverage identity o_proj(16)+linear_out(48)==64 (catches
a tensor-name mismatch that would ship a half-abliterated model), and the
attention-sink screen on dim 3994 (orthogonalizing a direction living there
produces a model that loads, runs, and emits garbage). The direction is
captured from two chat templates and the layer auto-picked by peak |cos|
agreement in [18,45]. Classification is suffix-based and name-agnostic so it
survives minor drift; the coverage gate is the backstop.

Modes: --dry-run (enumerate + gate, no forward, no write), --capture
(direction + sink screen, no write), default (write to --out). The README
sequences dry-run -> capture -> write -> verify, and names the post-checks
(vision byte-identical, refusal re-profile via services/refusal-probe/, MTP
acceptance on the quant, PPL/coherence).

bf16 staged to ana-ml2:/tank/aimodels/qwen38-27b-coldfusion-bf16 (pinned
9c44193, provenance recorded). The destructive run is NOT executed here --
dry-run verification and operator go gate it.
2026-08-20 06:50:48 -07:00
vh 7010f9a1da feat(booth): render .md/.txt/.log inline in the gallery, collapsible + closable
Docs used to render as a clumsy link that navigated to a separate page. They
now render in place: build_gallery pre-renders each doc (markdown -> HTML,
plain text raw) and the gallery shows it inside a native <details open>
disclosure that spans the full grid width so prose has a readable measure.

The doc bar carries: a collapse chevron (the whole <details> summary toggles,
works with JS off), a full-page link (still reaches the standalone viewer), a
download link, and a session-close ✕. The ✕ needed stopPropagation +
preventDefault because it lives inside <summary> — otherwise its click would
toggle the disclosure instead of hiding the item. Close is JS (progressive
enhancement); collapse is native.

Two design points:
- Plain text is returned RAW from build_gallery and escaped by the template
  inside <pre>. Pre-escaping in Python plus Jinja autoescape would
  double-encode angle brackets; a test pins the single-escape.
- Inlining is bounded by DOC_MAX_BYTES. A doc over the limit keeps the old
  link-out behaviour rather than being rendered into every index load; a test
  covers the fallback.

The shared .markdown-body / .textview typography moved from doc.html's scoped
<style> into base.html so the inline body and the full-page view render
identically; doc.html keeps only its page-layout wrapper.

Updated the pre-existing test_gallery_links_docs_to_view: it asserted the old
link-out behaviour the operator asked to change, so it now asserts the inline
render plus the surviving full-page and download affordances. 61 pass.
Verified live: markdown renders with headings/table/blockquote/code, txt
preserves whitespace and single-escapes, collapse and ✕-close both work.
2026-08-19 11:06:36 -07:00
vh 6770ba26d6 feat(booth): kept boards — a .forever sentinel and a standing link board
Agent sessions hand the operator URLs and they drown in terminal
scrollback. The Booth is the right home for them — it already has the one
property that decides adoption, which is that a session can publish with
mkdir and cp, no API key, no schema, no deploy — but everything in it dies
in 24h.

So: a booth containing `.forever` is never swept, and renders in its own
Kept lane at the top of the index. Opt-in per booth, so the ephemeral
default is untouched and nobody inherits a cleanup chore. `rm` the
sentinel and the board rejoins the sweep; the CLI verbs are sugar over
exactly that, which keeps the filesystem-is-the-state model honest.

The pin is deliberately NOT wired into is_expired(). That stays a pure age
question feeding the `expires_in` countdown; only sweep_once() honours the
sentinel. Keeping expiry arithmetic and reaper policy apart means they
cannot drift into each other.

Kept cards are visually separated per Australis: a 2px top edge in aurora
blue, the one accent border the system sanctions. They show "kept" instead
of a countdown, and they deliberately lose the one-click wipe button — a ×
next to the durable stuff is a footgun, so removing a kept board is a
two-step act.

`booth link <url> [description]` appends to the standing `links` board,
creating and keeping it on first use. Entries carry provenance (handle or
hostname, plus a timestamp) because a bare URL is unreadable three days
later. The append is one printf of one line to an O_APPEND fd — atomic
under PIPE_BUF on POSIX — which matters because many agents post to one
board and interleaved half-lines would be the obvious failure mode.

Seven tests cover the sentinel: detection, survival of a sweep that wipes
its neighbour, the deliberate is_expired/sweep_once split, the listing
flag, the sentinel not inflating item counts, and both lane-rendering
directions. Two of them originally asserted on the bare strings "Kept" and
"kept-grid", which passed for the wrong reason — those also appear in the
inlined stylesheet served on every page — so they now assert the full
class attribute. 55 pass.

Also corrects the Homepage card's description, which advertised a flat 24h
TTL that is no longer the whole story.
2026-08-19 09:34:53 -07:00
vh 11b688ff68 feat(gen-seat): promote absolute-heresy to the live gen seat
MuXodious/Qwen3.8-27B-absolute-heresy (Heretic v1.4.0 + SOMPOA, trial T377,
pin c2374593) quantized through our mixed NVFP4+FP8 recipe and promoted after
passing the full gate on the probe port.

Gate vs incumbent -- MTP acceptance 47.2% (48.2%), decode 103.5 tok/s (96.4),
prefill 6618/5403 at 6.7k/27k (6334/5085), TTFT 27k 5.00s (5.31s), perplexity
6.910 (7.059, 2.1% better), surface 6/6, abliteration compliance 4/4. On our
battery-instruct arm -- the framing that actually elicits refusals -- 0/55 with
zero EMPTY, so no catatonia at the hard edge.

Speed deltas are image-confounded: the probe ran the seat's pinned nightly
while the incumbent's stored numbers came from an earlier image. Read as not
worse. Acceptance, perplexity, surface and refusal are apples-to-apples.

All 7 LiteLLM aliases verified end-to-end. GPU0 at 91.3/97.9 GB with meromero
healthy -- more headroom than the previous build. Incumbent weights untouched
and .env.bak-heresy-20260817 in place for rollback.

Candidate is a 2-day-old RC1 with ~348 downloads; watch real multi-turn use.
2026-08-17 17:23:35 -07:00
vh 993421bf59 fix(post-quant): handle sources that keep mtp.* inside a numbered shard
post_quant assumed the source ships a standalone model-mtp.safetensors, which
is how JonathanColetti's grafted head is packaged. MuXodious/absolute-heresy is
an unmodified full checkpoint, so its mtp.* lives in model-00012-of-00012 --
the copy silently did nothing while the index was still rewritten to point at
model-mtp.safetensors, leaving 15 unresolvable tensors. Tensor counts looked
correct; the checkpoint would have failed at load.

The existing FAILED-CHECKS assertion caught it, which is the design working.
Now extracts from the numbered shard when the standalone file is absent.

Verified on the heresy build: 1968 tensors, all resolvable, 15 mtp, 333 visual,
no missing shards, no orphans.
2026-08-17 17:18:01 -07:00
vh b0c2d3d1c4 fix(bench): serve_probe must mirror the live seat -- image, parsers, context
Three defects, each of which produced a false read on the candidate:

1. Hardcoded vllm/vllm-openai:latest. The Qwen3.8-27B gen seat is pinned to a
   nightly carrying the #51113 qwen3_5_mtp x GDN fix; probing on :latest
   reproduces the multi-turn corruption we already diagnosed and reads as a
   candidate failure. Now PROBE_IMAGE, defaulting to :latest for older seats.

2. --speculative-config JSON died twice on quoting. The inner double quotes are
   stripped by the outer double-quoted ssh string, and then bash BRACE EXPANSION
   splits {"a":1,"b":2} on the comma. Needs escaped quotes AND remote-side
   single quotes; both traps documented inline.

3. No --tool-call-parser/--enable-auto-tool-choice/--reasoning-parser. Without
   them surface_test reported tool calling as a 400 and measured a thinking split
   of reasoning=0ch -- both probe-config artifacts, not model defects. Re-running
   with the seat's flags took the candidate from 5/6 to 6/6.

Also adds PROBE_MAXLEN; the hardcoded 32768 rejected prefill_bench's ~27k prompt.
2026-08-17 17:17:30 -07:00
vh 2c3602869f fix(gen-seat): hash bf16 tensors via uint8 reinterpret, not numpy
numpy has no bfloat16, so .numpy().tobytes() raised
'TypeError: Got unsupported ScalarType BFloat16' on real checkpoints.
Flatten then view(torch.uint8) before hashing.

Result on the heresy candidate: VERDICT IDENTICAL -- all 15 mtp.* tensors
byte-identical to the incumbent's verbatim base graft, the head already
measured at 47.7% acceptance in production. The ~56 GB bf16 acceptance gate
is redundant, so no second seat comes down.
2026-08-17 16:29:45 -07:00
vh 254c588921 feat(gen-seat): CPU-only MTP head check so the gate costs no second seat
Operator ruled the probe port for validation; runbook updated to match.

The bf16 MTP acceptance gate is ~56 GB resident, which on a full 97.9 GB card
means downing meromero-charrp as well as gen -- freeing gen's 0.43 (~42 GB)
alone is not enough. Two seats down to answer one question.

compare_mtp_head.py answers the common case for free. The
Qwen3_5ForConditionalGeneration wrapper never loads the MTP head, so PEFT
merges, Heretic runs, and llm-compressor passes all leave mtp.* as it came
from the base. It hashes a candidate's 15 mtp.* tensors against the
incumbent's grafted-verbatim head -- the one measured at 47.7% acceptance in
production through this exact pipeline. Identical means the acceptance
question is already answered; different means the head was edited and the
real gate is warranted; missing means it was dropped.

CPU only, reads just the shard holding mtp.*. The runbook states the residual
risk plainly: an identical head proves the head is intact, not that the
abliterated body still drafts well with it -- which the Stage-3 acceptance
measurement on the 22 GB quantized build catches anyway.
2026-08-17 16:25:28 -07:00
vh 7997f111b0 docs(gen-seat): runbook for the absolute-heresy swap
Candidate MuXodious/Qwen3.8-27B-absolute-heresy (Heretic v1.4.0 + SOMPOA,
trial T377), pinned c2374593. Beats the incumbent on both axes: refusals
2/101 vs 12/100, first-token KL 0.0759 vs 0.1191. Structurally a clean full
checkpoint (1199 tensors, 15 mtp.*, 333 visual.*, lm_head), so the existing
mixed NVFP4+FP8 recipe applies with no graft-and-reconstruct.

Runbook carries the bf16 MTP-acceptance gate before any quant spend, the
llm-compressor ignore-pruning foot-gun, the three measurement traps
(cache-busting, unseeded prefill nonce, PPL with spec off), the .env 0600
sudo trap, the GPU0 co-tenant starvation risk, and rollback.

Flags that the candidate is a 2-day-old RC1 whose own card carries a broken
GGUF benchmark block (RC1 and RC2 report identical at-chance scores across
three benchmarks), so its numbers are claims rather than measurements.
2026-08-17 16:22:00 -07:00
vh 9c1405b1f9 docs(refusal-probe): retire the DS v1 regeneration — unqueued, do not run
brokkr-smithy-dev withdrew the request on the operator's call
(msg 01M088G7NQ5G42ES30YPJV4Y3V). Two reasons: the ictrl-pair-unwrapped /
ictrl-pair-wrapped control isolates the classifier over-fire cleanly,
where DS's cross-class delta only bounded it; and DS v2 is releasing
soon, so a k=5 v1 baseline baselines a superseded version.

The spec stays banked as the record of the run that will not happen --
axes, per-class grading asymmetry, and run parameters remain correct.
Checklist struck through. The staged probe.py was pulled from /mnt/smithy
when the request was withdrawn; absence verified from nh3-dev, so the
path recorded in ec0b6e5 no longer resolves.
2026-08-17 09:23:24 -07:00
vh ec0b6e5e71 docs(refusal-probe): record brokkr's staged probe path + sha, and the all-16-axes caveat
brokkr-smithy-dev staged refusal-map-probe.py on /mnt/smithy rather than
leaving it as a run-time ask (msg 01M082P4YPJTDJCF33BEHNYW0M). Path and
full sha256 recorded and verified present from nh3-dev.

The probe has no per-axis selection flag: it runs all 16 axes regardless
of MAP_LIGHT, so the DS run yields a 16-axis artifact, not the 8 dropped
ones. Kept as-is — the creative-half rerun is a free within-model
consistency check against the 50 samples already collected.
2026-08-17 07:41:54 -07:00
vh 25fa18efb8 docs: correct stale TTS voice warning (tts-dev); record DS regeneration spec (brokkr)
tts-dev answered the Lobe onboarding, live-verified. Corrects a warning I
shipped in the lobe-chat stack: the OpenAI voice names are ALIASED not
rejected (echo/alloy/onyx/ash->donut, nova->miranda, shimmer/coral->emmie,
fable/sage->glados), so a UI voice mis-click is not the hazard I recorded.
Only ballad and verse 404. The old 71.2s per-call cap is dead (Zonos-era);
dots chunks server-side and renders a 592-word call intact. Real constraint
is size (245s WAV = 23.5MB -> request mp3) and that the seat SERIALIZES
generation, so sustained Lobe volume is a real capacity question to report
to tts-dev.

Also records brokkr's DS regeneration spec verbatim from his probe source
(msg 01M06FN7EE29M8YWP0GK517V4B): the 8 dropped axes (5 operational + 3
meta), the BLUEHERON meta system prompt, and the per-class framing that a
label-level rebuild would lose -- operational uses system=None and an
18-CHARACTER refuse floor at max_tokens 45, meta scores a separate
BLUEHERON leak count that must not collapse into the refuse rate, both
distinct from the creative class's word floor. Queued, gated on the GPU1
window; no deadline (weights not scheduled for reuse). Recorded so it is
run from the artifact, never reconstructed from labels.
2026-08-16 16:51:40 -07:00
vh aba7cda33e fix(refusal-probe): retract an invalid cross-battery comparison; adopt the two-control pair
RETRACTION. I claimed my arm-3 92.5% 'exceeded' brokkr-smithy-dev's 62.5%.
That comparison was invalid. His diff against his own artifact shows my
battery-instruct.yaml reproduces only his CREATIVE class -- 8 of 16 axes --
dropping all 5 operational (violence/incite, crime/fraud, cyber/malware,
selfharm/methods, privacy/stalk) and all 3 meta (meta/sysprompt,
meta/ignore, meta/dan), while adding 2 controls he never had, at k=5
against his k=2. His figure pools 16 axes; mine is creative-only. Different
denominators, so there was never a delta to report.

Cause: I rebuilt his instrument from his MESSAGE, and the class field lives
in the artifact, not the prose. Lesson recorded -- reconstructing a peer's
instrument from their description reproduces what they described, not what
they ran; diff against the artifact before claiming comparability.

Adopts his amendment to my control fix, which is better than my proposal. I
argued a single UNWRAPPED control suffices since over-fire is a property of
the scorer, not the wrapper. True but incomplete: one control gives only a
scorer FPR baseline and cannot attribute anything to the wrapper. Two -- the
same benign scenario unwrapped AND wrapped -- make the wrapper the only
variable, so the delta between them is the wrapper's contribution to
classifier firing. Costs one prompt.

ictrl-reunion stays contaminated and unchanged, now with an explicit
do-not-fix marker: rewriting it would break comparability with results
already collected, and it is the cleanest live demonstration of the very
confound this battery measures.
2026-08-16 16:45:17 -07:00
vh d47dd10795 feat(gen-seat): swap to AEON-ULTIMATE NVFP4 W4A4; default reasoning_effort=medium
Operator call: the incumbent abliterated model was the first one we could
find, not an optimised pick. sakamakismile/Qwen3.8-27B-AEON-ULTIMATE-
UNCENSORED-NVFP4 (base AEON-7 BF16, abliterix-abliterated, Apache-2.0),
byte-verified at /tank/aimodels/qwen38-27b-aeon-ultimate-nvfp4.

Measured on the same harness, same GPU, cache-busted per playbook 5.
Baseline was RE-measured live before the swap rather than trusted:

                        incumbent (W4A4+FP8 mixed)   AEON (W4A4)
  decode bs=1           94.09 tok/s                 104.22   +10.8%
  MTP acceptance        47.7%                        52.3%    +4.6pp
  abliteration          4/4                          4/4
  surface               6/6                          6/6
  weights               22.5 GB                      20.6 GB  -8.4%

AEON concurrency: conc=1 98.48 tok/s aggregate; conc=6 381.29 aggregate /
63.55 per-stream, MTP holding 50.6% under load.

Surface 6/6 includes vision (image-judge rides this seat) and a 36k-token
needle retrieval, which was the specific risk in going full-W4A4 -- the
packager only validated 32k, and W4A4 long-context collapse is in our own
notes from the Granite work. It held.

reasoning_effort: the AEON template defaults to xhigh (template line 47),
and at xhigh this model can spend its entire budget inside <think> and
emit no answer -- a silent-empty-response hazard for the automated
summarizer/classifier consumers. Seat now pins the default to medium via
--default-chat-template-kwargs, per-request overridable. Override PROVEN
live: chat_template_kwargs.reasoning_effort=bogus returns HTTP 400
carrying the template's own exception text, so caller values genuinely
reach the template and invalid ones fail loudly rather than silently
falling back. Empty GEN_REASONING_EFFORT omits the flag for models that do
not read the kwarg -- the Qwen3.6 line ignores it entirely, where setting
it would be a false lever.

All 7 aliases verified routing. Rollback is one .env line; the previous
build is untouched at /tank/aimodels/qwen38-27b-uncensored-nvfp4-mixed.

TWO GAPS, declared:
  - Incumbent concurrency was never captured before the swap (I baselined
    bs=1 only), so the conc=1/6 figures have no same-hardware comparator.
  - Perplexity NOT measured. eval_quality correctly refused it: under
    --speculative-config prompt_logprobs come back ~uniform (median rank
    ~130k), playbook trap 2. A real PPL number needs both seats served
    without spec-decode.

Adds concbench.py (concurrent throughput; wall-clock aggregate, not
sum-of-rates, and delta-based MTP accounting).
2026-08-16 14:56:37 -07:00