use trueno_gpu::graph::{ComputeGraph, OpParams, TensorOp};
use crate::cuda::types::{ValidatedLayerWeights, WeightQuantType};
#[allow(clippy::too_many_arguments)]
fn ggml_code(q: WeightQuantType) -> u32 {
match q {
WeightQuantType::Q4_0 => 2,
WeightQuantType::Q4_1 => 3,
WeightQuantType::Q5_0 => 6,
WeightQuantType::Q8_0 => 8,
WeightQuantType::Q4K => 12,
WeightQuantType::Q5K => 13,
WeightQuantType::Q6K => 14,
_ => 12,
}
}
pub fn build_layer_graph(
layer_weights: &ValidatedLayerWeights,
input_ptr: u64,
hidden_dim: u32,
intermediate_dim: u32,
q_dim: u32,
kv_dim: u32,
m: u32,
epsilon: f32,
layer_idx: usize,
hidden_buf1_ptr: u64,
hidden_buf2_ptr: u64,
q_buf_ptr: u64,
k_buf_ptr: u64,
v_buf_ptr: u64,
attn_out_ptr: u64,
ffn_gate_ptr: u64,
ffn_up_ptr: u64,
ffn_act_ptr: u64,
input_staging_ptr: u64,
) -> (ComputeGraph, usize) {
let mut g = ComputeGraph::new();
let input = g.add_leaf(input_ptr, [hidden_dim, 1, m, 0]);
let normed_attn = g.add_op(
TensorOp::RmsNorm,
hidden_buf1_ptr,
[hidden_dim, 1, m, 0],
vec![input],
OpParams {
gamma_ptr: layer_weights.attn_norm_ptr,
scalar: epsilon,
..Default::default()
},
);
let q = g.add_op(
TensorOp::MulMat,
q_buf_ptr,
[q_dim, hidden_dim, m, 0],
vec![normed_attn],
OpParams {
weight_ptr: layer_weights.attn_q_ptr,
weight_qtype: ggml_code(layer_weights.attn_q_qtype),
bias_ptr: layer_weights.attn_q_bias_ptr,
bias_len: layer_weights.attn_q_bias_len,
..Default::default()
},
);
let k = g.add_op(
TensorOp::MulMat,
k_buf_ptr,
[kv_dim, hidden_dim, m, 0],
vec![normed_attn],
OpParams {
weight_ptr: layer_weights.attn_k_ptr,
weight_qtype: ggml_code(layer_weights.attn_k_qtype),
bias_ptr: layer_weights.attn_k_bias_ptr,
bias_len: layer_weights.attn_k_bias_len,
..Default::default()
},
);
let v = g.add_op(
TensorOp::MulMat,
v_buf_ptr,
[kv_dim, hidden_dim, m, 0],
vec![normed_attn],
OpParams {
weight_ptr: layer_weights.attn_v_ptr,
weight_qtype: ggml_code(layer_weights.attn_v_qtype),
bias_ptr: layer_weights.attn_v_bias_ptr,
bias_len: layer_weights.attn_v_bias_len,
..Default::default()
},
);
let attn_out = g.add_op(
TensorOp::SoftMax,
attn_out_ptr,
[q_dim, 1, m, 0],
vec![q, k, v],
OpParams {
int_param: layer_idx as u32,
..Default::default()
},
);
let o_proj = g.add_op(
TensorOp::MulMat,
hidden_buf1_ptr,
[hidden_dim, q_dim, m, 0],
vec![attn_out],
OpParams {
weight_ptr: layer_weights.attn_output_ptr,
weight_qtype: ggml_code(layer_weights.attn_output_qtype),
..Default::default()
},
);
let residual_1 = g.add_op(
TensorOp::Add,
input_staging_ptr,
[hidden_dim, 1, m, 0],
vec![input, o_proj],
OpParams::default(),
);
let normed_ffn = g.add_op(
TensorOp::RmsNorm,
hidden_buf1_ptr,
[hidden_dim, 1, m, 0],
vec![residual_1],
OpParams {
gamma_ptr: layer_weights.ffn_norm_ptr,
scalar: epsilon,
..Default::default()
},
);
let gate = g.add_op(
TensorOp::MulMat,
ffn_gate_ptr,
[intermediate_dim, hidden_dim, m, 0],
vec![normed_ffn],
OpParams {
weight_ptr: layer_weights.ffn_gate_ptr,
weight_qtype: ggml_code(layer_weights.ffn_gate_qtype),
..Default::default()
},
);
let up = g.add_op(
TensorOp::MulMat,
ffn_up_ptr,
[intermediate_dim, hidden_dim, m, 0],
vec![normed_ffn],
OpParams {
weight_ptr: layer_weights.ffn_up_ptr,
weight_qtype: ggml_code(layer_weights.ffn_up_qtype),
..Default::default()
},
);
let ffn_act = g.add_op(
TensorOp::Mul,
ffn_act_ptr,
[intermediate_dim, 1, m, 0],
vec![gate, up],
OpParams::default(),
);
let down = g.add_op(
TensorOp::MulMat,
hidden_buf1_ptr,
[hidden_dim, intermediate_dim, m, 0],
vec![ffn_act],
OpParams {
weight_ptr: layer_weights.ffn_down_ptr,
weight_qtype: ggml_code(layer_weights.ffn_down_qtype),
..Default::default()
},
);
let residual_2 = g.add_op(
TensorOp::Add,
hidden_buf2_ptr,
[hidden_dim, 1, m, 0],
vec![residual_1, down],
OpParams::default(),
);
(g, residual_2)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cuda::types::{IndexedLayerWeights, WeightQuantType};
fn mock_weights() -> ValidatedLayerWeights {
let raw = IndexedLayerWeights {
attn_q_ptr: 0x10000,
attn_q_len: 1024,
attn_q_qtype: WeightQuantType::Q4K,
attn_k_ptr: 0x20000,
attn_k_len: 512,
attn_k_qtype: WeightQuantType::Q4K,
attn_v_ptr: 0x30000,
attn_v_len: 512,
attn_v_qtype: WeightQuantType::Q6K,
attn_output_ptr: 0x40000,
attn_output_len: 1024,
attn_output_qtype: WeightQuantType::Q4K,
ffn_gate_ptr: 0x50000,
ffn_gate_len: 2048,
ffn_gate_qtype: WeightQuantType::Q4K,
ffn_up_ptr: 0x60000,
ffn_up_len: 2048,
ffn_up_qtype: WeightQuantType::Q4K,
ffn_down_ptr: 0x70000,
ffn_down_len: 2048,
ffn_down_qtype: WeightQuantType::Q4K,
attn_norm_ptr: 0x80000,
attn_norm_len: 256,
ffn_norm_ptr: 0x90000,
ffn_norm_len: 256,
attn_q_bias_ptr: 0,
attn_q_bias_len: 0,
attn_k_bias_ptr: 0,
attn_k_bias_len: 0,
attn_v_bias_ptr: 0,
attn_v_bias_len: 0,
attn_q_norm_ptr: 0,
attn_q_norm_len: 0,
attn_k_norm_ptr: 0,
attn_k_norm_len: 0,
};
ValidatedLayerWeights::new_unchecked(raw)
}
#[test]
fn test_layer_graph_node_count() {
let weights = mock_weights();
let (graph, output_idx) = build_layer_graph(
&weights, 0xA0000, 1536, 8960, 1536, 256, 4, 1e-6, 0, 0xB0000, 0xC0000, 0xD0000,
0xE0000, 0xF0000, 0x100000, 0x110000, 0x120000, 0x130000, 0x140000,
);
assert_eq!(graph.nodes.len(), 14);
assert_eq!(graph.n_leafs, 1);
assert_eq!(graph.n_ops(), 13);
assert_eq!(output_idx, 13);
}
#[test]
fn test_layer_graph_execution_count() {
use trueno_gpu::graph::execute_graph;
let weights = mock_weights();
let (graph, _) = build_layer_graph(
&weights, 0xA0000, 1536, 8960, 1536, 256, 4, 1e-6, 0, 0xB0000, 0xC0000, 0xD0000,
0xE0000, 0xF0000, 0x100000, 0x110000, 0x120000, 0x130000, 0x140000,
);
struct Counter(usize);
impl trueno_gpu::graph::KernelDispatch for Counter {
fn dispatch_mul_mat(
&mut self,
_: &trueno_gpu::graph::TensorNode,
_: u64,
_: u64,
_: u32,
_: u32,
_: u32,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
fn dispatch_rms_norm(
&mut self,
_: &trueno_gpu::graph::TensorNode,
_: u64,
_: u64,
_: u32,
_: u32,
_: f32,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
fn dispatch_add(
&mut self,
_: u64,
_: u64,
_: u64,
_: usize,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
fn dispatch_rope(
&mut self,
_: &trueno_gpu::graph::TensorNode,
_: u64,
_: &[u32],
_: u32,
_: u32,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
fn dispatch_attention(
&mut self,
_: &trueno_gpu::graph::TensorNode,
_: u64,
_: u64,
_: u64,
_: u64,
_: u32,
_: usize,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
fn dispatch_copy(
&mut self,
_: u64,
_: u64,
_: usize,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
fn dispatch_mul(
&mut self,
_: u64,
_: u64,
_: u64,
_: usize,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
fn dispatch_silu(
&mut self,
_: u64,
_: u64,
_: usize,
) -> Result<(), trueno_gpu::GpuError> {
self.0 += 1;
Ok(())
}
}
let mut counter = Counter(0);
let n = execute_graph(&graph, &mut counter).unwrap();
assert_eq!(n, 13);
assert_eq!(counter.0, 13);
}
}