Skip to content

Add mlx-lm as a third server, and settle the quant and peg-native anomalies - #1

Merged
devYRPauli merged 7 commits into
mainfrom
m6-mlx-replication
Jul 21, 2026
Merged

Add mlx-lm as a third server, and settle the quant and peg-native anomalies#1
devYRPauli merged 7 commits into
mainfrom
m6-mlx-replication

Conversation

@devYRPauli

Copy link
Copy Markdown
Owner

M6. Adds a third server, retracts one published reading, and closes the two
outstanding anomalies at the replication bar. 230 measured runs behind it.

Retraction first

M5 measured nine quantization arms once each and read Qwen2.5-7B's 47/45/48 as
the lowest quant beating the highest, against folklore. That does not hold.

Re-running every arm five times reproduced the M5 numbers exactly, which looked
like confirmation and was not: the runner hardcoded temperature 0.0 and seed 42,
so decoding was greedy and each repeat was the same computation. All 45 runs
matched down to the identical set of failing scenario ids. Under five distinct
seeds at temperature 0.7 the three 7B quants land at 46.8, 45.4 and 46.4 with
fully overlapping ranges.

What survives is narrower: quantization level from Q8_0 to Q3_K_M does not
predict tool-calling ability on this corpus, and the ordering is not monotonic
(Qwen2.5-1.5B puts Q4_K_M below the more aggressively quantized Q3_K_M).

--seed and --temperature are now flags, defaults unchanged so existing
results stay reproducible. Amendment 7 records that greedy repetition shows
reproducibility, and only a seed-varied arm supports a claim that two arms
differ.

Decode-constraint disclosure

Required for launch and previously missing. llama.cpp compiles the supplied tool
definitions into a GBNF grammar and constrains decoding, so a call naming a
function that was never supplied cannot be sampled. Ollama and mlx-lm generate
unconstrained and parse afterwards. That favours llama.cpp in every row, so a
llama.cpp-vs-Ollama delta is a stack property, not a server defect or a model
difference. Now stated in the README and on the matrix page, and recorded per
result in server.quirk_flags. Verified for mlx-lm 0.31.3 by source inspection:
tools reach the chat template and nothing else, and parse failures are swallowed.

peg-native anomaly: replicated, excluded, not version-bound

Llama-3.1-8B loses 7-9 of 50 scenarios per run to llama.cpp returning HTTP 500,
"does not match the expected peg-native format". The model is fine; it emits
correct calls as bare JSON, often separated by "; ". Its lazy GBNF trigger never
fires, so generation is unconstrained, and the post-hoc PEG re-parse then
rejects the text and escalates to a 500 with no fallback.

Homebrew is pinned at b10050 so brew upgrade was a no-op; the arm was re-run
against the upstream b10075 binary instead. Identical on every arm and run, with
a clean Qwen control. Not version-bound. The three Llama-3.1 llama.cpp arms are
excluded with reason from the quantization conclusion and shown as such on the
site. Recorded as a candidate upstream issue, not filed.

mlx-lm, and the one real quantization effect

Six seeded rows, chosen to overlap the existing model set. Qwen2.5-7B 4bit
scores 46/50, Qwen2.5-1.5B 41/50.

Llama-3.1-8B, Mistral-7B and Phi-4-mini all score exactly 7/50, passing only the
negative traps. mlx-lm logs "model does not support tool calling" on all 50
scenarios for each, so the tools never reach the model. A gap in mlx-lm's
parser coverage, which is what a whole-stack cell should expose.

Qwen2.5-7B scores 46/50 at 4bit and 7/50 at 8bit, five runs each, identical
every run. The 8bit conversion emits doubled braces:

8bit:  {{"name": "get_weather", "arguments": {"city": "Boston"}}}
4bit:  {"name": "get_weather", "arguments": {"city": "Boston"}}

