Source code for protomotions.agents.peft.adapters
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Conditioned LoRA/DoRA adapters for TransformerEncoder stacks.
This module is model-agnostic: it only knows how to wrap transformer layers,
freeze base weights, and expose adapter parameters for training.
"""
import torch
from torch import nn
# ---------------------------------------------------------------------------
# LoRA / DoRA layer
# ---------------------------------------------------------------------------
[docs]
class LoRALayer(nn.Module):
"""Low-rank adaptation with FiLM-style gating from a conditioning vector."""
[docs]
def __init__(
self,
c_dim: int,
in_dim: int,
out_dim: int,
rank: int,
alpha: float,
):
super().__init__()
self.A = nn.Parameter(torch.randn(in_dim, rank) / (in_dim ** 0.5))
self.B = nn.Parameter(torch.randn(rank, out_dim) * 1e-4)
self.scaling = alpha / rank
[docs]
def forward(self, x, c_mul, c_add):
xa = x @ self.A # (B, T, rank)
xa_gated = xa * c_mul.unsqueeze(1) # FiLM multiplicative
xa_gated = xa_gated + c_add.unsqueeze(1) # FiLM additive
return self.scaling * (xa_gated @ self.B) # (B, T, out_dim)
[docs]
class TransformerLayerWithLoRA(nn.Module):
[docs]
def __init__(self, transformer_layer, c_dim: int, rank: int, alpha: float):
super().__init__()
self.transformer_layer = transformer_layer
d_model = transformer_layer.self_attn.embed_dim
self.lora = LoRALayer(c_dim, d_model, d_model, rank, alpha)
self.gamma = nn.Linear(c_dim, rank)
self.beta = nn.Linear(c_dim, rank)
nn.init.zeros_(self.gamma.weight)
nn.init.ones_(self.gamma.bias)
nn.init.zeros_(self.beta.weight)
nn.init.zeros_(self.beta.bias)
[docs]
def forward(self, c, x, **kwargs):
c_mul = self.gamma(c)
c_add = self.beta(c)
return self.transformer_layer(x, **kwargs) + self.lora(x, c_mul, c_add)
[docs]
class TransformerLayerWithDoRA(nn.Module):
[docs]
def __init__(self, transformer_layer, c_dim: int, rank: int, alpha: float):
super().__init__()
self.transformer_layer = transformer_layer
d_model = transformer_layer.self_attn.embed_dim
self.lora = LoRALayer(c_dim, d_model, d_model, rank, alpha)
self.m = nn.Parameter(torch.empty(1, d_model).uniform_(-1e-5, 1e-5))
self.gamma = nn.Linear(c_dim, rank)
self.beta = nn.Linear(c_dim, rank)
nn.init.zeros_(self.gamma.weight)
nn.init.ones_(self.gamma.bias)
nn.init.zeros_(self.beta.weight)
nn.init.zeros_(self.beta.bias)
[docs]
def forward(self, c, x, **kwargs):
transformer_output = self.transformer_layer(x, **kwargs)
c_mul = self.gamma(c)
c_add = self.beta(c)
lora_output = self.lora(x, c_mul, c_add)
lora_output_norm = lora_output / (
lora_output.norm(p=2, dim=-1, keepdim=True) + 1e-6
)
return transformer_output + self.m * lora_output_norm
# ---------------------------------------------------------------------------
# Conditioning-aware transformer encoder
# ---------------------------------------------------------------------------
[docs]
class TransformerEncoderWithConditioning(nn.Module):
"""Passes task conditioning to PEFT layers, standard forward to others."""
[docs]
def forward(self, x, task_c=None, mask=None, src_key_padding_mask=None):
for layer in self.layers:
if hasattr(layer, "lora"):
x = layer(
task_c,
x,
src_mask=mask,
src_key_padding_mask=src_key_padding_mask,
)
else:
x = layer(x, src_mask=mask, src_key_padding_mask=src_key_padding_mask)
return x
[docs]
def freeze_base_and_enable_peft(module: nn.Module) -> None:
"""Freeze base weights and leave adapter/conditioning parameters trainable."""
for parameter in module.parameters():
parameter.requires_grad = False
for submodule in module.modules():
if hasattr(submodule, "lora"):
for parameter in submodule.lora.parameters():
parameter.requires_grad = True
for parameter in submodule.gamma.parameters():
parameter.requires_grad = True
for parameter in submodule.beta.parameters():
parameter.requires_grad = True
if hasattr(submodule, "m"):
submodule.m.requires_grad = True
if hasattr(submodule, "transformer_layer"):
submodule.transformer_layer.eval()
[docs]
def set_peft_layers_train_mode(module: nn.Module, mode: bool) -> None:
"""Set PEFT wrappers to train/eval while keeping wrapped base layers eval."""
for submodule in module.modules():
if not hasattr(submodule, "lora"):
continue
submodule.train(mode)
if hasattr(submodule, "transformer_layer"):
submodule.transformer_layer.eval()
[docs]
def inject_transformer_peft(
transformer: nn.TransformerEncoder,
conditioning_dim: int,
rank: int,
alpha: float,
peft_type: str,
) -> TransformerEncoderWithConditioning:
"""Wrap TransformerEncoder layers with conditioned LoRA/DoRA adapters."""
layer_cls = (
TransformerLayerWithDoRA
if peft_type == "dora"
else TransformerLayerWithLoRA
)
for index, layer in enumerate(transformer.layers):
peft_layer = layer_cls(layer, conditioning_dim, rank, alpha)
peft_layer = peft_layer.to(next(layer.parameters()).device)
transformer.layers[index] = peft_layer
wrapped = TransformerEncoderWithConditioning(transformer.layers)
freeze_base_and_enable_peft(wrapped)
return wrapped