use nuvec::{
Array, DType, Result, add, dequantize, fast, matmul, multiply, quantize, quantized_matmul,
random, take,
};
use crate::{Module, Parameters};
#[derive(Debug, Clone)]
pub struct Linear {
pub weight: Array,
pub bias: Option<Array>,
}
impl Linear {
pub fn new(input_dims: usize, output_dims: usize, bias: bool) -> Result<Self> {
let scale = (1.0 / input_dims as f64).sqrt();
let draw = |shape: &[usize]| random::uniform(-scale, scale, shape, DType::F32, None);
let weight = draw(&[output_dims, input_dims])?;
let bias = if bias {
Some(draw(&[output_dims])?)
} else {
None
};
Ok(Self { weight, bias })
}
pub fn forward(&self, x: &Array) -> Result<Array> {
match &self.bias {
Some(b) => nuvec::addmm(b, x, &self.weight.t()?, 1.0, 1.0),
None => matmul(x, &self.weight.t()?),
}
}
}
impl Module for Linear {
fn visit(&mut self, p: &mut Parameters<'_>) {
p.param("weight", &mut self.weight);
p.optional("bias", &mut self.bias);
}
}
#[derive(Debug, Clone)]
pub struct QuantizedLinear {
pub weight: Array,
pub scales: Array,
pub biases: Array,
pub bias: Option<Array>,
pub group_size: usize,
pub bits: u32,
}
impl QuantizedLinear {
pub fn from_linear(linear: &Linear, group_size: usize, bits: u32) -> Result<Self> {
let (weight, scales, biases) = quantize(&linear.weight, group_size, bits)?;
Ok(Self {
weight,
scales,
biases,
bias: linear.bias.clone(),
group_size,
bits,
})
}
pub fn dequantized_weight(&self) -> Result<Array> {
dequantize(
&self.weight,
&self.scales,
&self.biases,
self.group_size,
self.bits,
)
}
pub fn forward(&self, x: &Array) -> Result<Array> {
let y = quantized_matmul(
x,
&self.weight,
&self.scales,
&self.biases,
true,
self.group_size,
self.bits,
)?;
match &self.bias {
Some(b) => add(&y, b),
None => Ok(y),
}
}
}
impl Module for QuantizedLinear {
fn visit(&mut self, p: &mut Parameters<'_>) {
p.frozen("weight", &mut self.weight);
p.frozen("scales", &mut self.scales);
p.frozen("biases", &mut self.biases);
p.optional("bias", &mut self.bias);
}
}
#[derive(Debug, Clone)]
pub struct Embedding {
pub weight: Array,
}
impl Embedding {
pub fn new(num_embeddings: usize, dims: usize) -> Result<Self> {
let scale = (1.0 / dims as f64).sqrt();
let weight = random::normal(&[num_embeddings, dims], DType::F32, None, Some(scale), None)?;
Ok(Self { weight })
}
pub fn forward(&self, x: &Array) -> Result<Array> {
take(&self.weight, x, Some(0))
}
pub fn as_linear(&self, x: &Array) -> Result<Array> {
matmul(x, &self.weight.t()?)
}
}
impl Module for Embedding {
fn visit(&mut self, p: &mut Parameters<'_>) {
p.param("weight", &mut self.weight);
}
}
#[derive(Debug, Clone)]
pub struct QuantizedEmbedding {
pub weight: Array,
pub scales: Array,
pub biases: Array,
pub group_size: usize,
pub bits: u32,
}
impl QuantizedEmbedding {
pub fn from_embedding(embedding: &Embedding, group_size: usize, bits: u32) -> Result<Self> {
let (weight, scales, biases) = quantize(&embedding.weight, group_size, bits)?;
Ok(Self {
weight,
scales,
biases,
group_size,
bits,
})
}
pub fn forward(&self, x: &Array) -> Result<Array> {
dequantize(
&take(&self.weight, x, Some(0))?,
&take(&self.scales, x, Some(0))?,
&take(&self.biases, x, Some(0))?,
self.group_size,
self.bits,
)
}
pub fn as_linear(&self, x: &Array) -> Result<Array> {
quantized_matmul(
x,
&self.weight,
&self.scales,
&self.biases,
true,
self.group_size,
self.bits,
)
}
}
impl Module for QuantizedEmbedding {
fn visit(&mut self, p: &mut Parameters<'_>) {
p.frozen("weight", &mut self.weight);
p.frozen("scales", &mut self.scales);
p.frozen("biases", &mut self.biases);
}
}
#[derive(Debug, Clone)]
pub struct RmsNorm {
pub weight: Array,
pub eps: f32,
}
impl RmsNorm {
pub fn new(dims: usize, eps: f32) -> Result<Self> {
Ok(Self {
weight: nuvec::ones(&[dims], DType::F32)?,
eps,
})
}
pub fn forward(&self, x: &Array) -> Result<Array> {
fast::rms_norm(x, Some(&self.weight), self.eps)
}
}
impl Module for RmsNorm {
fn visit(&mut self, p: &mut Parameters<'_>) {
p.param("weight", &mut self.weight);
}
}
#[derive(Debug, Clone)]
pub struct LayerNorm {
pub weight: Option<Array>,
pub bias: Option<Array>,
pub eps: f32,
}
impl LayerNorm {
pub fn new(dims: usize, eps: f32, affine: bool, bias: bool) -> Result<Self> {
let weight = affine
.then(|| nuvec::ones(&[dims], DType::F32))
.transpose()?;
let bias = (affine && bias)
.then(|| nuvec::zeros(&[dims], DType::F32))
.transpose()?;
Ok(Self { weight, bias, eps })
}
pub fn forward(&self, x: &Array) -> Result<Array> {
fast::layer_norm(x, self.weight.as_ref(), self.bias.as_ref(), self.eps)
}
}
impl Module for LayerNorm {
fn visit(&mut self, p: &mut Parameters<'_>) {
p.optional("weight", &mut self.weight);
p.optional("bias", &mut self.bias);
}
}
#[derive(Debug, Clone)]
pub struct Rope {
pub dims: usize,
pub traditional: bool,
pub base: f32,
pub scale: f32,
}
impl Rope {
pub fn new(dims: usize) -> Self {
Self {
dims,
traditional: false,
base: 10000.0,
scale: 1.0,
}
}
pub fn forward(&self, x: &Array, offset: usize) -> Result<Array> {
fast::rope(
x,
self.dims,
self.traditional,
Some(self.base),
self.scale,
&Array::scalar(offset as i32),
None,
)
}
}
impl Module for Rope {
fn visit(&mut self, _: &mut Parameters<'_>) {}
}
#[derive(Debug, Clone)]
pub enum LoraBase {
Linear(Linear),
Quantized(QuantizedLinear),
}
#[derive(Debug, Clone)]
pub struct LoraLinear {
pub base: LoraBase,
pub lora_a: Array,
pub lora_b: Array,
pub scale: f32,
}
impl LoraLinear {
pub fn from_base(base: LoraBase, r: usize, scale: f32) -> Result<Self> {
let (output_dims, input_dims) = match &base {
LoraBase::Linear(l) => (l.weight.shape()[0], l.weight.shape()[1]),
LoraBase::Quantized(q) => (
q.weight.shape()[0],
q.weight.shape()[1] * 32 / q.bits as usize,
),
};
Linear::new(input_dims, output_dims, false)?;
let bound = 1.0 / (input_dims as f64).sqrt();
let lora_a = random::uniform(-bound, bound, &[input_dims, r], DType::F32, None)?;
let lora_b = nuvec::zeros(&[r, output_dims], DType::F32)?;
Ok(Self {
base,
lora_a,
lora_b,
scale,
})
}
pub fn forward(&self, x: &Array) -> Result<Array> {
let y = match &self.base {
LoraBase::Linear(l) => l.forward(x)?,
LoraBase::Quantized(q) => q.forward(x)?,
};
let z = matmul(&matmul(x, &self.lora_a)?, &self.lora_b)?;
add(&y, multiply(z, f64::from(self.scale))?.astype(x.dtype())?)
}
pub fn fuse(&self) -> Result<Linear> {
let (weight, bias) = match &self.base {
LoraBase::Linear(l) => (l.weight.clone(), l.bias.clone()),
LoraBase::Quantized(q) => (q.dequantized_weight()?, q.bias.clone()),
};
let delta = matmul(
&multiply(f64::from(self.scale), self.lora_b.t()?)?,
&self.lora_a.t()?,
)?
.astype(weight.dtype())?;
Ok(Linear {
weight: add(&weight, &delta)?,
bias,
})
}
}
impl Module for LoraLinear {
fn visit(&mut self, p: &mut Parameters<'_>) {
let base = &mut self.base;
p.freeze(|p| match base {
LoraBase::Linear(l) => p.module("linear", l),
LoraBase::Quantized(q) => p.module("linear", q),
});
p.param("lora_a", &mut self.lora_a);
p.param("lora_b", &mut self.lora_b);
}
}