Embedding API

August 4, 2026 ยท View on GitHub

Dependencies and platform requirements

The shared sequence embedding API requires Python 3.11-3.14, PyTorch 2.13, and Transformers 5.13. Install these dependencies. Transformers loads the runtime source from the pinned Hugging Face model repository:

python -m pip install \
  "torch>=2.13,<2.14" \
  "transformers>=5.13,<5.14"

Core tokenizer-mode embeddings run on CPU or CUDA. E1 uses its raw-sequence adapter rather than a tokenizer. Structure models and optional FlashAttention backends require the additional dependencies and CUDA platforms declared in the support matrix.

Quick start

Published models expose model.embed_dataset(...). A source checkout also exposes the same implementation as fastplms.embed_dataset(model, ...) when run with PYTHONPATH=src. This minimal Hugging Face example returns one mean-pooled vector per sequence:

from transformers import AutoModel

model = AutoModel.from_pretrained(
    "Synthyra/ESM2-150M",
    trust_remote_code=True,
).eval()
result = model.embed_dataset(
    [
        ("protein-a", "MSTNPKPQRKTKRNT"),
        ("protein-b", "MKTIIALSYIFCLVFA"),
    ],
    batch_size=2,
    pooling=("mean",),
)
print(result[0].id, result[0].tensor.shape)

The operation accepts sequences, (id, sequence) pairs, EmbeddingInput values, an insertion-ordered {id: sequence} mapping, or a FASTA path. It preserves input order, mapping and FASTA identifiers, and duplicate records.

Argument reference

ArgumentMeaning
inputsSequence iterable, (id, sequence) pairs, EmbeddingInput values, {id: sequence} mapping, or FASTA path
batch_sizeNumber of records prepared together; must be positive
poolingOne pooler or an ordered pooler sequence; None selects mean unless full_embeddings=True
full_embeddingsReturn residue-level tensors instead of pooled vectors
outputSafetensors directory or SQLite file; omit for in-memory results
formatsafetensors or sqlite when output is set
resumeReuse an exact compatible ordered prefix when persistent output exists
tokenizerExplicit tokenizer override for a compatible tokenizer-mode family
max_lengthOptional maximum number of biological residues, excluding tokenizer-added special tokens
truncateTruncate biological residues to max_length; when false, an over-length record raises
batch_window_sizeBounded number of records eligible for stable length bucketing; defaults to 16 * batch_size
max_tokens_per_batchOptional padded biological-residue budget for one inference batch
dtypeOutput tensor dtype; None retains the model output dtype
shard_sizeTarget safetensors shard size in bytes
model_state_fingerprintCaller-supplied state identity for offloaded or externally managed models
**model_kwargsFamily-specific embedding controls such as hidden-state selection

store_all_hidden_states=True is a model keyword and requires full_embeddings=True. full_embeddings=True cannot be combined with an explicit pooler. The output format and every invalid argument combination are validated before input hashing, model inference, or output creation.

Bounded streaming and length policy

The runner reads FASTA input line by line into an immutable incrementally fingerprinted spool. It never reads the complete FASTA file into memory. It keeps one bounded batch_window_size group for length bucketing, applies max_tokens_per_batch to the padded biological-residue count, and restores source order. If omitted, the window is sixteen times the batch size. An explicit value takes precedence. Result metadata and the resume fingerprint record the resolved window. Persistent outputs store result descriptors once and keep tensor payloads lazy.

SQLite prefixes commit at completed batch-window boundaries. Safetensors packs windows into bounded shards and publishes a resumable prefix whenever a shard flushes; an interruption replays the unflushed in-memory shard. Set batch_window_size=batch_size when per-batch inference boundaries matter more than the default padding-efficiency lookahead.

result.metadata["batching"]["resume_commit_granularity"] records "batch-window" for persistent SQLite, "shard-flush" for persistent safetensors, and "not-applicable" for an in-memory result. A new or replacement SQLite run remains staged or deferred and does not replace the default readable run until its first batch window commits.

max_length always counts amino-acid residues. Tokenizer-mode families add their required BOS, EOS, or modality boundary width when constructing the model token budget. truncate=False does not silently exceed the model contract: an input longer than max_length raises with the record position and identifier.

Result types

The following source-level imports are for contributor workflows run with PYTHONPATH=src; Hugging Face users can pass (id, sequence) pairs directly to the model method.

from fastplms import EmbeddingInput

inputs = [
    EmbeddingInput("a", "MSTNPKPQRKTKRNT"),
    EmbeddingInput("a", "MKTIIALSYIFCLVFA"),
]
result = model.embed_dataset(inputs, batch_size=2)

for record in result.records:
    print(record.id, record.sequence, record.tensor)

EmbeddingRecord(id, sequence, tensor) is ordered and retains the original sequence. EmbeddingResult(records, metadata) is sequence-like. Persisted records may hold a LazyTensorReference; call record.load_tensor() to load that tensor.

result.as_dict(key="id") raises when keys repeat. Callers must explicitly choose a duplicate policy if they want first or last. This prevents silent loss of repeated FASTA identifiers.

Biological-residue policy

