Skip to content

Commit ff1204d

Browse files
authored
Merge pull request #35 from theproteinbot/feat/multi-substrate-ki-separation
feat(web): multi-substrate input, Ki separation, and frontend improvements
2 parents fbb5ef0 + b4d7b3b commit ff1204d

51 files changed

Lines changed: 6964 additions & 63 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.vercel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CATPRED_DEFAULT_BACKEND=modal
2+
CATPRED_MODAL_ENDPOINT=https://kaalabhairava2026--catpred-modal-api-predict.modal.run
3+
CATPRED_MODAL_TOKEN=
4+
CATPRED_MODAL_FALLBACK_TO_LOCAL=0

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
validate:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: "3.10"
22+
23+
- name: Install minimal runtime dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
pip install -r requirements.txt
27+
28+
- name: Compile Python sources
29+
run: |
30+
git ls-files '*.py' | xargs -r python -m py_compile
31+
32+
- name: Smoke test API entrypoints
33+
run: |
34+
python - <<'PY'
35+
from catpred.web.app import create_app
36+
from api.index import app as vercel_app
37+
38+
api_app = create_app()
39+
assert api_app.title == "CatPred API"
40+
assert vercel_app is not None
41+
print("API smoke checks passed.")
42+
PY

.github/workflows/deploy-modal.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Deploy Modal
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- ".github/workflows/deploy-modal.yml"
10+
- "modal_app.py"
11+
- "predict.py"
12+
- "scripts/create_pdbrecords.py"
13+
- "catpred/**"
14+
15+
jobs:
16+
deploy:
17+
runs-on: ubuntu-latest
18+
permissions:
19+
contents: read
20+
concurrency:
21+
group: modal-production-deploy
22+
cancel-in-progress: true
23+
env:
24+
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
25+
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
26+
steps:
27+
- name: Skip when Modal credentials are not configured
28+
if: ${{ env.MODAL_TOKEN_ID == '' || env.MODAL_TOKEN_SECRET == '' }}
29+
run: echo "Skipping Modal deploy because MODAL_TOKEN_ID / MODAL_TOKEN_SECRET are not configured."
30+
31+
- name: Checkout
32+
if: ${{ env.MODAL_TOKEN_ID != '' && env.MODAL_TOKEN_SECRET != '' }}
33+
uses: actions/checkout@v4
34+
35+
- name: Set up Python
36+
if: ${{ env.MODAL_TOKEN_ID != '' && env.MODAL_TOKEN_SECRET != '' }}
37+
uses: actions/setup-python@v5
38+
with:
39+
python-version: "3.10"
40+
41+
- name: Install Modal CLI
42+
if: ${{ env.MODAL_TOKEN_ID != '' && env.MODAL_TOKEN_SECRET != '' }}
43+
run: |
44+
python -m pip install --upgrade pip
45+
pip install "modal>=0.73"
46+
47+
- name: Deploy modal_app.py
48+
if: ${{ env.MODAL_TOKEN_ID != '' && env.MODAL_TOKEN_SECRET != '' }}
49+
run: modal deploy modal_app.py

.vercelignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.venv/
2+
.e2e-assets/
3+
.e2e-tests/
4+
results/
5+
output/
6+
checkpoints/
7+
external/
8+
*.ipynb

