1use super::esm2::{ESM2, ESM2Config, ESM2Output};
5use crate::plm_runner::PlmRunner;
6use crate::types::PseudoProbability;
7use anyhow::{Error as E, Result, anyhow};
8use candle_core::{DType, Device, Tensor};
9use candle_nn::ops::softmax;
10use candle_nn::VarBuilder;
11use hf_hub::HFClientSync;
12use serde_json;
13use tokenizers::Tokenizer;
14
15const ESM2_DTYPE: DType = DType::F32;
16
17const ESM2_STD_AA: [(usize, char); 20] = [
19 (4, 'L'), (5, 'A'), (6, 'G'), (7, 'V'), (8, 'S'),
20 (9, 'E'), (10, 'R'), (11, 'T'), (12, 'I'), (13, 'D'),
21 (14, 'P'), (15, 'K'), (16, 'Q'), (17, 'N'), (18, 'F'),
22 (19, 'Y'), (20, 'M'), (21, 'H'), (22, 'W'), (23, 'C'),
23];
24
25pub enum ESM2Models {
26 T6_8M,
27 T12_35M,
28 T30_150M,
29 T33_650M,
30 T36_3B,
31 T48_15B,
32}
33impl ESM2Models {
34 pub fn get_model_files(model: Self) -> (&'static str, &'static str, ESM2Config) {
35 match model {
36 Self::T6_8M => ("facebook/esm2_t6_8M_UR50D", "main", ESM2Config::t6_8m()),
37 Self::T12_35M => ("facebook/esm2_t12_35M_UR50D", "main", ESM2Config::t12_35m()),
38 Self::T30_150M => (
39 "facebook/esm2_t30_150M_UR50D",
40 "main",
41 ESM2Config::t30_150m(),
42 ),
43 Self::T33_650M => (
44 "facebook/esm2_t33_650M_UR50D",
45 "main",
46 ESM2Config::t33_650m(),
47 ),
48 Self::T36_3B => ("facebook/esm2_t36_3B_UR50D", "main", ESM2Config::t36_3b()),
49 Self::T48_15B => ("facebook/esm2_t48_15B_UR50D", "main", ESM2Config::t48_15b()),
50 }
51 }
52}
53
54pub struct ESM2Runner {
55 model: ESM2,
56 tokenizer: Tokenizer,
57}
58impl ESM2Runner {
59 pub fn load_model(modeltype: ESM2Models, device: Device) -> Result<ESM2Runner> {
61 let (model_id, revision, fallback_config) = ESM2Models::get_model_files(modeltype);
62 let (owner, name) = model_id.split_once('/').unwrap_or(("", model_id));
63 let client = HFClientSync::new()?;
64 let repo = client.model(owner, name);
65 let config = match repo
67 .download_file()
68 .filename("config.json")
69 .revision(revision)
70 .send()
71 {
72 Ok(config_path) => {
73 let config_str = std::fs::read_to_string(config_path)?;
74 serde_json::from_str::<ESM2Config>(&config_str).unwrap_or(fallback_config)
75 }
76 Err(_) => fallback_config,
77 };
78 let weights_filename = repo
79 .download_file()
80 .filename("model.safetensors")
81 .revision(revision)
82 .send()?;
83 let vb = unsafe {
84 VarBuilder::from_mmaped_safetensors(&[weights_filename], ESM2_DTYPE, &device)?
85 };
86 let model = ESM2::load(vb, config)?;
87 let tokenizer = ESM2::load_tokenizer()?;
88 Ok(ESM2Runner { model, tokenizer })
89 }
90 pub fn run_forward(&self, prot_sequence: &str) -> Result<ESM2Output> {
91 let device = self.model.get_device();
92 let tokens = self
93 .tokenizer
94 .encode(prot_sequence.to_string(), false)
95 .map_err(E::msg)?
96 .get_ids()
97 .to_vec();
98 let token_ids = Tensor::new(&tokens[..], device)?.unsqueeze(0)?;
99 let encoded = self.model.forward(&token_ids, None)?;
100 Ok(encoded)
101 }
102 pub fn predict_contacts(&self, prot_sequence: &str) -> Result<Tensor> {
107 let device = self.model.get_device();
108 let tokens = self
109 .tokenizer
110 .encode(prot_sequence.to_string(), false)
111 .map_err(E::msg)?
112 .get_ids()
113 .to_vec();
114 let token_ids = Tensor::new(&tokens[..], device)?.unsqueeze(0)?;
115 self.model
117 .predict_contacts(&token_ids, None)
118 .map_err(E::msg)?
119 .squeeze(0)
120 .map_err(E::msg)
121 }
122
123 pub fn decode_logits(&self, output: ESM2Output) -> Result<String> {
124 let predicted_token_ids = output.logits.argmax(2)?;
126 let predicted_token_ids = if predicted_token_ids.dims().len() > 1 {
127 predicted_token_ids.squeeze(0)?
128 } else {
129 predicted_token_ids
130 };
131 let token_ids: Vec<u32> = predicted_token_ids.to_vec1::<u32>()?;
132 let decoded_sequence = self
133 .tokenizer
134 .decode(&token_ids, true) .map_err(|e| anyhow!("Failed to decode tokens: {}", e))?
136 .replace(" ", "");
137 Ok(decoded_sequence)
138 }
139
140 pub fn get_pseudo_probabilities(&self, prot_sequence: &str) -> Result<Vec<PseudoProbability>> {
145 let output = self.run_forward(prot_sequence)?;
146 let seq_len = output.logits.dim(1)? - 2;
148 let logits = output.logits.narrow(1, 1, seq_len)?;
149 let probs = softmax(&logits, 2)?;
150 let probs = probs.squeeze(0)?; let probs_data: Vec<Vec<f32>> = probs.to_vec2()?;
152
153 let mut result = Vec::new();
154 for (pos, pos_probs) in probs_data.iter().enumerate() {
155 for (vocab_idx, aa_char) in ESM2_STD_AA.iter() {
156 let prob = pos_probs[*vocab_idx];
157 if prob > 0.01 {
158 result.push(PseudoProbability {
159 position: pos,
160 pseudo_prob: prob,
161 amino_acid: *aa_char,
162 });
163 }
164 }
165 }
166 Ok(result)
167 }
168}
169
170impl PlmRunner for ESM2Runner {
171 fn embed(&self, sequence: &str) -> Result<Tensor> {
175 let device = self.model.get_device();
176 let tokens = self
177 .tokenizer
178 .encode(sequence.to_string(), false)
179 .map_err(E::msg)?
180 .get_ids()
181 .to_vec();
182 let token_ids = Tensor::new(&tokens[..], device)?.unsqueeze(0)?;
183 Ok(self.model.embed(&token_ids, None)?)
184 }
185
186 fn model_name(&self) -> &str {
187 "esm2"
188 }
189}