Skip to content

fix(mlxrunner): report a malformed cache limit, and pin the C call it makes - #188

Merged
glennneuber merged 1 commit into
mainfrom
fix/mlx-cache-limit-gaps
Aug 17, 2026
Merged

glennneuber merged 1 commit into
mainfrom
fix/mlx-cache-limit-gaps

Conversation

@glennneuber

Copy link
Copy Markdown

Two gaps from the review of the cache-limit work, which landed on main as #185. Both are live there now: configureCacheLimit still returns silently on a bad value, and memory_test.go has zero references to SetCacheLimit.

Originally pushed to #181's branch — that PR turns out to be orphaned (its base fix/mlx-memory-limit-overridable was deleted when #180 merged, and its content shipped via #185 instead), so this re-targets main where the code actually lives.

A malformed OLLAMA_MLX_CACHE_LIMIT was discarded in total silence

parseCacheLimit folds unset, unparseable and out-of-range into one false, and configureCacheLimit returned on all three — so a hand-typed 8GiB was indistinguishable from not setting the variable at all.

That is precisely the defect this feature family exists to remove. budgetWithOverride's own comment records that a sweep over 24/48/82 GiB produced three identical runs "because nothing in the output revealed that the request had been discarded", and parseCacheLimit's comment records being burned again by the same shape when cap-0 reported the default's footprint under the cap-0 label. The zero case was fixed; the silence that made it hard to see was not. The sibling knob configureMemoryLimit already warns, so the two disagreed within one file.

Exposure is higher here than for OLLAMA_MLX_MEMORY_LIMIT, which the parent machine-writes as a raw byte count. This one is hand-typed by an operator following a comment that tells them to set it, and nothing there says the value is decimal bytes — so 8GiB is the plausible thing to type, and it parses as nothing.

Unset stays silent. Only malformed warns, naming both the variable and the value that was read.

SetCacheLimit had no test at all

mlx_set_cache_limit and mlx_set_memory_limit have identical Go-side signatures, so swapping one for the other compiles and keeps every existing test green — while converting a cache bound into a cap on total allocation. That is exactly the confusion memory.go's doc comment is written to defend against, and it is undefendable by review alone.

A round-trip on SetCacheLimit cannot catch it: both C calls return the previous value and round-trip identically. Cross-checking that the MEMORY limit is untouched can, so that is what the test asserts.

Verified by mutation

mutation result
restore the silent discard TestMalformedCacheLimitIsReported fails
delete the limit < 0 guard TestSetCacheLimitRejectsNegative fails

Honest limitation

TestSetCacheLimitLeavesTheMemoryLimitAlone needs a real MLX library and skips without one, so it does not catch the swapped-C-call mutation in CI — only on a machine with MLX. The malformed-value and negative-guard tests return before any C call and run everywhere, including CI.

🤖 Generated with Claude Code

… makes

Two gaps from a review of this PR.

A malformed OLLAMA_MLX_CACHE_LIMIT was discarded in total silence.
parseCacheLimit folds unset, unparseable and out-of-range into one `false`, and
configureCacheLimit returned on all three -- so a hand-typed "8GiB" was
indistinguishable from not setting it at all. That is precisely the defect the
override work exists to remove: budgetWithOverride's own comment records that a
sweep over 24/48/82 GiB produced three identical runs "because nothing in the
output revealed that the request had been discarded", and this file's
parseCacheLimit comment records being burned again by the same shape when
cap-0 reported the DEFAULT's footprint. The zero case was fixed; the silence
that made it hard to see was not. The sibling knob already warns, so the two
also disagreed within one file.

Exposure is higher here than for OLLAMA_MLX_MEMORY_LIMIT, which the parent
machine-writes as a raw byte count. This one is hand-typed by an operator
following a comment that tells them to set it, and nothing there says the value
is decimal bytes -- so "8GiB" is the plausible thing to type and it parses as
nothing. Unset stays silent; only malformed warns, and the warning names both
the variable and the value that was read.

SetCacheLimit had no test at all. mlx_set_cache_limit and mlx_set_memory_limit
have identical Go-side signatures, so swapping one for the other compiles and
keeps every existing test green while converting a cache bound into a cap on
total allocation -- exactly the confusion memory.go's doc comment is written to
defend against, and undefendable by review alone. A round-trip cannot catch it
(both return the previous value); cross-checking that the MEMORY limit is
untouched can, so that is what the test asserts.

Verified by mutation: restoring the silent-discard fails
TestMalformedCacheLimitIsReported, and deleting the negative guard fails
TestSetCacheLimitRejectsNegative.

Honest limitation: the cross-check test needs a real MLX library and SKIPS
without one, so it does not catch the swapped-C-call mutation in CI -- only on a
machine with MLX. The malformed-value and negative-guard tests return before any
C call and run everywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@glennneuber
glennneuber merged commit 84bd68c into main Aug 17, 2026
19 of 21 checks passed
@glennneuber
glennneuber deleted the fix/mlx-cache-limit-gaps branch August 17, 2026 15:15
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