pepe-cli 1.1__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {pepe_cli-1.1/src/pepe_cli.egg-info → pepe_cli-1.3.0}/PKG-INFO +24 -5
  2. {pepe_cli-1.1 → pepe_cli-1.3.0}/README.md +22 -4
  3. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/model_selection.md +15 -1
  4. {pepe_cli-1.1 → pepe_cli-1.3.0}/pyproject.toml +2 -1
  5. {pepe_cli-1.1 → pepe_cli-1.3.0}/requirements.txt +2 -1
  6. {pepe_cli-1.1 → pepe_cli-1.3.0}/setup.py +2 -1
  7. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/__init__.py +1 -1
  8. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/embedders/custom_embedder.py +3 -2
  9. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/embedders/esm_embedder.py +3 -2
  10. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/embedders/huggingface_embedder.py +289 -21
  11. pepe_cli-1.3.0/src/pepe/model_selecter.py +169 -0
  12. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/parse_arguments.py +0 -1
  13. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/utils.py +8 -4
  14. {pepe_cli-1.1 → pepe_cli-1.3.0/src/pepe_cli.egg-info}/PKG-INFO +24 -5
  15. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe_cli.egg-info/SOURCES.txt +5 -0
  16. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe_cli.egg-info/requires.txt +1 -0
  17. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/tests/test_device_logic.py +5 -1
  18. pepe_cli-1.3.0/src/tests/test_esmc.py +61 -0
  19. pepe_cli-1.3.0/src/tests/test_esmc_modes.py +93 -0
  20. pepe_cli-1.3.0/src/tests/test_load_layers_default.py +30 -0
  21. pepe_cli-1.3.0/src/tests/test_model_selection.py +104 -0
  22. pepe_cli-1.3.0/src/tests/test_parse_arguments.py +114 -0
  23. pepe_cli-1.1/src/pepe/model_selecter.py +0 -114
  24. {pepe_cli-1.1 → pepe_cli-1.3.0}/LICENSE +0 -0
  25. {pepe_cli-1.1 → pepe_cli-1.3.0}/MANIFEST.in +0 -0
  26. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/create_example_custom_model.py +0 -0
  27. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_protein_model/config.json +0 -0
  28. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_protein_model/pytorch_model.pt +0 -0
  29. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_protein_model/special_tokens_map.json +0 -0
  30. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_protein_model/tokenizer_config.json +0 -0
  31. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_protein_model/vocab.json +0 -0
  32. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_protein_model/vocab.txt +0 -0
  33. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_sequences.fasta +0 -0
  34. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/custom_model/example_substring.csv +0 -0
  35. {pepe_cli-1.1 → pepe_cli-1.3.0}/examples/embedding_options.md +0 -0
  36. {pepe_cli-1.1 → pepe_cli-1.3.0}/setup.cfg +0 -0
  37. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/__main__.py +0 -0
  38. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/api.py +0 -0
  39. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/embedders/__init__.py +0 -0
  40. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe/embedders/base_embedder.py +0 -0
  41. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe_cli.egg-info/dependency_links.txt +0 -0
  42. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe_cli.egg-info/entry_points.txt +0 -0
  43. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/pepe_cli.egg-info/top_level.txt +0 -0
  44. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/tests/__init__.py +0 -0
  45. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/tests/test_api_unittest.py +0 -0
  46. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/tests/test_run.py +0 -0
  47. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/tests/test_splitting.py +0 -0
  48. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/tests/verify_cross_tool_consistency.py +0 -0
  49. {pepe_cli-1.1 → pepe_cli-1.3.0}/src/tests/verify_readme.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pepe-cli
3
- Version: 1.1
3
+ Version: 1.3.0
4
4
  Summary: Pipeline for Easy Protein Embedding - Extract embeddings and attention matrices from protein sequences
5
5
  Home-page: https://github.com/csi-greifflab/pepe-cli
6
6
  Author: Jahn Zhong
@@ -13,7 +13,7 @@ Project-URL: Documentation, https://github.com/csi-greifflab/pepe-cli#readme
13
13
  Keywords: protein,embeddings,bioinformatics,machine-learning,nlp,transformers