Models return a representation X and a biological residue mask M:

X: (b, l, d)
M: (b, l)

Pooling includes positions where M is true. BOS, EOS, padding, chain delimiters, and non-protein structure tokens are excluded. E1 derives M from its native raw-sequence preparation because it has no tokenizer. DPLM2 accepts raw amino-acid sequences through a model adapter that adds its modality-specific boundaries and invokes the exact tokenizer with add_special_tokens=False. Each persisted run records the token policy and tokenizer metadata.

Pooling

The supported operations are:

NameTransformationLimitation
meanArithmetic mean over valid residuesNone
maxElementwise maximum over valid residuesNone
normElementwise L2 norm across valid residuesNone
medianElementwise median over valid residuesMore expensive than mean
stdElementwise population standard deviationRequires at least one residue
varElementwise population varianceRequires at least one residue
clsModel-defined classification positionRejected without meaningful CLS semantics
partiAttention-graph weighted residue summaryEager only and at most 2,048 residues

Multiple poolers are concatenated in request order. Metadata records the output slice for each operation. parti uses Torch power iteration with damping 0.85, tolerance 1e-6, and at most 100 iterations. It requires an explicit attn_implementation="eager" because it materializes the attention graph.

result = model.embed_dataset(
    inputs,
    batch_size=8,
    pooling=("mean", "max", "std"),
)
print(result.metadata["pool_slices"])

Choose poolers for the downstream object. mean gives a sequence summary, max shows large per-feature responses, and std or var describes within-sequence dispersion. Concatenating poolers increases output width. It is a feature-design decision, not a free accuracy improvement.

Full residue embeddings

full_embeddings=True returns one ragged residue tensor per input and cannot be combined with pooling:

result = model.embed_dataset(
    inputs,
    batch_size=4,
    full_embeddings=True,
)

Each tensor has shape (l_i, d), where l_i is the number of retained biological residues for record i. Padding is never persisted as a residue embedding.

Passing store_all_hidden_states=True requires full_embeddings=True and returns one tensor with shape (n, l_i, d) per input, where n follows the model's hidden-state output order. The biological residue mask is applied only to the token axis. Safetensors and SQLite preserve this rank without flattening the state axis.

ESMFold2 returns the learned projection with shape (l_i, 256). Its dataset path accepts only single-chain sequences and FASTA records and supports the residue-statistic poolers. It rejects cls and parti.

ANKH encoder and decoder layers

The Synthyra ANKH repositories contain the complete encoder-decoder checkpoints. AutoModel exposes the encoder view and AutoModelForSeq2SeqLM exposes the full sequence-to-sequence view.

ANKH defaults to the encoder final state:

encoder = model.embed_dataset(
    inputs,
    hidden_state_source="encoder",
    hidden_state_index=-1,
    full_embeddings=True,
)

hidden_state_index is applied to the selected stack, and store_all_hidden_states=True stores every state from that stack. Decoder extraction requires the full AutoModelForSeq2SeqLM view and exactly one explicit aligned decoder_inputs sequence or decoder_input_ids tensor:

Use raw protein strings such as MSTNPK, not space-separated residues. Decoder sentinels must be adjacent to their residues, as in M<extra_id_0>. FastPLMs applies this normalization consistently to the model-owned tokenizer and an explicitly supplied tokenizer object.

decoder = seq2seq.embed_dataset(
    inputs,
    hidden_state_source="decoder",
    decoder_inputs=["M<extra_id_0>" for _ in inputs],
    hidden_state_index=-1,
    full_embeddings=True,
)

There is no implicit shifted-source decoder input. Official ANKH tasks use task-dependent prompts, sentinels, or generated tokens. A decoder_attention_mask is valid only with decoder_input_ids. Decoder pooling uses the decoder biological mask and excludes start, EOS, padding, sentinel, and other tokenizer-special positions. Metadata records stack, layer, decoder input and mask fingerprints, input-position alignment, and mask policy.

E1 MSA-aware embeddings

E1 keeps its native raw-sequence and retrieval preparation, but returns the same ordered, duplicate-preserving EmbeddingResult as the shared embedding API. Record IDs are the zero-based input positions, so repeated query sequences remain independently addressable as "0", "1", and so on.

result = model.embed_dataset_with_msa(
    [query, query],
    msa_lookup={query: "/data/query.a3m"},
    batch_size=2,
    max_len=len(query),
    pooling_types=["mean"],
    seed=7,
    batch_window_size=2,
    max_tokens_per_batch=2 * len(query),
    output="e1-msa.sqlite",
    format="sqlite",
    resume=True,
)
assert [record.id for record in result] == ["0", "1"]

max_len is measured in biological residues. matrix_embed=True selects full residue output. output, format, resume, shard_size, and model_state_fingerprint have the same persistence and compatibility meaning as ordinary dataset embedding. Local A3M input is offline; homology search and Hub MSA acquisition are separate, explicit networked workflows.

Safetensors storage

With format="safetensors", output names an output directory. FastPLMs writes generation-scoped shards and then transactionally publishes:

