Repository navigation
test(peft): assert grad_fn on NF4 and add dtype to fast-LoRA kernel pin - #1173
Conversation
MakazhanAlpamys
left a comment
There was a problem hiding this comment.
Review of 5b6b180b.
What holds, verified by execution. Your commit is exactly what #1163 asked for, and both of the issue's mutations now die. I ran the file on a card so that both gpu tests execute, on main plus your change:
mutation at fast_lora.py:278 |
on main |
with this PR (card) | with this PR (CPU, what CI sees) |
|---|---|---|---|
| none | 27 passed | 31 passed | 29 passed, 2 skipped |
if x.dtype.itemsize != 4: return original_forward(x) |
27 passed (survives) | 5 failed (4 bf16 arms + NF4) | 4 failed (the bf16 arms) |
delegate when the base weight has a quant_state |
27 passed (survives) | 1 failed: :861, "grad_fn is AddBackward0" |
29 passed, 2 skipped |
The NF4 vacuity precondition is in place (:844), both #968 citations now point at #776, nothing was deleted or loosened (the 8 original fp32 IDs still collect), no new skip, and ruff is clean. A test-only change needs no fragment. Your body reports only the dtype mutation. The NF4 one needs a card, so the second row above is my measurement.
Blocking
-
The branch conflicts with
main. It still carries #1087's four pre-squash commits, andmainhas the squash (7b78e528), so the test file is an add/add conflict. GitHub reportsCONFLICTINGandmerge-freshnessfails. The 13 required checks are green, but they ran on a merge ref from before #1087 landed. Fix:git rebase --onto origin/main 1d62c347. I simulated it: it applies cleanly as 1 file, +18/-13, and it is identical to the tree I tested. CI then runs on the new head. -
The dtype arm never builds the dtype layout training actually uses, so a narrowing of the same shape still survives, even on a card.
get_peft_modeldefaults toautocast_adapter_dtype=True, which leaves fp32 adapters on a bf16 base. I measured that on a dense bf16 base and on an NF4 base throughget_peft_model:lora_Acomes back float32 on both. So every real bf16 LoRA and QLoRA run takes the kernel's mixed-dtype branch. The bf16 arm casts the whole wrapper withmodel.to(bf16)(:976), and the NF4 fixture usesinject_adapter_in_model, which gives bf16 adapters. Both are uniform-dtype. Results with your file:if lora_a.dtype != x.dtype: return original_forward(x)gives 31 passed on the card.if x.dtype == torch.float16: return original_forward(x)also gives 31 passed. fp16 is the pre-Ampere training dtype.
Without the mutation, the kernel does run in both layouts (
_FastLoraSingleProjectionBackwardon CPU, and for mixed on NF4 too). So the pin can stay on CPU:@pytest.mark.parametrize("dtype", ["fp32", "bf16", "fp16", "mixed"], ids=["fp32", "bf16", "fp16", "mixed"]) ... torch_dtype = {"fp32": torch.float32, "bf16": torch.bfloat16, "fp16": torch.float16, "mixed": torch.bfloat16}[dtype] model, layer = _make_adapted_linear(bias=bias) model.to(torch_dtype) if dtype == "mixed": # what get_peft_model leaves by default: fp32 adapters on a bf16 base for name, param in model.named_parameters(): if "lora_" in name: param.data = param.data.float() x = torch.randn(*shape, dtype=torch_dtype, requires_grad=True)
I ran exactly this on CPU: 37 passed and 2 skipped with no mutation. The mixed-dtype mutation fails 4 tests (the
mixedarms) and the fp16 one fails 4 (thefp16arms). I know this goes past the issue's literal text. It is still the same pattern the issue describes, one round later, and mixed dtype is the configuration this kernel exists for.
Non-blocking
- The NF4 kill only happens on a card.
TestNf4Parityis correctlygpu-marked, so under the quant-state mutation every CI cell stays green (29 passed, 2 skipped). A CPU twin is possible: bitsandbytes 0.50.2 quantizes NF4 on CPU, and the patched forward runs there (AddBackward0unpatched, the kernel's node patched, backward fine). CI's test matrix installs.[dev], which pulls bitsandbytes. Guard it withimportorskip, plus a skip when CPU quantization raises on an older bitsandbytes. Optional, since the issue put the assertion in the gpu test, but it would let CI catch that mutation. - Please keep the provenance paragraph. The docstring paragraph you replaced (old
:957-965) was the file's only record of the three survivors at:278and of why the o_proj shape (3-D, no bias) matters. Keep it, and add the rows this PR adds, with their counts.
Nits
- The module docstring's
gpubullet for NF4 still says "parity" only; it now also pins that the kernel ran. The "Asserted on CPU" list doesn't mention the dtype arms. :975-977:nn.Module.toworks in place, so both reassignments are no-ops.
|
Confirming blocking item 1, and pinning the exact rebase so it is a copy-paste rather than a re-derivation. The conflict is mine. #1087 merged as a squash ( I ran the rebase you proposed, on a scratch branch, with nothing pushed: git fetch upstream
git rebase --onto upstream/main 1d62c347It applies cleanly as one commit and the result is byte-identical to what you tested: One correction to the issue body while you are here: For what it is worth, the failure mode is the one the kernel is about: a green suite that cannot see the difference. The 13 required checks were green on a merge ref from before #1087 landed, so they agreed with the old tree. Same shape as the delegation problem, one level up. Blocking item 2 is yours to decide, and I have no stake in it either way. Mixed dtype being the configuration |
5b6b180 to
aa39479
Compare
…in (MakazhanAlpamys#1163) TestTheFastPathIsActuallyTaken previously pinned the fast-LoRA single projection kernel over bias x rank in fp32, but missed non-fp32 dtypes and 4-bit base weights. Neither agreement parity nor gradcheck can distinguish kernel execution from silent delegation. 1. TestTheFastPathIsActuallyTaken: - Parametrised test_the_output_carries_the_kernels_own_grad_fn over dtype (fp32, bf16, fp16, mixed), including real-world mixed dtype (fp32 adapters on bf16 base as left by get_peft_model with autocast_adapter_dtype=True). - Confirmed mutations delegating on non-fp32 or mixed dtypes fail with AddBackward0. 2. TestNf4Parity: - Added unpatched vacuity precondition assertion on ref.grad_fn. - Asserted type(out.grad_fn).__name__ == "_FastLoraSingleProjectionBackward". 3. Citation cleanup: - Replaced closed issue citation MakazhanAlpamys#968 with root-cause issue MakazhanAlpamys#776. Closes MakazhanAlpamys#1163
|
Thank you @MakazhanAlpamys and @sloemo01! The branch is cleanly rebased onto
Ready for merge! |
MakazhanAlpamys
left a comment
There was a problem hiding this comment.
Review of aa39479e.
Thank you for the rebase and for taking the mixed/fp16 arms as written. Both blocking items from my last review are closed, and I checked each by execution.
1. The conflict is gone. The branch is now a single commit on main. It merges cleanly onto today's main (1c053a8c), and the only file that differs is this test file. GitHub now reports it clean.
2. The narrowings that survived last round now die. I ran the file on the merged tree, both CPU-only (what CI sees) and with the card visible, so both gpu tests execute. Each mutation is inserted at fast_lora.py:278:
| mutation | main's file (card) |
this PR, CPU | this PR, card |
|---|---|---|---|
| none | 27 passed | 37 passed, 2 skipped | 39 passed |
if x.dtype.itemsize != 4: return original_forward(x) |
27 passed | 12 failed | 13 failed |
delegate when the base weight has a quant_state |
27 passed | 37 passed, 2 skipped | 1 failed (NF4 grad_fn) |
if lora_a.dtype != x.dtype: return original_forward(x) |
27 passed | 4 failed (the mixed arms) |
4 failed |
| delegate on fp16 | 27 passed | 4 failed (the fp16 arms) |
4 failed |
| delegate on bf16 | — | 8 failed | 9 failed |
The mixed-dtype and fp16 rows are the two that gave 31/31 at 5b6b180b.
Other checks:
- Nothing was deleted or loosened. Collection goes from 27 to 39, and the four original IDs survive as the
fp32arm. There is no new skip. - Ruff is clean, and the file is format-clean.
- The provenance paragraph is back, and both docstring nits are fixed.
- This is test-only, so no fragment is needed.
- CI is 13/13 required checks green on this head. That merge ref predates three later merges to
main, but none of them touchfast_lora.pyor this file. My local merge gives the numbers above.
This does not depend on the two SwiGLU/QKV drafts. When this lands, their copy of the #1163 hunk has to go; I'll take that up on those PRs.
Non-blocking
- The quant-state row still dies only on a card, so every CI cell stays green under it. A CPU twin (bitsandbytes 0.50.2 quantizes NF4 on CPU) would let CI see it. That's fine as a follow-up; the issue put the assertion in the gpu test, and you did exactly that.
- The sentence you appended to the provenance paragraph has no counts, while the paragraph above it records counts for the earlier rounds. Something like: "itemsize: 12 CPU / 13 card; quant_state: 1, card only; mixed: 4; fp16: 4".
Nit
- The PR body still describes the previous head: fp32/bf16 only, "built on top of PR #1087 (
1d62c347)", and 29 passed. Please refresh it before the squash.
Approving.
|
Merged as Both mutations that survived my last round (mixed dtype and fp16) now fail four tests each, on CPU, which is where CI can see them. All six mutations I ran were killed with the card visible, and the file merged cleanly onto current |
…s missed #1088/#1092, and the 09-23/24 queue (#1148, #1118, #1087, #1096, #1098, #1092, #1088, #1150, #1031, #1111, #1173, #1167, #1160, #1061, #1159, #1134, #1120, #1175, #1176, #1183) Eleven people, in merge order: - #1148 @Srinivasan8888: moe_lora on six trainers - #1118 @Guts1005 (first contribution): the Windows Hub id - #1087 @sloemo01 (first contribution): the fast-LoRA kernel - #1096 @Srinivasan8888: the preprocess BOS - #1098 @taljeon: the page-lock recovery - #1092 @kok-o (2026-09-22, missed until now): the ANSI guard reaches CLI-output assertions - #1088 @kok-o (2026-09-22, missed until now): MLX monitoring flags refused - #1150 @xiechimon (first contribution): the MCP run-log symlink - #1031 @dchaudhari7177: the streamed adapter that reloads as nothing - #1111 @Amix29: mini_tool_call off the ceiling - #1173 @kok-o: the fast-LoRA NF4 pin - #1167 @Srinivasan8888: the #1101/#1102 review nits - #1160 @Guts1005: the pretrain LoRA target resolver pinned - #1061 @dchaudhari7177: the untied streamed head - #1159 @Shutaru: the QuEST dry-run assertion and the BF16 sentence - #1134 @Guts1005: the gpu marker counts visible devices - #1120 @AaronProbha18: labels in the preprocess cache - #1175 @Blizzeq: an unregistered chat_template exits cleanly - #1176 @Shutaru: the portable QuEST base identity - #1183 @Srinivasan8888: the loader reports the rows it drops #1092 sits before #1088 because it merged first (08:36:24Z against 08:38:24Z). Display names are from `gh api users/<login> --jq .name`; @Shutaru has none, so the entry keeps the login. Also restores four escape sequences that had landed in the file as raw control bytes: `\boxed {A}` in the #396 entry (0x08, from d8cfebc), the trailing-`\b` / textual `\b` pair in the #1081 entry (0x08, from 3b37ed1), and `"\x1b" not in result.output` in the #993 entry (0x1B, from 5ee1278). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolves #1163.
Summary
Follow-up to #839 / PR #1087 (by @sloemo01).
TestTheFastPathIsActuallyTakenintests/test_issue839_fast_lora_single_projection.pypinned that the fast-LoRA kernel actually runs over bias x rank in fp32, but missed non-fp32 dtypes and 4-bit base weights.Neither agreement parity nor gradcheck can distinguish kernel execution from silent delegation. This PR widens the pin to assert on
grad_fnacross dtypes and on NF4.What it does
Dtype Parameterization in
TestTheFastPathIsActuallyTaken:test_the_output_carries_the_kernels_own_grad_fnoverdtype(fp32,bf16).if x.dtype.itemsize != 4: return original_forward(x)fails 4 tests (all bf16 variants) withAssertionError: the kernel did not run: grad_fn is AddBackward0.NF4
grad_fnAssertion:ref.grad_fn:assert type(ref.grad_fn).__name__ != "_FastLoraSingleProjectionBackward".type(out.grad_fn).__name__ == "_FastLoraSingleProjectionBackward"after patching.Citation Cleanup:
#968with root-cause issue#776.Note on base
This branch is built on top of PR #1087 (
1d62c347). Once #1087 merges or is lifted into #837/#838, this commit can be integrated cleanly.Verification
pytest --no-cov tests/test_issue839_fast_lora_single_projection.py: 29 passed, 2 skipped (gpu) in 14.0sruff check tests/test_issue839_fast_lora_single_projection.py: 100% clean