lhallee commited on
Commit
1f5996d
·
verified ·
1 Parent(s): bfc9937

Upload modeling_boltz2.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. modeling_boltz2.py +882 -882
modeling_boltz2.py CHANGED
@@ -1,882 +1,882 @@
1
- import entrypoint_setup
2
- import copy
3
- import inspect
4
- from collections.abc import Mapping, Sequence
5
- from dataclasses import dataclass
6
- from typing import Any, Dict, Optional, Tuple, Union
7
-
8
- import torch
9
- import torch._dynamo
10
- import torch.nn as nn
11
- from torch import Tensor
12
- from transformers import PreTrainedModel, PretrainedConfig
13
- from transformers.modeling_outputs import ModelOutput
14
-
15
- from .cif_writer import write_cif
16
- from .minimal_featurizer import build_boltz2_features
17
- from .minimal_structures import ProteinStructureTemplate
18
- from .vb_const import bond_types as _vb_const_bond_types # noqa: F401
19
- from .vb_layers_attention import AttentionPairBias as _vb_layers_attention_marker # noqa: F401
20
- from .vb_layers_attentionv2 import AttentionPairBias as _vb_layers_attentionv2_marker # noqa: F401
21
- from .vb_layers_confidence_utils import compute_ptms as _vb_layers_confidence_utils_marker # noqa: F401
22
- from .vb_layers_dropout import get_dropout_mask as _vb_layers_dropout_marker # noqa: F401
23
- from .vb_layers_initialize import gating_init_ as _vb_layers_initialize_marker # noqa: F401
24
- from .vb_layers_outer_product_mean import OuterProductMean as _vb_layers_outer_product_mean_marker # noqa: F401
25
- from .vb_layers_pair_averaging import PairWeightedAveraging as _vb_layers_pair_averaging_marker # noqa: F401
26
- from .vb_layers_transition import Transition as _vb_layers_transition_marker # noqa: F401
27
- from .vb_layers_triangular_mult import TriangleMultiplicationIncoming as _vb_layers_triangular_mult_marker # noqa: F401
28
- from .vb_loss_diffusionv2 import weighted_rigid_align as _vb_loss_diffusionv2_marker # noqa: F401
29
- from .vb_modules_transformersv2 import DiffusionTransformer as _vb_modules_transformersv2_marker # noqa: F401
30
- from .vb_modules_utils import LinearNoBias as _vb_modules_utils_marker # noqa: F401
31
- from .vb_potentials_potentials import get_potentials as _vb_potentials_potentials_marker # noqa: F401
32
- from .vb_potentials_schedules import ParameterSchedule as _vb_potentials_schedules_marker # noqa: F401
33
- from .vb_tri_attn_attention import TriangleAttentionStartingNode as _vb_tri_attn_attention_marker # noqa: F401
34
- from .vb_tri_attn_primitives import Attention as _vb_tri_attn_primitives_marker # noqa: F401
35
- from .vb_tri_attn_utils import permute_final_dims as _vb_tri_attn_utils_marker # noqa: F401
36
- from . import vb_const as const
37
- from . import vb_layers_initialize as init
38
- from .vb_layers_pairformer import PairformerModule
39
- from .vb_modules_confidencev2 import ConfidenceModule
40
- from .vb_modules_diffusion_conditioning import DiffusionConditioning
41
- from .vb_modules_diffusionv2 import AtomDiffusion, DiffusionModule
42
- from .vb_modules_encodersv2 import RelativePositionEncoder
43
- from .vb_modules_trunkv2 import (
44
- ContactConditioning,
45
- DistogramModule,
46
- InputEmbedder,
47
- MSAModule,
48
- )
49
-
50
-
51
- def _default_steering_args() -> Dict[str, Any]:
52
- return {
53
- "fk_steering": False,
54
- "num_particles": 3,
55
- "fk_lambda": 4.0,
56
- "fk_resampling_interval": 3,
57
- "physical_guidance_update": False,
58
- "contact_guidance_update": False,
59
- "num_gd_steps": 16,
60
- }
61
-
62
-
63
- def _boltz2_reference_diffusion_overrides() -> Dict[str, Any]:
64
- # Match Boltz2 CLI inference defaults from boltz.main/Boltz2DiffusionParams.
65
- return {
66
- "gamma_0": 0.8,
67
- "gamma_min": 1.0,
68
- "noise_scale": 1.003,
69
- "rho": 7,
70
- "step_scale": 1.5,
71
- "sigma_min": 0.0001,
72
- "sigma_max": 160.0,
73
- "sigma_data": 16.0,
74
- "P_mean": -1.2,
75
- "P_std": 1.5,
76
- "coordinate_augmentation": True,
77
- "alignment_reverse_diff": True,
78
- "synchronize_sigmas": True,
79
- }
80
-
81
-
82
- def _enforce_pairformer_v2(pairformer_args: Mapping[str, Any], context: str) -> Dict[str, Any]:
83
- assert isinstance(pairformer_args, Mapping), (
84
- f"Expected {context} pairformer_args to be a dictionary."
85
- )
86
- out = _to_plain_python(copy.deepcopy(pairformer_args))
87
- if "v2" in out:
88
- assert out["v2"], f"{context} pairformer_args['v2'] must be True for Boltz2."
89
- out["v2"] = True
90
- return out
91
-
92
-
93
- def _require_key(mapping: Dict[str, Any], key: str) -> Any:
94
- assert key in mapping, f"Missing required key '{key}' in checkpoint hyperparameters."
95
- return mapping[key]
96
-
97
-
98
- def _state_dict_without_wrappers(state_dict: Dict[str, Tensor]) -> Dict[str, Tensor]:
99
- cleaned: Dict[str, Tensor] = {}
100
- for key, value in state_dict.items():
101
- if key.startswith("ema."):
102
- continue
103
- new_key = key
104
- if new_key.startswith("model."):
105
- new_key = new_key[len("model.") :]
106
- if new_key.startswith("module."):
107
- new_key = new_key[len("module.") :]
108
- cleaned[new_key] = value
109
- return cleaned
110
-
111
-
112
- def _to_cpu_detached(value: Any) -> Any:
113
- if torch.is_tensor(value):
114
- return value.detach().cpu()
115
- if isinstance(value, dict):
116
- out: Dict[Any, Any] = {}
117
- for key, nested_value in value.items():
118
- out[key] = _to_cpu_detached(nested_value)
119
- return out
120
- if isinstance(value, list):
121
- return [_to_cpu_detached(item) for item in value]
122
- if isinstance(value, tuple):
123
- return tuple(_to_cpu_detached(item) for item in value)
124
- return value
125
-
126
-
127
- def _to_plain_python(value: Any) -> Any:
128
- if isinstance(value, Mapping):
129
- out: Dict[Any, Any] = {}
130
- for key, nested_value in value.items():
131
- out[key] = _to_plain_python(nested_value)
132
- return out
133
- if isinstance(value, list):
134
- return [_to_plain_python(item) for item in value]
135
- if isinstance(value, tuple):
136
- return [_to_plain_python(item) for item in value]
137
- if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
138
- return [_to_plain_python(item) for item in value]
139
- return value
140
-
141
-
142
- def _filtered_kwargs(target: Any, kwargs: Dict[str, Any]) -> Dict[str, Any]:
143
- signature = inspect.signature(target.__init__)
144
- allowed = set(signature.parameters.keys())
145
- allowed.discard("self")
146
- filtered: Dict[str, Any] = {}
147
- for key, value in kwargs.items():
148
- if key in allowed:
149
- filtered[key] = value
150
- return filtered
151
-
152
-
153
- @dataclass
154
- class Boltz2StructureOutput(ModelOutput):
155
- sample_atom_coords: Optional[torch.Tensor] = None
156
- atom_pad_mask: Optional[torch.Tensor] = None
157
- plddt: Optional[torch.Tensor] = None
158
- confidence_score: Optional[torch.Tensor] = None
159
- complex_plddt: Optional[torch.Tensor] = None
160
- iptm: Optional[torch.Tensor] = None
161
- ptm: Optional[torch.Tensor] = None
162
- sequence: Optional[str] = None
163
- structure_template: Optional[ProteinStructureTemplate] = None
164
- raw_output: Optional[Dict[str, torch.Tensor]] = None
165
-
166
-
167
- class Boltz2Config(PretrainedConfig):
168
- model_type = "boltz2_automodel"
169
-
170
- def __init__(
171
- self,
172
- core_kwargs: Optional[Dict[str, Any]] = None,
173
- num_bins: int = 64,
174
- default_recycling_steps: int = 3,
175
- default_sampling_steps: int = 200,
176
- default_diffusion_samples: int = 1,
177
- **kwargs,
178
- ) -> None:
179
- super().__init__(**kwargs)
180
- if core_kwargs is None:
181
- core_kwargs = {}
182
- self.core_kwargs = core_kwargs
183
- self.num_bins = num_bins
184
- self.default_recycling_steps = default_recycling_steps
185
- self.default_sampling_steps = default_sampling_steps
186
- self.default_diffusion_samples = default_diffusion_samples
187
-
188
- @classmethod
189
- def from_hyperparameters(
190
- cls,
191
- hparams: Dict[str, Any],
192
- use_kernels: bool = False,
193
- default_recycling_steps: Optional[int] = None,
194
- default_sampling_steps: Optional[int] = None,
195
- default_diffusion_samples: Optional[int] = None,
196
- ) -> "Boltz2Config":
197
- assert isinstance(hparams, dict), "Expected checkpoint hyperparameters as a dictionary."
198
- required = [
199
- "atom_s",
200
- "atom_z",
201
- "token_s",
202
- "token_z",
203
- "num_bins",
204
- "embedder_args",
205
- "msa_args",
206
- "pairformer_args",
207
- "score_model_args",
208
- "diffusion_process_args",
209
- ]
210
- for key in required:
211
- _require_key(hparams, key)
212
-
213
- pairformer_args = _enforce_pairformer_v2(
214
- hparams["pairformer_args"],
215
- context="checkpoint",
216
- )
217
- diffusion_process_args = _to_plain_python(
218
- copy.deepcopy(hparams["diffusion_process_args"])
219
- )
220
- diffusion_overrides = _boltz2_reference_diffusion_overrides()
221
- for key in diffusion_overrides:
222
- diffusion_process_args[key] = diffusion_overrides[key]
223
-
224
- core_kwargs: Dict[str, Any] = {
225
- "atom_s": hparams["atom_s"],
226
- "atom_z": hparams["atom_z"],
227
- "token_s": hparams["token_s"],
228
- "token_z": hparams["token_z"],
229
- "num_bins": hparams["num_bins"],
230
- "embedder_args": _to_plain_python(copy.deepcopy(hparams["embedder_args"])),
231
- "msa_args": _to_plain_python(copy.deepcopy(hparams["msa_args"])),
232
- "pairformer_args": pairformer_args,
233
- "score_model_args": _to_plain_python(copy.deepcopy(hparams["score_model_args"])),
234
- "diffusion_process_args": diffusion_process_args,
235
- "use_kernels": use_kernels,
236
- }
237
-
238
- if "confidence_model_args" in hparams:
239
- confidence_model_args = _to_plain_python(
240
- copy.deepcopy(hparams["confidence_model_args"])
241
- )
242
- if "pairformer_args" in confidence_model_args:
243
- confidence_model_args["pairformer_args"] = _enforce_pairformer_v2(
244
- confidence_model_args["pairformer_args"],
245
- context="confidence",
246
- )
247
- core_kwargs["confidence_model_args"] = confidence_model_args
248
- else:
249
- core_kwargs["confidence_model_args"] = None
250
-
251
- if "confidence_prediction" in hparams:
252
- core_kwargs["confidence_prediction"] = hparams["confidence_prediction"]
253
- else:
254
- core_kwargs["confidence_prediction"] = True
255
-
256
- if "token_level_confidence" in hparams:
257
- core_kwargs["token_level_confidence"] = hparams["token_level_confidence"]
258
- else:
259
- core_kwargs["token_level_confidence"] = True
260
-
261
- if "alpha_pae" in hparams:
262
- core_kwargs["alpha_pae"] = hparams["alpha_pae"]
263
- else:
264
- core_kwargs["alpha_pae"] = 0.0
265
-
266
- if "atoms_per_window_queries" in hparams:
267
- core_kwargs["atoms_per_window_queries"] = hparams["atoms_per_window_queries"]
268
- else:
269
- core_kwargs["atoms_per_window_queries"] = 32
270
-
271
- if "atoms_per_window_keys" in hparams:
272
- core_kwargs["atoms_per_window_keys"] = hparams["atoms_per_window_keys"]
273
- else:
274
- core_kwargs["atoms_per_window_keys"] = 128
275
-
276
- if "atom_feature_dim" in hparams:
277
- core_kwargs["atom_feature_dim"] = hparams["atom_feature_dim"]
278
- else:
279
- core_kwargs["atom_feature_dim"] = 128
280
-
281
- if "bond_type_feature" in hparams:
282
- core_kwargs["bond_type_feature"] = hparams["bond_type_feature"]
283
- else:
284
- core_kwargs["bond_type_feature"] = False
285
-
286
- if "run_trunk_and_structure" in hparams:
287
- core_kwargs["run_trunk_and_structure"] = hparams["run_trunk_and_structure"]
288
- else:
289
- core_kwargs["run_trunk_and_structure"] = True
290
-
291
- if "skip_run_structure" in hparams:
292
- core_kwargs["skip_run_structure"] = hparams["skip_run_structure"]
293
- else:
294
- core_kwargs["skip_run_structure"] = False
295
-
296
- if "fix_sym_check" in hparams:
297
- core_kwargs["fix_sym_check"] = hparams["fix_sym_check"]
298
- else:
299
- core_kwargs["fix_sym_check"] = False
300
-
301
- if "cyclic_pos_enc" in hparams:
302
- core_kwargs["cyclic_pos_enc"] = hparams["cyclic_pos_enc"]
303
- else:
304
- core_kwargs["cyclic_pos_enc"] = False
305
-
306
- if "use_no_atom_char" in hparams:
307
- core_kwargs["use_no_atom_char"] = hparams["use_no_atom_char"]
308
- else:
309
- core_kwargs["use_no_atom_char"] = False
310
-
311
- if "use_atom_backbone_feat" in hparams:
312
- core_kwargs["use_atom_backbone_feat"] = hparams["use_atom_backbone_feat"]
313
- else:
314
- core_kwargs["use_atom_backbone_feat"] = False
315
-
316
- if "use_residue_feats_atoms" in hparams:
317
- core_kwargs["use_residue_feats_atoms"] = hparams["use_residue_feats_atoms"]
318
- else:
319
- core_kwargs["use_residue_feats_atoms"] = False
320
-
321
- if "conditioning_cutoff_min" in hparams:
322
- core_kwargs["conditioning_cutoff_min"] = hparams["conditioning_cutoff_min"]
323
- else:
324
- core_kwargs["conditioning_cutoff_min"] = 4.0
325
-
326
- if "conditioning_cutoff_max" in hparams:
327
- core_kwargs["conditioning_cutoff_max"] = hparams["conditioning_cutoff_max"]
328
- else:
329
- core_kwargs["conditioning_cutoff_max"] = 20.0
330
-
331
- if "steering_args" in hparams and hparams["steering_args"] is not None:
332
- core_kwargs["steering_args"] = _to_plain_python(
333
- copy.deepcopy(hparams["steering_args"])
334
- )
335
- else:
336
- core_kwargs["steering_args"] = _default_steering_args()
337
-
338
- if "validation_args" in hparams:
339
- validation_args = hparams["validation_args"]
340
- assert isinstance(validation_args, Mapping), (
341
- "Expected 'validation_args' in checkpoint hyperparameters to be a mapping."
342
- )
343
- if default_recycling_steps is None and "recycling_steps" in validation_args:
344
- default_recycling_steps = validation_args["recycling_steps"]
345
- if default_sampling_steps is None and "sampling_steps" in validation_args:
346
- default_sampling_steps = validation_args["sampling_steps"]
347
- if default_diffusion_samples is None and "diffusion_samples" in validation_args:
348
- default_diffusion_samples = validation_args["diffusion_samples"]
349
-
350
- if default_recycling_steps is None:
351
- default_recycling_steps = 3
352
- if default_sampling_steps is None:
353
- default_sampling_steps = 200
354
- if default_diffusion_samples is None:
355
- default_diffusion_samples = 1
356
-
357
- return cls(
358
- core_kwargs=core_kwargs,
359
- num_bins=hparams["num_bins"],
360
- default_recycling_steps=default_recycling_steps,
361
- default_sampling_steps=default_sampling_steps,
362
- default_diffusion_samples=default_diffusion_samples,
363
- )
364
-
365
-
366
- class Boltz2InferenceCore(nn.Module):
367
- def __init__(
368
- self,
369
- atom_s: int,
370
- atom_z: int,
371
- token_s: int,
372
- token_z: int,
373
- num_bins: int,
374
- embedder_args: Dict[str, Any],
375
- msa_args: Dict[str, Any],
376
- pairformer_args: Dict[str, Any],
377
- score_model_args: Dict[str, Any],
378
- diffusion_process_args: Dict[str, Any],
379
- confidence_model_args: Optional[Dict[str, Any]] = None,
380
- atom_feature_dim: int = 128,
381
- confidence_prediction: bool = True,
382
- token_level_confidence: bool = True,
383
- alpha_pae: float = 0.0,
384
- atoms_per_window_queries: int = 32,
385
- atoms_per_window_keys: int = 128,
386
- run_trunk_and_structure: bool = True,
387
- skip_run_structure: bool = False,
388
- bond_type_feature: bool = False,
389
- fix_sym_check: bool = False,
390
- cyclic_pos_enc: bool = False,
391
- use_no_atom_char: bool = False,
392
- use_atom_backbone_feat: bool = False,
393
- use_residue_feats_atoms: bool = False,
394
- conditioning_cutoff_min: float = 4.0,
395
- conditioning_cutoff_max: float = 20.0,
396
- use_kernels: bool = False,
397
- steering_args: Optional[Dict[str, Any]] = None,
398
- ) -> None:
399
- super().__init__()
400
- self.use_kernels = use_kernels
401
- self.confidence_prediction = confidence_prediction
402
- self.token_level_confidence = token_level_confidence
403
- self.alpha_pae = alpha_pae
404
- self.run_trunk_and_structure = run_trunk_and_structure
405
- self.skip_run_structure = skip_run_structure
406
- self.bond_type_feature = bond_type_feature
407
- self.steering_args = steering_args if steering_args is not None else _default_steering_args()
408
- assert "v2" in pairformer_args, "Boltz2 requires pairformer_args['v2']."
409
- assert pairformer_args["v2"], "Boltz2 requires pairformer_args['v2']=True."
410
-
411
- full_embedder_args = {
412
- "atom_s": atom_s,
413
- "atom_z": atom_z,
414
- "token_s": token_s,
415
- "token_z": token_z,
416
- "atoms_per_window_queries": atoms_per_window_queries,
417
- "atoms_per_window_keys": atoms_per_window_keys,
418
- "atom_feature_dim": atom_feature_dim,
419
- "use_no_atom_char": use_no_atom_char,
420
- "use_atom_backbone_feat": use_atom_backbone_feat,
421
- "use_residue_feats_atoms": use_residue_feats_atoms,
422
- **embedder_args,
423
- }
424
- full_embedder_args = _filtered_kwargs(InputEmbedder, full_embedder_args)
425
- self.input_embedder = InputEmbedder(**full_embedder_args)
426
-
427
- self.s_init = nn.Linear(token_s, token_s, bias=False)
428
- self.z_init_1 = nn.Linear(token_s, token_z, bias=False)
429
- self.z_init_2 = nn.Linear(token_s, token_z, bias=False)
430
- self.rel_pos = RelativePositionEncoder(
431
- token_z,
432
- fix_sym_check=fix_sym_check,
433
- cyclic_pos_enc=cyclic_pos_enc,
434
- )
435
- self.token_bonds = nn.Linear(1, token_z, bias=False)
436
- if self.bond_type_feature:
437
- self.token_bonds_type = nn.Embedding(len(const.bond_types) + 1, token_z)
438
-
439
- self.contact_conditioning = ContactConditioning(
440
- token_z=token_z,
441
- cutoff_min=conditioning_cutoff_min,
442
- cutoff_max=conditioning_cutoff_max,
443
- )
444
- self.s_norm = nn.LayerNorm(token_s)
445
- self.z_norm = nn.LayerNorm(token_z)
446
-
447
- self.s_recycle = nn.Linear(token_s, token_s, bias=False)
448
- self.z_recycle = nn.Linear(token_z, token_z, bias=False)
449
- init.gating_init_(self.s_recycle.weight)
450
- init.gating_init_(self.z_recycle.weight)
451
-
452
- torch._dynamo.config.cache_size_limit = 512 # noqa: SLF001
453
- torch._dynamo.config.accumulated_cache_size_limit = 512 # noqa: SLF001
454
-
455
- msa_kwargs = _filtered_kwargs(MSAModule, {"token_z": token_z, "token_s": token_s, **msa_args})
456
- self.msa_module = MSAModule(**msa_kwargs)
457
-
458
- pairformer_kwargs = _filtered_kwargs(
459
- PairformerModule,
460
- {"token_s": token_s, "token_z": token_z, **pairformer_args},
461
- )
462
- assert "token_s" in pairformer_kwargs and "token_z" in pairformer_kwargs
463
- pairformer_token_s = pairformer_kwargs.pop("token_s")
464
- pairformer_token_z = pairformer_kwargs.pop("token_z")
465
- self.pairformer_module = PairformerModule(
466
- pairformer_token_s,
467
- pairformer_token_z,
468
- **pairformer_kwargs,
469
- )
470
-
471
- diffusion_conditioning_kwargs = {
472
- "token_s": token_s,
473
- "token_z": token_z,
474
- "atom_s": atom_s,
475
- "atom_z": atom_z,
476
- "atoms_per_window_queries": atoms_per_window_queries,
477
- "atoms_per_window_keys": atoms_per_window_keys,
478
- "atom_encoder_depth": score_model_args["atom_encoder_depth"],
479
- "atom_encoder_heads": score_model_args["atom_encoder_heads"],
480
- "token_transformer_depth": score_model_args["token_transformer_depth"],
481
- "token_transformer_heads": score_model_args["token_transformer_heads"],
482
- "atom_decoder_depth": score_model_args["atom_decoder_depth"],
483
- "atom_decoder_heads": score_model_args["atom_decoder_heads"],
484
- "atom_feature_dim": atom_feature_dim,
485
- "conditioning_transition_layers": score_model_args["conditioning_transition_layers"],
486
- "use_no_atom_char": use_no_atom_char,
487
- "use_atom_backbone_feat": use_atom_backbone_feat,
488
- "use_residue_feats_atoms": use_residue_feats_atoms,
489
- }
490
- diffusion_conditioning_kwargs = _filtered_kwargs(
491
- DiffusionConditioning,
492
- diffusion_conditioning_kwargs,
493
- )
494
- self.diffusion_conditioning = DiffusionConditioning(**diffusion_conditioning_kwargs)
495
-
496
- structure_score_model_args = {
497
- "token_s": token_s,
498
- "atom_s": atom_s,
499
- "atoms_per_window_queries": atoms_per_window_queries,
500
- "atoms_per_window_keys": atoms_per_window_keys,
501
- **score_model_args,
502
- }
503
- structure_score_model_args = _filtered_kwargs(
504
- DiffusionModule,
505
- structure_score_model_args,
506
- )
507
- structure_module_kwargs = {
508
- "score_model_args": structure_score_model_args,
509
- "compile_score": False,
510
- **diffusion_process_args,
511
- }
512
- structure_module_kwargs = _filtered_kwargs(AtomDiffusion, structure_module_kwargs)
513
- self.structure_module = AtomDiffusion(**structure_module_kwargs)
514
- self.distogram_module = DistogramModule(token_z, num_bins)
515
-
516
- if self.confidence_prediction:
517
- assert confidence_model_args is not None, (
518
- "confidence_prediction=True requires confidence_model_args in config."
519
- )
520
- confidence_kwargs = {
521
- "token_s": token_s,
522
- "token_z": token_z,
523
- "token_level_confidence": token_level_confidence,
524
- "bond_type_feature": bond_type_feature,
525
- "fix_sym_check": fix_sym_check,
526
- "cyclic_pos_enc": cyclic_pos_enc,
527
- "conditioning_cutoff_min": conditioning_cutoff_min,
528
- "conditioning_cutoff_max": conditioning_cutoff_max,
529
- **confidence_model_args,
530
- }
531
- confidence_kwargs = _filtered_kwargs(ConfidenceModule, confidence_kwargs)
532
- self.confidence_module = ConfidenceModule(**confidence_kwargs)
533
-
534
- def forward(
535
- self,
536
- feats: Dict[str, Tensor],
537
- recycling_steps: int = 3,
538
- num_sampling_steps: Optional[int] = None,
539
- diffusion_samples: int = 1,
540
- max_parallel_samples: Optional[int] = None,
541
- run_confidence_sequentially: bool = True,
542
- detach_confidence: bool = True,
543
- ) -> Dict[str, Tensor]:
544
- s_inputs = self.input_embedder(feats)
545
- s_init = self.s_init(s_inputs)
546
-
547
- z_init = self.z_init_1(s_inputs)[:, :, None] + self.z_init_2(s_inputs)[:, None, :]
548
- relative_position_encoding = self.rel_pos(feats)
549
- z_init = z_init + relative_position_encoding
550
- z_init = z_init + self.token_bonds(feats["token_bonds"].float())
551
- if self.bond_type_feature:
552
- z_init = z_init + self.token_bonds_type(feats["type_bonds"].long())
553
- z_init = z_init + self.contact_conditioning(feats)
554
-
555
- s = torch.zeros_like(s_init)
556
- z = torch.zeros_like(z_init)
557
- mask = feats["token_pad_mask"].float()
558
- pair_mask = mask[:, :, None] * mask[:, None, :]
559
-
560
- if self.run_trunk_and_structure:
561
- for _ in range(recycling_steps + 1):
562
- s = s_init + self.s_recycle(self.s_norm(s))
563
- z = z_init + self.z_recycle(self.z_norm(z))
564
- z = z + self.msa_module(
565
- z,
566
- s_inputs,
567
- feats,
568
- use_kernels=self.use_kernels,
569
- )
570
- s, z = self.pairformer_module(
571
- s,
572
- z,
573
- mask=mask,
574
- pair_mask=pair_mask,
575
- use_kernels=self.use_kernels,
576
- )
577
-
578
- pdistogram = self.distogram_module(z)
579
- output: Dict[str, Tensor] = {
580
- "pdistogram": pdistogram,
581
- "s": s,
582
- "z": z,
583
- }
584
-
585
- if self.run_trunk_and_structure and (not self.skip_run_structure):
586
- q, c, to_keys, atom_enc_bias, atom_dec_bias, token_trans_bias = (
587
- self.diffusion_conditioning(
588
- s_trunk=s,
589
- z_trunk=z,
590
- relative_position_encoding=relative_position_encoding,
591
- feats=feats,
592
- )
593
- )
594
- diffusion_conditioning = {
595
- "q": q,
596
- "c": c,
597
- "to_keys": to_keys,
598
- "atom_enc_bias": atom_enc_bias,
599
- "atom_dec_bias": atom_dec_bias,
600
- "token_trans_bias": token_trans_bias,
601
- }
602
- with torch.autocast("cuda", enabled=False):
603
- struct_out = self.structure_module.sample(
604
- s_trunk=s.float(),
605
- s_inputs=s_inputs.float(),
606
- feats=feats,
607
- num_sampling_steps=num_sampling_steps,
608
- atom_mask=feats["atom_pad_mask"].float(),
609
- multiplicity=diffusion_samples,
610
- max_parallel_samples=max_parallel_samples,
611
- steering_args=self.steering_args,
612
- diffusion_conditioning=diffusion_conditioning,
613
- )
614
- output.update(struct_out)
615
-
616
- if self.confidence_prediction:
617
- if self.skip_run_structure:
618
- x_pred = feats["coords"].repeat_interleave(diffusion_samples, 0)
619
- else:
620
- assert "sample_atom_coords" in output, (
621
- "Structure sampling did not produce sample_atom_coords."
622
- )
623
- x_pred = output["sample_atom_coords"]
624
-
625
- if detach_confidence:
626
- s_inputs_c = s_inputs.detach()
627
- s_c = s.detach()
628
- z_c = z.detach()
629
- x_pred_c = x_pred.detach()
630
- pdist_c = output["pdistogram"][:, :, :, 0].detach()
631
- else:
632
- s_inputs_c = s_inputs
633
- s_c = s
634
- z_c = z
635
- x_pred_c = x_pred
636
- pdist_c = output["pdistogram"][:, :, :, 0]
637
-
638
- output.update(
639
- self.confidence_module(
640
- s_inputs=s_inputs_c,
641
- s=s_c,
642
- z=z_c,
643
- x_pred=x_pred_c,
644
- feats=feats,
645
- pred_distogram_logits=pdist_c,
646
- multiplicity=diffusion_samples,
647
- run_sequentially=run_confidence_sequentially,
648
- use_kernels=self.use_kernels,
649
- )
650
- )
651
-
652
- return output
653
-
654
-
655
- class Boltz2Model(PreTrainedModel):
656
- config_class = Boltz2Config
657
- base_model_prefix = "core"
658
- all_tied_weights_keys = {}
659
-
660
- def __init__(self, config: Boltz2Config) -> None:
661
- super().__init__(config)
662
- assert isinstance(config.core_kwargs, dict), "config.core_kwargs must be a dictionary."
663
- self.core = Boltz2InferenceCore(**config.core_kwargs)
664
-
665
- def _init_weights(self, module: nn.Module) -> None: # noqa: ARG002
666
- return
667
-
668
- def _detied_state_dict(self) -> Dict[str, Tensor]:
669
- raw_state = self.state_dict()
670
- seen_ptrs: Dict[int, str] = {}
671
- out: Dict[str, Tensor] = {}
672
- for key, tensor in raw_state.items():
673
- if torch.is_tensor(tensor):
674
- ptr = tensor.untyped_storage().data_ptr()
675
- if ptr in seen_ptrs:
676
- out[key] = tensor.clone()
677
- else:
678
- seen_ptrs[ptr] = key
679
- out[key] = tensor
680
- else:
681
- out[key] = tensor
682
- return out
683
-
684
- def save_pretrained(self, save_directory: str, **kwargs: Any) -> None:
685
- if "safe_serialization" not in kwargs:
686
- kwargs["safe_serialization"] = False
687
- if "state_dict" not in kwargs:
688
- kwargs["state_dict"] = self._detied_state_dict()
689
- super().save_pretrained(save_directory, **kwargs)
690
-
691
- @property
692
- def device(self) -> torch.device:
693
- return next(self.parameters()).device
694
-
695
- @classmethod
696
- def from_boltz_checkpoint(
697
- cls,
698
- checkpoint_path: str,
699
- map_location: Union[str, torch.device] = "cpu",
700
- use_kernels: bool = False,
701
- default_recycling_steps: Optional[int] = None,
702
- default_sampling_steps: Optional[int] = None,
703
- default_diffusion_samples: Optional[int] = None,
704
- ) -> "Boltz2Model":
705
- # Boltz Lightning checkpoints include OmegaConf objects and require full unpickling.
706
- checkpoint = torch.load(
707
- checkpoint_path,
708
- map_location=map_location,
709
- weights_only=False,
710
- )
711
- assert isinstance(checkpoint, dict), "Checkpoint must deserialize to a dictionary."
712
- _require_key(checkpoint, "hyper_parameters")
713
- _require_key(checkpoint, "state_dict")
714
-
715
- hparams = checkpoint["hyper_parameters"]
716
- assert isinstance(hparams, dict), "Checkpoint hyper_parameters must be a dictionary."
717
- state_dict = checkpoint["state_dict"]
718
- assert isinstance(state_dict, dict), "Checkpoint state_dict must be a dictionary."
719
-
720
- config = Boltz2Config.from_hyperparameters(
721
- hparams,
722
- use_kernels=use_kernels,
723
- default_recycling_steps=default_recycling_steps,
724
- default_sampling_steps=default_sampling_steps,
725
- default_diffusion_samples=default_diffusion_samples,
726
- )
727
- model = cls(config)
728
- cleaned = _state_dict_without_wrappers(state_dict)
729
- target_keys = set(model.core.state_dict().keys())
730
- for key in target_keys:
731
- assert ".attention.norm_s." not in key, (
732
- "Boltz2 inference core unexpectedly uses v1 attention parameters. "
733
- "Expected pairformer v2 architecture."
734
- )
735
- filtered: Dict[str, Tensor] = {}
736
- for key, value in cleaned.items():
737
- if key in target_keys:
738
- filtered[key] = value
739
-
740
- missing = sorted(target_keys.difference(filtered.keys()))
741
- assert len(missing) == 0, (
742
- "Checkpoint is missing required parameters for Boltz2 inference core. "
743
- f"Missing keys (first 20): {missing[:20]}"
744
- )
745
-
746
- load_result = model.core.load_state_dict(filtered, strict=False)
747
- loaded_missing = sorted(load_result.missing_keys)
748
- assert len(loaded_missing) == 0, (
749
- "Model has unexpected missing keys after load_state_dict. "
750
- f"Missing keys (first 20): {loaded_missing[:20]}"
751
- )
752
- assert len(load_result.unexpected_keys) == 0
753
- model.eval()
754
- return model
755
-
756
- def forward(
757
- self,
758
- feats: Dict[str, Tensor],
759
- recycling_steps: Optional[int] = None,
760
- num_sampling_steps: Optional[int] = None,
761
- diffusion_samples: Optional[int] = None,
762
- max_parallel_samples: Optional[int] = None,
763
- run_confidence_sequentially: bool = True,
764
- detach_confidence: bool = True,
765
- ) -> Dict[str, Tensor]:
766
- if recycling_steps is None:
767
- recycling_steps = self.config.default_recycling_steps
768
- if num_sampling_steps is None:
769
- num_sampling_steps = self.config.default_sampling_steps
770
- if diffusion_samples is None:
771
- diffusion_samples = self.config.default_diffusion_samples
772
- return self.core(
773
- feats=feats,
774
- recycling_steps=recycling_steps,
775
- num_sampling_steps=num_sampling_steps,
776
- diffusion_samples=diffusion_samples,
777
- max_parallel_samples=max_parallel_samples,
778
- run_confidence_sequentially=run_confidence_sequentially,
779
- detach_confidence=detach_confidence,
780
- )
781
-
782
- def _to_model_device(
783
- self,
784
- feats: Dict[str, Tensor],
785
- float_dtype: torch.dtype,
786
- ) -> Dict[str, Tensor]:
787
- moved: Dict[str, Tensor] = {}
788
- for key, value in feats.items():
789
- if torch.is_tensor(value):
790
- if value.is_floating_point():
791
- moved[key] = value.to(device=self.device, dtype=float_dtype)
792
- else:
793
- moved[key] = value.to(device=self.device)
794
- else:
795
- moved[key] = value
796
- return moved
797
-
798
- def predict_structure(
799
- self,
800
- amino_acid_sequence: str,
801
- recycling_steps: Optional[int] = None,
802
- num_sampling_steps: Optional[int] = None,
803
- diffusion_samples: Optional[int] = None,
804
- max_parallel_samples: Optional[int] = None,
805
- run_confidence_sequentially: bool = True,
806
- float_dtype: Optional[torch.dtype] = None,
807
- ) -> Boltz2StructureOutput:
808
- if float_dtype is None:
809
- float_dtype = torch.float32
810
-
811
- feats, template = build_boltz2_features(
812
- amino_acid_sequence=amino_acid_sequence,
813
- num_bins=self.config.num_bins,
814
- atoms_per_window_queries=self.core.input_embedder.atom_encoder.atoms_per_window_queries,
815
- )
816
- feats = self._to_model_device(feats, float_dtype=float_dtype)
817
-
818
- with torch.no_grad():
819
- output = self.forward(
820
- feats=feats,
821
- recycling_steps=recycling_steps,
822
- num_sampling_steps=num_sampling_steps,
823
- diffusion_samples=diffusion_samples,
824
- max_parallel_samples=max_parallel_samples,
825
- run_confidence_sequentially=run_confidence_sequentially,
826
- )
827
-
828
- sample_atom_coords = output["sample_atom_coords"].detach().cpu()
829
- non_finite_mask = torch.logical_not(torch.isfinite(sample_atom_coords))
830
- assert not torch.any(non_finite_mask), (
831
- "sample_atom_coords contains non-finite values. "
832
- f"Non-finite count: {int(non_finite_mask.sum().item())}"
833
- )
834
- atom_pad_mask = feats["atom_pad_mask"][0].detach().cpu()
835
- plddt = output["plddt"].detach().cpu() if "plddt" in output else None
836
- complex_plddt = output["complex_plddt"].detach().cpu() if "complex_plddt" in output else None
837
- iptm = output["iptm"].detach().cpu() if "iptm" in output else None
838
- ptm = output["ptm"].detach().cpu() if "ptm" in output else None
839
-
840
- confidence_score = None
841
- if (complex_plddt is not None) and (iptm is not None) and (ptm is not None):
842
- if torch.allclose(iptm, torch.zeros_like(iptm)):
843
- confidence_score = (4 * complex_plddt + ptm) / 5
844
- else:
845
- confidence_score = (4 * complex_plddt + iptm) / 5
846
-
847
- return Boltz2StructureOutput(
848
- sample_atom_coords=sample_atom_coords,
849
- atom_pad_mask=atom_pad_mask,
850
- plddt=plddt,
851
- confidence_score=confidence_score,
852
- complex_plddt=complex_plddt,
853
- iptm=iptm,
854
- ptm=ptm,
855
- sequence=template.sequence,
856
- structure_template=template,
857
- raw_output={key: _to_cpu_detached(val) for key, val in output.items()},
858
- )
859
-
860
- def save_as_cif(
861
- self,
862
- structure_output: Boltz2StructureOutput,
863
- output_path: str,
864
- sample_index: int = 0,
865
- ) -> str:
866
- assert structure_output.structure_template is not None, (
867
- "structure_output.structure_template is required for CIF export."
868
- )
869
- assert structure_output.sample_atom_coords is not None, (
870
- "structure_output.sample_atom_coords is required for CIF export."
871
- )
872
- assert structure_output.atom_pad_mask is not None, (
873
- "structure_output.atom_pad_mask is required for CIF export."
874
- )
875
- return write_cif(
876
- structure_template=structure_output.structure_template,
877
- atom_coords=structure_output.sample_atom_coords,
878
- atom_mask=structure_output.atom_pad_mask,
879
- output_path=output_path,
880
- plddt=structure_output.plddt,
881
- sample_index=sample_index,
882
- )
 
