1use crate::esm3::layers::relative_position::RelativePositionEmbedding;
12use crate::esm3::layers::transformer_stack::TransformerStack;
13use crate::esm3::models::esm3::ESM3Config;
14use crate::esm3::utils::affine3d::Affine3D;
15use candle_core::{D, DType, Module, Result, Tensor};
16use candle_nn::{self as nn, VarBuilder};
17
18#[derive(Debug, Clone)]
21pub struct VqVaeConfig {
22 pub enc_d_model: usize, pub enc_n_heads: usize, pub enc_v_heads: usize, pub enc_n_layers: usize, pub d_codebook: usize, pub n_codes: usize, pub dec_d_model: usize, pub dec_n_heads: usize, pub dec_n_layers: usize, }
35
36impl Default for VqVaeConfig {
37 fn default() -> Self {
38 Self {
39 enc_d_model: 1024,
40 enc_n_heads: 1,
41 enc_v_heads: 128,
42 enc_n_layers: 2,
43 d_codebook: 128,
44 n_codes: 4096,
45 dec_d_model: 1280,
46 dec_n_heads: 20,
47 dec_n_layers: 30,
48 }
49 }
50}
51
52impl VqVaeConfig {
53 fn encoder_esm3_config(&self) -> ESM3Config {
57 ESM3Config {
58 d_model: self.enc_d_model,
59 n_heads: self.enc_n_heads,
60 n_layers: self.enc_n_layers,
61 n_layers_geom: self.enc_n_layers, v_head_transformer: self.enc_v_heads,
63 expansion_ratio: 4.0,
69 scale_residue: false,
70 mask_and_zero_frameless: true,
71 qk_layernorm: true,
72 bias: true,
76 d_sequence_vocab: 0,
78 d_structure_vocab: self.n_codes,
79 d_ss8_vocab: 0,
80 d_sasa_vocab: 0,
81 n_function_tracks: 0,
82 d_function_vocab: 0,
83 d_residue_vocab: 0,
84 n_rbf_bins: 0,
85 }
86 }
87}
88
89struct VqCodebook {
94 embeddings: Tensor, }
96
97impl VqCodebook {
98 pub fn load(vb: VarBuilder, n_codes: usize, d_codebook: usize) -> Result<Self> {
99 let embeddings = vb.get((n_codes, d_codebook), "embeddings")?;
100 Ok(Self { embeddings })
101 }
102
103 pub fn quantize(&self, z: &Tensor) -> Result<Tensor> {
108 let (b, l, d) = z.dims3()?;
109 let z_flat = z.reshape((b * l, d))?; let z_sq = z_flat.sqr()?.sum_keepdim(1)?; let z_et = z_flat.matmul(&self.embeddings.transpose(0, 1)?)?; let e_sq = self.embeddings.sqr()?.sum_keepdim(1)?.transpose(0, 1)?; let distances = z_sq
118 .broadcast_sub(&z_et.affine(2.0, 0.0)?)?
119 .broadcast_add(&e_sq)?;
120
121 let indices = distances.argmin(1)?; indices.reshape((b, l))
123 }
124}
125
126pub const KNN: usize = 16;
130
131const FAR: f64 = 1e9;
137
138fn knn_edges(ca: &Tensor, valid: &Tensor, knn: usize) -> Result<Tensor> {
151 let (b, l, _) = ca.dims3()?;
152 let knn = knn.min(l);
153
154 let sq = ca.sqr()?.sum_keepdim(D::Minus1)?; let cross = ca.matmul(&ca.transpose(1, 2)?)?; let d2 = (sq.broadcast_add(&sq.transpose(1, 2)?)? - cross.affine(2.0, 0.0)?)?;
158
159 let invalid = valid
161 .to_dtype(d2.dtype())?
162 .affine(-1.0, 1.0)? .reshape((b, 1, l))?
164 .broadcast_as((b, l, l))?;
165 let d2 = (d2 + invalid.affine(FAR, 0.0)?)?;
166
167 let order = d2.arg_sort_last_dim(true)?; order.narrow(D::Minus1, 0, knn)?.contiguous()
169}
170
171fn node_gather(src: &Tensor, edges: &Tensor) -> Result<Tensor> {
176 let (b, l, d) = src.dims3()?;
177 let k = edges.dim(D::Minus1)?;
178 let idx = edges
181 .reshape((b, l * k, 1))?
182 .broadcast_as((b, l * k, d))?
183 .contiguous()?;
184 src.contiguous()?.gather(&idx, 1)?.reshape((b, l, k, d))
185}
186
187pub struct StructureTokenEncoder {
197 transformer: TransformerStack,
198 relative_positional_embedding: RelativePositionEmbedding,
199 pre_vq_proj: nn::Linear,
200 codebook: VqCodebook,
201 config: VqVaeConfig,
202}
203
204impl StructureTokenEncoder {
205 pub fn load(vb: VarBuilder, config: VqVaeConfig) -> Result<Self> {
213 let enc_cfg = config.encoder_esm3_config();
214 let transformer = TransformerStack::load_geometric_encoder(vb.pp("transformer"), &enc_cfg)?;
215 let relative_positional_embedding = RelativePositionEmbedding::load(
216 vb.pp("relative_positional_embedding"),
217 config.enc_d_model,
218 )?;
219 let pre_vq_proj = nn::linear(config.enc_d_model, config.d_codebook, vb.pp("pre_vq_proj"))?;
220 let codebook = VqCodebook::load(vb.pp("codebook"), config.n_codes, config.d_codebook)?;
221 Ok(Self {
222 transformer,
223 relative_positional_embedding,
224 pre_vq_proj,
225 codebook,
226 config,
227 })
228 }
229
230 pub fn encode(&self, coords: &Tensor, sequence_id: Option<&Tensor>) -> Result<Tensor> {
246 let (b, l, _, _) = coords.dims4()?;
247 let device = coords.device();
248 let dtype = coords.dtype();
249
250 let (affine, affine_mask) = Affine3D::build_affine3d_from_coordinates(coords)?;
251
252 let ca = coords.narrow(D::Minus2, 1, 1)?.squeeze(D::Minus2)?; let edges = knn_edges(&ca, &affine_mask, KNN)?; let k = edges.dim(D::Minus1)?;
256
257 let rot =
260 node_gather(&affine.rot.reshape((b, l, 9))?, &edges)?.reshape((b * l, k, 3, 3))?;
261 let trans = node_gather(&affine.trans, &edges)?.reshape((b * l, k, 3))?;
262 let knn_affine = Affine3D::new(rot, trans);
263
264 let knn_affine_mask =
265 node_gather(&affine_mask.to_dtype(dtype)?.unsqueeze(D::Minus1)?, &edges)?
266 .reshape((b * l, k))?
267 .to_dtype(affine_mask.dtype())?;
268
269 let knn_sequence_id = match sequence_id {
270 Some(sid) => node_gather(&sid.to_dtype(dtype)?.unsqueeze(D::Minus1)?, &edges)?
271 .reshape((b * l, k))?
272 .to_dtype(sid.dtype())?,
273 None => Tensor::zeros((b * l, k), DType::U32, device)?,
274 };
275 let chain_id = Tensor::zeros((b * l, k), DType::U32, device)?;
276
277 let res_idxs = edges.reshape((b * l, k))?;
280 let centre = res_idxs.narrow(D::Minus1, 0, 1)?.squeeze(D::Minus1)?;
281 let z = self
282 .relative_positional_embedding
283 .forward(¢re, &res_idxs)?
284 .to_dtype(dtype)?;
285
286 let (z, _pre_norm) = self.transformer.forward(
287 &z,
288 Some(&knn_sequence_id),
289 Some(&knn_affine),
290 Some(&knn_affine_mask),
291 Some(&chain_id),
292 )?;
293
294 let z = z
297 .reshape((b, l, k, self.config.enc_d_model))?
298 .narrow(2, 0, 1)?
299 .squeeze(2)?;
300
301 let z = self.pre_vq_proj.forward(&z)?;
302 self.codebook.quantize(&z)
303 }
304}
305
306pub struct StructureTokenDecoder;
313
314impl StructureTokenDecoder {
315 pub fn stub() -> Self {
316 Self
317 }
318}
319
320#[cfg(test)]
323mod tests {
324 use super::*;
325 use candle_core::{Device, Tensor};
326
327 #[test]
328 fn test_vq_codebook_quantize_shape() -> Result<()> {
329 let device = &Device::Cpu;
330 let n_codes = 16usize;
331 let d = 8usize;
332 let b = 2usize;
333 let l = 5usize;
334
335 let embeddings = Tensor::randn(0f32, 1f32, (n_codes, d), device)?;
336 let codebook = VqCodebook { embeddings };
337
338 let z = Tensor::randn(0f32, 1f32, (b, l, d), device)?;
339 let tokens = codebook.quantize(&z)?;
340
341 assert_eq!(tokens.shape().dims(), &[b, l]);
342 Ok(())
343 }
344
345 #[test]
346 fn test_vq_codebook_nearest_neighbour() -> Result<()> {
347 let device = &Device::Cpu;
348 let embeddings = Tensor::new(&[[1f32, 0.], [-1., 0.]], device)?;
350 let codebook = VqCodebook { embeddings };
351
352 let z = Tensor::new(&[[[0.9f32, 0.1]]], device)?; let tokens = codebook.quantize(&z)?;
355 assert_eq!(tokens.to_vec2::<u32>()?, vec![vec![0u32]]);
356
357 let z = Tensor::new(&[[[-0.8f32, 0.1]]], device)?;
359 let tokens = codebook.quantize(&z)?;
360 assert_eq!(tokens.to_vec2::<u32>()?, vec![vec![1u32]]);
361 Ok(())
362 }
363
364 #[test]
365 fn test_vqvae_config_encoder_esm3_config() {
366 let cfg = VqVaeConfig::default();
367 let enc = cfg.encoder_esm3_config();
368 assert_eq!(enc.d_model, 1024);
369 assert_eq!(enc.n_heads, 1);
370 assert_eq!(enc.n_layers, 2);
371 assert_eq!(
372 enc.n_layers_geom, 2,
373 "all layers should use geometric attention"
374 );
375 assert_eq!(enc.v_head_transformer, 128);
376 assert!(!enc.scale_residue);
377 assert!(enc.mask_and_zero_frameless);
378 }
379}
380
381#[cfg(test)]
382mod encoder_tests {
383 use super::*;
384 use candle_core::Device;
385
386 fn helix(l: usize) -> Result<Tensor> {
391 let (r, rise, turn) = (2.3f64, 1.5f64, 100f64.to_radians());
392 let mut v: Vec<f32> = Vec::with_capacity(l * 9);
393 for i in 0..l {
394 for (k, off) in [(-1.0f64), 0.0, 1.0].iter().enumerate() {
395 let _ = k;
396 let t = (i as f64 + off * 0.35) * turn;
397 let z = (i as f64 + off * 0.35) * rise;
398 v.extend_from_slice(&[(r * t.cos()) as f32, (r * t.sin()) as f32, z as f32]);
399 }
400 }
401 Tensor::from_vec(v, (1, l, 3, 3), &Device::Cpu)
402 }
403
404 #[test]
410 fn test_first_neighbour_is_the_residue_itself() -> Result<()> {
411 let coords = helix(12)?;
412 let ca = coords.narrow(D::Minus2, 1, 1)?.squeeze(D::Minus2)?;
413 let valid = Tensor::ones((1, 12), DType::U8, &Device::Cpu)?;
414 let edges = knn_edges(&ca, &valid, KNN)?;
415 let first = edges
416 .narrow(D::Minus1, 0, 1)?
417 .flatten_all()?
418 .to_vec1::<u32>()?;
419 assert_eq!(
420 first,
421 (0..12).collect::<Vec<u32>>(),
422 "a residue is at distance zero from itself, so it must sort first"
423 );
424 Ok(())
425 }
426
427 #[test]
429 fn test_neighbours_are_distance_sorted() -> Result<()> {
430 let coords = helix(20)?;
431 let ca = coords.narrow(D::Minus2, 1, 1)?.squeeze(D::Minus2)?;
432 let valid = Tensor::ones((1, 20), DType::U8, &Device::Cpu)?;
433 let edges = knn_edges(&ca, &valid, KNN)?
434 .flatten_all()?
435 .to_vec1::<u32>()?;
436 let ca_v = ca.flatten_all()?.to_vec1::<f32>()?;
437 let k = KNN.min(20);
438 for i in 0..20 {
439 let mut prev = -1.0f32;
440 for j in 0..k {
441 let n = edges[i * k + j] as usize;
442 let d: f32 = (0..3)
443 .map(|c| (ca_v[i * 3 + c] - ca_v[n * 3 + c]).powi(2))
444 .sum();
445 assert!(
446 d >= prev - 1e-4,
447 "neighbour {j} of residue {i} is out of order"
448 );
449 prev = d;
450 }
451 }
452 Ok(())
453 }
454
455 #[test]
458 fn test_invalid_residues_are_not_selected_as_neighbours() -> Result<()> {
459 let coords = helix(20)?;
460 let ca = coords.narrow(D::Minus2, 1, 1)?.squeeze(D::Minus2)?;
461 let mut mask = vec![1u8; 20];
463 mask[1] = 0;
464 let valid = Tensor::from_vec(mask, (1, 20), &Device::Cpu)?;
465 let edges = knn_edges(&ca, &valid, KNN)?
466 .flatten_all()?
467 .to_vec1::<u32>()?;
468 let k = KNN.min(20);
469 assert!(
471 !edges[0..k / 2].contains(&1),
472 "an invalid residue must not rank as a near neighbour: {:?}",
473 &edges[0..k]
474 );
475 Ok(())
476 }
477
478 #[test]
479 fn test_node_gather_selects_the_named_rows() -> Result<()> {
480 let src = Tensor::from_vec(
482 vec![0f32, 0., 1., 10., 2., 20., 3., 30.],
483 (1, 4, 2),
484 &Device::Cpu,
485 )?;
486 let edges = Tensor::from_vec(vec![2u32, 0, 3, 1, 0, 3, 1, 2], (1, 4, 2), &Device::Cpu)?;
488 let out = node_gather(&src, &edges)?;
489 assert_eq!(out.dims(), &[1, 4, 2, 2]);
490 assert_eq!(
491 out.flatten_all()?.to_vec1::<f32>()?,
492 vec![
493 2., 20., 0., 0., 3., 30., 1., 10., 0., 0., 3., 30., 1., 10., 2., 20., ]
498 );
499 Ok(())
500 }
501}