1type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
5
6use crate::parameter::Parameter;
7use crate::{GraphData, GraphLayer};
8use torsh_tensor::{
9 creation::{randn, zeros},
10 Tensor,
11};
12
13#[derive(Debug)]
15pub struct GraphTransformer {
16 in_features: usize,
17 out_features: usize,
18 heads: usize,
19 edge_dim: usize,
20 query_weight: Parameter,
21 key_weight: Parameter,
22 value_weight: Parameter,
23 edge_weight: Parameter,
24 output_weight: Parameter,
25 bias: Option<Parameter>,
26 dropout: f32,
27}
28
29impl GraphTransformer {
30 pub fn new(
37 in_features: usize,
38 out_features: usize,
39 heads: usize,
40 edge_dim: usize,
41 dropout: f32,
42 bias: bool,
43 ) -> Result<Self> {
44 if heads == 0 || out_features % heads != 0 {
46 return Err(torsh_core::error::TorshError::InvalidArgument(format!(
47 "out_features ({out_features}) must be a positive multiple of heads ({heads})"
48 )));
49 }
50
51 let query_weight = Parameter::new(randn(&[in_features, out_features])?);
52 let key_weight = Parameter::new(randn(&[in_features, out_features])?);
53 let value_weight = Parameter::new(randn(&[in_features, out_features])?);
54 let edge_weight = Parameter::new(randn(&[edge_dim, heads])?);
55 let output_weight = Parameter::new(randn(&[out_features, out_features])?);
56
57 let bias = if bias {
58 Some(Parameter::new(zeros(&[out_features])?))
59 } else {
60 None
61 };
62
63 Ok(Self {
64 in_features,
65 out_features,
66 heads,
67 edge_dim,
68 query_weight,
69 key_weight,
70 value_weight,
71 edge_weight,
72 output_weight,
73 bias,
74 dropout,
75 })
76 }
77
78 pub fn in_features(&self) -> usize {
80 self.in_features
81 }
82
83 pub fn out_features(&self) -> usize {
85 self.out_features
86 }
87
88 pub fn heads(&self) -> usize {
90 self.heads
91 }
92
93 pub fn edge_dim(&self) -> usize {
95 self.edge_dim
96 }
97
98 pub fn dropout(&self) -> f32 {
100 self.dropout
101 }
102
103 pub fn forward(&self, graph: &GraphData) -> Result<GraphData> {
105 let num_nodes = graph.num_nodes;
106 let head_dim = self.out_features / self.heads;
107
108 let queries = graph.x.matmul(&self.query_weight.clone_data())?;
110 let keys = graph.x.matmul(&self.key_weight.clone_data())?;
111 let values = graph.x.matmul(&self.value_weight.clone_data())?;
112
113 let q = queries.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
115 let k = keys.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
116 let v = values.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
117
118 let mut output_features = zeros(&[num_nodes, self.out_features])?;
120
121 for head in 0..self.heads {
123 let head_dim_start = head * head_dim;
124 let head_dim_end = (head + 1) * head_dim;
125
126 let q_head = q.slice(1, head, head + 1)?;
127 let k_head = k.slice(1, head, head + 1)?;
128 let v_head = v.slice(1, head, head + 1)?;
129
130 let scale = 1.0 / (head_dim as f64).sqrt();
132 let k_head_tensor = k_head.to_tensor()?.squeeze_tensor(1)?;
133 let q_head_tensor = q_head.to_tensor()?.squeeze_tensor(1)?;
134 let v_head_tensor = v_head.to_tensor()?.squeeze_tensor(1)?;
135
136 let k_transposed = k_head_tensor.transpose(0, 1)?;
137 let attention_scores = q_head_tensor
138 .matmul(&k_transposed)?
139 .mul_scalar(scale as f32)?;
140 let attention_weights = attention_scores.softmax(-1)?;
141 let head_output = attention_weights.matmul(&v_head_tensor)?;
142
143 let output_slice = output_features.slice(1, head_dim_start, head_dim_end)?;
145 let mut output_slice_tensor = output_slice.to_tensor()?;
147 output_slice_tensor.copy_(&head_output)?;
148 }
149
150 output_features = output_features.matmul(&self.output_weight.clone_data())?;
152
153 if let Some(ref bias) = self.bias {
155 output_features = output_features.add(&bias.clone_data())?;
156 }
157
158 Ok(GraphData {
159 x: output_features,
160 edge_index: graph.edge_index.clone(),
161 edge_attr: graph.edge_attr.clone(),
162 batch: graph.batch.clone(),
163 num_nodes: graph.num_nodes,
164 num_edges: graph.num_edges,
165 })
166 }
167}
168
169impl GraphLayer for GraphTransformer {
170 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
171 self.forward(graph)
172 }
173
174 fn parameters(&self) -> Vec<Tensor> {
175 let mut params = vec![
176 self.query_weight.clone_data(),
177 self.key_weight.clone_data(),
178 self.value_weight.clone_data(),
179 self.edge_weight.clone_data(),
180 self.output_weight.clone_data(),
181 ];
182 if let Some(ref bias) = self.bias {
183 params.push(bias.clone_data());
184 }
185 params
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use torsh_core::device::DeviceType;
193 use torsh_tensor::creation::from_vec;
194
195 #[test]
196 fn test_transformer_creation() {
197 let transformer =
198 GraphTransformer::new(16, 32, 8, 4, 0.1, true).expect("operation should succeed");
199 let params = transformer.parameters();
200 assert_eq!(params.len(), 6); assert_eq!(transformer.heads, 8);
202 }
203
204 #[test]
205 fn test_transformer_forward() {
206 let transformer = GraphTransformer::new(6, 12, 3, 2, 0.0, false);
207
208 let x = from_vec(
210 vec![
211 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, ],
215 &[3, 6],
216 DeviceType::Cpu,
217 )
218 .expect("operation should succeed");
219 let edge_index = from_vec(vec![0.0, 1.0, 2.0, 1.0, 2.0, 0.0], &[2, 3], DeviceType::Cpu)
220 .expect("from vec should succeed");
221 let graph = GraphData::new(x, edge_index);
222
223 let output = transformer
224 .expect("operation should succeed")
225 .forward(&graph)
226 .expect("operation should succeed");
227 assert_eq!(output.x.shape().dims(), &[3, 12]);
228 assert_eq!(output.num_nodes, 3);
229 }
230}