Skip to main content

burn_core/module/param/
lora.rs

1use super::{Param, Reparameterization};
2use crate as burn;
3use crate::module::Module;
4use burn_tensor::Tensor;
5
6/// A LoRA (Low-Rank Adaptation) adapter attached to a frozen weight [parameter](Param).
7///
8/// When present on a `Param<Tensor<2>>`, the parameter materializes its effective value as
9/// `base + scale * (a @ b)`, where `base` is the frozen (and optionally quantized) weight and
10/// `a`/`b` are the trainable low-rank factors. The frozen base is the stored value of the
11/// parameter; the adapter factors are surfaced to the optimizer, autodiff and record systems as
12/// regular parameters with their own [`ParamId`](super::ParamId)s through the module
13/// visitor/mapper traversal.
14#[derive(Debug, Module)]
15pub struct LoraAdapter {
16    /// Down-projection factor with shape `[d_in, rank]` (trainable).
17    pub a: Param<Tensor<2>>,
18    /// Up-projection factor with shape `[rank, d_out]` (trainable).
19    pub b: Param<Tensor<2>>,
20    /// Scaling factor applied to the low-rank product, typically `alpha / rank`.
21    pub scale: f64,
22}
23
24impl Reparameterization for LoraAdapter {
25    const NAME: &'static str = "lora";
26
27    fn materialize<const D: usize>(&self, base: Tensor<D>) -> Tensor<D> {
28        let delta = self.delta().reshape(base.shape());
29        base + delta
30    }
31}
32
33impl LoraAdapter {
34    /// Compute the low-rank delta `scale * (a @ b)` with shape `[d_in, d_out]`.
35    ///
36    /// `a` and `b` are read through [`Param::val`], so the delta always reflects the current
37    /// (optimizer-updated) factors and keeps them as autodiff leaves for backpropagation.
38    pub fn delta(&self) -> Tensor<2> {
39        self.a.val().matmul(self.b.val()).mul_scalar(self.scale)
40    }
41}