The bytes were recovered by generating through mlx_lm directly with the 4bit
conversion as control, before writing any claim. That is what turned this from
"the server dropped a call" into "the model emitted malformed JSON" - the same
correction this project had to make once before. mlx-lm's parser is correct.
The server defect that remains is the reporting: HTTP 200, finish_reason
tool_calls, and a message carrying neither content nor tool_calls, so a client
cannot tell this from silence. Same class as the Ollama silent-discard issue.
Both recorded as candidates, neither filed.

Also

  • New mlx_lm preset on a dedicated port 8081, since mlx-lm's own default of
    8080 collides with the llama.cpp convention; 8081 joins the contention
    preflight.
  • README documents two ways to get an mlx-lm row wrong: MLX rows are converted
    weights, so the one-blob-two-servers control does not cross that boundary,
    and mlx-lm's /v1/models lists the entire local cache rather than the loaded
    model. A first pass of the seeding script used it for discovery and filed
    three rows under the wrong model name; those runs were discarded and re-run
    behind an identity assertion.
  • Site gains the MLX LM column, the decode-constraint paragraph, replication
    totals, and an excluded-rows block.

Verification

fmt, clippy and 97 tests green. All 32 result files pass the CI schema job
locally, and evidence hashes verify. Every model run was CERES health-gated on
macstudio and the guard never tripped.

devYRPauli and others added 7 commits July 21, 2026 10:40
mlx-lm becomes the third server preset. It runs on a dedicated port 8081
because its own default of 8080 collides with this project's llama.cpp
convention, and 8081 joins the contention preflight probe list.

quirk_flags stops being permanently empty and now records which side of the
constrained-decoding line a stack sits on. llama.cpp builds a GBNF grammar
from the tool definitions, so a tool call with an invalid function name is
structurally impossible there. Ollama and mlx-lm generate unconstrained text
and parse the tool call out of it afterwards, so a wrong name is possible and
is caught, or silently dropped, only after the fact. Verified in mlx-lm
0.31.3: tools reach the chat template and nothing else, the package contains
no grammar or logit-mask machinery, and parse failures are swallowed.

LM Studio, vLLM and custom stay unflagged because their decode path has not
been verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…raint

Both sampling parameters were hardcoded (seed 42, temperature 0.0), which
makes every published run greedy and byte-for-byte deterministic. Five repeat
runs of one configuration therefore score identically and fail the identical
scenarios, so the project's replicate-five-times bar has been demonstrating
reproducibility rather than sampling variance. Varying the seed alone would
not have helped: at temperature 0 decoding is argmax and the seed is inert.
Both are now flags, and both keep their old defaults so existing results stay
reproducible.

The README gains the disclosure that a cell is a property of the whole stack.
llama.cpp compiles the supplied tool definitions into a GBNF grammar and
constrains decoding with it, so a call naming a function that was never
supplied cannot be sampled there. Ollama and mlx-lm generate unconstrained and
parse afterwards. That difference is systematic and favours llama.cpp in every
row, so a llamacpp-versus-Ollama delta is a stack property and not per-se
evidence of a server defect or a difference between models.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Amendment 7 documents that the n>=5 rule has been measuring the harness rather
than the model: with seed and temperature hardcoded to 42 and 0.0, decoding was
greedy, and all 45 runs of M6's nine quant arms produced not just identical
scores but identical sets of failing scenario ids. Greedy repetition answers
"is this measurement stable?"; only a seed-varied arm at non-zero temperature
answers "is this difference real?". A verdict asserting a difference between
arms now requires the latter.

Amendment 8 makes the decode-constraint disclosure a launch requirement and
records that mlx-lm sits with Ollama on the unconstrained side, verified by
source inspection of 0.31.3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M5 measured nine quant arms once each and read Qwen2.5-7B's 47/45/48 as the
lowest quant beating the highest, against folklore. Replication does not
support that. Re-running every arm five times reproduced the M5 numbers
exactly, but that was greedy decoding re-measuring itself, not confirmation.
Under five distinct seeds at temperature 0.7 the three 7B quants land at 46.8,
45.4 and 46.4 with fully overlapping ranges.

