Instructions to use Synthyra/Profluent-E1-600M with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Synthyra/Profluent-E1-600M with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("fill-mask", model="Synthyra/Profluent-E1-600M", trust_remote_code=True)# Load model directly from transformers import AutoModelForMaskedLM model = AutoModelForMaskedLM.from_pretrained("Synthyra/Profluent-E1-600M", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Update FastPLMs runtime and model cards
Browse filesAdd-only FastPLMs files-only publication. Checkpoint weights and complete-artifact attestations are unchanged.
- README.md +138 -50
- config.json +5 -5
- fastplms/__init__.py +1 -0
- fastplms/attention/__init__.py +1 -0
- fastplms/attention/_core.py +68 -47
- fastplms/attention/_kernel_lock.py +17 -33
- fastplms/attention/interfaces.py +7 -7
- fastplms/embeddings/__init__.py +1 -0
- fastplms/embeddings/pooling.py +48 -40
- fastplms/embeddings/runner.py +60 -36
- fastplms/embeddings/storage.py +26 -19
- fastplms/embeddings/types.py +4 -5
- fastplms/models/__init__.py +1 -0
- fastplms/models/e1/attention.py +116 -72
- fastplms/models/e1/cache.py +30 -19
- fastplms/models/e1/modeling_e1.py +51 -42
- fastplms/models/e1/preparation.py +33 -25
- fastplms/models/e1/retrieval.py +60 -39
- fastplms/models/ttt.py +97 -81
- fastplms/registry.py +45 -44
- fastplms/runtime.py +1 -0
- fastplms_bundle.py +0 -0
- modeling_fastplms.py +29 -73
- requirements.txt +10 -0
- runtime-attestation.json +29 -28
README.md
CHANGED
|
@@ -19,15 +19,34 @@ Supported Transformers entry points are `AutoConfig`, `AutoModel`,
|
|
| 19 |
`AutoModelForMaskedLM`, `AutoModelForSequenceClassification`,
|
| 20 |
`AutoModelForTokenClassification`.
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
## Install and platform requirements
|
| 23 |
|
| 24 |
-
Install the
|
| 25 |
|
| 26 |
```bash
|
| 27 |
-
python -m pip install \
|
| 28 |
-
"
|
| 29 |
```
|
| 30 |
|
|
|
|
|
|
|
|
|
|
| 31 |
Python 3.11-3.14, PyTorch 2.13, and Transformers 5.13 are required. The declared CPU gate covers tiny offline contracts; published checkpoint throughput and parity require the documented device tier. The Hub quick start below requires network
|
| 32 |
access on first download. For an air-gapped run, first build the manifest-pinned
|
| 33 |
local artifact and use the offline form shown in the example.
|
|
@@ -41,62 +60,137 @@ model_id = "Synthyra/Profluent-E1-600M"
|
|
| 41 |
model = AutoModel.from_pretrained(
|
| 42 |
model_id,
|
| 43 |
trust_remote_code=True,
|
|
|
|
| 44 |
).eval()
|
| 45 |
```
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
calls are unchanged.
|
| 59 |
-
For BF16 execution, this family uses parameters loaded directly in BF16.
|
| 60 |
|
| 61 |
## Dataset embeddings
|
| 62 |
|
| 63 |
-
The shared embedding
|
| 64 |
-
|
| 65 |
-
FASTA path. Results preserve order and duplicate identifiers:
|
| 66 |
|
| 67 |
```python
|
| 68 |
-
|
| 69 |
["MSTNPKPQRKTKRNT", "MKTIIALSYIFCLVFA"],
|
| 70 |
batch_size=2,
|
| 71 |
pooling=("mean", "std"),
|
| 72 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
| 76 |
```
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
batch-level database commits and exact resume. Pooling excludes boundary,
|
| 82 |
-
padding, and other non-biological positions.
|
| 83 |
|
| 84 |
-
|
|
|
|
|
|
|
| 85 |
|
| 86 |
```python
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
)
|
| 95 |
```
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
## Tokenizer-free E1 input
|
| 102 |
|
|
@@ -126,7 +220,7 @@ the attribution required by the upstream agreement.
|
|
| 126 |
- Precision policies: `default`
|
| 127 |
- BF16 execution: `static_parameters`
|
| 128 |
- Generation contract: `not_applicable`
|
| 129 |
-
-
|
| 130 |
- Weight publication allowed: `true`
|
| 131 |
- Weight license status: `resolved`
|
| 132 |
- Redistributable: `true`
|
|
@@ -137,28 +231,22 @@ the attribution required by the upstream agreement.
|
|
| 137 |
- FastPLMs weights: `Synthyra/Profluent-E1-600M`
|
| 138 |
- Runtime revision: recorded separately in the built artifact and published commit
|
| 139 |
- Source-tree and runtime-bundle SHA-256: recorded in `provenance.json`
|
| 140 |
-
- Generator/schema version and complete/runtime-only attestations: recorded in `provenance.json`
|
| 141 |
- Official checkpoint: `Profluent-Bio/E1-600m`
|
| 142 |
- Artifact source: `fast`
|
| 143 |
- State transform: `e1_to_fastplms_v1`
|
| 144 |
-
- BF16 execution: `static_parameters`
|
| 145 |
- Pinned upstreams: `e1`
|
| 146 |
-
- Reference container: `reference-e1`
|
| 147 |
- Release tiers: `check`, `compliance`, `feature`, `artifact`, `benchmark`
|
| 148 |
- Unresolved required file identities: `0`
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
release blocker.
|
| 153 |
|
| 154 |
## Validation boundary
|
| 155 |
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
passed, that one backend is faster, or that an output has biological or
|
| 161 |
-
therapeutic validity.
|
| 162 |
|
| 163 |
## License
|
| 164 |
|
|
|
|
| 19 |
`AutoModelForMaskedLM`, `AutoModelForSequenceClassification`,
|
| 20 |
`AutoModelForTokenClassification`.
|
| 21 |
|
| 22 |
+
## Capabilities
|
| 23 |
+
|
| 24 |
+
| Feature | Status |
|
| 25 |
+
| --- | --- |
|
| 26 |
+
| Sequence classification | Supported: base weights with an untrained task head |
|
| 27 |
+
| Token classification | Supported: base weights with an untrained task head |
|
| 28 |
+
| PEFT fine-tuning | Supported pattern: preserve the separately trained `classifier` |
|
| 29 |
+
| Embeddings | Special: tokenizer-free raw-sequence preparation |
|
| 30 |
+
| Test-time training | Supported: low-rank masked-residue adaptation |
|
| 31 |
+
| Attention variants | Supported: `sdpa`, `flex_attention` |
|
| 32 |
+
| Compliance | Declared: exact release evidence is required |
|
| 33 |
+
|
| 34 |
+
A supported interface is not a pretrained downstream predictor. Classification
|
| 35 |
+
heads start untrained, and declared compliance metadata is not a claim that an
|
| 36 |
+
arbitrary local build passed its release gate.
|
| 37 |
+
|
| 38 |
## Install and platform requirements
|
| 39 |
|
| 40 |
+
Install the direct dependencies published with this model:
|
| 41 |
|
| 42 |
```bash
|
| 43 |
+
python -m pip install -r \
|
| 44 |
+
"https://huggingface.co/Synthyra/Profluent-E1-600M/resolve/main/requirements.txt"
|
| 45 |
```
|
| 46 |
|
| 47 |
+
The FastPLMs implementation itself is embedded in the model repository and loaded
|
| 48 |
+
by Transformers through `trust_remote_code=True`.
|
| 49 |
+
|
| 50 |
Python 3.11-3.14, PyTorch 2.13, and Transformers 5.13 are required. The declared CPU gate covers tiny offline contracts; published checkpoint throughput and parity require the documented device tier. The Hub quick start below requires network
|
| 51 |
access on first download. For an air-gapped run, first build the manifest-pinned
|
| 52 |
local artifact and use the offline form shown in the example.
|
|
|
|
| 60 |
model = AutoModel.from_pretrained(
|
| 61 |
model_id,
|
| 62 |
trust_remote_code=True,
|
| 63 |
+
attn_implementation="sdpa",
|
| 64 |
).eval()
|
| 65 |
```
|
| 66 |
|
| 67 |
+
For offline validation, replace `model_id` with the manifest-built
|
| 68 |
+
`dist/hub/Profluent-E1-600M` path and pass `local_files_only=True`.
|
| 69 |
+
|
| 70 |
+
## Attention and compliance
|
| 71 |
|
| 72 |
+
The quick start selects `sdpa` explicitly. Declared variants are `sdpa`, `flex_attention`. An unavailable requested
|
| 73 |
+
backend raises instead of silently switching implementations.
|
| 74 |
+
`output_attentions=True` may use the documented, one-call eager fallback solely
|
| 75 |
+
to materialize attention tensors; the configured backend remains unchanged.
|
| 76 |
+
|
| 77 |
+
This family declares the `compliance` tier. Release evidence binds the exact
|
| 78 |
+
checkpoint, backend, dtype, hardware, inputs, and reference revision.
|
|
|
|
|
|
|
| 79 |
|
| 80 |
## Dataset embeddings
|
| 81 |
|
| 82 |
+
The shared embedding mixin preserves input order and biological-position
|
| 83 |
+
masking. It accepts sequences, identified records, mappings, or a FASTA path:
|
|
|
|
| 84 |
|
| 85 |
```python
|
| 86 |
+
pooled = model.embed_dataset(
|
| 87 |
["MSTNPKPQRKTKRNT", "MKTIIALSYIFCLVFA"],
|
| 88 |
batch_size=2,
|
| 89 |
pooling=("mean", "std"),
|
| 90 |
)
|
| 91 |
+
residues = model.embed_dataset(
|
| 92 |
+
["MSTNPKPQRKTKRNT"],
|
| 93 |
+
full_embeddings=True,
|
| 94 |
+
)
|
| 95 |
+
print(pooled[0].tensor.shape) # (2 * d,)
|
| 96 |
+
print(residues[0].tensor.shape) # (l, d)
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
Set `output` and `format="safetensors"` or `"sqlite"` for transactional,
|
| 100 |
+
bounded-memory persistence. Resume verifies input order, model state, tokenizer
|
| 101 |
+
policy, backend, dtype, and pooling configuration before appending.
|
| 102 |
+
|
| 103 |
+
## Downstream classification
|
| 104 |
+
|
| 105 |
+
Both downstream AutoClasses reuse the checkpoint backbone and initialize a new,
|
| 106 |
+
untrained `classifier`. Sequence labels have shape `(b,)`; residue labels have
|
| 107 |
+
shape `(b, l)` and use `-100` outside biological positions:
|
| 108 |
+
|
| 109 |
+
```python
|
| 110 |
+
import torch
|
| 111 |
+
from transformers import (
|
| 112 |
+
AutoModelForSequenceClassification,
|
| 113 |
+
AutoModelForTokenClassification,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
model_id = "Synthyra/Profluent-E1-600M"
|
| 117 |
+
sequence_model = AutoModelForSequenceClassification.from_pretrained(
|
| 118 |
+
model_id, num_labels=2, trust_remote_code=True
|
| 119 |
+
).eval()
|
| 120 |
+
token_model = AutoModelForTokenClassification.from_pretrained(
|
| 121 |
+
model_id, num_labels=3, trust_remote_code=True
|
| 122 |
+
).eval()
|
| 123 |
+
sequences = ["MSTNPKPQRKTKRNT", "MKTIIALSYIFCLVFA"]
|
| 124 |
+
batch = sequence_model.prep_tokens.get_batch_kwargs(
|
| 125 |
+
sequences,
|
| 126 |
+
device=sequence_model.device,
|
| 127 |
+
)
|
| 128 |
+
biological = batch["sequence_ids"].ne(-1)
|
| 129 |
+
|
| 130 |
+
sequence_labels = torch.zeros(len(sequences), dtype=torch.long)
|
| 131 |
+
token_labels = torch.full_like(batch["input_ids"], -100)
|
| 132 |
+
token_labels[biological] = 0
|
| 133 |
|
| 134 |
+
with torch.inference_mode():
|
| 135 |
+
sequence_output = sequence_model(**batch, labels=sequence_labels)
|
| 136 |
+
token_output = token_model(**batch, labels=token_labels)
|
| 137 |
+
print(sequence_output.logits.shape) # (b, 2)
|
| 138 |
+
print(token_output.logits.shape) # (b, l, 3)
|
| 139 |
```
|
| 140 |
|
| 141 |
+
## PEFT fine-tuning
|
| 142 |
+
|
| 143 |
+
Install the direct training dependencies, then attach LoRA to the loaded checkpoint:
|
|
|
|
|
|
|
| 144 |
|
| 145 |
+
```bash
|
| 146 |
+
python -m pip install "datasets>=4.8,<5" "peft>=0.19,<0.20"
|
| 147 |
+
```
|
| 148 |
|
| 149 |
```python
|
| 150 |
+
from peft import LoraConfig, TaskType, get_peft_model
|
| 151 |
+
|
| 152 |
+
peft_model = get_peft_model(
|
| 153 |
+
sequence_model,
|
| 154 |
+
LoraConfig(
|
| 155 |
+
task_type=TaskType.SEQ_CLS,
|
| 156 |
+
r=8,
|
| 157 |
+
lora_alpha=16,
|
| 158 |
+
target_modules="all-linear",
|
| 159 |
+
modules_to_save=["classifier"],
|
| 160 |
+
),
|
| 161 |
)
|
| 162 |
```
|
| 163 |
|
| 164 |
+
This checkpoint advertises a classification head, so the separately trained
|
| 165 |
+
`classifier` is saved with the adapter.
|
| 166 |
+
All FastPLMs checkpoints follow the Transformers `PreTrainedModel` contract and
|
| 167 |
+
can be adapted with PEFT. The ESM2-specific shipped CLI is an example, not a
|
| 168 |
+
support boundary. Record the target modules, base revision, data identity, and
|
| 169 |
+
trainable parameter scope.
|
| 170 |
+
|
| 171 |
+
## Test-time training
|
| 172 |
+
|
| 173 |
+
TTT samples masked views of one protein and updates only injected low-rank
|
| 174 |
+
adapters. Base checkpoint weights remain frozen:
|
| 175 |
+
|
| 176 |
+
```python
|
| 177 |
+
from transformers import AutoModelForMaskedLM
|
| 178 |
+
|
| 179 |
+
ttt_model = AutoModelForMaskedLM.from_pretrained(
|
| 180 |
+
"Synthyra/Profluent-E1-600M",
|
| 181 |
+
trust_remote_code=True,
|
| 182 |
+
)
|
| 183 |
+
metrics = ttt_model.ttt(
|
| 184 |
+
seq="MSTNPKPQRKTKRNT",
|
| 185 |
+
ttt_config={"steps": 3, "batch_size": 1, "seed": 7},
|
| 186 |
+
)
|
| 187 |
+
ttt_model.save_pretrained("adapted", safe_serialization=True)
|
| 188 |
+
ttt_model.ttt_reset()
|
| 189 |
+
print(metrics)
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
Persisted adapters retain their deterministic reset state. TTT adds latency
|
| 193 |
+
and memory, can worsen an output, and does not establish biological function.
|
| 194 |
|
| 195 |
## Tokenizer-free E1 input
|
| 196 |
|
|
|
|
| 220 |
- Precision policies: `default`
|
| 221 |
- BF16 execution: `static_parameters`
|
| 222 |
- Generation contract: `not_applicable`
|
| 223 |
+
- Artifact dependency set: `core`
|
| 224 |
- Weight publication allowed: `true`
|
| 225 |
- Weight license status: `resolved`
|
| 226 |
- Redistributable: `true`
|
|
|
|
| 231 |
- FastPLMs weights: `Synthyra/Profluent-E1-600M`
|
| 232 |
- Runtime revision: recorded separately in the built artifact and published commit
|
| 233 |
- Source-tree and runtime-bundle SHA-256: recorded in `provenance.json`
|
|
|
|
| 234 |
- Official checkpoint: `Profluent-Bio/E1-600m`
|
| 235 |
- Artifact source: `fast`
|
| 236 |
- State transform: `e1_to_fastplms_v1`
|
|
|
|
| 237 |
- Pinned upstreams: `e1`
|
|
|
|
| 238 |
- Release tiers: `check`, `compliance`, `feature`, `artifact`, `benchmark`
|
| 239 |
- Unresolved required file identities: `0`
|
| 240 |
|
| 241 |
+
`provenance.json` records exact file identities, conversion, source revisions,
|
| 242 |
+
legal texts, schema, and attestations. A nonzero unresolved count blocks release.
|
|
|
|
| 243 |
|
| 244 |
## Validation boundary
|
| 245 |
|
| 246 |
+
Declared tiers compare applicable configuration, tokenizer behavior, state,
|
| 247 |
+
and representative inference with the pinned reference. Metadata alone does
|
| 248 |
+
not claim a build passed, a backend is faster, or an output is biologically
|
| 249 |
+
valid.
|
|
|
|
|
|
|
| 250 |
|
| 251 |
## License
|
| 252 |
|
config.json
CHANGED
|
@@ -18,11 +18,11 @@
|
|
| 18 |
"fastplms_checkpoint_repo_id": "Synthyra/Profluent-E1-600M",
|
| 19 |
"fastplms_checkpoint_revision": "6c8bf0ec83b0e0178677c528b101efffd0677742",
|
| 20 |
"fastplms_model_id": "e1_600m",
|
| 21 |
-
"fastplms_release_tool_revision": "
|
| 22 |
-
"fastplms_release_tool_sha256": "
|
| 23 |
-
"fastplms_runtime_bundle_sha256": "
|
| 24 |
-
"fastplms_runtime_revision": "
|
| 25 |
-
"fastplms_source_tree_sha256": "
|
| 26 |
"fastplms_weights_revision": "6c8bf0ec83b0e0178677c528b101efffd0677742",
|
| 27 |
"gated_mlp": true,
|
| 28 |
"global_attention_every_n_layers": 3,
|
|
|
|
| 18 |
"fastplms_checkpoint_repo_id": "Synthyra/Profluent-E1-600M",
|
| 19 |
"fastplms_checkpoint_revision": "6c8bf0ec83b0e0178677c528b101efffd0677742",
|
| 20 |
"fastplms_model_id": "e1_600m",
|
| 21 |
+
"fastplms_release_tool_revision": "e6dd397a9ad368c998d714f6bd64d40b533d1ed1",
|
| 22 |
+
"fastplms_release_tool_sha256": "6d335c05aa49a232086a816deb25d248d1490529e5783acc3355b9bc6f03e0c2",
|
| 23 |
+
"fastplms_runtime_bundle_sha256": "a8dda20d6a4a55e6808c7c2f2c9477c113edaa7d6ec6ef57f4a898cd47b73d48",
|
| 24 |
+
"fastplms_runtime_revision": "e6dd397a9ad368c998d714f6bd64d40b533d1ed1",
|
| 25 |
+
"fastplms_source_tree_sha256": "9e3cbab448ffde4fbbba79f17cf0321645584acd534c5f96c78b1017d9f3806b",
|
| 26 |
"fastplms_weights_revision": "6c8bf0ec83b0e0178677c528b101efffd0677742",
|
| 27 |
"gated_mlp": true,
|
| 28 |
"global_attention_every_n_layers": 3,
|
fastplms/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
| 9 |
from importlib import import_module
|
| 10 |
from typing import Any
|
| 11 |
|
|
|
|
| 12 |
__version__ = "1.0.0"
|
| 13 |
|
| 14 |
_LAZY_EXPORTS = {
|
|
|
|
| 9 |
from importlib import import_module
|
| 10 |
from typing import Any
|
| 11 |
|
| 12 |
+
|
| 13 |
__version__ = "1.0.0"
|
| 14 |
|
| 15 |
_LAZY_EXPORTS = {
|
fastplms/attention/__init__.py
CHANGED
|
@@ -32,6 +32,7 @@ from .interfaces import (
|
|
| 32 |
validate_transformers_attention_interfaces,
|
| 33 |
)
|
| 34 |
|
|
|
|
| 35 |
__all__ = [
|
| 36 |
"FASTPLMS_ATTENTION_FUNCTIONS",
|
| 37 |
"FASTPLMS_ATTENTION_MASKS",
|
|
|
|
| 32 |
validate_transformers_attention_interfaces,
|
| 33 |
)
|
| 34 |
|
| 35 |
+
|
| 36 |
__all__ = [
|
| 37 |
"FASTPLMS_ATTENTION_FUNCTIONS",
|
| 38 |
"FASTPLMS_ATTENTION_MASKS",
|
fastplms/attention/_core.py
CHANGED
|
@@ -8,17 +8,17 @@ FastPLMs never downloads or compiles code.
|
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
import warnings
|
|
|
|
| 11 |
from collections import OrderedDict
|
| 12 |
from collections.abc import Callable
|
| 13 |
from enum import Enum
|
| 14 |
from threading import RLock
|
| 15 |
-
|
| 16 |
-
import torch
|
| 17 |
from einops import rearrange
|
| 18 |
from torch.nn import functional as F
|
| 19 |
|
| 20 |
from ._kernel_lock import load_locked_kernel
|
| 21 |
|
|
|
|
| 22 |
try:
|
| 23 |
from torch.nn.attention.flex_attention import BlockMask, create_block_mask, flex_attention
|
| 24 |
except ImportError:
|
|
@@ -117,12 +117,12 @@ def _get_flex_block_mask(
|
|
| 117 |
raise RuntimeError(
|
| 118 |
"'flex_attention' was requested, but torch.create_block_mask is unavailable."
|
| 119 |
)
|
| 120 |
-
pattern = mask_pattern.detach().to(device=device).contiguous()
|
| 121 |
# One device-to-host transfer is required for an exact cache identity. Use
|
| 122 |
# the contiguous buffer directly instead of materializing one Python int
|
| 123 |
# per byte, which is prohibitively expensive for long batched sequences.
|
| 124 |
-
host_pattern = pattern.to(device="cpu").contiguous()
|
| 125 |
-
pattern_bytes = host_pattern.view(torch.uint8).numpy().tobytes(order="C")
|
| 126 |
cache_key = (
|
| 127 |
str(device),
|
| 128 |
None if dtype is None else str(dtype),
|
|
@@ -203,6 +203,7 @@ def _validate_kernels_flash_dtype(
|
|
| 203 |
) -> torch.dtype:
|
| 204 |
"""Reject dtypes outside the immutable kernel manifest before dispatch."""
|
| 205 |
|
|
|
|
| 206 |
tensor_dtypes = {query_states.dtype, key_states.dtype, value_states.dtype}
|
| 207 |
if len(tensor_dtypes) != 1:
|
| 208 |
observed = ", ".join(sorted(str(dtype) for dtype in tensor_dtypes))
|
|
@@ -243,6 +244,7 @@ def _validate_kernels_flash_device(
|
|
| 243 |
) -> torch.device:
|
| 244 |
"""Require Q, K, and V on one CUDA device before loading a kernel."""
|
| 245 |
|
|
|
|
| 246 |
devices = (query_states.device, key_states.device, value_states.device)
|
| 247 |
if len(set(devices)) != 1:
|
| 248 |
observed = ", ".join(str(device) for device in devices)
|
|
@@ -284,9 +286,10 @@ def _kernels_flash_forward(
|
|
| 284 |
Failing to override when Q is pre-scaled applies the scale twice and breaks
|
| 285 |
parity with eager attention and SDPA.
|
| 286 |
"""
|
|
|
|
| 287 |
flash_kernel, flash_kernel_variant = _ensure_flash_kernels_loaded(implementation)
|
| 288 |
if flash_kernel_variant == "flash_attn2":
|
| 289 |
-
output = flash_kernel.flash_attn_func(
|
| 290 |
q=query_states,
|
| 291 |
k=key_states,
|
| 292 |
v=value_states,
|
|
@@ -294,9 +297,9 @@ def _kernels_flash_forward(
|
|
| 294 |
softmax_scale=softmax_scale,
|
| 295 |
causal=causal,
|
| 296 |
)
|
| 297 |
-
return output[0] if isinstance(output, tuple) else output
|
| 298 |
if flash_kernel_variant == "flash_attn3":
|
| 299 |
-
output = flash_kernel.flash_attn_func(
|
| 300 |
q=query_states,
|
| 301 |
k=key_states,
|
| 302 |
v=value_states,
|
|
@@ -304,8 +307,8 @@ def _kernels_flash_forward(
|
|
| 304 |
causal=causal,
|
| 305 |
)
|
| 306 |
if isinstance(output, tuple):
|
| 307 |
-
return output[0]
|
| 308 |
-
return output
|
| 309 |
raise RuntimeError(f"Unsupported FlashAttention kernel variant: {flash_kernel_variant}")
|
| 310 |
|
| 311 |
|
|
@@ -326,9 +329,11 @@ def _kernels_flash_varlen_forward(
|
|
| 326 |
See `_kernels_flash_forward` docstring for why `softmax_scale=1.0` must be
|
| 327 |
passed when Q has been pre-scaled by the caller.
|
| 328 |
"""
|
|
|
|
|
|
|
| 329 |
flash_kernel, flash_kernel_variant = _ensure_flash_kernels_loaded(implementation)
|
| 330 |
if flash_kernel_variant == "flash_attn2":
|
| 331 |
-
output = flash_kernel.flash_attn_varlen_func(
|
| 332 |
q=query_states,
|
| 333 |
k=key_states,
|
| 334 |
v=value_states,
|
|
@@ -340,9 +345,9 @@ def _kernels_flash_varlen_forward(
|
|
| 340 |
softmax_scale=softmax_scale,
|
| 341 |
causal=causal,
|
| 342 |
)
|
| 343 |
-
return output[0] if isinstance(output, tuple) else output
|
| 344 |
if flash_kernel_variant == "flash_attn3":
|
| 345 |
-
output = flash_kernel.flash_attn_varlen_func(
|
| 346 |
q=query_states,
|
| 347 |
k=key_states,
|
| 348 |
v=value_states,
|
|
@@ -354,8 +359,8 @@ def _kernels_flash_varlen_forward(
|
|
| 354 |
causal=causal,
|
| 355 |
)
|
| 356 |
if isinstance(output, tuple):
|
| 357 |
-
return output[0]
|
| 358 |
-
return output
|
| 359 |
raise RuntimeError(f"Unsupported FlashAttention kernel variant: {flash_kernel_variant}")
|
| 360 |
|
| 361 |
|
|
@@ -364,6 +369,7 @@ def _kernels_flash_varlen_forward(
|
|
| 364 |
class IndexFirstAxis(torch.autograd.Function):
|
| 365 |
@staticmethod
|
| 366 |
def forward(ctx, input, indices) -> torch.Tensor:
|
|
|
|
| 367 |
ctx.save_for_backward(indices)
|
| 368 |
if input.ndim < 2:
|
| 369 |
raise ValueError(
|
|
@@ -377,12 +383,13 @@ class IndexFirstAxis(torch.autograd.Function):
|
|
| 377 |
)
|
| 378 |
ctx.first_axis_dim, other_shape = input.shape[0], input.shape[1:]
|
| 379 |
second_dim = other_shape.numel()
|
| 380 |
-
return torch.gather(
|
| 381 |
rearrange(input, "b ... -> b (...)"), 0, indices.unsqueeze(1).expand(-1, second_dim)
|
| 382 |
).reshape(-1, *other_shape)
|
| 383 |
|
| 384 |
@staticmethod
|
| 385 |
def backward(ctx, grad_output) -> tuple[torch.Tensor, None]:
|
|
|
|
| 386 |
(indices,) = ctx.saved_tensors
|
| 387 |
if grad_output.ndim < 2:
|
| 388 |
raise RuntimeError(
|
|
@@ -390,19 +397,20 @@ class IndexFirstAxis(torch.autograd.Function):
|
|
| 390 |
"two dimensions."
|
| 391 |
)
|
| 392 |
other_shape = grad_output.shape[1:]
|
| 393 |
-
grad_output = rearrange(grad_output, "b ... -> b (...)")
|
| 394 |
-
grad_input = torch.zeros(
|
| 395 |
[ctx.first_axis_dim, grad_output.shape[1]],
|
| 396 |
device=grad_output.device,
|
| 397 |
dtype=grad_output.dtype,
|
| 398 |
)
|
| 399 |
grad_input.scatter_(0, indices.unsqueeze(1).expand(-1, grad_output.shape[1]), grad_output)
|
| 400 |
-
return grad_input.reshape(ctx.first_axis_dim, *other_shape), None
|
| 401 |
|
| 402 |
|
| 403 |
class IndexPutFirstAxis(torch.autograd.Function):
|
| 404 |
@staticmethod
|
| 405 |
def forward(ctx, values, indices, first_axis_dim) -> torch.Tensor:
|
|
|
|
| 406 |
ctx.save_for_backward(indices)
|
| 407 |
if indices.ndim != 1:
|
| 408 |
raise ValueError(
|
|
@@ -414,16 +422,17 @@ class IndexPutFirstAxis(torch.autograd.Function):
|
|
| 414 |
"index_put_first_axis values must have at least two dimensions; "
|
| 415 |
f"received shape {tuple(values.shape)}."
|
| 416 |
)
|
| 417 |
-
output = torch.zeros(
|
| 418 |
first_axis_dim, *values.shape[1:], device=values.device, dtype=values.dtype
|
| 419 |
)
|
| 420 |
output[indices] = values
|
| 421 |
-
return output
|
| 422 |
|
| 423 |
@staticmethod
|
| 424 |
def backward(ctx, grad_output) -> tuple[torch.Tensor, None, None]:
|
|
|
|
| 425 |
(indices,) = ctx.saved_tensors
|
| 426 |
-
return grad_output[indices], None, None
|
| 427 |
|
| 428 |
|
| 429 |
index_first_axis = IndexFirstAxis.apply
|
|
@@ -433,8 +442,9 @@ index_put_first_axis = IndexPutFirstAxis.apply
|
|
| 433 |
def pad_input(
|
| 434 |
hidden_states: torch.Tensor, indices: torch.Tensor, batch: int, seqlen: int
|
| 435 |
) -> torch.Tensor:
|
| 436 |
-
|
| 437 |
-
|
|
|
|
| 438 |
|
| 439 |
|
| 440 |
def _unpad_input(
|
|
@@ -450,18 +460,19 @@ def _unpad_input(
|
|
| 450 |
tuple[torch.Tensor, torch.Tensor],
|
| 451 |
tuple[int, int],
|
| 452 |
]:
|
|
|
|
| 453 |
batch_size, seq_len, num_heads, head_dim = query_layer.shape
|
| 454 |
-
seqlens = attention_mask_2d.sum(dim=1).int()
|
| 455 |
-
cu_seqlens = F.pad(seqlens.cumsum(0, dtype=torch.int32), (1, 0))
|
| 456 |
max_seqlen = int(seqlens.max().item())
|
| 457 |
-
indices = attention_mask_2d.flatten().nonzero(as_tuple=False).flatten()
|
| 458 |
-
query_layer = index_first_axis(
|
| 459 |
query_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices
|
| 460 |
)
|
| 461 |
-
key_layer = index_first_axis(
|
| 462 |
key_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices
|
| 463 |
)
|
| 464 |
-
value_layer = index_first_axis(
|
| 465 |
value_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices
|
| 466 |
)
|
| 467 |
return (
|
|
@@ -482,6 +493,7 @@ def _validate_flash_padding_mask(
|
|
| 482 |
) -> torch.Tensor:
|
| 483 |
"""Validate the self-attention padding mask used by the varlen kernels."""
|
| 484 |
|
|
|
|
| 485 |
if attention_mask_2d.ndim != 2:
|
| 486 |
raise ValueError("FlashAttention padding masks must have shape (batch, sequence_length).")
|
| 487 |
expected_shape = query_states.shape[:2]
|
|
@@ -497,7 +509,7 @@ def _validate_flash_padding_mask(
|
|
| 497 |
)
|
| 498 |
if attention_mask_2d.device != query_states.device:
|
| 499 |
raise ValueError("FlashAttention padding mask and Q, K, and V must be on the same device.")
|
| 500 |
-
return attention_mask_2d.to(dtype=torch.bool)
|
| 501 |
|
| 502 |
|
| 503 |
def kernels_flash_attention_func(
|
|
@@ -521,6 +533,8 @@ def kernels_flash_attention_func(
|
|
| 521 |
`softmax_scale=1.0`. Otherwise the flash kernel applies its default scale
|
| 522 |
again, yielding an effective `1/head_dim` scale that drifts across layers.
|
| 523 |
"""
|
|
|
|
|
|
|
| 524 |
_validate_kernels_flash_device(
|
| 525 |
query_states,
|
| 526 |
key_states,
|
|
@@ -534,11 +548,11 @@ def kernels_flash_attention_func(
|
|
| 534 |
implementation,
|
| 535 |
)
|
| 536 |
if query_states.dtype != runtime_dtype:
|
| 537 |
-
query_states = query_states.to(dtype=runtime_dtype)
|
| 538 |
-
key_states = key_states.to(dtype=runtime_dtype)
|
| 539 |
-
value_states = value_states.to(dtype=runtime_dtype)
|
| 540 |
if attention_mask_2d is not None:
|
| 541 |
-
attention_mask_2d = _validate_flash_padding_mask(
|
| 542 |
query_states,
|
| 543 |
key_states,
|
| 544 |
value_states,
|
|
@@ -554,8 +568,13 @@ def kernels_flash_attention_func(
|
|
| 554 |
indices_q,
|
| 555 |
(cu_seqlens_q, cu_seqlens_k),
|
| 556 |
(max_seqlen_q, max_seqlen_k),
|
| 557 |
-
) = _unpad_input(
|
| 558 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
query_states=query_states,
|
| 560 |
key_states=key_states,
|
| 561 |
value_states=value_states,
|
|
@@ -567,10 +586,10 @@ def kernels_flash_attention_func(
|
|
| 567 |
softmax_scale=softmax_scale,
|
| 568 |
implementation=implementation,
|
| 569 |
)
|
| 570 |
-
output = pad_input(attn_output_unpad, indices_q, batch_size, q_len)
|
| 571 |
-
return output.masked_fill(~attention_mask_2d[:, :, None, None], 0)
|
| 572 |
else:
|
| 573 |
-
return _kernels_flash_forward(
|
| 574 |
query_states=query_states,
|
| 575 |
key_states=key_states,
|
| 576 |
value_states=value_states,
|
|
@@ -706,6 +725,7 @@ def get_attention_mask(
|
|
| 706 |
|
| 707 |
Returns (attention_mask_2d, attention_mask_4d, flex_block_mask).
|
| 708 |
"""
|
|
|
|
| 709 |
if attention_mask is None:
|
| 710 |
return None, None, None
|
| 711 |
|
|
@@ -720,14 +740,14 @@ def get_attention_mask(
|
|
| 720 |
"attention_mask shape must match the input batch and sequence dimensions; "
|
| 721 |
f"expected {expected_shape}, received {tuple(attention_mask.shape)}."
|
| 722 |
)
|
| 723 |
-
attention_mask_2d = attention_mask.to(device=device, dtype=torch.bool)
|
| 724 |
if not bool(attention_mask_2d.any(dim=1).all()):
|
| 725 |
raise ValueError("attention_mask must keep at least one valid key per batch row.")
|
| 726 |
|
| 727 |
effective_backend = resolve_attention_backend(effective_backend)
|
| 728 |
|
| 729 |
if effective_backend.is_flash:
|
| 730 |
-
return attention_mask_2d, None, None
|
| 731 |
|
| 732 |
if effective_backend == AttentionBackend.FLEX_ATTENTION:
|
| 733 |
if create_block_mask is None:
|
|
@@ -751,12 +771,12 @@ def get_attention_mask(
|
|
| 751 |
mask_semantics=mask_semantics,
|
| 752 |
mask_mod=mask_mod,
|
| 753 |
)
|
| 754 |
-
return attention_mask_2d, None, flex_block_mask
|
| 755 |
|
| 756 |
# SDPA/manual masks only keys. Padding queries still attend to real keys, so
|
| 757 |
# their outputs stay finite instead of softmaxing over all -inf scores.
|
| 758 |
-
attention_mask_4d = attention_mask_2d[:, None, None, :]
|
| 759 |
-
return attention_mask_2d, attention_mask_4d, None
|
| 760 |
|
| 761 |
|
| 762 |
def bool_to_additive_mask(
|
|
@@ -770,10 +790,11 @@ def bool_to_additive_mask(
|
|
| 770 |
That silently drops the mask. Always allocate a float tensor first, then fill it.
|
| 771 |
This helper is the sanctioned way to build an SDPA additive mask from a bool validity mask.
|
| 772 |
"""
|
|
|
|
| 773 |
if bool_mask.dtype != torch.bool:
|
| 774 |
raise TypeError(
|
| 775 |
f"bool_to_additive_mask requires a bool tensor, got dtype={bool_mask.dtype}"
|
| 776 |
)
|
| 777 |
-
additive = torch.zeros_like(bool_mask, dtype=dtype)
|
| 778 |
additive.masked_fill_(bool_mask.logical_not(), float("-inf"))
|
| 779 |
-
return additive
|
|
|
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
import warnings
|
| 11 |
+
import torch
|
| 12 |
from collections import OrderedDict
|
| 13 |
from collections.abc import Callable
|
| 14 |
from enum import Enum
|
| 15 |
from threading import RLock
|
|
|
|
|
|
|
| 16 |
from einops import rearrange
|
| 17 |
from torch.nn import functional as F
|
| 18 |
|
| 19 |
from ._kernel_lock import load_locked_kernel
|
| 20 |
|
| 21 |
+
|
| 22 |
try:
|
| 23 |
from torch.nn.attention.flex_attention import BlockMask, create_block_mask, flex_attention
|
| 24 |
except ImportError:
|
|
|
|
| 117 |
raise RuntimeError(
|
| 118 |
"'flex_attention' was requested, but torch.create_block_mask is unavailable."
|
| 119 |
)
|
| 120 |
+
pattern = mask_pattern.detach().to(device=device).contiguous() # mask_pattern.shape
|
| 121 |
# One device-to-host transfer is required for an exact cache identity. Use
|
| 122 |
# the contiguous buffer directly instead of materializing one Python int
|
| 123 |
# per byte, which is prohibitively expensive for long batched sequences.
|
| 124 |
+
host_pattern = pattern.to(device="cpu").contiguous() # mask_pattern.shape
|
| 125 |
+
pattern_bytes = host_pattern.view(torch.uint8).numpy().tobytes(order="C") # bytes
|
| 126 |
cache_key = (
|
| 127 |
str(device),
|
| 128 |
None if dtype is None else str(dtype),
|
|
|
|
| 203 |
) -> torch.dtype:
|
| 204 |
"""Reject dtypes outside the immutable kernel manifest before dispatch."""
|
| 205 |
|
| 206 |
+
# query_states, key_states, value_states: (b, l, h, d) or (t, h, d)
|
| 207 |
tensor_dtypes = {query_states.dtype, key_states.dtype, value_states.dtype}
|
| 208 |
if len(tensor_dtypes) != 1:
|
| 209 |
observed = ", ".join(sorted(str(dtype) for dtype in tensor_dtypes))
|
|
|
|
| 244 |
) -> torch.device:
|
| 245 |
"""Require Q, K, and V on one CUDA device before loading a kernel."""
|
| 246 |
|
| 247 |
+
# query_states, key_states, value_states: (b, l, h, d) or (t, h, d)
|
| 248 |
devices = (query_states.device, key_states.device, value_states.device)
|
| 249 |
if len(set(devices)) != 1:
|
| 250 |
observed = ", ".join(str(device) for device in devices)
|
|
|
|
| 286 |
Failing to override when Q is pre-scaled applies the scale twice and breaks
|
| 287 |
parity with eager attention and SDPA.
|
| 288 |
"""
|
| 289 |
+
# query_states, key_states, value_states: (b, l, h, d)
|
| 290 |
flash_kernel, flash_kernel_variant = _ensure_flash_kernels_loaded(implementation)
|
| 291 |
if flash_kernel_variant == "flash_attn2":
|
| 292 |
+
output = flash_kernel.flash_attn_func( # (b, l, h, d) or tuple with that first
|
| 293 |
q=query_states,
|
| 294 |
k=key_states,
|
| 295 |
v=value_states,
|
|
|
|
| 297 |
softmax_scale=softmax_scale,
|
| 298 |
causal=causal,
|
| 299 |
)
|
| 300 |
+
return output[0] if isinstance(output, tuple) else output # (b, l, h, d)
|
| 301 |
if flash_kernel_variant == "flash_attn3":
|
| 302 |
+
output = flash_kernel.flash_attn_func( # (b, l, h, d) or tuple with that first
|
| 303 |
q=query_states,
|
| 304 |
k=key_states,
|
| 305 |
v=value_states,
|
|
|
|
| 307 |
causal=causal,
|
| 308 |
)
|
| 309 |
if isinstance(output, tuple):
|
| 310 |
+
return output[0] # (b, l, h, d)
|
| 311 |
+
return output # (b, l, h, d)
|
| 312 |
raise RuntimeError(f"Unsupported FlashAttention kernel variant: {flash_kernel_variant}")
|
| 313 |
|
| 314 |
|
|
|
|
| 329 |
See `_kernels_flash_forward` docstring for why `softmax_scale=1.0` must be
|
| 330 |
passed when Q has been pre-scaled by the caller.
|
| 331 |
"""
|
| 332 |
+
# query_states, key_states, value_states: (t, h, d)
|
| 333 |
+
# cu_seqlens_q, cu_seqlens_k: (b + 1,)
|
| 334 |
flash_kernel, flash_kernel_variant = _ensure_flash_kernels_loaded(implementation)
|
| 335 |
if flash_kernel_variant == "flash_attn2":
|
| 336 |
+
output = flash_kernel.flash_attn_varlen_func( # (t, h, d) or tuple with that first
|
| 337 |
q=query_states,
|
| 338 |
k=key_states,
|
| 339 |
v=value_states,
|
|
|
|
| 345 |
softmax_scale=softmax_scale,
|
| 346 |
causal=causal,
|
| 347 |
)
|
| 348 |
+
return output[0] if isinstance(output, tuple) else output # (t, h, d)
|
| 349 |
if flash_kernel_variant == "flash_attn3":
|
| 350 |
+
output = flash_kernel.flash_attn_varlen_func( # (t, h, d) or tuple with that first
|
| 351 |
q=query_states,
|
| 352 |
k=key_states,
|
| 353 |
v=value_states,
|
|
|
|
| 359 |
causal=causal,
|
| 360 |
)
|
| 361 |
if isinstance(output, tuple):
|
| 362 |
+
return output[0] # (t, h, d)
|
| 363 |
+
return output # (t, h, d)
|
| 364 |
raise RuntimeError(f"Unsupported FlashAttention kernel variant: {flash_kernel_variant}")
|
| 365 |
|
| 366 |
|
|
|
|
| 369 |
class IndexFirstAxis(torch.autograd.Function):
|
| 370 |
@staticmethod
|
| 371 |
def forward(ctx, input, indices) -> torch.Tensor:
|
| 372 |
+
# input: (n, ...); indices: (m,)
|
| 373 |
ctx.save_for_backward(indices)
|
| 374 |
if input.ndim < 2:
|
| 375 |
raise ValueError(
|
|
|
|
| 383 |
)
|
| 384 |
ctx.first_axis_dim, other_shape = input.shape[0], input.shape[1:]
|
| 385 |
second_dim = other_shape.numel()
|
| 386 |
+
return torch.gather( # (m, ...)
|
| 387 |
rearrange(input, "b ... -> b (...)"), 0, indices.unsqueeze(1).expand(-1, second_dim)
|
| 388 |
).reshape(-1, *other_shape)
|
| 389 |
|
| 390 |
@staticmethod
|
| 391 |
def backward(ctx, grad_output) -> tuple[torch.Tensor, None]:
|
| 392 |
+
# grad_output: (m, ...)
|
| 393 |
(indices,) = ctx.saved_tensors
|
| 394 |
if grad_output.ndim < 2:
|
| 395 |
raise RuntimeError(
|
|
|
|
| 397 |
"two dimensions."
|
| 398 |
)
|
| 399 |
other_shape = grad_output.shape[1:]
|
| 400 |
+
grad_output = rearrange(grad_output, "b ... -> b (...)") # (m, product(...))
|
| 401 |
+
grad_input = torch.zeros( # (n, product(...))
|
| 402 |
[ctx.first_axis_dim, grad_output.shape[1]],
|
| 403 |
device=grad_output.device,
|
| 404 |
dtype=grad_output.dtype,
|
| 405 |
)
|
| 406 |
grad_input.scatter_(0, indices.unsqueeze(1).expand(-1, grad_output.shape[1]), grad_output)
|
| 407 |
+
return grad_input.reshape(ctx.first_axis_dim, *other_shape), None # (n, ...), None
|
| 408 |
|
| 409 |
|
| 410 |
class IndexPutFirstAxis(torch.autograd.Function):
|
| 411 |
@staticmethod
|
| 412 |
def forward(ctx, values, indices, first_axis_dim) -> torch.Tensor:
|
| 413 |
+
# values: (m, ...); indices: (m,)
|
| 414 |
ctx.save_for_backward(indices)
|
| 415 |
if indices.ndim != 1:
|
| 416 |
raise ValueError(
|
|
|
|
| 422 |
"index_put_first_axis values must have at least two dimensions; "
|
| 423 |
f"received shape {tuple(values.shape)}."
|
| 424 |
)
|
| 425 |
+
output = torch.zeros( # (n, ...)
|
| 426 |
first_axis_dim, *values.shape[1:], device=values.device, dtype=values.dtype
|
| 427 |
)
|
| 428 |
output[indices] = values
|
| 429 |
+
return output # (n, ...)
|
| 430 |
|
| 431 |
@staticmethod
|
| 432 |
def backward(ctx, grad_output) -> tuple[torch.Tensor, None, None]:
|
| 433 |
+
# grad_output: (n, ...)
|
| 434 |
(indices,) = ctx.saved_tensors
|
| 435 |
+
return grad_output[indices], None, None # (m, ...), None, None
|
| 436 |
|
| 437 |
|
| 438 |
index_first_axis = IndexFirstAxis.apply
|
|
|
|
| 442 |
def pad_input(
|
| 443 |
hidden_states: torch.Tensor, indices: torch.Tensor, batch: int, seqlen: int
|
| 444 |
) -> torch.Tensor:
|
| 445 |
+
# hidden_states: (t, ...); indices: (t,)
|
| 446 |
+
output = index_put_first_axis(hidden_states, indices, batch * seqlen) # (b * l, ...)
|
| 447 |
+
return rearrange(output, "(b s) ... -> b s ...", b=batch) # (b, l, ...)
|
| 448 |
|
| 449 |
|
| 450 |
def _unpad_input(
|
|
|
|
| 460 |
tuple[torch.Tensor, torch.Tensor],
|
| 461 |
tuple[int, int],
|
| 462 |
]:
|
| 463 |
+
# query_layer, key_layer, value_layer: (b, l, h, d); attention_mask_2d: (b, l)
|
| 464 |
batch_size, seq_len, num_heads, head_dim = query_layer.shape
|
| 465 |
+
seqlens = attention_mask_2d.sum(dim=1).int() # (b,)
|
| 466 |
+
cu_seqlens = F.pad(seqlens.cumsum(0, dtype=torch.int32), (1, 0)) # (b + 1,)
|
| 467 |
max_seqlen = int(seqlens.max().item())
|
| 468 |
+
indices = attention_mask_2d.flatten().nonzero(as_tuple=False).flatten() # (t,)
|
| 469 |
+
query_layer = index_first_axis( # (t, h, d)
|
| 470 |
query_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices
|
| 471 |
)
|
| 472 |
+
key_layer = index_first_axis( # (t, h, d)
|
| 473 |
key_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices
|
| 474 |
)
|
| 475 |
+
value_layer = index_first_axis( # (t, h, d)
|
| 476 |
value_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices
|
| 477 |
)
|
| 478 |
return (
|
|
|
|
| 493 |
) -> torch.Tensor:
|
| 494 |
"""Validate the self-attention padding mask used by the varlen kernels."""
|
| 495 |
|
| 496 |
+
# query_states, key_states, value_states: (b, l, h, d); attention_mask_2d: (b, l)
|
| 497 |
if attention_mask_2d.ndim != 2:
|
| 498 |
raise ValueError("FlashAttention padding masks must have shape (batch, sequence_length).")
|
| 499 |
expected_shape = query_states.shape[:2]
|
|
|
|
| 509 |
)
|
| 510 |
if attention_mask_2d.device != query_states.device:
|
| 511 |
raise ValueError("FlashAttention padding mask and Q, K, and V must be on the same device.")
|
| 512 |
+
return attention_mask_2d.to(dtype=torch.bool) # (b, l)
|
| 513 |
|
| 514 |
|
| 515 |
def kernels_flash_attention_func(
|
|
|
|
| 533 |
`softmax_scale=1.0`. Otherwise the flash kernel applies its default scale
|
| 534 |
again, yielding an effective `1/head_dim` scale that drifts across layers.
|
| 535 |
"""
|
| 536 |
+
# query_states, key_states, value_states: (b, l, h, d)
|
| 537 |
+
# attention_mask_2d: (b, l) or None
|
| 538 |
_validate_kernels_flash_device(
|
| 539 |
query_states,
|
| 540 |
key_states,
|
|
|
|
| 548 |
implementation,
|
| 549 |
)
|
| 550 |
if query_states.dtype != runtime_dtype:
|
| 551 |
+
query_states = query_states.to(dtype=runtime_dtype) # (b, l, h, d)
|
| 552 |
+
key_states = key_states.to(dtype=runtime_dtype) # (b, l, h, d)
|
| 553 |
+
value_states = value_states.to(dtype=runtime_dtype) # (b, l, h, d)
|
| 554 |
if attention_mask_2d is not None:
|
| 555 |
+
attention_mask_2d = _validate_flash_padding_mask( # (b, l)
|
| 556 |
query_states,
|
| 557 |
key_states,
|
| 558 |
value_states,
|
|
|
|
| 568 |
indices_q,
|
| 569 |
(cu_seqlens_q, cu_seqlens_k),
|
| 570 |
(max_seqlen_q, max_seqlen_k),
|
| 571 |
+
) = _unpad_input( # (t, h, d), (t, h, d), (t, h, d), (t,), (b + 1,), scalars
|
| 572 |
+
query_states,
|
| 573 |
+
key_states,
|
| 574 |
+
value_states,
|
| 575 |
+
attention_mask_2d,
|
| 576 |
+
)
|
| 577 |
+
attn_output_unpad = _kernels_flash_varlen_forward( # (t, h, d)
|
| 578 |
query_states=query_states,
|
| 579 |
key_states=key_states,
|
| 580 |
value_states=value_states,
|
|
|
|
| 586 |
softmax_scale=softmax_scale,
|
| 587 |
implementation=implementation,
|
| 588 |
)
|
| 589 |
+
output = pad_input(attn_output_unpad, indices_q, batch_size, q_len) # (b, l, h, d)
|
| 590 |
+
return output.masked_fill(~attention_mask_2d[:, :, None, None], 0) # (b, l, h, d)
|
| 591 |
else:
|
| 592 |
+
return _kernels_flash_forward( # (b, l, h, d)
|
| 593 |
query_states=query_states,
|
| 594 |
key_states=key_states,
|
| 595 |
value_states=value_states,
|
|
|
|
| 725 |
|
| 726 |
Returns (attention_mask_2d, attention_mask_4d, flex_block_mask).
|
| 727 |
"""
|
| 728 |
+
# attention_mask: (b, l) or None
|
| 729 |
if attention_mask is None:
|
| 730 |
return None, None, None
|
| 731 |
|
|
|
|
| 740 |
"attention_mask shape must match the input batch and sequence dimensions; "
|
| 741 |
f"expected {expected_shape}, received {tuple(attention_mask.shape)}."
|
| 742 |
)
|
| 743 |
+
attention_mask_2d = attention_mask.to(device=device, dtype=torch.bool) # (b, l)
|
| 744 |
if not bool(attention_mask_2d.any(dim=1).all()):
|
| 745 |
raise ValueError("attention_mask must keep at least one valid key per batch row.")
|
| 746 |
|
| 747 |
effective_backend = resolve_attention_backend(effective_backend)
|
| 748 |
|
| 749 |
if effective_backend.is_flash:
|
| 750 |
+
return attention_mask_2d, None, None # (b, l), None, None
|
| 751 |
|
| 752 |
if effective_backend == AttentionBackend.FLEX_ATTENTION:
|
| 753 |
if create_block_mask is None:
|
|
|
|
| 771 |
mask_semantics=mask_semantics,
|
| 772 |
mask_mod=mask_mod,
|
| 773 |
)
|
| 774 |
+
return attention_mask_2d, None, flex_block_mask # (b, l), None, BlockMask
|
| 775 |
|
| 776 |
# SDPA/manual masks only keys. Padding queries still attend to real keys, so
|
| 777 |
# their outputs stay finite instead of softmaxing over all -inf scores.
|
| 778 |
+
attention_mask_4d = attention_mask_2d[:, None, None, :] # (b, 1, 1, l)
|
| 779 |
+
return attention_mask_2d, attention_mask_4d, None # (b, l), (b, 1, 1, l), None
|
| 780 |
|
| 781 |
|
| 782 |
def bool_to_additive_mask(
|
|
|
|
| 790 |
That silently drops the mask. Always allocate a float tensor first, then fill it.
|
| 791 |
This helper is the sanctioned way to build an SDPA additive mask from a bool validity mask.
|
| 792 |
"""
|
| 793 |
+
# bool_mask: (...)
|
| 794 |
if bool_mask.dtype != torch.bool:
|
| 795 |
raise TypeError(
|
| 796 |
f"bool_to_additive_mask requires a bool tensor, got dtype={bool_mask.dtype}"
|
| 797 |
)
|
| 798 |
+
additive = torch.zeros_like(bool_mask, dtype=dtype) # (...)
|
| 799 |
additive.masked_fill_(bool_mask.logical_not(), float("-inf"))
|
| 800 |
+
return additive # (...)
|
fastplms/attention/_kernel_lock.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
-
import importlib.metadata
|
| 6 |
import json
|
| 7 |
import os
|
| 8 |
from pathlib import Path
|
|
@@ -15,50 +14,35 @@ def require_kernels_package() -> None:
|
|
| 15 |
import kernels # noqa: F401
|
| 16 |
except ImportError as error:
|
| 17 |
raise RuntimeError(
|
| 18 |
-
"Precompiled FlashAttention requires
|
| 19 |
) from error
|
| 20 |
|
| 21 |
|
| 22 |
def _kernel_lock_path() -> Path:
|
| 23 |
-
"""Return the lock from
|
| 24 |
source_path = Path(__file__).resolve()
|
| 25 |
-
|
| 26 |
source_path.parents[1] / "kernels.lock",
|
| 27 |
source_path.parents[3] / "kernels.lock",
|
| 28 |
-
|
| 29 |
-
try:
|
| 30 |
-
import fastplms
|
| 31 |
-
|
| 32 |
-
candidates.extend(Path(root) / "kernels.lock" for root in fastplms.__path__)
|
| 33 |
-
except (ImportError, AttributeError):
|
| 34 |
-
pass
|
| 35 |
-
for candidate in candidates:
|
| 36 |
if candidate.is_file():
|
| 37 |
return candidate
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
raise RuntimeError("FastPLMs was installed without kernels.lock.") from error
|
| 43 |
-
for relative in distribution.files or ():
|
| 44 |
-
if relative.name != "kernels.lock":
|
| 45 |
-
continue
|
| 46 |
-
candidate = Path(distribution.locate_file(relative))
|
| 47 |
-
if candidate.is_file():
|
| 48 |
-
return candidate
|
| 49 |
-
raise RuntimeError("The installed FastPLMs distribution does not contain kernels.lock.")
|
| 50 |
|
| 51 |
|
| 52 |
def _locked_entry(lock_path: Path, repository: str) -> dict[str, Any]:
|
| 53 |
try:
|
| 54 |
-
|
| 55 |
except (OSError, json.JSONDecodeError) as error:
|
| 56 |
-
raise RuntimeError(f"Unable to read the
|
| 57 |
-
if not isinstance(
|
| 58 |
raise RuntimeError("kernels.lock must contain a JSON list.")
|
| 59 |
-
if any(not isinstance(entry, dict) for entry in
|
| 60 |
raise RuntimeError("Every kernels.lock entry must be a JSON object.")
|
| 61 |
-
matches = [entry for entry in
|
| 62 |
if len(matches) != 1:
|
| 63 |
raise RuntimeError(
|
| 64 |
f"kernels.lock must contain exactly one entry for {repository!r}; found {len(matches)}."
|
|
@@ -125,11 +109,11 @@ def _load_offline_locked_kernel(
|
|
| 125 |
from kernels.variants import get_variants_local, resolve_variants
|
| 126 |
except ImportError as error:
|
| 127 |
raise RuntimeError(
|
| 128 |
-
"Precompiled FlashAttention requires
|
| 129 |
) from error
|
| 130 |
|
| 131 |
-
|
| 132 |
-
parsed_names = {variant.variant_str for variant in
|
| 133 |
invalid = sorted(set(cached_names).difference(parsed_names))
|
| 134 |
if invalid:
|
| 135 |
raise RuntimeError(
|
|
@@ -137,7 +121,7 @@ def _load_offline_locked_kernel(
|
|
| 137 |
f"{', '.join(invalid)}"
|
| 138 |
)
|
| 139 |
|
| 140 |
-
compatible, _ = resolve_variants(
|
| 141 |
if len(compatible) != 1:
|
| 142 |
names = ", ".join(variant.variant_str for variant in compatible) or "none"
|
| 143 |
raise RuntimeError(
|
|
@@ -165,7 +149,7 @@ def load_locked_kernel(repository: str, revision: str) -> object:
|
|
| 165 |
from kernels.lockfile import KernelLock
|
| 166 |
except ImportError as error:
|
| 167 |
raise RuntimeError(
|
| 168 |
-
"Precompiled FlashAttention requires
|
| 169 |
) from error
|
| 170 |
|
| 171 |
lock_path = _kernel_lock_path()
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
| 5 |
import json
|
| 6 |
import os
|
| 7 |
from pathlib import Path
|
|
|
|
| 14 |
import kernels # noqa: F401
|
| 15 |
except ImportError as error:
|
| 16 |
raise RuntimeError(
|
| 17 |
+
"Precompiled FlashAttention requires requirements/features/flash.in."
|
| 18 |
) from error
|
| 19 |
|
| 20 |
|
| 21 |
def _kernel_lock_path() -> Path:
|
| 22 |
+
"""Return the kernel lock from a Hub artifact or source checkout."""
|
| 23 |
source_path = Path(__file__).resolve()
|
| 24 |
+
for candidate in (
|
| 25 |
source_path.parents[1] / "kernels.lock",
|
| 26 |
source_path.parents[3] / "kernels.lock",
|
| 27 |
+
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
if candidate.is_file():
|
| 29 |
return candidate
|
| 30 |
|
| 31 |
+
raise RuntimeError(
|
| 32 |
+
"kernels.lock is missing from the Hugging Face artifact or source checkout."
|
| 33 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
|
| 36 |
def _locked_entry(lock_path: Path, repository: str) -> dict[str, Any]:
|
| 37 |
try:
|
| 38 |
+
lock_entries = json.loads(lock_path.read_text(encoding="utf-8"))
|
| 39 |
except (OSError, json.JSONDecodeError) as error:
|
| 40 |
+
raise RuntimeError(f"Unable to read the kernel lock: {lock_path}") from error
|
| 41 |
+
if not isinstance(lock_entries, list):
|
| 42 |
raise RuntimeError("kernels.lock must contain a JSON list.")
|
| 43 |
+
if any(not isinstance(entry, dict) for entry in lock_entries):
|
| 44 |
raise RuntimeError("Every kernels.lock entry must be a JSON object.")
|
| 45 |
+
matches = [entry for entry in lock_entries if entry.get("repo_id") == repository]
|
| 46 |
if len(matches) != 1:
|
| 47 |
raise RuntimeError(
|
| 48 |
f"kernels.lock must contain exactly one entry for {repository!r}; found {len(matches)}."
|
|
|
|
| 109 |
from kernels.variants import get_variants_local, resolve_variants
|
| 110 |
except ImportError as error:
|
| 111 |
raise RuntimeError(
|
| 112 |
+
"Precompiled FlashAttention requires requirements/features/flash.in."
|
| 113 |
) from error
|
| 114 |
|
| 115 |
+
cached_variants = get_variants_local(build_root)
|
| 116 |
+
parsed_names = {variant.variant_str for variant in cached_variants}
|
| 117 |
invalid = sorted(set(cached_names).difference(parsed_names))
|
| 118 |
if invalid:
|
| 119 |
raise RuntimeError(
|
|
|
|
| 121 |
f"{', '.join(invalid)}"
|
| 122 |
)
|
| 123 |
|
| 124 |
+
compatible, _ = resolve_variants(cached_variants)
|
| 125 |
if len(compatible) != 1:
|
| 126 |
names = ", ".join(variant.variant_str for variant in compatible) or "none"
|
| 127 |
raise RuntimeError(
|
|
|
|
| 149 |
from kernels.lockfile import KernelLock
|
| 150 |
except ImportError as error:
|
| 151 |
raise RuntimeError(
|
| 152 |
+
"Precompiled FlashAttention requires requirements/features/flash.in."
|
| 153 |
) from error
|
| 154 |
|
| 155 |
lock_path = _kernel_lock_path()
|
fastplms/attention/interfaces.py
CHANGED
|
@@ -2,11 +2,10 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
| 5 |
from collections.abc import Mapping
|
| 6 |
from functools import partial
|
| 7 |
from typing import Any
|
| 8 |
-
|
| 9 |
-
import torch
|
| 10 |
from transformers import AttentionInterface, AttentionMaskInterface
|
| 11 |
|
| 12 |
from ._core import (
|
|
@@ -36,6 +35,7 @@ def _kernels_attention_forward(
|
|
| 36 |
FastPLMs kernel adapter uses the latter layout internally.
|
| 37 |
"""
|
| 38 |
|
|
|
|
| 39 |
dropout = float(kwargs.get("dropout", 0.0) or 0.0)
|
| 40 |
if module.training and dropout:
|
| 41 |
raise RuntimeError(
|
|
@@ -45,15 +45,15 @@ def _kernels_attention_forward(
|
|
| 45 |
causal = bool(kwargs.get("is_causal", getattr(module, "is_causal", False)))
|
| 46 |
softmax_scale = kwargs.get("scaling")
|
| 47 |
output = kernels_flash_attention_func(
|
| 48 |
-
query_states=query.transpose(1, 2).contiguous(),
|
| 49 |
-
key_states=key.transpose(1, 2).contiguous(),
|
| 50 |
-
value_states=value.transpose(1, 2).contiguous(),
|
| 51 |
attention_mask_2d=attention_mask,
|
| 52 |
causal=causal,
|
| 53 |
softmax_scale=softmax_scale,
|
| 54 |
implementation=implementation,
|
| 55 |
-
)
|
| 56 |
-
return output, None
|
| 57 |
|
| 58 |
|
| 59 |
# Keep FastPLMs' kernels-only adapters local to this registry instance.
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
+
import torch
|
| 6 |
from collections.abc import Mapping
|
| 7 |
from functools import partial
|
| 8 |
from typing import Any
|
|
|
|
|
|
|
| 9 |
from transformers import AttentionInterface, AttentionMaskInterface
|
| 10 |
|
| 11 |
from ._core import (
|
|
|
|
| 35 |
FastPLMs kernel adapter uses the latter layout internally.
|
| 36 |
"""
|
| 37 |
|
| 38 |
+
# query, key, value: (b, h, l, d); attention_mask: (b, l) or None
|
| 39 |
dropout = float(kwargs.get("dropout", 0.0) or 0.0)
|
| 40 |
if module.training and dropout:
|
| 41 |
raise RuntimeError(
|
|
|
|
| 45 |
causal = bool(kwargs.get("is_causal", getattr(module, "is_causal", False)))
|
| 46 |
softmax_scale = kwargs.get("scaling")
|
| 47 |
output = kernels_flash_attention_func(
|
| 48 |
+
query_states=query.transpose(1, 2).contiguous(), # (b, l, h, d)
|
| 49 |
+
key_states=key.transpose(1, 2).contiguous(), # (b, l, h, d)
|
| 50 |
+
value_states=value.transpose(1, 2).contiguous(), # (b, l, h, d)
|
| 51 |
attention_mask_2d=attention_mask,
|
| 52 |
causal=causal,
|
| 53 |
softmax_scale=softmax_scale,
|
| 54 |
implementation=implementation,
|
| 55 |
+
) # (b, l, h, d)
|
| 56 |
+
return output, None # (b, l, h, d), None
|
| 57 |
|
| 58 |
|
| 59 |
# Keep FastPLMs' kernels-only adapters local to this registry instance.
|
fastplms/embeddings/__init__.py
CHANGED
|
@@ -33,6 +33,7 @@ from .types import (
|
|
| 33 |
TensorValue,
|
| 34 |
)
|
| 35 |
|
|
|
|
| 36 |
__all__ = [
|
| 37 |
"DEFAULT_SHARD_SIZE",
|
| 38 |
"POOLING_NAMES",
|
|
|
|
| 33 |
TensorValue,
|
| 34 |
)
|
| 35 |
|
| 36 |
+
|
| 37 |
__all__ = [
|
| 38 |
"DEFAULT_SHARD_SIZE",
|
| 39 |
"POOLING_NAMES",
|
fastplms/embeddings/pooling.py
CHANGED
|
@@ -3,15 +3,16 @@
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import math
|
| 6 |
-
from collections.abc import Sequence
|
| 7 |
-
|
| 8 |
import torch
|
|
|
|
| 9 |
from torch import Tensor
|
| 10 |
|
|
|
|
| 11 |
POOLING_NAMES = frozenset({"mean", "max", "norm", "median", "std", "var", "cls", "parti"})
|
| 12 |
|
| 13 |
|
| 14 |
def _validate_inputs(X: Tensor, M: Tensor) -> Tensor:
|
|
|
|
| 15 |
if not isinstance(X, Tensor) or not isinstance(M, Tensor):
|
| 16 |
raise TypeError("X and M must be tensors.")
|
| 17 |
if X.ndim != 3:
|
|
@@ -24,12 +25,12 @@ def _validate_inputs(X: Tensor, M: Tensor) -> Tensor:
|
|
| 24 |
raise TypeError("M must be a boolean or binary numeric residue mask.")
|
| 25 |
if not bool(torch.isfinite(M).all()) or not bool(((M == 0) | (M == 1)).all()):
|
| 26 |
raise ValueError("M must contain only finite binary mask values.")
|
| 27 |
-
M = M.to(device=X.device, dtype=torch.bool)
|
| 28 |
if not bool(M.any(dim=1).all()):
|
| 29 |
raise ValueError("Every sample must contain at least one biological residue.")
|
| 30 |
if not bool((torch.isfinite(X) | ~M.unsqueeze(-1)).all()):
|
| 31 |
raise ValueError("Biological residue embeddings produced non-finite output.")
|
| 32 |
-
return M
|
| 33 |
|
| 34 |
|
| 35 |
def _pooled_attention(attentions: Tensor | Sequence[Tensor], *, batch_size: int) -> Tensor:
|
|
@@ -43,27 +44,27 @@ def _pooled_attention(attentions: Tensor | Sequence[Tensor], *, batch_size: int)
|
|
| 43 |
if isinstance(attentions, Sequence):
|
| 44 |
if not attentions:
|
| 45 |
raise ValueError("parti received an empty attention sequence.")
|
| 46 |
-
# Each A_i
|
| 47 |
-
A = torch.stack(tuple(attentions), dim=1)
|
| 48 |
else:
|
| 49 |
-
A = attentions
|
| 50 |
|
| 51 |
if A.ndim == 5:
|
| 52 |
if A.shape[0] != batch_size and A.shape[1] == batch_size:
|
| 53 |
-
A = A.transpose(0, 1)
|
| 54 |
if A.shape[0] != batch_size:
|
| 55 |
raise ValueError("Five-dimensional attentions must use (b, n, h, l, l).")
|
| 56 |
-
A = A.flatten(1, 2).amax(dim=1)
|
| 57 |
elif A.ndim == 4:
|
| 58 |
if A.shape[0] != batch_size:
|
| 59 |
raise ValueError("Four-dimensional attentions must use (b, h, l, l).")
|
| 60 |
-
A = A.amax(dim=1)
|
| 61 |
elif A.ndim == 3:
|
| 62 |
if A.shape[0] != batch_size:
|
| 63 |
raise ValueError("Three-dimensional attentions must use (b, l, l).")
|
| 64 |
else:
|
| 65 |
raise ValueError("Attentions must have shape (b, l, l), (b, h, l, l), or (b, n, h, l, l).")
|
| 66 |
-
return A
|
| 67 |
|
| 68 |
|
| 69 |
def pagerank_weights(
|
|
@@ -79,6 +80,7 @@ def pagerank_weights(
|
|
| 79 |
probabilities; dangling rows transition uniformly.
|
| 80 |
"""
|
| 81 |
|
|
|
|
| 82 |
if not isinstance(A, Tensor):
|
| 83 |
raise TypeError("A must be a tensor.")
|
| 84 |
if A.ndim != 2 or A.shape[0] != A.shape[1]:
|
|
@@ -103,19 +105,23 @@ def pagerank_weights(
|
|
| 103 |
if not bool(torch.isfinite(A).all()):
|
| 104 |
raise ValueError("A must contain only finite attention values.")
|
| 105 |
work_dtype = torch.float64 if A.dtype == torch.float64 else torch.float32
|
| 106 |
-
P = A.detach().to(dtype=work_dtype).clamp_min(0)
|
| 107 |
-
row_sum = P.sum(dim=-1, keepdim=True)
|
| 108 |
-
uniform = torch.full_like(P, 1.0 / length)
|
| 109 |
-
P = torch.where(
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
teleport = (1.0 - damping) / length
|
| 112 |
for _ in range(max_iterations):
|
| 113 |
-
p_next = teleport + damping * (P.transpose(0, 1) @ p)
|
| 114 |
if torch.linalg.vector_norm(p_next - p, ord=1) <= tolerance:
|
| 115 |
-
p = p_next
|
| 116 |
break
|
| 117 |
-
p = p_next
|
| 118 |
-
return p / p.sum()
|
| 119 |
|
| 120 |
|
| 121 |
class Pooler:
|
|
@@ -157,28 +163,29 @@ class Pooler:
|
|
| 157 |
attentions: Tensor | Sequence[Tensor] | None = None,
|
| 158 |
attention_backend: str | None = None,
|
| 159 |
) -> Tensor:
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
| 164 |
outputs: list[Tensor] = []
|
| 165 |
|
| 166 |
for name in self.names:
|
| 167 |
if name == "mean":
|
| 168 |
-
Y = X_residues.sum(dim=1) / count
|
| 169 |
elif name == "max":
|
| 170 |
-
Y = X.masked_fill(~M_expanded, -torch.inf).max(dim=1).values
|
| 171 |
elif name == "norm":
|
| 172 |
-
Y = torch.linalg.vector_norm(X_residues, ord=2, dim=1)
|
| 173 |
elif name == "median":
|
| 174 |
-
Y = X.masked_fill(~M_expanded, torch.nan).nanmedian(dim=1).values
|
| 175 |
elif name in {"var", "std"}:
|
| 176 |
-
mean = X_residues.sum(dim=1, keepdim=True) / count.unsqueeze(1)
|
| 177 |
-
centered = (X - mean).masked_fill(~M_expanded, 0)
|
| 178 |
-
variance = (centered**2).sum(dim=1) / count
|
| 179 |
-
Y = variance.sqrt() if name == "std" else variance
|
| 180 |
elif name == "cls":
|
| 181 |
-
Y = X[:, 0]
|
| 182 |
else:
|
| 183 |
if attention_backend != "eager":
|
| 184 |
raise ValueError(
|
|
@@ -189,14 +196,15 @@ class Pooler:
|
|
| 189 |
raise ValueError("parti requires model attention matrices.")
|
| 190 |
if int(M.sum(dim=1).max().item()) > 2048:
|
| 191 |
raise ValueError("parti supports at most 2,048 biological residues.")
|
| 192 |
-
A = _pooled_attention(attentions, batch_size=X.shape[0]).to(X.device)
|
| 193 |
pooled: list[Tensor] = []
|
| 194 |
for X_i, M_i, A_i in zip(X, M, A, strict=True):
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
|
|
|
| 200 |
if not bool(torch.isfinite(Y).all()):
|
| 201 |
raise ValueError(
|
| 202 |
f"Pooling operation {name!r} produced non-finite output from "
|
|
@@ -204,7 +212,7 @@ class Pooler:
|
|
| 204 |
)
|
| 205 |
outputs.append(Y)
|
| 206 |
|
| 207 |
-
return torch.cat(outputs, dim=-1)
|
| 208 |
|
| 209 |
|
| 210 |
__all__ = ["POOLING_NAMES", "Pooler", "pagerank_weights"]
|
|
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import math
|
|
|
|
|
|
|
| 6 |
import torch
|
| 7 |
+
from collections.abc import Sequence
|
| 8 |
from torch import Tensor
|
| 9 |
|
| 10 |
+
|
| 11 |
POOLING_NAMES = frozenset({"mean", "max", "norm", "median", "std", "var", "cls", "parti"})
|
| 12 |
|
| 13 |
|
| 14 |
def _validate_inputs(X: Tensor, M: Tensor) -> Tensor:
|
| 15 |
+
# X: (b, l, d); M: (b, l)
|
| 16 |
if not isinstance(X, Tensor) or not isinstance(M, Tensor):
|
| 17 |
raise TypeError("X and M must be tensors.")
|
| 18 |
if X.ndim != 3:
|
|
|
|
| 25 |
raise TypeError("M must be a boolean or binary numeric residue mask.")
|
| 26 |
if not bool(torch.isfinite(M).all()) or not bool(((M == 0) | (M == 1)).all()):
|
| 27 |
raise ValueError("M must contain only finite binary mask values.")
|
| 28 |
+
M = M.to(device=X.device, dtype=torch.bool) # (b, l)
|
| 29 |
if not bool(M.any(dim=1).all()):
|
| 30 |
raise ValueError("Every sample must contain at least one biological residue.")
|
| 31 |
if not bool((torch.isfinite(X) | ~M.unsqueeze(-1)).all()):
|
| 32 |
raise ValueError("Biological residue embeddings produced non-finite output.")
|
| 33 |
+
return M # (b, l)
|
| 34 |
|
| 35 |
|
| 36 |
def _pooled_attention(attentions: Tensor | Sequence[Tensor], *, batch_size: int) -> Tensor:
|
|
|
|
| 44 |
if isinstance(attentions, Sequence):
|
| 45 |
if not attentions:
|
| 46 |
raise ValueError("parti received an empty attention sequence.")
|
| 47 |
+
# Each A_i: (b, h, l, l).
|
| 48 |
+
A = torch.stack(tuple(attentions), dim=1) # (b, n, h, l, l)
|
| 49 |
else:
|
| 50 |
+
A = attentions # (b, ..., l, l)
|
| 51 |
|
| 52 |
if A.ndim == 5:
|
| 53 |
if A.shape[0] != batch_size and A.shape[1] == batch_size:
|
| 54 |
+
A = A.transpose(0, 1) # (b, n, h, l, l)
|
| 55 |
if A.shape[0] != batch_size:
|
| 56 |
raise ValueError("Five-dimensional attentions must use (b, n, h, l, l).")
|
| 57 |
+
A = A.flatten(1, 2).amax(dim=1) # (b, l, l)
|
| 58 |
elif A.ndim == 4:
|
| 59 |
if A.shape[0] != batch_size:
|
| 60 |
raise ValueError("Four-dimensional attentions must use (b, h, l, l).")
|
| 61 |
+
A = A.amax(dim=1) # (b, l, l)
|
| 62 |
elif A.ndim == 3:
|
| 63 |
if A.shape[0] != batch_size:
|
| 64 |
raise ValueError("Three-dimensional attentions must use (b, l, l).")
|
| 65 |
else:
|
| 66 |
raise ValueError("Attentions must have shape (b, l, l), (b, h, l, l), or (b, n, h, l, l).")
|
| 67 |
+
return A # (b, l, l)
|
| 68 |
|
| 69 |
|
| 70 |
def pagerank_weights(
|
|
|
|
| 80 |
probabilities; dangling rows transition uniformly.
|
| 81 |
"""
|
| 82 |
|
| 83 |
+
# A: (l, l)
|
| 84 |
if not isinstance(A, Tensor):
|
| 85 |
raise TypeError("A must be a tensor.")
|
| 86 |
if A.ndim != 2 or A.shape[0] != A.shape[1]:
|
|
|
|
| 105 |
if not bool(torch.isfinite(A).all()):
|
| 106 |
raise ValueError("A must contain only finite attention values.")
|
| 107 |
work_dtype = torch.float64 if A.dtype == torch.float64 else torch.float32
|
| 108 |
+
P = A.detach().to(dtype=work_dtype).clamp_min(0) # (l, l)
|
| 109 |
+
row_sum = P.sum(dim=-1, keepdim=True) # (l, 1)
|
| 110 |
+
uniform = torch.full_like(P, 1.0 / length) # (l, l)
|
| 111 |
+
P = torch.where( # (l, l)
|
| 112 |
+
row_sum > 0,
|
| 113 |
+
P / row_sum.clamp_min(torch.finfo(work_dtype).tiny),
|
| 114 |
+
uniform,
|
| 115 |
+
)
|
| 116 |
+
p = torch.full((length,), 1.0 / length, device=P.device, dtype=work_dtype) # (l,)
|
| 117 |
teleport = (1.0 - damping) / length
|
| 118 |
for _ in range(max_iterations):
|
| 119 |
+
p_next = teleport + damping * (P.transpose(0, 1) @ p) # (l,)
|
| 120 |
if torch.linalg.vector_norm(p_next - p, ord=1) <= tolerance:
|
| 121 |
+
p = p_next # (l,)
|
| 122 |
break
|
| 123 |
+
p = p_next # (l,)
|
| 124 |
+
return p / p.sum() # (l,)
|
| 125 |
|
| 126 |
|
| 127 |
class Pooler:
|
|
|
|
| 163 |
attentions: Tensor | Sequence[Tensor] | None = None,
|
| 164 |
attention_backend: str | None = None,
|
| 165 |
) -> Tensor:
|
| 166 |
+
# X: (b, l, d); residue_mask: (b, l)
|
| 167 |
+
M = _validate_inputs(X, residue_mask) # (b, l)
|
| 168 |
+
M_expanded = M.unsqueeze(-1) # (b, l, 1)
|
| 169 |
+
count = M_expanded.sum(dim=1).clamp_min(1) # (b, 1)
|
| 170 |
+
X_residues = X.masked_fill(~M_expanded, 0) # (b, l, d)
|
| 171 |
outputs: list[Tensor] = []
|
| 172 |
|
| 173 |
for name in self.names:
|
| 174 |
if name == "mean":
|
| 175 |
+
Y = X_residues.sum(dim=1) / count # (b, d)
|
| 176 |
elif name == "max":
|
| 177 |
+
Y = X.masked_fill(~M_expanded, -torch.inf).max(dim=1).values # (b, d)
|
| 178 |
elif name == "norm":
|
| 179 |
+
Y = torch.linalg.vector_norm(X_residues, ord=2, dim=1) # (b, d)
|
| 180 |
elif name == "median":
|
| 181 |
+
Y = X.masked_fill(~M_expanded, torch.nan).nanmedian(dim=1).values # (b, d)
|
| 182 |
elif name in {"var", "std"}:
|
| 183 |
+
mean = X_residues.sum(dim=1, keepdim=True) / count.unsqueeze(1) # (b, 1, d)
|
| 184 |
+
centered = (X - mean).masked_fill(~M_expanded, 0) # (b, l, d)
|
| 185 |
+
variance = (centered**2).sum(dim=1) / count # (b, d)
|
| 186 |
+
Y = variance.sqrt() if name == "std" else variance # (b, d)
|
| 187 |
elif name == "cls":
|
| 188 |
+
Y = X[:, 0] # (b, d)
|
| 189 |
else:
|
| 190 |
if attention_backend != "eager":
|
| 191 |
raise ValueError(
|
|
|
|
| 196 |
raise ValueError("parti requires model attention matrices.")
|
| 197 |
if int(M.sum(dim=1).max().item()) > 2048:
|
| 198 |
raise ValueError("parti supports at most 2,048 biological residues.")
|
| 199 |
+
A = _pooled_attention(attentions, batch_size=X.shape[0]).to(X.device) # (b, l, l)
|
| 200 |
pooled: list[Tensor] = []
|
| 201 |
for X_i, M_i, A_i in zip(X, M, A, strict=True):
|
| 202 |
+
# X_i: (l, d); M_i: (l,); A_i: (l, l)
|
| 203 |
+
indices = M_i.nonzero(as_tuple=True)[0] # (r,)
|
| 204 |
+
A_residue = A_i.index_select(0, indices).index_select(1, indices) # (r, r)
|
| 205 |
+
w = pagerank_weights(A_residue).to(dtype=X.dtype) # (r,)
|
| 206 |
+
pooled.append(w @ X_i.index_select(0, indices)) # (d,)
|
| 207 |
+
Y = torch.stack(pooled) # (b, d)
|
| 208 |
if not bool(torch.isfinite(Y).all()):
|
| 209 |
raise ValueError(
|
| 210 |
f"Pooling operation {name!r} produced non-finite output from "
|
|
|
|
| 212 |
)
|
| 213 |
outputs.append(Y)
|
| 214 |
|
| 215 |
+
return torch.cat(outputs, dim=-1) # (b, len(self.names) * d)
|
| 216 |
|
| 217 |
|
| 218 |
__all__ = ["POOLING_NAMES", "Pooler", "pagerank_weights"]
|
fastplms/embeddings/runner.py
CHANGED
|
@@ -7,12 +7,11 @@ import json
|
|
| 7 |
import platform
|
| 8 |
import sqlite3
|
| 9 |
import tempfile
|
|
|
|
| 10 |
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
| 11 |
from contextlib import contextmanager
|
| 12 |
from pathlib import Path
|
| 13 |
from typing import Any, overload
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
from torch import Tensor
|
| 17 |
|
| 18 |
from .pooling import Pooler
|
|
@@ -35,6 +34,7 @@ from .types import (
|
|
| 35 |
LazyTensorReference,
|
| 36 |
)
|
| 37 |
|
|
|
|
| 38 |
_MAX_PARTI_RESIDUES = 2_048
|
| 39 |
_RUN_FINGERPRINT_SCHEMA_VERSION = 3
|
| 40 |
_MODEL_STATE_HASH_CHUNK_BYTES = 16 * 1024**2
|
|
@@ -45,6 +45,7 @@ _SUPPORTED_STORAGE_FORMATS = frozenset({"safetensors", "sqlite"})
|
|
| 45 |
def _validate_parti_length(M: Tensor) -> None:
|
| 46 |
"""Reject an oversized attention graph before model inference."""
|
| 47 |
|
|
|
|
| 48 |
n_residues = int(M.to(dtype=torch.int64).sum(dim=1).max().item())
|
| 49 |
if n_residues > _MAX_PARTI_RESIDUES:
|
| 50 |
raise ValueError(f"parti supports at most {_MAX_PARTI_RESIDUES:,} biological residues.")
|
|
@@ -58,16 +59,17 @@ def select_hidden_state_embeddings(
|
|
| 58 |
store_all_hidden_states: bool = False,
|
| 59 |
) -> Tensor:
|
| 60 |
"""Select one hidden state or stack every state without changing values."""
|
|
|
|
| 61 |
if store_all_hidden_states:
|
| 62 |
if not hidden_states:
|
| 63 |
raise ValueError("store_all_hidden_states requires model hidden states.")
|
| 64 |
# H has shape (b, n, l, d), where n follows the model's output order.
|
| 65 |
-
return torch.stack(hidden_states, dim=1)
|
| 66 |
if hidden_state_index == -1:
|
| 67 |
-
return last_hidden_state
|
| 68 |
if not hidden_states:
|
| 69 |
raise ValueError("hidden_state_index requires model hidden states.")
|
| 70 |
-
return hidden_states[hidden_state_index]
|
| 71 |
|
| 72 |
|
| 73 |
def iter_fasta(path: str | Path) -> Iterator[EmbeddingInput]:
|
|
@@ -508,12 +510,17 @@ def _biological_residue_mask(
|
|
| 508 |
) -> Tensor:
|
| 509 |
"""Remove padding and tokenizer-declared special tokens from M."""
|
| 510 |
|
| 511 |
-
|
|
|
|
| 512 |
special_ids = tuple(int(token_id) for token_id in getattr(tokenizer, "all_special_ids", ()))
|
| 513 |
if special_ids:
|
| 514 |
-
specials = torch.tensor(
|
| 515 |
-
|
| 516 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
|
| 518 |
|
| 519 |
def _generic_embedding_batch(
|
|
@@ -535,20 +542,23 @@ def _generic_embedding_batch(
|
|
| 535 |
output = model._embed(sequences, return_attention_mask=True, **model_kwargs)
|
| 536 |
if not isinstance(output, tuple) or len(output) != 2:
|
| 537 |
raise TypeError("E1 _embed must return (X, residue_mask).")
|
| 538 |
-
X, M = output
|
| 539 |
preparer = getattr(model, "prep_tokens", None)
|
| 540 |
if preparer is not None and hasattr(preparer, "get_batch_kwargs"):
|
| 541 |
prepared = preparer.get_batch_kwargs(sequences, device=X.device)
|
| 542 |
-
input_ids = prepared["input_ids"]
|
| 543 |
-
boundary_ids = preparer.boundary_token_ids.to(
|
| 544 |
device=input_ids.device, dtype=input_ids.dtype
|
| 545 |
)
|
| 546 |
# E1 wraps each raw sequence in BOS, context-label, terminal-label,
|
| 547 |
# and EOS tokens. Only amino-acid rows are biological residues.
|
| 548 |
-
M = M.to(dtype=torch.bool) & ~torch.isin(input_ids, boundary_ids)
|
| 549 |
if need_attentions:
|
| 550 |
raise ValueError("parti is not available for tokenizer-free E1 embedding.")
|
| 551 |
-
return EmbeddingBatch(X
|
|
|
|
|
|
|
|
|
|
| 552 |
if tokenizer is None:
|
| 553 |
raise ValueError("A tokenizer is required for this model's embedding path.")
|
| 554 |
|
|
@@ -572,14 +582,17 @@ def _generic_embedding_batch(
|
|
| 572 |
else:
|
| 573 |
encoded = tokenizer(sequences, **tokenize_kwargs)
|
| 574 |
device = _model_device(model)
|
| 575 |
-
input_ids = encoded["input_ids"].to(device)
|
| 576 |
-
attention_mask = encoded.get(
|
| 577 |
-
|
|
|
|
|
|
|
|
|
|
| 578 |
if need_attentions:
|
| 579 |
# Validate l before either the backbone or its quadratic attention graph
|
| 580 |
# is materialized. M has shape (b, l).
|
| 581 |
_validate_parti_length(M)
|
| 582 |
-
X = model._embed(input_ids, attention_mask, **model_kwargs)
|
| 583 |
attentions = None
|
| 584 |
if need_attentions:
|
| 585 |
output = model(
|
|
@@ -588,10 +601,14 @@ def _generic_embedding_batch(
|
|
| 588 |
output_attentions=True,
|
| 589 |
return_dict=True,
|
| 590 |
)
|
| 591 |
-
attentions = getattr(output, "attentions", None)
|
| 592 |
if attentions is None:
|
| 593 |
raise ValueError("The model did not return attentions required by parti.")
|
| 594 |
-
return EmbeddingBatch(X
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
|
| 596 |
|
| 597 |
def _first_metadata_value(*values: Any) -> Any:
|
|
@@ -638,6 +655,7 @@ def _model_identity_metadata(model: Any) -> dict[str, Any]:
|
|
| 638 |
def _bounded_tensor_chunks(X: Tensor, max_elements: int) -> Iterable[Tensor]:
|
| 639 |
"""Yield X in logical row-major order without materializing a full copy."""
|
| 640 |
|
|
|
|
| 641 |
if X.numel() == 0:
|
| 642 |
return
|
| 643 |
if X.ndim == 0:
|
|
@@ -649,7 +667,7 @@ def _bounded_tensor_chunks(X: Tensor, max_elements: int) -> Iterable[Tensor]:
|
|
| 649 |
if trailing_elements <= max_elements:
|
| 650 |
rows_per_chunk = max(1, max_elements // trailing_elements)
|
| 651 |
for start in range(0, X.shape[0], rows_per_chunk):
|
| 652 |
-
yield X[start : start + rows_per_chunk]
|
| 653 |
return
|
| 654 |
for row in X:
|
| 655 |
yield from _bounded_tensor_chunks(row, max_elements)
|
|
@@ -685,7 +703,7 @@ def _model_state_sha256(model: Any) -> str:
|
|
| 685 |
digest.update(header)
|
| 686 |
max_elements = max(1, _MODEL_STATE_HASH_CHUNK_BYTES // value.element_size())
|
| 687 |
for chunk in _bounded_tensor_chunks(value.detach(), max_elements):
|
| 688 |
-
cpu_chunk = chunk.to(device="cpu").contiguous()
|
| 689 |
digest.update(cpu_chunk.reshape(-1).view(torch.uint8).numpy().tobytes())
|
| 690 |
return digest.hexdigest()
|
| 691 |
|
|
@@ -1306,22 +1324,24 @@ def embed_dataset(
|
|
| 1306 |
normalized_decoder_inputs[position] for position in batch_positions
|
| 1307 |
]
|
| 1308 |
if decoder_input_ids is not None:
|
| 1309 |
-
|
|
|
|
| 1310 |
batch_positions,
|
| 1311 |
device=decoder_input_ids.device,
|
| 1312 |
dtype=torch.long,
|
| 1313 |
)
|
| 1314 |
-
batch_model_kwargs["decoder_input_ids"] =
|
| 1315 |
-
0, indices
|
| 1316 |
)
|
| 1317 |
if decoder_attention_mask is not None:
|
| 1318 |
-
|
|
|
|
| 1319 |
batch_positions,
|
| 1320 |
device=decoder_attention_mask.device,
|
| 1321 |
dtype=torch.long,
|
| 1322 |
)
|
| 1323 |
batch_model_kwargs["decoder_attention_mask"] = (
|
| 1324 |
-
decoder_attention_mask.index_select(0, indices)
|
| 1325 |
)
|
| 1326 |
custom_batch = _embedding_batch_fn or getattr(model, "_embedding_batch", None)
|
| 1327 |
if custom_batch is not None:
|
|
@@ -1348,8 +1368,8 @@ def embed_dataset(
|
|
| 1348 |
need_attentions=need_attentions,
|
| 1349 |
model_kwargs=batch_model_kwargs,
|
| 1350 |
)
|
| 1351 |
-
X = batch.X
|
| 1352 |
-
raw_mask = batch.residue_mask
|
| 1353 |
if not isinstance(X, Tensor) or not isinstance(raw_mask, Tensor):
|
| 1354 |
raise TypeError("Embedding batches must provide Tensor X and residue_mask.")
|
| 1355 |
if X.is_meta or raw_mask.is_meta:
|
|
@@ -1360,7 +1380,7 @@ def embed_dataset(
|
|
| 1360 |
raise ValueError("Embedding residue_mask must contain finite binary values.")
|
| 1361 |
if not bool(((raw_mask == 0) | (raw_mask == 1)).all()):
|
| 1362 |
raise ValueError("Embedding residue_mask must contain finite binary values.")
|
| 1363 |
-
M = raw_mask.to(device=X.device, dtype=torch.bool)
|
| 1364 |
valid_X_shape = (
|
| 1365 |
X.ndim == 3
|
| 1366 |
and X.shape[0] == len(batch_records)
|
|
@@ -1384,7 +1404,7 @@ def embed_dataset(
|
|
| 1384 |
)
|
| 1385 |
if not bool(M.any(dim=1).all()):
|
| 1386 |
raise ValueError("Every embedding sample must contain a biological residue.")
|
| 1387 |
-
finite_selected = (
|
| 1388 |
torch.isfinite(X) | ~M.unsqueeze(-1)
|
| 1389 |
if X.ndim == 3
|
| 1390 |
else torch.isfinite(X) | ~M[:, None, :, None]
|
|
@@ -1395,28 +1415,32 @@ def embed_dataset(
|
|
| 1395 |
# Validate the biological graph only after mask integrity is established.
|
| 1396 |
_validate_parti_length(M)
|
| 1397 |
if dtype is not None:
|
| 1398 |
-
X = X.to(dtype=dtype)
|
| 1399 |
|
| 1400 |
if full_embeddings:
|
| 1401 |
if X.ndim == 4:
|
| 1402 |
values = [
|
| 1403 |
-
X_i[:, M_i, :].detach().cpu()
|
|
|
|
| 1404 |
]
|
| 1405 |
else:
|
| 1406 |
-
values = [
|
|
|
|
|
|
|
|
|
|
| 1407 |
else:
|
| 1408 |
if pooler is None:
|
| 1409 |
raise RuntimeError(
|
| 1410 |
"Pooled embedding output was requested without an initialized pooler."
|
| 1411 |
)
|
| 1412 |
-
Y = pooler(
|
| 1413 |
X,
|
| 1414 |
M,
|
| 1415 |
attentions=batch.attentions,
|
| 1416 |
attention_backend=attention_backend,
|
| 1417 |
)
|
| 1418 |
pool_slices = pooler.output_slices(X.shape[-1])
|
| 1419 |
-
values = list(Y.detach().cpu().unbind(0))
|
| 1420 |
for position, record, value in zip(
|
| 1421 |
batch_positions, batch_records, values, strict=True
|
| 1422 |
):
|
|
|
|
| 7 |
import platform
|
| 8 |
import sqlite3
|
| 9 |
import tempfile
|
| 10 |
+
import torch
|
| 11 |
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
| 12 |
from contextlib import contextmanager
|
| 13 |
from pathlib import Path
|
| 14 |
from typing import Any, overload
|
|
|
|
|
|
|
| 15 |
from torch import Tensor
|
| 16 |
|
| 17 |
from .pooling import Pooler
|
|
|
|
| 34 |
LazyTensorReference,
|
| 35 |
)
|
| 36 |
|
| 37 |
+
|
| 38 |
_MAX_PARTI_RESIDUES = 2_048
|
| 39 |
_RUN_FINGERPRINT_SCHEMA_VERSION = 3
|
| 40 |
_MODEL_STATE_HASH_CHUNK_BYTES = 16 * 1024**2
|
|
|
|
| 45 |
def _validate_parti_length(M: Tensor) -> None:
|
| 46 |
"""Reject an oversized attention graph before model inference."""
|
| 47 |
|
| 48 |
+
# M: (b, l)
|
| 49 |
n_residues = int(M.to(dtype=torch.int64).sum(dim=1).max().item())
|
| 50 |
if n_residues > _MAX_PARTI_RESIDUES:
|
| 51 |
raise ValueError(f"parti supports at most {_MAX_PARTI_RESIDUES:,} biological residues.")
|
|
|
|
| 59 |
store_all_hidden_states: bool = False,
|
| 60 |
) -> Tensor:
|
| 61 |
"""Select one hidden state or stack every state without changing values."""
|
| 62 |
+
# last_hidden_state and each hidden_states entry: (b, l, d)
|
| 63 |
if store_all_hidden_states:
|
| 64 |
if not hidden_states:
|
| 65 |
raise ValueError("store_all_hidden_states requires model hidden states.")
|
| 66 |
# H has shape (b, n, l, d), where n follows the model's output order.
|
| 67 |
+
return torch.stack(hidden_states, dim=1) # (b, n, l, d)
|
| 68 |
if hidden_state_index == -1:
|
| 69 |
+
return last_hidden_state # (b, l, d)
|
| 70 |
if not hidden_states:
|
| 71 |
raise ValueError("hidden_state_index requires model hidden states.")
|
| 72 |
+
return hidden_states[hidden_state_index] # (b, l, d)
|
| 73 |
|
| 74 |
|
| 75 |
def iter_fasta(path: str | Path) -> Iterator[EmbeddingInput]:
|
|
|
|
| 510 |
) -> Tensor:
|
| 511 |
"""Remove padding and tokenizer-declared special tokens from M."""
|
| 512 |
|
| 513 |
+
# input_ids, attention_mask: (b, l)
|
| 514 |
+
M = attention_mask.to(dtype=torch.bool) # (b, l)
|
| 515 |
special_ids = tuple(int(token_id) for token_id in getattr(tokenizer, "all_special_ids", ()))
|
| 516 |
if special_ids:
|
| 517 |
+
specials = torch.tensor( # (n_special,)
|
| 518 |
+
special_ids,
|
| 519 |
+
device=input_ids.device,
|
| 520 |
+
dtype=input_ids.dtype,
|
| 521 |
+
)
|
| 522 |
+
M = M & ~torch.isin(input_ids, specials) # (b, l)
|
| 523 |
+
return M # (b, l)
|
| 524 |
|
| 525 |
|
| 526 |
def _generic_embedding_batch(
|
|
|
|
| 542 |
output = model._embed(sequences, return_attention_mask=True, **model_kwargs)
|
| 543 |
if not isinstance(output, tuple) or len(output) != 2:
|
| 544 |
raise TypeError("E1 _embed must return (X, residue_mask).")
|
| 545 |
+
X, M = output # (b, l, d), (b, l)
|
| 546 |
preparer = getattr(model, "prep_tokens", None)
|
| 547 |
if preparer is not None and hasattr(preparer, "get_batch_kwargs"):
|
| 548 |
prepared = preparer.get_batch_kwargs(sequences, device=X.device)
|
| 549 |
+
input_ids = prepared["input_ids"] # (b, l)
|
| 550 |
+
boundary_ids = preparer.boundary_token_ids.to( # (n_boundary,)
|
| 551 |
device=input_ids.device, dtype=input_ids.dtype
|
| 552 |
)
|
| 553 |
# E1 wraps each raw sequence in BOS, context-label, terminal-label,
|
| 554 |
# and EOS tokens. Only amino-acid rows are biological residues.
|
| 555 |
+
M = M.to(dtype=torch.bool) & ~torch.isin(input_ids, boundary_ids) # (b, l)
|
| 556 |
if need_attentions:
|
| 557 |
raise ValueError("parti is not available for tokenizer-free E1 embedding.")
|
| 558 |
+
return EmbeddingBatch( # X: (b, l, d); residue_mask: (b, l)
|
| 559 |
+
X=X,
|
| 560 |
+
residue_mask=M.to(dtype=torch.bool),
|
| 561 |
+
)
|
| 562 |
if tokenizer is None:
|
| 563 |
raise ValueError("A tokenizer is required for this model's embedding path.")
|
| 564 |
|
|
|
|
| 582 |
else:
|
| 583 |
encoded = tokenizer(sequences, **tokenize_kwargs)
|
| 584 |
device = _model_device(model)
|
| 585 |
+
input_ids = encoded["input_ids"].to(device) # (b, l)
|
| 586 |
+
attention_mask = encoded.get( # (b, l)
|
| 587 |
+
"attention_mask",
|
| 588 |
+
input_ids.new_ones(input_ids.shape),
|
| 589 |
+
).to(device)
|
| 590 |
+
M = _biological_residue_mask(input_ids, attention_mask, tokenizer) # (b, l)
|
| 591 |
if need_attentions:
|
| 592 |
# Validate l before either the backbone or its quadratic attention graph
|
| 593 |
# is materialized. M has shape (b, l).
|
| 594 |
_validate_parti_length(M)
|
| 595 |
+
X = model._embed(input_ids, attention_mask, **model_kwargs) # (b, l, d)
|
| 596 |
attentions = None
|
| 597 |
if need_attentions:
|
| 598 |
output = model(
|
|
|
|
| 601 |
output_attentions=True,
|
| 602 |
return_dict=True,
|
| 603 |
)
|
| 604 |
+
attentions = getattr(output, "attentions", None) # each: (b, h, l, l)
|
| 605 |
if attentions is None:
|
| 606 |
raise ValueError("The model did not return attentions required by parti.")
|
| 607 |
+
return EmbeddingBatch( # X: (b, l, d); M: (b, l)
|
| 608 |
+
X=X,
|
| 609 |
+
residue_mask=M,
|
| 610 |
+
attentions=attentions,
|
| 611 |
+
)
|
| 612 |
|
| 613 |
|
| 614 |
def _first_metadata_value(*values: Any) -> Any:
|
|
|
|
| 655 |
def _bounded_tensor_chunks(X: Tensor, max_elements: int) -> Iterable[Tensor]:
|
| 656 |
"""Yield X in logical row-major order without materializing a full copy."""
|
| 657 |
|
| 658 |
+
# X: (...)
|
| 659 |
if X.numel() == 0:
|
| 660 |
return
|
| 661 |
if X.ndim == 0:
|
|
|
|
| 667 |
if trailing_elements <= max_elements:
|
| 668 |
rows_per_chunk = max(1, max_elements // trailing_elements)
|
| 669 |
for start in range(0, X.shape[0], rows_per_chunk):
|
| 670 |
+
yield X[start : start + rows_per_chunk] # (chunk_rows, ...)
|
| 671 |
return
|
| 672 |
for row in X:
|
| 673 |
yield from _bounded_tensor_chunks(row, max_elements)
|
|
|
|
| 703 |
digest.update(header)
|
| 704 |
max_elements = max(1, _MODEL_STATE_HASH_CHUNK_BYTES // value.element_size())
|
| 705 |
for chunk in _bounded_tensor_chunks(value.detach(), max_elements):
|
| 706 |
+
cpu_chunk = chunk.to(device="cpu").contiguous() # chunk.shape
|
| 707 |
digest.update(cpu_chunk.reshape(-1).view(torch.uint8).numpy().tobytes())
|
| 708 |
return digest.hexdigest()
|
| 709 |
|
|
|
|
| 1324 |
normalized_decoder_inputs[position] for position in batch_positions
|
| 1325 |
]
|
| 1326 |
if decoder_input_ids is not None:
|
| 1327 |
+
# decoder_input_ids: (n_records, l_decoder)
|
| 1328 |
+
indices = torch.tensor( # (b,)
|
| 1329 |
batch_positions,
|
| 1330 |
device=decoder_input_ids.device,
|
| 1331 |
dtype=torch.long,
|
| 1332 |
)
|
| 1333 |
+
batch_model_kwargs["decoder_input_ids"] = ( # (b, l_decoder)
|
| 1334 |
+
decoder_input_ids.index_select(0, indices)
|
| 1335 |
)
|
| 1336 |
if decoder_attention_mask is not None:
|
| 1337 |
+
# decoder_attention_mask: (n_records, l_decoder)
|
| 1338 |
+
indices = torch.tensor( # (b,)
|
| 1339 |
batch_positions,
|
| 1340 |
device=decoder_attention_mask.device,
|
| 1341 |
dtype=torch.long,
|
| 1342 |
)
|
| 1343 |
batch_model_kwargs["decoder_attention_mask"] = (
|
| 1344 |
+
decoder_attention_mask.index_select(0, indices) # (b, l_decoder)
|
| 1345 |
)
|
| 1346 |
custom_batch = _embedding_batch_fn or getattr(model, "_embedding_batch", None)
|
| 1347 |
if custom_batch is not None:
|
|
|
|
| 1368 |
need_attentions=need_attentions,
|
| 1369 |
model_kwargs=batch_model_kwargs,
|
| 1370 |
)
|
| 1371 |
+
X = batch.X # (b, l, d) or (b, n_states, l, d)
|
| 1372 |
+
raw_mask = batch.residue_mask # (b, l)
|
| 1373 |
if not isinstance(X, Tensor) or not isinstance(raw_mask, Tensor):
|
| 1374 |
raise TypeError("Embedding batches must provide Tensor X and residue_mask.")
|
| 1375 |
if X.is_meta or raw_mask.is_meta:
|
|
|
|
| 1380 |
raise ValueError("Embedding residue_mask must contain finite binary values.")
|
| 1381 |
if not bool(((raw_mask == 0) | (raw_mask == 1)).all()):
|
| 1382 |
raise ValueError("Embedding residue_mask must contain finite binary values.")
|
| 1383 |
+
M = raw_mask.to(device=X.device, dtype=torch.bool) # (b, l)
|
| 1384 |
valid_X_shape = (
|
| 1385 |
X.ndim == 3
|
| 1386 |
and X.shape[0] == len(batch_records)
|
|
|
|
| 1404 |
)
|
| 1405 |
if not bool(M.any(dim=1).all()):
|
| 1406 |
raise ValueError("Every embedding sample must contain a biological residue.")
|
| 1407 |
+
finite_selected = ( # X.shape
|
| 1408 |
torch.isfinite(X) | ~M.unsqueeze(-1)
|
| 1409 |
if X.ndim == 3
|
| 1410 |
else torch.isfinite(X) | ~M[:, None, :, None]
|
|
|
|
| 1415 |
# Validate the biological graph only after mask integrity is established.
|
| 1416 |
_validate_parti_length(M)
|
| 1417 |
if dtype is not None:
|
| 1418 |
+
X = X.to(dtype=dtype) # unchanged shape
|
| 1419 |
|
| 1420 |
if full_embeddings:
|
| 1421 |
if X.ndim == 4:
|
| 1422 |
values = [
|
| 1423 |
+
X_i[:, M_i, :].detach().cpu() # (n_states, r_i, d)
|
| 1424 |
+
for X_i, M_i in zip(X, M, strict=True)
|
| 1425 |
]
|
| 1426 |
else:
|
| 1427 |
+
values = [
|
| 1428 |
+
X_i[M_i].detach().cpu() # (r_i, d)
|
| 1429 |
+
for X_i, M_i in zip(X, M, strict=True)
|
| 1430 |
+
]
|
| 1431 |
else:
|
| 1432 |
if pooler is None:
|
| 1433 |
raise RuntimeError(
|
| 1434 |
"Pooled embedding output was requested without an initialized pooler."
|
| 1435 |
)
|
| 1436 |
+
Y = pooler( # (b, n_poolers * d)
|
| 1437 |
X,
|
| 1438 |
M,
|
| 1439 |
attentions=batch.attentions,
|
| 1440 |
attention_backend=attention_backend,
|
| 1441 |
)
|
| 1442 |
pool_slices = pooler.output_slices(X.shape[-1])
|
| 1443 |
+
values = list(Y.detach().cpu().unbind(0)) # each: (n_poolers * d,)
|
| 1444 |
for position, record, value in zip(
|
| 1445 |
batch_positions, batch_records, values, strict=True
|
| 1446 |
):
|
fastplms/embeddings/storage.py
CHANGED
|
@@ -7,14 +7,13 @@ import io
|
|
| 7 |
import json
|
| 8 |
import sqlite3
|
| 9 |
import struct
|
|
|
|
|
|
|
| 10 |
from bisect import bisect_right
|
| 11 |
from collections.abc import Iterable, Iterator, Sequence
|
| 12 |
from pathlib import Path
|
| 13 |
from typing import Any, cast, overload
|
| 14 |
from uuid import uuid4
|
| 15 |
-
|
| 16 |
-
import numpy as np
|
| 17 |
-
import torch
|
| 18 |
from torch import Tensor
|
| 19 |
|
| 20 |
from .types import (
|
|
@@ -23,6 +22,7 @@ from .types import (
|
|
| 23 |
LazyTensorReference,
|
| 24 |
)
|
| 25 |
|
|
|
|
| 26 |
_DTYPE_NAMES: dict[torch.dtype, str] = {
|
| 27 |
torch.float16: "float16",
|
| 28 |
torch.bfloat16: "bfloat16",
|
|
@@ -80,22 +80,24 @@ def _persistent_metadata(
|
|
| 80 |
def _tensor_bytes(X: Tensor) -> bytes:
|
| 81 |
"""Return the exact contiguous byte representation of X."""
|
| 82 |
|
| 83 |
-
|
|
|
|
| 84 |
return X.view(torch.uint8).numpy().tobytes()
|
| 85 |
|
| 86 |
|
| 87 |
def _bounded_tensor_chunks(X: Tensor, max_bytes: int) -> Iterator[Tensor]:
|
| 88 |
"""Yield row-major CPU chunks without materializing one full byte string."""
|
| 89 |
|
| 90 |
-
|
|
|
|
| 91 |
if flattened.numel() == 0:
|
| 92 |
return
|
| 93 |
chunk_elements = max(1, max_bytes // flattened.element_size())
|
| 94 |
for start in range(0, flattened.numel(), chunk_elements):
|
| 95 |
-
chunk = flattened[start : start + chunk_elements]
|
| 96 |
if chunk.stride(0) != 1:
|
| 97 |
-
chunk = chunk.clone(memory_format=torch.contiguous_format)
|
| 98 |
-
yield chunk
|
| 99 |
|
| 100 |
|
| 101 |
def _tensor_hash_chunks(X: Tensor) -> Iterator[bytes]:
|
|
@@ -136,9 +138,9 @@ def _decode_tensor(dtype_name: str, shape_json: str, data: bytes) -> Tensor:
|
|
| 136 |
raise ValueError(f"Unsupported stored dtype {dtype_name!r}.") from error
|
| 137 |
shape = tuple(json.loads(shape_json))
|
| 138 |
# uint8 is used only as a byte-level carrier, preserving BF16 bits exactly.
|
| 139 |
-
byte_array = np.frombuffer(data, dtype=np.uint8).copy()
|
| 140 |
-
X = torch.from_numpy(byte_array).view(dtype)
|
| 141 |
-
return X.reshape(shape).clone()
|
| 142 |
|
| 143 |
|
| 144 |
def _index_path(path: str | Path) -> Path:
|
|
@@ -651,7 +653,7 @@ class SafetensorsStreamWriter:
|
|
| 651 |
|
| 652 |
for record in records:
|
| 653 |
position = self._record_count + len(self._pending)
|
| 654 |
-
tensor = record.load_tensor().detach().cpu().contiguous()
|
| 655 |
if tensor.dtype not in _DTYPE_NAMES:
|
| 656 |
raise TypeError(f"Unsupported tensor dtype {tensor.dtype}.")
|
| 657 |
nbytes = tensor.numel() * tensor.element_size()
|
|
@@ -949,7 +951,7 @@ def save_sqlite_result(result: EmbeddingResult, path: str | Path) -> EmbeddingRe
|
|
| 949 |
(run_id, metadata_json),
|
| 950 |
)
|
| 951 |
for position, record in enumerate(result):
|
| 952 |
-
X = record.load_tensor().detach().cpu().contiguous()
|
| 953 |
dtype_name, shape_json, data = _encode_tensor(X)
|
| 954 |
digest = tensor_sha256(X)
|
| 955 |
connection.execute(
|
|
@@ -1053,7 +1055,7 @@ def append_sqlite_records(
|
|
| 1053 |
)
|
| 1054 |
for offset, record in enumerate(records):
|
| 1055 |
position = start_position + offset
|
| 1056 |
-
X = record.load_tensor().detach().cpu().contiguous()
|
| 1057 |
dtype_name, shape_json, data = _encode_tensor(X)
|
| 1058 |
digest = tensor_sha256(X)
|
| 1059 |
connection.execute(
|
|
@@ -1414,6 +1416,7 @@ def load_legacy_pth(path: str | Path, *, allow_unsafe_pickle: bool = False) -> E
|
|
| 1414 |
raise ValueError("A legacy .pth embedding file must contain a mapping.")
|
| 1415 |
records: list[EmbeddingRecord] = []
|
| 1416 |
for position, (sequence, X) in enumerate(payload.items()):
|
|
|
|
| 1417 |
if not isinstance(sequence, str) or not isinstance(X, Tensor):
|
| 1418 |
raise ValueError("Legacy embedding mappings must use str keys and Tensor values.")
|
| 1419 |
records.append(EmbeddingRecord(str(position), sequence, X.detach().cpu()))
|
|
@@ -1450,8 +1453,10 @@ def _decode_legacy_sqlite_blob(
|
|
| 1450 |
expected = int(np.prod(shape, dtype=np.int64)) * numpy_dtype.itemsize
|
| 1451 |
if len(data) - offset != expected:
|
| 1452 |
raise ValueError("Legacy compact embedding payload length does not match shape.")
|
| 1453 |
-
array =
|
| 1454 |
-
|
|
|
|
|
|
|
| 1455 |
|
| 1456 |
try:
|
| 1457 |
loaded = torch.load(io.BytesIO(data), map_location="cpu", weights_only=True)
|
|
@@ -1470,11 +1475,13 @@ def _decode_legacy_sqlite_blob(
|
|
| 1470 |
raise ValueError(
|
| 1471 |
"Legacy raw FP32 payload length does not match fallback_shape."
|
| 1472 |
) from safe_error
|
| 1473 |
-
array = np.frombuffer(data, dtype=np.float32).copy().reshape(fallback_shape
|
| 1474 |
-
|
|
|
|
|
|
|
| 1475 |
if not isinstance(loaded, Tensor):
|
| 1476 |
raise ValueError("Legacy serialized embedding payload must contain one tensor.")
|
| 1477 |
-
return loaded.detach().cpu()
|
| 1478 |
|
| 1479 |
|
| 1480 |
def convert_legacy_sqlite(
|
|
|
|
| 7 |
import json
|
| 8 |
import sqlite3
|
| 9 |
import struct
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
from bisect import bisect_right
|
| 13 |
from collections.abc import Iterable, Iterator, Sequence
|
| 14 |
from pathlib import Path
|
| 15 |
from typing import Any, cast, overload
|
| 16 |
from uuid import uuid4
|
|
|
|
|
|
|
|
|
|
| 17 |
from torch import Tensor
|
| 18 |
|
| 19 |
from .types import (
|
|
|
|
| 22 |
LazyTensorReference,
|
| 23 |
)
|
| 24 |
|
| 25 |
+
|
| 26 |
_DTYPE_NAMES: dict[torch.dtype, str] = {
|
| 27 |
torch.float16: "float16",
|
| 28 |
torch.bfloat16: "bfloat16",
|
|
|
|
| 80 |
def _tensor_bytes(X: Tensor) -> bytes:
|
| 81 |
"""Return the exact contiguous byte representation of X."""
|
| 82 |
|
| 83 |
+
# X: (...)
|
| 84 |
+
X = X.detach().cpu().contiguous() # (...)
|
| 85 |
return X.view(torch.uint8).numpy().tobytes()
|
| 86 |
|
| 87 |
|
| 88 |
def _bounded_tensor_chunks(X: Tensor, max_bytes: int) -> Iterator[Tensor]:
|
| 89 |
"""Yield row-major CPU chunks without materializing one full byte string."""
|
| 90 |
|
| 91 |
+
# X: (...)
|
| 92 |
+
flattened = X.detach().to(device="cpu").reshape(-1) # (n,)
|
| 93 |
if flattened.numel() == 0:
|
| 94 |
return
|
| 95 |
chunk_elements = max(1, max_bytes // flattened.element_size())
|
| 96 |
for start in range(0, flattened.numel(), chunk_elements):
|
| 97 |
+
chunk = flattened[start : start + chunk_elements] # (n_chunk,)
|
| 98 |
if chunk.stride(0) != 1:
|
| 99 |
+
chunk = chunk.clone(memory_format=torch.contiguous_format) # (n_chunk,)
|
| 100 |
+
yield chunk # (n_chunk,)
|
| 101 |
|
| 102 |
|
| 103 |
def _tensor_hash_chunks(X: Tensor) -> Iterator[bytes]:
|
|
|
|
| 138 |
raise ValueError(f"Unsupported stored dtype {dtype_name!r}.") from error
|
| 139 |
shape = tuple(json.loads(shape_json))
|
| 140 |
# uint8 is used only as a byte-level carrier, preserving BF16 bits exactly.
|
| 141 |
+
byte_array = np.frombuffer(data, dtype=np.uint8).copy() # (n_bytes,)
|
| 142 |
+
X = torch.from_numpy(byte_array).view(dtype) # (n_elements,)
|
| 143 |
+
return X.reshape(shape).clone() # shape
|
| 144 |
|
| 145 |
|
| 146 |
def _index_path(path: str | Path) -> Path:
|
|
|
|
| 653 |
|
| 654 |
for record in records:
|
| 655 |
position = self._record_count + len(self._pending)
|
| 656 |
+
tensor = record.load_tensor().detach().cpu().contiguous() # (...)
|
| 657 |
if tensor.dtype not in _DTYPE_NAMES:
|
| 658 |
raise TypeError(f"Unsupported tensor dtype {tensor.dtype}.")
|
| 659 |
nbytes = tensor.numel() * tensor.element_size()
|
|
|
|
| 951 |
(run_id, metadata_json),
|
| 952 |
)
|
| 953 |
for position, record in enumerate(result):
|
| 954 |
+
X = record.load_tensor().detach().cpu().contiguous() # (...)
|
| 955 |
dtype_name, shape_json, data = _encode_tensor(X)
|
| 956 |
digest = tensor_sha256(X)
|
| 957 |
connection.execute(
|
|
|
|
| 1055 |
)
|
| 1056 |
for offset, record in enumerate(records):
|
| 1057 |
position = start_position + offset
|
| 1058 |
+
X = record.load_tensor().detach().cpu().contiguous() # (...)
|
| 1059 |
dtype_name, shape_json, data = _encode_tensor(X)
|
| 1060 |
digest = tensor_sha256(X)
|
| 1061 |
connection.execute(
|
|
|
|
| 1416 |
raise ValueError("A legacy .pth embedding file must contain a mapping.")
|
| 1417 |
records: list[EmbeddingRecord] = []
|
| 1418 |
for position, (sequence, X) in enumerate(payload.items()):
|
| 1419 |
+
# X: (...)
|
| 1420 |
if not isinstance(sequence, str) or not isinstance(X, Tensor):
|
| 1421 |
raise ValueError("Legacy embedding mappings must use str keys and Tensor values.")
|
| 1422 |
records.append(EmbeddingRecord(str(position), sequence, X.detach().cpu()))
|
|
|
|
| 1453 |
expected = int(np.prod(shape, dtype=np.int64)) * numpy_dtype.itemsize
|
| 1454 |
if len(data) - offset != expected:
|
| 1455 |
raise ValueError("Legacy compact embedding payload length does not match shape.")
|
| 1456 |
+
array = ( # shape
|
| 1457 |
+
np.frombuffer(data, dtype=numpy_dtype, offset=offset).copy().reshape(shape)
|
| 1458 |
+
)
|
| 1459 |
+
return torch.from_numpy(array).to(dtype=target_dtype) # shape
|
| 1460 |
|
| 1461 |
try:
|
| 1462 |
loaded = torch.load(io.BytesIO(data), map_location="cpu", weights_only=True)
|
|
|
|
| 1475 |
raise ValueError(
|
| 1476 |
"Legacy raw FP32 payload length does not match fallback_shape."
|
| 1477 |
) from safe_error
|
| 1478 |
+
array = np.frombuffer(data, dtype=np.float32).copy().reshape( # fallback_shape
|
| 1479 |
+
fallback_shape
|
| 1480 |
+
)
|
| 1481 |
+
return torch.from_numpy(array) # fallback_shape
|
| 1482 |
if not isinstance(loaded, Tensor):
|
| 1483 |
raise ValueError("Legacy serialized embedding payload must contain one tensor.")
|
| 1484 |
+
return loaded.detach().cpu() # (...)
|
| 1485 |
|
| 1486 |
|
| 1487 |
def convert_legacy_sqlite(
|
fastplms/embeddings/types.py
CHANGED
|
@@ -5,7 +5,6 @@ from __future__ import annotations
|
|
| 5 |
from collections.abc import Callable, Iterator, Mapping, Sequence
|
| 6 |
from dataclasses import dataclass, field
|
| 7 |
from typing import Any, Literal, overload
|
| 8 |
-
|
| 9 |
from torch import Tensor
|
| 10 |
|
| 11 |
|
|
@@ -39,7 +38,7 @@ class LazyTensorReference:
|
|
| 39 |
|
| 40 |
if not isinstance(verify, bool):
|
| 41 |
raise TypeError("verify must be a boolean.")
|
| 42 |
-
X = self._loader()
|
| 43 |
if not isinstance(X, Tensor):
|
| 44 |
raise TypeError(f"Stored tensor loader for {self.key!r} must return a Tensor.")
|
| 45 |
if tuple(X.shape) != self.shape:
|
|
@@ -57,7 +56,7 @@ class LazyTensorReference:
|
|
| 57 |
digest = tensor_sha256(X)
|
| 58 |
if digest != self.sha256:
|
| 59 |
raise ValueError(f"Stored tensor {self.key!r} failed SHA-256 verification.")
|
| 60 |
-
return X
|
| 61 |
|
| 62 |
|
| 63 |
TensorValue = Tensor | LazyTensorReference
|
|
@@ -85,8 +84,8 @@ class EmbeddingRecord:
|
|
| 85 |
if not isinstance(verify, bool):
|
| 86 |
raise TypeError("verify must be a boolean.")
|
| 87 |
if isinstance(self.tensor, LazyTensorReference):
|
| 88 |
-
return self.tensor.load(verify=verify)
|
| 89 |
-
return self.tensor
|
| 90 |
|
| 91 |
|
| 92 |
class EmbeddingResult(Sequence[EmbeddingRecord]):
|
|
|
|
| 5 |
from collections.abc import Callable, Iterator, Mapping, Sequence
|
| 6 |
from dataclasses import dataclass, field
|
| 7 |
from typing import Any, Literal, overload
|
|
|
|
| 8 |
from torch import Tensor
|
| 9 |
|
| 10 |
|
|
|
|
| 38 |
|
| 39 |
if not isinstance(verify, bool):
|
| 40 |
raise TypeError("verify must be a boolean.")
|
| 41 |
+
X = self._loader() # self.shape
|
| 42 |
if not isinstance(X, Tensor):
|
| 43 |
raise TypeError(f"Stored tensor loader for {self.key!r} must return a Tensor.")
|
| 44 |
if tuple(X.shape) != self.shape:
|
|
|
|
| 56 |
digest = tensor_sha256(X)
|
| 57 |
if digest != self.sha256:
|
| 58 |
raise ValueError(f"Stored tensor {self.key!r} failed SHA-256 verification.")
|
| 59 |
+
return X # self.shape
|
| 60 |
|
| 61 |
|
| 62 |
TensorValue = Tensor | LazyTensorReference
|
|
|
|
| 84 |
if not isinstance(verify, bool):
|
| 85 |
raise TypeError("verify must be a boolean.")
|
| 86 |
if isinstance(self.tensor, LazyTensorReference):
|
| 87 |
+
return self.tensor.load(verify=verify) # (...)
|
| 88 |
+
return self.tensor # (...)
|
| 89 |
|
| 90 |
|
| 91 |
class EmbeddingResult(Sequence[EmbeddingRecord]):
|
fastplms/models/__init__.py
CHANGED
|
@@ -7,4 +7,5 @@ tokenizers, compile kernels, or initialize an accelerator runtime.
|
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
|
|
|
| 10 |
__all__: tuple[str, ...] = ()
|
|
|
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
+
|
| 11 |
__all__: tuple[str, ...] = ()
|
fastplms/models/e1/attention.py
CHANGED
|
@@ -3,9 +3,8 @@
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import os
|
| 6 |
-
from collections.abc import Callable
|
| 7 |
-
|
| 8 |
import torch
|
|
|
|
| 9 |
from torch.nn.attention.flex_attention import _create_sparse_block_from_block_mask
|
| 10 |
|
| 11 |
from fastplms.attention import (
|
|
@@ -23,6 +22,7 @@ from fastplms.attention import (
|
|
| 23 |
|
| 24 |
@torch.compiler.disable
|
| 25 |
def create_block_causal_mask_optimized(sequence_ids: torch.Tensor) -> BlockMask:
|
|
|
|
| 26 |
if create_block_mask is None:
|
| 27 |
raise RuntimeError("Flex Attention block-mask creation is unavailable in this environment.")
|
| 28 |
# Assumes sequence_ids is sorted in increasing order for each batch item, except for
|
|
@@ -42,6 +42,7 @@ def create_block_causal_mask_optimized(sequence_ids: torch.Tensor) -> BlockMask:
|
|
| 42 |
|
| 43 |
@torch.compiler.disable
|
| 44 |
def create_within_seq_block_mask(sequence_ids: torch.Tensor) -> BlockMask:
|
|
|
|
| 45 |
if create_block_mask is None:
|
| 46 |
raise RuntimeError("Flex Attention block-mask creation is unavailable in this environment.")
|
| 47 |
def document_mask(b, h, q_idx, kv_idx): # type: ignore[no-untyped-def]
|
|
@@ -58,17 +59,19 @@ def create_within_seq_block_mask(sequence_ids: torch.Tensor) -> BlockMask:
|
|
| 58 |
|
| 59 |
|
| 60 |
def build_within_seq_mask_4d(sequence_ids: torch.Tensor) -> torch.Tensor:
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
| 65 |
|
| 66 |
|
| 67 |
def build_block_causal_mask_4d(sequence_ids: torch.Tensor) -> torch.Tensor:
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
| 72 |
|
| 73 |
|
| 74 |
def flex_attention_func(
|
|
@@ -84,9 +87,9 @@ def flex_attention_func(
|
|
| 84 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 85 |
if score_mod is not None:
|
| 86 |
raise NotImplementedError("E1 Flex Attention does not support score_mod.")
|
| 87 |
-
query_states = query_states.transpose(1, 2).contiguous() # (
|
| 88 |
-
key_states = key_states.transpose(1, 2).contiguous() # (
|
| 89 |
-
value_states = value_states.transpose(1, 2).contiguous() # (
|
| 90 |
|
| 91 |
fn = _get_flex_attention_fn(
|
| 92 |
device=query_states.device,
|
|
@@ -97,7 +100,7 @@ def flex_attention_func(
|
|
| 97 |
)
|
| 98 |
if fn is None:
|
| 99 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 100 |
-
outputs = fn(
|
| 101 |
query_states,
|
| 102 |
key_states,
|
| 103 |
value_states,
|
|
@@ -106,19 +109,20 @@ def flex_attention_func(
|
|
| 106 |
enable_gqa=query_states.shape[1] != key_states.shape[1], # if nkv != nh
|
| 107 |
)
|
| 108 |
|
| 109 |
-
outputs = outputs.transpose(1, 2) # (
|
| 110 |
-
return outputs
|
| 111 |
|
| 112 |
|
| 113 |
def kernels_flash_attention_func(
|
| 114 |
-
query_states: torch.Tensor, # (
|
| 115 |
-
key_states: torch.Tensor, # (
|
| 116 |
-
value_states: torch.Tensor, # (
|
| 117 |
q_sequence_ids: torch.Tensor,
|
| 118 |
k_sequence_ids: torch.Tensor,
|
| 119 |
causal: bool = False,
|
| 120 |
implementation: str = "flash_attention_3",
|
| 121 |
-
) -> torch.Tensor: # (
|
|
|
|
| 122 |
_ensure_flash_kernels_loaded(implementation)
|
| 123 |
|
| 124 |
if not causal:
|
|
@@ -130,9 +134,15 @@ def kernels_flash_attention_func(
|
|
| 130 |
indices_q,
|
| 131 |
(cu_seqlens_q, cu_seqlens_k),
|
| 132 |
(max_seqlen_in_batch_q, max_seqlen_in_batch_k),
|
| 133 |
-
) = _unpad_input(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
-
attn_output_unpad = _kernels_flash_varlen_forward(
|
| 136 |
query_states,
|
| 137 |
key_states,
|
| 138 |
value_states,
|
|
@@ -143,14 +153,19 @@ def kernels_flash_attention_func(
|
|
| 143 |
causal=False,
|
| 144 |
implementation=implementation,
|
| 145 |
)
|
| 146 |
-
attn_output = pad_input(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
else:
|
| 149 |
-
attn_output = _kernels_flash_forward(
|
| 150 |
query_states, key_states, value_states, causal=True, implementation=implementation
|
| 151 |
)
|
| 152 |
|
| 153 |
-
return attn_output
|
| 154 |
|
| 155 |
|
| 156 |
def block_min_max_seq_ids(
|
|
@@ -159,7 +174,8 @@ def block_min_max_seq_ids(
|
|
| 159 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 160 |
"""Map each physical attention block to its first and last sequence."""
|
| 161 |
|
| 162 |
-
|
|
|
|
| 163 |
block_count = int(
|
| 164 |
torch.div(
|
| 165 |
total_tokens + block_size - 1,
|
|
@@ -167,22 +183,22 @@ def block_min_max_seq_ids(
|
|
| 167 |
rounding_mode="floor",
|
| 168 |
).item()
|
| 169 |
)
|
| 170 |
-
padded_tokens = block_count * block_size - total_tokens
|
| 171 |
-
lengths_with_tail = torch.cat(
|
| 172 |
(sequence_lengths, padded_tokens.to(sequence_lengths).reshape(1)),
|
| 173 |
)
|
| 174 |
-
sequence_ends = lengths_with_tail.to(torch.long).cumsum(dim=0)
|
| 175 |
-
block_starts = torch.arange(
|
| 176 |
start=0,
|
| 177 |
end=block_count * block_size,
|
| 178 |
step=block_size,
|
| 179 |
dtype=torch.long,
|
| 180 |
device=sequence_lengths.device,
|
| 181 |
)
|
| 182 |
-
block_last_tokens = block_starts + block_size - 1
|
| 183 |
-
first_sequence = torch.searchsorted(sequence_ends, block_starts, right=True)
|
| 184 |
-
last_sequence = torch.searchsorted(sequence_ends, block_last_tokens, right=True)
|
| 185 |
-
return first_sequence, last_sequence
|
| 186 |
|
| 187 |
|
| 188 |
def get_overlapping_blocks(
|
|
@@ -191,32 +207,42 @@ def get_overlapping_blocks(
|
|
| 191 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 192 |
"""Classify query/key block pairs as full, partial, or disjoint."""
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
|
| 204 |
def _document_ids(sequence_lengths: torch.Tensor) -> torch.Tensor:
|
| 205 |
-
|
|
|
|
| 206 |
sequence_lengths.numel(),
|
| 207 |
device=sequence_lengths.device,
|
| 208 |
dtype=torch.long,
|
| 209 |
)
|
| 210 |
-
return sequence_numbers.repeat_interleave(sequence_lengths.to(torch.long))
|
| 211 |
|
| 212 |
|
| 213 |
@torch.compiler.disable
|
| 214 |
def direct_block_mask(q_lengths: torch.Tensor, k_lengths: torch.Tensor) -> BlockMask:
|
| 215 |
"""Build a packed-sequence mask from preclassified sparse blocks."""
|
| 216 |
|
| 217 |
-
full, partial = get_overlapping_blocks(q_lengths, k_lengths)
|
| 218 |
-
q_document = _document_ids(q_lengths)
|
| 219 |
-
k_document = _document_ids(k_lengths)
|
| 220 |
|
| 221 |
def same_document(
|
| 222 |
_batch: torch.Tensor,
|
|
@@ -239,8 +265,8 @@ def direct_block_mask(q_lengths: torch.Tensor, k_lengths: torch.Tensor) -> Block
|
|
| 239 |
def doc_id_mask(q_lengths: torch.Tensor, k_lengths: torch.Tensor) -> BlockMask:
|
| 240 |
if create_block_mask is None:
|
| 241 |
raise RuntimeError("Flex Attention block-mask creation is unavailable in this environment.")
|
| 242 |
-
q_document = _document_ids(q_lengths)
|
| 243 |
-
k_document = _document_ids(k_lengths)
|
| 244 |
|
| 245 |
def same_document(
|
| 246 |
_batch: torch.Tensor,
|
|
@@ -268,6 +294,8 @@ def varlen_flex_attention_func(
|
|
| 268 |
q_sequence_ids: torch.Tensor,
|
| 269 |
k_sequence_ids: torch.Tensor,
|
| 270 |
) -> torch.Tensor:
|
|
|
|
|
|
|
| 271 |
if flex_attention is None:
|
| 272 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 273 |
batch_size, q_len = query_states.shape[0], query_states.shape[1]
|
|
@@ -278,14 +306,20 @@ def varlen_flex_attention_func(
|
|
| 278 |
indices_q,
|
| 279 |
(cu_seqlens_q, cu_seqlens_k),
|
| 280 |
(_max_seqlen_in_batch_q, _max_seqlen_in_batch_k),
|
| 281 |
-
) = _unpad_input(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
-
query_states = query_states.unsqueeze(0).transpose(1, 2).contiguous()
|
| 284 |
-
key_states = key_states.unsqueeze(0).transpose(1, 2).contiguous()
|
| 285 |
-
value_states = value_states.unsqueeze(0).transpose(1, 2).contiguous()
|
| 286 |
|
| 287 |
-
seqlens_q = cu_seqlens_q[1:] - cu_seqlens_q[:-1]
|
| 288 |
-
seqlens_k = cu_seqlens_k[1:] - cu_seqlens_k[:-1]
|
| 289 |
block_mask = block_mask_creator(seqlens_q, seqlens_k)
|
| 290 |
|
| 291 |
packed_lengths = (
|
|
@@ -302,7 +336,7 @@ def varlen_flex_attention_func(
|
|
| 302 |
)
|
| 303 |
if fn is None:
|
| 304 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 305 |
-
attn_output_unpad = fn(
|
| 306 |
query_states,
|
| 307 |
key_states,
|
| 308 |
value_states,
|
|
@@ -310,39 +344,44 @@ def varlen_flex_attention_func(
|
|
| 310 |
enable_gqa=query_states.shape[1] != key_states.shape[1],
|
| 311 |
)
|
| 312 |
|
| 313 |
-
attn_output = pad_input(
|
| 314 |
attn_output_unpad.transpose(1, 2).squeeze(0), indices_q, batch_size, q_len
|
| 315 |
)
|
| 316 |
|
| 317 |
-
return attn_output
|
| 318 |
|
| 319 |
|
| 320 |
def _get_unpad_data(sequence_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, int]:
|
| 321 |
"""Return packed indices and run lengths for the non-padding sequence IDs."""
|
| 322 |
|
| 323 |
-
|
| 324 |
-
|
|
|
|
| 325 |
if non_pad_indices.numel() == 0:
|
| 326 |
raise ValueError("Packed attention requires at least one non-padding token.")
|
| 327 |
|
| 328 |
-
valid_ids = flat_ids.index_select(0, non_pad_indices)
|
| 329 |
-
row_ids = torch.div(
|
| 330 |
non_pad_indices,
|
| 331 |
sequence_ids.shape[1],
|
| 332 |
rounding_mode="floor",
|
| 333 |
)
|
| 334 |
-
run_starts = torch.ones_like(valid_ids, dtype=torch.bool)
|
| 335 |
run_starts[1:] = (valid_ids[1:] != valid_ids[:-1]) | (row_ids[1:] != row_ids[:-1])
|
| 336 |
-
start_indices = torch.where(run_starts)[0]
|
| 337 |
-
end_indices = torch.cat(
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
| 340 |
(
|
| 341 |
torch.zeros(1, dtype=torch.int32, device=sequence_ids.device),
|
| 342 |
sequence_lengths.cumsum(dim=0, dtype=torch.int32),
|
| 343 |
),
|
| 344 |
)
|
| 345 |
-
return non_pad_indices, cumulative_lengths, int(
|
|
|
|
|
|
|
| 346 |
|
| 347 |
|
| 348 |
def _unpad_input(
|
|
@@ -359,6 +398,8 @@ def _unpad_input(
|
|
| 359 |
tuple[torch.Tensor, torch.Tensor],
|
| 360 |
tuple[int, int],
|
| 361 |
]:
|
|
|
|
|
|
|
| 362 |
for name, layer in (
|
| 363 |
("query_layer", query_layer),
|
| 364 |
("key_layer", key_layer),
|
|
@@ -402,12 +443,14 @@ def _unpad_input(
|
|
| 402 |
f"{query_length} > {kv_seq_len}"
|
| 403 |
)
|
| 404 |
|
| 405 |
-
indices_k, cu_seqlens_k, max_seqlen_in_batch_k = _get_unpad_data(
|
|
|
|
|
|
|
| 406 |
|
| 407 |
-
key_layer = index_first_axis(
|
| 408 |
key_layer.reshape(batch_size * kv_seq_len, num_heads, head_dim), indices_k
|
| 409 |
)
|
| 410 |
-
value_layer = index_first_axis(
|
| 411 |
value_layer.reshape(batch_size * kv_seq_len, num_heads, head_dim), indices_k
|
| 412 |
)
|
| 413 |
|
|
@@ -416,9 +459,10 @@ def _unpad_input(
|
|
| 416 |
cu_seqlens_q = cu_seqlens_k
|
| 417 |
max_seqlen_in_batch_q = max_seqlen_in_batch_k
|
| 418 |
else:
|
|
|
|
| 419 |
indices_q, cu_seqlens_q, max_seqlen_in_batch_q = _get_unpad_data(q_sequence_ids)
|
| 420 |
|
| 421 |
-
query_layer = index_first_axis(
|
| 422 |
query_layer.reshape(batch_size * query_length, num_q_heads, head_dim), indices_q
|
| 423 |
)
|
| 424 |
|
|
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import os
|
|
|
|
|
|
|
| 6 |
import torch
|
| 7 |
+
from collections.abc import Callable
|
| 8 |
from torch.nn.attention.flex_attention import _create_sparse_block_from_block_mask
|
| 9 |
|
| 10 |
from fastplms.attention import (
|
|
|
|
| 22 |
|
| 23 |
@torch.compiler.disable
|
| 24 |
def create_block_causal_mask_optimized(sequence_ids: torch.Tensor) -> BlockMask:
|
| 25 |
+
# sequence_ids: (b, l)
|
| 26 |
if create_block_mask is None:
|
| 27 |
raise RuntimeError("Flex Attention block-mask creation is unavailable in this environment.")
|
| 28 |
# Assumes sequence_ids is sorted in increasing order for each batch item, except for
|
|
|
|
| 42 |
|
| 43 |
@torch.compiler.disable
|
| 44 |
def create_within_seq_block_mask(sequence_ids: torch.Tensor) -> BlockMask:
|
| 45 |
+
# sequence_ids: (b, l)
|
| 46 |
if create_block_mask is None:
|
| 47 |
raise RuntimeError("Flex Attention block-mask creation is unavailable in this environment.")
|
| 48 |
def document_mask(b, h, q_idx, kv_idx): # type: ignore[no-untyped-def]
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
def build_within_seq_mask_4d(sequence_ids: torch.Tensor) -> torch.Tensor:
|
| 62 |
+
# sequence_ids: (b, l)
|
| 63 |
+
not_pad = sequence_ids != -1 # (b, l)
|
| 64 |
+
same_seq = sequence_ids.unsqueeze(-1) == sequence_ids.unsqueeze(-2) # (b, l, l)
|
| 65 |
+
valid = not_pad.unsqueeze(-1) & not_pad.unsqueeze(-2) # (b, l, l)
|
| 66 |
+
return (same_seq & valid).unsqueeze(1) # (b, 1, l, l)
|
| 67 |
|
| 68 |
|
| 69 |
def build_block_causal_mask_4d(sequence_ids: torch.Tensor) -> torch.Tensor:
|
| 70 |
+
# sequence_ids: (b, l)
|
| 71 |
+
not_pad = sequence_ids != -1 # (b, l)
|
| 72 |
+
causal = sequence_ids.unsqueeze(-1) >= sequence_ids.unsqueeze(-2) # (b, l, l)
|
| 73 |
+
valid = not_pad.unsqueeze(-1) & not_pad.unsqueeze(-2) # (b, l, l)
|
| 74 |
+
return (causal & valid).unsqueeze(1) # (b, 1, l, l)
|
| 75 |
|
| 76 |
|
| 77 |
def flex_attention_func(
|
|
|
|
| 87 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 88 |
if score_mod is not None:
|
| 89 |
raise NotImplementedError("E1 Flex Attention does not support score_mod.")
|
| 90 |
+
query_states = query_states.transpose(1, 2).contiguous() # (b, h, l, d)
|
| 91 |
+
key_states = key_states.transpose(1, 2).contiguous() # (b, h_kv, l, d)
|
| 92 |
+
value_states = value_states.transpose(1, 2).contiguous() # (b, h_kv, l, d)
|
| 93 |
|
| 94 |
fn = _get_flex_attention_fn(
|
| 95 |
device=query_states.device,
|
|
|
|
| 100 |
)
|
| 101 |
if fn is None:
|
| 102 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 103 |
+
outputs = fn( # (b, h, l, d)
|
| 104 |
query_states,
|
| 105 |
key_states,
|
| 106 |
value_states,
|
|
|
|
| 109 |
enable_gqa=query_states.shape[1] != key_states.shape[1], # if nkv != nh
|
| 110 |
)
|
| 111 |
|
| 112 |
+
outputs = outputs.transpose(1, 2) # (b, l, h, d)
|
| 113 |
+
return outputs # (b, l, h, d)
|
| 114 |
|
| 115 |
|
| 116 |
def kernels_flash_attention_func(
|
| 117 |
+
query_states: torch.Tensor, # (b, l_q, h, d)
|
| 118 |
+
key_states: torch.Tensor, # (b, l_kv, h_kv, d)
|
| 119 |
+
value_states: torch.Tensor, # (b, l_kv, h_kv, d)
|
| 120 |
q_sequence_ids: torch.Tensor,
|
| 121 |
k_sequence_ids: torch.Tensor,
|
| 122 |
causal: bool = False,
|
| 123 |
implementation: str = "flash_attention_3",
|
| 124 |
+
) -> torch.Tensor: # (b, l_q, h, d)
|
| 125 |
+
# q_sequence_ids: (b, l_q); k_sequence_ids: (b, l_kv)
|
| 126 |
_ensure_flash_kernels_loaded(implementation)
|
| 127 |
|
| 128 |
if not causal:
|
|
|
|
| 134 |
indices_q,
|
| 135 |
(cu_seqlens_q, cu_seqlens_k),
|
| 136 |
(max_seqlen_in_batch_q, max_seqlen_in_batch_k),
|
| 137 |
+
) = _unpad_input( # Q: (t_q, h, d); K/V: (t_kv, h_kv, d)
|
| 138 |
+
query_states,
|
| 139 |
+
key_states,
|
| 140 |
+
value_states,
|
| 141 |
+
q_sequence_ids,
|
| 142 |
+
k_sequence_ids,
|
| 143 |
+
)
|
| 144 |
|
| 145 |
+
attn_output_unpad = _kernels_flash_varlen_forward( # (t_q, h, d)
|
| 146 |
query_states,
|
| 147 |
key_states,
|
| 148 |
value_states,
|
|
|
|
| 153 |
causal=False,
|
| 154 |
implementation=implementation,
|
| 155 |
)
|
| 156 |
+
attn_output = pad_input( # (b, l_q, h, d)
|
| 157 |
+
attn_output_unpad,
|
| 158 |
+
indices_q,
|
| 159 |
+
batch_size,
|
| 160 |
+
q_len,
|
| 161 |
+
)
|
| 162 |
|
| 163 |
else:
|
| 164 |
+
attn_output = _kernels_flash_forward( # (b, l_q, h, d)
|
| 165 |
query_states, key_states, value_states, causal=True, implementation=implementation
|
| 166 |
)
|
| 167 |
|
| 168 |
+
return attn_output # (b, l_q, h, d)
|
| 169 |
|
| 170 |
|
| 171 |
def block_min_max_seq_ids(
|
|
|
|
| 174 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 175 |
"""Map each physical attention block to its first and last sequence."""
|
| 176 |
|
| 177 |
+
# sequence_lengths: (n,)
|
| 178 |
+
total_tokens = sequence_lengths.sum() # ()
|
| 179 |
block_count = int(
|
| 180 |
torch.div(
|
| 181 |
total_tokens + block_size - 1,
|
|
|
|
| 183 |
rounding_mode="floor",
|
| 184 |
).item()
|
| 185 |
)
|
| 186 |
+
padded_tokens = block_count * block_size - total_tokens # ()
|
| 187 |
+
lengths_with_tail = torch.cat( # (n + 1,)
|
| 188 |
(sequence_lengths, padded_tokens.to(sequence_lengths).reshape(1)),
|
| 189 |
)
|
| 190 |
+
sequence_ends = lengths_with_tail.to(torch.long).cumsum(dim=0) # (n + 1,)
|
| 191 |
+
block_starts = torch.arange( # (n_blocks,)
|
| 192 |
start=0,
|
| 193 |
end=block_count * block_size,
|
| 194 |
step=block_size,
|
| 195 |
dtype=torch.long,
|
| 196 |
device=sequence_lengths.device,
|
| 197 |
)
|
| 198 |
+
block_last_tokens = block_starts + block_size - 1 # (n_blocks,)
|
| 199 |
+
first_sequence = torch.searchsorted(sequence_ends, block_starts, right=True) # (n_blocks,)
|
| 200 |
+
last_sequence = torch.searchsorted(sequence_ends, block_last_tokens, right=True) # (n_blocks,)
|
| 201 |
+
return first_sequence, last_sequence # (n_blocks,), (n_blocks,)
|
| 202 |
|
| 203 |
|
| 204 |
def get_overlapping_blocks(
|
|
|
|
| 207 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 208 |
"""Classify query/key block pairs as full, partial, or disjoint."""
|
| 209 |
|
| 210 |
+
# q_lengths: (n_q,); k_lengths: (n_k,)
|
| 211 |
+
q_first, q_last = block_min_max_seq_ids(q_lengths) # (q_blocks,), (q_blocks,)
|
| 212 |
+
k_first, k_last = block_min_max_seq_ids(k_lengths) # (k_blocks,), (k_blocks,)
|
| 213 |
+
intersection_start = torch.maximum( # (q_blocks, k_blocks)
|
| 214 |
+
q_first[:, None],
|
| 215 |
+
k_first[None, :],
|
| 216 |
+
)
|
| 217 |
+
intersection_end = torch.minimum( # (q_blocks, k_blocks)
|
| 218 |
+
q_last[:, None],
|
| 219 |
+
k_last[None, :],
|
| 220 |
+
)
|
| 221 |
+
intersects = intersection_start <= intersection_end # (q_blocks, k_blocks)
|
| 222 |
+
both_blocks_are_single_sequence = ( # (q_blocks, k_blocks)
|
| 223 |
+
(q_first == q_last)[:, None] & (k_first == k_last)[None, :]
|
| 224 |
+
)
|
| 225 |
+
full_blocks = intersects & both_blocks_are_single_sequence # (q_blocks, k_blocks)
|
| 226 |
+
return full_blocks, intersects & ~both_blocks_are_single_sequence # both (q_blocks, k_blocks)
|
| 227 |
|
| 228 |
|
| 229 |
def _document_ids(sequence_lengths: torch.Tensor) -> torch.Tensor:
|
| 230 |
+
# sequence_lengths: (n,)
|
| 231 |
+
sequence_numbers = torch.arange( # (n,)
|
| 232 |
sequence_lengths.numel(),
|
| 233 |
device=sequence_lengths.device,
|
| 234 |
dtype=torch.long,
|
| 235 |
)
|
| 236 |
+
return sequence_numbers.repeat_interleave(sequence_lengths.to(torch.long)) # (t,)
|
| 237 |
|
| 238 |
|
| 239 |
@torch.compiler.disable
|
| 240 |
def direct_block_mask(q_lengths: torch.Tensor, k_lengths: torch.Tensor) -> BlockMask:
|
| 241 |
"""Build a packed-sequence mask from preclassified sparse blocks."""
|
| 242 |
|
| 243 |
+
full, partial = get_overlapping_blocks(q_lengths, k_lengths) # both (q_blocks, k_blocks)
|
| 244 |
+
q_document = _document_ids(q_lengths) # (t_q,)
|
| 245 |
+
k_document = _document_ids(k_lengths) # (t_k,)
|
| 246 |
|
| 247 |
def same_document(
|
| 248 |
_batch: torch.Tensor,
|
|
|
|
| 265 |
def doc_id_mask(q_lengths: torch.Tensor, k_lengths: torch.Tensor) -> BlockMask:
|
| 266 |
if create_block_mask is None:
|
| 267 |
raise RuntimeError("Flex Attention block-mask creation is unavailable in this environment.")
|
| 268 |
+
q_document = _document_ids(q_lengths) # (t_q,)
|
| 269 |
+
k_document = _document_ids(k_lengths) # (t_k,)
|
| 270 |
|
| 271 |
def same_document(
|
| 272 |
_batch: torch.Tensor,
|
|
|
|
| 294 |
q_sequence_ids: torch.Tensor,
|
| 295 |
k_sequence_ids: torch.Tensor,
|
| 296 |
) -> torch.Tensor:
|
| 297 |
+
# query_states: (b, l_q, h, d); key_states, value_states: (b, l_kv, h_kv, d)
|
| 298 |
+
# q_sequence_ids: (b, l_q); k_sequence_ids: (b, l_kv)
|
| 299 |
if flex_attention is None:
|
| 300 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 301 |
batch_size, q_len = query_states.shape[0], query_states.shape[1]
|
|
|
|
| 306 |
indices_q,
|
| 307 |
(cu_seqlens_q, cu_seqlens_k),
|
| 308 |
(_max_seqlen_in_batch_q, _max_seqlen_in_batch_k),
|
| 309 |
+
) = _unpad_input( # Q: (t_q, h, d); K/V: (t_kv, h_kv, d)
|
| 310 |
+
query_states,
|
| 311 |
+
key_states,
|
| 312 |
+
value_states,
|
| 313 |
+
q_sequence_ids,
|
| 314 |
+
k_sequence_ids,
|
| 315 |
+
)
|
| 316 |
|
| 317 |
+
query_states = query_states.unsqueeze(0).transpose(1, 2).contiguous() # (1, h, t_q, d)
|
| 318 |
+
key_states = key_states.unsqueeze(0).transpose(1, 2).contiguous() # (1, h_kv, t_kv, d)
|
| 319 |
+
value_states = value_states.unsqueeze(0).transpose(1, 2).contiguous() # (1, h_kv, t_kv, d)
|
| 320 |
|
| 321 |
+
seqlens_q = cu_seqlens_q[1:] - cu_seqlens_q[:-1] # (n,)
|
| 322 |
+
seqlens_k = cu_seqlens_k[1:] - cu_seqlens_k[:-1] # (n,)
|
| 323 |
block_mask = block_mask_creator(seqlens_q, seqlens_k)
|
| 324 |
|
| 325 |
packed_lengths = (
|
|
|
|
| 336 |
)
|
| 337 |
if fn is None:
|
| 338 |
raise RuntimeError("Flex Attention is not available in this environment.")
|
| 339 |
+
attn_output_unpad = fn( # (1, h, t_q, d)
|
| 340 |
query_states,
|
| 341 |
key_states,
|
| 342 |
value_states,
|
|
|
|
| 344 |
enable_gqa=query_states.shape[1] != key_states.shape[1],
|
| 345 |
)
|
| 346 |
|
| 347 |
+
attn_output = pad_input( # (b, l_q, h, d)
|
| 348 |
attn_output_unpad.transpose(1, 2).squeeze(0), indices_q, batch_size, q_len
|
| 349 |
)
|
| 350 |
|
| 351 |
+
return attn_output # (b, l_q, h, d)
|
| 352 |
|
| 353 |
|
| 354 |
def _get_unpad_data(sequence_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, int]:
|
| 355 |
"""Return packed indices and run lengths for the non-padding sequence IDs."""
|
| 356 |
|
| 357 |
+
# sequence_ids: (b, l)
|
| 358 |
+
flat_ids = sequence_ids.reshape(-1) # (b * l,)
|
| 359 |
+
non_pad_indices = torch.where(flat_ids.ne(-1))[0] # (t,)
|
| 360 |
if non_pad_indices.numel() == 0:
|
| 361 |
raise ValueError("Packed attention requires at least one non-padding token.")
|
| 362 |
|
| 363 |
+
valid_ids = flat_ids.index_select(0, non_pad_indices) # (t,)
|
| 364 |
+
row_ids = torch.div( # (t,)
|
| 365 |
non_pad_indices,
|
| 366 |
sequence_ids.shape[1],
|
| 367 |
rounding_mode="floor",
|
| 368 |
)
|
| 369 |
+
run_starts = torch.ones_like(valid_ids, dtype=torch.bool) # (t,)
|
| 370 |
run_starts[1:] = (valid_ids[1:] != valid_ids[:-1]) | (row_ids[1:] != row_ids[:-1])
|
| 371 |
+
start_indices = torch.where(run_starts)[0] # (n,)
|
| 372 |
+
end_indices = torch.cat( # (n,)
|
| 373 |
+
(start_indices[1:], start_indices.new_tensor([valid_ids.numel()]))
|
| 374 |
+
)
|
| 375 |
+
sequence_lengths = end_indices - start_indices # (n,)
|
| 376 |
+
cumulative_lengths = torch.cat( # (n + 1,)
|
| 377 |
(
|
| 378 |
torch.zeros(1, dtype=torch.int32, device=sequence_ids.device),
|
| 379 |
sequence_lengths.cumsum(dim=0, dtype=torch.int32),
|
| 380 |
),
|
| 381 |
)
|
| 382 |
+
return non_pad_indices, cumulative_lengths, int( # (t,), (n + 1,), scalar
|
| 383 |
+
sequence_lengths.max().item()
|
| 384 |
+
)
|
| 385 |
|
| 386 |
|
| 387 |
def _unpad_input(
|
|
|
|
| 398 |
tuple[torch.Tensor, torch.Tensor],
|
| 399 |
tuple[int, int],
|
| 400 |
]:
|
| 401 |
+
# query_layer: (b, l_q, h, d); key_layer, value_layer: (b, l_kv, h_kv, d)
|
| 402 |
+
# q_sequence_ids: (b, l_q); k_sequence_ids: (b, l_kv)
|
| 403 |
for name, layer in (
|
| 404 |
("query_layer", query_layer),
|
| 405 |
("key_layer", key_layer),
|
|
|
|
| 443 |
f"{query_length} > {kv_seq_len}"
|
| 444 |
)
|
| 445 |
|
| 446 |
+
indices_k, cu_seqlens_k, max_seqlen_in_batch_k = _get_unpad_data( # (t_kv,), (n + 1,), scalar
|
| 447 |
+
k_sequence_ids
|
| 448 |
+
)
|
| 449 |
|
| 450 |
+
key_layer = index_first_axis( # (t_kv, h_kv, d)
|
| 451 |
key_layer.reshape(batch_size * kv_seq_len, num_heads, head_dim), indices_k
|
| 452 |
)
|
| 453 |
+
value_layer = index_first_axis( # (t_kv, h_kv, d)
|
| 454 |
value_layer.reshape(batch_size * kv_seq_len, num_heads, head_dim), indices_k
|
| 455 |
)
|
| 456 |
|
|
|
|
| 459 |
cu_seqlens_q = cu_seqlens_k
|
| 460 |
max_seqlen_in_batch_q = max_seqlen_in_batch_k
|
| 461 |
else:
|
| 462 |
+
# (t_q,), (n + 1,), scalar
|
| 463 |
indices_q, cu_seqlens_q, max_seqlen_in_batch_q = _get_unpad_data(q_sequence_ids)
|
| 464 |
|
| 465 |
+
query_layer = index_first_axis( # (t_q, h, d)
|
| 466 |
query_layer.reshape(batch_size * query_length, num_q_heads, head_dim), indices_q
|
| 467 |
)
|
| 468 |
|
fastplms/models/e1/cache.py
CHANGED
|
@@ -2,9 +2,8 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
-
from typing import Any
|
| 6 |
-
|
| 7 |
import torch
|
|
|
|
| 8 |
from transformers.modeling_outputs import ModelOutput
|
| 9 |
from transformers.utils import logging
|
| 10 |
|
|
@@ -44,9 +43,9 @@ class DynamicCache:
|
|
| 44 |
tuple[`torch.Tensor`, `torch.Tensor`]: Cached K and V, each with shape
|
| 45 |
(b, l, h, d).
|
| 46 |
"""
|
| 47 |
-
#
|
| 48 |
if len(self.key_cache) <= layer_idx:
|
| 49 |
-
#
|
| 50 |
for _ in range(len(self.key_cache), layer_idx):
|
| 51 |
self.key_cache.append(torch.tensor([]))
|
| 52 |
self.value_cache.append(torch.tensor([]))
|
|
@@ -60,12 +59,18 @@ class DynamicCache:
|
|
| 60 |
self.key_cache[layer_idx] = key_states
|
| 61 |
self.value_cache[layer_idx] = value_states
|
| 62 |
else:
|
| 63 |
-
self.key_cache[layer_idx] = torch.cat(
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
| 65 |
[self.value_cache[layer_idx], value_states], dim=1
|
| 66 |
)
|
| 67 |
|
| 68 |
-
return
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
def get_seq_length(self, layer_idx: int = 0) -> int:
|
| 71 |
"""Return the cached sequence length for one layer."""
|
|
@@ -95,19 +100,22 @@ class DynamicCache:
|
|
| 95 |
"""Repeat the cache `repeats` times in the batch dimension. Used in contrastive search."""
|
| 96 |
for layer_idx in range(len(self.key_cache)):
|
| 97 |
if self.key_cache[layer_idx].numel():
|
|
|
|
| 98 |
self.key_cache[layer_idx] = self.key_cache[layer_idx].repeat_interleave(
|
| 99 |
repeats, dim=0
|
| 100 |
)
|
| 101 |
self.value_cache[layer_idx] = self.value_cache[layer_idx].repeat_interleave(
|
| 102 |
repeats, dim=0
|
| 103 |
-
)
|
| 104 |
|
| 105 |
-
def batch_select_indices(self, indices: torch.Tensor) -> None:
|
| 106 |
"""Keep selected rows of the cache batch dimension."""
|
| 107 |
for layer_idx in range(len(self.key_cache)):
|
| 108 |
if self.key_cache[layer_idx].numel():
|
| 109 |
-
self.key_cache[layer_idx] = self.key_cache[layer_idx][indices, ...]
|
| 110 |
-
self.value_cache[layer_idx] = self.value_cache[layer_idx][
|
|
|
|
|
|
|
| 111 |
|
| 112 |
|
| 113 |
class KVCache:
|
|
@@ -158,7 +166,7 @@ class KVCache:
|
|
| 158 |
)
|
| 159 |
return
|
| 160 |
|
| 161 |
-
batch_size = batch["input_ids"].shape[0]
|
| 162 |
|
| 163 |
unique_context = contexts[0]
|
| 164 |
unique_context_len = context_lens[0]
|
|
@@ -171,10 +179,10 @@ class KVCache:
|
|
| 171 |
past_key_values = self.cache_dict[unique_context]
|
| 172 |
batch["past_key_values"] = past_key_values
|
| 173 |
|
| 174 |
-
#
|
| 175 |
for field_name in self.tensor_input_field_names:
|
| 176 |
if batch.get(field_name) is not None:
|
| 177 |
-
batch[field_name] = batch[field_name][:, unique_context_len:]
|
| 178 |
|
| 179 |
def after_forward(self, batch: dict[str, Any], outputs: ModelOutput) -> None:
|
| 180 |
contexts = batch.get("context")
|
|
@@ -209,18 +217,21 @@ class KVCache:
|
|
| 209 |
self.cache_dict[unique_context] = past_key_values
|
| 210 |
self.cache_queue.append(unique_context)
|
| 211 |
|
| 212 |
-
#
|
| 213 |
for field_name in self.tensor_input_field_names:
|
| 214 |
if field_name in batch and batch[field_name] is not None:
|
| 215 |
-
batch[field_name] = batch[field_name][
|
|
|
|
|
|
|
| 216 |
|
| 217 |
-
# Remove context from the output fields
|
| 218 |
for field_name in self.tensor_output_field_names:
|
| 219 |
if field_name in outputs and outputs[field_name] is not None:
|
| 220 |
-
outputs[field_name] = outputs[field_name][
|
|
|
|
|
|
|
| 221 |
if "hidden_states" in outputs and outputs["hidden_states"] is not None:
|
| 222 |
hidden_states = outputs["hidden_states"]
|
| 223 |
-
sliced_hidden_states = tuple(
|
| 224 |
hidden_state[:, unique_context_len:] for hidden_state in hidden_states
|
| 225 |
)
|
| 226 |
outputs["hidden_states"] = sliced_hidden_states
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
|
|
|
| 5 |
import torch
|
| 6 |
+
from typing import Any
|
| 7 |
from transformers.modeling_outputs import ModelOutput
|
| 8 |
from transformers.utils import logging
|
| 9 |
|
|
|
|
| 43 |
tuple[`torch.Tensor`, `torch.Tensor`]: Cached K and V, each with shape
|
| 44 |
(b, l, h, d).
|
| 45 |
"""
|
| 46 |
+
# key_states, value_states: (b, l_new, h, d)
|
| 47 |
if len(self.key_cache) <= layer_idx:
|
| 48 |
+
# Empty tensors preserve skipped layer indices until those layers receive state.
|
| 49 |
for _ in range(len(self.key_cache), layer_idx):
|
| 50 |
self.key_cache.append(torch.tensor([]))
|
| 51 |
self.value_cache.append(torch.tensor([]))
|
|
|
|
| 59 |
self.key_cache[layer_idx] = key_states
|
| 60 |
self.value_cache[layer_idx] = value_states
|
| 61 |
else:
|
| 62 |
+
self.key_cache[layer_idx] = torch.cat( # (b, l_cached + l_new, h, d)
|
| 63 |
+
[self.key_cache[layer_idx], key_states],
|
| 64 |
+
dim=1,
|
| 65 |
+
)
|
| 66 |
+
self.value_cache[layer_idx] = torch.cat( # (b, l_cached + l_new, h, d)
|
| 67 |
[self.value_cache[layer_idx], value_states], dim=1
|
| 68 |
)
|
| 69 |
|
| 70 |
+
return ( # (b, l_total, h, d), (b, l_total, h, d)
|
| 71 |
+
self.key_cache[layer_idx],
|
| 72 |
+
self.value_cache[layer_idx],
|
| 73 |
+
)
|
| 74 |
|
| 75 |
def get_seq_length(self, layer_idx: int = 0) -> int:
|
| 76 |
"""Return the cached sequence length for one layer."""
|
|
|
|
| 100 |
"""Repeat the cache `repeats` times in the batch dimension. Used in contrastive search."""
|
| 101 |
for layer_idx in range(len(self.key_cache)):
|
| 102 |
if self.key_cache[layer_idx].numel():
|
| 103 |
+
# (b * repeats, l, h, d)
|
| 104 |
self.key_cache[layer_idx] = self.key_cache[layer_idx].repeat_interleave(
|
| 105 |
repeats, dim=0
|
| 106 |
)
|
| 107 |
self.value_cache[layer_idx] = self.value_cache[layer_idx].repeat_interleave(
|
| 108 |
repeats, dim=0
|
| 109 |
+
) # (b * repeats, l, h, d)
|
| 110 |
|
| 111 |
+
def batch_select_indices(self, indices: torch.Tensor | list[int]) -> None:
|
| 112 |
"""Keep selected rows of the cache batch dimension."""
|
| 113 |
for layer_idx in range(len(self.key_cache)):
|
| 114 |
if self.key_cache[layer_idx].numel():
|
| 115 |
+
self.key_cache[layer_idx] = self.key_cache[layer_idx][indices, ...] # (n, l, h, d)
|
| 116 |
+
self.value_cache[layer_idx] = self.value_cache[layer_idx][
|
| 117 |
+
indices, ...
|
| 118 |
+
] # (n, l, h, d)
|
| 119 |
|
| 120 |
|
| 121 |
class KVCache:
|
|
|
|
| 166 |
)
|
| 167 |
return
|
| 168 |
|
| 169 |
+
batch_size = batch["input_ids"].shape[0] # b
|
| 170 |
|
| 171 |
unique_context = contexts[0]
|
| 172 |
unique_context_len = context_lens[0]
|
|
|
|
| 179 |
past_key_values = self.cache_dict[unique_context]
|
| 180 |
batch["past_key_values"] = past_key_values
|
| 181 |
|
| 182 |
+
# A cached prefix leaves only query-suffix tokens for the model call.
|
| 183 |
for field_name in self.tensor_input_field_names:
|
| 184 |
if batch.get(field_name) is not None:
|
| 185 |
+
batch[field_name] = batch[field_name][:, unique_context_len:] # (b, l_suffix, ...)
|
| 186 |
|
| 187 |
def after_forward(self, batch: dict[str, Any], outputs: ModelOutput) -> None:
|
| 188 |
contexts = batch.get("context")
|
|
|
|
| 217 |
self.cache_dict[unique_context] = past_key_values
|
| 218 |
self.cache_queue.append(unique_context)
|
| 219 |
|
| 220 |
+
# The first uncached call returns the full sequence; expose its query suffix.
|
| 221 |
for field_name in self.tensor_input_field_names:
|
| 222 |
if field_name in batch and batch[field_name] is not None:
|
| 223 |
+
batch[field_name] = batch[field_name][
|
| 224 |
+
:, unique_context_len:
|
| 225 |
+
] # (b, l_suffix, ...)
|
| 226 |
|
|
|
|
| 227 |
for field_name in self.tensor_output_field_names:
|
| 228 |
if field_name in outputs and outputs[field_name] is not None:
|
| 229 |
+
outputs[field_name] = outputs[field_name][
|
| 230 |
+
:, unique_context_len:
|
| 231 |
+
] # (b, l_suffix, ...)
|
| 232 |
if "hidden_states" in outputs and outputs["hidden_states"] is not None:
|
| 233 |
hidden_states = outputs["hidden_states"]
|
| 234 |
+
sliced_hidden_states = tuple( # each: (b, l_suffix, d)
|
| 235 |
hidden_state[:, unique_context_len:] for hidden_state in hidden_states
|
| 236 |
)
|
| 237 |
outputs["hidden_states"] = sliced_hidden_states
|
fastplms/models/e1/modeling_e1.py
CHANGED
|
@@ -3,21 +3,21 @@ from __future__ import annotations
|
|
| 3 |
import hashlib
|
| 4 |
import os
|
| 5 |
import sys
|
|
|
|
|
|
|
|
|
|
| 6 |
from collections import defaultdict
|
| 7 |
from contextvars import ContextVar
|
| 8 |
from dataclasses import dataclass
|
| 9 |
from enum import Enum
|
| 10 |
from typing import Any, ClassVar, TypedDict
|
| 11 |
-
|
| 12 |
-
import torch
|
| 13 |
-
import torch.nn as nn
|
| 14 |
-
import torch.nn.functional as F
|
| 15 |
from tqdm.auto import tqdm
|
| 16 |
from transformers import PretrainedConfig, PreTrainedModel
|
| 17 |
from transformers.activations import ACT2FN
|
| 18 |
from transformers.modeling_outputs import ModelOutput
|
| 19 |
from transformers.utils import logging
|
| 20 |
|
|
|
|
| 21 |
try:
|
| 22 |
from fastplms.attention import (
|
| 23 |
AttentionBackend,
|
|
@@ -282,7 +282,7 @@ class RotaryPositionalEmbedding(nn.Module):
|
|
| 282 |
max_position_embeddings: int = 2048,
|
| 283 |
base: int = 10000,
|
| 284 |
device: torch.device | None = None,
|
| 285 |
-
):
|
| 286 |
super().__init__()
|
| 287 |
|
| 288 |
self.dim = dim
|
|
@@ -311,11 +311,11 @@ class RotaryPositionalEmbedding(nn.Module):
|
|
| 311 |
self.max_seq_len_cached = seq_len
|
| 312 |
inv_freq = self.base ** -(
|
| 313 |
torch.arange(0, self.dim, 2, dtype=torch.float32, device=device) / self.dim
|
| 314 |
-
)
|
| 315 |
self.inv_freq = inv_freq
|
| 316 |
-
t = torch.arange(seq_len, device=device, dtype=torch.float32)
|
| 317 |
-
angles = torch.outer(t, inv_freq)
|
| 318 |
-
angles = torch.cat((angles, angles), dim=1)
|
| 319 |
self.cos_cached = angles.cos()
|
| 320 |
self.sin_cached = angles.sin()
|
| 321 |
|
|
@@ -326,18 +326,17 @@ class RotaryPositionalEmbedding(nn.Module):
|
|
| 326 |
position_ids: torch.LongTensor,
|
| 327 |
seq_len: int | None = None,
|
| 328 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 329 |
-
#
|
| 330 |
device, dtype = q.device, q.dtype
|
| 331 |
seq_len = position_ids.max().item() + 1 if seq_len is None else seq_len
|
| 332 |
|
| 333 |
if seq_len > self.max_seq_len_cached:
|
| 334 |
self._set_sin_cos_cache(seq_len=seq_len, device=device)
|
| 335 |
|
| 336 |
-
# Selecting by position
|
| 337 |
-
# axis so they broadcast over Q and K with shape (b, l, h, d).
|
| 338 |
idxs = position_ids.to(device)
|
| 339 |
-
cos = self.cos_cached.to(device=device, dtype=dtype).unsqueeze(-2)[idxs]
|
| 340 |
-
sin = self.sin_cached.to(device=device, dtype=dtype).unsqueeze(-2)[idxs]
|
| 341 |
|
| 342 |
# Apply the real and imaginary parts of the rotary transform to Q and K.
|
| 343 |
# Both halves reuse C and S, so rotate_half supplies the cross terms.
|
|
@@ -349,7 +348,7 @@ class RotaryPositionalEmbedding(nn.Module):
|
|
| 349 |
class Attention(nn.Module):
|
| 350 |
"""Multi-headed attention from 'Attention Is All You Need' paper."""
|
| 351 |
|
| 352 |
-
def __init__(self, config: E1Config, layer_idx: int):
|
| 353 |
super().__init__()
|
| 354 |
self.config = config
|
| 355 |
self.layer_idx = layer_idx
|
|
@@ -407,14 +406,21 @@ class Attention(nn.Module):
|
|
| 407 |
past_key_value: DynamicCache | None = None,
|
| 408 |
use_cache: bool = False,
|
| 409 |
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
|
|
| 410 |
bsz, q_len, _ = hidden_states.size()
|
| 411 |
query_states: torch.Tensor = self.q_proj(hidden_states)
|
| 412 |
key_states: torch.Tensor = self.k_proj(hidden_states)
|
| 413 |
val_states: torch.Tensor = self.v_proj(hidden_states)
|
| 414 |
|
| 415 |
-
query_states = query_states.view(
|
| 416 |
-
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
if self.clip_qkv is not None:
|
| 420 |
query_states = query_states.clamp(-self.clip_qkv, self.clip_qkv)
|
|
@@ -456,6 +462,7 @@ class Attention(nn.Module):
|
|
| 456 |
use_cache: bool = False,
|
| 457 |
effective_backend: AttentionBackend | None = None,
|
| 458 |
) -> tuple[torch.Tensor, torch.Tensor | None, DynamicCache | None, list[torch.Tensor] | None]:
|
|
|
|
| 459 |
is_cache_prefilled = (
|
| 460 |
use_cache
|
| 461 |
and past_key_value is not None
|
|
@@ -586,12 +593,12 @@ class Attention(nn.Module):
|
|
| 586 |
query_states: torch.Tensor, # Q has shape (b, l, h, d).
|
| 587 |
key_states: torch.Tensor, # K has shape (b, l, h_kv, d).
|
| 588 |
) -> list[torch.Tensor]:
|
| 589 |
-
query_heads = query_states.transpose(1, 2).contiguous()
|
| 590 |
-
key_heads = key_states.transpose(1, 2).contiguous()
|
| 591 |
key_heads = repeat_kv(key_heads, self.num_key_value_groups)
|
| 592 |
scale = 1.0 / (self.head_dim**0.5)
|
| 593 |
-
q_norm = torch.linalg.vector_norm(query_heads, dim=-1)
|
| 594 |
-
k_norm = torch.linalg.vector_norm(key_heads, dim=-1)
|
| 595 |
s_max_bound = (q_norm.max(dim=-1).values * k_norm.max(dim=-1).values).max(
|
| 596 |
dim=0
|
| 597 |
).values * scale
|
|
@@ -763,14 +770,14 @@ class Attention(nn.Module):
|
|
| 763 |
else:
|
| 764 |
attention_mask_4d = None
|
| 765 |
|
| 766 |
-
query_heads = query_states.transpose(1, 2).contiguous()
|
| 767 |
-
key_heads = key_states.transpose(1, 2).contiguous()
|
| 768 |
-
value_heads = val_states.transpose(1, 2).contiguous()
|
| 769 |
key_heads = repeat_kv(key_heads, self.num_key_value_groups)
|
| 770 |
value_heads = repeat_kv(value_heads, self.num_key_value_groups)
|
| 771 |
context_heads = F.scaled_dot_product_attention(
|
| 772 |
query_heads, key_heads, value_heads, attn_mask=attention_mask_4d
|
| 773 |
-
)
|
| 774 |
attn_output = (
|
| 775 |
context_heads.transpose(1, 2).reshape(bsz, q_len, self.hidden_size).contiguous()
|
| 776 |
)
|
|
@@ -807,13 +814,15 @@ class Attention(nn.Module):
|
|
| 807 |
else:
|
| 808 |
attention_mask_4d = None
|
| 809 |
|
| 810 |
-
query_heads = query_states.transpose(1, 2).contiguous()
|
| 811 |
-
key_heads = key_states.transpose(1, 2).contiguous()
|
| 812 |
-
value_heads = val_states.transpose(1, 2).contiguous()
|
| 813 |
key_heads = repeat_kv(key_heads, self.num_key_value_groups)
|
| 814 |
value_heads = repeat_kv(value_heads, self.num_key_value_groups)
|
| 815 |
scale = 1.0 / (self.head_dim**0.5)
|
| 816 |
-
attn_weights =
|
|
|
|
|
|
|
| 817 |
if attention_mask_4d is not None:
|
| 818 |
attention_mask_4d = attention_mask_4d.to(dtype=torch.bool)
|
| 819 |
attn_weights = attn_weights.masked_fill(
|
|
@@ -823,7 +832,7 @@ class Attention(nn.Module):
|
|
| 823 |
attn_weights = F.softmax(attn_weights, dim=-1)
|
| 824 |
if attention_mask_4d is not None:
|
| 825 |
attn_weights = attn_weights.masked_fill(attention_mask_4d.logical_not(), 0.0)
|
| 826 |
-
context_heads = torch.matmul(attn_weights, value_heads)
|
| 827 |
attn_output = (
|
| 828 |
context_heads.transpose(1, 2).reshape(bsz, q_len, self.hidden_size).contiguous()
|
| 829 |
)
|
|
@@ -832,7 +841,7 @@ class Attention(nn.Module):
|
|
| 832 |
|
| 833 |
|
| 834 |
class MLP(nn.Module):
|
| 835 |
-
def __init__(self, config: E1Config):
|
| 836 |
super().__init__()
|
| 837 |
self.ffn_dim = config.intermediate_size
|
| 838 |
self.hidden_dim = config.hidden_size
|
|
@@ -845,7 +854,7 @@ class MLP(nn.Module):
|
|
| 845 |
|
| 846 |
|
| 847 |
class GLUMLP(nn.Module):
|
| 848 |
-
def __init__(self, config: E1Config):
|
| 849 |
super().__init__()
|
| 850 |
self.ffn_dim = config.intermediate_size
|
| 851 |
self.hidden_dim = config.hidden_size
|
|
@@ -861,7 +870,7 @@ class GLUMLP(nn.Module):
|
|
| 861 |
|
| 862 |
|
| 863 |
class FFN(nn.Module):
|
| 864 |
-
def __init__(self, config: E1Config):
|
| 865 |
super().__init__()
|
| 866 |
mlp_cls = GLUMLP if config.gated_mlp else MLP
|
| 867 |
self.mlp = mlp_cls(config)
|
|
@@ -927,7 +936,7 @@ class E1TokenClassificationOutputWithPast(ModelOutput):
|
|
| 927 |
|
| 928 |
|
| 929 |
class RMSNorm(nn.Module):
|
| 930 |
-
def __init__(self, hidden_size: int, eps: float = 1e-6):
|
| 931 |
super().__init__()
|
| 932 |
self.weight = nn.Parameter(torch.ones(hidden_size))
|
| 933 |
self.variance_epsilon = eps
|
|
@@ -941,7 +950,7 @@ class RMSNorm(nn.Module):
|
|
| 941 |
|
| 942 |
|
| 943 |
class NormAttentionNorm(nn.Module):
|
| 944 |
-
def __init__(self, config: E1Config, layer_idx: int):
|
| 945 |
super().__init__()
|
| 946 |
self.self_attn = Attention(config, layer_idx)
|
| 947 |
self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
@@ -988,7 +997,7 @@ class NormAttentionNorm(nn.Module):
|
|
| 988 |
|
| 989 |
|
| 990 |
class DecoderLayer(nn.Module):
|
| 991 |
-
def __init__(self, config: E1Config, layer_idx: int):
|
| 992 |
super().__init__()
|
| 993 |
self.initializer_range = config.initializer_range
|
| 994 |
self.hidden_size = config.hidden_size
|
|
@@ -1179,7 +1188,7 @@ class FAST_E1_ENCODER(E1PreTrainedModel, EmbeddingMixin):
|
|
| 1179 |
config_class = E1Config
|
| 1180 |
_is_internal_encoder = True
|
| 1181 |
|
| 1182 |
-
def __init__(self, config: E1Config, **kwargs):
|
| 1183 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 1184 |
self.padding_idx = config.pad_token_id
|
| 1185 |
self.vocab_size = config.vocab_size
|
|
@@ -1540,7 +1549,7 @@ class E1Model(E1PreTrainedModel, EmbeddingMixin):
|
|
| 1540 |
config: E1Config
|
| 1541 |
config_class = E1Config
|
| 1542 |
|
| 1543 |
-
def __init__(self, config: E1Config, **kwargs):
|
| 1544 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 1545 |
self.model: FAST_E1_ENCODER = FAST_E1_ENCODER(config, **kwargs)
|
| 1546 |
self.post_init()
|
|
@@ -1589,7 +1598,7 @@ class E1ForMaskedLM(FastPLMTestTimeTrainingMixin, E1PreTrainedModel, EmbeddingMi
|
|
| 1589 |
config: E1Config
|
| 1590 |
config_class = E1Config
|
| 1591 |
|
| 1592 |
-
def __init__(self, config: E1Config, **kwargs):
|
| 1593 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 1594 |
self.model: FAST_E1_ENCODER = FAST_E1_ENCODER(config, **kwargs)
|
| 1595 |
self.vocab_size = config.vocab_size
|
|
@@ -2092,7 +2101,7 @@ class E1ForSequenceClassification(E1PreTrainedModel, EmbeddingMixin):
|
|
| 2092 |
config: E1Config
|
| 2093 |
config_class = E1Config
|
| 2094 |
|
| 2095 |
-
def __init__(self, config: E1Config, **kwargs):
|
| 2096 |
pooling_types = kwargs.pop("pooling_types", None)
|
| 2097 |
if pooling_types is None:
|
| 2098 |
pooling_types = ["mean", "var"]
|
|
@@ -2227,7 +2236,7 @@ class E1ForTokenClassification(E1PreTrainedModel, EmbeddingMixin):
|
|
| 2227 |
config: E1Config
|
| 2228 |
config_class = E1Config
|
| 2229 |
|
| 2230 |
-
def __init__(self, config: E1Config, **kwargs):
|
| 2231 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 2232 |
self.model: FAST_E1_ENCODER = FAST_E1_ENCODER(config, **kwargs)
|
| 2233 |
self.vocab_size = config.vocab_size
|
|
|
|
| 3 |
import hashlib
|
| 4 |
import os
|
| 5 |
import sys
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
from collections import defaultdict
|
| 10 |
from contextvars import ContextVar
|
| 11 |
from dataclasses import dataclass
|
| 12 |
from enum import Enum
|
| 13 |
from typing import Any, ClassVar, TypedDict
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
from tqdm.auto import tqdm
|
| 15 |
from transformers import PretrainedConfig, PreTrainedModel
|
| 16 |
from transformers.activations import ACT2FN
|
| 17 |
from transformers.modeling_outputs import ModelOutput
|
| 18 |
from transformers.utils import logging
|
| 19 |
|
| 20 |
+
|
| 21 |
try:
|
| 22 |
from fastplms.attention import (
|
| 23 |
AttentionBackend,
|
|
|
|
| 282 |
max_position_embeddings: int = 2048,
|
| 283 |
base: int = 10000,
|
| 284 |
device: torch.device | None = None,
|
| 285 |
+
) -> None:
|
| 286 |
super().__init__()
|
| 287 |
|
| 288 |
self.dim = dim
|
|
|
|
| 311 |
self.max_seq_len_cached = seq_len
|
| 312 |
inv_freq = self.base ** -(
|
| 313 |
torch.arange(0, self.dim, 2, dtype=torch.float32, device=device) / self.dim
|
| 314 |
+
) # (d / 2,)
|
| 315 |
self.inv_freq = inv_freq
|
| 316 |
+
t = torch.arange(seq_len, device=device, dtype=torch.float32) # (l,)
|
| 317 |
+
angles = torch.outer(t, inv_freq) # (l, d / 2)
|
| 318 |
+
angles = torch.cat((angles, angles), dim=1) # (l, d)
|
| 319 |
self.cos_cached = angles.cos()
|
| 320 |
self.sin_cached = angles.sin()
|
| 321 |
|
|
|
|
| 326 |
position_ids: torch.LongTensor,
|
| 327 |
seq_len: int | None = None,
|
| 328 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 329 |
+
# q, k: (b, l, h, d)
|
| 330 |
device, dtype = q.device, q.dtype
|
| 331 |
seq_len = position_ids.max().item() + 1 if seq_len is None else seq_len
|
| 332 |
|
| 333 |
if seq_len > self.max_seq_len_cached:
|
| 334 |
self._set_sin_cos_cache(seq_len=seq_len, device=device)
|
| 335 |
|
| 336 |
+
# Selecting by position inserts a head axis for broadcasting.
|
|
|
|
| 337 |
idxs = position_ids.to(device)
|
| 338 |
+
cos = self.cos_cached.to(device=device, dtype=dtype).unsqueeze(-2)[idxs] # (b, l, 1, d)
|
| 339 |
+
sin = self.sin_cached.to(device=device, dtype=dtype).unsqueeze(-2)[idxs] # (b, l, 1, d)
|
| 340 |
|
| 341 |
# Apply the real and imaginary parts of the rotary transform to Q and K.
|
| 342 |
# Both halves reuse C and S, so rotate_half supplies the cross terms.
|
|
|
|
| 348 |
class Attention(nn.Module):
|
| 349 |
"""Multi-headed attention from 'Attention Is All You Need' paper."""
|
| 350 |
|
| 351 |
+
def __init__(self, config: E1Config, layer_idx: int) -> None:
|
| 352 |
super().__init__()
|
| 353 |
self.config = config
|
| 354 |
self.layer_idx = layer_idx
|
|
|
|
| 406 |
past_key_value: DynamicCache | None = None,
|
| 407 |
use_cache: bool = False,
|
| 408 |
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 409 |
+
# hidden_states: (b, l, d); position_ids: (b, l)
|
| 410 |
bsz, q_len, _ = hidden_states.size()
|
| 411 |
query_states: torch.Tensor = self.q_proj(hidden_states)
|
| 412 |
key_states: torch.Tensor = self.k_proj(hidden_states)
|
| 413 |
val_states: torch.Tensor = self.v_proj(hidden_states)
|
| 414 |
|
| 415 |
+
query_states = query_states.view(
|
| 416 |
+
bsz, q_len, self.num_heads, self.head_dim
|
| 417 |
+
) # (b, l, h, d_h)
|
| 418 |
+
key_states = key_states.view(
|
| 419 |
+
bsz, q_len, self.num_kv_heads, self.head_dim
|
| 420 |
+
) # (b, l, h_kv, d_h)
|
| 421 |
+
val_states = val_states.view(
|
| 422 |
+
bsz, q_len, self.num_kv_heads, self.head_dim
|
| 423 |
+
) # (b, l, h_kv, d_h)
|
| 424 |
|
| 425 |
if self.clip_qkv is not None:
|
| 426 |
query_states = query_states.clamp(-self.clip_qkv, self.clip_qkv)
|
|
|
|
| 462 |
use_cache: bool = False,
|
| 463 |
effective_backend: AttentionBackend | None = None,
|
| 464 |
) -> tuple[torch.Tensor, torch.Tensor | None, DynamicCache | None, list[torch.Tensor] | None]:
|
| 465 |
+
# hidden_states: (b, l, d); position and sequence IDs: (b, l)
|
| 466 |
is_cache_prefilled = (
|
| 467 |
use_cache
|
| 468 |
and past_key_value is not None
|
|
|
|
| 593 |
query_states: torch.Tensor, # Q has shape (b, l, h, d).
|
| 594 |
key_states: torch.Tensor, # K has shape (b, l, h_kv, d).
|
| 595 |
) -> list[torch.Tensor]:
|
| 596 |
+
query_heads = query_states.transpose(1, 2).contiguous() # (b, h, l, d_h)
|
| 597 |
+
key_heads = key_states.transpose(1, 2).contiguous() # (b, h_kv, l, d_h)
|
| 598 |
key_heads = repeat_kv(key_heads, self.num_key_value_groups)
|
| 599 |
scale = 1.0 / (self.head_dim**0.5)
|
| 600 |
+
q_norm = torch.linalg.vector_norm(query_heads, dim=-1) # (b, h, l)
|
| 601 |
+
k_norm = torch.linalg.vector_norm(key_heads, dim=-1) # (b, h, l)
|
| 602 |
s_max_bound = (q_norm.max(dim=-1).values * k_norm.max(dim=-1).values).max(
|
| 603 |
dim=0
|
| 604 |
).values * scale
|
|
|
|
| 770 |
else:
|
| 771 |
attention_mask_4d = None
|
| 772 |
|
| 773 |
+
query_heads = query_states.transpose(1, 2).contiguous() # (b, h, l, d_h)
|
| 774 |
+
key_heads = key_states.transpose(1, 2).contiguous() # (b, h_kv, l, d_h)
|
| 775 |
+
value_heads = val_states.transpose(1, 2).contiguous() # (b, h_kv, l, d_h)
|
| 776 |
key_heads = repeat_kv(key_heads, self.num_key_value_groups)
|
| 777 |
value_heads = repeat_kv(value_heads, self.num_key_value_groups)
|
| 778 |
context_heads = F.scaled_dot_product_attention(
|
| 779 |
query_heads, key_heads, value_heads, attn_mask=attention_mask_4d
|
| 780 |
+
) # (b, h, l, d_h)
|
| 781 |
attn_output = (
|
| 782 |
context_heads.transpose(1, 2).reshape(bsz, q_len, self.hidden_size).contiguous()
|
| 783 |
)
|
|
|
|
| 814 |
else:
|
| 815 |
attention_mask_4d = None
|
| 816 |
|
| 817 |
+
query_heads = query_states.transpose(1, 2).contiguous() # (b, h, l, d_h)
|
| 818 |
+
key_heads = key_states.transpose(1, 2).contiguous() # (b, h_kv, l, d_h)
|
| 819 |
+
value_heads = val_states.transpose(1, 2).contiguous() # (b, h_kv, l, d_h)
|
| 820 |
key_heads = repeat_kv(key_heads, self.num_key_value_groups)
|
| 821 |
value_heads = repeat_kv(value_heads, self.num_key_value_groups)
|
| 822 |
scale = 1.0 / (self.head_dim**0.5)
|
| 823 |
+
attn_weights = (
|
| 824 |
+
torch.matmul(query_heads, key_heads.transpose(-2, -1)) * scale
|
| 825 |
+
) # (b, h, l, l)
|
| 826 |
if attention_mask_4d is not None:
|
| 827 |
attention_mask_4d = attention_mask_4d.to(dtype=torch.bool)
|
| 828 |
attn_weights = attn_weights.masked_fill(
|
|
|
|
| 832 |
attn_weights = F.softmax(attn_weights, dim=-1)
|
| 833 |
if attention_mask_4d is not None:
|
| 834 |
attn_weights = attn_weights.masked_fill(attention_mask_4d.logical_not(), 0.0)
|
| 835 |
+
context_heads = torch.matmul(attn_weights, value_heads) # (b, h, l, d_h)
|
| 836 |
attn_output = (
|
| 837 |
context_heads.transpose(1, 2).reshape(bsz, q_len, self.hidden_size).contiguous()
|
| 838 |
)
|
|
|
|
| 841 |
|
| 842 |
|
| 843 |
class MLP(nn.Module):
|
| 844 |
+
def __init__(self, config: E1Config) -> None:
|
| 845 |
super().__init__()
|
| 846 |
self.ffn_dim = config.intermediate_size
|
| 847 |
self.hidden_dim = config.hidden_size
|
|
|
|
| 854 |
|
| 855 |
|
| 856 |
class GLUMLP(nn.Module):
|
| 857 |
+
def __init__(self, config: E1Config) -> None:
|
| 858 |
super().__init__()
|
| 859 |
self.ffn_dim = config.intermediate_size
|
| 860 |
self.hidden_dim = config.hidden_size
|
|
|
|
| 870 |
|
| 871 |
|
| 872 |
class FFN(nn.Module):
|
| 873 |
+
def __init__(self, config: E1Config) -> None:
|
| 874 |
super().__init__()
|
| 875 |
mlp_cls = GLUMLP if config.gated_mlp else MLP
|
| 876 |
self.mlp = mlp_cls(config)
|
|
|
|
| 936 |
|
| 937 |
|
| 938 |
class RMSNorm(nn.Module):
|
| 939 |
+
def __init__(self, hidden_size: int, eps: float = 1e-6) -> None:
|
| 940 |
super().__init__()
|
| 941 |
self.weight = nn.Parameter(torch.ones(hidden_size))
|
| 942 |
self.variance_epsilon = eps
|
|
|
|
| 950 |
|
| 951 |
|
| 952 |
class NormAttentionNorm(nn.Module):
|
| 953 |
+
def __init__(self, config: E1Config, layer_idx: int) -> None:
|
| 954 |
super().__init__()
|
| 955 |
self.self_attn = Attention(config, layer_idx)
|
| 956 |
self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
|
|
| 997 |
|
| 998 |
|
| 999 |
class DecoderLayer(nn.Module):
|
| 1000 |
+
def __init__(self, config: E1Config, layer_idx: int) -> None:
|
| 1001 |
super().__init__()
|
| 1002 |
self.initializer_range = config.initializer_range
|
| 1003 |
self.hidden_size = config.hidden_size
|
|
|
|
| 1188 |
config_class = E1Config
|
| 1189 |
_is_internal_encoder = True
|
| 1190 |
|
| 1191 |
+
def __init__(self, config: E1Config, **kwargs) -> None:
|
| 1192 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 1193 |
self.padding_idx = config.pad_token_id
|
| 1194 |
self.vocab_size = config.vocab_size
|
|
|
|
| 1549 |
config: E1Config
|
| 1550 |
config_class = E1Config
|
| 1551 |
|
| 1552 |
+
def __init__(self, config: E1Config, **kwargs) -> None:
|
| 1553 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 1554 |
self.model: FAST_E1_ENCODER = FAST_E1_ENCODER(config, **kwargs)
|
| 1555 |
self.post_init()
|
|
|
|
| 1598 |
config: E1Config
|
| 1599 |
config_class = E1Config
|
| 1600 |
|
| 1601 |
+
def __init__(self, config: E1Config, **kwargs) -> None:
|
| 1602 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 1603 |
self.model: FAST_E1_ENCODER = FAST_E1_ENCODER(config, **kwargs)
|
| 1604 |
self.vocab_size = config.vocab_size
|
|
|
|
| 2101 |
config: E1Config
|
| 2102 |
config_class = E1Config
|
| 2103 |
|
| 2104 |
+
def __init__(self, config: E1Config, **kwargs) -> None:
|
| 2105 |
pooling_types = kwargs.pop("pooling_types", None)
|
| 2106 |
if pooling_types is None:
|
| 2107 |
pooling_types = ["mean", "var"]
|
|
|
|
| 2236 |
config: E1Config
|
| 2237 |
config_class = E1Config
|
| 2238 |
|
| 2239 |
+
def __init__(self, config: E1Config, **kwargs) -> None:
|
| 2240 |
E1PreTrainedModel.__init__(self, config, **kwargs)
|
| 2241 |
self.model: FAST_E1_ENCODER = FAST_E1_ENCODER(config, **kwargs)
|
| 2242 |
self.vocab_size = config.vocab_size
|
fastplms/models/e1/preparation.py
CHANGED
|
@@ -4,12 +4,12 @@ from __future__ import annotations
|
|
| 4 |
|
| 5 |
import itertools
|
| 6 |
import os
|
| 7 |
-
from dataclasses import dataclass
|
| 8 |
-
|
| 9 |
import torch
|
|
|
|
| 10 |
from tokenizers import Tokenizer
|
| 11 |
from torch.nn.utils.rnn import pad_sequence
|
| 12 |
|
|
|
|
| 13 |
PAD_TOKEN_ID = 0
|
| 14 |
BOS_TOKEN_ID = 1
|
| 15 |
EOS_TOKEN_ID = 2
|
|
@@ -100,7 +100,7 @@ class E1BatchPreparer:
|
|
| 100 |
revision: str | None = None,
|
| 101 |
token: str | bool | None = None,
|
| 102 |
preserve_context_labels: bool = False,
|
| 103 |
-
):
|
| 104 |
self.tokenizer = tokenizer or get_tokenizer(
|
| 105 |
tokenizer_source,
|
| 106 |
local_files_only=local_files_only,
|
|
@@ -111,7 +111,7 @@ class E1BatchPreparer:
|
|
| 111 |
self.data_prep_config = data_prep_config or DataPrepConfig()
|
| 112 |
self.pad_token_id = self.tokenizer.token_to_id("<pad>")
|
| 113 |
self.preserve_context_labels = preserve_context_labels
|
| 114 |
-
self.boundary_token_ids = torch.tensor(
|
| 115 |
[self.tokenizer.token_to_id(token) for token in ["<bos>", "<eos>", "1", "2", "<pad>"]]
|
| 116 |
).long()
|
| 117 |
self.mask_token = "?" # nosec
|
|
@@ -135,12 +135,11 @@ class E1BatchPreparer:
|
|
| 135 |
device: torch.device | None = None,
|
| 136 |
non_blocking: bool = False,
|
| 137 |
) -> dict[str, torch.Tensor | list[str] | list[int]]:
|
|
|
|
| 138 |
device = torch.device("cpu") if device is None else device
|
| 139 |
non_blocking = non_blocking and device.type == "cuda"
|
| 140 |
padded_encodings = {}
|
| 141 |
-
#
|
| 142 |
-
# is a valid value for sequence and position ids. -1 is then used to distinguish valid
|
| 143 |
-
# tokens from padding tokens, for example, when doing padding/unpadding for flash attention.
|
| 144 |
for key, padding_value in {
|
| 145 |
"input_ids": self.pad_token_id,
|
| 146 |
"sequence_ids": -1,
|
|
@@ -148,7 +147,7 @@ class E1BatchPreparer:
|
|
| 148 |
"global_position_ids": -1,
|
| 149 |
"labels": self.pad_token_id,
|
| 150 |
}.items():
|
| 151 |
-
padded_encodings[key] = pad_sequence(
|
| 152 |
[enc[key] for enc in sequence_encodings],
|
| 153 |
batch_first=True,
|
| 154 |
padding_value=padding_value,
|
|
@@ -169,30 +168,33 @@ class E1BatchPreparer:
|
|
| 169 |
)
|
| 170 |
|
| 171 |
encodings = tuple(self.prepare_singleseq(item) for item in sequences)
|
| 172 |
-
token_counts = torch.tensor(
|
| 173 |
[encoding["input_ids"].numel() for encoding in encodings],
|
| 174 |
dtype=torch.long,
|
| 175 |
)
|
| 176 |
-
input_ids = torch.cat(tuple(encoding["input_ids"] for encoding in encodings))
|
| 177 |
-
labels = torch.cat(tuple(encoding["labels"] for encoding in encodings))
|
| 178 |
positions = tuple(encoding["position_ids"] for encoding in encodings)
|
| 179 |
-
within_seq_position_ids = torch.cat(positions)
|
| 180 |
|
| 181 |
# Offsets preserve gaps left by optional X-token removal.
|
| 182 |
-
position_spans = torch.tensor(
|
| 183 |
[int(position_ids[-1].item()) + 1 for position_ids in positions],
|
| 184 |
dtype=torch.long,
|
| 185 |
)
|
| 186 |
-
position_offsets = torch.cat(
|
| 187 |
(torch.zeros(1, dtype=torch.long), position_spans[:-1]),
|
| 188 |
).cumsum(dim=0)
|
| 189 |
-
global_position_ids = torch.cat(
|
| 190 |
tuple(
|
| 191 |
position_ids + offset
|
| 192 |
for position_ids, offset in zip(positions, position_offsets, strict=True)
|
| 193 |
)
|
| 194 |
)
|
| 195 |
-
sequence_ids = torch.arange(
|
|
|
|
|
|
|
|
|
|
| 196 |
token_counts
|
| 197 |
)
|
| 198 |
|
|
@@ -241,24 +243,30 @@ class E1BatchPreparer:
|
|
| 241 |
)
|
| 242 |
|
| 243 |
symbols = itertools.chain(("<bos>", "1"), sequence, ("2", "<eos>"))
|
| 244 |
-
tokens = torch.tensor(
|
| 245 |
[self.vocab[symbol] for symbol in symbols],
|
| 246 |
dtype=torch.long,
|
| 247 |
)
|
| 248 |
-
position_ids = torch.arange(tokens.numel(), dtype=torch.long)
|
| 249 |
|
| 250 |
if self.data_prep_config.remove_X_tokens:
|
| 251 |
-
keep = tokens.ne(self.X_token_id)
|
| 252 |
-
tokens = tokens[keep]
|
| 253 |
-
position_ids = position_ids[keep]
|
| 254 |
-
|
| 255 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
def get_boundary_token_mask(self, tokens: torch.Tensor) -> torch.BoolTensor:
|
| 258 |
-
|
|
|
|
| 259 |
|
| 260 |
def get_mask_positions_mask(self, tokens: torch.Tensor) -> torch.BoolTensor:
|
| 261 |
-
|
|
|
|
| 262 |
|
| 263 |
def validate_sequence(self, sequence: str) -> bool:
|
| 264 |
if not isinstance(sequence, str):
|
|
|
|
| 4 |
|
| 5 |
import itertools
|
| 6 |
import os
|
|
|
|
|
|
|
| 7 |
import torch
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
from tokenizers import Tokenizer
|
| 10 |
from torch.nn.utils.rnn import pad_sequence
|
| 11 |
|
| 12 |
+
|
| 13 |
PAD_TOKEN_ID = 0
|
| 14 |
BOS_TOKEN_ID = 1
|
| 15 |
EOS_TOKEN_ID = 2
|
|
|
|
| 100 |
revision: str | None = None,
|
| 101 |
token: str | bool | None = None,
|
| 102 |
preserve_context_labels: bool = False,
|
| 103 |
+
) -> None:
|
| 104 |
self.tokenizer = tokenizer or get_tokenizer(
|
| 105 |
tokenizer_source,
|
| 106 |
local_files_only=local_files_only,
|
|
|
|
| 111 |
self.data_prep_config = data_prep_config or DataPrepConfig()
|
| 112 |
self.pad_token_id = self.tokenizer.token_to_id("<pad>")
|
| 113 |
self.preserve_context_labels = preserve_context_labels
|
| 114 |
+
self.boundary_token_ids = torch.tensor( # (5,)
|
| 115 |
[self.tokenizer.token_to_id(token) for token in ["<bos>", "<eos>", "1", "2", "<pad>"]]
|
| 116 |
).long()
|
| 117 |
self.mask_token = "?" # nosec
|
|
|
|
| 135 |
device: torch.device | None = None,
|
| 136 |
non_blocking: bool = False,
|
| 137 |
) -> dict[str, torch.Tensor | list[str] | list[int]]:
|
| 138 |
+
# Each sequence encoding contains aligned one-dimensional tensors of length l_i.
|
| 139 |
device = torch.device("cpu") if device is None else device
|
| 140 |
non_blocking = non_blocking and device.type == "cuda"
|
| 141 |
padded_encodings = {}
|
| 142 |
+
# Sequence and position ID zero is valid, so -1 unambiguously marks padding.
|
|
|
|
|
|
|
| 143 |
for key, padding_value in {
|
| 144 |
"input_ids": self.pad_token_id,
|
| 145 |
"sequence_ids": -1,
|
|
|
|
| 147 |
"global_position_ids": -1,
|
| 148 |
"labels": self.pad_token_id,
|
| 149 |
}.items():
|
| 150 |
+
padded_encodings[key] = pad_sequence( # (b, l_max)
|
| 151 |
[enc[key] for enc in sequence_encodings],
|
| 152 |
batch_first=True,
|
| 153 |
padding_value=padding_value,
|
|
|
|
| 168 |
)
|
| 169 |
|
| 170 |
encodings = tuple(self.prepare_singleseq(item) for item in sequences)
|
| 171 |
+
token_counts = torch.tensor( # (n,)
|
| 172 |
[encoding["input_ids"].numel() for encoding in encodings],
|
| 173 |
dtype=torch.long,
|
| 174 |
)
|
| 175 |
+
input_ids = torch.cat(tuple(encoding["input_ids"] for encoding in encodings)) # (t,)
|
| 176 |
+
labels = torch.cat(tuple(encoding["labels"] for encoding in encodings)) # (t,)
|
| 177 |
positions = tuple(encoding["position_ids"] for encoding in encodings)
|
| 178 |
+
within_seq_position_ids = torch.cat(positions) # (t,)
|
| 179 |
|
| 180 |
# Offsets preserve gaps left by optional X-token removal.
|
| 181 |
+
position_spans = torch.tensor( # (n,)
|
| 182 |
[int(position_ids[-1].item()) + 1 for position_ids in positions],
|
| 183 |
dtype=torch.long,
|
| 184 |
)
|
| 185 |
+
position_offsets = torch.cat( # (n,)
|
| 186 |
(torch.zeros(1, dtype=torch.long), position_spans[:-1]),
|
| 187 |
).cumsum(dim=0)
|
| 188 |
+
global_position_ids = torch.cat( # (t,)
|
| 189 |
tuple(
|
| 190 |
position_ids + offset
|
| 191 |
for position_ids, offset in zip(positions, position_offsets, strict=True)
|
| 192 |
)
|
| 193 |
)
|
| 194 |
+
sequence_ids = torch.arange( # (t,)
|
| 195 |
+
len(encodings),
|
| 196 |
+
dtype=torch.long,
|
| 197 |
+
).repeat_interleave(
|
| 198 |
token_counts
|
| 199 |
)
|
| 200 |
|
|
|
|
| 243 |
)
|
| 244 |
|
| 245 |
symbols = itertools.chain(("<bos>", "1"), sequence, ("2", "<eos>"))
|
| 246 |
+
tokens = torch.tensor( # (l + 4,)
|
| 247 |
[self.vocab[symbol] for symbol in symbols],
|
| 248 |
dtype=torch.long,
|
| 249 |
)
|
| 250 |
+
position_ids = torch.arange(tokens.numel(), dtype=torch.long) # (l + 4,)
|
| 251 |
|
| 252 |
if self.data_prep_config.remove_X_tokens:
|
| 253 |
+
keep = tokens.ne(self.X_token_id) # (l + 4,)
|
| 254 |
+
tokens = tokens[keep] # (t,)
|
| 255 |
+
position_ids = position_ids[keep] # (t,)
|
| 256 |
+
|
| 257 |
+
return { # Each tensor: (t,)
|
| 258 |
+
"input_ids": tokens,
|
| 259 |
+
"labels": tokens,
|
| 260 |
+
"position_ids": position_ids,
|
| 261 |
+
}
|
| 262 |
|
| 263 |
def get_boundary_token_mask(self, tokens: torch.Tensor) -> torch.BoolTensor:
|
| 264 |
+
# tokens: (...)
|
| 265 |
+
return torch.isin(tokens, self.boundary_token_ids.to(tokens.device)) # (...)
|
| 266 |
|
| 267 |
def get_mask_positions_mask(self, tokens: torch.Tensor) -> torch.BoolTensor:
|
| 268 |
+
# tokens: (...)
|
| 269 |
+
return tokens == self.mask_token_id # (...)
|
| 270 |
|
| 271 |
def validate_sequence(self, sequence: str) -> bool:
|
| 272 |
if not isinstance(sequence, str):
|
fastplms/models/e1/retrieval.py
CHANGED
|
@@ -19,6 +19,8 @@ import time
|
|
| 19 |
import urllib.error
|
| 20 |
import urllib.parse
|
| 21 |
import urllib.request
|
|
|
|
|
|
|
| 22 |
from collections import defaultdict, namedtuple
|
| 23 |
from collections.abc import Iterator, Sequence
|
| 24 |
from dataclasses import dataclass
|
|
@@ -26,18 +28,15 @@ from datetime import UTC, datetime
|
|
| 26 |
from email.utils import parsedate_to_datetime
|
| 27 |
from pathlib import Path, PurePosixPath, PureWindowsPath
|
| 28 |
from typing import TYPE_CHECKING, Any, TypedDict
|
| 29 |
-
|
| 30 |
-
import numpy as np
|
| 31 |
-
import torch
|
| 32 |
from tqdm.auto import tqdm
|
| 33 |
from transformers import PreTrainedModel
|
| 34 |
from transformers.utils import logging
|
| 35 |
|
| 36 |
from fastplms.embeddings import Pooler
|
| 37 |
-
|
| 38 |
from .cache import KVCache
|
| 39 |
from .preparation import DataPrepConfig, E1BatchPreparer, get_context
|
| 40 |
|
|
|
|
| 41 |
if TYPE_CHECKING:
|
| 42 |
from .modeling_e1 import E1MaskedLMOutputWithPast
|
| 43 |
|
|
@@ -259,19 +258,22 @@ def convert_to_tensor(
|
|
| 259 |
"MSA rows must have equal aligned lengths after removing insertions: "
|
| 260 |
f"{sorted(lengths)}"
|
| 261 |
)
|
| 262 |
-
array = np.vstack(
|
| 263 |
[np.frombuffer(byte_sequence, dtype=np.uint8) for byte_sequence in byte_sequences]
|
| 264 |
)
|
| 265 |
-
return torch.from_numpy(array).to(device)
|
| 266 |
|
| 267 |
|
| 268 |
def get_num_neighbors(byte_seqs: torch.ByteTensor, sim_threshold: float = 0.8) -> list[int]:
|
|
|
|
| 269 |
gap_token_id = np.frombuffer(b"-", np.uint8)[0].item()
|
| 270 |
-
seq_lens = (byte_seqs != gap_token_id).sum(dim=1)
|
| 271 |
num_neighbors: list[int] = []
|
| 272 |
for i in range(byte_seqs.shape[0]):
|
| 273 |
-
query_non_gaps = byte_seqs[i] != gap_token_id
|
| 274 |
-
seqs_sim = (
|
|
|
|
|
|
|
| 275 |
dim=1
|
| 276 |
) / seq_lens
|
| 277 |
num_neighbors.append(int((seqs_sim >= sim_threshold).sum().item()))
|
|
@@ -279,7 +281,8 @@ def get_num_neighbors(byte_seqs: torch.ByteTensor, sim_threshold: float = 0.8) -
|
|
| 279 |
|
| 280 |
|
| 281 |
def get_similarity_to_query(byte_seqs: torch.ByteTensor) -> torch.FloatTensor:
|
| 282 |
-
|
|
|
|
| 283 |
|
| 284 |
|
| 285 |
def sample_context(
|
|
@@ -296,19 +299,19 @@ def sample_context(
|
|
| 296 |
cache_num_neighbors_path: str | None = None,
|
| 297 |
) -> tuple[str, list[str]]:
|
| 298 |
msa_sequences = parse_msa(msa_path)
|
| 299 |
-
msa_as_byte_tensor = convert_to_tensor(msa_sequences, device)
|
| 300 |
if cache_num_neighbors_path is not None and os.path.exists(cache_num_neighbors_path):
|
| 301 |
-
num_neighbors = np.load(cache_num_neighbors_path)
|
| 302 |
else:
|
| 303 |
-
num_neighbors = np.array(
|
| 304 |
get_num_neighbors(msa_as_byte_tensor, neighbor_similarity_lower_bound)
|
| 305 |
)
|
| 306 |
if cache_num_neighbors_path is not None:
|
| 307 |
np.save(cache_num_neighbors_path, num_neighbors)
|
| 308 |
|
| 309 |
-
sampling_weights = 1.0 / num_neighbors
|
| 310 |
-
query_similarity = get_similarity_to_query(msa_as_byte_tensor)
|
| 311 |
-
filtered_mask = (query_similarity <= max_query_similarity) & (
|
| 312 |
query_similarity >= min_query_similarity
|
| 313 |
)
|
| 314 |
if int(filtered_mask.sum()) < 1:
|
|
@@ -317,8 +320,12 @@ def sample_context(
|
|
| 317 |
f"{min_query_similarity} <= query_similarity <= {max_query_similarity}."
|
| 318 |
)
|
| 319 |
|
| 320 |
-
filtered_weights = np.where(
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
len(filtered_weights),
|
| 323 |
size=min(max_num_samples, int(filtered_mask.sum())),
|
| 324 |
p=filtered_weights / filtered_weights.sum(),
|
|
@@ -380,7 +387,9 @@ def sample_multiple_contexts(
|
|
| 380 |
max_token_length=context_specification.max_token_length,
|
| 381 |
max_query_similarity=context_specification.max_query_similarity,
|
| 382 |
min_query_similarity=context_specification.min_query_similarity,
|
| 383 |
-
neighbor_similarity_lower_bound=
|
|
|
|
|
|
|
| 384 |
use_full_sequences_in_context=use_full_sequences_in_context,
|
| 385 |
full_sequences_path=full_sequences_path,
|
| 386 |
seed=seed + i,
|
|
@@ -633,16 +642,17 @@ class ContextCache:
|
|
| 633 |
|
| 634 |
|
| 635 |
def compute_ppll(logits: torch.Tensor, token_ids: torch.Tensor) -> float:
|
|
|
|
| 636 |
if token_ids.numel() == 0:
|
| 637 |
raise ValueError("Cannot score an empty token sequence")
|
| 638 |
if token_ids.device != logits.device:
|
| 639 |
-
token_ids = token_ids.to(logits.device)
|
| 640 |
if logits.shape[0] != token_ids.shape[0]:
|
| 641 |
raise ValueError(
|
| 642 |
f"Logits length {logits.shape[0]} != token_ids length {token_ids.shape[0]}"
|
| 643 |
)
|
| 644 |
-
probs = logits.softmax(dim=-1)
|
| 645 |
-
token_probs = probs.gather(dim=1, index=token_ids.unsqueeze(1)).squeeze(1)
|
| 646 |
return float(token_probs.mean().item())
|
| 647 |
|
| 648 |
|
|
@@ -716,12 +726,14 @@ class _E1ContextPredictor:
|
|
| 716 |
self, sequences: list[str], sequence_metadata: list[dict[str, str | int]]
|
| 717 |
) -> list[E1Prediction]:
|
| 718 |
outputs = self.predict_batch_padded(sequences)
|
| 719 |
-
outputs["logits"] = outputs["logits"].float()
|
| 720 |
-
outputs["embeddings"] = outputs["embeddings"].float()
|
| 721 |
|
| 722 |
-
token_mask =
|
|
|
|
|
|
|
| 723 |
if self.save_masked_positions_only:
|
| 724 |
-
token_mask = token_mask & outputs["mask_positions_mask"]
|
| 725 |
|
| 726 |
predictions: list[E1Prediction] = []
|
| 727 |
for i in range(len(sequences)):
|
|
@@ -729,17 +741,21 @@ class _E1ContextPredictor:
|
|
| 729 |
if "context_id" in sequence_metadata[i]:
|
| 730 |
pred["context_id"] = sequence_metadata[i]["context_id"]
|
| 731 |
if "logits" in self.fields_to_save:
|
| 732 |
-
pred["logits"] = outputs["logits"][i, token_mask[i]]
|
| 733 |
if not self.keep_predictions_in_gpu:
|
| 734 |
-
pred["logits"] = pred["logits"].to("cpu")
|
| 735 |
if "token_embeddings" in self.fields_to_save:
|
| 736 |
-
pred["token_embeddings"] = outputs["embeddings"][i, token_mask[i]]
|
| 737 |
if not self.keep_predictions_in_gpu:
|
| 738 |
-
pred["token_embeddings"] = pred["token_embeddings"].to("cpu")
|
| 739 |
if "mean_token_embeddings" in self.fields_to_save:
|
| 740 |
-
pred["mean_token_embeddings"] = outputs["embeddings"][
|
|
|
|
|
|
|
| 741 |
if not self.keep_predictions_in_gpu:
|
| 742 |
-
pred["mean_token_embeddings"] = pred["mean_token_embeddings"].to(
|
|
|
|
|
|
|
| 743 |
predictions.append(pred)
|
| 744 |
return predictions
|
| 745 |
|
|
@@ -767,12 +783,16 @@ class _E1ContextPredictor:
|
|
| 767 |
if self.kv_cache is not None:
|
| 768 |
self.kv_cache.after_forward(batch, output)
|
| 769 |
|
| 770 |
-
padding_mask = batch["input_ids"] == self.batch_preparer.pad_token_id
|
| 771 |
-
last_sequence_mask = (
|
| 772 |
batch["sequence_ids"] == batch["sequence_ids"].max(dim=1).values[:, None]
|
| 773 |
)
|
| 774 |
-
boundary_token_mask = self.batch_preparer.get_boundary_token_mask(
|
| 775 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 776 |
return {
|
| 777 |
"logits": output.logits,
|
| 778 |
"embeddings": output.last_hidden_state,
|
|
@@ -817,17 +837,18 @@ def _pool_hidden_states(
|
|
| 817 |
pooling_types: list[str],
|
| 818 |
device: torch.device,
|
| 819 |
) -> torch.Tensor:
|
|
|
|
| 820 |
pooler = Pooler(pooling_types)
|
| 821 |
max_len = max(hidden.shape[0] for hidden in hidden_list)
|
| 822 |
hidden_dim = hidden_list[0].shape[1]
|
| 823 |
batch_size = len(hidden_list)
|
| 824 |
-
padded = torch.zeros(batch_size, max_len, hidden_dim, device=device)
|
| 825 |
-
attention_mask = torch.zeros(batch_size, max_len, device=device)
|
| 826 |
for i, hidden in enumerate(hidden_list):
|
| 827 |
seq_len = hidden.shape[0]
|
| 828 |
padded[i, :seq_len] = hidden
|
| 829 |
attention_mask[i, :seq_len] = 1.0
|
| 830 |
-
return pooler(padded, attention_mask)
|
| 831 |
|
| 832 |
|
| 833 |
def _forward_for_embedding(
|
|
|
|
| 19 |
import urllib.error
|
| 20 |
import urllib.parse
|
| 21 |
import urllib.request
|
| 22 |
+
import numpy as np
|
| 23 |
+
import torch
|
| 24 |
from collections import defaultdict, namedtuple
|
| 25 |
from collections.abc import Iterator, Sequence
|
| 26 |
from dataclasses import dataclass
|
|
|
|
| 28 |
from email.utils import parsedate_to_datetime
|
| 29 |
from pathlib import Path, PurePosixPath, PureWindowsPath
|
| 30 |
from typing import TYPE_CHECKING, Any, TypedDict
|
|
|
|
|
|
|
|
|
|
| 31 |
from tqdm.auto import tqdm
|
| 32 |
from transformers import PreTrainedModel
|
| 33 |
from transformers.utils import logging
|
| 34 |
|
| 35 |
from fastplms.embeddings import Pooler
|
|
|
|
| 36 |
from .cache import KVCache
|
| 37 |
from .preparation import DataPrepConfig, E1BatchPreparer, get_context
|
| 38 |
|
| 39 |
+
|
| 40 |
if TYPE_CHECKING:
|
| 41 |
from .modeling_e1 import E1MaskedLMOutputWithPast
|
| 42 |
|
|
|
|
| 258 |
"MSA rows must have equal aligned lengths after removing insertions: "
|
| 259 |
f"{sorted(lengths)}"
|
| 260 |
)
|
| 261 |
+
array = np.vstack( # (n, l)
|
| 262 |
[np.frombuffer(byte_sequence, dtype=np.uint8) for byte_sequence in byte_sequences]
|
| 263 |
)
|
| 264 |
+
return torch.from_numpy(array).to(device) # (n, l)
|
| 265 |
|
| 266 |
|
| 267 |
def get_num_neighbors(byte_seqs: torch.ByteTensor, sim_threshold: float = 0.8) -> list[int]:
|
| 268 |
+
# byte_seqs: (n, l)
|
| 269 |
gap_token_id = np.frombuffer(b"-", np.uint8)[0].item()
|
| 270 |
+
seq_lens = (byte_seqs != gap_token_id).sum(dim=1) # (n,)
|
| 271 |
num_neighbors: list[int] = []
|
| 272 |
for i in range(byte_seqs.shape[0]):
|
| 273 |
+
query_non_gaps = byte_seqs[i] != gap_token_id # (l,)
|
| 274 |
+
seqs_sim = ( # (n,)
|
| 275 |
+
byte_seqs[:, query_non_gaps] == byte_seqs[i, query_non_gaps]
|
| 276 |
+
).sum(
|
| 277 |
dim=1
|
| 278 |
) / seq_lens
|
| 279 |
num_neighbors.append(int((seqs_sim >= sim_threshold).sum().item()))
|
|
|
|
| 281 |
|
| 282 |
|
| 283 |
def get_similarity_to_query(byte_seqs: torch.ByteTensor) -> torch.FloatTensor:
|
| 284 |
+
# byte_seqs: (n, l)
|
| 285 |
+
return (byte_seqs == byte_seqs[0, :]).sum(dim=1) / byte_seqs.shape[1] # (n,)
|
| 286 |
|
| 287 |
|
| 288 |
def sample_context(
|
|
|
|
| 299 |
cache_num_neighbors_path: str | None = None,
|
| 300 |
) -> tuple[str, list[str]]:
|
| 301 |
msa_sequences = parse_msa(msa_path)
|
| 302 |
+
msa_as_byte_tensor = convert_to_tensor(msa_sequences, device) # (n, l)
|
| 303 |
if cache_num_neighbors_path is not None and os.path.exists(cache_num_neighbors_path):
|
| 304 |
+
num_neighbors = np.load(cache_num_neighbors_path) # (n,)
|
| 305 |
else:
|
| 306 |
+
num_neighbors = np.array( # (n,)
|
| 307 |
get_num_neighbors(msa_as_byte_tensor, neighbor_similarity_lower_bound)
|
| 308 |
)
|
| 309 |
if cache_num_neighbors_path is not None:
|
| 310 |
np.save(cache_num_neighbors_path, num_neighbors)
|
| 311 |
|
| 312 |
+
sampling_weights = 1.0 / num_neighbors # (n,)
|
| 313 |
+
query_similarity = get_similarity_to_query(msa_as_byte_tensor) # (n,)
|
| 314 |
+
filtered_mask = (query_similarity <= max_query_similarity) & ( # (n,)
|
| 315 |
query_similarity >= min_query_similarity
|
| 316 |
)
|
| 317 |
if int(filtered_mask.sum()) < 1:
|
|
|
|
| 320 |
f"{min_query_similarity} <= query_similarity <= {max_query_similarity}."
|
| 321 |
)
|
| 322 |
|
| 323 |
+
filtered_weights = np.where( # (n,)
|
| 324 |
+
filtered_mask.cpu().numpy(),
|
| 325 |
+
sampling_weights,
|
| 326 |
+
0.0,
|
| 327 |
+
)
|
| 328 |
+
sampled_indices = np.random.default_rng(seed).choice( # (n_sampled,)
|
| 329 |
len(filtered_weights),
|
| 330 |
size=min(max_num_samples, int(filtered_mask.sum())),
|
| 331 |
p=filtered_weights / filtered_weights.sum(),
|
|
|
|
| 387 |
max_token_length=context_specification.max_token_length,
|
| 388 |
max_query_similarity=context_specification.max_query_similarity,
|
| 389 |
min_query_similarity=context_specification.min_query_similarity,
|
| 390 |
+
neighbor_similarity_lower_bound=(
|
| 391 |
+
context_specification.neighbor_similarity_lower_bound
|
| 392 |
+
),
|
| 393 |
use_full_sequences_in_context=use_full_sequences_in_context,
|
| 394 |
full_sequences_path=full_sequences_path,
|
| 395 |
seed=seed + i,
|
|
|
|
| 642 |
|
| 643 |
|
| 644 |
def compute_ppll(logits: torch.Tensor, token_ids: torch.Tensor) -> float:
|
| 645 |
+
# logits: (l, c); token_ids: (l,)
|
| 646 |
if token_ids.numel() == 0:
|
| 647 |
raise ValueError("Cannot score an empty token sequence")
|
| 648 |
if token_ids.device != logits.device:
|
| 649 |
+
token_ids = token_ids.to(logits.device) # (l,)
|
| 650 |
if logits.shape[0] != token_ids.shape[0]:
|
| 651 |
raise ValueError(
|
| 652 |
f"Logits length {logits.shape[0]} != token_ids length {token_ids.shape[0]}"
|
| 653 |
)
|
| 654 |
+
probs = logits.softmax(dim=-1) # (l, c)
|
| 655 |
+
token_probs = probs.gather(dim=1, index=token_ids.unsqueeze(1)).squeeze(1) # (l,)
|
| 656 |
return float(token_probs.mean().item())
|
| 657 |
|
| 658 |
|
|
|
|
| 726 |
self, sequences: list[str], sequence_metadata: list[dict[str, str | int]]
|
| 727 |
) -> list[E1Prediction]:
|
| 728 |
outputs = self.predict_batch_padded(sequences)
|
| 729 |
+
outputs["logits"] = outputs["logits"].float() # (b, l, c)
|
| 730 |
+
outputs["embeddings"] = outputs["embeddings"].float() # (b, l, d)
|
| 731 |
|
| 732 |
+
token_mask = ( # (b, l)
|
| 733 |
+
outputs["non_boundary_token_mask"] & outputs["last_sequence_mask"]
|
| 734 |
+
)
|
| 735 |
if self.save_masked_positions_only:
|
| 736 |
+
token_mask = token_mask & outputs["mask_positions_mask"] # (b, l)
|
| 737 |
|
| 738 |
predictions: list[E1Prediction] = []
|
| 739 |
for i in range(len(sequences)):
|
|
|
|
| 741 |
if "context_id" in sequence_metadata[i]:
|
| 742 |
pred["context_id"] = sequence_metadata[i]["context_id"]
|
| 743 |
if "logits" in self.fields_to_save:
|
| 744 |
+
pred["logits"] = outputs["logits"][i, token_mask[i]] # (r_i, c)
|
| 745 |
if not self.keep_predictions_in_gpu:
|
| 746 |
+
pred["logits"] = pred["logits"].to("cpu") # (r_i, c)
|
| 747 |
if "token_embeddings" in self.fields_to_save:
|
| 748 |
+
pred["token_embeddings"] = outputs["embeddings"][i, token_mask[i]] # (r_i, d)
|
| 749 |
if not self.keep_predictions_in_gpu:
|
| 750 |
+
pred["token_embeddings"] = pred["token_embeddings"].to("cpu") # (r_i, d)
|
| 751 |
if "mean_token_embeddings" in self.fields_to_save:
|
| 752 |
+
pred["mean_token_embeddings"] = outputs["embeddings"][
|
| 753 |
+
i, token_mask[i]
|
| 754 |
+
].mean(dim=0) # (d,)
|
| 755 |
if not self.keep_predictions_in_gpu:
|
| 756 |
+
pred["mean_token_embeddings"] = pred["mean_token_embeddings"].to( # (d,)
|
| 757 |
+
"cpu"
|
| 758 |
+
)
|
| 759 |
predictions.append(pred)
|
| 760 |
return predictions
|
| 761 |
|
|
|
|
| 783 |
if self.kv_cache is not None:
|
| 784 |
self.kv_cache.after_forward(batch, output)
|
| 785 |
|
| 786 |
+
padding_mask = batch["input_ids"] == self.batch_preparer.pad_token_id # (b, l)
|
| 787 |
+
last_sequence_mask = ( # (b, l)
|
| 788 |
batch["sequence_ids"] == batch["sequence_ids"].max(dim=1).values[:, None]
|
| 789 |
)
|
| 790 |
+
boundary_token_mask = self.batch_preparer.get_boundary_token_mask( # (b, l)
|
| 791 |
+
batch["input_ids"]
|
| 792 |
+
)
|
| 793 |
+
mask_positions_mask = self.batch_preparer.get_mask_positions_mask( # (b, l)
|
| 794 |
+
batch["input_ids"]
|
| 795 |
+
)
|
| 796 |
return {
|
| 797 |
"logits": output.logits,
|
| 798 |
"embeddings": output.last_hidden_state,
|
|
|
|
| 837 |
pooling_types: list[str],
|
| 838 |
device: torch.device,
|
| 839 |
) -> torch.Tensor:
|
| 840 |
+
# Each hidden_list entry: (l_i, d)
|
| 841 |
pooler = Pooler(pooling_types)
|
| 842 |
max_len = max(hidden.shape[0] for hidden in hidden_list)
|
| 843 |
hidden_dim = hidden_list[0].shape[1]
|
| 844 |
batch_size = len(hidden_list)
|
| 845 |
+
padded = torch.zeros(batch_size, max_len, hidden_dim, device=device) # (b, l_max, d)
|
| 846 |
+
attention_mask = torch.zeros(batch_size, max_len, device=device) # (b, l_max)
|
| 847 |
for i, hidden in enumerate(hidden_list):
|
| 848 |
seq_len = hidden.shape[0]
|
| 849 |
padded[i, :seq_len] = hidden
|
| 850 |
attention_mask[i, :seq_len] = 1.0
|
| 851 |
+
return pooler(padded, attention_mask) # (b, n_poolers * d)
|
| 852 |
|
| 853 |
|
| 854 |
def _forward_for_embedding(
|
fastplms/models/ttt.py
CHANGED
|
@@ -3,12 +3,13 @@ from __future__ import annotations
|
|
| 3 |
import contextlib
|
| 4 |
import math
|
| 5 |
import numbers
|
| 6 |
-
import typing as T
|
| 7 |
-
from dataclasses import asdict, dataclass, fields
|
| 8 |
-
|
| 9 |
import torch
|
| 10 |
import torch.nn as nn
|
| 11 |
import torch.nn.functional as F
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
_STANDARD_AMINO_ACIDS = "ACDEFGHIKLMNPQRSTVWY"
|
| 14 |
_TTT_SERIALIZATION_VERSION = 1
|
|
@@ -42,7 +43,7 @@ class TTTConfig:
|
|
| 42 |
self.verify()
|
| 43 |
|
| 44 |
@classmethod
|
| 45 |
-
def from_kwargs(cls, **kwargs:
|
| 46 |
valid_names = {field.name for field in fields(cls)}
|
| 47 |
unknown_names = set(kwargs) - valid_names
|
| 48 |
if unknown_names:
|
|
@@ -53,7 +54,7 @@ class TTTConfig:
|
|
| 53 |
kwargs["lora_target_modules"] = tuple(kwargs["lora_target_modules"])
|
| 54 |
return cls(**kwargs)
|
| 55 |
|
| 56 |
-
def merged(self, overrides:
|
| 57 |
if overrides is None:
|
| 58 |
return self
|
| 59 |
if isinstance(overrides, TTTConfig):
|
|
@@ -65,7 +66,7 @@ class TTTConfig:
|
|
| 65 |
values[name] = value
|
| 66 |
return TTTConfig(**values)
|
| 67 |
|
| 68 |
-
def to_dict(self) -> dict[str,
|
| 69 |
return asdict(self)
|
| 70 |
|
| 71 |
def verify(self) -> None:
|
|
@@ -224,9 +225,12 @@ class LoraInjectedLinear(nn.Module):
|
|
| 224 |
return self.linear._parameters["bias"]
|
| 225 |
|
| 226 |
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
def reset_lora_parameters(self) -> None:
|
| 232 |
with torch.no_grad():
|
|
@@ -235,7 +239,7 @@ class LoraInjectedLinear(nn.Module):
|
|
| 235 |
|
| 236 |
|
| 237 |
class FastPLMTestTimeTrainingMixin:
|
| 238 |
-
def init_ttt(self, ttt_config: TTTConfig |
|
| 239 |
base_config = self.__dict__.get("_ttt_cfg")
|
| 240 |
if base_config is None:
|
| 241 |
base_config = TTTConfig()
|
|
@@ -245,7 +249,7 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 245 |
serialized = getattr(getattr(self, "config", None), "fastplms_ttt", None)
|
| 246 |
serialized_initialized = False
|
| 247 |
if serialized is not None:
|
| 248 |
-
if not isinstance(serialized,
|
| 249 |
raise ValueError("config.fastplms_ttt must be a mapping.")
|
| 250 |
version = serialized.get("version")
|
| 251 |
if version != _TTT_SERIALIZATION_VERSION:
|
|
@@ -254,7 +258,7 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 254 |
f"{version!r}; expected {_TTT_SERIALIZATION_VERSION}."
|
| 255 |
)
|
| 256 |
serialized_config = serialized.get("config")
|
| 257 |
-
if not isinstance(serialized_config,
|
| 258 |
raise ValueError("Serialized FastPLMs TTT state is missing its config mapping.")
|
| 259 |
configured = TTTConfig.from_kwargs(**dict(serialized_config))
|
| 260 |
initialized_value = serialized.get("initialized", False)
|
|
@@ -285,15 +289,15 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 285 |
self,
|
| 286 |
seq: str | list[str] | None = None,
|
| 287 |
input_ids: torch.Tensor | None = None,
|
| 288 |
-
**kwargs:
|
| 289 |
) -> torch.Tensor | dict[str, torch.Tensor]:
|
| 290 |
del kwargs
|
| 291 |
if input_ids is not None:
|
| 292 |
-
return input_ids
|
| 293 |
if seq is None:
|
| 294 |
raise ValueError("Pass either seq or input_ids for TTT.")
|
| 295 |
tokenized = self.tokenizer(seq, return_tensors="pt", padding=True)
|
| 296 |
-
return tokenized["input_ids"]
|
| 297 |
|
| 298 |
def _ttt_mask_token(self) -> int:
|
| 299 |
return int(self.tokenizer.mask_token_id)
|
|
@@ -302,6 +306,7 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 302 |
return int(self.tokenizer.pad_token_id)
|
| 303 |
|
| 304 |
def _ttt_replacement_tokens(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
|
|
| 305 |
tokenizer = self.tokenizer
|
| 306 |
special_ids = set(tokenizer.all_special_ids)
|
| 307 |
vocab_size = int(self.config.vocab_size)
|
|
@@ -309,13 +314,13 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 309 |
if unknown_id is not None:
|
| 310 |
special_ids.add(int(unknown_id))
|
| 311 |
|
| 312 |
-
vocab:
|
| 313 |
get_vocab = getattr(tokenizer, "get_vocab", None)
|
| 314 |
if callable(get_vocab):
|
| 315 |
vocab = get_vocab()
|
| 316 |
-
elif isinstance(getattr(tokenizer, "vocab", None),
|
| 317 |
vocab = tokenizer.vocab
|
| 318 |
-
elif isinstance(getattr(tokenizer, "_token_to_id", None),
|
| 319 |
vocab = tokenizer._token_to_id
|
| 320 |
|
| 321 |
ids: list[int] = []
|
|
@@ -334,20 +339,20 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 334 |
"TTT could not resolve any canonical amino-acid token IDs from the tokenizer; "
|
| 335 |
"refusing to sample arbitrary or reserved vocabulary entries."
|
| 336 |
)
|
| 337 |
-
return torch.tensor(ids, device=input_ids.device, dtype=input_ids.dtype)
|
| 338 |
|
| 339 |
def _ttt_predict_logits(
|
| 340 |
self,
|
| 341 |
batch: torch.Tensor | dict[str, torch.Tensor],
|
| 342 |
-
**kwargs:
|
| 343 |
) -> torch.Tensor:
|
| 344 |
del kwargs
|
| 345 |
if isinstance(batch, dict):
|
| 346 |
output = self(**batch)
|
| 347 |
-
return output.logits
|
| 348 |
-
attention_mask = batch.ne(self._ttt_padding_token())
|
| 349 |
output = self(input_ids=batch, attention_mask=attention_mask)
|
| 350 |
-
return output.logits
|
| 351 |
|
| 352 |
def _ttt_eval_step(
|
| 353 |
self,
|
|
@@ -355,8 +360,8 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 355 |
loss: float,
|
| 356 |
seq: str | list[str] | None = None,
|
| 357 |
input_ids: torch.Tensor | None = None,
|
| 358 |
-
**kwargs:
|
| 359 |
-
) -> tuple[dict[str,
|
| 360 |
del step, loss, seq, input_ids, kwargs
|
| 361 |
return {}, None
|
| 362 |
|
|
@@ -443,8 +448,8 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 443 |
for module in self._ttt_lora_modules():
|
| 444 |
snapshot.append(
|
| 445 |
{
|
| 446 |
-
"lora_down.weight": module.lora_down.weight.detach().clone(),
|
| 447 |
-
"lora_up.weight": module.lora_up.weight.detach().clone(),
|
| 448 |
}
|
| 449 |
)
|
| 450 |
if not snapshot:
|
|
@@ -473,14 +478,14 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 473 |
for module in self._ttt_lora_modules():
|
| 474 |
module.reset_lora_parameters()
|
| 475 |
|
| 476 |
-
def _ttt_serialized_contract(self) -> dict[str,
|
| 477 |
return {
|
| 478 |
"version": _TTT_SERIALIZATION_VERSION,
|
| 479 |
"initialized": bool(self._ttt_initialized),
|
| 480 |
"config": self.ttt_config.to_dict(),
|
| 481 |
}
|
| 482 |
|
| 483 |
-
def save_pretrained(self, save_directory:
|
| 484 |
"""Save initialized adapters, their reset baseline, and the TTT config.
|
| 485 |
|
| 486 |
Adapter injection changes the module tree, so the serialized config must
|
|
@@ -523,16 +528,16 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 523 |
device: torch.device,
|
| 524 |
) -> torch.Tensor | dict[str, torch.Tensor]:
|
| 525 |
if isinstance(batch, dict):
|
| 526 |
-
return {name: tensor.to(device) for name, tensor in batch.items()}
|
| 527 |
-
return batch.to(device)
|
| 528 |
|
| 529 |
def _ttt_input_ids_from_batch(
|
| 530 |
self,
|
| 531 |
batch: torch.Tensor | dict[str, torch.Tensor],
|
| 532 |
) -> torch.Tensor:
|
| 533 |
if isinstance(batch, dict):
|
| 534 |
-
return batch["input_ids"]
|
| 535 |
-
return batch
|
| 536 |
|
| 537 |
def _ttt_set_input_ids(
|
| 538 |
self,
|
|
@@ -541,13 +546,14 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 541 |
) -> torch.Tensor | dict[str, torch.Tensor]:
|
| 542 |
if isinstance(batch, dict):
|
| 543 |
updated = dict(batch)
|
| 544 |
-
updated["input_ids"] = input_ids
|
| 545 |
return updated
|
| 546 |
-
return input_ids
|
| 547 |
|
| 548 |
def _ttt_non_special_mask(self, input_ids: torch.Tensor) -> torch.Tensor:
|
| 549 |
-
|
| 550 |
-
|
|
|
|
| 551 |
|
| 552 |
def _ttt_validate_tokenized_batch(
|
| 553 |
self,
|
|
@@ -570,12 +576,14 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 570 |
"DPLM2 TTT could not resolve the structure-token boundary safely."
|
| 571 |
)
|
| 572 |
pad_token = self._ttt_padding_token()
|
| 573 |
-
generic_aa_special_ids = torch.tensor(
|
| 574 |
[int(self.config.vocab_size) + offset for offset in range(4)],
|
| 575 |
device=input_ids.device,
|
| 576 |
dtype=input_ids.dtype,
|
| 577 |
)
|
| 578 |
-
is_structure = input_ids.ge(int(struct_boundary)) & input_ids.ne(
|
|
|
|
|
|
|
| 579 |
is_structure &= ~torch.isin(input_ids, generic_aa_special_ids)
|
| 580 |
if bool(is_structure.any()):
|
| 581 |
raise ValueError(
|
|
@@ -584,8 +592,11 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 584 |
)
|
| 585 |
|
| 586 |
if isinstance(batch, dict) and "type_ids" in batch:
|
| 587 |
-
type_ids = batch["type_ids"]
|
| 588 |
-
attention_mask = batch.get(
|
|
|
|
|
|
|
|
|
|
| 589 |
if bool(((type_ids == int(self.config.struct_type)) & attention_mask).any()):
|
| 590 |
raise ValueError(
|
| 591 |
"DPLM2 TTT currently supports amino-acid-only inputs; structure "
|
|
@@ -607,13 +618,15 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 607 |
cfg = self.ttt_config
|
| 608 |
if input_ids.shape[1] <= cfg.crop_size:
|
| 609 |
return batch
|
| 610 |
-
position_has_residue =
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
|
|
|
|
|
|
| 614 |
if valid_starts.numel() == 0:
|
| 615 |
raise ValueError("TTT could not find a crop containing a biological residue token.")
|
| 616 |
-
selected = torch.randint(
|
| 617 |
valid_starts.numel(),
|
| 618 |
(1,),
|
| 619 |
generator=generator,
|
|
@@ -625,11 +638,11 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 625 |
cropped = {}
|
| 626 |
for name, tensor in batch.items():
|
| 627 |
if tensor.ndim >= 2 and tensor.shape[1] == input_ids.shape[1]:
|
| 628 |
-
cropped[name] = tensor[:, start:end]
|
| 629 |
else:
|
| 630 |
cropped[name] = tensor
|
| 631 |
return cropped
|
| 632 |
-
return input_ids[:, start:end]
|
| 633 |
|
| 634 |
def _ttt_sample_batch(
|
| 635 |
self,
|
|
@@ -638,69 +651,69 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 638 |
) -> tuple[torch.Tensor | dict[str, torch.Tensor], torch.Tensor]:
|
| 639 |
cfg = self.ttt_config
|
| 640 |
batch = self._ttt_sample_crop(tokenized, generator)
|
| 641 |
-
input_ids = self._ttt_input_ids_from_batch(batch)
|
| 642 |
-
row_has_residue = self._ttt_non_special_mask(input_ids).any(dim=1)
|
| 643 |
-
eligible_rows = torch.where(row_has_residue)[0]
|
| 644 |
if eligible_rows.numel() == 0:
|
| 645 |
raise ValueError(
|
| 646 |
"TTT sampled batch contains no trainable biological residue tokens."
|
| 647 |
)
|
| 648 |
-
sampled_row_indices = torch.randint(
|
| 649 |
eligible_rows.numel(),
|
| 650 |
(cfg.batch_size,),
|
| 651 |
generator=generator,
|
| 652 |
device=input_ids.device,
|
| 653 |
)
|
| 654 |
-
rows = eligible_rows[sampled_row_indices]
|
| 655 |
if isinstance(batch, dict):
|
| 656 |
sampled: torch.Tensor | dict[str, torch.Tensor] = {}
|
| 657 |
for name, tensor in batch.items():
|
| 658 |
if tensor.ndim >= 1 and tensor.shape[0] == input_ids.shape[0]:
|
| 659 |
-
sampled[name] = tensor.index_select(0, rows)
|
| 660 |
else:
|
| 661 |
sampled[name] = tensor
|
| 662 |
else:
|
| 663 |
-
sampled = input_ids.index_select(0, rows)
|
| 664 |
|
| 665 |
-
sampled_ids = self._ttt_input_ids_from_batch(sampled)
|
| 666 |
-
labels = sampled_ids.clone()
|
| 667 |
-
non_special = self._ttt_non_special_mask(sampled_ids)
|
| 668 |
-
label_mask = torch.zeros_like(non_special)
|
| 669 |
for row_idx in range(sampled_ids.shape[0]):
|
| 670 |
-
candidate_positions = torch.where(non_special[row_idx])[0]
|
| 671 |
if candidate_positions.numel() == 0:
|
| 672 |
continue
|
| 673 |
num_mask = max(1, round(candidate_positions.numel() * cfg.mask_ratio))
|
| 674 |
-
order = torch.randperm(
|
| 675 |
candidate_positions.numel(),
|
| 676 |
generator=generator,
|
| 677 |
device=sampled_ids.device,
|
| 678 |
)
|
| 679 |
-
chosen = candidate_positions[order[:num_mask]]
|
| 680 |
label_mask[row_idx, chosen] = True
|
| 681 |
-
labels = labels.masked_fill(~label_mask, -100)
|
| 682 |
|
| 683 |
-
masked_ids = sampled_ids.clone()
|
| 684 |
-
chosen_positions = torch.where(label_mask)
|
| 685 |
if chosen_positions[0].numel() > 0:
|
| 686 |
-
random_values = torch.rand(
|
| 687 |
chosen_positions[0].shape,
|
| 688 |
generator=generator,
|
| 689 |
device=sampled_ids.device,
|
| 690 |
)
|
| 691 |
-
leave = random_values < cfg.bert_leave_prob
|
| 692 |
-
replace = (random_values >= cfg.bert_leave_prob) & (
|
| 693 |
random_values < cfg.bert_leave_prob + cfg.bert_replace_prob
|
| 694 |
)
|
| 695 |
-
mask = ~(leave | replace)
|
| 696 |
if mask.any():
|
| 697 |
masked_ids[
|
| 698 |
chosen_positions[0][mask],
|
| 699 |
chosen_positions[1][mask],
|
| 700 |
] = self._ttt_mask_token()
|
| 701 |
if replace.any():
|
| 702 |
-
replacement_tokens = self._ttt_replacement_tokens(sampled_ids)
|
| 703 |
-
replacement_idx = torch.randint(
|
| 704 |
replacement_tokens.shape[0],
|
| 705 |
(int(replace.sum().item()),),
|
| 706 |
generator=generator,
|
|
@@ -711,10 +724,10 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 711 |
chosen_positions[1][replace],
|
| 712 |
] = replacement_tokens[replacement_idx]
|
| 713 |
|
| 714 |
-
return self._ttt_set_input_ids(sampled, masked_ids), labels
|
| 715 |
|
| 716 |
@contextlib.contextmanager
|
| 717 |
-
def _ttt_seed_scope(self, seed: int | None) ->
|
| 718 |
if seed is None:
|
| 719 |
yield
|
| 720 |
return
|
|
@@ -736,9 +749,9 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 736 |
self,
|
| 737 |
seq: str | list[str] | None = None,
|
| 738 |
input_ids: torch.Tensor | None = None,
|
| 739 |
-
ttt_config: TTTConfig |
|
| 740 |
-
**kwargs:
|
| 741 |
-
) -> dict[str,
|
| 742 |
if ttt_config is not None:
|
| 743 |
if "_ttt_initialized" in self.__dict__ and self._ttt_initialized:
|
| 744 |
next_cfg = self.ttt_config.merged(ttt_config)
|
|
@@ -788,7 +801,7 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 788 |
module_modes = {module: module.training for module in self.modules()}
|
| 789 |
requires_grad = {param: param.requires_grad for param in self.parameters()}
|
| 790 |
losses: list[float] = []
|
| 791 |
-
step_metrics: list[dict[str,
|
| 792 |
best_state: list[dict[str, torch.Tensor]] | None = None
|
| 793 |
best_metric: float | None = None
|
| 794 |
best_step = 0
|
|
@@ -805,14 +818,17 @@ class FastPLMTestTimeTrainingMixin:
|
|
| 805 |
optimizer.zero_grad(set_to_none=True)
|
| 806 |
total_micro_steps = cfg.steps * cfg.ags
|
| 807 |
for micro_step in range(total_micro_steps):
|
| 808 |
-
batch, labels = self._ttt_sample_batch(
|
|
|
|
|
|
|
|
|
|
| 809 |
if not bool(labels.ne(-100).any()):
|
| 810 |
raise RuntimeError(
|
| 811 |
"TTT produced an all-ignored label batch; refusing a NaN update."
|
| 812 |
)
|
| 813 |
-
logits = self._ttt_predict_logits(batch, **kwargs)
|
| 814 |
-
labels = labels.to(device=logits.device)
|
| 815 |
-
loss = F.cross_entropy(
|
| 816 |
logits.reshape(-1, logits.shape[-1]),
|
| 817 |
labels.reshape(-1),
|
| 818 |
ignore_index=-100,
|
|
|
|
| 3 |
import contextlib
|
| 4 |
import math
|
| 5 |
import numbers
|
|
|
|
|
|
|
|
|
|
| 6 |
import torch
|
| 7 |
import torch.nn as nn
|
| 8 |
import torch.nn.functional as F
|
| 9 |
+
from collections.abc import Iterator, Mapping
|
| 10 |
+
from dataclasses import asdict, dataclass, fields
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
|
| 14 |
_STANDARD_AMINO_ACIDS = "ACDEFGHIKLMNPQRSTVWY"
|
| 15 |
_TTT_SERIALIZATION_VERSION = 1
|
|
|
|
| 43 |
self.verify()
|
| 44 |
|
| 45 |
@classmethod
|
| 46 |
+
def from_kwargs(cls, **kwargs: Any) -> TTTConfig:
|
| 47 |
valid_names = {field.name for field in fields(cls)}
|
| 48 |
unknown_names = set(kwargs) - valid_names
|
| 49 |
if unknown_names:
|
|
|
|
| 54 |
kwargs["lora_target_modules"] = tuple(kwargs["lora_target_modules"])
|
| 55 |
return cls(**kwargs)
|
| 56 |
|
| 57 |
+
def merged(self, overrides: Mapping[str, Any] | TTTConfig | None) -> TTTConfig:
|
| 58 |
if overrides is None:
|
| 59 |
return self
|
| 60 |
if isinstance(overrides, TTTConfig):
|
|
|
|
| 66 |
values[name] = value
|
| 67 |
return TTTConfig(**values)
|
| 68 |
|
| 69 |
+
def to_dict(self) -> dict[str, Any]:
|
| 70 |
return asdict(self)
|
| 71 |
|
| 72 |
def verify(self) -> None:
|
|
|
|
| 225 |
return self.linear._parameters["bias"]
|
| 226 |
|
| 227 |
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 228 |
+
# x: (..., d_in)
|
| 229 |
+
base = self.linear(x) # (..., d_out)
|
| 230 |
+
delta = ( # (..., d_out)
|
| 231 |
+
self.lora_up(self.lora_down(x.to(dtype=torch.float32))) * self.scale
|
| 232 |
+
)
|
| 233 |
+
return base + delta.to(dtype=base.dtype) # (..., d_out)
|
| 234 |
|
| 235 |
def reset_lora_parameters(self) -> None:
|
| 236 |
with torch.no_grad():
|
|
|
|
| 239 |
|
| 240 |
|
| 241 |
class FastPLMTestTimeTrainingMixin:
|
| 242 |
+
def init_ttt(self, ttt_config: TTTConfig | Mapping[str, Any] | None = None) -> None:
|
| 243 |
base_config = self.__dict__.get("_ttt_cfg")
|
| 244 |
if base_config is None:
|
| 245 |
base_config = TTTConfig()
|
|
|
|
| 249 |
serialized = getattr(getattr(self, "config", None), "fastplms_ttt", None)
|
| 250 |
serialized_initialized = False
|
| 251 |
if serialized is not None:
|
| 252 |
+
if not isinstance(serialized, Mapping):
|
| 253 |
raise ValueError("config.fastplms_ttt must be a mapping.")
|
| 254 |
version = serialized.get("version")
|
| 255 |
if version != _TTT_SERIALIZATION_VERSION:
|
|
|
|
| 258 |
f"{version!r}; expected {_TTT_SERIALIZATION_VERSION}."
|
| 259 |
)
|
| 260 |
serialized_config = serialized.get("config")
|
| 261 |
+
if not isinstance(serialized_config, Mapping):
|
| 262 |
raise ValueError("Serialized FastPLMs TTT state is missing its config mapping.")
|
| 263 |
configured = TTTConfig.from_kwargs(**dict(serialized_config))
|
| 264 |
initialized_value = serialized.get("initialized", False)
|
|
|
|
| 289 |
self,
|
| 290 |
seq: str | list[str] | None = None,
|
| 291 |
input_ids: torch.Tensor | None = None,
|
| 292 |
+
**kwargs: Any,
|
| 293 |
) -> torch.Tensor | dict[str, torch.Tensor]:
|
| 294 |
del kwargs
|
| 295 |
if input_ids is not None:
|
| 296 |
+
return input_ids # (b, l)
|
| 297 |
if seq is None:
|
| 298 |
raise ValueError("Pass either seq or input_ids for TTT.")
|
| 299 |
tokenized = self.tokenizer(seq, return_tensors="pt", padding=True)
|
| 300 |
+
return tokenized["input_ids"] # (b, l)
|
| 301 |
|
| 302 |
def _ttt_mask_token(self) -> int:
|
| 303 |
return int(self.tokenizer.mask_token_id)
|
|
|
|
| 306 |
return int(self.tokenizer.pad_token_id)
|
| 307 |
|
| 308 |
def _ttt_replacement_tokens(self, input_ids: torch.Tensor) -> torch.Tensor:
|
| 309 |
+
# input_ids: (b, l)
|
| 310 |
tokenizer = self.tokenizer
|
| 311 |
special_ids = set(tokenizer.all_special_ids)
|
| 312 |
vocab_size = int(self.config.vocab_size)
|
|
|
|
| 314 |
if unknown_id is not None:
|
| 315 |
special_ids.add(int(unknown_id))
|
| 316 |
|
| 317 |
+
vocab: Mapping[str, Any] = {}
|
| 318 |
get_vocab = getattr(tokenizer, "get_vocab", None)
|
| 319 |
if callable(get_vocab):
|
| 320 |
vocab = get_vocab()
|
| 321 |
+
elif isinstance(getattr(tokenizer, "vocab", None), Mapping):
|
| 322 |
vocab = tokenizer.vocab
|
| 323 |
+
elif isinstance(getattr(tokenizer, "_token_to_id", None), Mapping):
|
| 324 |
vocab = tokenizer._token_to_id
|
| 325 |
|
| 326 |
ids: list[int] = []
|
|
|
|
| 339 |
"TTT could not resolve any canonical amino-acid token IDs from the tokenizer; "
|
| 340 |
"refusing to sample arbitrary or reserved vocabulary entries."
|
| 341 |
)
|
| 342 |
+
return torch.tensor(ids, device=input_ids.device, dtype=input_ids.dtype) # (c_aa,)
|
| 343 |
|
| 344 |
def _ttt_predict_logits(
|
| 345 |
self,
|
| 346 |
batch: torch.Tensor | dict[str, torch.Tensor],
|
| 347 |
+
**kwargs: Any,
|
| 348 |
) -> torch.Tensor:
|
| 349 |
del kwargs
|
| 350 |
if isinstance(batch, dict):
|
| 351 |
output = self(**batch)
|
| 352 |
+
return output.logits # (b, l, c)
|
| 353 |
+
attention_mask = batch.ne(self._ttt_padding_token()) # (b, l)
|
| 354 |
output = self(input_ids=batch, attention_mask=attention_mask)
|
| 355 |
+
return output.logits # (b, l, c)
|
| 356 |
|
| 357 |
def _ttt_eval_step(
|
| 358 |
self,
|
|
|
|
| 360 |
loss: float,
|
| 361 |
seq: str | list[str] | None = None,
|
| 362 |
input_ids: torch.Tensor | None = None,
|
| 363 |
+
**kwargs: Any,
|
| 364 |
+
) -> tuple[dict[str, Any], float | None]:
|
| 365 |
del step, loss, seq, input_ids, kwargs
|
| 366 |
return {}, None
|
| 367 |
|
|
|
|
| 448 |
for module in self._ttt_lora_modules():
|
| 449 |
snapshot.append(
|
| 450 |
{
|
| 451 |
+
"lora_down.weight": module.lora_down.weight.detach().clone(), # (r, d_in)
|
| 452 |
+
"lora_up.weight": module.lora_up.weight.detach().clone(), # (d_out, r)
|
| 453 |
}
|
| 454 |
)
|
| 455 |
if not snapshot:
|
|
|
|
| 478 |
for module in self._ttt_lora_modules():
|
| 479 |
module.reset_lora_parameters()
|
| 480 |
|
| 481 |
+
def _ttt_serialized_contract(self) -> dict[str, Any]:
|
| 482 |
return {
|
| 483 |
"version": _TTT_SERIALIZATION_VERSION,
|
| 484 |
"initialized": bool(self._ttt_initialized),
|
| 485 |
"config": self.ttt_config.to_dict(),
|
| 486 |
}
|
| 487 |
|
| 488 |
+
def save_pretrained(self, save_directory: Any, *args: Any, **kwargs: Any) -> Any:
|
| 489 |
"""Save initialized adapters, their reset baseline, and the TTT config.
|
| 490 |
|
| 491 |
Adapter injection changes the module tree, so the serialized config must
|
|
|
|
| 528 |
device: torch.device,
|
| 529 |
) -> torch.Tensor | dict[str, torch.Tensor]:
|
| 530 |
if isinstance(batch, dict):
|
| 531 |
+
return {name: tensor.to(device) for name, tensor in batch.items()} # unchanged shapes
|
| 532 |
+
return batch.to(device) # unchanged shape
|
| 533 |
|
| 534 |
def _ttt_input_ids_from_batch(
|
| 535 |
self,
|
| 536 |
batch: torch.Tensor | dict[str, torch.Tensor],
|
| 537 |
) -> torch.Tensor:
|
| 538 |
if isinstance(batch, dict):
|
| 539 |
+
return batch["input_ids"] # (b, l)
|
| 540 |
+
return batch # (b, l)
|
| 541 |
|
| 542 |
def _ttt_set_input_ids(
|
| 543 |
self,
|
|
|
|
| 546 |
) -> torch.Tensor | dict[str, torch.Tensor]:
|
| 547 |
if isinstance(batch, dict):
|
| 548 |
updated = dict(batch)
|
| 549 |
+
updated["input_ids"] = input_ids # (b, l)
|
| 550 |
return updated
|
| 551 |
+
return input_ids # (b, l)
|
| 552 |
|
| 553 |
def _ttt_non_special_mask(self, input_ids: torch.Tensor) -> torch.Tensor:
|
| 554 |
+
# input_ids: (b, l)
|
| 555 |
+
residue_ids = self._ttt_replacement_tokens(input_ids) # (c_aa,)
|
| 556 |
+
return torch.isin(input_ids, residue_ids) # (b, l)
|
| 557 |
|
| 558 |
def _ttt_validate_tokenized_batch(
|
| 559 |
self,
|
|
|
|
| 576 |
"DPLM2 TTT could not resolve the structure-token boundary safely."
|
| 577 |
)
|
| 578 |
pad_token = self._ttt_padding_token()
|
| 579 |
+
generic_aa_special_ids = torch.tensor( # (4,)
|
| 580 |
[int(self.config.vocab_size) + offset for offset in range(4)],
|
| 581 |
device=input_ids.device,
|
| 582 |
dtype=input_ids.dtype,
|
| 583 |
)
|
| 584 |
+
is_structure = input_ids.ge(int(struct_boundary)) & input_ids.ne( # (b, l)
|
| 585 |
+
pad_token
|
| 586 |
+
)
|
| 587 |
is_structure &= ~torch.isin(input_ids, generic_aa_special_ids)
|
| 588 |
if bool(is_structure.any()):
|
| 589 |
raise ValueError(
|
|
|
|
| 592 |
)
|
| 593 |
|
| 594 |
if isinstance(batch, dict) and "type_ids" in batch:
|
| 595 |
+
type_ids = batch["type_ids"] # (b, l)
|
| 596 |
+
attention_mask = batch.get( # (b, l)
|
| 597 |
+
"attention_mask",
|
| 598 |
+
input_ids.ne(pad_token),
|
| 599 |
+
).bool()
|
| 600 |
if bool(((type_ids == int(self.config.struct_type)) & attention_mask).any()):
|
| 601 |
raise ValueError(
|
| 602 |
"DPLM2 TTT currently supports amino-acid-only inputs; structure "
|
|
|
|
| 618 |
cfg = self.ttt_config
|
| 619 |
if input_ids.shape[1] <= cfg.crop_size:
|
| 620 |
return batch
|
| 621 |
+
position_has_residue = ( # (l,)
|
| 622 |
+
self._ttt_non_special_mask(input_ids).any(dim=0).to(torch.int64)
|
| 623 |
+
)
|
| 624 |
+
prefix = F.pad(position_has_residue.cumsum(dim=0), (1, 0)) # (l + 1,)
|
| 625 |
+
window_counts = prefix[cfg.crop_size :] - prefix[: -cfg.crop_size] # (l-crop+1,)
|
| 626 |
+
valid_starts = torch.where(window_counts > 0)[0] # (n_valid,)
|
| 627 |
if valid_starts.numel() == 0:
|
| 628 |
raise ValueError("TTT could not find a crop containing a biological residue token.")
|
| 629 |
+
selected = torch.randint( # (1,)
|
| 630 |
valid_starts.numel(),
|
| 631 |
(1,),
|
| 632 |
generator=generator,
|
|
|
|
| 638 |
cropped = {}
|
| 639 |
for name, tensor in batch.items():
|
| 640 |
if tensor.ndim >= 2 and tensor.shape[1] == input_ids.shape[1]:
|
| 641 |
+
cropped[name] = tensor[:, start:end] # (b, crop_size, ...)
|
| 642 |
else:
|
| 643 |
cropped[name] = tensor
|
| 644 |
return cropped
|
| 645 |
+
return input_ids[:, start:end] # (b, crop_size)
|
| 646 |
|
| 647 |
def _ttt_sample_batch(
|
| 648 |
self,
|
|
|
|
| 651 |
) -> tuple[torch.Tensor | dict[str, torch.Tensor], torch.Tensor]:
|
| 652 |
cfg = self.ttt_config
|
| 653 |
batch = self._ttt_sample_crop(tokenized, generator)
|
| 654 |
+
input_ids = self._ttt_input_ids_from_batch(batch) # (b, l)
|
| 655 |
+
row_has_residue = self._ttt_non_special_mask(input_ids).any(dim=1) # (b,)
|
| 656 |
+
eligible_rows = torch.where(row_has_residue)[0] # (n_eligible,)
|
| 657 |
if eligible_rows.numel() == 0:
|
| 658 |
raise ValueError(
|
| 659 |
"TTT sampled batch contains no trainable biological residue tokens."
|
| 660 |
)
|
| 661 |
+
sampled_row_indices = torch.randint( # (b_sample,)
|
| 662 |
eligible_rows.numel(),
|
| 663 |
(cfg.batch_size,),
|
| 664 |
generator=generator,
|
| 665 |
device=input_ids.device,
|
| 666 |
)
|
| 667 |
+
rows = eligible_rows[sampled_row_indices] # (b_sample,)
|
| 668 |
if isinstance(batch, dict):
|
| 669 |
sampled: torch.Tensor | dict[str, torch.Tensor] = {}
|
| 670 |
for name, tensor in batch.items():
|
| 671 |
if tensor.ndim >= 1 and tensor.shape[0] == input_ids.shape[0]:
|
| 672 |
+
sampled[name] = tensor.index_select(0, rows) # (b_sample, ...)
|
| 673 |
else:
|
| 674 |
sampled[name] = tensor
|
| 675 |
else:
|
| 676 |
+
sampled = input_ids.index_select(0, rows) # (b_sample, l)
|
| 677 |
|
| 678 |
+
sampled_ids = self._ttt_input_ids_from_batch(sampled) # (b_sample, l)
|
| 679 |
+
labels = sampled_ids.clone() # (b_sample, l)
|
| 680 |
+
non_special = self._ttt_non_special_mask(sampled_ids) # (b_sample, l)
|
| 681 |
+
label_mask = torch.zeros_like(non_special) # (b_sample, l)
|
| 682 |
for row_idx in range(sampled_ids.shape[0]):
|
| 683 |
+
candidate_positions = torch.where(non_special[row_idx])[0] # (n_candidates,)
|
| 684 |
if candidate_positions.numel() == 0:
|
| 685 |
continue
|
| 686 |
num_mask = max(1, round(candidate_positions.numel() * cfg.mask_ratio))
|
| 687 |
+
order = torch.randperm( # (n_candidates,)
|
| 688 |
candidate_positions.numel(),
|
| 689 |
generator=generator,
|
| 690 |
device=sampled_ids.device,
|
| 691 |
)
|
| 692 |
+
chosen = candidate_positions[order[:num_mask]] # (n_mask,)
|
| 693 |
label_mask[row_idx, chosen] = True
|
| 694 |
+
labels = labels.masked_fill(~label_mask, -100) # (b_sample, l)
|
| 695 |
|
| 696 |
+
masked_ids = sampled_ids.clone() # (b_sample, l)
|
| 697 |
+
chosen_positions = torch.where(label_mask) # two (n_chosen,) tensors
|
| 698 |
if chosen_positions[0].numel() > 0:
|
| 699 |
+
random_values = torch.rand( # (n_chosen,)
|
| 700 |
chosen_positions[0].shape,
|
| 701 |
generator=generator,
|
| 702 |
device=sampled_ids.device,
|
| 703 |
)
|
| 704 |
+
leave = random_values < cfg.bert_leave_prob # (n_chosen,)
|
| 705 |
+
replace = (random_values >= cfg.bert_leave_prob) & ( # (n_chosen,)
|
| 706 |
random_values < cfg.bert_leave_prob + cfg.bert_replace_prob
|
| 707 |
)
|
| 708 |
+
mask = ~(leave | replace) # (n_chosen,)
|
| 709 |
if mask.any():
|
| 710 |
masked_ids[
|
| 711 |
chosen_positions[0][mask],
|
| 712 |
chosen_positions[1][mask],
|
| 713 |
] = self._ttt_mask_token()
|
| 714 |
if replace.any():
|
| 715 |
+
replacement_tokens = self._ttt_replacement_tokens(sampled_ids) # (c_aa,)
|
| 716 |
+
replacement_idx = torch.randint( # (n_replace,)
|
| 717 |
replacement_tokens.shape[0],
|
| 718 |
(int(replace.sum().item()),),
|
| 719 |
generator=generator,
|
|
|
|
| 724 |
chosen_positions[1][replace],
|
| 725 |
] = replacement_tokens[replacement_idx]
|
| 726 |
|
| 727 |
+
return self._ttt_set_input_ids(sampled, masked_ids), labels # batch, (b_sample, l)
|
| 728 |
|
| 729 |
@contextlib.contextmanager
|
| 730 |
+
def _ttt_seed_scope(self, seed: int | None) -> Iterator[None]:
|
| 731 |
if seed is None:
|
| 732 |
yield
|
| 733 |
return
|
|
|
|
| 749 |
self,
|
| 750 |
seq: str | list[str] | None = None,
|
| 751 |
input_ids: torch.Tensor | None = None,
|
| 752 |
+
ttt_config: TTTConfig | Mapping[str, Any] | None = None,
|
| 753 |
+
**kwargs: Any,
|
| 754 |
+
) -> dict[str, Any]:
|
| 755 |
if ttt_config is not None:
|
| 756 |
if "_ttt_initialized" in self.__dict__ and self._ttt_initialized:
|
| 757 |
next_cfg = self.ttt_config.merged(ttt_config)
|
|
|
|
| 801 |
module_modes = {module: module.training for module in self.modules()}
|
| 802 |
requires_grad = {param: param.requires_grad for param in self.parameters()}
|
| 803 |
losses: list[float] = []
|
| 804 |
+
step_metrics: list[dict[str, Any]] = []
|
| 805 |
best_state: list[dict[str, torch.Tensor]] | None = None
|
| 806 |
best_metric: float | None = None
|
| 807 |
best_step = 0
|
|
|
|
| 818 |
optimizer.zero_grad(set_to_none=True)
|
| 819 |
total_micro_steps = cfg.steps * cfg.ags
|
| 820 |
for micro_step in range(total_micro_steps):
|
| 821 |
+
batch, labels = self._ttt_sample_batch( # batch, (b_sample, l)
|
| 822 |
+
tokenized,
|
| 823 |
+
generator,
|
| 824 |
+
)
|
| 825 |
if not bool(labels.ne(-100).any()):
|
| 826 |
raise RuntimeError(
|
| 827 |
"TTT produced an all-ignored label batch; refusing a NaN update."
|
| 828 |
)
|
| 829 |
+
logits = self._ttt_predict_logits(batch, **kwargs) # (b_sample, l, c)
|
| 830 |
+
labels = labels.to(device=logits.device) # (b_sample, l)
|
| 831 |
+
loss = F.cross_entropy( # ()
|
| 832 |
logits.reshape(-1, logits.shape[-1]),
|
| 833 |
labels.reshape(-1),
|
| 834 |
ignore_index=-100,
|
fastplms/registry.py
CHANGED
|
@@ -18,6 +18,7 @@ from types import MappingProxyType
|
|
| 18 |
from typing import Any, Literal, cast
|
| 19 |
from urllib.parse import urlparse
|
| 20 |
|
|
|
|
| 21 |
_HEX_RE = re.compile(r"^[0-9a-f]+$")
|
| 22 |
_IDENTIFIER_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
|
| 23 |
_HUB_LICENSE_NAME_RE = re.compile(r"[^a-z0-9.]+")
|
|
@@ -559,20 +560,20 @@ def _require_str_list(table: Mapping[str, Any], key: str, context: str) -> tuple
|
|
| 559 |
value = table.get(key)
|
| 560 |
if not isinstance(value, list) or not value or any(not isinstance(item, str) for item in value):
|
| 561 |
raise RegistryError(f"{context}.{key} must be a non-empty string array.")
|
| 562 |
-
|
| 563 |
-
if len(set(
|
| 564 |
raise RegistryError(f"{context}.{key} contains duplicate values.")
|
| 565 |
-
return
|
| 566 |
|
| 567 |
|
| 568 |
def _optional_str_list(table: Mapping[str, Any], key: str, context: str) -> tuple[str, ...]:
|
| 569 |
value = table.get(key, [])
|
| 570 |
if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
|
| 571 |
raise RegistryError(f"{context}.{key} must be a string array.")
|
| 572 |
-
|
| 573 |
-
if len(set(
|
| 574 |
raise RegistryError(f"{context}.{key} contains duplicate values.")
|
| 575 |
-
return
|
| 576 |
|
| 577 |
|
| 578 |
def _optional_str(table: Mapping[str, Any], key: str, context: str) -> str | None:
|
|
@@ -657,11 +658,11 @@ def _require_digest_list(
|
|
| 657 |
table: Mapping[str, Any], key: str, context: str
|
| 658 |
) -> tuple[FileDigest, ...]:
|
| 659 |
encoded = _require_str_list(table, key, context)
|
| 660 |
-
|
| 661 |
-
paths = [item.path for item in
|
| 662 |
if len(paths) != len(set(paths)):
|
| 663 |
raise RegistryError(f"{context}.{key} contains duplicate paths.")
|
| 664 |
-
return
|
| 665 |
|
| 666 |
|
| 667 |
def _validate_revision(revision: str, context: str) -> None:
|
|
@@ -701,7 +702,7 @@ def _parse_oracle_assets(table: Mapping[str, Any], context: str) -> tuple[Oracle
|
|
| 701 |
raw = table.get("oracle_assets", [])
|
| 702 |
if not isinstance(raw, list):
|
| 703 |
raise RegistryError(f"{context}.oracle_assets must be an array of tables.")
|
| 704 |
-
|
| 705 |
for index, value in enumerate(raw):
|
| 706 |
asset_context = f"{context}.oracle_assets[{index}]"
|
| 707 |
if not isinstance(value, dict):
|
|
@@ -736,7 +737,7 @@ def _parse_oracle_assets(table: Mapping[str, Any], context: str) -> tuple[Oracle
|
|
| 736 |
size = value.get("size")
|
| 737 |
if isinstance(size, bool) or not isinstance(size, int) or size <= 0:
|
| 738 |
raise RegistryError(f"{asset_context}.size must be a positive byte count.")
|
| 739 |
-
|
| 740 |
OracleAsset(
|
| 741 |
role=role,
|
| 742 |
path=path,
|
|
@@ -745,16 +746,16 @@ def _parse_oracle_assets(table: Mapping[str, Any], context: str) -> tuple[Oracle
|
|
| 745 |
size=size,
|
| 746 |
)
|
| 747 |
)
|
| 748 |
-
roles = [asset.role for asset in
|
| 749 |
-
paths = [asset.path for asset in
|
| 750 |
-
urls = [asset.url for asset in
|
| 751 |
if (
|
| 752 |
len(roles) != len(set(roles))
|
| 753 |
or len(paths) != len(set(paths))
|
| 754 |
or len(urls) != len(set(urls))
|
| 755 |
):
|
| 756 |
raise RegistryError(f"{context}.oracle_assets contains duplicate identities.")
|
| 757 |
-
return tuple(
|
| 758 |
|
| 759 |
|
| 760 |
def _parse_official_golden(
|
|
@@ -789,7 +790,7 @@ def _parse_official_golden(
|
|
| 789 |
def _parse_attention_kernels(raw: object) -> dict[str, AttentionKernelSpec]:
|
| 790 |
if not isinstance(raw, list) or not raw:
|
| 791 |
raise RegistryError("The manifest must contain [[attention_kernels]] entries.")
|
| 792 |
-
|
| 793 |
expected_variants = {
|
| 794 |
"flash_attention_2": "flash_attn2",
|
| 795 |
"flash_attention_3": "flash_attn3",
|
|
@@ -812,7 +813,7 @@ def _parse_attention_kernels(raw: object) -> dict[str, AttentionKernelSpec]:
|
|
| 812 |
implementation = _require_str(value, "implementation", context)
|
| 813 |
if implementation not in expected_variants:
|
| 814 |
raise RegistryError(f"Unsupported attention kernel {implementation!r}.")
|
| 815 |
-
if implementation in
|
| 816 |
raise RegistryError(f"Duplicate attention kernel {implementation!r}.")
|
| 817 |
repository = _require_str(value, "repository", context)
|
| 818 |
if _REPOSITORY_ID_RE.fullmatch(repository) is None:
|
|
@@ -834,7 +835,7 @@ def _parse_attention_kernels(raw: object) -> dict[str, AttentionKernelSpec]:
|
|
| 834 |
dtypes = _require_str_list(value, "dtypes", context)
|
| 835 |
if not set(dtypes).issubset(_ALLOWED_DTYPES):
|
| 836 |
raise RegistryError(f"{context}.dtypes contains unsupported dtypes.")
|
| 837 |
-
|
| 838 |
implementation=implementation,
|
| 839 |
repository=repository,
|
| 840 |
revision=revision,
|
|
@@ -842,15 +843,15 @@ def _parse_attention_kernels(raw: object) -> dict[str, AttentionKernelSpec]:
|
|
| 842 |
expected_variant=expected_variant,
|
| 843 |
dtypes=cast(tuple[DtypeName, ...], dtypes),
|
| 844 |
)
|
| 845 |
-
if set(
|
| 846 |
raise RegistryError("The manifest must pin both FlashAttention kernel versions.")
|
| 847 |
-
return
|
| 848 |
|
| 849 |
|
| 850 |
def _parse_upstreams(raw: object) -> dict[str, UpstreamSource]:
|
| 851 |
if not isinstance(raw, list) or not raw:
|
| 852 |
raise RegistryError("The manifest must contain at least one [[upstreams]] entry.")
|
| 853 |
-
|
| 854 |
paths: set[str] = set()
|
| 855 |
for index, value in enumerate(raw):
|
| 856 |
context = f"upstreams[{index}]"
|
|
@@ -860,7 +861,7 @@ def _parse_upstreams(raw: object) -> dict[str, UpstreamSource]:
|
|
| 860 |
source_id = _require_str(value, "id", context)
|
| 861 |
if _IDENTIFIER_RE.fullmatch(source_id) is None:
|
| 862 |
raise RegistryError(f"Invalid upstream ID: {source_id!r}")
|
| 863 |
-
if source_id in
|
| 864 |
raise RegistryError(f"Duplicate upstream ID: {source_id!r}")
|
| 865 |
revision = _require_str(value, "revision", context)
|
| 866 |
_validate_revision(revision, f"{context}.revision")
|
|
@@ -913,7 +914,7 @@ def _parse_upstreams(raw: object) -> dict[str, UpstreamSource]:
|
|
| 913 |
missing_e1 = sorted(required_e1.difference(distribution_map))
|
| 914 |
if missing_e1:
|
| 915 |
raise RegistryError(f"{context} is missing E1 legal files: {missing_e1}")
|
| 916 |
-
|
| 917 |
id=source_id,
|
| 918 |
path=path,
|
| 919 |
url=url,
|
|
@@ -923,7 +924,7 @@ def _parse_upstreams(raw: object) -> dict[str, UpstreamSource]:
|
|
| 923 |
license_digests=license_digests,
|
| 924 |
distribution_files=distribution_files,
|
| 925 |
)
|
| 926 |
-
return
|
| 927 |
|
| 928 |
|
| 929 |
def _parse_families(
|
|
@@ -932,7 +933,7 @@ def _parse_families(
|
|
| 932 |
) -> dict[str, ModelFamily]:
|
| 933 |
if not isinstance(raw, dict) or not raw:
|
| 934 |
raise RegistryError("The manifest must contain [families.<id>] tables.")
|
| 935 |
-
|
| 936 |
for family_id, value in raw.items():
|
| 937 |
context = f"families.{family_id}"
|
| 938 |
if _IDENTIFIER_RE.fullmatch(family_id) is None or not isinstance(value, dict):
|
|
@@ -1068,7 +1069,7 @@ def _parse_families(
|
|
| 1068 |
f"{context}.conversion_provenance must identify {state_transform!r} and "
|
| 1069 |
f"contain mechanism-first sections; missing {missing_sections}."
|
| 1070 |
)
|
| 1071 |
-
|
| 1072 |
id=family_id,
|
| 1073 |
architecture=_require_str(value, "architecture", context),
|
| 1074 |
upstreams=source_ids,
|
|
@@ -1099,7 +1100,7 @@ def _parse_families(
|
|
| 1099 |
conversion_provenance=conversion_provenance,
|
| 1100 |
backbone_model=backbone_model,
|
| 1101 |
)
|
| 1102 |
-
return
|
| 1103 |
|
| 1104 |
|
| 1105 |
def _parse_runtime_assets(
|
|
@@ -1108,7 +1109,7 @@ def _parse_runtime_assets(
|
|
| 1108 |
) -> dict[str, RuntimeAsset]:
|
| 1109 |
if not isinstance(raw, list) or not raw:
|
| 1110 |
raise RegistryError("The manifest must contain at least one [[runtime_assets]] entry.")
|
| 1111 |
-
|
| 1112 |
identities: set[tuple[str, str, str]] = set()
|
| 1113 |
for index, value in enumerate(raw):
|
| 1114 |
context = f"runtime_assets[{index}]"
|
|
@@ -1118,7 +1119,7 @@ def _parse_runtime_assets(
|
|
| 1118 |
asset_id = _require_str(value, "id", context)
|
| 1119 |
if _IDENTIFIER_RE.fullmatch(asset_id) is None:
|
| 1120 |
raise RegistryError(f"Invalid runtime asset ID: {asset_id!r}")
|
| 1121 |
-
if asset_id in
|
| 1122 |
raise RegistryError(f"Duplicate runtime asset ID: {asset_id!r}")
|
| 1123 |
repository = _require_str(value, "repository", context)
|
| 1124 |
if _REPOSITORY_ID_RE.fullmatch(repository) is None:
|
|
@@ -1164,7 +1165,7 @@ def _parse_runtime_assets(
|
|
| 1164 |
if identity in identities:
|
| 1165 |
raise RegistryError(f"Duplicate runtime asset identity: {identity!r}")
|
| 1166 |
identities.add(identity)
|
| 1167 |
-
|
| 1168 |
id=asset_id,
|
| 1169 |
repository=repository,
|
| 1170 |
revision=revision,
|
|
@@ -1176,7 +1177,7 @@ def _parse_runtime_assets(
|
|
| 1176 |
license_expression=license_expression,
|
| 1177 |
offline_behavior=offline_behavior,
|
| 1178 |
)
|
| 1179 |
-
return
|
| 1180 |
|
| 1181 |
|
| 1182 |
def _parse_models(
|
|
@@ -1185,7 +1186,7 @@ def _parse_models(
|
|
| 1185 |
) -> dict[str, ModelSpec]:
|
| 1186 |
if not isinstance(raw, list) or not raw:
|
| 1187 |
raise RegistryError("The manifest must contain at least one [[models]] entry.")
|
| 1188 |
-
|
| 1189 |
fast_repositories: set[str] = set()
|
| 1190 |
for index, value in enumerate(raw):
|
| 1191 |
context = f"models[{index}]"
|
|
@@ -1195,7 +1196,7 @@ def _parse_models(
|
|
| 1195 |
model_id = _require_str(value, "id", context)
|
| 1196 |
if _IDENTIFIER_RE.fullmatch(model_id) is None:
|
| 1197 |
raise RegistryError(f"Invalid model ID: {model_id!r}")
|
| 1198 |
-
if model_id in
|
| 1199 |
raise RegistryError(f"Duplicate model ID: {model_id!r}")
|
| 1200 |
family_id = _require_str(value, "family", context)
|
| 1201 |
if family_id not in families:
|
|
@@ -1278,7 +1279,7 @@ def _parse_models(
|
|
| 1278 |
if not class_path.startswith("fastplms.") or class_path.count(".") < 2:
|
| 1279 |
raise RegistryError(f"Invalid Python class path in {context}: {class_path!r}")
|
| 1280 |
auto_map.append((auto_class, class_path))
|
| 1281 |
-
|
| 1282 |
id=model_id,
|
| 1283 |
family=family,
|
| 1284 |
fast=fast,
|
|
@@ -1294,7 +1295,7 @@ def _parse_models(
|
|
| 1294 |
notes=notes,
|
| 1295 |
msa_conditioning=msa_conditioning,
|
| 1296 |
)
|
| 1297 |
-
return
|
| 1298 |
|
| 1299 |
|
| 1300 |
def _validate_registry(
|
|
@@ -1409,21 +1410,21 @@ def _validate_registry(
|
|
| 1409 |
|
| 1410 |
def _load_manifest_bytes(raw_bytes: bytes) -> ModelRegistry:
|
| 1411 |
try:
|
| 1412 |
-
|
| 1413 |
except (UnicodeDecodeError, tomllib.TOMLDecodeError) as error:
|
| 1414 |
raise RegistryError(f"Unable to parse model manifest: {error}") from error
|
| 1415 |
-
_reject_unknown_fields(
|
| 1416 |
-
if
|
| 1417 |
raise RegistryError("Unsupported model manifest schema_version; expected 1.")
|
| 1418 |
-
legal_files = _require_digest_list(
|
| 1419 |
required_legal_paths = {"LICENSE", "THIRD_PARTY_NOTICES.md"}
|
| 1420 |
if {item.path for item in legal_files} != required_legal_paths:
|
| 1421 |
raise RegistryError("manifest.legal_files must contain LICENSE and THIRD_PARTY_NOTICES.md.")
|
| 1422 |
-
attention_kernels = _parse_attention_kernels(
|
| 1423 |
-
upstreams = _parse_upstreams(
|
| 1424 |
-
families = _parse_families(
|
| 1425 |
-
runtime_assets = _parse_runtime_assets(
|
| 1426 |
-
models = _parse_models(
|
| 1427 |
_validate_registry(upstreams, attention_kernels, families, models)
|
| 1428 |
return ModelRegistry(
|
| 1429 |
schema_version=1,
|
|
|
|
| 18 |
from typing import Any, Literal, cast
|
| 19 |
from urllib.parse import urlparse
|
| 20 |
|
| 21 |
+
|
| 22 |
_HEX_RE = re.compile(r"^[0-9a-f]+$")
|
| 23 |
_IDENTIFIER_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
|
| 24 |
_HUB_LICENSE_NAME_RE = re.compile(r"[^a-z0-9.]+")
|
|
|
|
| 560 |
value = table.get(key)
|
| 561 |
if not isinstance(value, list) or not value or any(not isinstance(item, str) for item in value):
|
| 562 |
raise RegistryError(f"{context}.{key} must be a non-empty string array.")
|
| 563 |
+
strings = tuple(value)
|
| 564 |
+
if len(set(strings)) != len(strings):
|
| 565 |
raise RegistryError(f"{context}.{key} contains duplicate values.")
|
| 566 |
+
return strings
|
| 567 |
|
| 568 |
|
| 569 |
def _optional_str_list(table: Mapping[str, Any], key: str, context: str) -> tuple[str, ...]:
|
| 570 |
value = table.get(key, [])
|
| 571 |
if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
|
| 572 |
raise RegistryError(f"{context}.{key} must be a string array.")
|
| 573 |
+
strings = tuple(value)
|
| 574 |
+
if len(set(strings)) != len(strings):
|
| 575 |
raise RegistryError(f"{context}.{key} contains duplicate values.")
|
| 576 |
+
return strings
|
| 577 |
|
| 578 |
|
| 579 |
def _optional_str(table: Mapping[str, Any], key: str, context: str) -> str | None:
|
|
|
|
| 658 |
table: Mapping[str, Any], key: str, context: str
|
| 659 |
) -> tuple[FileDigest, ...]:
|
| 660 |
encoded = _require_str_list(table, key, context)
|
| 661 |
+
digests = tuple(FileDigest.parse(value) for value in encoded)
|
| 662 |
+
paths = [item.path for item in digests]
|
| 663 |
if len(paths) != len(set(paths)):
|
| 664 |
raise RegistryError(f"{context}.{key} contains duplicate paths.")
|
| 665 |
+
return digests
|
| 666 |
|
| 667 |
|
| 668 |
def _validate_revision(revision: str, context: str) -> None:
|
|
|
|
| 702 |
raw = table.get("oracle_assets", [])
|
| 703 |
if not isinstance(raw, list):
|
| 704 |
raise RegistryError(f"{context}.oracle_assets must be an array of tables.")
|
| 705 |
+
assets: list[OracleAsset] = []
|
| 706 |
for index, value in enumerate(raw):
|
| 707 |
asset_context = f"{context}.oracle_assets[{index}]"
|
| 708 |
if not isinstance(value, dict):
|
|
|
|
| 737 |
size = value.get("size")
|
| 738 |
if isinstance(size, bool) or not isinstance(size, int) or size <= 0:
|
| 739 |
raise RegistryError(f"{asset_context}.size must be a positive byte count.")
|
| 740 |
+
assets.append(
|
| 741 |
OracleAsset(
|
| 742 |
role=role,
|
| 743 |
path=path,
|
|
|
|
| 746 |
size=size,
|
| 747 |
)
|
| 748 |
)
|
| 749 |
+
roles = [asset.role for asset in assets]
|
| 750 |
+
paths = [asset.path for asset in assets]
|
| 751 |
+
urls = [asset.url for asset in assets]
|
| 752 |
if (
|
| 753 |
len(roles) != len(set(roles))
|
| 754 |
or len(paths) != len(set(paths))
|
| 755 |
or len(urls) != len(set(urls))
|
| 756 |
):
|
| 757 |
raise RegistryError(f"{context}.oracle_assets contains duplicate identities.")
|
| 758 |
+
return tuple(assets)
|
| 759 |
|
| 760 |
|
| 761 |
def _parse_official_golden(
|
|
|
|
| 790 |
def _parse_attention_kernels(raw: object) -> dict[str, AttentionKernelSpec]:
|
| 791 |
if not isinstance(raw, list) or not raw:
|
| 792 |
raise RegistryError("The manifest must contain [[attention_kernels]] entries.")
|
| 793 |
+
kernels: dict[str, AttentionKernelSpec] = {}
|
| 794 |
expected_variants = {
|
| 795 |
"flash_attention_2": "flash_attn2",
|
| 796 |
"flash_attention_3": "flash_attn3",
|
|
|
|
| 813 |
implementation = _require_str(value, "implementation", context)
|
| 814 |
if implementation not in expected_variants:
|
| 815 |
raise RegistryError(f"Unsupported attention kernel {implementation!r}.")
|
| 816 |
+
if implementation in kernels:
|
| 817 |
raise RegistryError(f"Duplicate attention kernel {implementation!r}.")
|
| 818 |
repository = _require_str(value, "repository", context)
|
| 819 |
if _REPOSITORY_ID_RE.fullmatch(repository) is None:
|
|
|
|
| 835 |
dtypes = _require_str_list(value, "dtypes", context)
|
| 836 |
if not set(dtypes).issubset(_ALLOWED_DTYPES):
|
| 837 |
raise RegistryError(f"{context}.dtypes contains unsupported dtypes.")
|
| 838 |
+
kernels[implementation] = AttentionKernelSpec(
|
| 839 |
implementation=implementation,
|
| 840 |
repository=repository,
|
| 841 |
revision=revision,
|
|
|
|
| 843 |
expected_variant=expected_variant,
|
| 844 |
dtypes=cast(tuple[DtypeName, ...], dtypes),
|
| 845 |
)
|
| 846 |
+
if set(kernels) != set(expected_variants):
|
| 847 |
raise RegistryError("The manifest must pin both FlashAttention kernel versions.")
|
| 848 |
+
return kernels
|
| 849 |
|
| 850 |
|
| 851 |
def _parse_upstreams(raw: object) -> dict[str, UpstreamSource]:
|
| 852 |
if not isinstance(raw, list) or not raw:
|
| 853 |
raise RegistryError("The manifest must contain at least one [[upstreams]] entry.")
|
| 854 |
+
upstreams: dict[str, UpstreamSource] = {}
|
| 855 |
paths: set[str] = set()
|
| 856 |
for index, value in enumerate(raw):
|
| 857 |
context = f"upstreams[{index}]"
|
|
|
|
| 861 |
source_id = _require_str(value, "id", context)
|
| 862 |
if _IDENTIFIER_RE.fullmatch(source_id) is None:
|
| 863 |
raise RegistryError(f"Invalid upstream ID: {source_id!r}")
|
| 864 |
+
if source_id in upstreams:
|
| 865 |
raise RegistryError(f"Duplicate upstream ID: {source_id!r}")
|
| 866 |
revision = _require_str(value, "revision", context)
|
| 867 |
_validate_revision(revision, f"{context}.revision")
|
|
|
|
| 914 |
missing_e1 = sorted(required_e1.difference(distribution_map))
|
| 915 |
if missing_e1:
|
| 916 |
raise RegistryError(f"{context} is missing E1 legal files: {missing_e1}")
|
| 917 |
+
upstreams[source_id] = UpstreamSource(
|
| 918 |
id=source_id,
|
| 919 |
path=path,
|
| 920 |
url=url,
|
|
|
|
| 924 |
license_digests=license_digests,
|
| 925 |
distribution_files=distribution_files,
|
| 926 |
)
|
| 927 |
+
return upstreams
|
| 928 |
|
| 929 |
|
| 930 |
def _parse_families(
|
|
|
|
| 933 |
) -> dict[str, ModelFamily]:
|
| 934 |
if not isinstance(raw, dict) or not raw:
|
| 935 |
raise RegistryError("The manifest must contain [families.<id>] tables.")
|
| 936 |
+
families: dict[str, ModelFamily] = {}
|
| 937 |
for family_id, value in raw.items():
|
| 938 |
context = f"families.{family_id}"
|
| 939 |
if _IDENTIFIER_RE.fullmatch(family_id) is None or not isinstance(value, dict):
|
|
|
|
| 1069 |
f"{context}.conversion_provenance must identify {state_transform!r} and "
|
| 1070 |
f"contain mechanism-first sections; missing {missing_sections}."
|
| 1071 |
)
|
| 1072 |
+
families[family_id] = ModelFamily(
|
| 1073 |
id=family_id,
|
| 1074 |
architecture=_require_str(value, "architecture", context),
|
| 1075 |
upstreams=source_ids,
|
|
|
|
| 1100 |
conversion_provenance=conversion_provenance,
|
| 1101 |
backbone_model=backbone_model,
|
| 1102 |
)
|
| 1103 |
+
return families
|
| 1104 |
|
| 1105 |
|
| 1106 |
def _parse_runtime_assets(
|
|
|
|
| 1109 |
) -> dict[str, RuntimeAsset]:
|
| 1110 |
if not isinstance(raw, list) or not raw:
|
| 1111 |
raise RegistryError("The manifest must contain at least one [[runtime_assets]] entry.")
|
| 1112 |
+
runtime_assets: dict[str, RuntimeAsset] = {}
|
| 1113 |
identities: set[tuple[str, str, str]] = set()
|
| 1114 |
for index, value in enumerate(raw):
|
| 1115 |
context = f"runtime_assets[{index}]"
|
|
|
|
| 1119 |
asset_id = _require_str(value, "id", context)
|
| 1120 |
if _IDENTIFIER_RE.fullmatch(asset_id) is None:
|
| 1121 |
raise RegistryError(f"Invalid runtime asset ID: {asset_id!r}")
|
| 1122 |
+
if asset_id in runtime_assets:
|
| 1123 |
raise RegistryError(f"Duplicate runtime asset ID: {asset_id!r}")
|
| 1124 |
repository = _require_str(value, "repository", context)
|
| 1125 |
if _REPOSITORY_ID_RE.fullmatch(repository) is None:
|
|
|
|
| 1165 |
if identity in identities:
|
| 1166 |
raise RegistryError(f"Duplicate runtime asset identity: {identity!r}")
|
| 1167 |
identities.add(identity)
|
| 1168 |
+
runtime_assets[asset_id] = RuntimeAsset(
|
| 1169 |
id=asset_id,
|
| 1170 |
repository=repository,
|
| 1171 |
revision=revision,
|
|
|
|
| 1177 |
license_expression=license_expression,
|
| 1178 |
offline_behavior=offline_behavior,
|
| 1179 |
)
|
| 1180 |
+
return runtime_assets
|
| 1181 |
|
| 1182 |
|
| 1183 |
def _parse_models(
|
|
|
|
| 1186 |
) -> dict[str, ModelSpec]:
|
| 1187 |
if not isinstance(raw, list) or not raw:
|
| 1188 |
raise RegistryError("The manifest must contain at least one [[models]] entry.")
|
| 1189 |
+
models: dict[str, ModelSpec] = {}
|
| 1190 |
fast_repositories: set[str] = set()
|
| 1191 |
for index, value in enumerate(raw):
|
| 1192 |
context = f"models[{index}]"
|
|
|
|
| 1196 |
model_id = _require_str(value, "id", context)
|
| 1197 |
if _IDENTIFIER_RE.fullmatch(model_id) is None:
|
| 1198 |
raise RegistryError(f"Invalid model ID: {model_id!r}")
|
| 1199 |
+
if model_id in models:
|
| 1200 |
raise RegistryError(f"Duplicate model ID: {model_id!r}")
|
| 1201 |
family_id = _require_str(value, "family", context)
|
| 1202 |
if family_id not in families:
|
|
|
|
| 1279 |
if not class_path.startswith("fastplms.") or class_path.count(".") < 2:
|
| 1280 |
raise RegistryError(f"Invalid Python class path in {context}: {class_path!r}")
|
| 1281 |
auto_map.append((auto_class, class_path))
|
| 1282 |
+
models[model_id] = ModelSpec(
|
| 1283 |
id=model_id,
|
| 1284 |
family=family,
|
| 1285 |
fast=fast,
|
|
|
|
| 1295 |
notes=notes,
|
| 1296 |
msa_conditioning=msa_conditioning,
|
| 1297 |
)
|
| 1298 |
+
return models
|
| 1299 |
|
| 1300 |
|
| 1301 |
def _validate_registry(
|
|
|
|
| 1410 |
|
| 1411 |
def _load_manifest_bytes(raw_bytes: bytes) -> ModelRegistry:
|
| 1412 |
try:
|
| 1413 |
+
manifest = tomllib.loads(raw_bytes.decode("utf-8"))
|
| 1414 |
except (UnicodeDecodeError, tomllib.TOMLDecodeError) as error:
|
| 1415 |
raise RegistryError(f"Unable to parse model manifest: {error}") from error
|
| 1416 |
+
_reject_unknown_fields(manifest, _ROOT_FIELDS, "manifest")
|
| 1417 |
+
if manifest.get("schema_version") != 1:
|
| 1418 |
raise RegistryError("Unsupported model manifest schema_version; expected 1.")
|
| 1419 |
+
legal_files = _require_digest_list(manifest, "legal_files", "manifest")
|
| 1420 |
required_legal_paths = {"LICENSE", "THIRD_PARTY_NOTICES.md"}
|
| 1421 |
if {item.path for item in legal_files} != required_legal_paths:
|
| 1422 |
raise RegistryError("manifest.legal_files must contain LICENSE and THIRD_PARTY_NOTICES.md.")
|
| 1423 |
+
attention_kernels = _parse_attention_kernels(manifest.get("attention_kernels"))
|
| 1424 |
+
upstreams = _parse_upstreams(manifest.get("upstreams"))
|
| 1425 |
+
families = _parse_families(manifest.get("families"), upstreams)
|
| 1426 |
+
runtime_assets = _parse_runtime_assets(manifest.get("runtime_assets"), families)
|
| 1427 |
+
models = _parse_models(manifest.get("models"), families)
|
| 1428 |
_validate_registry(upstreams, attention_kernels, families, models)
|
| 1429 |
return ModelRegistry(
|
| 1430 |
schema_version=1,
|
fastplms/runtime.py
CHANGED
|
@@ -11,6 +11,7 @@ from contextlib import contextmanager
|
|
| 11 |
from dataclasses import dataclass
|
| 12 |
from typing import TYPE_CHECKING, Literal
|
| 13 |
|
|
|
|
| 14 |
if TYPE_CHECKING:
|
| 15 |
from collections.abc import Iterator
|
| 16 |
|
|
|
|
| 11 |
from dataclasses import dataclass
|
| 12 |
from typing import TYPE_CHECKING, Literal
|
| 13 |
|
| 14 |
+
|
| 15 |
if TYPE_CHECKING:
|
| 16 |
from collections.abc import Iterator
|
| 17 |
|
fastplms_bundle.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
modeling_fastplms.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Generated bridge to the
|
| 2 |
|
| 3 |
import base64
|
| 4 |
import hashlib
|
|
@@ -6,14 +6,13 @@ import importlib
|
|
| 6 |
import importlib.util
|
| 7 |
import sys
|
| 8 |
import tempfile
|
| 9 |
-
from importlib.metadata import PackageNotFoundError, distribution
|
| 10 |
from io import BytesIO
|
| 11 |
from pathlib import Path
|
| 12 |
from zipfile import ZIP_DEFLATED, ZipFile
|
| 13 |
|
| 14 |
from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
|
| 15 |
|
| 16 |
-
if RUNTIME_HASH != "
|
| 17 |
raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
|
| 18 |
|
| 19 |
_RUNTIME_TEMPORARIES = []
|
|
@@ -83,24 +82,6 @@ def _runtime_file_hashes(package_root):
|
|
| 83 |
result[relative.as_posix()] = hashlib.sha256(path.read_bytes()).hexdigest()
|
| 84 |
return result
|
| 85 |
|
| 86 |
-
def _installed_runtime_digest(installed_root, relative):
|
| 87 |
-
candidate = installed_root / relative
|
| 88 |
-
if candidate.is_file():
|
| 89 |
-
return hashlib.sha256(candidate.read_bytes()).hexdigest()
|
| 90 |
-
if relative != "kernels.lock":
|
| 91 |
-
return None
|
| 92 |
-
try:
|
| 93 |
-
installed_distribution = distribution("fastplms")
|
| 94 |
-
except PackageNotFoundError:
|
| 95 |
-
return None
|
| 96 |
-
for entry in installed_distribution.files or ():
|
| 97 |
-
normalized = str(entry).replace("\\", "/")
|
| 98 |
-
if normalized.endswith(".dist-info/kernels.lock"):
|
| 99 |
-
lock_path = Path(installed_distribution.locate_file(entry))
|
| 100 |
-
if lock_path.is_file():
|
| 101 |
-
return hashlib.sha256(lock_path.read_bytes()).hexdigest()
|
| 102 |
-
return None
|
| 103 |
-
|
| 104 |
def _extend_loaded_package_paths(package_root):
|
| 105 |
for name, module in list(sys.modules.items()):
|
| 106 |
if name != "fastplms" and not name.startswith("fastplms."):
|
|
@@ -114,29 +95,14 @@ def _extend_loaded_package_paths(package_root):
|
|
| 114 |
if candidate.is_dir() and candidate_text not in paths:
|
| 115 |
paths.append(candidate_text)
|
| 116 |
|
| 117 |
-
def _merge_runtime(
|
| 118 |
incoming = _runtime_file_hashes(package_root)
|
| 119 |
-
known =
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
if installed_file is None:
|
| 126 |
-
raise RuntimeError(
|
| 127 |
-
"The loaded fastplms package has no source path and cannot be verified "
|
| 128 |
-
"against the embedded artifact runtime."
|
| 129 |
-
)
|
| 130 |
-
installed_root = Path(installed_file).resolve().parent
|
| 131 |
-
for relative, digest in incoming.items():
|
| 132 |
-
if _installed_runtime_digest(installed_root, relative) != digest:
|
| 133 |
-
raise RuntimeError(
|
| 134 |
-
"The installed FastPLMs runtime differs from this artifact at "
|
| 135 |
-
f"{relative!r}. Install the artifact's matching FastPLMs release "
|
| 136 |
-
"or use a separate Python process."
|
| 137 |
-
)
|
| 138 |
-
installed_root_text = str(installed_root)
|
| 139 |
-
installed.__fastplms_artifact_installed_root__ = installed_root_text
|
| 140 |
conflicts = sorted(
|
| 141 |
relative
|
| 142 |
for relative, digest in incoming.items()
|
|
@@ -148,35 +114,25 @@ def _merge_runtime(installed, package_root):
|
|
| 148 |
+ ", ".join(repr(path) for path in conflicts[:5])
|
| 149 |
+ ". Load incompatible releases in separate Python processes."
|
| 150 |
)
|
| 151 |
-
|
| 152 |
-
installed_root = Path(installed_root_text)
|
| 153 |
-
for relative, digest in incoming.items():
|
| 154 |
-
if relative in known:
|
| 155 |
-
continue
|
| 156 |
-
if _installed_runtime_digest(installed_root, relative) != digest:
|
| 157 |
-
raise RuntimeError(
|
| 158 |
-
"The installed FastPLMs runtime differs from this artifact at "
|
| 159 |
-
f"{relative!r}. Install the artifact's matching FastPLMs release "
|
| 160 |
-
"or use a separate Python process."
|
| 161 |
-
)
|
| 162 |
known.update(incoming)
|
| 163 |
-
|
| 164 |
-
roots = list(getattr(
|
| 165 |
if str(package_root) not in roots:
|
| 166 |
roots.append(str(package_root))
|
| 167 |
-
|
| 168 |
temporaries = list(
|
| 169 |
-
getattr(
|
| 170 |
)
|
| 171 |
for temporary in _RUNTIME_TEMPORARIES:
|
| 172 |
if temporary not in temporaries:
|
| 173 |
temporaries.append(temporary)
|
| 174 |
-
|
| 175 |
-
hashes = set(getattr(
|
| 176 |
hashes.add(RUNTIME_HASH)
|
| 177 |
-
|
| 178 |
_extend_loaded_package_paths(package_root)
|
| 179 |
-
return
|
| 180 |
|
| 181 |
def _import_without_bytecode(module_name):
|
| 182 |
previous = sys.dont_write_bytecode
|
|
@@ -187,13 +143,13 @@ def _import_without_bytecode(module_name):
|
|
| 187 |
sys.dont_write_bytecode = previous
|
| 188 |
|
| 189 |
def _install_runtime():
|
| 190 |
-
|
| 191 |
-
hashes = getattr(
|
| 192 |
if RUNTIME_HASH in hashes:
|
| 193 |
-
return
|
| 194 |
package_root = _ensure_runtime()
|
| 195 |
-
if
|
| 196 |
-
return _merge_runtime(
|
| 197 |
spec = importlib.util.spec_from_file_location(
|
| 198 |
"fastplms",
|
| 199 |
package_root / "__init__.py",
|
|
@@ -223,14 +179,14 @@ def _install_runtime():
|
|
| 223 |
return package
|
| 224 |
|
| 225 |
_install_runtime()
|
| 226 |
-
|
| 227 |
-
E1Config =
|
| 228 |
E1Config.__module__ = __name__
|
| 229 |
-
E1ForMaskedLM =
|
| 230 |
E1ForMaskedLM.__module__ = __name__
|
| 231 |
-
E1ForSequenceClassification =
|
| 232 |
E1ForSequenceClassification.__module__ = __name__
|
| 233 |
-
E1ForTokenClassification =
|
| 234 |
E1ForTokenClassification.__module__ = __name__
|
| 235 |
-
E1Model =
|
| 236 |
E1Model.__module__ = __name__
|
|
|
|
| 1 |
+
"""Generated bridge to the embedded FastPLMs runtime sources."""
|
| 2 |
|
| 3 |
import base64
|
| 4 |
import hashlib
|
|
|
|
| 6 |
import importlib.util
|
| 7 |
import sys
|
| 8 |
import tempfile
|
|
|
|
| 9 |
from io import BytesIO
|
| 10 |
from pathlib import Path
|
| 11 |
from zipfile import ZIP_DEFLATED, ZipFile
|
| 12 |
|
| 13 |
from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
|
| 14 |
|
| 15 |
+
if RUNTIME_HASH != "a8dda20d6a4a55e6808c7c2f2c9477c113edaa7d6ec6ef57f4a898cd47b73d48":
|
| 16 |
raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
|
| 17 |
|
| 18 |
_RUNTIME_TEMPORARIES = []
|
|
|
|
| 82 |
result[relative.as_posix()] = hashlib.sha256(path.read_bytes()).hexdigest()
|
| 83 |
return result
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
def _extend_loaded_package_paths(package_root):
|
| 86 |
for name, module in list(sys.modules.items()):
|
| 87 |
if name != "fastplms" and not name.startswith("fastplms."):
|
|
|
|
| 95 |
if candidate.is_dir() and candidate_text not in paths:
|
| 96 |
paths.append(candidate_text)
|
| 97 |
|
| 98 |
+
def _merge_runtime(package, package_root):
|
| 99 |
incoming = _runtime_file_hashes(package_root)
|
| 100 |
+
known = getattr(package, "__fastplms_artifact_runtime_files__", None)
|
| 101 |
+
if not isinstance(known, dict):
|
| 102 |
+
raise RuntimeError(
|
| 103 |
+
"A non-artifact fastplms module is already loaded. Load the Hub artifact "
|
| 104 |
+
"in a separate Python process."
|
| 105 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
conflicts = sorted(
|
| 107 |
relative
|
| 108 |
for relative, digest in incoming.items()
|
|
|
|
| 114 |
+ ", ".join(repr(path) for path in conflicts[:5])
|
| 115 |
+ ". Load incompatible releases in separate Python processes."
|
| 116 |
)
|
| 117 |
+
known = dict(known)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
known.update(incoming)
|
| 119 |
+
package.__fastplms_artifact_runtime_files__ = known
|
| 120 |
+
roots = list(getattr(package, "__fastplms_artifact_runtime_roots__", ()))
|
| 121 |
if str(package_root) not in roots:
|
| 122 |
roots.append(str(package_root))
|
| 123 |
+
package.__fastplms_artifact_runtime_roots__ = tuple(roots)
|
| 124 |
temporaries = list(
|
| 125 |
+
getattr(package, "__fastplms_artifact_runtime_temporaries__", ())
|
| 126 |
)
|
| 127 |
for temporary in _RUNTIME_TEMPORARIES:
|
| 128 |
if temporary not in temporaries:
|
| 129 |
temporaries.append(temporary)
|
| 130 |
+
package.__fastplms_artifact_runtime_temporaries__ = tuple(temporaries)
|
| 131 |
+
hashes = set(getattr(package, "__fastplms_artifact_runtime_hashes__", ()))
|
| 132 |
hashes.add(RUNTIME_HASH)
|
| 133 |
+
package.__fastplms_artifact_runtime_hashes__ = frozenset(hashes)
|
| 134 |
_extend_loaded_package_paths(package_root)
|
| 135 |
+
return package
|
| 136 |
|
| 137 |
def _import_without_bytecode(module_name):
|
| 138 |
previous = sys.dont_write_bytecode
|
|
|
|
| 143 |
sys.dont_write_bytecode = previous
|
| 144 |
|
| 145 |
def _install_runtime():
|
| 146 |
+
package = sys.modules.get("fastplms")
|
| 147 |
+
hashes = getattr(package, "__fastplms_artifact_runtime_hashes__", ())
|
| 148 |
if RUNTIME_HASH in hashes:
|
| 149 |
+
return package
|
| 150 |
package_root = _ensure_runtime()
|
| 151 |
+
if package is not None:
|
| 152 |
+
return _merge_runtime(package, package_root)
|
| 153 |
spec = importlib.util.spec_from_file_location(
|
| 154 |
"fastplms",
|
| 155 |
package_root / "__init__.py",
|
|
|
|
| 179 |
return package
|
| 180 |
|
| 181 |
_install_runtime()
|
| 182 |
+
_module_181 = _import_without_bytecode("fastplms.models.e1.modeling_e1")
|
| 183 |
+
E1Config = _module_181.E1Config
|
| 184 |
E1Config.__module__ = __name__
|
| 185 |
+
E1ForMaskedLM = _module_181.E1ForMaskedLM
|
| 186 |
E1ForMaskedLM.__module__ = __name__
|
| 187 |
+
E1ForSequenceClassification = _module_181.E1ForSequenceClassification
|
| 188 |
E1ForSequenceClassification.__module__ = __name__
|
| 189 |
+
E1ForTokenClassification = _module_181.E1ForTokenClassification
|
| 190 |
E1ForTokenClassification.__module__ = __name__
|
| 191 |
+
E1Model = _module_181.E1Model
|
| 192 |
E1Model.__module__ = __name__
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Direct runtime dependencies for Synthyra/Profluent-E1-600M.
|
| 2 |
+
# FastPLMs source is embedded in this model repository.
|
| 3 |
+
torch>=2.13,<2.14
|
| 4 |
+
transformers>=5.13,<5.14
|
| 5 |
+
huggingface-hub>=0.34,<2
|
| 6 |
+
tokenizers>=0.22,<0.23
|
| 7 |
+
safetensors>=0.5,<1
|
| 8 |
+
numpy>=1.26,<3
|
| 9 |
+
einops>=0.8,<1
|
| 10 |
+
tqdm>=4.67,<5
|
runtime-attestation.json
CHANGED
|
@@ -7,43 +7,44 @@
|
|
| 7 |
"LICENSES/e1/LICENSE": "sha256:8ef1dd556091544db3044164a8015424a3dcb3450fb3765a81b88463551bbe81",
|
| 8 |
"LICENSES/e1/MODIFICATIONS.md": "sha256:2506f47c0f5475af8e8ff2cff13eb8b79e8e25a08a054cdd617bf336536750ca",
|
| 9 |
"LICENSES/e1/NOTICE": "sha256:6de9db0320b4ee82f665c0951d8fd4cd53701a659c9dbce9bc3e3ea6afc4c6b3",
|
| 10 |
-
"README.md": "sha256:
|
| 11 |
"THIRD_PARTY_NOTICES.md": "sha256:25704b3c76404696cae52e7fca13088d329f70f412687340351259e86cd62baa",
|
| 12 |
-
"config.json": "sha256:
|
| 13 |
-
"fastplms/__init__.py": "sha256:
|
| 14 |
-
"fastplms/attention/__init__.py": "sha256:
|
| 15 |
-
"fastplms/attention/_core.py": "sha256:
|
| 16 |
-
"fastplms/attention/_kernel_lock.py": "sha256:
|
| 17 |
-
"fastplms/attention/interfaces.py": "sha256:
|
| 18 |
-
"fastplms/embeddings/__init__.py": "sha256:
|
| 19 |
-
"fastplms/embeddings/pooling.py": "sha256:
|
| 20 |
-
"fastplms/embeddings/runner.py": "sha256:
|
| 21 |
-
"fastplms/embeddings/storage.py": "sha256:
|
| 22 |
-
"fastplms/embeddings/types.py": "sha256:
|
| 23 |
"fastplms/models.toml": "sha256:1b0d92911222f31b435bfee80251c9abe20b85392d65f8af8d285d1ccfbbc3c5",
|
| 24 |
-
"fastplms/models/__init__.py": "sha256:
|
| 25 |
"fastplms/models/e1/__init__.py": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
| 26 |
-
"fastplms/models/e1/attention.py": "sha256:
|
| 27 |
-
"fastplms/models/e1/cache.py": "sha256:
|
| 28 |
-
"fastplms/models/e1/modeling_e1.py": "sha256:
|
| 29 |
-
"fastplms/models/e1/preparation.py": "sha256:
|
| 30 |
-
"fastplms/models/e1/retrieval.py": "sha256:
|
| 31 |
"fastplms/models/e1/tokenizer.json": "sha256:65d4345dbe908d3deb554891dc01b94e215fb17cd9e0bd0535e062e256737415",
|
| 32 |
-
"fastplms/models/ttt.py": "sha256:
|
| 33 |
-
"fastplms/registry.py": "sha256:
|
| 34 |
-
"fastplms/runtime.py": "sha256:
|
| 35 |
-
"fastplms_bundle.py": "sha256:
|
| 36 |
-
"modeling_fastplms.py": "sha256:
|
|
|
|
| 37 |
},
|
| 38 |
"model_id": "e1_600m",
|
| 39 |
"redistributable": true,
|
| 40 |
-
"release_tool_revision": "
|
| 41 |
-
"release_tool_sha256": "
|
| 42 |
-
"runtime_bundle_sha256": "
|
| 43 |
-
"runtime_revision": "
|
| 44 |
"schema_version": 2,
|
| 45 |
"scope": "runtime-only",
|
| 46 |
-
"source_tree_sha256": "
|
| 47 |
"weights": {
|
| 48 |
"repo_id": "Synthyra/Profluent-E1-600M",
|
| 49 |
"revision": "6c8bf0ec83b0e0178677c528b101efffd0677742"
|
|
|
|
| 7 |
"LICENSES/e1/LICENSE": "sha256:8ef1dd556091544db3044164a8015424a3dcb3450fb3765a81b88463551bbe81",
|
| 8 |
"LICENSES/e1/MODIFICATIONS.md": "sha256:2506f47c0f5475af8e8ff2cff13eb8b79e8e25a08a054cdd617bf336536750ca",
|
| 9 |
"LICENSES/e1/NOTICE": "sha256:6de9db0320b4ee82f665c0951d8fd4cd53701a659c9dbce9bc3e3ea6afc4c6b3",
|
| 10 |
+
"README.md": "sha256:f4e31044d720a5b058539f147dd9b1d4d85d34f2a6929dbf4d3684a07974b6de",
|
| 11 |
"THIRD_PARTY_NOTICES.md": "sha256:25704b3c76404696cae52e7fca13088d329f70f412687340351259e86cd62baa",
|
| 12 |
+
"config.json": "sha256:a679591daa125bbdf6689af936ebfb72b90a06d7b83c0b64845d7d0ed7b4f78d",
|
| 13 |
+
"fastplms/__init__.py": "sha256:8503e02debf24abc6d33c1913ff3eb9f245e63f7e62017e220d072c7393fdf2c",
|
| 14 |
+
"fastplms/attention/__init__.py": "sha256:ab3c0b6156968f418f3c97ea664959cb978f4137717b4362213bcfe25ab5e3d6",
|
| 15 |
+
"fastplms/attention/_core.py": "sha256:09d0f71a5ea2ad2138c9441f6a2f72bbf8e72729be41d8db737b163f36ce47da",
|
| 16 |
+
"fastplms/attention/_kernel_lock.py": "sha256:56455eb57cee9af08438eda52b775c9cff3cf994cadb9b116ce2ee10fd5f1801",
|
| 17 |
+
"fastplms/attention/interfaces.py": "sha256:6562a342961022fc2848d7004bd01fddaf8be8e08e782d0856e5c30a74232623",
|
| 18 |
+
"fastplms/embeddings/__init__.py": "sha256:cf0648de905a00ad16c0aece328eeb05f25739a4cf89b47b915e0f2b19c3ce26",
|
| 19 |
+
"fastplms/embeddings/pooling.py": "sha256:9bed8c466fce053333de9e25b04d1ab304f2954f403ffa0f5a3c749e8277b3ce",
|
| 20 |
+
"fastplms/embeddings/runner.py": "sha256:a7839763ad641a63d0e2671dcf34aa7b2a8114198950b64254188521b27453a8",
|
| 21 |
+
"fastplms/embeddings/storage.py": "sha256:98cf725d26766f32300f105345129ca6cf02ebd10628e746a7e71e107bc911a6",
|
| 22 |
+
"fastplms/embeddings/types.py": "sha256:c58f8c6a37a11a937c3f77b4e1617d5f213923d98ecaffddcdd283bd9dc42fe7",
|
| 23 |
"fastplms/models.toml": "sha256:1b0d92911222f31b435bfee80251c9abe20b85392d65f8af8d285d1ccfbbc3c5",
|
| 24 |
+
"fastplms/models/__init__.py": "sha256:d3dd084f5e8fafcf2d0fe4a26e4ab9284d3f9414b1f9a5bd86e308a406447c43",
|
| 25 |
"fastplms/models/e1/__init__.py": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
| 26 |
+
"fastplms/models/e1/attention.py": "sha256:114bff9ed29a9f98a91965a57b702413f4a0e84bdd4063e7047e03a49a61241a",
|
| 27 |
+
"fastplms/models/e1/cache.py": "sha256:b4d065b22b3be06f929eac451a0e5a4658ac3ad366fc6ee5e9ac195af821b434",
|
| 28 |
+
"fastplms/models/e1/modeling_e1.py": "sha256:bb1c963731b7277a67d444ea0df6ed340384a6fafe80a0042e943fb844ae26e0",
|
| 29 |
+
"fastplms/models/e1/preparation.py": "sha256:b207f85032a3fbac8494ec958ecdfa37de470f6996b8568da9374e2e7ee0598c",
|
| 30 |
+
"fastplms/models/e1/retrieval.py": "sha256:6f4ac4b9f21ea874d99b91ee1f366bc456af1c0060e678e3ee7cda4c2820cefd",
|
| 31 |
"fastplms/models/e1/tokenizer.json": "sha256:65d4345dbe908d3deb554891dc01b94e215fb17cd9e0bd0535e062e256737415",
|
| 32 |
+
"fastplms/models/ttt.py": "sha256:f1d7f9298a930f4c40f8e321ffa08b09d1c5b8492bca623bdc7bfd03ec85774d",
|
| 33 |
+
"fastplms/registry.py": "sha256:cb37799dae5a4c0c780089ab2cca4cc56300c1c5f515b275892aa4e1c4b97eb6",
|
| 34 |
+
"fastplms/runtime.py": "sha256:9521c37fcd168bf9a4137277fb73672403ccf30d70d4845aa879fb22f42ddeb6",
|
| 35 |
+
"fastplms_bundle.py": "sha256:c5015ec74cd70db2cd82ed30d675510757d26bcd18a184c73560f85a8b1e9f74",
|
| 36 |
+
"modeling_fastplms.py": "sha256:bb40b09e9d61bf567d0590cb22ae7a5f8f7fb74d3694fd87c42c862c7301ecf3",
|
| 37 |
+
"requirements.txt": "sha256:9dba69cb9fd6b1c083423aa17df29bb5f98d46e7189b8c5b2393a45501056ebf"
|
| 38 |
},
|
| 39 |
"model_id": "e1_600m",
|
| 40 |
"redistributable": true,
|
| 41 |
+
"release_tool_revision": "e6dd397a9ad368c998d714f6bd64d40b533d1ed1",
|
| 42 |
+
"release_tool_sha256": "6d335c05aa49a232086a816deb25d248d1490529e5783acc3355b9bc6f03e0c2",
|
| 43 |
+
"runtime_bundle_sha256": "a8dda20d6a4a55e6808c7c2f2c9477c113edaa7d6ec6ef57f4a898cd47b73d48",
|
| 44 |
+
"runtime_revision": "e6dd397a9ad368c998d714f6bd64d40b533d1ed1",
|
| 45 |
"schema_version": 2,
|
| 46 |
"scope": "runtime-only",
|
| 47 |
+
"source_tree_sha256": "9e3cbab448ffde4fbbba79f17cf0321645584acd534c5f96c78b1017d9f3806b",
|
| 48 |
"weights": {
|
| 49 |
"repo_id": "Synthyra/Profluent-E1-600M",
|
| 50 |
"revision": "6c8bf0ec83b0e0178677c528b101efffd0677742"
|