@@ -300,12 +300,10 @@ def forward(self, x):
300300 use_memory_efficient_lora = self ._should_use_memory_efficient_lora (x )
301301 if use_memory_efficient_lora :
302302 if self .dropout_position == "pre" or not self .training or self .dropout_p == 0.0 :
303- return LoRATritonFunction . apply (
304- x , self .lora_A .weight , self .lora_B .weight , self .scale , x . dtype , False , res
303+ return apply_memory_efficient_lora (
304+ x , self .lora_A .weight , self .lora_B .weight , self .scale , False , res
305305 )
306- lora_res = LoRATritonFunction .apply (
307- x , self .lora_A .weight , self .lora_B .weight , self .scale , x .dtype , False
308- )
306+ lora_res = apply_memory_efficient_lora (x , self .lora_A .weight , self .lora_B .weight , self .scale , False )
309307 else :
310308 lora_res = self .lora_B (self .lora_A (x ) * self .scale )
311309 if self .dropout_position == "post" :
@@ -393,10 +391,8 @@ def forward(self, x):
393391 x = F .dropout (x , p = self .dropout_p , training = self .training )
394392 if self .use_memory_efficient_lora :
395393 if self .dropout_position == "pre" or not self .training or self .dropout_p == 0.0 :
396- return LoRATritonFunction .apply (
397- x , self .lora_A .weight , self .lora_B .weight , self .scale , x .dtype , True , res
398- )
399- lora_res = LoRATritonFunction .apply (x , self .lora_A .weight , self .lora_B .weight , self .scale , x .dtype , True )
394+ return apply_memory_efficient_lora (x , self .lora_A .weight , self .lora_B .weight , self .scale , True , res )
395+ lora_res = apply_memory_efficient_lora (x , self .lora_A .weight , self .lora_B .weight , self .scale , True )
400396 else :
401397 lora_res = self .lora_B (self .lora_A (x ) * self .scale )
402398 if self .dropout_position == "post" :
@@ -704,11 +700,14 @@ def forward(x, lora_A, lora_B, scale, dtype, use_triton_kernel=True, res=None):
704700
705701 Reshapes 3D tensors into 2D and then calls either Triton kernels or PyTorch matmuls. When ``res`` is
706702 provided, the residual is added in-place into the LoRA output to avoid allocating a separate add result.
703+
704+ Always returns a **2D** tensor; the caller restores the original leading dimensions. Keeping the
705+ ``(N, out) -> (bs, seq, out)`` reshape *outside* this ``autograd.Function`` means the Function's output
706+ is never a view, so a downstream consumer may safely mutate the LoRA output in place (the reshape done
707+ by the caller is an ordinary autograd view, which supports in-place ops).
707708 """
708- reshape = x .dim () == 3
709- if reshape :
710- bs , seq_len , d = x .shape
711- x = x .reshape (- 1 , d )
709+ if x .dim () == 3 :
710+ x = x .reshape (- 1 , x .shape [- 1 ])
712711 if res is not None :
713712 res = res .reshape (- 1 , res .shape [- 1 ])
714713
@@ -720,8 +719,6 @@ def forward(x, lora_A, lora_B, scale, dtype, use_triton_kernel=True, res=None):
720719 if res is not None :
721720 lora_res .add_ (res )
722721
723- if reshape :
724- return lora_res .view (bs , seq_len , - 1 )
725722 return lora_res
726723
727724 @staticmethod
@@ -734,11 +731,16 @@ def backward(ctx, d_y):
734731 """
735732 x , lora_A , lora_B = ctx .saved_tensors
736733 scale = ctx .scale
737- d_res = d_y if ctx .has_residual and ctx .needs_input_grad [6 ] else None
738734
739735 reshape = x .dim () == 3
740736 if reshape :
741737 bs , seq_len , d = x .shape
738+ # forward now returns a 2D output, so d_y arrives 2D; the residual input kept its original
739+ # (possibly 3D) shape, so its gradient must be reshaped back to match that input.
740+ d_res = None
741+ if ctx .has_residual and ctx .needs_input_grad [6 ]:
742+ d_res = d_y .reshape (bs , seq_len , - 1 ) if reshape else d_y
743+ if reshape :
742744 d_y = d_y .reshape (- 1 , d_y .shape [- 1 ])
743745 x = x .reshape (- 1 , d )
744746
@@ -770,3 +772,17 @@ def backward(ctx, d_y):
770772 if ctx .num_inputs == 6 :
771773 return gradients + (None ,)
772774 return gradients
775+
776+
777+ def apply_memory_efficient_lora (x , lora_A , lora_B , scale , use_triton_kernel , res = None ):
778+ """Run :class:`LoRATritonFunction` and restore the input's leading dimensions.
779+
780+ ``LoRATritonFunction.forward`` returns a 2D tensor (its reshape is intentionally kept outside the
781+ autograd Function so the output is never a view). Reshape back to the input rank here; the result
782+ is an ordinary autograd view, which — unlike a custom-Function output view — a downstream consumer
783+ may mutate in place (e.g. transformers' gemma3n ``project_per_layer_inputs``).
784+ """
785+ out = LoRATritonFunction .apply (x , lora_A , lora_B , scale , x .dtype , use_triton_kernel , res )
786+ if x .dim () == 3 :
787+ out = out .reshape (* x .shape [:- 1 ], - 1 )
788+ return out
0 commit comments