14
14
  Classifier: Development Status :: 3 - Alpha
15
15
  Classifier: Intended Audience :: Science/Research
16
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3
16
+ Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3
19
19
  Classifier: Programming Language :: Python :: 3.8
@@ -31,6 +31,7 @@ Requires-Dist: sentencepiece
31
31
  Requires-Dist: numpy
32
32
  Requires-Dist: protobuf
33
33
  Requires-Dist: alive_progress
34
+ Requires-Dist: rjieba
34
35
  Dynamic: author
35
36
  Dynamic: home-page
36
37
  Dynamic: license-file
@@ -40,9 +41,16 @@ Dynamic: requires-python
40
41
 
41
42
  PEPE (Pipeline for Easy Protein Embedding) is a tool for extracting embeddings and attention matrices from protein sequences using pre-trained models. This tool supports various configurations for extracting embeddings and attention matrices, including options for handling CDR3 sequences. Currently implemented models are ESM2 from the 2023 paper ["Evolutionary-scale prediction of atomic-level protein structure with a language model"](https://science.org/doi/10.1126/science.ade2574) and AntiBERTa2-CSSP from the 2023 conference paper ["Enhancing Antibody Language Models with Structural Information"](https://www.mlsb.io/papers_2023/Enhancing_Antibody_Language_Models_with_Structural_Information.pdf). PEPE also supports custom PLMs from local files or from Huggingface Hub addresses.
42
43
 
44
+ ### Citation
45
+ > **PEPE: Scalable extraction of multi-modal protein language model representations**
46
+ > Jahn Zhong, Niccolò Cardente, Geir Kjetil Sandve, Habib Bashour, Maria Francesca Abbate, Victor Greiff
47
+ > *bioRxiv* (2026)
48
+ > [DOI: 10.1101/2025.10.13.680902](https://doi.org/10.1101/2025.10.13.680902)
49
+
43
50
  ## Quick start
44
51
 
45
- 1. Install PEPE \
52
+ 1. Install PEPE
53
+
46
54
  From PyPI:
47
55
  ```sh
48
56
  pip install pepe-cli
@@ -57,7 +65,13 @@ PEPE (Pipeline for Easy Protein Embedding) is a tool for extracting embeddings a
57
65
  cd pepe-cli
58
66
  pip install .
59
67
  ```
60
- 2. Run the embedding script:\
68
+
69
+ 2. *(Optional)* For ESMC models (e.g. `biohub/ESMC-300M`), install Biohub's transformers fork:
70
+ ```sh
71
+ pip install git+https://github.com/Biohub/transformers.git@main
72
+ ```
73
+
74
+ 3. Extract embeddings:\
61
75
  Extract mean pooled embeddings from protein amino acid sequences in FASTA file:
62
76
  ```sh
63
77
  pepe --experiment_name <optional_string> --fasta_path <file_path> --output_path <directory> --model_name <model_name>
@@ -67,7 +81,7 @@ PEPE (Pipeline for Easy Protein Embedding) is a tool for extracting embeddings a
67
81
 
68
82
  PEPE can also be used as a Python library. This allows for programmatic access to protein embeddings without using the command-line interface.
69
83
 
70
- 1. Install PEPE (see [Quick start with CLI](#quick-start-with-cli) for details).
84
+ 1. Install PEPE (see above for details).
71
85
  2. Use the `pepe.embed()` function in your script:
72
86
 
73
87
  ```python
@@ -220,6 +234,10 @@ results = pepe.embed(
220
234
  - RoFormer models
221
235
  - alchemab/antiberta2-cssp
222
236
  - alchemab/antiberta2
237
+ - ESMC models (requires Biohub transformers fork; see install instructions above)
238
+ - biohub/ESMC-300M
239
+ - biohub/ESMC-600M
240
+ - biohub/ESMC-6B
223
241
  - Custom Hugging Face models
224
242
  - Any compatible model from Hugging Face Hub: `username/model-name`
225
243
  - Private models with authentication
@@ -234,6 +252,7 @@ results = pepe.embed(
234
252
  ### Required Arguments
235
253
  - **`--model_name`** (str): Name of model or link to model. Choose from [List of supported models](../README.md#list-of-supported-models) or use custom models:
236
254
  - ESM models: `esm2_t33_650M_UR50D`
255
+ - ESMC models: `biohub/ESMC-300M` (requires Biohub transformers fork; see Quick start)
237
256
  - Hugging Face models: `username/model-name`
238
257
  - Custom PyTorch models: `/path/to/model.pt` or `/path/to/model_directory/`
239
258
  - Local HF models: `/path/to/local_hf_directory/`
@@ -2,9 +2,16 @@
2
2
 
3
3
  PEPE (Pipeline for Easy Protein Embedding) is a tool for extracting embeddings and attention matrices from protein sequences using pre-trained models. This tool supports various configurations for extracting embeddings and attention matrices, including options for handling CDR3 sequences. Currently implemented models are ESM2 from the 2023 paper ["Evolutionary-scale prediction of atomic-level protein structure with a language model"](https://science.org/doi/10.1126/science.ade2574) and AntiBERTa2-CSSP from the 2023 conference paper ["Enhancing Antibody Language Models with Structural Information"](https://www.mlsb.io/papers_2023/Enhancing_Antibody_Language_Models_with_Structural_Information.pdf). PEPE also supports custom PLMs from local files or from Huggingface Hub addresses.
4
4
 
5
+ ### Citation
6
+ > **PEPE: Scalable extraction of multi-modal protein language model representations**
7
+ > Jahn Zhong, Niccolò Cardente, Geir Kjetil Sandve, Habib Bashour, Maria Francesca Abbate, Victor Greiff
8
+ > *bioRxiv* (2026)
9
+ > [DOI: 10.1101/2025.10.13.680902](https://doi.org/10.1101/2025.10.13.680902)
10
+
5
11
  ## Quick start
6
12
 
7
- 1. Install PEPE \
13
+ 1. Install PEPE
14
+
8
15
  From PyPI:
9
16
  ```sh
10
17
  pip install pepe-cli
@@ -19,7 +26,13 @@ PEPE (Pipeline for Easy Protein Embedding) is a tool for extracting embeddings a
19
26
  cd pepe-cli
20
27
  pip install .
21
28
  ```
22
- 2. Run the embedding script:\
29
+
30
+ 2. *(Optional)* For ESMC models (e.g. `biohub/ESMC-300M`), install Biohub's transformers fork:
31
+ ```sh
32
+ pip install git+https://github.com/Biohub/transformers.git@main
33
+ ```
34
+
35
+ 3. Extract embeddings:\
23
36
  Extract mean pooled embeddings from protein amino acid sequences in FASTA file:
24
37
  ```sh
25
38
  pepe --experiment_name <optional_string> --fasta_path <file_path> --output_path <directory> --model_name <model_name>
@@ -29,7 +42,7 @@ PEPE (Pipeline for Easy Protein Embedding) is a tool for extracting embeddings a
29
42
 
30
43
  PEPE can also be used as a Python library. This allows for programmatic access to protein embeddings without using the command-line interface.
31
44
 
32
- 1. Install PEPE (see [Quick start with CLI](#quick-start-with-cli) for details).
45
+ 1. Install PEPE (see above for details).
33
46
  2. Use the `pepe.embed()` function in your script:
34
47
 
35
48
  ```python
@@ -182,6 +195,10 @@ results = pepe.embed(
182
195
  - RoFormer models
183
196
  - alchemab/antiberta2-cssp
184
197
  - alchemab/antiberta2
198
+ - ESMC models (requires Biohub transformers fork; see install instructions above)
199
+ - biohub/ESMC-300M
200
+ - biohub/ESMC-600M
201
+ - biohub/ESMC-6B
185
202
  - Custom Hugging Face models
186
203
  - Any compatible model from Hugging Face Hub: `username/model-name`
187
204
  - Private models with authentication
@@ -196,6 +213,7 @@ results = pepe.embed(
196
213
  ### Required Arguments
197
214
  - **`--model_name`** (str): Name of model or link to model. Choose from [List of supported models](../README.md#list-of-supported-models) or use custom models:
198
215
  - ESM models: `esm2_t33_650M_UR50D`
216
+ - ESMC models: `biohub/ESMC-300M` (requires Biohub transformers fork; see Quick start)
199
217
  - Hugging Face models: `username/model-name`
200
218
  - Custom PyTorch models: `/path/to/model.pt` or `/path/to/model_directory/`
201
219
  - Local HF models: `/path/to/local_hf_directory/`
@@ -239,4 +257,4 @@ When False, all outputs are stored in RAM and written to disk at once after comp
239
257
 
240
258
  ### Performance Configuration
241
259
  - **`--num_workers`** (int, optional): Number of workers for asynchronous data writing. Only relevant when `--streaming_output` is enabled. Default is `8`.
242
- - **`--flush_batches_after`** (int, optional): Size (in MB) of outputs to accumulate in RAM per worker before flushing to disk. Default is `128`.
260
+ - **`--flush_batches_after`** (int, optional): Size (in MB) of outputs to accumulate in RAM per worker before flushing to disk. Default is `128`.
@@ -10,7 +10,7 @@ pepe \
10
10
  --output_path "src/tests/test_files/test_output"
11
11
  ```
12
12
  ## Models hosted on Huggingface
13
- The user can also choose from any PLM with the same input and output formats as the supported models hosted on Huggingface Hub:
13
+ The user can also choose from PLMs hosted on Huggingface Hub by passing the repository path as `--model_name`:
14
14
  ```sh
15
15
  pepe \
16
16
  --experiment_name "test" \
@@ -19,6 +19,20 @@ pepe \
19
19
  --output_path "src/tests/test_files/test_output"
20
20
  ```
21
21
 
22
+ ### ESMC models
23
+ ESMC (ESM Cambrian) is a distinct architecture from ESM2. It requires Biohub's transformers fork, which coexists with ESM1's `fair-esm` package:
24
+ ```sh
25
+ pip install git+https://github.com/Biohub/transformers.git@main
26
+ pepe \
27
+ --experiment_name "esmc_test" \
28
+ --model_name "biohub/ESMC-300M" \
29
+ --fasta_path "src/tests/test_files/test.fasta" \
30
+ --output_path "src/tests/test_files/test_output" \
31
+ --extract_embeddings mean_pooled \
32
+ --device cpu \
33
+ --layers -1
34
+ ```
35
+
22
36
  ## Custom models
23
37
  To specify your own custom model, pass the path to a directory containing:
24
38
  - .pt or .pth file (model weights)
@@ -15,7 +15,7 @@ requires-python = ">=3.8"
15
15
  classifiers = [
16
16
  "Development Status :: 3 - Alpha",
17
17
  "Intended Audience :: Science/Research",
18
- "License :: OSI Approved :: GNU Affero General Public License v3",
18
+ "License :: OSI Approved :: MIT License",
19
19
  "Operating System :: OS Independent",
20
20
  "Programming Language :: Python :: 3",
21
21
  "Programming Language :: Python :: 3.8",
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "numpy",
34
34
  "protobuf",
35
35
  "alive_progress",
36
+ "rjieba",
36
37
  ]
37
38
 
38
39
  [project.urls]
@@ -5,4 +5,5 @@ fair-esm
5
5
  sentencepiece
6
6
  numpy
7
7
  protobuf
8
- alive_progress
8
+ alive_progress
9
+ rjieba
@@ -28,7 +28,7 @@ setup(
28
28
  classifiers=[
29
29
  "Development Status :: 3 - Alpha",
30
30
  "Intended Audience :: Science/Research",
31
- "License :: OSI Approved :: GNU Affero General Public License v3",
31
+ "License :: OSI Approved :: MIT License",
32
32
  "Operating System :: Linux",
33
33
  "Operating System :: macOS",
34
34
  "Programming Language :: Python :: 3",
@@ -47,6 +47,7 @@ setup(
47
47
  "numpy",
48
48
  "protobuf",
49
49
  "alive_progress",
50
+ "rjieba",
50
51
  ],
51
52
  entry_points={
52
53
  "console_scripts": [
@@ -7,7 +7,7 @@ except ImportError:
7
7
  pass
8
8
 
9
9
  # Package metadata - single source of truth
10
- __version__ = "1.1"
10
+ __version__ = "1.3.0"
11
11
  __package_name__ = "pepe-cli"
12
12
  __module_name__ = "pepe"
13
13
  __author__ = "Jahn Zhong"
@@ -288,9 +288,10 @@ class CustomEmbedder(BaseEmbedder):
288
288
 
289
289
  def _load_layers(self, layers):
290
290
  """Process layer specification."""
291
+ if layers is None:
292
+ return list(range(1, self.num_layers + 1))
291
293
  if not layers:
292
- layers = list(range(1, self.num_layers + 1))
293
- return layers
294
+ layers = [-1]
294
295
 
295
296
  # Validate layer indices
296
297
  assert all(
@@ -102,9 +102,10 @@ class ESMEmbedder(BaseEmbedder):
102
102
  return special_token_ids
103
103
 
104
104
  def _load_layers(self, layers):
105
+ if layers is None:
106
+ return list(range(1, self.model.num_layers + 1)) # type: ignore
105
107
  if not layers:
106
- layers = list(range(1, self.model.num_layers + 1)) # type: ignore
107
- return layers
108
+ layers = [-1]
108
109
  # Checking if the specified representation layers are valid
109
110
  assert all(
110
111
  -(self.model.num_layers + 1) <= i <= self.model.num_layers for i in layers # type: ignore
@@ -15,6 +15,7 @@ def _import_transformers():
15
15
  RoFormerSinusoidalPositionalEmbedding,
16
16
  )
17
17
  from transformers import AutoModel, AutoTokenizer, AutoModelForCausalLM
18
+ from transformers import AutoModelForMaskedLM
18
19
 
19
20
  return (
20
21
  T5EncoderModel,
@@ -25,6 +26,7 @@ def _import_transformers():
25
26
  AutoModel,
26
27
  AutoTokenizer,
27
28
  AutoModelForCausalLM,
29
+ AutoModelForMaskedLM,
28
30
  )
29
31
  except ImportError as e:
30
32
  logger.error(f"Failed to import transformers: {e}")
@@ -51,20 +53,13 @@ class HuggingfaceEmbedder(BaseEmbedder):
51
53
 
52
54
  def _load_layers(self, layers):
53
55
  """Check if the specified representation layers are valid."""
56
+ num_layers = self.num_layers # type: ignore
57
+ if layers is None:
58
+ return list(range(1, num_layers + 1))
54
59
  if not layers:
55
- layers = list(range(1, self.model.config.num_hidden_layers + 1)) # type: ignore
56
- return layers
57
- assert all(
58
- -(self.model.config.num_hidden_layers + 1) # type: ignore
59
- <= i
60
- <= self.model.config.num_hidden_layers # type: ignore
61
- for i in layers
62
- )
63
- layers = [
64
- (i + self.model.config.num_hidden_layers + 1) # type: ignore
65
- % (self.model.config.num_hidden_layers + 1) # type: ignore
66
- for i in layers
67
- ]
60
+ layers = [-1]
61
+ assert all(-(num_layers + 1) <= i <= num_layers for i in layers)
62
+ layers = [(i + num_layers + 1) % (num_layers + 1) for i in layers]
68
63
  return layers
69
64
 
70
65
  def _load_data(self, sequences, substring_dict, bracket_type):
@@ -116,14 +111,18 @@ class HuggingfaceEmbedder(BaseEmbedder):
116
111
  else:
117
112
  attention_matrices = None
118
113
  if return_embeddings:
119
- representations = {
120
- layer: outputs.hidden_states[layer]
121
- .to(
122
- self._precision_to_dtype(self.precision, "torch"),
123
- )
124
- .cpu()
125
- for layer in self.layers # type: ignore
126
- }
114
+ dtype = self._precision_to_dtype(self.precision, "torch")
115
+ hidden_states = outputs.hidden_states
116
+ if isinstance(hidden_states, torch.Tensor):
117
+ representations = {
118
+ layer: hidden_states[layer].to(dtype).cpu()
119
+ for layer in self.layers # type: ignore
120
+ }
121
+ else:
122
+ representations = {
123
+ layer: hidden_states[layer].to(dtype).cpu()
124
+ for layer in self.layers # type: ignore
125
+ }
127
126
  torch.cuda.empty_cache()
128
127
  else:
129
128
  representations = None
@@ -182,6 +181,7 @@ class Antiberta2Embedder(HuggingfaceEmbedder):
182
181
  AutoModel,
183
182
  AutoTokenizer,
184
183
  AutoModelForCausalLM,
184
+ AutoModelForMaskedLM,
185
185
  ) = _import_transformers()
186
186
 
187
187
  tokenizer = RoFormerTokenizer.from_pretrained(model_link, use_fast=True)
@@ -246,6 +246,7 @@ class T5Embedder(HuggingfaceEmbedder):
246
246
  AutoModel,
247
247
  AutoTokenizer,
248
248
  AutoModelForCausalLM,
249
+ AutoModelForMaskedLM,
249
250
  ) = _import_transformers()
250
251
 
251
252
  tokenizer = T5Tokenizer.from_pretrained(model_link, use_fast=True)
@@ -257,6 +258,272 @@ class T5Embedder(HuggingfaceEmbedder):
257
258
  return model, tokenizer, num_heads, num_layers, embedding_size
258
259
 
259
260
 
261
+ def _resolve_esm2_model_link(model_name):
262
+ if "/" in model_name:
263
+ return model_name
264
+ return f"facebook/{model_name}"
265
+
266
+
267
+ class ESM2Embedder(HuggingfaceEmbedder):
268
+ """ESM-2 embedder using HuggingFace transformers instead of fair-esm."""
269
+
270
+ def __init__(self, args):
271
+ BaseEmbedder.__init__(self, args)
272
+ self.sequences = pepe.utils.fasta_to_dict(args.fasta_path)
273
+ self.num_sequences = len(self.sequences)
274
+ (
275
+ self.model,
276
+ self.tokenizer,
277
+ self.num_heads,
278
+ self.num_layers,
279
+ self.embedding_size,
280
+ ) = self._initialize_model(self.model_name)
281
+ self.valid_tokens = self._get_valid_tokens()
282
+ self.bracket_type = pepe.utils.get_bracket_type(self.tokenizer)
283
+ self._check_max_input_length()
284
+ pepe.utils.check_input_tokens(
285
+ self.valid_tokens,
286
+ self.sequences,
287
+ self.model_name,
288
+ split_long_sequences=self.split_long_sequences,
289
+ )
290
+ self.special_tokens = torch.tensor(
291
+ self.tokenizer.all_special_ids, device=self.device, dtype=torch.int8
292
+ )
293
+ self.layers = self._load_layers(self.layers)
294
+ self.data_loader, self.max_input_length = self._load_data(
295
+ self.sequences, self.substring_dict, self.bracket_type
296
+ )
297
+ self._set_output_objects()
298
+
299
+ def _get_valid_tokens(self):
300
+ return {tok for tok in self.tokenizer.get_vocab().keys() if len(tok) == 1}
301
+
302
+ def _load_data(self, sequences, substring_dict, bracket_type):
303
+ dataset = pepe.utils.HuggingFaceDataset(
304
+ sequences,
305
+ substring_dict,
306
+ self.context,
307
+ bracket_type,
308
+ self.tokenizer,
309
+ self.max_input_length,
310
+ add_special_tokens=not self.disable_special_tokens,
311
+ )
312
+ dataset.pad_token_id = self.tokenizer.pad_token_id
313
+ logger.info("Batching sequences...")
314
+ batch_sampler = pepe.utils.TokenBudgetBatchSampler(
315
+ dataset=dataset, token_budget=self.batch_size
316
+ )
317
+ data_loader = torch.utils.data.DataLoader(
318
+ dataset, batch_sampler=batch_sampler, collate_fn=dataset.safe_collate
319
+ )
320
+ max_length = dataset.get_max_encoded_length()
321
+ logger.info("Finished tokenizing and batching sequences")
322
+ return data_loader, max_length
323
+
324
+ def _initialize_model(self, model_name):
325
+ model_link = _resolve_esm2_model_link(model_name)
326
+ if torch.cuda.is_available() and self.device.type == "cuda":
327
+ device = torch.device("cuda")
328
+ logger.info("Transferred model to GPU")
329
+ else:
330
+ device = torch.device("cpu")
331
+ logger.info("No GPU available, using CPU")
332
+
333
+ (
334
+ T5EncoderModel,
335
+ T5Tokenizer,
336
+ RoFormerTokenizer,
337
+ RoFormerModel,
338
+ RoFormerSinusoidalPositionalEmbedding,
339
+ AutoModel,
340
+ AutoTokenizer,
341
+ AutoModelForCausalLM,
342
+ AutoModelForMaskedLM,
343
+ ) = _import_transformers()
344
+
345
+ logger.info(f"Loading ESM-2 model from HuggingFace: {model_link}")
346
+ tokenizer = AutoTokenizer.from_pretrained(model_link)
347
+ model_kwargs = {}
348
+ if self.return_contacts:
349
+ model_kwargs["attn_implementation"] = "eager"
350
+
351
+ if self.return_logits:
352
+ model = AutoModelForMaskedLM.from_pretrained(
353
+ model_link, **model_kwargs
354
+ ).to(device)
355
+ else:
356
+ model = AutoModel.from_pretrained(model_link, **model_kwargs).to(device)
357
+ model.eval()
358
+
359
+ config = model.config
360
+ num_heads = config.num_attention_heads
361
+ num_layers = config.num_hidden_layers
362
+ embedding_size = config.hidden_size
363
+ return model, tokenizer, num_heads, num_layers, embedding_size
364
+
365
+ def _compute_outputs(
366
+ self,
367
+ model,
368
+ toks,
369
+ attention_mask,
370
+ return_embeddings,
371
+ return_contacts,
372
+ return_logits=False,
373
+ ):
374
+ outputs = model(
375
+ input_ids=toks,
376
+ attention_mask=attention_mask,
377
+ output_hidden_states=return_embeddings,
378
+ output_attentions=return_contacts,
379
+ )
380
+ if return_logits:
381
+ logits = (
382
+ outputs.logits
383
+ .to(dtype=self._precision_to_dtype(self.precision, "torch"))
384
+ .permute(2, 0, 1)
385
+ .cpu()
386
+ )
387
+ torch.cuda.empty_cache()
388
+ else:
389
+ logits = None
390
+
391
+ if return_contacts:
392
+ attention_matrices = (
393
+ torch.stack(outputs.attentions) # type: ignore
394
+ .to(self._precision_to_dtype(self.precision, "torch")) # type: ignore
395
+ .cpu()
396
+ )
397
+ torch.cuda.empty_cache()
398
+ else:
399
+ attention_matrices = None
400
+
401
+ if return_embeddings:
402
+ representations = {
403
+ layer: outputs.hidden_states[layer]
404
+ .to(self._precision_to_dtype(self.precision, "torch"))
405
+ .cpu()
406
+ for layer in self.layers # type: ignore
407
+ }
408
+ torch.cuda.empty_cache()
409
+ else:
410
+ representations = None
411
+
412
+ return logits, representations, attention_matrices
413
+
414
+
415
+ def _get_config_attr(config, *names, default=None):
416
+ for name in names:
417
+ if hasattr(config, name):
418
+ return getattr(config, name)
419
+ return default
420
+
421
+
422
+ class ESMCEmbedder(HuggingfaceEmbedder):
423
+ """ESMC embedder using Biohub transformers fork (model_type esmc)."""
424
+
425
+ def __init__(self, args):
426
+ BaseEmbedder.__init__(self, args)
427
+ self.sequences = pepe.utils.fasta_to_dict(args.fasta_path)
428
+ self.num_sequences = len(self.sequences)
429
+ (
430
+ self.model,
431
+ self.tokenizer,
432
+ self.num_heads,
433
+ self.num_layers,
434
+ self.embedding_size,
435
+ ) = self._initialize_model(self.model_link)
436
+ self.valid_tokens = self._get_valid_tokens()
437
+ self.bracket_type = pepe.utils.get_bracket_type(self.tokenizer)
438
+ self._check_max_input_length()
439
+ pepe.utils.check_input_tokens(
440
+ self.valid_tokens,
441
+ self.sequences,
442
+ self.model_name,
443
+ split_long_sequences=self.split_long_sequences,
444
+ )
445
+ self.special_tokens = torch.tensor(
446
+ self.tokenizer.all_special_ids, device=self.device, dtype=torch.int8
447
+ )
448
+ self.layers = self._load_layers(self.layers)
449
+ self.data_loader, self.max_input_length = self._load_data(
450
+ self.sequences, self.substring_dict, self.bracket_type
451
+ )
452
+ self._set_output_objects()
453
+
454
+ def _get_valid_tokens(self):
455
+ return {tok for tok in self.tokenizer.get_vocab().keys() if len(tok) == 1}
456
+
457
+ def _load_data(self, sequences, substring_dict, bracket_type):
458
+ dataset = pepe.utils.HuggingFaceDataset(
459
+ sequences,
460
+ substring_dict,
461
+ self.context,
462
+ bracket_type,
463
+ self.tokenizer,
464
+ self.max_input_length,
465
+ add_special_tokens=not self.disable_special_tokens,
466
+ gapped_sequences=False,
467
+ )
468
+ dataset.pad_token_id = self.tokenizer.pad_token_id
469
+ logger.info("Batching sequences...")
470
+ batch_sampler = pepe.utils.TokenBudgetBatchSampler(
471
+ dataset=dataset, token_budget=self.batch_size
472
+ )
473
+ data_loader = torch.utils.data.DataLoader(
474
+ dataset, batch_sampler=batch_sampler, collate_fn=dataset.safe_collate
475
+ )
476
+ max_length = dataset.get_max_encoded_length()
477
+ logger.info("Finished tokenizing and batching sequences")
478
+ return data_loader, max_length
479
+
480
+ def _initialize_model(self, model_link):
481
+ if torch.cuda.is_available() and self.device.type == "cuda":
482
+ device = torch.device("cuda")
483
+ logger.info("Transferred model to GPU")
484
+ else:
485
+ device = torch.device("cpu")
486
+ logger.info("No GPU available, using CPU")
487
+
488
+ (
489
+ T5EncoderModel,
490
+ T5Tokenizer,
491
+ RoFormerTokenizer,
492
+ RoFormerModel,
493
+ RoFormerSinusoidalPositionalEmbedding,
494
+ AutoModel,
495
+ AutoTokenizer,
496
+ AutoModelForCausalLM,
497
+ AutoModelForMaskedLM,
498
+ ) = _import_transformers()
499
+
500
+ logger.info(f"Loading ESMC model from HuggingFace: {model_link}")
501
+ tokenizer = AutoTokenizer.from_pretrained(model_link)
502
+ model_kwargs = {}
503
+ if self.return_contacts:
504
+ model_kwargs["attn_implementation"] = "eager"
505
+
506
+ if self.return_logits:
507
+ model = AutoModelForMaskedLM.from_pretrained(
508
+ model_link, **model_kwargs
509
+ ).to(device)
510
+ else:
511
+ model = AutoModel.from_pretrained(model_link, **model_kwargs).to(device)
512
+ model.eval()
513
+
514
+ config = model.config
515
+ num_heads = _get_config_attr(
516
+ config, "num_attention_heads", "n_heads", "num_heads"
517
+ )
518
+ num_layers = _get_config_attr(
519
+ config, "num_hidden_layers", "n_layers", "num_layers"
520
+ )
521
+ embedding_size = _get_config_attr(
522
+ config, "hidden_size", "d_model", "embed_dim"
523
+ )
524
+ return model, tokenizer, num_heads, num_layers, embedding_size
525
+
526
+
260
527
  class GenericHuggingFaceEmbedder(HuggingfaceEmbedder):
261
528
  """Generic HuggingFace embedder that can handle models with unknown architectures using AutoModel and AutoTokenizer."""
262
529
 
@@ -320,6 +587,7 @@ class GenericHuggingFaceEmbedder(HuggingfaceEmbedder):
320
587
  AutoModel,
321
588
  AutoTokenizer,
322
589
  AutoModelForCausalLM,
590
+ AutoModelForMaskedLM,
323
591
  ) = _import_transformers()
324
592
 
325
593
  # For models that commonly require custom code, use trust_remote_code=True immediately