ferritin_plms/esmc/layers/
geom_attention.rs1use crate::esm3::utils::affine3d::Affine3D;
2use crate::esmc::models::esmc::ESMCConfig;
3use candle_core::{D, Module, Result, Tensor};
4use candle_nn::{self as nn, LayerNorm, LayerNormConfig, Linear, VarBuilder};
5
6const SQRT_3: f64 = 1.7320508075688772;
7
8#[allow(dead_code)]
9pub struct GeometricReasoningOriginalImpl {
10 c_s: usize,
11 v_heads: usize,
12 num_vector_messages: usize,
13 mask_and_zero_frameless: bool,
14 s_norm: LayerNorm,
15 proj: Linear,
16 out_proj: Linear,
17 distance_scale_per_head: Tensor,
18 rotation_scale_per_head: Tensor,
19}
20
21impl GeometricReasoningOriginalImpl {
22 pub fn load(vb: VarBuilder, config: &ESMCConfig) -> Result<Self> {
62 let ESMCConfig {
63 d_model,
64 v_head_transformer,
65 mask_and_zero_frameless,
66 bias,
67 ..
68 } = config;
69
70 let num_vector_messages = 1usize;
71
72 let v_heads = v_head_transformer.ok_or_else(|| {
77 candle_core::Error::Msg(
78 "geometric attention needs v_head_transformer to be set; it determines the \
79 projection sizes and cannot be guessed"
80 .to_string(),
81 )
82 })?;
83
84 let dim_proj = 4 * v_heads * 3 + v_heads * 3 * num_vector_messages;
85 let channels_out = v_heads * 3 * num_vector_messages;
86
87 let s_norm = if *bias {
88 nn::layer_norm(*d_model, LayerNormConfig::from(1e-5), vb.pp("s_norm"))?
89 } else {
90 LayerNorm::new_no_bias(vb.pp("s_norm").get((*d_model,), "weight")?, 1e-5)
91 };
92
93 let (proj, out_proj) = if *bias {
94 (
95 nn::linear(*d_model, dim_proj, vb.pp("proj"))?,
96 nn::linear(channels_out, *d_model, vb.pp("out_proj"))?,
97 )
98 } else {
99 (
100 nn::linear_no_bias(*d_model, dim_proj, vb.pp("proj"))?,
101 nn::linear_no_bias(channels_out, *d_model, vb.pp("out_proj"))?,
102 )
103 };
104
105 let distance_scale_per_head = vb.get((v_heads,), "distance_scale_per_head")?;
109 let rotation_scale_per_head = vb.get((v_heads,), "rotation_scale_per_head")?;
110
111 Ok(Self {
112 c_s: *d_model,
113 v_heads,
114 num_vector_messages,
115 mask_and_zero_frameless: *mask_and_zero_frameless,
116 s_norm,
117 proj,
118 out_proj,
119 distance_scale_per_head,
120 rotation_scale_per_head,
121 })
122 }
123
124 pub fn forward(
134 &self,
135 s: &Tensor,
136 affine: &Affine3D,
137 affine_mask: &Tensor,
138 sequence_id: Option<&Tensor>,
139 chain_id: Option<&Tensor>,
140 ) -> Result<Tensor> {
141 let (b, l, _) = s.dims3()?;
142 let dtype = s.dtype();
143 let device = s.device();
144
145 let attn_bias = if let Some(seq_id) = sequence_id {
148 let seq_q = seq_id.unsqueeze(D::Minus1)?; let seq_k = seq_id.unsqueeze(D::Minus2)?; let same_seq = seq_q
152 .broadcast_as((b, l, l))?
153 .eq(&seq_k.broadcast_as((b, l, l))?)?
154 .to_dtype(dtype)?;
155 same_seq.unsqueeze(1)? } else {
157 Tensor::ones((b, 1, l, l), dtype, device)?
158 };
159
160 let neg_inf =
162 Tensor::full(f32::NEG_INFINITY as f64, attn_bias.shape(), device)?.to_dtype(dtype)?;
163 let frame_mask_k = affine_mask
165 .unsqueeze(1)?
166 .unsqueeze(1)?
167 .broadcast_as(attn_bias.shape())?;
168 let mut attn_bias = frame_mask_k.where_cond(&attn_bias, &neg_inf)?;
169
170 if let Some(cid) = chain_id {
172 let chain_q = cid.unsqueeze(D::Minus1)?.broadcast_as((b, l, l))?;
173 let chain_k = cid.unsqueeze(D::Minus2)?.broadcast_as((b, l, l))?;
174 let diff_chain = chain_q.ne(&chain_k)?.unsqueeze(1)?; let diff_bias = diff_chain.broadcast_as(attn_bias.shape())?;
176 attn_bias = diff_bias.where_cond(&neg_inf, &attn_bias)?;
177 }
178
179 let ns = self.s_norm.forward(s)?;
181 let proj_out = self.proj.forward(&ns)?;
182
183 let vec_rot_size = self.v_heads * 2 * 3 + self.v_heads * 3 * self.num_vector_messages;
184 let vec_dist_size = self.v_heads * 2 * 3;
185 let vec_rot = proj_out.narrow(D::Minus1, 0, vec_rot_size)?;
186 let vec_dist = proj_out.narrow(D::Minus1, vec_rot_size, vec_dist_size)?;
187
188 let h_rot = 2 * self.v_heads + self.v_heads * self.num_vector_messages;
191 let vec_rot = vec_rot.reshape((b, l, h_rot, 3))?;
192 let vec_rot = Affine3D::apply_rot(&affine.rot, &vec_rot)?;
194
195 let query_rot = vec_rot.narrow(D::Minus2, 0, self.v_heads)?; let key_rot = vec_rot.narrow(D::Minus2, self.v_heads, self.v_heads)?;
197 let value = vec_rot.narrow(
198 D::Minus2,
199 2 * self.v_heads,
200 self.v_heads * self.num_vector_messages,
201 )?;
202
203 let vec_dist = vec_dist.reshape((b, l, self.v_heads * 2, 3))?;
205 let vec_dist = affine.apply(&vec_dist)?; let query_dist = vec_dist.narrow(D::Minus2, 0, self.v_heads)?; let key_dist = vec_dist.narrow(D::Minus2, self.v_heads, self.v_heads)?;
208
209 let query_rot = query_rot.permute((0, 2, 1, 3))?;
212 let key_rot = key_rot.permute((0, 2, 3, 1))?;
214 let query_dist = query_dist.permute((0, 2, 1, 3))?.unsqueeze(D::Minus2)?;
216 let key_dist = key_dist.permute((0, 2, 1, 3))?.unsqueeze(2)?;
218 let value = value
220 .reshape((b, l, self.v_heads, self.num_vector_messages * 3))?
221 .permute((0, 2, 1, 3))?;
222
223 let rotation_term = query_rot
227 .contiguous()?
228 .matmul(&key_rot.contiguous()?)?
229 .affine(SQRT_3.recip(), 0.0)?;
230
231 let diff = query_dist
233 .broadcast_as((b, self.v_heads, l, l, 3))?
234 .sub(&key_dist.broadcast_as((b, self.v_heads, l, l, 3))?)?;
235 let distance_term = diff
236 .sqr()?
237 .sum(D::Minus1)?
238 .sqrt()?
239 .affine(SQRT_3.recip(), 0.0)?;
240
241 let dist_w = softplus(&self.distance_scale_per_head)?
243 .reshape((1, self.v_heads, 1, 1))?
244 .broadcast_as((b, self.v_heads, l, l))?;
245 let rot_w = softplus(&self.rotation_scale_per_head)?
246 .reshape((1, self.v_heads, 1, 1))?
247 .broadcast_as((b, self.v_heads, l, l))?;
248
249 let mut attn_weight = rotation_term
250 .mul(&rot_w)?
251 .sub(&distance_term.mul(&dist_w)?)?;
252
253 let attn_bias = attn_bias.broadcast_as(attn_weight.shape())?;
255 attn_weight = attn_weight.add(&attn_bias)?;
256
257 let attn_weight = candle_nn::ops::softmax(&attn_weight, D::Minus1)?;
258
259 let attn_out = attn_weight.matmul(&value.contiguous()?)?;
262
263 let attn_out = attn_out
265 .permute((0, 2, 1, 3))? .contiguous()?
267 .reshape((b, l, self.v_heads * self.num_vector_messages, 3))?;
268
269 let attn_out = Affine3D::apply_rot_inv(&affine.rot, &attn_out)?;
271
272 let mut attn_out =
274 attn_out
275 .contiguous()?
276 .reshape((b, l, self.v_heads * self.num_vector_messages * 3))?;
277
278 if self.mask_and_zero_frameless {
280 let zeros = Tensor::zeros_like(&attn_out)?;
281 let mask_exp = affine_mask
282 .unsqueeze(D::Minus1)?
283 .broadcast_as(attn_out.shape())?;
284 attn_out = mask_exp.where_cond(&attn_out, &zeros)?;
285 }
286
287 self.out_proj.forward(&attn_out)
288 }
289}
290
291fn softplus(t: &Tensor) -> Result<Tensor> {
293 t.exp()?.affine(1.0, 1.0)?.log()
295}