use nuvec::distributed::{self, Group};
use nuvec::{Array, Error, Param, Result};
use crate::{Linear, Module, QuantizedLinear};
#[derive(Debug, Clone, Module)]
pub enum Shard {
Linear(Linear),
Quantized(QuantizedLinear),
}
impl Shard {
#[track_caller]
fn forward(&self, x: &Array) -> Array {
match self {
Self::Linear(l) => l.forward(x),
Self::Quantized(q) => q.forward(x),
}
}
}
fn slice(layer: &str, what: &str, n: i32, unit: i32, group: &Group) -> Result<(i32, i32)> {
let size = group.size() as i32;
if n % (size * unit) != 0 {
return Err(Error::InvalidArgument(format!(
"{layer}: {n} {what} do not split into {size} shards{}",
if unit > 1 {
format!(" of whole groups of {unit}")
} else {
String::new()
}
)));
}
let per = n / size;
let r = group.rank() as i32;
Ok((r * per, (r + 1) * per))
}
fn dims(a: &Array) -> (i32, i32) {
let s = a.shape();
(
s.first().copied().unwrap_or(0),
s.get(1).copied().unwrap_or(0),
)
}
#[derive(Debug, Clone, Module)]
pub struct AllToShardedLinear {
pub shard: Shard,
#[tree(skip)]
pub group: Group,
}
impl AllToShardedLinear {
pub fn from_linear(linear: &Linear, group: &Group) -> Result<Self> {
let (out, _) = dims(&linear.weight);
let (a, b) = slice("AllToShardedLinear", "output features", out, 1, group)?;
Ok(Self {
shard: Shard::Linear(Linear {
weight: Param::new(linear.weight.i((a..b, ..))),
bias: linear.bias.as_ref().map(|p| Param::new(p.i(a..b))),
}),
group: group.clone(),
})
}
pub fn from_quantized(layer: &QuantizedLinear, group: &Group) -> Result<Self> {
let (out, _) = dims(&layer.weight);
let (a, b) = slice("AllToShardedLinear", "output features", out, 1, group)?;
let rows = |x: &Array| x.i((a..b, ..));
Ok(Self {
shard: Shard::Quantized(QuantizedLinear {
weight: rows(&layer.weight),
scales: rows(&layer.scales),
biases: layer.biases.as_ref().map(rows),
bias: layer.bias.as_ref().map(|p| Param::new(p.i(a..b))),
quant: layer.quant,
}),
group: group.clone(),
})
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
self.shard
.forward(&distributed::sum_gradients(x, Some(&self.group)))
}
}
#[derive(Debug, Clone, Module)]
pub struct ShardedToAllLinear {
pub shard: Shard,
pub bias: Option<Param>,
#[tree(skip)]
pub group: Group,
}
impl ShardedToAllLinear {
pub fn from_linear(linear: &Linear, group: &Group) -> Result<Self> {
let (_, input) = dims(&linear.weight);
let (a, b) = slice("ShardedToAllLinear", "input features", input, 1, group)?;
Ok(Self {
shard: Shard::Linear(Linear {
weight: Param::new(linear.weight.i((.., a..b))),
bias: None,
}),
bias: linear.bias.clone(),
group: group.clone(),
})
}
pub fn from_quantized(layer: &QuantizedLinear, group: &Group) -> Result<Self> {
let (_, packed) = dims(&layer.weight);
let bits = layer.quant.bits.max(1);
let input = packed * 32 / bits;
let group_size = layer.quant.group_size.max(1);
let (a, b) = slice(
"ShardedToAllLinear",
"input features",
input,
group_size,
group,
)?;
let (pa, pb) = (a * bits / 32, b * bits / 32);
let (sa, sb) = (a / group_size, b / group_size);
let cols = |x: &Array| x.i((.., sa..sb));
Ok(Self {
shard: Shard::Quantized(QuantizedLinear {
weight: layer.weight.i((.., pa..pb)),
scales: cols(&layer.scales),
biases: layer.biases.as_ref().map(cols),
bias: None,
quant: layer.quant,
}),
bias: layer.bias.clone(),
group: group.clone(),
})
}
#[track_caller]
pub fn forward(&self, x: &Array) -> Array {
let y = distributed::all_sum(&self.shard.forward(x), Some(&self.group));
match &self.bias {
Some(b) => y.add(b),
None => y,
}
}
}