Desktop/CLI: default to 6875 (MTPL) and keep 8000 as fallback — avoid 8000 collisions - #419
Draft
Fe2-O3 wants to merge 712 commits into
Draft
Desktop/CLI: default to 6875 (MTPL) and keep 8000 as fallback — avoid 8000 collisions#419Fe2-O3 wants to merge 712 commits into
Fe2-O3 wants to merge 712 commits into
Conversation
…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.
…active-read compactor (youssofal#282)
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
…pers, drop unused Mapping import
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).
…t-level decision)
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.
…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)
…onditional (test_no_mlx_imports)
…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.
…, engagement-checked)
…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
force-pushed
the
main
branch
2 times, most recently
from
September 1, 2026 08:07
2382dfd to
8bc4d88
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/connectto6875— M T P L on a phone keypad — and keeps8000usable via--port 8000or existing configs. No breaking change; new installs just start on an uncontested port.Why
Every local runner defaulting to
8000forces silent fallback or harness resets. LM Studio uses1234, Ollama11434, Rapid-MLX is moving to7659(RMLX) with an8000fallback (raullenchai/Rapid-MLX#2879 and raullenchai/Rapid-MLX#2878). I run another gateway on8000; with both on 8000, run order matters and every harness has to be re-pointed. Run order shouldn't matter.MTPLX already has
--portand 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->6875mtplx/ui/onboarding.py:port: int = 8000->6875mtplx/diagnostics.py:server_port: int = 8000->68758000remains 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.