README.md

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
- [Installation](#installing)
2525
- [Prediction](#predict)
2626
- [Web API (Optional)](#web-api-optional)
27+
- [Vercel Deployment (Optional)](#vercel-deployment-optional)
2728
- [Reproducibility](#reproduce)
2829
- [Acknowledgements](#acknw)
2930
- [License](#license)
@@ -126,6 +127,7 @@ By default, the API is hardened for service use:
126127
- `input_file` requests are disabled (use `input_rows` instead).
127128
- request-time overrides of `repo_root` / `python_executable` are disabled.
128129
- `results_dir` is constrained under `CATPRED_API_RESULTS_ROOT`.
130+
- for local backend (and modal requests with fallback enabled), `checkpoint_dir` must resolve under `CATPRED_API_CHECKPOINT_ROOT`.
129131

130132
Minimal `POST /predict` example for local inference using `input_rows`:
131133

@@ -134,7 +136,7 @@ curl -X POST http://127.0.0.1:8000/predict \
134136
-H "Content-Type: application/json" \
135137
-d '{
136138
"parameter": "kcat",
137-
"checkpoint_dir": "../data/pretrained/reproduce_checkpoints/kcat",
139+
"checkpoint_dir": "kcat",
138140
"input_rows": [
139141
{"SMILES": "CCO", "sequence": "ACDEFGHIK", "pdbpath": "seq_a"},
140142
{"SMILES": "CCN", "sequence": "LMNPQRSTV", "pdbpath": "seq_b"}
@@ -154,6 +156,7 @@ export CATPRED_MODAL_FALLBACK_TO_LOCAL=1
154156
```
155157

156158
Use `"backend": "modal"` in `/predict` requests to route through Modal. If fallback is enabled (env var above or request field `fallback_to_local`), failed modal requests can automatically reroute to local inference.
159+
For local backend requests, place local checkpoints under `CATPRED_API_CHECKPOINT_ROOT` and pass a path relative to that root (for example, `"checkpoint_dir": "kcat"`).
157160

158161
Optional API environment variables:
159162

@@ -183,6 +186,93 @@ export CATPRED_TRUSTED_DESERIALIZATION_ROOTS="/srv/catpred:/srv/catpred-data"
183186
export CATPRED_ALLOW_UNSAFE_DESERIALIZATION=1
184187
```
185188

189+
### ▲ Vercel Deployment (Optional) <a name="vercel-deployment-optional"></a>
190+
191+
This repository includes a Vercel-ready ASGI entrypoint at `api/index.py` and a `vercel.json` route config.
192+
193+
1. Push this repository to GitHub.
194+
2. In Vercel, create a new project from that repo.
195+
3. Set Environment Variables in Vercel Project Settings:
196+
197+
```bash
198+
# Use remote inference backend in serverless deployments
199+
CATPRED_DEFAULT_BACKEND=modal
200+
CATPRED_MODAL_ENDPOINT=https://<your-modal-endpoint>
201+
CATPRED_MODAL_TOKEN=<optional-token>
202+
CATPRED_MODAL_FALLBACK_TO_LOCAL=0
203+
```
204+
205+
Notes:
206+
- Serverless filesystems are ephemeral/read-only except `/tmp`; this app auto-uses `/tmp/catpred` on Vercel.
207+
- Local checkpoint-based inference is not recommended on Vercel serverless due runtime/dependency limits.
208+
- If `CATPRED_MODAL_ENDPOINT` is not configured, the UI still loads but prediction requests will be limited by backend readiness.
209+
210+
#### Deploy a Modal endpoint for Vercel
211+
212+
This repo includes `modal_app.py`, a Modal `POST` endpoint compatible with CatPred's `/predict` modal backend contract.
213+
214+
1. Install and authenticate Modal CLI:
215+
216+
```bash
217+
pip install modal
218+
modal setup
219+
```
220+
221+
2. Create/upload checkpoints into a Modal Volume (one-time):
222+
223+
```bash
224+
modal volume create catpred-checkpoints
225+
modal volume put catpred-checkpoints ./checkpoints/kcat kcat
226+
modal volume put catpred-checkpoints ./checkpoints/km km
227+
modal volume put catpred-checkpoints ./checkpoints/ki ki
228+
```
229+
230+
3. (Recommended) create a secret token for endpoint auth:
231+
232+
```bash
233+
modal secret create catpred-modal-auth CATPRED_MODAL_AUTH_TOKEN="<your-token>"
234+
```
235+
236+
4. Deploy:
237+
238+
```bash
239+
modal deploy modal_app.py
240+
```
241+
242+
After deploy, copy the printed endpoint URL (for function `predict`) and set Vercel variables:
243+
244+
```bash
245+
CATPRED_DEFAULT_BACKEND=modal
246+
CATPRED_MODAL_ENDPOINT=https://<your-modal-endpoint>
247+
CATPRED_MODAL_TOKEN=<your-token>
248+
CATPRED_MODAL_FALLBACK_TO_LOCAL=0
249+
```
250+
251+
#### CI/CD (GitHub Actions + Vercel + Modal)
252+
253+
This repo includes two GitHub Actions workflows:
254+
255+
- `.github/workflows/ci.yml`
256+
- Runs on every PR and push to `main`.
257+
- Installs minimal API dependencies, compiles all Python files, and smoke-tests API entrypoints.
258+
- `.github/workflows/deploy-modal.yml`
259+
- Runs on push to `main` when backend files change (and manually via `workflow_dispatch`).
260+
- Deploys `modal_app.py` automatically.
261+
262+
To enable automatic Modal deploys from GitHub Actions, add repository secrets:
263+
264+
- `MODAL_TOKEN_ID`
265+
- `MODAL_TOKEN_SECRET`
266+
267+
Create these from Modal:
268+
269+
1. Go to [https://modal.com/settings/tokens](https://modal.com/settings/tokens).
270+
2. Create a token with deploy permissions for your workspace.
271+
3. Copy token ID and secret into GitHub repo settings:
272+
`Settings -> Secrets and variables -> Actions -> New repository secret`.
273+
274+
Vercel deployment remains automatic from the connected GitHub branch (`main`).
275+
186276
### 🧪 Fine-Tuning On Custom Data
187277
188278
You can fine-tune CatPred on your own regression targets using `train.py`.

api/index.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
import os
5+
import sys
6+
7+
8+
ROOT = Path(__file__).resolve().parent.parent
9+
if str(ROOT) not in sys.path:
10+
sys.path.insert(0, str(ROOT))
11+
12+
if os.environ.get("VERCEL"):
13+
os.environ.setdefault("CATPRED_API_RUNTIME_ROOT", "/tmp/catpred")
14+
os.environ.setdefault("CATPRED_MODAL_FALLBACK_TO_LOCAL", "0")
15+
if os.environ.get("CATPRED_MODAL_ENDPOINT"):
16+
os.environ.setdefault("CATPRED_DEFAULT_BACKEND", "modal")
17+
18+
from catpred.web.app import app
19+
20+
21+
__all__ = ["app"]

catpred/data/utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
from __future__ import annotations
2+
13
from collections import OrderedDict, defaultdict
24
import sys
35
import csv
46
import ctypes
57
from logging import Logger
68
from random import Random
7-
from typing import List, Set, Tuple, Union
9+
from typing import List, Set, Tuple, Union, TYPE_CHECKING
810
import os
911
import json
1012
import torch
@@ -18,11 +20,13 @@
1820
from .esm_utils import get_protein_embedder, get_coords
1921
from .data import MoleculeDatapoint, MoleculeDataset, make_mols
2022
from .scaffold import log_scaffold_stats, scaffold_split
21-
from catpred.args import PredictArgs, TrainArgs
2223
from catpred.features import load_features, load_valid_atom_or_bond_features, is_mol
2324
from catpred.rdkit import make_mol
2425
from catpred.security import load_index_artifact, load_pickle_artifact
2526

27+
if TYPE_CHECKING:
28+
from catpred.args import PredictArgs, TrainArgs
29+
2630
# Increase maximum size of field in the csv processing for the current architecture
2731
csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
2832

catpred/features/features_generators.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ def morgan_binary_features_generator(mol: Molecule,
8585

8686
# return features
8787

88-
import ipdb
8988
@register_features_generator('morgan_diff_fp')
9089
def morgan_difference_features_generator(rxn: Reaction) -> np.ndarray:
9190
"""

catpred/models/model.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,11 @@ def create_protein_model(self, args: TrainArgs) -> None:
127127
x = list(self.pretrained_egnn_feats_dict.values())
128128
self.pretrained_egnn_feats_avg = torch.stack(x).mean(dim=0)
129129

130-
# For rotary positional embeddings
131-
self.rotary_embedder = RotaryEmbedding(dim=args.seq_embed_dim//4)
130+
# Rotary embeddings require an even feature dimension.
131+
rotary_dim = max(2, args.seq_embed_dim // 4)
132+
if rotary_dim % 2 != 0:
133+
rotary_dim -= 1
134+
self.rotary_embedder = RotaryEmbedding(dim=rotary_dim)
132135

133136
# For self-attention
134137
self.multihead_attn = nn.MultiheadAttention(args.seq_embed_dim,

0 commit comments

Comments
 (0)