use std::cell::RefCell;
use nuvec::{Array, DType, OpErrorKind, Param, Quant, QuantMode, Quantized, fast, ops, random};
use crate::Module;
thread_local! {
static BIAS_DRAW: RefCell<Option<(Array, Array, usize)>> = const { RefCell::new(None) };
}
#[track_caller]
fn uniform_init(input: i32, shape: &[i32]) -> Array {
let scale = (1.0 / f64::from(input.max(1))).sqrt();
random::uniform(-scale, scale, shape, DType::F32, None)
}
#[derive(Debug, Clone, Module)]
pub struct Linear {
pub weight: Param,
pub bias: Option<Param>,
}
impl Linear {
#[track_caller]
pub fn new(input_dims: i32, output_dims: i32) -> Self {
let weight = uniform_init(input_dims, &[output_dims, input_dims]);
let before = random::sequence_key();
let bias = uniform_init(input_dims, &[output_dims]);
let after = random::sequence_key();
BIAS_DRAW.set(Some((before, after, nuvec::graph::id(&bias))));
Self {
weight: Param::new(weight),
bias: Some(Param::new(bias)),
}
}
pub fn without_bias(mut self) -> Self {
if let Some(bias) = self.bias.take() {
BIAS_DRAW.with_borrow_mut(|draw| {
if let Some((before, after, id)) = draw.take()
&& id == nuvec::graph::id(&bias)
&& nuvec::graph::id(&random::sequence_key()) == nuvec::graph::id(&after)
{
random::set_sequence_key(before);
}
});
}
self
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
match &self.bias {
Some(b) => b.addmm(x, &self.weight.t(), 1.0, 1.0),
None => x.matmul(&self.weight.t()),
}
}
}
#[derive(Debug, Clone, Module)]
pub struct QuantizedLinear {
#[tree(buffer)]
pub weight: Array,
#[tree(buffer)]
pub scales: Array,
#[tree(buffer)]
pub biases: Option<Array>,
pub bias: Option<Param>,
#[tree(skip)]
pub quant: Quant,
}
impl QuantizedLinear {
#[track_caller]
pub fn from_linear(linear: &Linear) -> Self {
Self::from_linear_with(linear, Quant::default())
}
#[track_caller]
pub fn from_linear_with(linear: &Linear, quant: impl Into<Quant>) -> Self {
let q = linear.weight.quantize(quant);
Self {
weight: q.weight,
scales: q.scales,
biases: q.biases,
bias: linear.bias.clone(),
quant: q.quant,
}
}
pub fn quantized(&self) -> Quantized {
Quantized::new(
self.weight.clone(),
self.scales.clone(),
self.biases.clone(),
self.quant,
)
}
#[track_caller]
pub fn dequantized_weight(&self, dtype: impl Into<Option<DType>>) -> Array {
self.quantized().dequantize(dtype)
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
let y = x.quantized_matmul(&self.quantized(), true);
match &self.bias {
Some(b) => y.add(b),
None => y,
}
}
}
#[derive(Debug, Clone, Module)]
pub struct Embedding {
pub weight: Param,
}
impl Embedding {
#[track_caller]
pub fn new(num_embeddings: i32, dims: i32) -> Self {
let scale = (1.0 / f64::from(dims.max(1))).sqrt();
let weight = random::normal([num_embeddings, dims], DType::F32, 0.0, scale, None);
Self {
weight: Param::new(weight),
}
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
self.weight.take(x, 0)
}
#[track_caller]
pub fn as_linear(&self, x: &Array) -> Array {
x.matmul(&self.weight.t())
}
}
#[derive(Debug, Clone, Module)]
pub struct QuantizedEmbedding {
#[tree(buffer)]
pub weight: Array,
#[tree(buffer)]
pub scales: Array,
#[tree(buffer)]
pub biases: Option<Array>,
#[tree(skip)]
pub quant: Quant,
}
impl QuantizedEmbedding {
#[track_caller]
pub fn from_embedding(embedding: &Embedding) -> Self {
Self::from_embedding_with(embedding, Quant::default())
}
#[track_caller]
pub fn from_embedding_with(embedding: &Embedding, quant: impl Into<Quant>) -> Self {
let q = embedding.weight.quantize(quant);
Self {
weight: q.weight,
scales: q.scales,
biases: q.biases,
quant: q.quant,
}
}
pub fn quantized(&self) -> Quantized {
Quantized::new(
self.weight.clone(),
self.scales.clone(),
self.biases.clone(),
self.quant,
)
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
let rows = Quantized::new(
self.weight.take(x, 0),
self.scales.take(x, 0),
self.biases.as_ref().map(|b| b.take(x, 0)),
self.quant,
);
rows.dequantize(None)
}
#[track_caller]
pub fn as_linear(&self, x: &Array) -> Array {
x.quantized_matmul(&self.quantized(), true)
}
}
#[derive(Debug, Clone, Module)]
pub struct RmsNorm {
pub weight: Param,
#[tree(skip)]
pub eps: f32,
}
impl RmsNorm {
#[track_caller]
pub fn new(dims: i32) -> Self {
Self {
weight: Param::new(ops::ones([dims], DType::F32)),
eps: 1e-5,
}
}
pub fn eps(mut self, eps: f32) -> Self {
self.eps = eps;
self
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
fast::rms_norm(x, &self.weight, self.eps)
}
}
#[derive(Debug, Clone, Module)]
pub struct LayerNorm {
pub weight: Option<Param>,
pub bias: Option<Param>,
#[tree(skip)]
pub eps: f32,
}
impl LayerNorm {
#[track_caller]
pub fn new(dims: i32) -> Self {
Self {
weight: Some(Param::new(ops::ones([dims], DType::F32))),
bias: Some(Param::new(ops::zeros([dims], DType::F32))),
eps: 1e-5,
}
}
pub fn eps(mut self, eps: f32) -> Self {
self.eps = eps;
self
}
pub fn affine(mut self, affine: bool) -> Self {
if !affine {
self.weight = None;
self.bias = None;
}
self
}
pub fn without_bias(mut self) -> Self {
self.bias = None;
self
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
fast::layer_norm(
x,
self.weight.as_ref().map(Param::value),
self.bias.as_ref().map(Param::value),
self.eps,
)
}
}
#[derive(Debug, Clone, Module)]
pub struct Rope {
#[tree(skip)]
pub dims: i32,
#[tree(skip)]
pub traditional: bool,
#[tree(skip)]
pub base: f32,
#[tree(skip)]
pub scale: f32,
#[tree(skip)]
scaling: crate::RopeScaling,
#[tree(skip)]
freqs: Option<Array>,
}
impl Rope {
pub fn new(dims: i32) -> Self {
Self {
dims,
traditional: false,
base: 10000.0,
scale: 1.0,
scaling: crate::RopeScaling::None,
freqs: None,
}
}
pub fn scaled(dims: i32, base: f32, scaling: &crate::RopeScaling) -> Self {
Self {
dims,
traditional: false,
base,
scale: scaling.position_scale(),
scaling: scaling.clone(),
freqs: scaling.frequencies(dims, base),
}
}
pub fn mscale(&self) -> f32 {
self.scaling.mscale()
}
pub fn rotation(&self) -> fast::Rotation<'_> {
fast::Rotation {
dims: self.dims,
traditional: self.traditional,
base: self.freqs.is_none().then_some(self.base),
scale: self.scale,
freqs: self.freqs.as_ref(),
}
}
pub fn traditional(mut self) -> Self {
self.traditional = true;
self
}
pub fn base(mut self, base: f32) -> Self {
self.base = base;
self.freqs = self.scaling.frequencies(self.dims, base);
self
}
pub fn scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
#[track_caller]
pub fn forward(&self, x: &Array, offset: i32) -> Array {
let m = self.mscale();
let x = if m == 1.0 {
x.clone()
} else if x.shape().last() == Some(&self.dims) {
x.multiply(m)
} else {
let head = (.., ..self.dims);
x.clone().at(head).set(x.i(head).multiply(m))
};
let r = self.rotation();
fast::rope(
&x,
r.dims,
r.traditional,
r.base,
r.scale,
&Array::from(offset),
r.freqs,
)
}
}
#[derive(Debug, Clone, Module)]
pub struct Dropout {
#[tree(skip)]
pub p: f32,
#[tree(mode)]
pub training: bool,
}
impl Dropout {
pub fn new(p: f32) -> Self {
Self { p, training: true }
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
self.apply(x, None)
}
#[track_caller]
pub fn forward_with_key(&self, x: &Array, key: &Array) -> Array {
self.apply(x, Some(key))
}
#[track_caller]
fn apply(&self, x: &Array, key: Option<&Array>) -> Array {
if !(0.0..1.0).contains(&self.p) {
return Array::failure(
"dropout",
OpErrorKind::InvalidArgument(format!(
"the probability must be in [0, 1), got {}",
self.p
)),
);
}
if !self.training || self.p == 0.0 {
return x.clone();
}
let p: f64 = self.p.to_string().parse().unwrap_or(f64::from(self.p));
let keep = 1.0 - p;
let mask = random::bernoulli(keep, x.shape(), key);
mask.multiply(x).multiply(1.0 / keep)
}
}
#[derive(Debug, Clone, Module)]
pub enum LoraBase {
Linear(Linear),
Quantized(QuantizedLinear),
}
impl LoraBase {
fn dims(&self) -> (i32, i32) {
match self {
Self::Linear(l) => {
let s = l.weight.shape();
(
s.first().copied().unwrap_or(0),
s.get(1).copied().unwrap_or(0),
)
}
Self::Quantized(q) => {
let s = q.weight.shape();
let packed = s.get(1).copied().unwrap_or(0);
(
s.first().copied().unwrap_or(0),
packed * 32 / q.quant.bits.max(1),
)
}
}
}
#[track_caller]
fn forward(&self, x: &Array) -> Array {
match self {
Self::Linear(l) => l.forward(x),
Self::Quantized(q) => q.forward(x),
}
}
}
#[derive(Debug, Clone, Module)]
pub struct LoraLinear {
#[tree(rename = "linear")]
pub base: LoraBase,
pub lora_a: Param,
pub lora_b: Param,
#[tree(skip)]
pub scale: f32,
pub dropout: Dropout,
}
impl LoraLinear {
#[track_caller]
pub fn from_base(base: impl Into<LoraBase>, r: i32) -> Self {
let mut base = base.into();
let (output_dims, input_dims) = base.dims();
let _ = uniform_init(input_dims, &[output_dims, input_dims]);
let lora_a = uniform_init(input_dims, &[input_dims, r]);
let lora_b = ops::zeros([r, output_dims], DType::F32);
let _ = base.freeze(|_| true);
Self {
base,
lora_a: Param::new(lora_a),
lora_b: Param::new(lora_b),
scale: 20.0,
dropout: Dropout::new(0.0),
}
}
pub fn scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
pub fn dropout(mut self, p: f32) -> Self {
self.dropout = Dropout::new(p);
self
}
pub fn rslora(mut self) -> Self {
let r = self.lora_a.shape().get(1).copied().unwrap_or(1).max(1);
self.scale *= (r as f32).sqrt();
self
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
let y = self.base.forward(x);
let z = self
.dropout
.forward(x)
.matmul(&self.lora_a)
.matmul(&self.lora_b);
y.add(z.multiply(f64::from(self.scale)).astype(x.dtype()))
}
#[track_caller]
pub fn fuse(&self) -> Linear {
let (weight, bias) = match &self.base {
LoraBase::Linear(l) => (l.weight.value().clone(), l.bias.clone()),
LoraBase::Quantized(q) => {
let dtype = (q.quant.mode != QuantMode::Affine).then(|| self.lora_a.dtype());
(q.dequantized_weight(dtype), q.bias.clone())
}
};
let delta = self
.lora_b
.t()
.multiply(f64::from(self.scale))
.matmul(&self.lora_a.t())
.astype(weight.dtype());
Linear {
weight: Param::new(weight.add(&delta)),
bias: bias.map(|b| Param::new(b.into_inner())),
}
}
}
impl From<Linear> for LoraBase {
fn from(l: Linear) -> Self {
Self::Linear(l)
}
}
impl From<QuantizedLinear> for LoraBase {
fn from(q: QuantizedLinear) -> Self {
Self::Quantized(q)
}
}