mirror of
https://github.com/SakanaAI/doc-to-lora.git
synced 2026-07-23 17:01:04 +02:00
187 lines
6.1 KiB
Python
187 lines
6.1 KiB
Python
import logging
|
|
from operator import attrgetter
|
|
from typing import Callable, Iterable, Optional
|
|
|
|
import torch
|
|
import torch.nn.functional as F
|
|
from einops import einsum
|
|
from jaxtyping import Float
|
|
from torch import Tensor
|
|
from torch.utils.hooks import RemovableHandle
|
|
from utils import get_layers
|
|
|
|
logger = logging.getLogger()
|
|
|
|
|
|
def remove_hook_handles(handles: list[RemovableHandle]) -> None:
|
|
"""
|
|
Removes all hook handles to detach them from the model.
|
|
|
|
Args:
|
|
handles (list[RemovableHandle]): A list of hook handles to be removed.
|
|
"""
|
|
for handle in handles:
|
|
if handle is not None:
|
|
handle.remove()
|
|
|
|
|
|
def apply_hook_to_model(
|
|
model: torch.nn.Module,
|
|
module_name: str,
|
|
pre_hook: Optional[Callable] = None,
|
|
post_hook: Optional[Callable] = None,
|
|
) -> torch.nn.Module:
|
|
if not ((pre_hook is not None) or (post_hook is not None)):
|
|
raise ValueError("No hooks provided. Nothing to apply.")
|
|
|
|
module = attrgetter(module_name)(model)
|
|
|
|
if pre_hook is not None:
|
|
pre_hook_handle = module.register_forward_pre_hook(pre_hook)
|
|
if post_hook is not None:
|
|
post_hook_handle = module.register_forward_hook(post_hook)
|
|
return pre_hook_handle, post_hook_handle
|
|
|
|
|
|
def apply_hook_to_layer(
|
|
layer: torch.nn.Module,
|
|
mname: str,
|
|
pre_hook: Optional[Callable] = None,
|
|
post_hook: Optional[Callable] = None,
|
|
) -> tuple[Optional[RemovableHandle], Optional[RemovableHandle]]:
|
|
"""
|
|
Applies pre and/or post hooks to a specific layer in the model.
|
|
|
|
Args:
|
|
layer (torch.nn.Module): The layer to which hooks will be applied.
|
|
mname (str): The name of the module within the layer.
|
|
pre_hook (Optional[Callable], optional): Function to be called before the forward pass.
|
|
post_hook (Optional[Callable], optional): Function to be called after the forward pass.
|
|
|
|
Returns:
|
|
Tuple[Optional[RemovableHandle], Optional[RemovableHandle]]:
|
|
Handles for the pre and post hooks.
|
|
"""
|
|
if not ((pre_hook is not None) or (post_hook is not None)):
|
|
raise ValueError("No hooks provided. Nothing to apply.")
|
|
|
|
pre_hook_handle = None
|
|
post_hook_handle = None
|
|
|
|
if mname in ["q_proj", "k_proj", "v_proj", "o_proj", "qkv_proj"]:
|
|
mname = f"self_attn.{mname}"
|
|
elif mname in ["down_proj", "up_proj", "gate_proj"]:
|
|
mname = f"mlp.{mname}"
|
|
module = attrgetter(mname)(layer)
|
|
|
|
if pre_hook is not None:
|
|
pre_hook_handle = module.register_forward_pre_hook(pre_hook)
|
|
if post_hook is not None:
|
|
post_hook_handle = module.register_forward_hook(post_hook)
|
|
return pre_hook_handle, post_hook_handle
|
|
|
|
|
|
def apply_hook_to_layers(
|
|
model: torch.nn.Module,
|
|
module_names: list[str],
|
|
layer_indices: Iterable[int],
|
|
pre_hook: Optional[Callable] = None,
|
|
post_hook: Optional[Callable] = None,
|
|
) -> list[RemovableHandle]:
|
|
"""
|
|
Applies custom hooks to specified layers and modules in the model.
|
|
|
|
Args:
|
|
model (torch.nn.Module): The model to which hooks will be applied.
|
|
module_names (list[str]): Names of the modules to hook.
|
|
layer_indices (Iterable[int]): Indices of the layers to hook.
|
|
pre_hook (Optional[Callable], optional): Function to be called before the forward pass.
|
|
post_hook (Optional[Callable], optional): Function to be called after the forward pass.
|
|
|
|
Returns:
|
|
list[RemovableHandle]: A list of hook handles.
|
|
"""
|
|
|
|
layers = get_layers(model)
|
|
out_handles = []
|
|
c = 0
|
|
for layer_idx in layer_indices:
|
|
layer = layers[layer_idx]
|
|
for mname in module_names:
|
|
handles = apply_hook_to_layer(
|
|
layer,
|
|
mname,
|
|
pre_hook=pre_hook,
|
|
post_hook=post_hook,
|
|
)
|
|
out_handles += handles
|
|
if handles[0] is not None or handles[1] is not None:
|
|
c += 1
|
|
if c == 0:
|
|
raise ValueError(
|
|
"No forward hooks applied. Something might be wrong. "
|
|
"Check if the module names are correct."
|
|
)
|
|
return out_handles
|
|
|
|
|
|
def add_generated_lora_hook(
|
|
model: torch.nn.Module,
|
|
module_name: str,
|
|
layer_index: int,
|
|
A: Float[Tensor, "bs r d_in"],
|
|
B: Float[Tensor, "bs d_out r"],
|
|
scaling: float,
|
|
input_dropout: float,
|
|
training: bool,
|
|
) -> list[RemovableHandle]:
|
|
"""
|
|
Adds LoRA hooks to the specified modules and layers of the model.
|
|
|
|
Args:
|
|
model (torch.nn.Module): The model to which hooks will be applied.
|
|
module_name (str): Name of the module to hook.
|
|
A (Tensor): Weight tensor A.
|
|
B (Tensor): Weight tensor B.
|
|
scaling (float): Scaling factor.
|
|
input_dropout (float): Dropout rate for input.
|
|
training (bool): Flag indicating if the model is in training mode.
|
|
|
|
Returns:
|
|
list[RemovableHandle]: A list of hook handles.
|
|
"""
|
|
|
|
def lora_hook(
|
|
module: torch.nn.Module,
|
|
args: tuple | Float[Tensor, "bs seq_len d_in"],
|
|
output: Float[Tensor, "bs seq_len d_out"],
|
|
) -> Float[Tensor, "bs seq_len d_out"]:
|
|
|
|
if isinstance(output, tuple):
|
|
model_out = output[0]
|
|
else:
|
|
model_out = output
|
|
|
|
x = args[0].to(A.dtype) # [bs, seq_len, d_in]
|
|
|
|
delta_x = F.dropout(x, input_dropout, training)
|
|
delta_x = einsum(A, delta_x, "bs r d_in, bs seq_len d_in -> bs seq_len r")
|
|
delta_x = einsum(B, delta_x, "bs d_out r, bs seq_len r -> bs seq_len d_out")
|
|
delta_x = delta_x * scaling
|
|
|
|
# # A and B repeat for each input token
|
|
# lora_A = A.repeat_interleave(seq_len, dim=0)
|
|
# lora_B = B.repeat_interleave(seq_len, dim=0)
|
|
# x = x.reshape(bs * seq_len, 1, -1)
|
|
# delta_x = (
|
|
# torch.bmm(torch.bmm(F.dropout(x, input_dropout, training), lora_A), lora_B)
|
|
# * scaling
|
|
# )
|
|
|
|
newoutput = model_out + delta_x.to(model_out.dtype)
|
|
if isinstance(output, tuple):
|
|
return (newoutput, *output[1:])
|
|
else:
|
|
return newoutput
|
|
|
|
return apply_hook_to_layers(model, [module_name], [layer_index], post_hook=lora_hook)
|