use crate::matmul;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LoraConfig {
pub r: usize,
pub alpha: f64,
}
impl LoraConfig {
pub fn scaling(&self) -> f64 {
ensure_nonzero(self.r);
self.alpha / self.r as f64
}
}
fn ensure_nonzero(r: usize) {
assert!(r > 0, "LoRA rank must be positive");
}
pub fn lora_delta(
a: &[f64],
b: &[f64],
in_features: usize,
out_features: usize,
cfg: LoraConfig,
) -> Result<Vec<f64>, String> {
if !(cfg.r > 0) {
return Err(format!("rank must be positive, got {}", cfg.r));
}
if !(a.len() == cfg.r * in_features) {
return Err(format!(
"A is {} values, expected {}·{in_features}",
a.len(),
cfg.r
));
}
if !(b.len() == out_features * cfg.r) {
return Err(format!(
"B is {} values, expected {out_features}·{}",
b.len(),
cfg.r
));
}
let mut d = matmul(b, a, out_features, cfg.r, in_features)?;
let s = cfg.scaling();
for v in &mut d {
*v *= s;
}
Ok(d)
}
pub fn ia3_apply(
y: &[f64],
l: &[f64],
rows: usize,
out_features: usize,
) -> Result<Vec<f64>, String> {
if !(y.len() == rows * out_features) {
return Err(format!(
"y is {} values, expected {rows}·{out_features}",
y.len()
));
}
if !(l.len() == out_features) {
return Err(format!("l is {} values, expected {out_features}", l.len()));
}
Ok((0..rows * out_features)
.map(|i| y[i] * l[i % out_features])
.collect())
}
pub fn adalora_delta(
p: &[f64],
lambda: &[f64],
q: &[f64],
in_features: usize,
out_features: usize,
cfg: LoraConfig,
) -> Result<Vec<f64>, String> {
let r = cfg.r;
if !(r > 0) {
return Err(format!("rank must be positive, got {r}"));
}
if !(p.len() == out_features * r) {
return Err(format!(
"P is {} values, expected {out_features}·{r}",
p.len()
));
}
if !(lambda.len() == r) {
return Err(format!("Lambda is {} values, expected {r}", lambda.len()));
}
if !(q.len() == r * in_features) {
return Err(format!(
"Q is {} values, expected {r}·{in_features}",
q.len()
));
}
let mut pl = vec![0f64; out_features * r];
for i in 0..out_features {
for j in 0..r {
pl[i * r + j] = p[i * r + j] * lambda[j];
}
}
let mut d = matmul(&pl, q, out_features, r, in_features)?;
let s = cfg.scaling();
for v in &mut d {
*v *= s;
}
Ok(d)
}
pub fn dora_weight(
w: &[f64],
delta: &[f64],
m: &[f64],
in_features: usize,
out_features: usize,
) -> Result<Vec<f64>, String> {
if !(w.len() == out_features * in_features) {
return Err(format!(
"W is {} values, expected {out_features}·{in_features}",
w.len()
));
}
if !(delta.len() == w.len()) {
return Err(format!(
"delta is {} values, expected {}",
delta.len(),
w.len()
));
}
if !(m.len() == out_features) {
return Err(format!("m is {} values, expected {out_features}", m.len()));
}
let mut out = vec![0f64; w.len()];
for i in 0..out_features {
let row: Vec<f64> = (0..in_features)
.map(|j| w[i * in_features + j] + delta[i * in_features + j])
.collect();
let norm = row.iter().map(|v| v * v).sum::<f64>().sqrt();
let scale = if norm > 0.0 { m[i] / norm } else { 0.0 };
for j in 0..in_features {
out[i * in_features + j] = row[j] * scale;
}
}
Ok(out)
}
pub fn dora_init_magnitude(
w: &[f64],
in_features: usize,
out_features: usize,
) -> Result<Vec<f64>, String> {
if !(w.len() == out_features * in_features) {
return Err(format!(
"W is {} values, expected {out_features}·{in_features}",
w.len()
));
}
Ok((0..out_features)
.map(|i| {
(0..in_features)
.map(|j| w[i * in_features + j].powi(2))
.sum::<f64>()
.sqrt()
})
.collect())
}