IAmKarthik's picture
Deploy ESM-2, MoLFormer, and affinity ONNX application
245bd92 verified
Raw
History Blame Contribute Delete
5.46 kB
from __future__ import annotations
import argparse
from pathlib import Path
import numpy as np
from affinity.onnx_embeddings import create_onnx_embedder, detect_onnx_providers
from affinity.pipeline import load_metadata, standardize_apply
class ThreeOnnxAffinityPredictor:
"""Protein ONNX + molecule ONNX + affinity-head ONNX inference."""
def __init__(
self,
artifact_directory: str | Path,
protein_directory: str | Path | None = None,
molecule_directory: str | Path | None = None,
device: str = "auto",
*,
prollama_directory: str | Path | None = None,
mol_llama_directory: str | Path | None = None,
) -> None:
import onnxruntime as ort
protein_directory = protein_directory or prollama_directory
molecule_directory = molecule_directory or mol_llama_directory
if protein_directory is None or molecule_directory is None:
raise ValueError("Both protein and molecule ONNX directories are required")
artifact = Path(artifact_directory)
metadata = load_metadata(artifact / "metadata.json")
feature_metadata = metadata.get("features", {})
if feature_metadata.get("feature_mode") != "onnx_embeddings":
raise ValueError("The affinity artifact was not trained with ONNX embeddings")
protein_settings = feature_metadata.get("protein_extraction", {})
molecule_settings = feature_metadata.get("molecule_extraction", {})
protein_type = protein_settings.get("encoder_type")
if not protein_type:
protein_type = (
"prollama"
if "prollama" in feature_metadata["protein_model"].lower()
else "esm2"
)
molecule_type = molecule_settings.get("encoder_type")
if not molecule_type:
molecule_type = (
"mol_llama"
if "mol-llama" in feature_metadata["molecule_model"].lower()
else "molformer"
)
providers = detect_onnx_providers(device, verbose=True)
self.protein_embedder = create_onnx_embedder(
protein_type,
model_directory=protein_directory,
model_id=feature_metadata["protein_model"],
max_length=int(protein_settings.get("max_length", 1536)),
providers=providers,
)
exported_protein_id = self.protein_embedder.metadata.get("model_id")
if exported_protein_id and exported_protein_id != feature_metadata["protein_model"]:
raise ValueError(
"Protein ONNX model ID does not match the training embedding cache"
)
self.molecule_embedder = create_onnx_embedder(
molecule_type,
model_directory=molecule_directory,
model_id=feature_metadata["molecule_model"],
max_length=int(molecule_settings.get("max_length", 202)),
providers=providers,
)
exported_molecule_id = self.molecule_embedder.metadata.get("model_id")
if exported_molecule_id and exported_molecule_id != feature_metadata["molecule_model"]:
raise ValueError(
"Molecule ONNX model ID does not match the training embedding cache"
)
self.session = ort.InferenceSession(
str(artifact / "model.onnx"),
providers=providers,
)
normalization = np.load(artifact / "normalization.npz")
self.mean = normalization["mean"]
self.scale = normalization["scale"]
def predict(self, proteins: list[str], smiles_values: list[str]) -> np.ndarray:
if len(proteins) != len(smiles_values):
raise ValueError("Protein and molecule input counts must match")
protein_embeddings = self.protein_embedder.encode(proteins)
molecule_embeddings = self.molecule_embedder.encode(smiles_values)
features = np.concatenate([protein_embeddings, molecule_embeddings], axis=1)
if features.shape[1] != len(self.mean):
raise ValueError(
f"ONNX embedding dimension {features.shape[1]} does not match "
f"the affinity model dimension {len(self.mean)}"
)
features = standardize_apply(features, self.mean, self.scale)
return self.session.run(["affinity"], {"features": features})[0]
def main() -> None:
parser = argparse.ArgumentParser(
description="Predict affinity through three ONNX models"
)
parser.add_argument("--protein", required=True)
parser.add_argument("--smiles", required=True)
parser.add_argument("--artifacts", default="/content/artifacts/affinity")
parser.add_argument(
"--protein-onnx",
"--prollama-onnx",
dest="protein_onnx",
required=True,
)
parser.add_argument(
"--molecule-onnx",
"--mol-llama-onnx",
dest="molecule_onnx",
required=True,
)
parser.add_argument("--device", choices=["auto", "cpu", "cuda"], default="auto")
args = parser.parse_args()
predictor = ThreeOnnxAffinityPredictor(
artifact_directory=args.artifacts,
protein_directory=args.protein_onnx,
molecule_directory=args.molecule_onnx,
device=args.device,
)
prediction = float(predictor.predict([args.protein], [args.smiles])[0])
print(f"{prediction:.6f}")
if __name__ == "__main__":
main()