1
+ import entrypoint_setup
2
+ import copy
3
+ import inspect
4
+ from collections.abc import Mapping, Sequence
5
+ from dataclasses import dataclass
6
+ from typing import Any, Dict, Optional, Tuple, Union
7
+
8
+ import torch
9
+ import torch._dynamo
10
+ import torch.nn as nn
11
+ from torch import Tensor
12
+ from transformers import PreTrainedModel, PretrainedConfig
13
+ from transformers.modeling_outputs import ModelOutput
14
+
15
+ from .cif_writer import write_cif
16
+ from .minimal_featurizer import build_boltz2_features
17
+ from .minimal_structures import ProteinStructureTemplate
18
+ from .vb_const import bond_types as _vb_const_bond_types # noqa: F401
19
+ from .vb_layers_attention import AttentionPairBias as _vb_layers_attention_marker # noqa: F401
20
+ from .vb_layers_attentionv2 import AttentionPairBias as _vb_layers_attentionv2_marker # noqa: F401
21
+ from .vb_layers_confidence_utils import compute_ptms as _vb_layers_confidence_utils_marker # noqa: F401
22
+ from .vb_layers_dropout import get_dropout_mask as _vb_layers_dropout_marker # noqa: F401
23
+ from .vb_layers_initialize import gating_init_ as _vb_layers_initialize_marker # noqa: F401
24
+ from .vb_layers_outer_product_mean import OuterProductMean as _vb_layers_outer_product_mean_marker # noqa: F401
25
+ from .vb_layers_pair_averaging import PairWeightedAveraging as _vb_layers_pair_averaging_marker # noqa: F401
26
+ from .vb_layers_transition import Transition as _vb_layers_transition_marker # noqa: F401
27
+ from .vb_layers_triangular_mult import TriangleMultiplicationIncoming as _vb_layers_triangular_mult_marker # noqa: F401
28
+ from .vb_loss_diffusionv2 import weighted_rigid_align as _vb_loss_diffusionv2_marker # noqa: F401
29
+ from .vb_modules_transformersv2 import DiffusionTransformer as _vb_modules_transformersv2_marker # noqa: F401
30
+ from .vb_modules_utils import LinearNoBias as _vb_modules_utils_marker # noqa: F401
31
+ from .vb_potentials_potentials import get_potentials as _vb_potentials_potentials_marker # noqa: F401
32
+ from .vb_potentials_schedules import ParameterSchedule as _vb_potentials_schedules_marker # noqa: F401
33
+ from .vb_tri_attn_attention import TriangleAttentionStartingNode as _vb_tri_attn_attention_marker # noqa: F401
34
+ from .vb_tri_attn_primitives import Attention as _vb_tri_attn_primitives_marker # noqa: F401
35
+ from .vb_tri_attn_utils import permute_final_dims as _vb_tri_attn_utils_marker # noqa: F401
36
+ from . import vb_const as const
37
+ from . import vb_layers_initialize as init
38
+ from .vb_layers_pairformer import PairformerModule
39
+ from .vb_modules_confidencev2 import ConfidenceModule
40
+ from .vb_modules_diffusion_conditioning import DiffusionConditioning
41
+ from .vb_modules_diffusionv2 import AtomDiffusion, DiffusionModule
42
+ from .vb_modules_encodersv2 import RelativePositionEncoder
43
+ from .vb_modules_trunkv2 import (
44
+ ContactConditioning,
45
+ DistogramModule,
46
+ InputEmbedder,
47
+ MSAModule,
48
+ )
49
+
50
+
51
+ def _default_steering_args() -> Dict[str, Any]:
52
+ return {
53
+ "fk_steering": False,
54
+ "num_particles": 3,
55
+ "fk_lambda": 4.0,
56
+ "fk_resampling_interval": 3,
57
+ "physical_guidance_update": False,
58
+ "contact_guidance_update": False,
59
+ "num_gd_steps": 16,
60
+ }
61
+
62
+
63
+ def _boltz2_reference_diffusion_overrides() -> Dict[str, Any]:
64
+ # Match Boltz2 CLI inference defaults from boltz.main/Boltz2DiffusionParams.
65
+ return {
66
+ "gamma_0": 0.8,
67
+ "gamma_min": 1.0,
68
+ "noise_scale": 1.003,
69
+ "rho": 7,
70
+ "step_scale": 1.5,
71
+ "sigma_min": 0.0001,
72
+ "sigma_max": 160.0,
73
+ "sigma_data": 16.0,
74
+ "P_mean": -1.2,
75
+ "P_std": 1.5,
76
+ "coordinate_augmentation": True,
77
+ "alignment_reverse_diff": True,
78
+ "synchronize_sigmas": True,
79
+ }
80
+
81
+
82
+ def _enforce_pairformer_v2(pairformer_args: Mapping[str, Any], context: str) -> Dict[str, Any]:
83
+ assert isinstance(pairformer_args, Mapping), (
84
+ f"Expected {context} pairformer_args to be a dictionary."
85
+ )
86
+ out = _to_plain_python(copy.deepcopy(pairformer_args))
87
+ if "v2" in out:
88
+ assert out["v2"], f"{context} pairformer_args['v2'] must be True for Boltz2."
89
+ out["v2"] = True
90
+ return out
91
+
92
+
93
+ def _require_key(mapping: Dict[str, Any], key: str) -> Any:
94
+ assert key in mapping, f"Missing required key '{key}' in checkpoint hyperparameters."
95
+ return mapping[key]
96
+
97
+
98
+ def _state_dict_without_wrappers(state_dict: Dict[str, Tensor]) -> Dict[str, Tensor]:
99
+ cleaned: Dict[str, Tensor] = {}
100
+ for key, value in state_dict.items():
101
+ if key.startswith("ema."):
102
+ continue
103
+ new_key = key
104
+ if new_key.startswith("model."):
105
+ new_key = new_key[len("model.") :]
106
+ if new_key.startswith("module."):
107
+ new_key = new_key[len("module.") :]
108
+ cleaned[new_key] = value
109
+ return cleaned
110
+
111
+
112
+ def _to_cpu_detached(value: Any) -> Any:
113
+ if torch.is_tensor(value):
114
+ return value.detach().cpu()
115
+ if isinstance(value, dict):
116
+ out: Dict[Any, Any] = {}
117
+ for key, nested_value in value.items():
118
+ out[key] = _to_cpu_detached(nested_value)
119
+ return out
120
+ if isinstance(value, list):
121
+ return [_to_cpu_detached(item) for item in value]
122
+ if isinstance(value, tuple):
123
+ return tuple(_to_cpu_detached(item) for item in value)
124
+ return value
125
+
126
+
127
+ def _to_plain_python(value: Any) -> Any:
128
+ if isinstance(value, Mapping):
129
+ out: Dict[Any, Any] = {}
130
+ for key, nested_value in value.items():
131
+ out[key] = _to_plain_python(nested_value)
132
+ return out
133
+ if isinstance(value, list):
134
+ return [_to_plain_python(item) for item in value]
135
+ if isinstance(value, tuple):
136
+ return [_to_plain_python(item) for item in value]
137
+ if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
138
+ return [_to_plain_python(item) for item in value]
139
+ return value
140
+
141
+
142
+ def _filtered_kwargs(target: Any, kwargs: Dict[str, Any]) -> Dict[str, Any]:
143
+ signature = inspect.signature(target.__init__)
144
+ allowed = set(signature.parameters.keys())
145
+ allowed.discard("self")
146
+ filtered: Dict[str, Any] = {}
147
+ for key, value in kwargs.items():
148
+ if key in allowed:
149
+ filtered[key] = value
150
+ return filtered
151
+
152
+
153
+ @dataclass
154
+ class Boltz2StructureOutput(ModelOutput):
155
+ sample_atom_coords: Optional[torch.Tensor] = None
156
+ atom_pad_mask: Optional[torch.Tensor] = None
157
+ plddt: Optional[torch.Tensor] = None
158
+ confidence_score: Optional[torch.Tensor] = None
159
+ complex_plddt: Optional[torch.Tensor] = None
160
+ iptm: Optional[torch.Tensor] = None
161
+ ptm: Optional[torch.Tensor] = None
162
+ sequence: Optional[str] = None
163
+ structure_template: Optional[ProteinStructureTemplate] = None
164
+ raw_output: Optional[Dict[str, torch.Tensor]] = None
165
+
166
+
167
+ class Boltz2Config(PretrainedConfig):
168
+ model_type = "boltz2_automodel"
169
+
170
+ def __init__(
171
+ self,
172
+ core_kwargs: Optional[Dict[str, Any]] = None,
173
+ num_bins: int = 64,
174
+ default_recycling_steps: int = 3,
175
+ default_sampling_steps: int = 200,
176
+ default_diffusion_samples: int = 1,
177
+ **kwargs,
178
+ ) -> None:
179
+ super().__init__(**kwargs)
180
+ if core_kwargs is None:
181
+ core_kwargs = {}
182
+ self.core_kwargs = core_kwargs
183
+ self.num_bins = num_bins
184
+ self.default_recycling_steps = default_recycling_steps
185
+ self.default_sampling_steps = default_sampling_steps
186
+ self.default_diffusion_samples = default_diffusion_samples
187
+
188
+ @classmethod
189
+ def from_hyperparameters(
190
+ cls,
191
+ hparams: Dict[str, Any],
192
+ use_kernels: bool = False,
193
+ default_recycling_steps: Optional[int] = None,
194
+ default_sampling_steps: Optional[int] = None,
195
+ default_diffusion_samples: Optional[int] = None,
196
+ ) -> "Boltz2Config":
197
+ assert isinstance(hparams, dict), "Expected checkpoint hyperparameters as a dictionary."
198
+ required = [
199
+ "atom_s",
200
+ "atom_z",
201
+ "token_s",
202
+ "token_z",
203
+ "num_bins",
204
+ "embedder_args",
205
+ "msa_args",
206
+ "pairformer_args",
207
+ "score_model_args",
208
+ "diffusion_process_args",
209
+ ]
210
+ for key in required:
211
+ _require_key(hparams, key)
212
+
213
+ pairformer_args = _enforce_pairformer_v2(
214
+ hparams["pairformer_args"],
215
+ context="checkpoint",
216
+ )
217
+ diffusion_process_args = _to_plain_python(
218
+ copy.deepcopy(hparams["diffusion_process_args"])
219
+ )
220
+ diffusion_overrides = _boltz2_reference_diffusion_overrides()
221
+ for key in diffusion_overrides:
222
+ diffusion_process_args[key] = diffusion_overrides[key]
223
+
224
+ core_kwargs: Dict[str, Any] = {
225
+ "atom_s": hparams["atom_s"],
226
+ "atom_z": hparams["atom_z"],
227
+ "token_s": hparams["token_s"],
228
+ "token_z": hparams["token_z"],
229
+ "num_bins": hparams["num_bins"],
230
+ "embedder_args": _to_plain_python(copy.deepcopy(hparams["embedder_args"])),
231
+ "msa_args": _to_plain_python(copy.deepcopy(hparams["msa_args"])),
232
+ "pairformer_args": pairformer_args,
233
+ "score_model_args": _to_plain_python(copy.deepcopy(hparams["score_model_args"])),
234
+ "diffusion_process_args": diffusion_process_args,
235
+ "use_kernels": use_kernels,
236
+ }
237
+
238
+ if "confidence_model_args" in hparams:
239
+ confidence_model_args = _to_plain_python(
240
+ copy.deepcopy(hparams["confidence_model_args"])
241
+ )
242
+ if "pairformer_args" in confidence_model_args:
243
+ confidence_model_args["pairformer_args"] = _enforce_pairformer_v2(
244
+ confidence_model_args["pairformer_args"],
245
+ context="confidence",
246
+ )
247
+ core_kwargs["confidence_model_args"] = confidence_model_args
248
+ else:
249
+ core_kwargs["confidence_model_args"] = None
250
+
251
+ if "confidence_prediction" in hparams:
252
+ core_kwargs["confidence_prediction"] = hparams["confidence_prediction"]
253
+ else:
254
+ core_kwargs["confidence_prediction"] = True
255
+
256
+ if "token_level_confidence" in hparams:
257
+ core_kwargs["token_level_confidence"] = hparams["token_level_confidence"]
258
+ else:
259
+ core_kwargs["token_level_confidence"] = True
260
+
261
+ if "alpha_pae" in hparams:
262
+ core_kwargs["alpha_pae"] = hparams["alpha_pae"]
263
+ else:
264
+ core_kwargs["alpha_pae"] = 0.0
265
+
266
+ if "atoms_per_window_queries" in hparams:
267
+ core_kwargs["atoms_per_window_queries"] = hparams["atoms_per_window_queries"]
268
+ else:
269
+ core_kwargs["atoms_per_window_queries"] = 32
270
+
271
+ if "atoms_per_window_keys" in hparams:
272
+ core_kwargs["atoms_per_window_keys"] = hparams["atoms_per_window_keys"]
273
+ else:
274
+ core_kwargs["atoms_per_window_keys"] = 128
275
+
276
+ if "atom_feature_dim" in hparams:
277
+ core_kwargs["atom_feature_dim"] = hparams["atom_feature_dim"]
278
+ else:
279
+ core_kwargs["atom_feature_dim"] = 128
280
+
281
+ if "bond_type_feature" in hparams:
282
+ core_kwargs["bond_type_feature"] = hparams["bond_type_feature"]
283
+ else:
284
+ core_kwargs["bond_type_feature"] = False
285
+
286
+ if "run_trunk_and_structure" in hparams:
287
+ core_kwargs["run_trunk_and_structure"] = hparams["run_trunk_and_structure"]
288
+ else:
289
+ core_kwargs["run_trunk_and_structure"] = True
290
+
291
+ if "skip_run_structure" in hparams:
292
+ core_kwargs["skip_run_structure"] = hparams["skip_run_structure"]
293
+ else:
294
+ core_kwargs["skip_run_structure"] = False
295
+
296
+ if "fix_sym_check" in hparams:
297
+ core_kwargs["fix_sym_check"] = hparams["fix_sym_check"]
298
+ else:
299
+ core_kwargs["fix_sym_check"] = False
300
+
301
+ if "cyclic_pos_enc" in hparams:
302
+ core_kwargs["cyclic_pos_enc"] = hparams["cyclic_pos_enc"]
303
+ else:
304
+ core_kwargs["cyclic_pos_enc"] = False
305
+
306
+ if "use_no_atom_char" in hparams:
307
+ core_kwargs["use_no_atom_char"] = hparams["use_no_atom_char"]
308
+ else:
309
+ core_kwargs["use_no_atom_char"] = False
310
+
311
+ if "use_atom_backbone_feat" in hparams:
312
+ core_kwargs["use_atom_backbone_feat"] = hparams["use_atom_backbone_feat"]
313
+ else:
314
+ core_kwargs["use_atom_backbone_feat"] = False
315
+
316
+ if "use_residue_feats_atoms" in hparams:
317
+ core_kwargs["use_residue_feats_atoms"] = hparams["use_residue_feats_atoms"]
318
+ else:
319
+ core_kwargs["use_residue_feats_atoms"] = False
320
+
321
+ if "conditioning_cutoff_min" in hparams:
322
+ core_kwargs["conditioning_cutoff_min"] = hparams["conditioning_cutoff_min"]
323
+ else:
324
+ core_kwargs["conditioning_cutoff_min"] = 4.0
325
+
326
+ if "conditioning_cutoff_max" in hparams:
327
+ core_kwargs["conditioning_cutoff_max"] = hparams["conditioning_cutoff_max"]
328
+ else:
329
+ core_kwargs["conditioning_cutoff_max"] = 20.0
330
+
331
+ if "steering_args" in hparams and hparams["steering_args"] is not None:
332
+ core_kwargs["steering_args"] = _to_plain_python(
333
+ copy.deepcopy(hparams["steering_args"])
334
+ )
335
+ else:
336
+ core_kwargs["steering_args"] = _default_steering_args()
337
+
338
+ if "validation_args" in hparams:
339
+ validation_args = hparams["validation_args"]
340
+ assert isinstance(validation_args, Mapping), (
341
+ "Expected 'validation_args' in checkpoint hyperparameters to be a mapping."
342
+ )
343
+ if default_recycling_steps is None and "recycling_steps" in validation_args:
344
+ default_recycling_steps = validation_args["recycling_steps"]
345
+ if default_sampling_steps is None and "sampling_steps" in validation_args:
346
+ default_sampling_steps = validation_args["sampling_steps"]
347
+ if default_diffusion_samples is None and "diffusion_samples" in validation_args:
348
+ default_diffusion_samples = validation_args["diffusion_samples"]
349
+
350
+ if default_recycling_steps is None:
351
+ default_recycling_steps = 3
352
+ if default_sampling_steps is None:
353
+ default_sampling_steps = 200
354
+ if default_diffusion_samples is None:
355
+ default_diffusion_samples = 1
356
+
357
+ return cls(
358
+ core_kwargs=core_kwargs,
359
+ num_bins=hparams["num_bins"],
360
+ default_recycling_steps=default_recycling_steps,
361
+ default_sampling_steps=default_sampling_steps,
362
+ default_diffusion_samples=default_diffusion_samples,
363
+ )
364
+
365
+
366
+ class Boltz2InferenceCore(nn.Module):
367
+ def __init__(
368
+ self,
369
+ atom_s: int,
370
+ atom_z: int,
371
+ token_s: int,
372
+ token_z: int,
373
+ num_bins: int,
374
+ embedder_args: Dict[str, Any],
375
+ msa_args: Dict[str, Any],
376
+ pairformer_args: Dict[str, Any],
377
+ score_model_args: Dict[str, Any],
378
+ diffusion_process_args: Dict[str, Any],
379
+ confidence_model_args: Optional[Dict[str, Any]] = None,
380
+ atom_feature_dim: int = 128,
381
+ confidence_prediction: bool = True,
382
+ token_level_confidence: bool = True,
383
+ alpha_pae: float = 0.0,
384
+ atoms_per_window_queries: int = 32,
385
+ atoms_per_window_keys: int = 128,
386
+ run_trunk_and_structure: bool = True,
387
+ skip_run_structure: bool = False,
388
+ bond_type_feature: bool = False,
389
+ fix_sym_check: bool = False,
390
+ cyclic_pos_enc: bool = False,
391
+ use_no_atom_char: bool = False,
392
+ use_atom_backbone_feat: bool = False,
393
+ use_residue_feats_atoms: bool = False,
394
+ conditioning_cutoff_min: float = 4.0,
395
+ conditioning_cutoff_max: float = 20.0,
396
+ use_kernels: bool = False,
397
+ steering_args: Optional[Dict[str, Any]] = None,
398
+ ) -> None:
399
+ super().__init__()
400
+ self.use_kernels = use_kernels
401
+ self.confidence_prediction = confidence_prediction
402
+ self.token_level_confidence = token_level_confidence
403
+ self.alpha_pae = alpha_pae
404
+ self.run_trunk_and_structure = run_trunk_and_structure
405
+ self.skip_run_structure = skip_run_structure
406
+ self.bond_type_feature = bond_type_feature
407
+ self.steering_args = steering_args if steering_args is not None else _default_steering_args()
408
+ assert "v2" in pairformer_args, "Boltz2 requires pairformer_args['v2']."
409
+ assert pairformer_args["v2"], "Boltz2 requires pairformer_args['v2']=True."
410
+
411
+ full_embedder_args = {
412
+ "atom_s": atom_s,
413
+ "atom_z": atom_z,
414
+ "token_s": token_s,
415
+ "token_z": token_z,
416
+ "atoms_per_window_queries": atoms_per_window_queries,
417
+ "atoms_per_window_keys": atoms_per_window_keys,
418
+ "atom_feature_dim": atom_feature_dim,
419
+ "use_no_atom_char": use_no_atom_char,
420
+ "use_atom_backbone_feat": use_atom_backbone_feat,
421
+ "use_residue_feats_atoms": use_residue_feats_atoms,
422
+ **embedder_args,
423
+ }
424
+ full_embedder_args = _filtered_kwargs(InputEmbedder, full_embedder_args)
425
+ self.input_embedder = InputEmbedder(**full_embedder_args)
426
+
427
+ self.s_init = nn.Linear(token_s, token_s, bias=False)
428
+ self.z_init_1 = nn.Linear(token_s, token_z, bias=False)
429
+ self.z_init_2 = nn.Linear(token_s, token_z, bias=False)
430
+ self.rel_pos = RelativePositionEncoder(
431
+ token_z,
432
+ fix_sym_check=fix_sym_check,
433
+ cyclic_pos_enc=cyclic_pos_enc,
434
+ )
435
+ self.token_bonds = nn.Linear(1, token_z, bias=False)
436
+ if self.bond_type_feature:
437
+ self.token_bonds_type = nn.Embedding(len(const.bond_types) + 1, token_z)
438
+
439
+ self.contact_conditioning = ContactConditioning(
440
+ token_z=token_z,
441
+ cutoff_min=conditioning_cutoff_min,
442
+ cutoff_max=conditioning_cutoff_max,
443
+ )
444
+ self.s_norm = nn.LayerNorm(token_s)
445
+ self.z_norm = nn.LayerNorm(token_z)
446
+
447
+ self.s_recycle = nn.Linear(token_s, token_s, bias=False)
448
+ self.z_recycle = nn.Linear(token_z, token_z, bias=False)
449
+ init.gating_init_(self.s_recycle.weight)
450
+ init.gating_init_(self.z_recycle.weight)
451
+
452
+ torch._dynamo.config.cache_size_limit = 512 # noqa: SLF001
453
+ torch._dynamo.config.accumulated_cache_size_limit = 512 # noqa: SLF001
454
+
455
+ msa_kwargs = _filtered_kwargs(MSAModule, {"token_z": token_z, "token_s": token_s, **msa_args})
456
+ self.msa_module = MSAModule(**msa_kwargs)
457
+
458
+ pairformer_kwargs = _filtered_kwargs(
459
+ PairformerModule,
460
+ {"token_s": token_s, "token_z": token_z, **pairformer_args},
461
+ )
462
+ assert "token_s" in pairformer_kwargs and "token_z" in pairformer_kwargs
463
+ pairformer_token_s = pairformer_kwargs.pop("token_s")
464
+ pairformer_token_z = pairformer_kwargs.pop("token_z")
465
+ self.pairformer_module = PairformerModule(
466
+ pairformer_token_s,
467
+ pairformer_token_z,
468
+ **pairformer_kwargs,
469
+ )
470
+
471
+ diffusion_conditioning_kwargs = {
472
+ "token_s": token_s,
473
+ "token_z": token_z,
474
+ "atom_s": atom_s,
475
+ "atom_z": atom_z,
476
+ "atoms_per_window_queries": atoms_per_window_queries,
477
+ "atoms_per_window_keys": atoms_per_window_keys,
478
+ "atom_encoder_depth": score_model_args["atom_encoder_depth"],
479
+ "atom_encoder_heads": score_model_args["atom_encoder_heads"],
480
+ "token_transformer_depth": score_model_args["token_transformer_depth"],
481
+ "token_transformer_heads": score_model_args["token_transformer_heads"],
482
+ "atom_decoder_depth": score_model_args["atom_decoder_depth"],
483
+ "atom_decoder_heads": score_model_args["atom_decoder_heads"],
484
+ "atom_feature_dim": atom_feature_dim,
485
+ "conditioning_transition_layers": score_model_args["conditioning_transition_layers"],
486
+ "use_no_atom_char": use_no_atom_char,
487
+ "use_atom_backbone_feat": use_atom_backbone_feat,
488
+ "use_residue_feats_atoms": use_residue_feats_atoms,
489
+ }
490
+ diffusion_conditioning_kwargs = _filtered_kwargs(
491
+ DiffusionConditioning,
492
+ diffusion_conditioning_kwargs,
493
+ )
494
+ self.diffusion_conditioning = DiffusionConditioning(**diffusion_conditioning_kwargs)
495
+
496
+ structure_score_model_args = {
497
+ "token_s": token_s,
498
+ "atom_s": atom_s,
499
+ "atoms_per_window_queries": atoms_per_window_queries,
500
+ "atoms_per_window_keys": atoms_per_window_keys,
501
+ **score_model_args,
502
+ }
503
+ structure_score_model_args = _filtered_kwargs(
504
+ DiffusionModule,
505
+ structure_score_model_args,
506
+ )
507
+ structure_module_kwargs = {
508
+ "score_model_args": structure_score_model_args,
509
+ "compile_score": False,
510
+ **diffusion_process_args,
511
+ }
512
+ structure_module_kwargs = _filtered_kwargs(AtomDiffusion, structure_module_kwargs)
513
+ self.structure_module = AtomDiffusion(**structure_module_kwargs)
514
+ self.distogram_module = DistogramModule(token_z, num_bins)
515
+
516
+ if self.confidence_prediction:
517
+ assert confidence_model_args is not None, (
518
+ "confidence_prediction=True requires confidence_model_args in config."
519
+ )
520
+ confidence_kwargs = {
521
+ "token_s": token_s,
522
+ "token_z": token_z,
523
+ "token_level_confidence": token_level_confidence,
524
+ "bond_type_feature": bond_type_feature,
525
+ "fix_sym_check": fix_sym_check,
526
+ "cyclic_pos_enc": cyclic_pos_enc,
527
+ "conditioning_cutoff_min": conditioning_cutoff_min,
528
+ "conditioning_cutoff_max": conditioning_cutoff_max,
529
+ **confidence_model_args,
530
+ }
531
+ confidence_kwargs = _filtered_kwargs(ConfidenceModule, confidence_kwargs)
532
+ self.confidence_module = ConfidenceModule(**confidence_kwargs)
533
+
534
+ def forward(
535
+ self,
536
+ feats: Dict[str, Tensor],
537
+ recycling_steps: int = 3,
538
+ num_sampling_steps: Optional[int] = None,
539
+ diffusion_samples: int = 1,
540
+ max_parallel_samples: Optional[int] = None,
541
+ run_confidence_sequentially: bool = True,
542
+ detach_confidence: bool = True,
543
+ ) -> Dict[str, Tensor]:
544
+ s_inputs = self.input_embedder(feats)
545
+ s_init = self.s_init(s_inputs)
546
+
547
+ z_init = self.z_init_1(s_inputs)[:, :, None] + self.z_init_2(s_inputs)[:, None, :]
548
+ relative_position_encoding = self.rel_pos(feats)
549
+ z_init = z_init + relative_position_encoding
550
+ z_init = z_init + self.token_bonds(feats["token_bonds"].float())
551
+ if self.bond_type_feature:
552
+ z_init = z_init + self.token_bonds_type(feats["type_bonds"].long())
553
+ z_init = z_init + self.contact_conditioning(feats)
554
+
555
+ s = torch.zeros_like(s_init)
556
+ z = torch.zeros_like(z_init)
557
+ mask = feats["token_pad_mask"].float()
558
+ pair_mask = mask[:, :, None] * mask[:, None, :]
559
+
560
+ if self.run_trunk_and_structure:
561
+ for _ in range(recycling_steps + 1):
562
+ s = s_init + self.s_recycle(self.s_norm(s))
563
+ z = z_init + self.z_recycle(self.z_norm(z))
564
+ z = z + self.msa_module(
565
+ z,
566
+ s_inputs,
567
+ feats,
568
+ use_kernels=self.use_kernels,
569
+ )
570
+ s, z = self.pairformer_module(
571
+ s,
572
+ z,
573
+ mask=mask,
574
+ pair_mask=pair_mask,
575
+ use_kernels=self.use_kernels,
576
+ )
577
+
578
+ pdistogram = self.distogram_module(z)
579
+ output: Dict[str, Tensor] = {
580
+ "pdistogram": pdistogram,
581
+ "s": s,
582
+ "z": z,
583
+ }
584
+
585
+ if self.run_trunk_and_structure and (not self.skip_run_structure):
586
+ q, c, to_keys, atom_enc_bias, atom_dec_bias, token_trans_bias = (
587
+ self.diffusion_conditioning(
588
+ s_trunk=s,
589
+ z_trunk=z,
590
+ relative_position_encoding=relative_position_encoding,
591
+ feats=feats,
592
+ )
593
+ )
594
+ diffusion_conditioning = {
595
+ "q": q,
596
+ "c": c,
597
+ "to_keys": to_keys,
598
+ "atom_enc_bias": atom_enc_bias,
599
+ "atom_dec_bias": atom_dec_bias,
600
+ "token_trans_bias": token_trans_bias,
601
+ }
602
+ with torch.autocast("cuda", enabled=False):
603
+ struct_out = self.structure_module.sample(
604
+ s_trunk=s.float(),
605
+ s_inputs=s_inputs.float(),
606
+ feats=feats,
607
+ num_sampling_steps=num_sampling_steps,
608
+ atom_mask=feats["atom_pad_mask"].float(),
609
+ multiplicity=diffusion_samples,
610
+ max_parallel_samples=max_parallel_samples,
611
+ steering_args=self.steering_args,
612
+ diffusion_conditioning=diffusion_conditioning,
613
+ )
614
+ output.update(struct_out)
615
+
616
+ if self.confidence_prediction:
617
+ if self.skip_run_structure:
618
+ x_pred = feats["coords"].repeat_interleave(diffusion_samples, 0)
619
+ else:
620
+ assert "sample_atom_coords" in output, (
621
+ "Structure sampling did not produce sample_atom_coords."
622
+ )
623
+ x_pred = output["sample_atom_coords"]
624
+
625
+ if detach_confidence:
626
+ s_inputs_c = s_inputs.detach()
627
+ s_c = s.detach()
628
+ z_c = z.detach()
629
+ x_pred_c = x_pred.detach()
630
+ pdist_c = output["pdistogram"][:, :, :, 0].detach()
631
+ else:
632
+ s_inputs_c = s_inputs
633
+ s_c = s
634
+ z_c = z
635
+ x_pred_c = x_pred
636
+ pdist_c = output["pdistogram"][:, :, :, 0]
637
+
638
+ output.update(
639
+ self.confidence_module(
640
+ s_inputs=s_inputs_c,
641
+ s=s_c,
642
+ z=z_c,
643
+ x_pred=x_pred_c,
644
+ feats=feats,
645
+ pred_distogram_logits=pdist_c,
646
+ multiplicity=diffusion_samples,
647
+ run_sequentially=run_confidence_sequentially,
648
+ use_kernels=self.use_kernels,
649
+ )
650
+ )
651
+
652
+ return output
653
+
654
+
655
+ class Boltz2Model(PreTrainedModel):
656
+ config_class = Boltz2Config
657
+ base_model_prefix = "core"
658
+ all_tied_weights_keys = {}
659
+
660
+ def __init__(self, config: Boltz2Config) -> None:
661
+ super().__init__(config)
662
+ assert isinstance(config.core_kwargs, dict), "config.core_kwargs must be a dictionary."
663
+ self.core = Boltz2InferenceCore(**config.core_kwargs)
664
+
665
+ def _init_weights(self, module: nn.Module) -> None: # noqa: ARG002
666
+ return
667
+
668
+ def _detied_state_dict(self) -> Dict[str, Tensor]:
669
+ raw_state = self.state_dict()
670
+ seen_ptrs: Dict[int, str] = {}
671
+ out: Dict[str, Tensor] = {}
672
+ for key, tensor in raw_state.items():
673
+ if torch.is_tensor(tensor):
674
+ ptr = tensor.untyped_storage().data_ptr()
675
+ if ptr in seen_ptrs:
676
+ out[key] = tensor.clone()
677
+ else:
678
+ seen_ptrs[ptr] = key
679
+ out[key] = tensor
680
+ else:
681
+ out[key] = tensor
682
+ return out
683
+
684
+ def save_pretrained(self, save_directory: str, **kwargs: Any) -> None:
685
+ if "safe_serialization" not in kwargs:
686
+ kwargs["safe_serialization"] = False
687
+ if "state_dict" not in kwargs:
688
+ kwargs["state_dict"] = self._detied_state_dict()
689
+ super().save_pretrained(save_directory, **kwargs)
690
+
691
+ @property
692
+ def device(self) -> torch.device:
693
+ return next(self.parameters()).device
694
+
695
+ @classmethod
696
+ def from_boltz_checkpoint(
697
+ cls,
698
+ checkpoint_path: str,
699
+ map_location: Union[str, torch.device] = "cpu",
700
+ use_kernels: bool = False,
701
+ default_recycling_steps: Optional[int] = None,
702
+ default_sampling_steps: Optional[int] = None,
703
+ default_diffusion_samples: Optional[int] = None,
704
+ ) -> "Boltz2Model":
705
+ # Boltz Lightning checkpoints include OmegaConf objects and require full unpickling.
706
+ checkpoint = torch.load(
707
+ checkpoint_path,
708
+ map_location=map_location,
709
+ weights_only=False,
710
+ )
711
+ assert isinstance(checkpoint, dict), "Checkpoint must deserialize to a dictionary."
712
+ _require_key(checkpoint, "hyper_parameters")
713
+ _require_key(checkpoint, "state_dict")
714
+
715
+ hparams = checkpoint["hyper_parameters"]
716
+ assert isinstance(hparams, dict), "Checkpoint hyper_parameters must be a dictionary."
717
+ state_dict = checkpoint["state_dict"]
718
+ assert isinstance(state_dict, dict), "Checkpoint state_dict must be a dictionary."
719
+
720
+ config = Boltz2Config.from_hyperparameters(
721
+ hparams,
722
+ use_kernels=use_kernels,
723
+ default_recycling_steps=default_recycling_steps,
724
+ default_sampling_steps=default_sampling_steps,
725
+ default_diffusion_samples=default_diffusion_samples,
726
+ )
727
+ model = cls(config)
728
+ cleaned = _state_dict_without_wrappers(state_dict)
729
+ target_keys = set(model.core.state_dict().keys())
730
+ for key in target_keys:
731
+ assert ".attention.norm_s." not in key, (
732
+ "Boltz2 inference core unexpectedly uses v1 attention parameters. "
733
+ "Expected pairformer v2 architecture."
734
+ )
735
+ filtered: Dict[str, Tensor] = {}
736
+ for key, value in cleaned.items():
737
+ if key in target_keys:
738
+ filtered[key] = value
739
+
740
+ missing = sorted(target_keys.difference(filtered.keys()))
741
+ assert len(missing) == 0, (
742
+ "Checkpoint is missing required parameters for Boltz2 inference core. "
743
+ f"Missing keys (first 20): {missing[:20]}"
744
+ )
745
+
746
+ load_result = model.core.load_state_dict(filtered, strict=False)
747
+ loaded_missing = sorted(load_result.missing_keys)
748
+ assert len(loaded_missing) == 0, (
749
+ "Model has unexpected missing keys after load_state_dict. "
750
+ f"Missing keys (first 20): {loaded_missing[:20]}"
751
+ )
752
+ assert len(load_result.unexpected_keys) == 0
753
+ model.eval()
754
+ return model
755
+
756
+ def forward(
757
+ self,
758
+ feats: Dict[str, Tensor],
759
+ recycling_steps: Optional[int] = None,
760
+ num_sampling_steps: Optional[int] = None,
761
+ diffusion_samples: Optional[int] = None,
762
+ max_parallel_samples: Optional[int] = None,
763
+ run_confidence_sequentially: bool = True,
764
+ detach_confidence: bool = True,
765
+ ) -> Dict[str, Tensor]:
766
+ if recycling_steps is None:
767
+ recycling_steps = self.config.default_recycling_steps
768
+ if num_sampling_steps is None:
769
+ num_sampling_steps = self.config.default_sampling_steps
770
+ if diffusion_samples is None:
771
+ diffusion_samples = self.config.default_diffusion_samples
772
+ return self.core(
773
+ feats=feats,
774
+ recycling_steps=recycling_steps,
775
+ num_sampling_steps=num_sampling_steps,
776
+ diffusion_samples=diffusion_samples,
777
+ max_parallel_samples=max_parallel_samples,
778
+ run_confidence_sequentially=run_confidence_sequentially,
779
+ detach_confidence=detach_confidence,
780
+ )
781
+
782
+ def _to_model_device(
783
+ self,
784
+ feats: Dict[str, Tensor],
785
+ float_dtype: torch.dtype,
786
+ ) -> Dict[str, Tensor]:
787
+ moved: Dict[str, Tensor] = {}
788
+ for key, value in feats.items():
789
+ if torch.is_tensor(value):
790
+ if value.is_floating_point():
791
+ moved[key] = value.to(device=self.device, dtype=float_dtype)
792
+ else:
793
+ moved[key] = value.to(device=self.device)
794
+ else:
795
+ moved[key] = value
796
+ return moved
797
+
798
+ def predict_structure(
799
+ self,
800
+ amino_acid_sequence: str,
801
+ recycling_steps: Optional[int] = None,
802
+ num_sampling_steps: Optional[int] = None,
803
+ diffusion_samples: Optional[int] = None,
804
+ max_parallel_samples: Optional[int] = None,
805
+ run_confidence_sequentially: bool = True,
806
+ float_dtype: Optional[torch.dtype] = None,
807
+ ) -> Boltz2StructureOutput:
808
+ if float_dtype is None:
809
+ float_dtype = torch.float32
810
+
811
+ feats, template = build_boltz2_features(
812
+ amino_acid_sequence=amino_acid_sequence,
813
+ num_bins=self.config.num_bins,
814
+ atoms_per_window_queries=self.core.input_embedder.atom_encoder.atoms_per_window_queries,
815
+ )
816
+ feats = self._to_model_device(feats, float_dtype=float_dtype)
817
+
818
+ with torch.no_grad():
819
+ output = self.forward(
820
+ feats=feats,
821
+ recycling_steps=recycling_steps,
822
+ num_sampling_steps=num_sampling_steps,
823
+ diffusion_samples=diffusion_samples,
824
+ max_parallel_samples=max_parallel_samples,
825
+ run_confidence_sequentially=run_confidence_sequentially,
826
+ )
827
+
828
+ sample_atom_coords = output["sample_atom_coords"].detach().cpu()
829
+ non_finite_mask = torch.logical_not(torch.isfinite(sample_atom_coords))
830
+ assert not torch.any(non_finite_mask), (
831
+ "sample_atom_coords contains non-finite values. "
832
+ f"Non-finite count: {int(non_finite_mask.sum().item())}"
833
+ )
834
+ atom_pad_mask = feats["atom_pad_mask"][0].detach().cpu()
835
+ plddt = output["plddt"].detach().cpu() if "plddt" in output else None
836
+ complex_plddt = output["complex_plddt"].detach().cpu() if "complex_plddt" in output else None
837
+ iptm = output["iptm"].detach().cpu() if "iptm" in output else None
838
+ ptm = output["ptm"].detach().cpu() if "ptm" in output else None
839
+
840
+ confidence_score = None
841
+ if (complex_plddt is not None) and (iptm is not None) and (ptm is not None):
842
+ if torch.allclose(iptm, torch.zeros_like(iptm)):
843
+ confidence_score = (4 * complex_plddt + ptm) / 5
844
+ else:
845
+ confidence_score = (4 * complex_plddt + iptm) / 5
846
+
847
+ return Boltz2StructureOutput(
848
+ sample_atom_coords=sample_atom_coords,
849
+ atom_pad_mask=atom_pad_mask,
850
+ plddt=plddt,
851
+ confidence_score=confidence_score,
852
+ complex_plddt=complex_plddt,
853
+ iptm=iptm,
854
+ ptm=ptm,
855
+ sequence=template.sequence,
856
+ structure_template=template,
857
+ raw_output={key: _to_cpu_detached(val) for key, val in output.items()},
858
+ )
859
+
860
+ def save_as_cif(
861
+ self,
862
+ structure_output: Boltz2StructureOutput,
863
+ output_path: str,
864
+ sample_index: int = 0,
865
+ ) -> str:
866
+ assert structure_output.structure_template is not None, (
867
+ "structure_output.structure_template is required for CIF export."
868
+ )
869
+ assert structure_output.sample_atom_coords is not None, (
870
+ "structure_output.sample_atom_coords is required for CIF export."
871
+ )
872
+ assert structure_output.atom_pad_mask is not None, (
873
+ "structure_output.atom_pad_mask is required for CIF export."
874
+ )
875
+ return write_cif(
876
+ structure_template=structure_output.structure_template,
877
+ atom_coords=structure_output.sample_atom_coords,
878
+ atom_mask=structure_output.atom_pad_mask,
879
+ output_path=output_path,
880
+ plddt=structure_output.plddt,
881
+ sample_index=sample_index,
882
+ )