Spaces:
Running on Zero
Running on Zero
File size: 3,486 Bytes
36cdb93 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | """Minimal LoRA, used to make the DMD critic share the teacher's weights.
DMD needs three networks: the causal student, a frozen *real* score (the
original bidirectional Wan) and a trainable *fake* score that tracks the
student's own output distribution. Three full 1.4B copies plus the student's
AdamW state does not fit on a 40 GB card, and the fake score's correct
initialisation is exactly the real score anyway -- so the fake score is the same
frozen base with a low-rank adapter on top, toggled by a flag:
with lora_enabled(base, False): v_real = ... # teacher
with lora_enabled(base, True): v_fake = ... # critic
That costs ~32 M trainable parameters instead of 1.4 B, starts the critic at the
right place by construction, and leaves the base weights bit-identical between
the two roles.
"""
from contextlib import contextmanager
import torch
import torch.nn as nn
class LoRALinear(nn.Module):
def __init__(self, base: nn.Linear, rank=32, alpha=None):
super().__init__()
self.base = base
self.base.weight.requires_grad_(False)
if self.base.bias is not None:
self.base.bias.requires_grad_(False)
self.rank = rank
self.scale = (alpha or rank) / rank
self.a = nn.Parameter(torch.zeros(rank, base.in_features))
self.b = nn.Parameter(torch.zeros(base.out_features, rank))
nn.init.normal_(self.a, std=1.0 / rank) # b stays zero -> starts as identity
self.enabled = True
def forward(self, x):
y = self.base(x)
if not self.enabled:
return y
h = nn.functional.linear(x.to(self.a.dtype), self.a)
return y + nn.functional.linear(h, self.b).to(y.dtype) * self.scale
TARGETS = ('self_attn.q', 'self_attn.k', 'self_attn.v', 'self_attn.o',
'ffn.0', 'ffn.2')
def inject_lora(model, rank=32, alpha=None, targets=TARGETS):
"""Wrap the targeted Linears of every transformer block. Returns the new
parameters, and freezes everything else in the model."""
for p in model.parameters():
p.requires_grad_(False)
n = 0
for blk in model.blocks:
for name in targets:
parent, _, leaf = name.rpartition('.')
mod = blk.get_submodule(parent) if parent else blk
lin = getattr(mod, leaf) if not leaf.isdigit() else mod[int(leaf)]
if isinstance(lin, LoRALinear):
continue
wrapped = LoRALinear(lin, rank, alpha).to(lin.weight.device)
wrapped.a.data = wrapped.a.data.float()
wrapped.b.data = wrapped.b.data.float()
if leaf.isdigit():
mod[int(leaf)] = wrapped
else:
setattr(mod, leaf, wrapped)
n += 1
params = [p for p in model.parameters() if p.requires_grad]
total = sum(p.numel() for p in params)
return params, n, total
def set_lora(model, on):
for m in model.modules():
if isinstance(m, LoRALinear):
m.enabled = on
@contextmanager
def lora_enabled(model, on):
prev = [m.enabled for m in model.modules() if isinstance(m, LoRALinear)]
set_lora(model, on)
try:
yield
finally:
for m, p in zip((m for m in model.modules()
if isinstance(m, LoRALinear)), prev):
m.enabled = p
def lora_state_dict(model):
return {k: v for k, v in model.state_dict().items()
if k.endswith('.a') or k.endswith('.b')}
|