Download scripts/inference.py from OneScience-Group/OpenFold: direct link, hf CLI and curl.
- Browser
- Download file 19.3 kB
-
https://huggingface.co/OneScience-Group/OpenFold/resolve/main/scripts/inference.py
- Command line
-
hf download hf://OneScience-Group/OpenFold/scripts/inference.py
-
curl -L -o inference.py https://huggingface.co/OneScience-Group/OpenFold/resolve/main/scripts/inference.py
19.3 kB
| # Copyright 2021 AlQuraishi Laboratory | |
| # Copyright 2021 DeepMind Technologies Limited | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import argparse | |
| import logging | |
| import math | |
| import numpy as np | |
| import os | |
| import pickle | |
| import random | |
| import time | |
| import json | |
| from pathlib import Path | |
| import sys | |
| root_path = Path(__file__).parent.parent | |
| sys.path.append(str(root_path)) | |
| logging.basicConfig() | |
| logger = logging.getLogger(__file__) | |
| logger.setLevel(level=logging.INFO) | |
| import torch | |
| torch_versions = torch.__version__.split(".") | |
| torch_major_version = int(torch_versions[0]) | |
| torch_minor_version = int(torch_versions[1]) | |
| if ( | |
| torch_major_version > 1 or | |
| (torch_major_version == 1 and torch_minor_version >= 12) | |
| ): | |
| # Gives a large speedup on Ampere-class GPUs | |
| torch.set_float32_matmul_precision("high") | |
| torch.set_grad_enabled(False) | |
| from onescience.configs.bio.openfold.config import model_config | |
| from onescience.datapipes.openfold import templates, feature_pipeline, data_pipeline | |
| from onescience.datapipes.openfold.tools import hhsearch, hmmsearch | |
| from onescience.utils.openfold.np import protein | |
| from onescience.utils.openfold.script_utils import (load_models_from_command_line, parse_fasta, run_model, | |
| prep_output, relax_protein) | |
| from onescience.utils.openfold.tensor_utils import tensor_tree_map | |
| from onescience.utils.openfold.trace_utils import ( | |
| pad_feature_dict_seq, | |
| trace_model_, | |
| ) | |
| # from scripts.precompute_embeddings import EmbeddingGenerator | |
| # from scripts.utils import add_data_args | |
| from precompute_embeddings import EmbeddingGenerator | |
| from utils import add_data_args | |
| TRACING_INTERVAL = 50 | |
| def precompute_alignments(tags, seqs, alignment_dir, args): | |
| for tag, seq in zip(tags, seqs): | |
| tmp_fasta_path = os.path.join(args.output_dir, f"tmp_{os.getpid()}.fasta") | |
| with open(tmp_fasta_path, "w") as fp: | |
| fp.write(f">{tag}\n{seq}") | |
| local_alignment_dir = os.path.join(alignment_dir, tag) | |
| if args.use_precomputed_alignments is None: | |
| logger.info(f"Generating alignments for {tag}...") | |
| os.makedirs(local_alignment_dir, exist_ok=True) | |
| if "multimer" in args.config_preset: | |
| template_searcher = hmmsearch.Hmmsearch( | |
| binary_path=args.hmmsearch_binary_path, | |
| hmmbuild_binary_path=args.hmmbuild_binary_path, | |
| database_path=args.pdb_seqres_database_path, | |
| ) | |
| else: | |
| template_searcher = hhsearch.HHSearch( | |
| binary_path=args.hhsearch_binary_path, | |
| databases=[args.pdb70_database_path], | |
| ) | |
| # In seqemb mode, use AlignmentRunner only to generate templates | |
| if args.use_single_seq_mode: | |
| alignment_runner = data_pipeline.AlignmentRunner( | |
| jackhmmer_binary_path=args.jackhmmer_binary_path, | |
| uniref90_database_path=args.uniref90_database_path, | |
| template_searcher=template_searcher, | |
| no_cpus=args.cpus, | |
| ) | |
| embedding_generator = EmbeddingGenerator() | |
| embedding_generator.run(tmp_fasta_path, alignment_dir) | |
| else: | |
| alignment_runner = data_pipeline.AlignmentRunner( | |
| jackhmmer_binary_path=args.jackhmmer_binary_path, | |
| hhblits_binary_path=args.hhblits_binary_path, | |
| uniref90_database_path=args.uniref90_database_path, | |
| mgnify_database_path=args.mgnify_database_path, | |
| bfd_database_path=args.bfd_database_path, | |
| uniref30_database_path=args.uniref30_database_path, | |
| uniclust30_database_path=args.uniclust30_database_path, | |
| uniprot_database_path=args.uniprot_database_path, | |
| template_searcher=template_searcher, | |
| use_small_bfd=args.bfd_database_path is None, | |
| no_cpus=args.cpus | |
| ) | |
| alignment_runner.run( | |
| tmp_fasta_path, local_alignment_dir | |
| ) | |
| else: | |
| logger.info( | |
| f"Using precomputed alignments for {tag} at {alignment_dir}..." | |
| ) | |
| # Remove temporary FASTA file | |
| os.remove(tmp_fasta_path) | |
| def round_up_seqlen(seqlen): | |
| return int(math.ceil(seqlen / TRACING_INTERVAL)) * TRACING_INTERVAL | |
| def generate_feature_dict( | |
| tags, | |
| seqs, | |
| alignment_dir, | |
| data_processor, | |
| args, | |
| ): | |
| tmp_fasta_path = os.path.join(args.output_dir, f"tmp_{os.getpid()}.fasta") | |
| if "multimer" in args.config_preset: | |
| with open(tmp_fasta_path, "w") as fp: | |
| fp.write( | |
| '\n'.join([f">{tag}\n{seq}" for tag, seq in zip(tags, seqs)]) | |
| ) | |
| feature_dict = data_processor.process_fasta( | |
| fasta_path=tmp_fasta_path, alignment_dir=alignment_dir, | |
| ) | |
| elif len(seqs) == 1: | |
| tag = tags[0] | |
| seq = seqs[0] | |
| with open(tmp_fasta_path, "w") as fp: | |
| fp.write(f">{tag}\n{seq}") | |
| local_alignment_dir = os.path.join(alignment_dir, tag) | |
| feature_dict = data_processor.process_fasta( | |
| fasta_path=tmp_fasta_path, | |
| alignment_dir=local_alignment_dir, | |
| seqemb_mode=args.use_single_seq_mode, | |
| ) | |
| else: | |
| with open(tmp_fasta_path, "w") as fp: | |
| fp.write( | |
| '\n'.join([f">{tag}\n{seq}" for tag, seq in zip(tags, seqs)]) | |
| ) | |
| feature_dict = data_processor.process_multiseq_fasta( | |
| fasta_path=tmp_fasta_path, super_alignment_dir=alignment_dir, | |
| ) | |
| # Remove temporary FASTA file | |
| os.remove(tmp_fasta_path) | |
| return feature_dict | |
| def list_files_with_extensions(dir, extensions): | |
| return [f for f in os.listdir(dir) if f.endswith(extensions)] | |
| def main(args): | |
| # Create the output directory | |
| os.makedirs(args.output_dir, exist_ok=True) | |
| if args.config_preset.startswith("seq"): | |
| args.use_single_seq_mode = True | |
| config = model_config( | |
| args.config_preset, | |
| long_sequence_inference=args.long_sequence_inference, | |
| use_deepspeed_evoformer_attention=args.use_deepspeed_evoformer_attention, | |
| ) | |
| if args.experiment_config_json: | |
| with open(args.experiment_config_json, 'r') as f: | |
| custom_config_dict = json.load(f) | |
| config.update_from_flattened_dict(custom_config_dict) | |
| if args.trace_model: | |
| if not config.data.predict.fixed_size: | |
| raise ValueError( | |
| "Tracing requires that fixed_size mode be enabled in the config" | |
| ) | |
| is_multimer = "multimer" in args.config_preset | |
| is_custom_template = "use_custom_template" in args and args.use_custom_template | |
| if is_custom_template: | |
| template_featurizer = templates.CustomHitFeaturizer( | |
| mmcif_dir=args.template_mmcif_dir, | |
| max_template_date="9999-12-31", # just dummy, not used | |
| max_hits=-1, # just dummy, not used | |
| kalign_binary_path=args.kalign_binary_path | |
| ) | |
| elif is_multimer: | |
| template_featurizer = templates.HmmsearchHitFeaturizer( | |
| mmcif_dir=args.template_mmcif_dir, | |
| max_template_date=args.max_template_date, | |
| max_hits=config.data.predict.max_templates, | |
| kalign_binary_path=args.kalign_binary_path, | |
| release_dates_path=args.release_dates_path, | |
| obsolete_pdbs_path=args.obsolete_pdbs_path | |
| ) | |
| else: | |
| template_featurizer = templates.HhsearchHitFeaturizer( | |
| mmcif_dir=args.template_mmcif_dir, | |
| max_template_date=args.max_template_date, | |
| max_hits=config.data.predict.max_templates, | |
| kalign_binary_path=args.kalign_binary_path, | |
| release_dates_path=args.release_dates_path, | |
| obsolete_pdbs_path=args.obsolete_pdbs_path | |
| ) | |
| data_processor = data_pipeline.DataPipeline( | |
| template_featurizer=template_featurizer, | |
| ) | |
| if is_multimer: | |
| data_processor = data_pipeline.DataPipelineMultimer( | |
| monomer_data_pipeline=data_processor, | |
| ) | |
| output_dir_base = args.output_dir | |
| random_seed = args.data_random_seed | |
| if random_seed is None: | |
| random_seed = random.randrange(2 ** 32) | |
| np.random.seed(random_seed) | |
| torch.manual_seed(random_seed + 1) | |
| feature_processor = feature_pipeline.FeaturePipeline(config.data) | |
| if not os.path.exists(output_dir_base): | |
| os.makedirs(output_dir_base) | |
| if args.use_precomputed_alignments is None: | |
| alignment_dir = os.path.join(output_dir_base, "alignments") | |
| else: | |
| alignment_dir = args.use_precomputed_alignments | |
| tag_list = [] | |
| seq_list = [] | |
| for fasta_file in list_files_with_extensions(args.fasta_dir, (".fasta", ".fa")): | |
| # Gather input sequences | |
| fasta_path = os.path.join(args.fasta_dir, fasta_file) | |
| with open(fasta_path, "r") as fp: | |
| data = fp.read() | |
| tags, seqs = parse_fasta(data) | |
| if not is_multimer and len(tags) != 1: | |
| print( | |
| f"{fasta_path} contains more than one sequence but " | |
| f"multimer mode is not enabled. Skipping..." | |
| ) | |
| continue | |
| # assert len(tags) == len(set(tags)), "All FASTA tags must be unique" | |
| tag = '-'.join(tags) | |
| tag_list.append((tag, tags)) | |
| seq_list.append(seqs) | |
| seq_sort_fn = lambda target: sum([len(s) for s in target[1]]) | |
| sorted_targets = sorted(zip(tag_list, seq_list), key=seq_sort_fn) | |
| feature_dicts = {} | |
| if is_multimer and args.openfold_checkpoint_path: | |
| raise ValueError( | |
| '`openfold_checkpoint_path` was specified, but no OpenFold checkpoints are available for multimer mode') | |
| model_generator = load_models_from_command_line( | |
| config, | |
| args.model_device, | |
| args.openfold_checkpoint_path, | |
| args.jax_param_path, | |
| args.output_dir) | |
| for model, output_directory in model_generator: | |
| cur_tracing_interval = 0 | |
| for (tag, tags), seqs in sorted_targets: | |
| output_name = f'{tag}_{args.config_preset}' | |
| if args.output_postfix is not None: | |
| output_name = f'{output_name}_{args.output_postfix}' | |
| # Does nothing if the alignments have already been computed | |
| precompute_alignments(tags, seqs, alignment_dir, args) | |
| feature_dict = feature_dicts.get(tag, None) | |
| if feature_dict is None: | |
| feature_dict = generate_feature_dict( | |
| tags, | |
| seqs, | |
| alignment_dir, | |
| data_processor, | |
| args, | |
| ) | |
| if args.trace_model: | |
| n = feature_dict["aatype"].shape[-2] | |
| rounded_seqlen = round_up_seqlen(n) | |
| feature_dict = pad_feature_dict_seq( | |
| feature_dict, rounded_seqlen, | |
| ) | |
| feature_dicts[tag] = feature_dict | |
| processed_feature_dict = feature_processor.process_features( | |
| feature_dict, mode='predict', is_multimer=is_multimer | |
| ) | |
| processed_feature_dict = { | |
| k: torch.as_tensor(v, device=args.model_device) | |
| for k, v in processed_feature_dict.items() | |
| } | |
| if args.trace_model: | |
| if rounded_seqlen > cur_tracing_interval: | |
| logger.info( | |
| f"Tracing model at {rounded_seqlen} residues..." | |
| ) | |
| t = time.perf_counter() | |
| trace_model_(model, processed_feature_dict) | |
| tracing_time = time.perf_counter() - t | |
| logger.info( | |
| f"Tracing time: {tracing_time}" | |
| ) | |
| cur_tracing_interval = rounded_seqlen | |
| out = run_model(model, processed_feature_dict, tag, args.output_dir) | |
| # Toss out the recycling dimensions --- we don't need them anymore | |
| processed_feature_dict = tensor_tree_map( | |
| lambda x: np.array(x[..., -1].cpu()), | |
| processed_feature_dict | |
| ) | |
| out = tensor_tree_map(lambda x: np.array(x.cpu()), out) | |
| unrelaxed_protein = prep_output( | |
| out, | |
| processed_feature_dict, | |
| feature_dict, | |
| feature_processor, | |
| args.config_preset, | |
| args.multimer_ri_gap, | |
| args.subtract_plddt | |
| ) | |
| unrelaxed_file_suffix = "_unrelaxed.pdb" | |
| if args.cif_output: | |
| unrelaxed_file_suffix = "_unrelaxed.cif" | |
| unrelaxed_output_path = os.path.join( | |
| output_directory, f'{output_name}{unrelaxed_file_suffix}' | |
| ) | |
| with open(unrelaxed_output_path, 'w') as fp: | |
| if args.cif_output: | |
| fp.write(protein.to_modelcif(unrelaxed_protein)) | |
| else: | |
| fp.write(protein.to_pdb(unrelaxed_protein)) | |
| logger.info(f"Output written to {unrelaxed_output_path}...") | |
| if not args.skip_relaxation: | |
| # Relax the prediction. | |
| logger.info(f"Running relaxation on {unrelaxed_output_path}...") | |
| relax_protein(config, args.model_device, unrelaxed_protein, output_directory, output_name, | |
| args.cif_output) | |
| if args.save_outputs: | |
| output_dict_path = os.path.join( | |
| output_directory, f'{output_name}_output_dict.pkl' | |
| ) | |
| with open(output_dict_path, "wb") as fp: | |
| pickle.dump(out, fp, protocol=pickle.HIGHEST_PROTOCOL) | |
| logger.info(f"Model output written to {output_dict_path}...") | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "fasta_dir", type=str, | |
| help="Path to directory containing FASTA files, one sequence per file" | |
| ) | |
| parser.add_argument( | |
| "template_mmcif_dir", type=str, | |
| ) | |
| parser.add_argument( | |
| "--use_precomputed_alignments", type=str, default=None, | |
| help="""Path to alignment directory. If provided, alignment computation | |
| is skipped and database path arguments are ignored.""" | |
| ) | |
| parser.add_argument( | |
| "--use_custom_template", action="store_true", default=False, | |
| help="""Use mmcif given with "template_mmcif_dir" argument as template input.""" | |
| ) | |
| parser.add_argument( | |
| "--use_single_seq_mode", action="store_true", default=False, | |
| help="""Use single sequence embeddings instead of MSAs.""" | |
| ) | |
| parser.add_argument( | |
| "--output_dir", type=str, default=os.getcwd(), | |
| help="""Name of the directory in which to output the prediction""", | |
| ) | |
| parser.add_argument( | |
| "--model_device", type=str, default="cpu", | |
| help="""Name of the device on which to run the model. Any valid torch | |
| device name is accepted (e.g. "cpu", "cuda:0")""" | |
| ) | |
| parser.add_argument( | |
| "--config_preset", type=str, default="model_1", | |
| help="""Name of a model config preset defined in openfold/config.py""" | |
| ) | |
| parser.add_argument( | |
| "--jax_param_path", type=str, default=None, | |
| help="""Path to JAX model parameters. If None, and openfold_checkpoint_path | |
| is also None, parameters are selected automatically according to | |
| the model name from openfold/resources/params""" | |
| ) | |
| parser.add_argument( | |
| "--openfold_checkpoint_path", type=str, default=None, | |
| help="""Path to OpenFold checkpoint. Can be either a DeepSpeed | |
| checkpoint directory or a .pt file""" | |
| ) | |
| parser.add_argument( | |
| "--save_outputs", action="store_true", default=False, | |
| help="Whether to save all model outputs, including embeddings, etc." | |
| ) | |
| parser.add_argument( | |
| "--cpus", type=int, default=4, | |
| help="""Number of CPUs with which to run alignment tools""" | |
| ) | |
| parser.add_argument( | |
| "--preset", type=str, default='full_dbs', | |
| choices=('reduced_dbs', 'full_dbs') | |
| ) | |
| parser.add_argument( | |
| "--output_postfix", type=str, default=None, | |
| help="""Postfix for output prediction filenames""" | |
| ) | |
| parser.add_argument( | |
| "--data_random_seed", type=int, default=None | |
| ) | |
| parser.add_argument( | |
| "--skip_relaxation", action="store_true", default=False, | |
| ) | |
| parser.add_argument( | |
| "--multimer_ri_gap", type=int, default=200, | |
| help="""Residue index offset between multiple sequences, if provided""" | |
| ) | |
| parser.add_argument( | |
| "--trace_model", action="store_true", default=False, | |
| help="""Whether to convert parts of each model to TorchScript. | |
| Significantly improves runtime at the cost of lengthy | |
| 'compilation.' Useful for large batch jobs.""" | |
| ) | |
| parser.add_argument( | |
| "--subtract_plddt", action="store_true", default=False, | |
| help=""""Whether to output (100 - pLDDT) in the B-factor column instead | |
| of the pLDDT itself""" | |
| ) | |
| parser.add_argument( | |
| "--long_sequence_inference", action="store_true", default=False, | |
| help="""enable options to reduce memory usage at the cost of speed, helps longer sequences fit into GPU memory, see the README for details""" | |
| ) | |
| parser.add_argument( | |
| "--cif_output", action="store_true", default=False, | |
| help="Output predicted models in ModelCIF format instead of PDB format (default)" | |
| ) | |
| parser.add_argument( | |
| "--experiment_config_json", default="", help="Path to a json file with custom config values to overwrite config setting", | |
| ) | |
| parser.add_argument( | |
| "--use_deepspeed_evoformer_attention", action="store_true", default=False, | |
| help="Whether to use the DeepSpeed evoformer attention layer. Must have deepspeed installed in the environment.", | |
| ) | |
| add_data_args(parser) | |
| args = parser.parse_args() | |
| if args.jax_param_path is None and args.openfold_checkpoint_path is None: | |
| args.jax_param_path = os.path.join( | |
| "openfold", "resources", "params", | |
| "params_" + args.config_preset + ".npz" | |
| ) | |
| if args.model_device == "cpu" and torch.cuda.is_available(): | |
| logging.warning( | |
| """The model is being run on CPU. Consider specifying | |
| --model_device for better performance""" | |
| ) | |
| main(args) | |