Skip to content

Commit 6b5c0e6

Browse files
committed
fix(mlx): defer to upstream when gated_delta_update gets unknown kwargs
A newer mlx-lm adding parameters (precedent: the qwen3_5 attention kwargs that needed #721) would previously TypeError inside the patched wrapper. Silently dropping them could change training semantics, so unknown kwargs now delegate to the original implementation with a one-time warning naming the new arguments — old mlx-lm is unchanged, newer mlx-lm stays correct until the patch is updated.
1 parent 72f980b commit 6b5c0e6

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

tests/test_mlx_gated_delta_vjp.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,24 @@ def spy(*args, **kwargs):
286286
y, s = gd.gated_delta_update(q, k, v, a, b, A_log, dt_bias, state=None)
287287
mx.eval(y, s)
288288
assert called.get("kernel"), "training call did not route to kernel VJP"
289+
290+
291+
def test_unknown_kwargs_delegate_to_original(fake_mlx_lm, monkeypatch):
292+
"""A newer mlx-lm adding parameters must not crash or silently drop
293+
them: the patched function defers to the original implementation."""
294+
calls = {}
295+
296+
def newer_original(q, k, v, a, b, A_log, dt_bias,
297+
state=None, mask=None, use_kernel=True, new_flag=None):
298+
calls["kwargs"] = {"new_flag": new_flag}
299+
return "y", "state"
300+
301+
fake_mlx_lm.gated_delta.gated_delta_update = newer_original
302+
for module in fake_mlx_lm.consumers.values():
303+
module.gated_delta_update = newer_original
304+
_patch()
305+
306+
patched = fake_mlx_lm.gated_delta.gated_delta_update
307+
result = patched(*[object()] * 7, state=None, new_flag="x")
308+
assert result == ("y", "state")
309+
assert calls["kwargs"] == {"new_flag": "x"}

unsloth_zoo/gated_delta_vjp.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ def _chunked_vjp(primals, cotangents, outputs):
250250

251251

252252
_WARNED_FOREIGN_GATED_DELTA: set = set()
253+
_WARNED_UNKNOWN_GD_KWARGS: set = set()
253254

254255

255256
def patch_gated_delta():
@@ -269,8 +270,26 @@ def patch_gated_delta():
269270

270271
def patched_gated_delta_update(
271272
q, k, v, a, b, A_log, dt_bias,
272-
state=None, mask=None, use_kernel=True,
273+
state=None, mask=None, use_kernel=True, **kwargs,
273274
):
275+
if kwargs:
276+
# A newer mlx-lm added parameters this patch does not know
277+
# about. Silently dropping them could change semantics, so
278+
# defer to the original implementation (correct, unpatched)
279+
# and warn once that the patch needs updating.
280+
if not _WARNED_UNKNOWN_GD_KWARGS:
281+
_WARNED_UNKNOWN_GD_KWARGS.add(True)
282+
print(
283+
"Unsloth: WARNING — gated_delta_update received "
284+
f"unknown arguments {sorted(kwargs)} from a newer "
285+
"mlx-lm; falling back to the unpatched implementation "
286+
"(training will be slow — pass compile=False — until "
287+
"unsloth_zoo/gated_delta_vjp.py is updated)."
288+
)
289+
return original_gated_delta_update(
290+
q, k, v, a, b, A_log, dt_bias,
291+
state=state, mask=mask, use_kernel=use_kernel, **kwargs,
292+
)
274293
# Heuristic: training calls enter with state=None (no cache); inference
275294
# passes the cached state in. Route training through the efficient ops
276295
# so the custom VJP runs — gated_delta_kernel has none, so it would keep

0 commit comments

Comments
 (0)