What survives is narrower and holds up: quantization level from Q8_0 down to
Q3_K_M does not predict tool-calling ability on this corpus, and the ordering
is not monotonic. Qwen2.5-1.5B puts Q4_K_M below the more aggressively
quantized Q3_K_M, and greedy decoding flattered that same Q4_K_M arm by about
five scenarios.

Llama-3.1-8B is excluded from the verdict with reason: every run lost 7-9
scenarios to llama.cpp returning HTTP 500 on the peg-native format, which is a
server error rather than a model failure and is not comparable across arms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The matrix page gains an MLX LM filter option and label, the decode-constraint
disclosure in prose so a reader meets it before drawing a cross-server
conclusion, the replication totals behind the current case studies, and an
excluded-rows block. The one exclusion today is Meta-Llama-3.1-8B-Instruct on
llama.cpp, dropped from the quantization conclusion because 7-9 scenarios per
run die on a server 500 rather than a model failure, with the case study
linked so the reason is checkable rather than asserted.

Adds the peg-native case study documenting that anomaly: the model emits
correct calls as bare JSON, llama.cpp's lazy grammar never triggers, and the
post-hoc PEG re-parse rejects them and escalates to HTTP 500. Reproduced
identically on b10050 and b10075 with a clean control arm, so it is not
version-bound. Recorded as a candidate upstream issue, not filed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Third server in the matrix, chosen to overlap the existing model set so the
column is comparable rather than a disjoint set of models. Qwen2.5-7B at 4bit
scores 46/50 and Qwen2.5-1.5B 41/50.

Llama-3.1-8B, Mistral-7B and Phi-4-mini all score exactly 7/50, passing only
the seven negative traps. That is not the models failing to call tools: mlx-lm
logs "Received tools but model does not support tool calling" on all 50
scenarios for each of them, so the tools never reach the model at all. It is a
gap in mlx-lm's tool-parser coverage, and it is what a whole-stack cell is
supposed to expose.

MLX consumes converted weights rather than GGUF, so these rows are not
bit-identical to the llama.cpp rows for the same model and the weights-control
trick used elsewhere does not apply across this boundary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qwen2.5-7B scores 46/50 at 4bit and 7/50 at 8bit on mlx-lm, five runs each,
identical every run. The 8bit conversion emits doubled braces in its tool
calls, so the JSON does not parse; mlx-lm rejects it correctly. The bytes were
recovered by generating through mlx_lm directly, with the 4bit conversion as
the control, before any claim was written. That step is what turned this from
"the server dropped a call" into "the model emitted malformed JSON", which is
the same correction this project had to make once before.

The server defect that remains is the reporting: on a parse failure mlx-lm
answers HTTP 200 with finish_reason tool_calls and a message carrying neither
content nor tool_calls, so a client cannot tell the difference between this and
silence. Same class as the Ollama silent-discard issue. Both recorded as
candidate upstream issues, neither filed.

The README gains the two caveats needed to add an mlx-lm row correctly: MLX
rows are converted weights rather than GGUF, so the one-blob-two-servers
control does not cross that boundary, and mlx-lm's /v1/models lists the entire
local cache rather than the loaded model, which will file a row under the wrong
name if used for discovery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13ce3bdb2b

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread README.md
Comment on lines +123 to +127
Each result records which side of this line its server sits on, in
`server.quirk_flags`: `grammar_constrained_decoding` for llama.cpp,
`unconstrained_post_hoc_parse` for Ollama and mlx-lm. LM Studio and vLLM are
unflagged because their decode path has not been verified here; absence of a
flag means unverified, not unconstrained.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Align quirk metadata before claiming every result records it

I checked the published results/*.json files, and the pre-existing Ollama and llama.cpp rows still have empty metadata.server.quirk_flags. With this new wording, those rows are documented as if an empty flag set means "unverified", so readers and downstream tooling will misclassify the existing Ollama/llama.cpp measurements unless the result files are backfilled or this statement is scoped to newly generated results.

Useful? React with 👍 / 👎.

@devYRPauli
devYRPauli merged commit d3c1a98 into main Jul 21, 2026
2 checks passed
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.

1 participant