output/
  embeddings-run-<generation>-00001.safetensors
  embeddings-records-run-<generation>-00001.jsonl
  embeddings-index-run-<generation>-00001.json
  index.json
  run.json

The default maximum shard size is 2 GiB. Tensors are packed across inference batches and written one shard at a time, so the complete tensor dataset is never materialized in host memory. Each flushed shard publishes an incomplete ordered prefix that a matching resume=True call can continue. An interrupted, unflushed shard is recomputed. Generation descriptors preserve record position, identifier, sequence, shape, dtype, tensor hash, and shard key. Loading the result creates lazy references rather than reading every shard into memory. run.json is the transactional commit marker. It points to one immutable generation index by filename and SHA-256 digest and is atomically replaced only after that index, its descriptor shards, and every tensor shard are durable. index.json is a non-authoritative convenience pointer; reopening follows run.json even when the convenience pointer is missing or interrupted.

Successful overwrites retain earlier immutable generation indexes, descriptors, and tensor shards. This is required because an EmbeddingResult opened before the overwrite resolves lazy tensors through the earlier paths. FastPLMs does not guess when those readers are released. Preview stale generations. Then collect them only after you confirm that no reader or writer for the output is active:

from fastplms.embeddings import garbage_collect_safetensors_generations

stale = garbage_collect_safetensors_generations("output")  # dry run
garbage_collect_safetensors_generations(
    "output",
    dry_run=False,
    confirm_no_active_readers_or_writers=True,
)

Destructive collection invalidates any older EmbeddingResult, EmbeddingRecord, or LazyTensorReference that still names a collected shard. It also removes abandoned generation files from interrupted writers. Never run it concurrently with embedding, overwrite, resume, or result retrieval.

SQLite streaming, retrieval, and resume

Use format="sqlite" when a long run should commit each batch:

result = model.embed_dataset(
    inputs,
    batch_size=16,
    output="embeddings.sqlite",
    format="sqlite",
    resume=True,
)

Tensor payloads store raw bytes and an explicit dtype, so BF16 is lossless. Each completed batch window is committed transactionally. Resume is allowed only when the full run fingerprint matches and existing records form the exact ordered prefix of the request.

SQLite keeps runs under their full fingerprint. With resume=False, a new or restarted run becomes the default result as soon as its first batch commits; other fingerprints remain available through run_id. An interrupted overwrite therefore exposes a resumable incomplete prefix while retaining the previous complete run. This is batch-transactional behavior, not the full-run atomic replacement provided by safetensors generations.

Reopening uses SQLite read-only mode. Filtered retrieval accepts exactly one ordered selector and preserves request order and duplicates:

from fastplms.embeddings import load_sqlite_result

selected = load_sqlite_result(
    "embeddings.sqlite",
    record_ids=["protein-b", "protein-a", "protein-b"],
)
print([record.id for record in selected])

Selectors are positions, record_ids, or sequences; run_id may select a specific compatible run. A writable connection is never opened by the result reader.

Convert an older FastPLMs SQLite database once, then use the current read-only reader:

from fastplms.embeddings import convert_legacy_sqlite

convert_legacy_sqlite("legacy.sqlite", "embeddings-v1.sqlite")

Compact and weights-only tensor blobs convert without pickle. An unsupported pickle payload is rejected unless allow_unsafe_pickle=True is explicitly set for a trusted source.

Run metadata

Persisted results include:

  • model ID, immutable revision, checkpoint hash, and package versions;
  • Torch and Transformers versions, backend/device policy, checkpoint identity, and adapter identity;
  • tensor dtype and resolved attention backend;
  • selected layer or projection;
  • tokenizer and biological-residue policy;
  • pooling names and output slices;
  • truncation settings;
  • input and complete-run fingerprints;
  • fingerprint schema version and exact model-state fingerprint;
  • generation-indexed output tensor shapes and SHA-256 hashes.

When a model is loaded from dist/hub/<model>, Transformers does not assign a Hub commit to config._commit_hash. The artifact therefore carries packaging-only model ID, checkpoint repository, immutable revision, and checkpoint-identity hash fields. Embedding metadata and resume fingerprints use those fields as the fallback, so local offline runs retain complete traceability. The packaging fields are excluded from semantic configuration parity.

Run-fingerprint schema v3 binds the current bytes, names, dtypes, and shapes of each model parameter and persistent buffer. State tensors are copied to CPU in bounded chunks. The digest is recomputed from authoritative bytes for each persisted run. FastPLMs does not trust object identity, autograd version counters, or cached state digests. A mutation through Parameter.data or another storage alias changes the model-state digest and resume identity. Changing any material input, model state, or setting prevents resume into an incompatible output. Results from older fingerprint schemas cannot resume.

Models with meta-device tensors, custom offloading, or an externally managed state identity may pass the keyword-only model_state_fingerprint override. The caller is responsible for changing this value whenever the effective model state changes; metadata records whether the identity was computed or supplied by the caller.

Legacy .pth files

FastPLMs never writes pickle-based .pth embeddings. A read-only importer is available for existing files only when the caller explicitly enables unsafe pickle loading. Treat such files as executable input and use the opt-in only for trusted data. Convert imported records to safetensors or SQLite immediately.