ferritin_plms/esm3/layers/
blocks.rs1use crate::esm3::models::esm3::ESM3Config;
4use crate::esm3::utils::affine3d::Affine3D;
5use crate::esmc::layers::attention::MultiHeadAttention;
6use crate::esmc::layers::geom_attention::GeometricReasoningOriginalImpl;
7use crate::esmc::models::esmc::{ESMCConfig, ESMTokenizer, FfnType};
8use candle_core::{D, Module, Result, Tensor};
9use candle_nn::{self as nn, VarBuilder};
10
11pub struct SwiGLU {
14 layer_norm: nn::LayerNorm,
15 linear1: nn::Linear,
16 linear2: nn::Linear,
17}
18
19impl SwiGLU {
20 fn hidden_dim(expansion_ratio: f64, d_model: usize) -> usize {
21 ((expansion_ratio * d_model as f64 + 255.0) / 256.0).floor() as usize * 256
22 }
23
24 pub fn load(vb: VarBuilder, config: &ESM3Config) -> Result<Self> {
47 let hidden = Self::hidden_dim(config.expansion_ratio, config.d_model);
48 let linear = |d_in, d_out, vb: VarBuilder| {
49 if vb.contains_tensor("bias") {
50 nn::linear(d_in, d_out, vb)
51 } else {
52 nn::linear_no_bias(d_in, d_out, vb)
53 }
54 };
55 Ok(Self {
56 layer_norm: nn::layer_norm(config.d_model, 1e-5, vb.pp("0"))?,
57 linear1: linear(config.d_model, hidden * 2, vb.pp("1"))?,
58 linear2: linear(hidden, config.d_model, vb.pp("3"))?,
59 })
60 }
61}
62
63impl Module for SwiGLU {
64 fn forward(&self, x: &Tensor) -> Result<Tensor> {
65 let x = self.layer_norm.forward(x)?;
66 let x = self.linear1.forward(&x)?;
67 let chunks = x.chunk(2, D::Minus1)?;
68 self.linear2.forward(&(chunks[0].silu()? * &chunks[1])?)
69 }
70}
71
72pub struct UnifiedTransformerBlock {
75 attn: Option<MultiHeadAttention>,
77 geom_attn: Option<GeometricReasoningOriginalImpl>,
78 ffn: SwiGLU,
79 scaling_factor: f64,
80}
81
82impl UnifiedTransformerBlock {
83 pub fn load(vb: VarBuilder, config: &ESM3Config, layer_idx: usize) -> Result<Self> {
84 let esmc_cfg = esmc_config_from_esm3(config);
86
87 let attn = Some(MultiHeadAttention::load(vb.pp("attn"), &esmc_cfg)?);
88
89 let geom_attn = if layer_idx < config.n_layers_geom {
90 Some(GeometricReasoningOriginalImpl::load(
93 vb.pp("geom_attn"),
94 &esmc_cfg,
95 )?)
96 } else {
97 None
98 };
99
100 let ffn = SwiGLU::load(vb.pp("ffn"), config)?;
101
102 Ok(Self {
103 attn,
104 geom_attn,
105 ffn,
106 scaling_factor: config.residue_scaling_factor(),
107 })
108 }
109
110 pub fn load_geometric(vb: VarBuilder, config: &ESM3Config) -> Result<Self> {
117 let esmc_cfg = esmc_config_from_esm3(config);
118 let geom_attn = Some(GeometricReasoningOriginalImpl::load(
119 vb.pp("geom_attn"),
120 &esmc_cfg,
121 )?);
122 let ffn = SwiGLU::load(vb.pp("ffn"), config)?;
123 Ok(Self {
124 attn: None,
125 geom_attn,
126 ffn,
127 scaling_factor: config.residue_scaling_factor(),
128 })
129 }
130
131 pub fn forward(
132 &self,
133 x: &Tensor,
134 sequence_id: Option<&Tensor>,
135 affine: Option<&Affine3D>,
136 affine_mask: Option<&Tensor>,
137 chain_id: Option<&Tensor>,
138 ) -> Result<Tensor> {
139 let mut x = x.clone();
140
141 if let Some(attn) = &self.attn {
143 let r1 = attn.forward(&x, sequence_id)?;
144 x = (&x + (r1 / self.scaling_factor)?)?;
145 }
146
147 if let (Some(geom), Some(aff), Some(mask)) = (&self.geom_attn, affine, affine_mask) {
149 let r2 = geom.forward(&x, aff, mask, sequence_id, chain_id)?;
150 x = (&x + (r2 / self.scaling_factor)?)?;
151 }
152
153 let r3 = self.ffn.forward(&x)?;
155 x = (&x + (r3 / self.scaling_factor)?)?;
156
157 Ok(x)
158 }
159}
160
161fn esmc_config_from_esm3(cfg: &ESM3Config) -> ESMCConfig {
163 let n_layers = cfg.n_layers;
164 ESMCConfig {
165 d_model: cfg.d_model,
166 n_heads: cfg.n_heads,
167 n_layers,
168 v_head_transformer: Some(cfg.v_head_transformer),
169 ffn_type: FfnType::SWIGLU,
170 tokenizer: ESMTokenizer::Esm3OpenSmall,
171 use_plain_attn: true,
172 n_layers_geom: cfg.n_layers_geom,
173 scale_residue: cfg.scale_residue,
174 residue_scaling_factor: cfg.residue_scaling_factor(),
175 mask_and_zero_frameless: cfg.mask_and_zero_frameless,
176 bias: cfg.bias,
177 qk_layernorm: cfg.qk_layernorm,
178 expansion_ratio: cfg.expansion_ratio,
179 regression_head_output_dim: 0,
181 regression_head_hidden_dim: 0,
182 embedding_dim: 0,
183 }
184}
185
186#[cfg(test)]
189mod tests {
190 use super::*;
191 use candle_core::{DType, Device};
192 use std::collections::HashMap;
193
194 fn cfg(d_model: usize) -> ESM3Config {
195 ESM3Config {
196 d_model,
197 expansion_ratio: 8.0 / 3.0,
198 ..ESM3Config::sm_open()
199 }
200 }
201
202 fn ffn_tensors(d_model: usize, with_bias: bool, device: &Device) -> HashMap<String, Tensor> {
205 let hidden = SwiGLU::hidden_dim(8.0 / 3.0, d_model);
206 let mut ts = HashMap::new();
207 let ones = |dims: (usize, usize)| Tensor::ones(dims, DType::F32, device).unwrap();
208 ts.insert(
209 "0.weight".into(),
210 Tensor::ones(d_model, DType::F32, device).unwrap(),
211 );
212 ts.insert(
213 "0.bias".into(),
214 Tensor::zeros(d_model, DType::F32, device).unwrap(),
215 );
216 ts.insert("1.weight".into(), ones((hidden * 2, d_model)));
217 ts.insert("3.weight".into(), ones((d_model, hidden)));
218 if with_bias {
219 ts.insert(
220 "1.bias".into(),
221 Tensor::ones(hidden * 2, DType::F32, device).unwrap(),
222 );
223 ts.insert(
224 "3.bias".into(),
225 Tensor::ones(d_model, DType::F32, device).unwrap(),
226 );
227 }
228 ts
229 }
230
231 #[test]
241 fn test_ffn_bias_follows_the_checkpoint() -> Result<()> {
242 let device = Device::Cpu;
243 let d_model = 16usize;
244
245 for with_bias in [false, true] {
246 let vb = VarBuilder::from_tensors(
247 ffn_tensors(d_model, with_bias, &device),
248 DType::F32,
249 &device,
250 );
251 let ffn = SwiGLU::load(vb, &cfg(d_model))?;
252 assert_eq!(
253 ffn.linear1.bias().is_some(),
254 with_bias,
255 "linear1 bias presence should follow the checkpoint (with_bias = {with_bias})"
256 );
257 assert_eq!(
258 ffn.linear2.bias().is_some(),
259 with_bias,
260 "linear2 bias presence should follow the checkpoint (with_bias = {with_bias})"
261 );
262 }
263 Ok(())
264 }
265
266 #[test]
270 fn test_ffn_biases_change_the_output() -> Result<()> {
271 let device = Device::Cpu;
272 let d_model = 16usize;
273 let x = Tensor::ones((1, 4, d_model), DType::F32, &device)?;
274
275 let plain = SwiGLU::load(
276 VarBuilder::from_tensors(ffn_tensors(d_model, false, &device), DType::F32, &device),
277 &cfg(d_model),
278 )?
279 .forward(&x)?;
280 let biased = SwiGLU::load(
281 VarBuilder::from_tensors(ffn_tensors(d_model, true, &device), DType::F32, &device),
282 &cfg(d_model),
283 )?
284 .forward(&x)?;
285
286 let a: Vec<f32> = plain.flatten_all()?.to_vec1()?;
287 let b: Vec<f32> = biased.flatten_all()?.to_vec1()?;
288 let diff = a
289 .iter()
290 .zip(&b)
291 .map(|(x, y)| (x - y).abs())
292 .fold(0f32, f32::max);
293 assert!(
294 diff > 1e-3,
295 "the biases should change the output; max diff {diff}"
296 );
297 Ok(())
298 }
299}