use heapless::Vec as HVec;
use super::protocol::{ChipId, FederationMessage};
pub const MAX_HEADS_PER_CHIP: usize = 4;
#[derive(Debug, Clone)]
pub struct TPConfig {
pub num_chips: usize,
pub chip_id: ChipId,
pub total_heads: usize,
pub my_heads: HVec<usize, MAX_HEADS_PER_CHIP>,
pub head_dim: usize,
}
impl TPConfig {
pub fn distribute_heads(
chip_id: usize,
num_chips: usize,
total_heads: usize,
head_dim: usize,
) -> Self {
let mut my_heads = HVec::new();
for h in 0..total_heads {
if h % num_chips == chip_id {
let _ = my_heads.push(h);
}
}
Self {
num_chips,
chip_id: ChipId(chip_id as u8),
total_heads,
my_heads,
head_dim,
}
}
}
pub struct TensorParallelNode {
config: TPConfig,
partial_outputs: HVec<HVec<i32, 64>, MAX_HEADS_PER_CHIP>,
output_buffer: HVec<i32, 256>,
}
impl TensorParallelNode {
pub fn new(config: TPConfig) -> Self {
Self {
config,
partial_outputs: HVec::new(),
output_buffer: HVec::new(),
}
}
pub fn my_heads(&self) -> &[usize] {
&self.config.my_heads
}
pub fn compute_partial_attention(
&mut self,
query: &[i8],
keys: &[&[i8]],
values: &[&[i8]],
) -> crate::Result<()> {
self.partial_outputs.clear();
for &head_idx in &self.config.my_heads {
let mut head_output = HVec::new();
let head_start = head_idx * self.config.head_dim;
let head_end = head_start + self.config.head_dim;
for &val in &values[0][head_start..head_end.min(values[0].len())] {
head_output.push(val as i32).map_err(|_| crate::Error::BufferOverflow)?;
}
self.partial_outputs.push(head_output).map_err(|_| crate::Error::BufferOverflow)?;
}
Ok(())
}
pub fn create_partial_result_message(&self, dst: ChipId, seq: u16) -> crate::Result<FederationMessage> {
let mut data: Vec<i8> = Vec::new();
for partial in &self.partial_outputs {
for &val in partial {
data.push((val >> 8) as i8); }
}
FederationMessage::activation(
self.config.chip_id,
dst,
seq,
0, 0,
&data,
)
}
pub fn memory_reduction(&self) -> f32 {
self.config.num_chips as f32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_head_distribution() {
let config0 = TPConfig::distribute_heads(0, 5, 4, 16);
let config1 = TPConfig::distribute_heads(1, 5, 4, 16);
assert_eq!(config0.my_heads.as_slice(), &[0]);
assert_eq!(config1.my_heads.as_slice(), &[1]);
}
}