1use candle_core::{D, DType, Result, Tensor};
35use candle_nn::{self as nn, LayerNorm, LayerNormConfig, Module, VarBuilder};
36
37struct FourierEmbedding {
45 weights: Tensor, dim: usize,
47}
48
49impl FourierEmbedding {
50 fn load(vb: VarBuilder, fourier_dim: usize) -> Result<Self> {
51 let weights = vb.get((fourier_dim / 2,), "weights")?;
52 Ok(Self {
53 weights,
54 dim: fourier_dim,
55 })
56 }
57
58 fn embed(&self, sigma: f32) -> Result<Tensor> {
60 let device = self.weights.device();
61 let sigma_t = Tensor::from_vec(vec![sigma], 1, device)?.to_dtype(DType::F32)?;
62 let x = sigma_t.broadcast_mul(&self.weights)?;
64 let x = (x * (2.0 * std::f64::consts::PI))?;
65 let sin_x = x.sin()?; let cos_x = x.cos()?; Tensor::cat(&[&sin_x, &cos_x], 0) }
69}
70
71struct TokenAttn {
77 norm: LayerNorm,
78 q_proj: nn::Linear,
79 k_proj: nn::Linear,
80 v_proj: nn::Linear,
81 pair_bias: nn::Linear, gate: nn::Linear,
83 out_proj: nn::Linear,
84 n_heads: usize,
85 d_head: usize,
86}
87
88impl TokenAttn {
89 fn load(vb: VarBuilder, c_token: usize, n_heads: usize, d_pair: usize) -> Result<Self> {
90 let d_head = c_token / n_heads;
91 Ok(Self {
92 norm: nn::layer_norm(c_token, LayerNormConfig::from(1e-5), vb.pp("norm"))?,
93 q_proj: nn::linear_no_bias(c_token, n_heads * d_head, vb.pp("q_proj"))?,
94 k_proj: nn::linear_no_bias(c_token, n_heads * d_head, vb.pp("k_proj"))?,
95 v_proj: nn::linear_no_bias(c_token, n_heads * d_head, vb.pp("v_proj"))?,
96 pair_bias: nn::linear_no_bias(d_pair, n_heads, vb.pp("pair_bias"))?,
97 gate: nn::linear_no_bias(c_token, n_heads * d_head, vb.pp("gate"))?,
98 out_proj: nn::linear_no_bias(n_heads * d_head, c_token, vb.pp("out_proj"))?,
99 n_heads,
100 d_head,
101 })
102 }
103
104 fn forward(&self, x: &Tensor, pair: &Tensor) -> Result<Tensor> {
106 let (b, n, _) = x.dims3()?;
107 let (h, dh) = (self.n_heads, self.d_head);
108
109 let x_n = self.norm.forward(x)?;
110
111 let q = self.q_proj.forward(&x_n)?; let k = self.k_proj.forward(&x_n)?;
113 let v = self.v_proj.forward(&x_n)?;
114 let gate = nn::ops::sigmoid(&self.gate.forward(&x_n)?)?; let pair_b = self
118 .pair_bias
119 .forward(pair)? .permute((0, 3, 1, 2))?
121 .contiguous()?; let to_heads = |t: Tensor| -> Result<Tensor> {
125 t.reshape((b, n, h, dh))?
126 .permute((0, 2, 1, 3))?
127 .contiguous()
128 };
129 let q = to_heads(q)?;
130 let k = to_heads(k)?;
131 let v = to_heads(v)?;
132
133 let scale = (dh as f64).sqrt();
134 let scores = (q.matmul(&k.transpose(D::Minus2, D::Minus1)?.contiguous()?)? / scale)?;
135 let scores = (scores + pair_b)?;
137 let attn = nn::ops::softmax(&scores, D::Minus1)?;
138 let out = attn.matmul(&v)?; let out = out
142 .permute((0, 2, 1, 3))?
143 .contiguous()? .reshape((b, n, h * dh))?;
145
146 let out = (gate * out)?;
147 let out = self.out_proj.forward(&out)?;
148 x + &out
149 }
150}
151
152struct TokenFfn {
153 norm: LayerNorm,
154 gate_up: nn::Linear, down: nn::Linear, }
157
158impl TokenFfn {
159 fn load(vb: VarBuilder, c_token: usize) -> Result<Self> {
160 let hidden = ((8.0 / 3.0 * c_token as f64 + 255.0) / 256.0).floor() as usize * 256;
162 Ok(Self {
163 norm: nn::layer_norm(c_token, LayerNormConfig::from(1e-5), vb.pp("norm"))?,
164 gate_up: nn::linear_no_bias(c_token, hidden * 2, vb.pp("gate_up"))?,
165 down: nn::linear_no_bias(hidden, c_token, vb.pp("down"))?,
166 })
167 }
168
169 fn forward(&self, x: &Tensor) -> Result<Tensor> {
170 let h = self.gate_up.forward(&self.norm.forward(x)?)?;
171 let chunks = h.chunk(2, D::Minus1)?;
172 let act = (chunks[0].silu()? * &chunks[1])?;
173 let out = self.down.forward(&act)?;
174 x + &out
175 }
176}
177
178struct TokenBlock {
179 attn: TokenAttn,
180 ffn: TokenFfn,
181}
182
183impl TokenBlock {
184 fn load(vb: VarBuilder, c_token: usize, n_heads: usize, d_pair: usize) -> Result<Self> {
185 Ok(Self {
186 attn: TokenAttn::load(vb.pp("attn"), c_token, n_heads, d_pair)?,
187 ffn: TokenFfn::load(vb.pp("ffn"), c_token)?,
188 })
189 }
190
191 fn forward(&self, x: &Tensor, pair: &Tensor) -> Result<Tensor> {
192 let x = self.attn.forward(x, pair)?;
193 self.ffn.forward(&x)
194 }
195}
196
197pub struct DiffusionModule {
205 token_proj: nn::Linear, noise_emb: FourierEmbedding,
207 noise_proj: nn::Linear, blocks: Vec<TokenBlock>, out_proj: nn::Linear, c_token: usize,
212 d_single: usize,
213 s_max: f64,
215 s_min: f64,
216 p: f64,
217 gamma_0: f64,
218 noise_scale: f64,
219 step_scale: f64,
220 device: candle_core::Device,
221}
222
223impl DiffusionModule {
224 pub fn load(
236 vb: VarBuilder,
237 c_token: usize,
238 c_atom: usize,
239 d_single: usize,
240 d_pair: usize,
241 n_token_blocks: usize,
242 n_token_heads: usize,
243 fourier_dim: usize,
244 ) -> Result<Self> {
245 let _ = c_atom; let token_proj = nn::linear_no_bias(d_single, c_token, vb.pp("token_proj"))?;
248 let noise_emb = FourierEmbedding::load(vb.pp("noise_embedding"), fourier_dim)?;
249 let noise_proj = nn::linear_no_bias(fourier_dim, c_token, vb.pp("noise_proj"))?;
250
251 let blocks = (0..n_token_blocks)
252 .map(|i| {
253 TokenBlock::load(
254 vb.pp(format!("token_transformer.blocks.{i}")),
255 c_token,
256 n_token_heads,
257 d_pair,
258 )
259 })
260 .collect::<Result<Vec<_>>>()?;
261
262 let out_proj = nn::linear_no_bias(c_token, 3, vb.pp("out_proj"))?;
263 let device = vb.device().clone();
264
265 Ok(Self {
266 token_proj,
267 noise_emb,
268 noise_proj,
269 blocks,
270 out_proj,
271 c_token,
272 d_single,
273 s_max: 160.0,
274 s_min: 0.0004,
275 p: 7.0,
276 gamma_0: 0.8,
277 noise_scale: 1.003,
278 step_scale: 1.5,
279 device,
280 })
281 }
282
283 fn sigma_schedule(&self, num_steps: usize) -> Vec<f32> {
285 (0..=num_steps)
286 .map(|t| {
287 let frac = t as f64 / num_steps as f64;
288 (self.s_max * (self.s_min / self.s_max).powf(frac.powf(self.p))) as f32
289 })
290 .collect()
291 }
292
293 fn score_network(
298 &self,
299 noisy: &Tensor,
300 sigma: f32,
301 single: &Tensor,
302 pair: &Tensor,
303 ) -> Result<Tensor> {
304 let (b, n, _) = single.dims3()?;
305
306 let mut tok = self.token_proj.forward(single)?; let noise_feat = self
309 .noise_proj
310 .forward(&self.noise_emb.embed(sigma)?.unsqueeze(0)?.unsqueeze(0)?)?; tok = tok.broadcast_add(&noise_feat)?;
312
313 for block in &self.blocks {
315 tok = block.forward(&tok, pair)?;
316 }
317
318 let coords = self.out_proj.forward(&tok)?; let scale = Tensor::from_vec(vec![sigma], 1, &self.device)?.to_dtype(DType::F32)?;
324 let _ = (b, n, noisy); coords.broadcast_mul(&scale.reshape((1, 1, 1))?.broadcast_as(coords.shape())?)
326 }
327
328 pub fn forward(
339 &self,
340 single: &Tensor,
341 pair: &Tensor,
342 n_atoms: usize,
343 num_steps: usize,
344 ) -> Result<Tensor> {
345 let (b, _n_tok, _) = single.dims3()?;
346 let sigmas = self.sigma_schedule(num_steps);
347
348 let sigma_0 = sigmas[0];
350 let mut x = Tensor::randn(0f32, sigma_0, (b, n_atoms, 3_usize), &self.device)?;
351
352 for step in 0..num_steps {
353 let sigma_t = sigmas[step];
354 let sigma_next = sigmas[step + 1];
355
356 let gamma = (self.gamma_0 as f32)
358 .min((sigma_next / sigma_t).sqrt() - 1.0)
359 .max(0.0);
360 let sigma_hat = sigma_t * (1.0 + gamma);
361
362 let x_hat = if gamma > 0.0 {
364 let extra_noise_std =
365 (sigma_hat * sigma_hat - sigma_t * sigma_t).sqrt() * self.noise_scale as f32;
366 let noise = Tensor::randn(0f32, extra_noise_std, x.shape(), &self.device)?;
367 (&x + &noise)?
368 } else {
369 x.clone()
370 };
371
372 let d = self.score_network(&x_hat, sigma_hat, single, pair)?;
374
375 let ratio = sigma_next / sigma_hat;
377 let step_factor = self.step_scale * (ratio as f64 - 1.0); let diff = (d - &x_hat)?;
380 x = (x_hat + (diff * step_factor)?)?;
381 }
382
383 Ok(x)
384 }
385}
386
387#[cfg(test)]
390mod tests {
391 use super::*;
392 use candle_core::{Device, Tensor};
393
394 const B: usize = 1;
395 const N: usize = 6;
396 const D_SINGLE: usize = 64; const D_PAIR: usize = 32;
398 const C_TOKEN: usize = 64;
399 const C_ATOM: usize = 16;
400 const N_HEADS: usize = 4;
401 const FOURIER_DIM: usize = 16;
402 const N_BLOCKS: usize = 2; fn make_module(device: &Device) -> DiffusionModule {
405 let vb = VarBuilder::zeros(DType::F32, device);
406 DiffusionModule::load(
407 vb,
408 C_TOKEN,
409 C_ATOM,
410 D_SINGLE,
411 D_PAIR,
412 N_BLOCKS,
413 N_HEADS,
414 FOURIER_DIM,
415 )
416 .unwrap()
417 }
418
419 #[test]
420 fn test_fourier_embedding_shape() {
421 let device = Device::Cpu;
422 let vb = VarBuilder::zeros(DType::F32, &device);
423 let emb = FourierEmbedding::load(vb, FOURIER_DIM).unwrap();
424 let out = emb.embed(1.0).unwrap();
425 assert_eq!(out.dims(), &[FOURIER_DIM]);
426 }
427
428 #[test]
429 fn test_sigma_schedule_length() {
430 let device = Device::Cpu;
431 let m = make_module(&device);
432 let sigmas = m.sigma_schedule(14);
433 assert_eq!(sigmas.len(), 15); assert!(sigmas[0] > sigmas[14], "sigma should decrease");
435 }
436
437 #[test]
438 fn test_sigma_schedule_bounds() {
439 let device = Device::Cpu;
440 let m = make_module(&device);
441 let sigmas = m.sigma_schedule(14);
442 assert!(sigmas[0] > 100.0, "first sigma near s_max=160");
444 assert!(sigmas[14] < 1.0, "last sigma near s_min=0.0004");
445 }
446
447 #[test]
448 fn test_token_block_shape() {
449 let device = Device::Cpu;
450 let vb = VarBuilder::zeros(DType::F32, &device);
451 let block = TokenBlock::load(vb, C_TOKEN, N_HEADS, D_PAIR).unwrap();
452 let x = Tensor::zeros(&[B, N, C_TOKEN], DType::F32, &device).unwrap();
453 let pair = Tensor::zeros(&[B, N, N, D_PAIR], DType::F32, &device).unwrap();
454 let out = block.forward(&x, &pair).unwrap();
455 assert_eq!(out.dims(), &[B, N, C_TOKEN]);
456 }
457
458 #[test]
459 fn test_diffusion_forward_shape() {
460 let device = Device::Cpu;
461 let m = make_module(&device);
462 let single = Tensor::zeros(&[B, N, D_SINGLE], DType::F32, &device).unwrap();
463 let pair = Tensor::zeros(&[B, N, N, D_PAIR], DType::F32, &device).unwrap();
464 let out = m.forward(&single, &pair, N, 2).unwrap();
465 assert_eq!(out.dims(), &[B, N, 3]);
466 }
467
468 #[test]
469 fn test_diffusion_batch_shape() {
470 let device = Device::Cpu;
471 let m = make_module(&device);
472 let single = Tensor::zeros(&[2, N, D_SINGLE], DType::F32, &device).unwrap();
473 let pair = Tensor::zeros(&[2, N, N, D_PAIR], DType::F32, &device).unwrap();
474 let out = m.forward(&single, &pair, N, 2).unwrap();
475 assert_eq!(out.dims(), &[2, N, 3]);
476 }
477}