Skip to content

Commit 913ada6

Browse files
authored
Add LoRA-IO support to LoRA linear and other needed OSS components (pytorch#19953)
Differential Revision: D107096617 Pull Request resolved: pytorch#19953
1 parent 6a3a3e2 commit 913ada6

4 files changed

Lines changed: 61 additions & 14 deletions

File tree

‎examples/models/llama/feed_forward.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,16 @@ def __init__(self, dim: int, hidden_dim: int, args: ModelArgs):
6464
else nn.Linear(dim, hidden_dim, bias=False)
6565
)
6666

67-
def forward(self, x):
68-
return self.w2(F.silu(self.w1(x)) * self.w3(x))
67+
def forward(self, x, lora_blob=None):
68+
# CoreML LoRA-as-IO Path-2: when `lora_blob` is provided, route per-
69+
# projection slices to LoRALinear instances tagged with `_lora_key`.
70+
# Default behavior (lora_blob=None) is unchanged.
71+
def _call(linear, x_in):
72+
if lora_blob is not None:
73+
key = getattr(linear, "_lora_key", None)
74+
if key is not None and key in lora_blob:
75+
a, b = lora_blob[key]
76+
return linear(x_in, a, b)
77+
return linear(x_in)
78+
79+
return _call(self.w2, F.silu(_call(self.w1, x)) * _call(self.w3, x))

‎examples/models/llama/llama_transformer.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,13 @@ def forward(self, x, freqs_cos, freqs_sin, attn_options: ForwardOptions): # x:
239239
else:
240240
out = h + ffn_out
241241
else:
242-
ffn_out = self.feed_forward(self.ffn_norm(h))
242+
if isinstance(self.feed_forward, LoRAFeedForward):
243+
ffn_out = self.feed_forward(
244+
self.ffn_norm(h),
245+
lora_blob=attn_options.get("__lora_io_blob__"),
246+
)
247+
else:
248+
ffn_out = self.feed_forward(self.ffn_norm(h))
243249
if hasattr(self, "post_ffn_norm"):
244250
ffn_out = self.post_ffn_norm(ffn_out)
245251
if self.use_residual_gate:

‎examples/models/llama/lora.py‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7+
from typing import Optional
8+
79
import torch
10+
import torch.nn.functional as F
811
from torch import nn
912

1013

@@ -49,9 +52,20 @@ def _load_from_state_dict(self, state_dict, prefix, *args, **kwargs):
4952
state_dict[new_key] = state_dict.pop(old_key)
5053
super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
5154

52-
def forward(self, x: torch.Tensor) -> torch.Tensor:
55+
def forward(
56+
self,
57+
x: torch.Tensor,
58+
# Optional forward-arg LoRA tensors (CoreML LoRA-as-IO Path 2). When
59+
# both are provided, they override the stored lora_a/lora_b for this
60+
# call. Default behavior (None, None) is unchanged.
61+
lora_a: Optional[torch.Tensor] = None,
62+
lora_b: Optional[torch.Tensor] = None,
63+
) -> torch.Tensor:
5364
out = self.linear(x)
54-
lora_out = self.lora_a(self.dropout(x))
55-
lora_out = (self.alpha / self.rank) * self.lora_b(lora_out)
56-
57-
return out + lora_out
65+
if lora_a is not None and lora_b is not None:
66+
z = F.linear(self.dropout(x), lora_a)
67+
z = (self.alpha / self.rank) * F.linear(z, lora_b)
68+
else:
69+
z = self.lora_a(self.dropout(x))
70+
z = (self.alpha / self.rank) * self.lora_b(z)
71+
return out + z

‎examples/models/llama/static_attention.py‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,14 @@ def from_attention_mha(
10141014

10151015
return instance
10161016

1017+
def _lora_call(self, linear, x_in, lora_blob):
1018+
if lora_blob is not None:
1019+
key = getattr(linear, "_lora_key", None)
1020+
if key is not None and key in lora_blob:
1021+
a, b = lora_blob[key]
1022+
return linear(x_in, a, b)
1023+
return linear(x_in)
1024+
10171025
def forward(
10181026
self,
10191027
x: torch.Tensor,
@@ -1030,7 +1038,13 @@ def forward(
10301038
if self.use_conv2d:
10311039
x = x.reshape(bsz, -1, 1, dim).transpose(1, 3)
10321040

1033-
new_qs = [wq(x) for wq in self.wqs]
1041+
# CoreML LoRA-as-IO Path-2: when an upstream wrapper has stashed
1042+
# a per-key LoRA blob in attn_options, route per-projection slices
1043+
# to LoRALinear instances that have been tagged with `_lora_key`.
1044+
# Default behavior (no blob, or no `_lora_key`) is unchanged.
1045+
_lora_blob = kwargs.get("__lora_io_blob__")
1046+
1047+
new_qs = [self._lora_call(wq, x, _lora_blob) for wq in self.wqs]
10341048

10351049
shared_kv = kwargs.get("shared_kv")
10361050
if shared_kv is not None:
@@ -1040,8 +1054,8 @@ def forward(
10401054
new_ks = []
10411055
new_vs = []
10421056
else:
1043-
new_ks = [wk(x) for wk in self.wks]
1044-
new_vs = [wv(x) for wv in self.wvs]
1057+
new_ks = [self._lora_call(wk, x, _lora_blob) for wk in self.wks]
1058+
new_vs = [self._lora_call(wv, x, _lora_blob) for wv in self.wvs]
10451059

10461060
if self.use_conv2d:
10471061

@@ -1078,14 +1092,16 @@ def from_conv2ds(ts):
10781092

10791093
if self.use_conv2d:
10801094
y = (
1081-
self.wo(
1082-
y.reshape(bsz, -1, 1, self.n_heads * self.head_dim).transpose(1, 3)
1095+
self._lora_call(
1096+
self.wo,
1097+
y.reshape(bsz, -1, 1, self.n_heads * self.head_dim).transpose(1, 3),
1098+
_lora_blob,
10831099
)
10841100
.transpose(1, 3)
10851101
.reshape(bsz, -1, self.dim)
10861102
)
10871103
else:
1088-
y = self.wo(y)
1104+
y = self._lora_call(self.wo, y, _lora_blob)
10891105

10901106
update = {"out_cache_state": out_cache_state}
10911107
if kv_to_share is not None:

0 commit comments

Comments
 (0)