ablanx 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ablang_jax/__init__.py +32 -0
- ablang_jax/model.py +109 -0
- ablang_jax/precompute.py +80 -0
- ablanx-0.1.0.dist-info/METADATA +159 -0
- ablanx-0.1.0.dist-info/RECORD +8 -0
- ablanx-0.1.0.dist-info/WHEEL +5 -0
- ablanx-0.1.0.dist-info/licenses/LICENSE +33 -0
- ablanx-0.1.0.dist-info/top_level.txt +1 -0
ablang_jax/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""ablang_jax - a JAX/Flax port of AbLang2 (OPIG).
|
|
2
|
+
|
|
3
|
+
This package re-implements AbLang2's AbRep encoder in Flax and provides the state_dict key
|
|
4
|
+
mapping needed to load the original BSD-3-Clause PyTorch weights unchanged. The AbLang2 model
|
|
5
|
+
and weights are the work of the AbLang2 authors (OPIG); this is a port. See ATTRIBUTION.md and
|
|
6
|
+
https://github.com/oxpig/AbLang2.
|
|
7
|
+
"""
|
|
8
|
+
from .model import (
|
|
9
|
+
HEAD_DIM,
|
|
10
|
+
HID,
|
|
11
|
+
LN_EPS,
|
|
12
|
+
N_BLOCK,
|
|
13
|
+
N_HEAD,
|
|
14
|
+
VOCAB,
|
|
15
|
+
Ablanx,
|
|
16
|
+
Block,
|
|
17
|
+
load_ablanx_params,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Ablanx",
|
|
22
|
+
"Block",
|
|
23
|
+
"load_ablanx_params",
|
|
24
|
+
"VOCAB",
|
|
25
|
+
"HID",
|
|
26
|
+
"N_HEAD",
|
|
27
|
+
"N_BLOCK",
|
|
28
|
+
"HEAD_DIM",
|
|
29
|
+
"LN_EPS",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
__version__ = "0.1.0"
|
ablang_jax/model.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Flax reimplementation of AbLang2's AbRep encoder.
|
|
2
|
+
|
|
3
|
+
A 12-block pre-norm transformer (hidden 480, 20 heads, SwiGLU feed-forward, rotary positions,
|
|
4
|
+
vocab 26) that reproduces AbLang2's AbRep so the original BSD-3-Clause PyTorch weights load
|
|
5
|
+
unchanged. A single forward returns per-residue hidden states and per-block attention maps.
|
|
6
|
+
|
|
7
|
+
PyTorch Linear weights [out, in] map to Flax Dense kernels [in, out] by transpose; see
|
|
8
|
+
load_ablanx_params for the full state_dict key mapping. Outputs match reference PyTorch AbLang2 to under
|
|
9
|
+
4e-6 across a 30-Fv panel (test_agreement.py).
|
|
10
|
+
|
|
11
|
+
AbLang2 is the work of the Oxford Protein Informatics Group; this is a port. See ATTRIBUTION.md and
|
|
12
|
+
https://github.com/oxpig/AbLang2.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import flax.linen as nn
|
|
17
|
+
import jax
|
|
18
|
+
import jax.numpy as jnp
|
|
19
|
+
|
|
20
|
+
VOCAB = 26
|
|
21
|
+
HID = 480
|
|
22
|
+
N_HEAD = 20
|
|
23
|
+
N_BLOCK = 12
|
|
24
|
+
HEAD_DIM = HID // N_HEAD # 24
|
|
25
|
+
LN_EPS = 1e-12
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _rope_freqs(head_dim, base=10000.0):
|
|
29
|
+
# inverse frequencies over half the head dim (matches AbLang2's rotary_emb.freqs)
|
|
30
|
+
half = head_dim // 2
|
|
31
|
+
return 1.0 / (base ** (jnp.arange(0, half, dtype=jnp.float32) * 2.0 / head_dim))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _apply_rope(x, freqs):
|
|
35
|
+
"""Rotary embedding on x [B, L, H, D], interleaved convention (as AbLang2 uses)."""
|
|
36
|
+
L = x.shape[1]
|
|
37
|
+
pos = jnp.arange(L, dtype=jnp.float32)
|
|
38
|
+
ang = pos[:, None] * freqs[None, :]
|
|
39
|
+
ang = jnp.repeat(ang, 2, axis=-1)
|
|
40
|
+
cos = jnp.cos(ang)[None, :, None, :]
|
|
41
|
+
sin = jnp.sin(ang)[None, :, None, :]
|
|
42
|
+
x_even = x[..., 0::2]; x_odd = x[..., 1::2]
|
|
43
|
+
rot = jnp.stack([-x_odd, x_even], axis=-1).reshape(x.shape)
|
|
44
|
+
return x * cos + rot * sin
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Block(nn.Module):
|
|
48
|
+
@nn.compact
|
|
49
|
+
def __call__(self, x, mask, freqs, return_attn=False):
|
|
50
|
+
B, L, _ = x.shape
|
|
51
|
+
h = nn.LayerNorm(epsilon=LN_EPS, name="pre_attn_layer_norm")(x)
|
|
52
|
+
q = nn.Dense(HID, name="q_proj")(h).reshape(B, L, N_HEAD, HEAD_DIM)
|
|
53
|
+
k = nn.Dense(HID, name="k_proj")(h).reshape(B, L, N_HEAD, HEAD_DIM)
|
|
54
|
+
v = nn.Dense(HID, name="v_proj")(h).reshape(B, L, N_HEAD, HEAD_DIM)
|
|
55
|
+
q = _apply_rope(q, freqs); k = _apply_rope(k, freqs)
|
|
56
|
+
# AbLang2's net attention scale is 1/head_dim (q scaled by head_dim^-0.5, then divided by sqrt(head_dim))
|
|
57
|
+
logits = jnp.einsum("blhd,bmhd->bhlm", q, k) / HEAD_DIM
|
|
58
|
+
logits = logits + (1.0 - mask[:, None, None, :]) * -1e9
|
|
59
|
+
attn = jax.nn.softmax(logits, axis=-1)
|
|
60
|
+
o = jnp.einsum("bhlm,bmhd->blhd", attn, v).reshape(B, L, HID)
|
|
61
|
+
o = nn.Dense(HID, name="out_proj")(o)
|
|
62
|
+
x = x + o
|
|
63
|
+
# SwiGLU: split the feed-forward projection into value and gate, gate with silu
|
|
64
|
+
h2 = nn.LayerNorm(epsilon=LN_EPS, name="final_layer_norm")(x)
|
|
65
|
+
up = nn.Dense(3840, name="ffn_in")(h2)
|
|
66
|
+
val, gate = jnp.split(up, 2, axis=-1)
|
|
67
|
+
x = x + nn.Dense(HID, name="ffn_out")(jax.nn.silu(gate) * val)
|
|
68
|
+
return (x, attn) if return_attn else (x, None)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Ablanx(nn.Module):
|
|
72
|
+
"""AbLang2 AbRep encoder. Returns hidden [B, L, 480] and, optionally, attention [N_BLOCK, B, H, L, L]."""
|
|
73
|
+
@nn.compact
|
|
74
|
+
def __call__(self, tokens, mask, return_attn=True):
|
|
75
|
+
freqs = _rope_freqs(HEAD_DIM)
|
|
76
|
+
x = nn.Embed(VOCAB, HID, name="aa_embed_layer")(tokens.astype("int32"))
|
|
77
|
+
attns = []
|
|
78
|
+
for i in range(N_BLOCK):
|
|
79
|
+
x, a = Block(name=f"encoder_blocks_{i}")(x, mask, freqs, return_attn=return_attn)
|
|
80
|
+
if return_attn:
|
|
81
|
+
attns.append(a)
|
|
82
|
+
# final layer norm after all blocks; this is what rescoding returns
|
|
83
|
+
x = nn.LayerNorm(epsilon=LN_EPS, name="layer_norm_after_encoder_blocks")(x)
|
|
84
|
+
return x, (jnp.stack(attns) if return_attn else None)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def load_ablanx_params(w):
|
|
88
|
+
"""Map AbLang2 AbRep PyTorch state_dict (dict of numpy arrays, keys 'AbRep....') -> flax param tree.
|
|
89
|
+
PyTorch Linear weight [out,in] -> flax Dense kernel [in,out] (transpose). LayerNorm weight->scale."""
|
|
90
|
+
import numpy as np
|
|
91
|
+
def T(k): return np.asarray(w[k]).T
|
|
92
|
+
def V(k): return np.asarray(w[k])
|
|
93
|
+
p = {"aa_embed_layer": {"embedding": V("AbRep.aa_embed_layer.weight")},
|
|
94
|
+
"layer_norm_after_encoder_blocks": {"scale": V("AbRep.layer_norm_after_encoder_blocks.weight"),
|
|
95
|
+
"bias": V("AbRep.layer_norm_after_encoder_blocks.bias")}}
|
|
96
|
+
for i in range(N_BLOCK):
|
|
97
|
+
b = f"AbRep.encoder_blocks.{i}"
|
|
98
|
+
p[f"encoder_blocks_{i}"] = {
|
|
99
|
+
"pre_attn_layer_norm": {"scale": V(f"{b}.pre_attn_layer_norm.weight"),
|
|
100
|
+
"bias": V(f"{b}.pre_attn_layer_norm.bias")},
|
|
101
|
+
"q_proj": {"kernel": T(f"{b}.multihead_attention.q_proj.weight"), "bias": V(f"{b}.multihead_attention.q_proj.bias")},
|
|
102
|
+
"k_proj": {"kernel": T(f"{b}.multihead_attention.k_proj.weight"), "bias": V(f"{b}.multihead_attention.k_proj.bias")},
|
|
103
|
+
"v_proj": {"kernel": T(f"{b}.multihead_attention.v_proj.weight"), "bias": V(f"{b}.multihead_attention.v_proj.bias")},
|
|
104
|
+
"out_proj": {"kernel": T(f"{b}.multihead_attention.out_proj.weight"), "bias": V(f"{b}.multihead_attention.out_proj.bias")},
|
|
105
|
+
"ffn_in": {"kernel": T(f"{b}.intermediate_layer.0.weight"), "bias": V(f"{b}.intermediate_layer.0.bias")},
|
|
106
|
+
"ffn_out": {"kernel": T(f"{b}.intermediate_layer.2.weight"), "bias": V(f"{b}.intermediate_layer.2.bias")},
|
|
107
|
+
"final_layer_norm": {"scale": V(f"{b}.final_layer_norm.weight"), "bias": V(f"{b}.final_layer_norm.bias")},
|
|
108
|
+
}
|
|
109
|
+
return p
|
ablang_jax/precompute.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""ablang_jax.precompute - precompute AbLang2 per-residue embeddings for a set of antibody records.
|
|
3
|
+
|
|
4
|
+
Runs the reference PyTorch AbLang2 (`pip install ablang2`) over antibody Fv sequences stored as
|
|
5
|
+
sharded .npz files and writes a per-record `seq_emb` field ([L, 480], VH then VL, residue positions
|
|
6
|
+
only). These embeddings can be used as a fixed antibody sequence prior.
|
|
7
|
+
|
|
8
|
+
This uses the reference PyTorch AbLang2, not the JAX port: it materialises per-residue embeddings
|
|
9
|
+
with the original model for use as a fixed antibody sequence prior.
|
|
10
|
+
|
|
11
|
+
Expected input: one or more npz shards named `{split}_*.npz` (split in {train, test}) under the data
|
|
12
|
+
directory, each an object-array store with per-record fields:
|
|
13
|
+
pdb_id [N] record id
|
|
14
|
+
aa [N][Li] integer amino-acid indices in the order "ARNDCQEGHILKMFPSTWYV"
|
|
15
|
+
chain_id [N][Li] 0 = heavy (VH), 1 = light (VL)
|
|
16
|
+
res_present [N][Li] bool mask of resolved residues
|
|
17
|
+
Each shard is rewritten in place with an added `seq_emb` object field.
|
|
18
|
+
|
|
19
|
+
python -m ablang_jax.precompute --data /path/to/records
|
|
20
|
+
# or set ABLANG_JAX_DATA (default: ./out/records)
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import glob
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
import ablang2
|
|
29
|
+
import numpy as np
|
|
30
|
+
|
|
31
|
+
_AA_ORDER = "ARNDCQEGHILKMFPSTWYV" # AlphaFold-style restype index order
|
|
32
|
+
|
|
33
|
+
_DEFAULT_OUT = os.environ.get("ABLANG_JAX_OUT", "./out")
|
|
34
|
+
_DEFAULT_DATA = os.environ.get("ABLANG_JAX_DATA", os.path.join(_DEFAULT_OUT, "records"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def embed_fv(model, vh_seq, vl_seq):
|
|
38
|
+
"""[L,480] per-residue AbLang2 embedding, residue positions only (ids 1..20), VH then VL."""
|
|
39
|
+
pair = [[vh_seq, vl_seq]] if vl_seq else [[vh_seq, ""]]
|
|
40
|
+
emb = np.asarray(model(pair, mode="rescoding")[0], np.float32) # [T_tok, 480]
|
|
41
|
+
ids = np.asarray(model.tokenizer(pair, pad=True))[0] # [T_tok]
|
|
42
|
+
keep = (ids >= 1) & (ids <= 20) # 20 standard AAs
|
|
43
|
+
return emb[: len(ids)][keep[: emb.shape[0]]] # [n_residues, 480]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main():
|
|
47
|
+
ap = argparse.ArgumentParser()
|
|
48
|
+
ap.add_argument("--data", default=_DEFAULT_DATA,
|
|
49
|
+
help="directory of {train,test}_*.npz shards (env: ABLANG_JAX_DATA)")
|
|
50
|
+
ap.add_argument("--limit-shards", type=int, default=0)
|
|
51
|
+
args = ap.parse_args()
|
|
52
|
+
model = ablang2.pretrained()
|
|
53
|
+
|
|
54
|
+
for split in ("train", "test"):
|
|
55
|
+
shards = sorted(glob.glob(os.path.join(args.data, f"{split}_*.npz")))
|
|
56
|
+
if args.limit_shards:
|
|
57
|
+
shards = shards[: args.limit_shards]
|
|
58
|
+
for si, shard in enumerate(shards):
|
|
59
|
+
z = np.load(shard, allow_pickle=True); arrs = {k: z[k] for k in z.files}
|
|
60
|
+
n = len(arrs["pdb_id"])
|
|
61
|
+
embs = []
|
|
62
|
+
for i in range(n):
|
|
63
|
+
aa = np.asarray(arrs["aa"][i]); cid = np.asarray(arrs["chain_id"][i])
|
|
64
|
+
pres = np.asarray(arrs["res_present"][i], bool)
|
|
65
|
+
vh = "".join(_AA_ORDER[a] for a in aa[(cid == 0) & pres])
|
|
66
|
+
vl = "".join(_AA_ORDER[a] for a in aa[(cid == 1) & pres])
|
|
67
|
+
e = embed_fv(model, vh, vl) # [len(vh)+len(vl), 480]
|
|
68
|
+
exp = len(vh) + len(vl)
|
|
69
|
+
if e.shape[0] != exp: # alignment guard
|
|
70
|
+
e = e[:exp] if e.shape[0] > exp else np.pad(e, [(0, exp - e.shape[0]), (0, 0)])
|
|
71
|
+
embs.append(e.astype(np.float32))
|
|
72
|
+
arrs["seq_emb"] = np.array(embs, dtype=object)
|
|
73
|
+
np.savez_compressed(shard, **arrs)
|
|
74
|
+
if si % 5 == 0:
|
|
75
|
+
print(f"{split} shard {si+1}/{len(shards)} emb[0]={embs[0].shape}", flush=True)
|
|
76
|
+
print(f"{split}: DONE ({len(shards)} shards)", flush=True)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
main()
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ablanx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: JAX/Flax port of AbLang2's AbRep encoder: antibody language-model embeddings and per-block attention.
|
|
5
|
+
Author: Fabricagen
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/fabricagen/ablanx
|
|
8
|
+
Project-URL: Repository, https://github.com/fabricagen/ablanx
|
|
9
|
+
Keywords: jax,flax,antibody,protein-language-model,ablang,ablang2
|
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: jax>=0.4.20
|
|
18
|
+
Requires-Dist: jaxlib>=0.4.20
|
|
19
|
+
Requires-Dist: flax>=0.7.5
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Provides-Extra: reference
|
|
22
|
+
Requires-Dist: ablang2; extra == "reference"
|
|
23
|
+
Requires-Dist: torch; extra == "reference"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# ablanx
|
|
27
|
+
|
|
28
|
+
ablanx is a faithful JAX/Flax port of the AbLang2 AbRep encoder. It loads the original AbLang2
|
|
29
|
+
weights unchanged and returns per-residue embeddings and per-block attention in a single JAX forward
|
|
30
|
+
pass, so an antibody sequence prior can sit inside a larger JAX/Flax model.
|
|
31
|
+
|
|
32
|
+
AbLang2 is the work of the Oxford Protein Informatics Group (OPIG); this repository is a port, not a
|
|
33
|
+
new model. Provenance and citation are in [ATTRIBUTION.md](ATTRIBUTION.md) and
|
|
34
|
+
[CITATION.cff](CITATION.cff).
|
|
35
|
+
|
|
36
|
+
## Why use it
|
|
37
|
+
|
|
38
|
+
- Drop-in AbLang2 embeddings inside JAX/Flax workflows.
|
|
39
|
+
- No PyTorch dependency at inference (weights load from a plain npz).
|
|
40
|
+
- Pure JAX forward: `jit` / `vmap` / `grad`-compatible and composable in a larger model.
|
|
41
|
+
- Per-block attention returned directly from the same call.
|
|
42
|
+
- Numerically verified against the reference implementation (max abs 3.9e-6 across a 30-Fv panel).
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
Install:
|
|
47
|
+
|
|
48
|
+
pip install git+https://github.com/fabricagen/ablanx
|
|
49
|
+
# from a clone: pip install .
|
|
50
|
+
# imports as `ablang_jax`; PyPI (pip install ablanx) is planned.
|
|
51
|
+
|
|
52
|
+
Embeddings:
|
|
53
|
+
|
|
54
|
+
import numpy as np, jax.numpy as jnp
|
|
55
|
+
from ablang_jax import Ablanx, load_ablanx_params
|
|
56
|
+
|
|
57
|
+
model = Ablanx()
|
|
58
|
+
params = {"params": load_ablanx_params(dict(np.load("ablang2_weights.npz")))} # AbRep weights
|
|
59
|
+
tokens = jnp.array([[...]], dtype=jnp.int32) # AbLang2 token ids, shape [B, L]
|
|
60
|
+
mask = jnp.ones_like(tokens, dtype=jnp.float32) # 1 = keep, 0 = pad
|
|
61
|
+
hidden, attentions = model.apply(params, tokens, mask, return_attn=True)
|
|
62
|
+
# hidden: [B, L, 480] per-residue embeddings
|
|
63
|
+
# attentions: [12, B, 20, L, L] per-block attention maps
|
|
64
|
+
|
|
65
|
+
See [Weights](#weights) for the npz.
|
|
66
|
+
|
|
67
|
+
## What it is
|
|
68
|
+
|
|
69
|
+
AbLang2 is an antibody-specific protein language model. Its encoder (AbRep) is a 12-block pre-norm
|
|
70
|
+
transformer: hidden 480, 20 heads, SwiGLU feed-forward, rotary position embeddings, vocab 26,
|
|
71
|
+
trained on paired antibody sequences. `ablanx` re-expresses AbRep in Flax so the original
|
|
72
|
+
BSD-3-Clause weights load without change, returning per-residue hidden states and per-block attention
|
|
73
|
+
maps in one forward pass.
|
|
74
|
+
|
|
75
|
+
## Differentiability
|
|
76
|
+
|
|
77
|
+
The forward is a pure JAX function, so it composes inside a larger model and runs under `jit`,
|
|
78
|
+
`vmap`, and `grad`. Useful gradients flow with respect to:
|
|
79
|
+
|
|
80
|
+
- the model parameters (`params`), and
|
|
81
|
+
- any continuous input substituted for the token lookup, for example a soft or one-hot embedding
|
|
82
|
+
passed in place of integer ids.
|
|
83
|
+
|
|
84
|
+
The public interface takes integer `tokens`, which are discrete and do not themselves admit
|
|
85
|
+
gradients; to optimize a sequence through the encoder, feed a continuous representation rather than
|
|
86
|
+
token ids. In the seam folder, ablanx runs frozen as a fixed sequence prior.
|
|
87
|
+
|
|
88
|
+
## What works today
|
|
89
|
+
|
|
90
|
+
- The Flax AbRep encoder, `ablang_jax.model.Ablanx`.
|
|
91
|
+
- The PyTorch-to-Flax weight key mapping, `ablang_jax.model.load_ablanx_params`, which maps an
|
|
92
|
+
AbLang2 AbRep `state_dict` (as numpy arrays) into the Flax parameter tree.
|
|
93
|
+
- A weight exporter, `export_weights.py`, that writes the AbRep weight npz from the reference model.
|
|
94
|
+
- A precompute script, `ablang_jax.precompute`, that runs the reference PyTorch AbLang2 over a set of
|
|
95
|
+
antibody records and stores per-residue embeddings.
|
|
96
|
+
|
|
97
|
+
## Validation
|
|
98
|
+
|
|
99
|
+
The JAX forward reproduces reference PyTorch AbLang2 to float32 precision. Across a 30-Fv panel (25
|
|
100
|
+
paired VH+VL and 5 VH-only, drawn from public PDB structures), per-residue embeddings match the
|
|
101
|
+
reference with maximum absolute difference 3.9e-6 (median 2.4e-6 per Fv) and cosine above 0.999999,
|
|
102
|
+
using the same weights. The weights are byte-identical to the reference AbRep state_dict (207 tensors,
|
|
103
|
+
exact match). Reproduce with `test_agreement.py`.
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
## Weights
|
|
108
|
+
|
|
109
|
+
The weights are the original AbLang2 AbRep weights, unmodified (207 tensors).
|
|
110
|
+
|
|
111
|
+
- Export from the reference model (works today, no release needed):
|
|
112
|
+
`pip install ablang2 torch && python export_weights.py` writes `ablang2_weights.npz`, verified
|
|
113
|
+
byte-identical to the reference.
|
|
114
|
+
- A torch-free `ablang2_weights.npz` is also attached to the GitHub release once one is tagged.
|
|
115
|
+
|
|
116
|
+
Point `ABLANG_WEIGHTS` at the npz, or pass `dict(np.load(...))` to `load_ablanx_params`.
|
|
117
|
+
|
|
118
|
+
## Not included
|
|
119
|
+
|
|
120
|
+
- Only the AbRep encoder (embeddings and attention) is ported. The amino-acid likelihood head used
|
|
121
|
+
for the pseudo-log-likelihood (sequence naturalness) is not part of this encoder port; use the
|
|
122
|
+
reference AbLang2 for likelihoods.
|
|
123
|
+
|
|
124
|
+
## Precompute
|
|
125
|
+
|
|
126
|
+
Precompute embeddings for a set of records with the reference PyTorch `ablang2`:
|
|
127
|
+
|
|
128
|
+
export ABLANG_JAX_DATA=/path/to/records
|
|
129
|
+
python -m ablang_jax.precompute --data $ABLANG_JAX_DATA
|
|
130
|
+
|
|
131
|
+
Input records are npz shards named `{train,test}_*.npz`; see `ablang_jax/precompute.py` for the
|
|
132
|
+
expected per-record fields. Outputs default to `./out/` when paths are not set.
|
|
133
|
+
|
|
134
|
+
## Tests
|
|
135
|
+
|
|
136
|
+
python test_shapes.py # forward, weight-key mapping, masking
|
|
137
|
+
ABLANG_WEIGHTS=ablang2_weights.npz python test_agreement.py # agreement vs reference (needs weights)
|
|
138
|
+
|
|
139
|
+
`test_shapes.py` needs no weights. `test_agreement.py` loads the committed golden panel
|
|
140
|
+
(`golden_ablang2_panel.npz`, reference embeddings for 30 Fvs) and checks the JAX forward matches every
|
|
141
|
+
one; it skips under pytest if `ABLANG_WEIGHTS` is unset.
|
|
142
|
+
|
|
143
|
+
## Attribution
|
|
144
|
+
|
|
145
|
+
- Original model, training, and weights: AbLang2, Oxford Protein Informatics Group.
|
|
146
|
+
https://github.com/oxpig/AbLang2 (BSD-3-Clause, Copyright (c) 2021, Tobias Hegelund Olsen).
|
|
147
|
+
- Paper: Olsen, Moal, Deane, "Addressing the antibody germline bias and its effect on language models
|
|
148
|
+
for improved antibody design", bioRxiv 2024, doi:10.1101/2024.02.02.578678.
|
|
149
|
+
- This port: Fabricagen.
|
|
150
|
+
|
|
151
|
+
ablanx is the language-model component of the **seam** bundle, a JAX antibody Fv structure predictor
|
|
152
|
+
that couples jaxfvld with ablanx. For antibody developability screening and repair, see **sift**:
|
|
153
|
+
https://sift.fabricagen.ai (coming soon, ca 08/26)
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
BSD-3-Clause. This port preserves the original AbLang2 copyright (Copyright (c) 2021, Tobias Hegelund
|
|
158
|
+
Olsen) and adds Copyright (c) 2026, Fabricagen for the port. The upstream AbLang2 license was
|
|
159
|
+
confirmed BSD-3-Clause. See `ATTRIBUTION.md`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ablang_jax/__init__.py,sha256=rqYVJ6ndHNvxWvjfoQRMqfBP-cvw2hFCXJF0vkob-mk,685
|
|
2
|
+
ablang_jax/model.py,sha256=vAxIkeUhpWIgciYke5-pk8geR3mqL6SjT1xNKCUlPPU,5471
|
|
3
|
+
ablang_jax/precompute.py,sha256=M_njZWBc64QhH_qYkXW_gKbAN7PMAq3xRz4lCecOB0s,3795
|
|
4
|
+
ablanx-0.1.0.dist-info/licenses/LICENSE,sha256=p3uEdiNWxk8j27b7COMXbRE8FrqcNc30BELSyZKF38Q,1763
|
|
5
|
+
ablanx-0.1.0.dist-info/METADATA,sha256=7CuM54ElBcSKq9hfeOa6rH15kacXSxYjMUriFkULnxo,7304
|
|
6
|
+
ablanx-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
7
|
+
ablanx-0.1.0.dist-info/top_level.txt,sha256=WASCZ06W0VkKEMRHzov2LbvmuJAJP1E9y_HZ1InHP-I,11
|
|
8
|
+
ablanx-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021, Tobias Hegelund Olsen
|
|
4
|
+
Copyright (c) 2026, Fabricagen
|
|
5
|
+
|
|
6
|
+
This software is a JAX/Flax port of AbLang2 (Oxford Protein Informatics Group). The
|
|
7
|
+
original AbLang2 model, weights, and copyright are the work of the AbLang2 authors and
|
|
8
|
+
are retained above. See ATTRIBUTION.md for details.
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted provided that the following conditions are met:
|
|
12
|
+
|
|
13
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
14
|
+
list of conditions and the following disclaimer.
|
|
15
|
+
|
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
|
18
|
+
and/or other materials provided with the distribution.
|
|
19
|
+
|
|
20
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
21
|
+
contributors may be used to endorse or promote products derived from
|
|
22
|
+
this software without specific prior written permission.
|
|
23
|
+
|
|
24
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
25
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
26
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
27
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
28
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
29
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
30
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
31
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
32
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
33
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ablang_jax
|