Skip to content

LoRA/DoRA fast forward: no weight materialization (1.6-2.0x on MPS)#69

Open
Cortexelus wants to merge 1 commit into
mps-trainingfrom
fast-lora-forward
Open

LoRA/DoRA fast forward: no weight materialization (1.6-2.0x on MPS)#69
Cortexelus wants to merge 1 commit into
mps-trainingfrom
fast-lora-forward

Conversation

@Cortexelus

@Cortexelus Cortexelus commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

LoRA/DoRA training-forward reformulation — no full-weight materialization. Stacked on #68 (base branch mps-training; uses its disable_autocast helper — GitHub will retarget this PR to main when #68 merges). Speeds up LoRA training on every backend; measured on MPS: sm-music 0.668→1.053 it/s (1.58×), medium 0.254→0.501 it/s (1.97×), sm peak memory 3.67→2.58 GiB.

What

torch.nn.utils.parametrize rebuilds the full [out,in] adapted weight on every forward. For lora / dora-rows / dora-cols that's algebraically unnecessary — dora-rows is exactly a row-scale of the (base + low-rank) output with the norm in closed form:

y = (x@W0.T + s·(x@A.T)@B.T) ⊙ m/rownorm + bias
rownorm² = Σ_row W0²  (cached const)  + 2s·rowsum((W0@A.T)⊙B) + s²·rowsum((B@(A@A.T))⊙B)

enable_fast_lora_forward(model) installs an instance-attribute forward on each parametrized Linear — the parametrization registration and the parametrizations.weight.<i>.* checkpoint layout are untouched, so saves/loads/resume/set_lora_strength/remove_lora/merge_lora work unchanged. Applied unconditionally by add_lora — there is no toggle: the cached norm constant is a per-layer vector (no memory tradeoff; peak memory strictly drops), speed strictly improves, and the naive path remains in-tree for the fallback cases. Falls back to the standard path for: stacked parametrizations (ARC at lora_index=1), bora/-xs, fan_in_fan_out, disable_lora, trainable base, custom Linear subclasses; transparently no-ops after remove_lora/merge_lora.

Fidelity

The reference's in-graph norm (no DoRA-paper detach) is mirrored; strength multiplies inside the delta; weight-space dropout drawn once per forward and shared with the norm terms; dora-rows bias added un-scaled after the row scale; strength 0 is bit-exact vs the frozen base. 40 unit tests: fp32 output parity 1e-5, gradient parity ≤4.4e-7 rel (A/B/magnitude), autocast parity ~1e-3 (fp16 GEMM noise), stacking fallback bit-exact, state_dict key sets identical. Same-seed 30-step training runs deviate ≤1.2e-3 from the pre-change loss curves with identical RNG consumption; the fp32 controlled-parity forward matches the recorded loss exactly (3.98231220).

Caveat

Default-ON changes CUDA training numerics at the ~1e-3 autocast level (op reordering, same class as any fusion). Gradient-parity tests cover the math on CPU/MPS; one CUDA smoke would be welcome. Rollback path is reverting the PR (the change is confined to models/lora/model.py).

@Cortexelus
Cortexelus force-pushed the fast-lora-forward branch 3 times, most recently from 26654c8 to cb6842d Compare July 15, 2026 06:08
…-2.0x MPS training)

torch.nn.utils.parametrize materializes W' = f(W0) on every self.weight
access, so a plain forward pays an [out,in] low-rank matmul + [out,in] norm
(and several [out,in] autograd intermediates in backward) per layer per step.

Reformulate to compute the same y without ever building an [out,in] tensor
beyond reading W0: fold the low-rank update into the data GEMMs, and get the
DoRA row/col norm from the closed-form norm^2 expansion (a cached rowsum(W0^2)
constant plus two small rank-sized terms). Semantics mirror LoRAParametrization
exactly — norm stays in the autograd graph, same 1e-12 eps, dropout drawn once
and shared, strength==0 short-circuit, un-scaled bias — verified to gradient
parity (<=4.4e-7 fp32).

Installed unconditionally by add_lora as an instance-attribute forward, leaving
state_dict layout and parametrization registration untouched. Anything the fast
path can't reproduce exactly (stacked/multi-LoRA, bora/-xs, fan_in_fan_out,
disable_lora, trainable base, custom Linear subclass) falls back to the naive
materialized forward per module.
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