Skip to content

Desktop/CLI: default to 6875 (MTPL) and keep 8000 as fallback — avoid 8000 collisions - #419

Draft
Fe2-O3 wants to merge 712 commits into
youssofal:mainfrom
Fe2-O3:mtplx-default-6875
Draft

Desktop/CLI: default to 6875 (MTPL) and keep 8000 as fallback — avoid 8000 collisions#419
Fe2-O3 wants to merge 712 commits into
youssofal:mainfrom
Fe2-O3:mtplx-default-6875

Conversation

@Fe2-O3

@Fe2-O3 Fe2-O3 commented Sep 1, 2026

Copy link
Copy Markdown

Thank you for MTPLX — the app is fantastic and MTP is real speed. Grateful for Dashboard and Forge polish.

Fixes #418

What this does

Defaults Desktop and mtplx serve/start/quickstart/connect to 6875 — M T P L on a phone keypad — and keeps 8000 usable via --port 8000 or existing configs. No breaking change; new installs just start on an uncontested port.

Why

Every local runner defaulting to 8000 forces silent fallback or harness resets. LM Studio uses 1234, Ollama 11434, Rapid-MLX is moving to 7659 (RMLX) with an 8000 fallback (raullenchai/Rapid-MLX#2879 and raullenchai/Rapid-MLX#2878). I run another gateway on 8000; with both on 8000, run order matters and every harness has to be re-pointed. Run order shouldn't matter.

MTPLX already has --port and path controls (thank you for that) — this is only the out-of-box default so two rigs can coexist.

Changes

  • mtplx/cli.py: --port default 8000 -> 6875 (serve, start, quickstart, connect, settings)
  • mtplx/server/openai.py: --port default 8000 -> 6875
  • mtplx/ui/onboarding.py: port: int = 8000 -> 6875
  • mtplx/diagnostics.py: server_port: int = 8000 -> 6875
  • 8000 remains valid via explicit --port 8000; existing installs keep working.

Happy to change the number to whatever you prefer — any uncontested pick fixes the collision. Will update tests if you want this direction.

youssofal and others added 30 commits August 22, 2026 10:08
…WIDTH_THRESHOLD (leg 2b)

Leg 2a's live verdict: accepted drafts carry 0.84-0.87 head confidence,
rejected 0.49-0.56 — the head already knows which drafts will die, and the
decode loop ignores the signal. This knob (default OFF) uses it: when
p(drafted) falls below the threshold, keep that draft and skip the deeper
ones — riding the native gated-stop break, so semantics match the existing
adaptive-width contract exactly.

- Greedy stock loop only (the youssofal#313 chain batches its confidence scalars
  into one deferred eval by design — a per-depth early stop there would
  reintroduce per-depth syncs; chain stays trace-only).
- Committed output tokens are invariant by verify semantics (fewer drafts
  never changes the target-argmax token stream, only speed) — every test
  asserts exact token equality against a knob-off baseline.
- Confidence computation is shared with the 2a trace flag (one prebound
  _draft_conf_needed); the knob works with the trace off. Invalid or
  out-of-range values (empty/0/1/negative/non-float) resolve to OFF.
- draft_confidence_width_stops counts fires through decode-trace
  (totals -> snapshot-diff -> width_stops_delta row key).

Tests (TinyMTP, analytic e/(3+e) pin): threshold above the pin gates every
cycle at depth 1 (drafted_by_depth [N,0,0], stops == N, tokens identical);
threshold below is fully inert (fingerprint identity, zero stops); six
invalid-value forms stay off. No default behavior change anywhere.

Live A/B on the real pack is the founder-gated next step; no speed claim
is made here.
…istill call

Leg 2b proved means mislead: at threshold 0.6 the gate cut drafts that
would have been accepted (accepted/rejected confidence distributions
overlap in the tails), and no threshold decision or distill target can be
justified from means alone. This extends the 2a trace with per-depth,
per-outcome 10-bucket histograms of p(drafted).

Flat depth-major int lists (index = depth*10 + bucket, length
speculative_depth*10) so the trace's existing list-aware snapshot-diff
carries them unchanged; emitted as draft_confidence_{accepted,rejected}_
hist_flat_delta. Attribution rides the existing 2a site (same outcome
attribution, same own-denominator contract); zero new syncs, zero cost
when the trace flag is off. AR-lane absence degrades to [] (same guard as
the mean fields).

Test: tiny-lane confidence is analytically e/(3+e), so every attributed
draft must land in bucket 4 — the accepted histogram carries exactly the
accepted counts there (all other buckets zero) on the all-accept lane, and
the rejected histogram mirrors it on the all-reject lane.
…sion (grzracz), with scar fixes

Port of PR youssofal#316 (grzracz, pr/04-gdn-proj-fusion) onto night-20260822, opt-in
via MTPLX_FUSE_PROJ (default off). Load-time row-concat of GDN in_proj
qkv|z|b|a (N=16480 across the 48 GDN layers), attention q|k|v and MLP
gate|up into single QuantizedLinear matmuls; members become zero-copy row
views served by an identity-keyed hub (one fused dispatch per distinct
input), so net weight memory is flat and the stock forward stays untouched.
Fused lane fires only at rows<=4 (the measured N-invariant qmv window that
 youssofal#320 independently established) — prefill and batch stay unfused.

Review fixes on top of the PR:
- mx.contiguous on the hub's split outputs: the packed_concats seed-825 scar
  (strided split views re-routed downstream kernels; 1-ulp flips broke
  sampled-trajectory identity on 2026-08-19). The PR was missing it.
- Mutual refusal with MTPLX_PACKED_PROJ_CONCATS in both directions: the two
  lanes fuse the same groups; stacking would duplicate payloads and confound
  A/Bs. packed_concats mirrors its existing NAX-refusal pattern.
- Docstring: why this lane does NOT refuse NAX (both fused and fallback
  lanes stay on the nn.QuantizedLinear.__call__ patch surface, unlike
  packed_concats' direct mx.quantized_matmul calls) — so the refusal is not
  cargo-culted over later.

Verified against the shipping OS pack layout (4-bit g32 affine, the four-way
GDN in-proj split serialized in the pack). tests/test_proj_fusion.py: env
parsing, all-group fusion, exact member parity at rows 1/4, one-dispatch hub
memo, above-window fallback, both refusal directions — 7 new tests, plus the
 youssofal#320/youssofal#319 exactness file, 12/12 green. Live A/B on the ladder is the next
gate before any default consideration; arena receipts (08-19 crown overlay,
row 27) say the win is real on this hardware class.
…tal, default off

New mtplx/verify_crossrow.py: cross-row affine-4 QMV for verify widths
M=2..5, ported from the mlxfast arena E120 family (Layr-Labs
qwen-3.8-mtp-challenge, MIT; chunk-sum lineage morganmcg1 6f1cd66 +13.24%
ranked, tight-launch jungjipdo 8849fad +15.49%, M=2 scarletbright e8f14c4)
onto mx.fast.metal_kernel, parameterized for our g32 trunk (their g64
hardcode) with the g64 variant kept. One simdgroup owns 4 output rows x M
input rows; integer nibble dot + per-group affine correction; optional
per-lane activation chunk-sum table with a fill kernel that replicates the
inline accumulation order exactly — table-on and table-off are bitwise
equal by construction. Launch is tight by construction (ceil(N/8) output
TGs, one input group at M<=5).

Gates (crossrow_check.out, fan-gated): ALL PASS — bitwise table twin,
perturbed-table positive control fires per cell, parity vs stock in the
tail-ULP class (2e-4..3e-3), and the three-way timing at real pack shapes:
gdn.fused M=4 253.7us vs stock 284.8 (+10.9%, ties vk_k 254.0); M=5 281.5
vs stock 322.0 (+12.6% — vk_k has no M=5 lane at all); g64 M=5 +7.4%;
attn/mlp wash; mlp.down stays vk_k's. MEASUREMENTS 19:13 has the table.

Not routed anywhere yet: MTPLX_VK_CROSSROW gate exists but no call site
consults it — integration into the turbo verify patch (GDN family at
M=4/5, in-graph fill) is the next step and carries its own identity/R1b
gates. Unit tests: env gate, eligibility geometry, generated-source
divisors (g32 lid/2, g64 lid/4), kernel cache keys — 4/4.
…/#315c1/youssofal#318 merge ruling)

The founder-ordered merge call, decided by measurement. Evidence:
- n=4 counterbalanced ABBA (2 stack-first + 2 base-first rounds, Ivan
  ladder, fan/die gated): blended mean +2.7%, median rung +2.5%; 0.5k-8k
  rungs +2.3..+9.8, only 16k (−1.8) and 32k (−3.1) negative.
- Dedicated bankless 16k/32k quad (base-stack-stack-base, one session):
  16k −2.9%, 32k −2.7% — third consecutive long-context negative read, so
  blanket ON would ship a pillar regression (flat-or-better law).
- Byte-identity on the final tree: PASS 6/6 (3 prompts x greedy +
  sampled-seed7, identical text and counters) — the greedy-only-refusal
  law's sampled-lane proof.

Ruling implemented: the three knobs resolve default-ON via
_env_enabled_default_on (opt-out, falsy set symmetric with graphbank) and
are fenced per request by MTPLX_GREEDY_TRIO_MAX_CONTEXT (default 12288
prompt tokens; 0/off = unlimited) — the same context-routing pattern as
MTPLX_COMPILED_VERIFY_MAX_CONTEXT. The fence is decided once per request
at the chain prebind and stamped through a graphbank ContextVar for the
paged-offsets read (batch lane keeps last-set/default = at most the
pre-youssofal#318 serial-sync behavior). Decode-trace receipts carry both the env
resolution and the per-request stamp (greedy_trio_max_context,
trio_context_ok) — the youssofal#314 dead-switch antidote.

Also fixed in the same surface: leg-2b's confidence-width threshold was
silently inert under the (now-default) chain lane — an explicitly set
experimental knob must win over the default path, so the chain eligibility
block now excludes it (dead-switch scar class).

Tests: test_greedy_trio_ports re-pinned (explicit-off baseline arm),
default-resolves-ON pin, fence resolver matrix, fence-disarms-chain live
gate with graphbank stamp assertions — 12/12; fence-adjacent files 84/84.
Full suite split on this tree: cold-tier solo rc=0; main run had ONE
fail, test_bank_shrink_to_bytes_evicts_lru_first — solo and file-solo
green 12/12, no mechanism from this diff (SessionBank untouched), same
suite green under identical serve load earlier today: interference-class
flake, same protocol as the 08-22 cold-tier precedent.
Cause of the long-context cost is UNKNOWN (stack-level measurement only);
knob isolation + 32k flight-recorder diff are the follow-up. Long-context
users lose nothing; short/mid-context users gain the blend.
…red slower)

Three same-process micro sessions (MEASUREMENTS 22:0x-22:1x) at the
production 4-bit g32 shapes put the padded-m6 ksplit lane +3..13% over
stock and the NAX m16 tile worst at every M=5 cell; stock never loses.
The m<=6 branch previously padded M=5 into the m6 template and the m16
tile would catch it otherwise — both now skip m==5 unless
MTPLX_M5_PADDED_LANE=1 opts the old routing back in (A/B escape hatch).
Fall-through is the stock kernel: byte-equal by construction, receipted
via the existing b4_m5 fallback counter.

Honest scope: the shipped runtime caps serve depth at 3 (the D4 serve
gate pair failed with '--depth must be between 1 and 3' — receipts in
m5-pair.log), so M=5 never occurs on the production serial path; the fix
matters only for batch-lane shapes that land on M=5 and for any future
head contract that raises the depth cap. Committed as routing hygiene on
micro receipts; no serve-level gate is possible on the shipped head.
tests/test_nax_verify.py: fall-through + counter + stock byte-equality +
env opt-in — 10/10.
The default-on ~/.mtplx/logs/request-log-<port>.jsonl claimed 'numeric/hash
telemetry only — no prompt or completion content' but every record carried
request_last_user_preview with literal user text, rotating across 4x64MB
generations of durable history.

Redact at the durable sink only: the JSONL line now carries a stable
sha256:<16-hex> digest under the same key (turn correlation survives), while
the in-RAM dashboard ring and flight-recorder trace labels keep the literal
preview for live diagnosis. The flight recorder's terminal event already
copies a curated numeric set, so nothing else content-bearing reaches disk.
MTPLX_REQUEST_LOG_CONTENT=1 opts back into literal previews for local
debugging. Help text and default-path comment updated to match reality.
…vention per sidecar (youssofal#301)

Forge's embedded-head extraction ran every tensor through
sanitize_plain_weight, whose MTP_RMSNORM_ALWAYS_SHIFT_SUFFIXES branch added
+1.0 unconditionally to q_norm, k_norm, and mtp.norm.weight. Correct for
HF-native Qwen3.5/3.8 exports (zero-centered norms), it corrupted exactly
those three tensors when the source already stored absolute gains (MLX-
converted checkpoints like youssofal#301's) — the drafter then mismatches its trunk
and acceptance collapses to 0-2%, making every depth slower than AR.

Measured on the real fleet (shipped 3.8/4B/9B sidecars vs raw HF Qwen3.5-4B
embedded head): the low set separates delta vs absolute at 0.30-0.39 vs
0.87+, q/k at 0.73-0.75 vs 1.73+, but the FINAL norm overlaps across
conventions (raw-delta 4B mean 2.58 vs absolute 3.8 mean 2.25) — it can
never be judged per tensor. So the convention is now decided once per
sidecar (both separable families must agree: max qk < 1.25 AND min low <
0.5, the exact two-signal gate the runtime heal shipped for youssofal#176) and
applied to all seven norms, in one shared shift_delta_mtp_norms():

- forge embedded extraction: sanitize per tensor, then one ensemble shift
- AWQ/compressed-tensors convert: same shift before mtp.safetensors write
- runtime heal (_heal_raw_delta_mtp_norms): collapses to the shared call

Absolute-convention sources now pass through byte-identical. Tests pin both
conventions, both key namespaces, the overlap-final-norm case, and the
missing-family conservative default.
…ng image parts in user canonicalization (youssofal#327)

The agent-transcript canonicalizer (active whenever tools are present)
pooled consecutive user messages by stringifying both sides through
_content_to_text, so an OpenAI image_url part in either message was
silently discarded before the vision extractor ran — DSH's standard preset
(user(image+text) -> user(runtime snapshot)) always answered blind, while
its minimal preset worked. Likely the same family as youssofal#328 (Pi + vision:
Pi sends tools, MTPLX chat does not).

Guard: a message whose structured content carries any non-text part
(image_url, audio, unknown parts) is exempt from tandem-repeat collapse,
duplicate dropping, and consecutive-user merging — it is appended as-is.
Consecutive text-only users keep merging (covered by regression test), and
downstream already renders consecutive user turns (the
skipped_repeated_assistant branch has always emitted them).
…ode and refuses --stock-ar loudly (youssofal#285)

The bench --harness depth-sweep branch accepted --stock-ar,
--generation-mode, --depths, and --seed, then discarded all four
(depths='3', seed=0, compare_ar=False hardcoded) — argparse validated,
exit code was 0, and a result file was written, so four 'different' A/B
configs produced byte-identical MTP-D3 runs and an 'AR baseline' that
drafted 129 tokens.

Now: --depths and --seed thread straight through (defaults unchanged:
'3'/0), --temperature/--top-p/--top-k and explicit --draft-* overrides are
honored over the runtime-contract values, and --generation-mode ar maps to
compare_ar+ar_only (a real target-only AR baseline; depth<1 already fails
loudly in the runner). --stock-ar cannot be delivered by this harness (it
always loads the MTP runtime), so it exits with a pointer to --harness
direct-http instead of pretending. The result profile records the
requested depths and ar_baseline instead of a hardcoded depth: 3.
…identifier fallback ladder (youssofal#329)

The Hardware card guessed the chip from the Mac model identifier with a
table that was off by a generation across the board (mac14->'M3',
mac16->'M3 Ultra', mac13->'M2'), so a Mac Studio M2 Max (Mac14,13) read
'M3'. The server has always sent the true sysctl brand string in the same
payload (machine.chip: 'Apple M2 Max') — the dashboard just never used it.

The badge now prefers the reported chip (Apple prefix stripped); the
identifier ladder remains only as a fallback for a missing brand string
and now maps chip families correctly: Mac13=M1-era Studio, Mac14=M2
(incl. Studio M2 Max/Ultra), Mac15=M3, Mac16=M4, Mac17=M5. Static bundle
rebuilt (tsc -b clean; new logic verified present in the emitted asset).
… replacing the link (youssofal#292)

The terminal-command installer appended its PATH block by rewriting
~/.zshrc with an atomic write — a temp-file rename that replaces a symlink
with a plain regular file. On the very common dotfiles setup (stow,
chezmoi, yadm, hand-rolled repos symlink ~/.zshrc into a git repo) this
silently detached the live file from version control: the repo copy and
the real file diverge with no signal, and the next dotfiles sync clobbers
the divergence.

Resolve the symlink and write to the target, so the PATH line lands inside
the dotfiles repo and the link survives. Regression test pins the link
destination, the appended line in the target, and the preserved content.
…ch; bf16 keeps tuned 512 (youssofal#319-derived)

grzracz reported (PR youssofal#319) that the gdn_capture fused_add_rmsnorm call at
threadgroup_size=512 perturbs prefill above 64 rows. Probed on M5 Max /
MLX 0.32: CONFIRMED for fp16 — max|diff| up to 3.9e-3 vs the unfused
x+r / mx.fast.rms_norm reference once the grid crosses 2^15 threads
(rows > 64 at 512 lanes), data-dependent by seed. bf16 is bit-exact at
512 in every probe, and the default 1024-lane loop is bit-exact for both
dtypes at every probed shape.

fp16 is exactly the M1/M2 model lane (-FP16 siblings), so this was a
silent numerics leak on the hardware least able to absorb it. Fix keeps
the tuned 512 width where it is provably exact (bf16 — mainline path
byte-identical in output and kernel config) and routes fp16 to the exact
default dispatch. No env knob (PR youssofal#319's dial declined in favor of the
dtype guard); exactness contract pinned by tests across seeds.

Follow-up for the gated bench pass: confirm fp16 prefill TPS is flat
under the 1024-lane loop.
…ussofal#330 by @CharliePetch)

Adopted as-is: completes the bit-exact replay envelope (prompt_token_ids
at dispatch + raw sampled output ids at finalize) for the opt-in request
capture. Zero hot-path cost (inside the capture_dir() gate), never-raises
coercion verified on ints/None/garbage; serial, ar_batch, and mtp_batch
finalizers all carry the field. Local pytest: test_request_capture green.
…iftedx)

Adopted as-is: a module_overrides entry with quantize:false now returns
False from the quantize_model predicate so sensitive modules keep their
source precision, instead of being silently 8-bit-quantized by the
fallback params. Typed override tuples, PR's own regression test included;
test_forge_mixed_convert green locally.
…ussofal#273 by @El-Patronum)

Adopted with a comment cleanup: cmark returns raw HTML as literal text, so
<br> in a table cell rendered verbatim and kept the cell one unbreakable
token that overflowed its fixed column into the neighbour. Single
choke-point regex converts <br>/<br/> (case-insensitive) to real newlines
before AttributedString parsing; comparison operators are untouched and
the parse-failure fallback uses the same converted text. swift build +
render tests green.
The May-era agent-rewrite machinery silently mutated client transcripts:
four content compactors truncated tool results (Kilo's active-read path
had no opt-out at all), heuristic text sniffs dropped client assistant
messages and hid declared tools (the Hermes 'direct reply turn: tools
are unavailable' report), the Pi convergence contract told the model to
stop reading files after 14 tool results, and any client hinting 'pi'
had its stream cut after the first complete tool call even though Pi
executes every tool call in a turn (pi-agent-core ships both
executeToolCallsSequential and executeToolCallsParallel).

Community receipts drove the default: tcpdump proof that Pi sends the
full file and the model receives a digest, a 10-run task measurement at
8/10 success with compaction on vs 10/10 off with no latency benefit,
and Hermes skill files silently reduced to fragments.

The new contract:
- Unset (default): passthrough. No content compaction, no steering
  contracts, no heuristic message drops or toolset filtering, no hint
  sniff stream cuts. tool_choice keeps its protocol meaning and the
  tool-format contract still renders where the template needs it.
- MTPLX_AGENT_REWRITES=on restores the full legacy machinery (the test
  suite pins it via the legacy_rewrites fixture).
- MTPLX_AGENT_REWRITES=off is a hard passthrough guarantee that beats
  per-feature opt-ins and resolves tool prompts template-native (a
  backend-required mode still wins: that is protocol, not policy).
- Per-feature MTPLX_*_COMPACT_THRESHOLD_CHARS env limits engage exactly
  one compactor at the chosen limit, for harnesses that want a lower
  cap than their own default.
- New --agent-rewrites {on,off} flag on serve and quickstart.
- Compaction wrapper text no longer names OpenCode on other clients.
- request_observability carries agent_rewrites; goldens regenerated
  (single field drifted: request_pi_convergence_after_tools 14 -> 0).
…oussofal#282)

Both Pi writers (app PiIntegration.sync and CLI mtplx/pi.py) replaced the
whole providers.mtplx block on every launch, silently reverting user
edits inside it: added vision input, a tuned thinkingLevelMap, explicit
maxTokens, renamed models, extra headers (the intensifi report).

New merge contract, identical in both writers:
- MTPLX owns connection identity only: baseUrl, api, apiKey, authHeader
  and the x-mtplx-client header, because ports move between launches.
- Every other key the user edited wins; MTPLX values fill gaps
  recursively. Model entries merge by id; user-added models and fields
  survive a sync untouched.
- Stale MTPLX-owned model entries (our own previous 'mtplx-'-prefixed
  ids) are pruned so switching models does not accumulate dead picker
  rows.
- The request-policy extension now carries an ownership marker: replace
  its content (dropping the marker and the mtplx identifiers) and it is
  yours, MTPLX never rewrites it again. Managed copies keep receiving
  updates; templates stay byte-identical across both writers.

Tests: Python round-trip preserving vision/thinking/cap/name edits plus
extension ownership; Swift mirrors both (654/654 green).
# Conflicts:
#	apps/MTPLXApp/Sources/MTPLXAppCore/Onboarding/RuntimeSetupService.swift
#	mtplx/cli.py
#	mtplx/commands/forge.py
#	mtplx/compressed_tensors.py
#	mtplx/gdn_capture.py
#	mtplx/mtp_patch.py
The bench handler applies the profile env block in-process by design;
the youssofal#285 test file exercised it without isolation, so performance-cold's
MTPLX_DROP_EVENTS leaked into the suite and silenced context-copy block
events two files later (caught by the first full-suite run of the
merged 2.9.2 tree).
The app's serve launcher exported the May-era transcript-rewrite envs
(tool-result 1200-char compactor, read-inspection line caps, force-answer
after 12 tools) on every coding-agent launch. Explicit envs re-arm those
compactors past the engine's MTPLX_AGENT_REWRITES passthrough default, so
app-launched daemons kept rewriting Pi/OpenCode transcripts after the
engine-side cleanup. Receipt: a live Pi session showed 133,794 chars of
tool results compacted at the 1200 threshold.
The receipts page for the project's founding claims, mirroring
mtplx.com/history/: first exact speculative sampling with the native MTP
heads on a Mac (2026-04-27, commit-stamped), shipped as an installable
runtime 2026-05-02, llama.cpp reaching MTP 05-16 and hybrid GDN 08-03,
vllm-metal documenting the cache-vs-speculation gap 08-10, and the oMLX
kernel attribution. Every entry carries a public commit, PR, or changelog
reference so the claims stay checkable. README gains a History section
pointing at both copies.

Prose authored by the founder's editing pass (recovered from
release-stash-20260817); facts verified against MEASUREMENTS.md, the
changelog, and the upstream PRs before landing.
The release gate's app/CLI parity pair caught the other half of the app
fix: mtplx start still exported the read-inspection battery (72/8/120 +
force-answer-after-12) on the shared block and the Pi compaction five
(threshold 1200 + line caps) on the Pi lane. Explicit envs re-arm those
compactors past the engine's passthrough default, so CLI-launched agent
sessions were still being rewritten. Both launchers now export none of
it, and a new parity test pins the full nine-key contract on all three
surfaces.
Version bump, changelog, and release notes for the 2.9.2 patch release:
passthrough-by-default serving endpoints and app launcher (youssofal#282), greedy
trio default-on below a 12288-token context fence (youssofal#313/youssofal#315/youssofal#318), forge
MTP norm convention decided per tensor set (youssofal#301) with a double-shift load
guard (youssofal#306), vision canonicalization and cache-restore fixes (youssofal#327/youssofal#296),
batched repetition stop (youssofal#311), content-free request log (youssofal#326), symlinked
zshrc installer fix (youssofal#292), honest bench flags (youssofal#285), exactness kernel
fixes (youssofal#319/youssofal#320), community ports (PR youssofal#273, youssofal#281, youssofal#330), and experimental
opt-in fusion/crossrow/headcal/marathon levers.
… windows)

Second float4 bank (rows 4..7) in the partials kernel; compiles out at
QL <= 4 so the shipping D3 verify shape is byte-identical. Dispatch gate
and kernel contract widen to 2..8; explicit max_q_len still narrows.
Exactness: fp32-reference parametrized q_len 2..8 x offsets {515, 2048,
2051} green (26/26). Depth 4's verify window (q_len 5) now rides the
split-KV fast path at >=8k ctx instead of silently falling to fused.
…d, exact by construction

Port of the Y-PC 3090 Session-2 lever (step −12.9% there): draft from the
top-N frequency-ranked lm_head rows; the target verifies the full vocab, so
ratio acceptance + residual correction stay exact for any proposal q.

Design decisions with receipts:
- Side-stamped head (_mtplx_frspec_draft_head), swapped in ONLY around the
  device draft core's warm+trace window and restored in a finally: legacy
  draft paths keep dense draft_q arrays indexed by real token id
  (generation.py ~9999), so a global swap would IndexError them.
- Every mapping site width-guards at trace time (row width == ids width):
  an mx.compile retrace resolving the restored full head degrades to exact
  full-vocab drafting, never a silent mismap.
- core['vocab_size'] now carries the distribution DOMAIN (full vocab) when
  FR-Spec is engaged — SparseDistribution.to_dense() scatters by real token
  id and would overflow a pruned-width domain.
- CPU unit receipt: pruned(x) == full(x)[ids] max|diff| = 0.0.

Env (default-off): MTPLX_FRSPEC_DRAFT=1, MTPLX_FRSPEC_VOCAB=<json>,
MTPLX_FRSPEC_N. The Y-PC ranked-ids artifact (runs/draft_vocab.json,
coverage 0.99728 on real traces, ceiling cost ≤2.2% over 8 positions)
loads unchanged — same tokenizer family.
youssofal and others added 28 commits August 29, 2026 04:31
…oning-effort + /effort, version and hardware output polish, did-you-mean

Seven release-night CLI polish items, all verified against main (d12e80a):

1. readline line editing + persistent history in the terminal chat REPL
   (~/.mtplx/history, 1000 entries, atexit save). The prompt's ANSI escapes
   are now \001..\002 bracketed under readline so editing a wrapped line does
   not redraw over the prompt.
2. stdin piping: `echo "..." | mtplx run` reads the prompt from a pipe, and
   `start cli` answers a piped prompt through the same turn path --prompt
   uses. The non-tty refusal survives for an empty pipe.
3. help stops hiding shipped commands: run/chat/serve join PUBLIC_COMMANDS,
   and list/remove/config/env/dashboard/integrate get a "Server and
   scripting" group.
4. `mtplx ask` gains --reasoning-effort (its siblings run/chat already had
   it and it shares their handler), plus /effort <level|status> in the chat
   REPL, mirroring how /reasoning mutates live state.
5. `mtplx --version` prints the parenthetical only when the display version
   differs from the package version, instead of "mtplx 2.10.0 (2.10.0)".
6. `mtplx hardware` no longer prints "hardware acceleration confirmed: false"
   at a human; the JSON document is unchanged.
7. unknown commands get one difflib "Did you mean" line over every registered
   subcommand, before the existing menu. Exit code stays 2.

tests/test_public_cli.py: 280 passed (baseline 256), stable over 3 runs.
…iping, help surfaces, ask effort + /effort, version and hardware output)
…exit 4 in a no-mlx-lm venv, was the old exit-2 refusal)
… FlashAttention

- Stage-1 tiled TensorOps indexer scoring with fused ReLU/reduction and per-row Metal radix top-k selection.
- Stage-2 block-sparse FlashAttention consumer with in-place KV streaming, GQA group parallelism, and fp32 online softmax.
- Cache and speculative MTP integration with strict geometric gating and fallback safety.
Pi's provider entry hardcoded input: ["text"], so Pi never offered or sent
image parts for MTPLX regardless of the pack's vision tower — while the
same model served images through the built-in chat (issue youssofal#328's exact
signature, and why oMLX worked under the same Pi config). The capability
now derives from vision_spec_for_model_dir on the resolved model dir — the
same probe /health uses — so Pi and serve can never disagree.

input is engine capability, not user preference: the merge now owns it the
way it owns compat (receipt: the 2.9.x supportsReasoningEffort:false that
survived re-syncs), so a stale ["text"] written by a pre-vision MTPLX
cannot outlive the engine that wrote it. Other user model fields still win.

Tests: builder flag, merge ownership + user-field preservation, and the
resolver probe on a synthetic pack; all fail against the unfixed tree.
Turbo's M=4 verify (lm_head included) runs vk_k — argmax-validated, NOT
bit-exact vs stock (~6e-3 accumulation drift; near-tie flip band ~1.6e-2)
— while AR decode runs M=1 stock. At temperature 0 that breaks the product
promise (MTP == AR token-for-token) at near-tie rows. Every shipped gate
was blind by construction: mtp1-greedy-gate is M=1, selfcheck tol 0.1,
unit tol 0.25, fp16 gate ~0.6 — all 6-100x the flip band.

Fix: exact_verify contextvar (mirrors attention_phase), armed at the
verify forward from the live sampler temperature; the QuantizedLinear
patch falls through to stock while set (counter exact_t0 in the nax
fallback counters as the engagement receipt). The shared compiled-verify
trace key now carries the route so a t>0 trace can never replay vk/nax
kernels into a greedy request.

Sampled requests (t>0) keep the fast kernels — the ratio-acceptance math
is exact by construction there. Greedy perf cost on the 27B 4-bit packs
to be measured (whole-profile receipts suggest -20..25% at t=0 only);
greedy identity D3 gate lands with the GPU session receipts.

Discrimination proven against the shipped 2.10.0 package: ImportError,
missing wiring, and missing trace-key flag on the baseline.
The generic vision pipeline already served Flash-Next end-to-end (spec
resolves, tower loads, splice feeds input_embeddings, /health says
enabled) — but text_config's mrope_section [11,11,10] / mrope_interleaved
were silently dropped, so image tokens got plain 1-D rope: numerically
wrong positions vs training for every admitted image request. This lands
the reference semantics (mlx-vlm / transformers Qwen-VL family, MIT,
adapted with attribution):

- mtplx/vision/mrope.py: per-request (t,h,w) position table + decode
  delta from expanded ids + image grids — position contraction included;
  refuses (falls back to plain rope) on video pads or layout mismatch,
  never emits a wrong table. Pure function of request content: nothing
  new rides cache state, so bank restores stay format-stable.
- qwen4_exp: TextArgs parses the mrope contract; interleaved axis layout
  t@0,3..30 h@1,4..31 w@2,5..29 over the 32 rotary pairs; Attention
  slices the table by its own cache offset (prefill) and runs equal-axes
  at seq+delta past it (decode = plain rope shifted, scalar path kept).
  Equal-axes tables reduce bit-exactly to plain rope — text serving is
  provably unchanged.
- Vision requests bypass QSA sparse selection and run dense-causal —
  the reference implementation's own multimodal fallback (its sparse
  fast paths exclude M-RoPE); the indexer still runs so QSA cache
  streams stay byte-identical with text serving.
- Wiring: vision_rope contextvar (attention_context); prompt-state
  builder decorated (request + warm-restore + idle postcommit forwards
  all covered — wrong rope on any one would poison banked states) and
  the decode verify block arms the scope; serve materializes the table
  in _materialize_vision_splice; the digest LRU now carries grids.

Tests: axis layout vs reference, contraction + delta receipts, equal-axes
bit-identity, poison-indexer proof that the dense override discriminates,
wiring pins. Vision+qwen4 suites: 83 tests green.
…rnels

- Extend QSA FlashAttention prefill ceiling to 1,048,576 tokens.
- Add canonical static YaRN RoPE inv_freq and attention_scaling to Metal kernels.
- Fuse RoPE amplitude scaling directly into query preparation and key pooling shaders.
…sure abort (youssofal#393)

The report: Flash-Next served at --context-window 262144 on a 128 GB
machine, a long prompt grew a ~119 GB footprint, macOS compressed and
swapped for minutes, and the engine never refused anything — the only
507 trigger was a Metal allocation error that swap prevents from ever
firing. Four coupled defects, four fixes:

1) The plan priced qwen4_exp like a dense family. New per-token terms:
   qsa_aux_bytes_per_token_from_config (raw indexer keys + pooled block
   keys + fp32-transposed mirror across 13 QSA caches + the MTP head's
   uncounted KV = 7,872 B/token for the shipped pack) and
   qsa_prefill_transient_bytes_per_token_from_config (the indexer
   dense-mask lane's peak scales with full token count: 12.75 B/elem x
   chunk 2048 x 4 live layers = 104,448 B/token). The fit solve now
   divides the budget by kv + aux + transient — honest fit on the youssofal#393
   machine lands ~100-120K, not 262K.

2) Request-time refusal: _reject_prompt_over_context gains a 507
   (insufficient_memory) arm — fires only when the served window is
   explicitly overcommitted past the plan fit AND this prompt's cold
   cost lands beyond the fit. Prompts under the fit still serve, so the
   warn-loudly override keeps its legitimate use; non-overcommitted
   serves are byte-identical to before.

3) Runtime backstop: the pressure guard loop arms a pressure_abort_event
   after 3 consecutive CRITICAL ticks (~30 s) with the engine busy; any
   sub-critical or idle tick disarms. Both request-lane prefill
   abort_checks (AR + MTP) and the postcommit abort predicate consult
   it; at the catch, a pressure-caused PostcommitAbort maps to the
   honest 507 (shedding caches) instead of the silent disconnect path.

4) QSA cache growth was Θ(N²): fixed +256-row growth full-copied the
   buffer every step (~34 GB memcpy per layer over a 262K decode).
   Doubling growth bounds copy traffic at O(N); the fp32 pooled mirror
   stays in lockstep and seeds from pooled CONTENT when absent (a
   zeros seed blanked restored blocks' scores — caught by the state
   round-trip gate). Model.__call__ also gains emit_logits/logits_keep
   so prefill chunks stop materializing 1.02 GB of unused vocab logits.

Tests: test_qsa_memory_plan_393.py (terms, fit shrink + overcommit flag,
doubling growth, mirror equality), test_pressure_abort_393.py (507
refusal matrix, arming/disarming state machine, wiring pins). Both
verified to fail against the shipped 2.10.0 package. Memory, server,
QSA, vision and exactness suites green (297 tests this batch).
…wn never downgrades user edits

The youssofal#328 fix made ``input`` engine-owned on re-sync, which stomped a
user-taught ["text", "image"] whenever the engine could not positively
resolve vision (including unresolvable model refs) — exactly the youssofal#282
clobber contract test_pi_models_config_sync_preserves_user_edits pins.
Split the rule: fresh ["text","image"] (positive vision knowledge)
still overrides a stale ["text"] from a pre-vision MTPLX; a fresh
["text"] fills the key only when missing and never deletes what a
human wrote. Full test_public_cli.py suite green (139).
…in) + dead-selected reorder

The QSA prefill overhaul's score-side arm (youssofal#393 audit): the whole-chunk
indexer scoring stages [1, S, H, nb] fp32 + its relu twin — ~2.1 GB per
layer per 2048-chunk at 262K, the dominant prefill transient the memory
plan now prices at 12.75 B/elem. Tiling the query rows computes the
identical per-row selection (dot, relu-sum, validity, 1e-12 tie nudge and
top-k never cross rows) with mx.eval retiring each tile's fp32 before the
next builds, bounding the live transient to one tile (8x smaller at the
default-sized 256-row tile).

Default OFF (env rows count, 0 = off) per the no-flip-without-receipt law:
the per-tile eval sync cost needs a GPU measurement before any default,
and only then can QSA_INDEXER_TRANSIENT_BYTES_PER_ELEM honestly shrink
(raising the plan's fit). The selection restructure also moves the dense
[S, nb] selected build behind the S==1 fast-lane returns (flash /
gather-decode only need top_idx — the audit's dead-mask-per-decode-step
note), and the rows-gather lane is guarded against a tile smaller than
its width.

Tests: parity at tiles 7/16/33 incl. ragged tail, exact-tie (all-zero
relu) stability, small-S untouched, rows-gather guard, default-off
contract. Full QSA/qwen4 suites green.
# Conflicts:
#	mtplx/models/qwen4_exp.py
…e flash_prefill contract

The NAX flash kernel (PR youssofal#397) covers M5-class Metal 4 TensorOps only; on
every other Metal device an armed MTPLX_QSA_PREFILL fell back to
reconstructing the dense [S,T] mask — paying the exact whale the lane
removes. The new MTPLX_QSA_PREFILL_GATHER tier consumes the same compact
per-row block selections with bounded gathered attention (row tiles,
fp32 softmax, per-tile eval) — the oMLX PR #3244 portable-lane approach
reimplemented on our block/validity contract. Same visible set as the
dense mask (parity + invisible-token perturbation + tile-invariance
tests), works on any Metal device.

Also: per-lane engagement counters (qsa_prefill_engagement +
MTPLX_QSA_PREFILL_DEBUG atexit receipt) so no A/B of these lanes can be
read without proof the arm ran, and test updates for the merged
selector signature + the scripts' broader pgrep safety pattern.
…stead of returning an empty message

User reports (27B + Flash-Next, chat and API): model streams reasoning,
stops, and no answer appears. Root cause: the F3 reasoning-as-content
recovery is deliberately disabled when tools are active (planning prose
is not an answer), and the continuation repair required
tool_result_history_present — so the FIRST turn of any tools-declared
conversation (app chat with web search on, agent clients' opening
message) that ended with a reasoning-only stop returned an empty
assistant message with no repair attempt. The repair now covers
tools-declared turns without prior tool results; observability
distinguishes the new trigger (tools_declared_reasoning_only_completion).
Discrimination proven: the new test fails on the unfixed tree (single
generation call, empty content) and passes with the fix.
…aware youssofal#393 admission pricing

Receipts (2026-08-30 ABBA battery + live serve QA, MEASUREMENTS.md):
98K cold prefill +34.8% paired means with peak memory 91.4->83.0 GB,
flat at/below the 32K crossover in both ABBA orders, 131K at 810.6
tok/s — and 262K cold prefill completing at 738.7 tok/s / 87.36 GB peak
on the exact 128 GB machine class issue youssofal#393 wedged at 119 GB.

- MTPLX_QSA_PREFILL unset resolves auto: on iff the flash kernel's own
  device gate passes (Metal 4 TensorOps); explicit env wins both ways.
  Non-NAX machines keep dense defaults until the portable gather tier
  carries receipts on that hardware class.
- Flash crossover default 65536 -> 32768 (flat at the 32K rung both
  orders, full win beyond; the conservative default gave away 32-64K).
- The youssofal#393 context-linear dense transient term drops to 0 when the lane
  serves: measured honest at 262K (predicted 87.7 GB vs 87.36 observed).
  Default serve on a 128 GB M5-class machine offers the full 262,144
  window again — with the request-time 507 and pressure abort intact as
  backstops. Live banner + /health receipts in MEASUREMENTS.md.
- Chunk 4096 falsified on M5/NAX (-5.7%, +4.5 GB peak): default stays
  2048, matching oMLX's own NAX note.
…he tool-parse fallback, repair failure can never 500 a servable turn

Full-gate catch: extending the reasoning-only repair to first turns made
it fire on unclosed tool-call markup (the thinking splitter classifies
post-<think> markup as reasoning), hijacking turns the established
unclosed_tool_call fallback owns — and a repair-path exception (e.g. a
tokenizer without the expected surface) 500d a request whose first pass
was perfectly servable. The repair now skips any first pass carrying
tool-control markers, and the whole attempt is contained: failure stamps
reasoning_completion_repair_error and returns the original result;
stream cancellation still propagates. Full gate after: 5218 passed / 0
failed.
…al#397 adapted) + portable tier + auto default + empty-answer repair

98K cold prefill +34.8% (ABBA, fan-verified, die-temp-gated), peak
91.4->83.0 GB; 131K at 810.6 tok/s; 262K — the youssofal#393 wedge scenario —
completes at 738.7 tok/s / 87.36 GB peak. Live product-path QA: default
serve offers the full 262,144 window honestly (lane-aware youssofal#393 pricing),
51K cold prompt at 913.8 tok/s / correct answer / healthy acceptance,
warm restore 1.71s TTFT. Full gate 5218/0. Receipts in research
MEASUREMENTS.md + LOG.md 2026-08-29 21:05 PDT.
…rebuild on failure

A user's app-owned venv (Application Support/MTPLX/runtime-venv) had a torn
mlx install: core.cpython-314-darwin.so could not dlopen against its own
libmlx (Symbol not found: ...scaled_dot_product_attention...). The daemon
died before /health on every launch and REINSTALLING THE APP CANNOT FIX IT:
the venv survives reinstall, `mtplx --version` never imports mlx so the
version floor stays green, and the bundled-wheel fingerprint still matches,
so installOrUpdate reused the corpse forever. Reported via Twitter with
`mtplx report --summary` receipts (FAIL mlx.import, macOS 26.4.1 / py3.14).

The bootstrapper now proves the venv can actually run before trusting it:
- Import probe (`venv/bin/python -I -c "import mlx.core, mtplx"`) on first
  adoption of each wheel; verdict cached in runtime-import-health.sha256
  inside the venv (healthy steady-state launches pay one stat()).
- A daemon death before /health writes a recheck breadcrumb
  (MTPLXBackendStore catch paths, classifier excludes cancellations and
  port conflicts), forcing a re-probe on the next launch — late breakage
  (foreign pip session, torn upgrade) self-heals in one relaunch.
- A failed probe rebuilds the venv from scratch (venv --clear + full
  reinstall from the bundled wheel) with a "Repairing MTPLX runtime"
  status, and fresh installs must pass the probe before being vouched for.

mtplx doctor's mlx.import fix text now states the real remedy (app
relaunch self-repairs; CLI: pip --force-reinstall) instead of the no-op
"pip install mlx". 9 new/updated Swift tests cover the marker fast path,
first-adoption probe, breadcrumb override, --clear rebuild, and the
death classifier.
…G14 device gates for 1024-thread kernels

Issue youssofal#400 (M2 Max 96GB, Flash-Next Optimized Speed) reported two boot
blockers this commit fixes; the third finding (committed-history verify
stall) is the decode-cliff campaign's subject.

1) Memory guard refused the pack by exactly its own margin: flat
   6 GiB floor margin + flat 16 GiB system reserve were sized on the
   128GB receipt machine (weights 77.3 + 6 + 16 = 99.3 > 96), while the
   reporter demonstrably serves the pack healthy there with ~16 GiB
   unwired. Both terms now scale below their receipt machines and are
   UNCHANGED at 128GB+: system reserve = clamp(RAM/8, 8, 16) GiB
   (96GB -> 12); floor margin = 6 GiB at >=112GB RAM else
   clamp(RAM/32, >=2) GiB (96GB -> 3). 96GB now admits at
   floor 80.3 + reserve 12 = 92.3 <= 96 with the wired cap raised to the
   floor and 15.7 GiB left unwired — in line with the issue's own
   healthy receipt (wired 79.3 / free 16.7). Oversized packs still
   refuse; Laguna 64GB refusal and all existing cap tests unchanged.
   The refusal message now prints the actual scaled reserve.

2) Three turbo-armed kernels hard-crashed serve boot on G14 GPUs:
   hyper_connection_v3 (R1+R2), gdn_conv_norm, and its verify-rows
   variant dispatch structurally-1024-thread pipelines, and on M2/M3
   register pressure caps the compiled pipelines at 896 threads — MLX
   raises at encode time, and pack contracts arm the envs, so the boot
   dies until the operator hand-exports three kill switches. Each
   kernel module now carries a one-shot device probe (dispatches the
   REAL pipeline on dummy family-shaped inputs — the limit is
   per-pipeline register pressure, so nothing cheaper proves it) and
   the qwen4_exp applies-gates consult it: unsupported devices land on
   the eager chain automatically with one log line. Probes verified
   green on M5 Max (no false negatives on healthy hardware); the rows
   parity test pre-warms the probe so its engagement counter stays
   exact.
Probe receipt (decodecliff battery, 2026-08-30, M5 Max, Flash-Next 91K
warm turn): a 384-token completion read decode_tok_s=21.3 while its own
sliding decode windows ran 50-62 tok/s — 10.7 s of session-restore
machinery (snapshot-view COW divergence; prompt_state_unattributed_time_s)
was charged INTO decode_elapsed_s because the rate split only subtracted
measured prefill compute + cache-restore fetch. This is a big slice of the
founder-visible "decode collapses to 20-25 tok/s at 80-90K in OpenCode":
the engine decoded at ~50, the receipts (and every UI reading them)
blamed decode for restore stalls.

_generation_rate_fields gains non_decode_extra_s; the mtpk loop passes
the full prompt-state span remainder + pre-first-token setup + the bank
commit span, and the AR loop now measures its prompt-state span and does
the same (stamping prompt_state_total/unattributed for AR receipts too).
end_to_end_tok_s still tells the whole-wall truth. Unit tests replay the
turn-4 receipt (21.3 -> 52.2 tok/s) and pin the clamp + default-zero
semantics.
…t time

The lazy put (kvcache-v2) keeps response tails flat by snapshotting the
live cache as zero-copy views — but every unevaluated view holds a
reference to a live cache buffer, blocking MLX buffer donation. The next
turn's first write to each buffer then pays a full COW divergence copy.
Historically the views only settled when the coalesced SSD cold-encode
job got an idle window; under back-to-back agent turns (OpenCode) that
window never arrives, and the divergence lands inside the following
request: measured 10.7 s of prompt_state time on a warm 91K turn
(decodecliff probe 2026-08-30), and the founder's session shows the same
class (12.7 s TTFT on a warm 86K turn, 30 s postcommit timeout followed
by a 44K re-prefill).

put() now dispatches a settle job (per-array mx.eval over the entry's
snapshot views) to the model-owner idle lane AHEAD of the SSD encode,
newest-wins coalesced per session, off-switch
MTPLX_SESSION_SNAPSHOT_SETTLE=0. Per-array granularity bounds any
foreground request that lands mid-settle to ~one array (~10 ms). First
receipts: the two battery arms that ran with settle live show zero stall
events across six warm 91K turns (TTFT <= 2.2 s) vs an 11.1 s landmine
in the pre-settle arm; dedicated on/off arms are running.
…wner-copy rewrite; decode-lane engagement counters

Two corrections from tonight's battery receipts (MEASUREMENTS.md 2026-08-30):

1. Settle v1's per-array mx.eval was a physical no-op: evaluating a
   full-range lazy view ALIASES the source buffer (measured: zero
   allocation; mx.contiguous also no-ops on contiguous inputs), so the
   donation-blocking reference survived and the COW stalls reproduced
   with settle on. Rewritten to metal_copy_leaf owner copies with
   field-at-a-time entry rebinding (every intermediate state
   value-correct for concurrent restores; rebind + value equality
   pinned by test).

2. Even mechanically correct, the phase-3 A/B (on/off/off/on, warm 91K
   turns) falsified settle as a DEFAULT: stall magnitude is dominated by
   idle-lane/SSD scheduling nondeterminism (the next request queues
   behind multi-GB encode/settle jobs) and the added copy produced the
   worst observed stall (27.6 s). Default flipped OFF; the mechanism
   stays as an opt-in instrument with the falsification documented at
   the switch. The structural fix for the stall class is youssofal#391-style
   fixed-capacity QSA banks + restore-priority idle-lane preemption
   (LOG.md same date).

Also: decode-lane engagement counters (decode_flash_skip / decode_gather
/ decode_dense_mask) on the QSA S=1 selector arms — the flash/gatherdec
91K arms read flat and future lane A/Bs need engagement receipts, per
the counters law — and the decode-cliff probe driver
(scripts/decodecliff_probe_20260830.py: process-per-arm serves,
fan-verified, die-temp gated, salted prompts, receipt extraction).
…on-truth split

The old assertion pinned decode_elapsed == elapsed - prompt_eval, i.e. the
exact accounting 586d087 fixed (restore machinery silently counted as
decode). Now asserts the full identity including cache_restore and
prompt_state_unattributed, plus the new AR prompt-state stats. Also adds
the pulse-cell driver script for instrumented decode captures. Full gate
after fix: rc=0, zero FAILED.
Patch release over 2.10.0: the lightning sparse prefill lane (PR youssofal#397 by
maceip, adapted; 98k prefill -35% time, 262k un-wedged at 87.4 GB where
2.10.0 hit 119 GB with zero tokens, youssofal#393), native Flash-Next image
serving (M-RoPE, youssofal#328), 96 GB admission + M2/M3 threadgroup crash fixes
(youssofal#400), tools-declared empty-answer repair, greedy Turbo exactness on
the 27B, app runtime venv self-heal, and honest decode attribution in
receipts. Stamps: pyproject / version.py / CITATION / uv.lock 2.10.1.
…s — ci job red since 2.10.0 on 7GB runners

The seat tests pinned mtplx.memory_plan.detect_total_ram_bytes but the
plan also consumes the host through _detect_total_ram_bytes_for_metal_caps,
whose 75%-of-RAM memory limit rides into the plan as usable_bytes_override.
On 7 GB GitHub macos-14 runners that collapsed the override to ~5 GiB:
the 128G test came back machine-bound at 4096 and the 48G seats drifted.
This exact no-mlx-smoke job was already red on the v2.10.0 release commit
(run 33252270428) — pre-existing, not a 2.10.1 regression. The harness now
routes the caps detector through the same per-test pinned source and
clears MTPLX_MEMORY_LIMIT_BYTES/MTPLX_WIRED_LIMIT_BYTES. CI file set
locally: 883 passed / 0 failed (was 3 failed on the runner).
… 8000 collisions

Thank you for MTPLX — Dashboard and Forge are great.

6875 = M T P L on a phone keypad. 8000 is the most contested port on a
dev Mac — every gateway, FastAPI, and other runners default to it.
LM Studio uses 1234, Ollama 11434, Rapid now moves to 7659 (RMLX).
Uncontested defaults let two local rigs coexist without harness resets.

- Change --port default 8000 -> 6875 in cli.py (serve/start/quickstart/
  connect/settings), server/openai.py, onboarding.py, diagnostics.py
- 8000 remains usable via --port 8000 or existing configs — no breaking
  change, just new installs start on 6875.
- Companion issue: raullenchai/Rapid-MLX#2879
  and PR raullenchai/Rapid-MLX#2878

Happy to adjust number or scope — any uncontested pick fixes the run-order
dependency.
@youssofal
youssofal force-pushed the main branch 2 times, most recently from 2382dfd to 8bc4d88 Compare September 1, 2026 08:07
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.

Consider defaulting Desktop/CLI to 6875 (and keeping 8000 as fallback) to avoid 8000 collisions

4 participants