|
3 | 3 | from functools import partial |
4 | 4 | import esm |
5 | 5 | from torch.nn.utils.rnn import pad_sequence |
6 | | -from .cache_utils import cache_fn, run_once |
| 6 | +from .cache_utils import cache_fn, load_cache_value, run_once, save_cache_value |
7 | 7 |
|
8 | 8 | def exists(val): |
9 | 9 | return val is not None |
@@ -63,6 +63,11 @@ def calc_protein_representations_with_subunits(proteins, get_repr_fn, *, device) |
63 | 63 |
|
64 | 64 | ESM_MAX_LENGTH = 2048 |
65 | 65 | ESM_EMBED_DIM = 1280 |
| 66 | +ESM_CACHE_PATH = 'esm/proteins' |
| 67 | +DEFAULT_ESM_BATCH_SIZE = max( |
| 68 | + int(os.getenv("CATPRED_ESM_BATCH_SIZE", "1" if PROTEIN_EMBED_USE_CPU else "4")), |
| 69 | + 1, |
| 70 | +) |
66 | 71 |
|
67 | 72 | INT_TO_AA_STR_MAP = { |
68 | 73 | 0: '<cls>', |
@@ -154,14 +159,94 @@ def get_single_esm_repr(protein_str): |
154 | 159 | representation = token_representations[0][1 : len(protein_str) + 1] |
155 | 160 | return representation |
156 | 161 |
|
| 162 | + |
| 163 | +def _run_esm_batch(protein_strs): |
| 164 | + init_esm() |
| 165 | + model, batch_converter = GLOBAL_VARIABLES['model'] |
| 166 | + |
| 167 | + data = [(f'protein_{index}', protein_str) for index, protein_str in enumerate(protein_strs)] |
| 168 | + batch_labels, batch_strs, batch_tokens = batch_converter(data) |
| 169 | + |
| 170 | + if batch_tokens.shape[1] > ESM_MAX_LENGTH: |
| 171 | + print(f'warning max length protein esm') |
| 172 | + |
| 173 | + batch_tokens = batch_tokens[:, :ESM_MAX_LENGTH] |
| 174 | + |
| 175 | + if not PROTEIN_EMBED_USE_CPU: |
| 176 | + batch_tokens = batch_tokens.to(next(model.parameters()).device) |
| 177 | + |
| 178 | + with torch.no_grad(): |
| 179 | + results = model(batch_tokens, repr_layers=[33]) |
| 180 | + |
| 181 | + token_representations = results['representations'][33] |
| 182 | + representations = [] |
| 183 | + max_residue_tokens = ESM_MAX_LENGTH - 1 |
| 184 | + for index, protein_str in enumerate(protein_strs): |
| 185 | + representation_length = min(len(protein_str), max_residue_tokens) |
| 186 | + representations.append( |
| 187 | + token_representations[index][1 : representation_length + 1].detach() |
| 188 | + ) |
| 189 | + return representations |
| 190 | + |
| 191 | + |
| 192 | +def _run_esm_batch_with_fallback(protein_strs): |
| 193 | + try: |
| 194 | + return _run_esm_batch(protein_strs) |
| 195 | + except RuntimeError as e: |
| 196 | + if 'out of memory' not in str(e) or len(protein_strs) == 1: |
| 197 | + raise e |
| 198 | + print('| WARNING: ran out of memory, retrying smaller ESM batches') |
| 199 | + if torch.cuda.is_available(): |
| 200 | + torch.cuda.empty_cache() |
| 201 | + midpoint = len(protein_strs) // 2 |
| 202 | + return ( |
| 203 | + _run_esm_batch_with_fallback(protein_strs[:midpoint]) |
| 204 | + + _run_esm_batch_with_fallback(protein_strs[midpoint:]) |
| 205 | + ) |
| 206 | + |
| 207 | + |
| 208 | +def get_many_esm_reprs(proteins, device='cpu', batch_size=None): |
| 209 | + if isinstance(proteins, torch.Tensor): |
| 210 | + proteins = tensor_to_aa_str(proteins) |
| 211 | + |
| 212 | + batch_size = max(int(batch_size or DEFAULT_ESM_BATCH_SIZE), 1) |
| 213 | + ordered_unique_proteins = list(dict.fromkeys(proteins)) |
| 214 | + representations_by_sequence = {} |
| 215 | + uncached_proteins = [] |
| 216 | + |
| 217 | + for protein_str in ordered_unique_proteins: |
| 218 | + cached = load_cache_value( |
| 219 | + path=ESM_CACHE_PATH, |
| 220 | + cache_key=protein_str, |
| 221 | + purpose="esm cache entry", |
| 222 | + map_location='cpu', |
| 223 | + ) |
| 224 | + if cached is None: |
| 225 | + uncached_proteins.append(protein_str) |
| 226 | + else: |
| 227 | + representations_by_sequence[protein_str] = cached |
| 228 | + |
| 229 | + for start in range(0, len(uncached_proteins), batch_size): |
| 230 | + batch = uncached_proteins[start : start + batch_size] |
| 231 | + batch_representations = _run_esm_batch_with_fallback(batch) |
| 232 | + for protein_str, representation in zip(batch, batch_representations): |
| 233 | + save_cache_value(representation, path=ESM_CACHE_PATH, cache_key=protein_str) |
| 234 | + representations_by_sequence[protein_str] = representation |
| 235 | + |
| 236 | + return { |
| 237 | + protein_str: representations_by_sequence[protein_str].to(device) |
| 238 | + for protein_str in ordered_unique_proteins |
| 239 | + } |
| 240 | + |
| 241 | + |
157 | 242 | def get_esm_repr(proteins, name, device): |
158 | 243 | if isinstance(proteins, torch.Tensor): |
159 | 244 | proteins = tensor_to_aa_str(proteins) |
160 | 245 |
|
161 | 246 | # Cache by sequence content to avoid collisions when different proteins |
162 | 247 | # are accidentally given the same pdb/name identifier. |
163 | 248 | _ = name |
164 | | - get_protein_repr_fn = cache_fn(get_single_esm_repr, path='esm/proteins') |
| 249 | + get_protein_repr_fn = cache_fn(get_single_esm_repr, path=ESM_CACHE_PATH) |
165 | 250 |
|
166 | 251 | return calc_protein_representations_with_subunits([proteins], get_protein_repr_fn, device=device) |
167 | 252 |
|
@@ -208,6 +293,7 @@ def get_esm_tokens(protein_str, device): |
208 | 293 | 'esm': { |
209 | 294 | 'dim': ESM_EMBED_DIM, |
210 | 295 | 'fn': get_esm_repr, |
| 296 | + 'batch_fn': get_many_esm_reprs, |
211 | 297 | 'tokenizer': get_esm_tokens, |
212 | 298 | } |
213 | 299 | } |
|
0 commit comments