Faster loglikelihood scoring in mlx_lm.evaluate - #1609
Closed
markjouh wants to merge 1 commit into
Closed
Conversation
- Only compute the vocabulary projection for the last prompt position when the final prefill chunk is long; short prompts are unchanged. - Rewind the prompt cache between continuations instead of deep copying it when the cache is trimmable. - Fix a NameError when a completion is longer than the context limit.
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.
Two changes to the
loglikelihoodpath inmlx_lm.evaluate, plus a bug fix found along the way:Skip the vocabulary projection for the final prompt chunk in
_process_prompt. The last prefill chunk previously materialized logits for up tostep_sizepositions (a[1, 2048, vocab]tensor) when only the final position's log probabilities are used. When the final chunk is long, the prompt is now processed the waygenerate_stephandles it: everything before the last token is prefilled evaluating only the cache state (the logits are never computed, thanks to lazy evaluation), then the last token is processed by itself so the vocabulary projection runs for one position instead of up to 2048. For prompts whose final chunk is shorter than 512 tokens, the extra model call costs more than the projection it avoids (measured crossover is ~150–250 tokens on M-series), so those keep the previous single-call behavior and are bit-identical to main.Rewind the prompt cache between continuations instead of deep-copying it. When the cache is trimmable, all continuations of a question are scored with the same cache, using
trim_prompt_cacheto rewind in between. This is bit-exact — scoring reads identical cache contents either way — and saves a cache copy plus a buffer-growth reallocation per continuation. Models with non-trimmable caches (SSM states, sliding-window caches past the window) keep thecopy.deepcopybehavior.Fix a latent
NameErrorin the truncated-prompt branch (all_scores/all_is_greedy→scores/is_greedy), which crashed evaluation whenever a completion was longer than the context limit. Same shape as theloglikelihood_rollingfix in Fix NameError in loglikelihood_rolling method #339.Benchmarks
M5 Max (40-core GPU, 128 GB), mlx 0.32.0, macOS 15. Paired A/B — the two implementations alternate per round in fresh processes, N questions × 4 continuations, median over rounds:
The gain comes from dropping the
[chunk, vocab]projection, so it is largest when the vocabulary is a big share of the model (small models, large vocabs) and for prompts just under a chunk boundary. Peak memory is unchanged (the prefill working set dominates it).End-to-end
mlx_lm evaluate --tasks arc_challenge --num-shots 10 --limit 150(prompts long enough to take the new path): accuracy and normalized accuracy are unchanged for both models, wall time 13.1s → 12.6s (1B) and 27.0s → 26.6s (4B), which includes model load and harness overhead.Correctness
tests/test_evaluate.py: score agreement with a full-sequence forward pass, and continuation order invariance, over both short and long contexts. Both tests also pass onmain.