seqmask
Composable MLM (masked language modeling) corruption strategies with curriculum schedulers, diffusion-style timestep schedules, and reference-driven position focusing. Pure NumPy on the inside; accepts and returns either NumPy arrays or CPU PyTorch tensors transparently.
Built primarily for protein-sequence pretraining but tokenizer-agnostic:
the caller supplies mask_token_id, valid_token_ids, and
special_token_ids, so the same machinery works for any vocabulary.
Installation
From source:
git clone <repo-url>
cd seqmask
pip install -e .
With dev dependencies (pytest):
pip install -e ".[dev]"
Requires Python 3.9+. Depends on numpy and torch or jax (Whatever you are using).
Quick start
import os
os.environ["SEQMASK_BACKEND"] = "torch" # or `jax`
import torch
from seqmask import BetaDistributionMasking
from seqmask import backend
seqmask = BetaDistributionMasking(
mlm_prob=0.15,
mlm_concentration=10.0,
masking_prob=0.8, # 80% of selected positions -> [MASK]
mutation_prob=0.1, # 10% -> random valid token
# remaining 10% -> kept as-is
mask_token_id=103,
valid_token_ids=list(range(10, 200)),
special_token_ids=[0, 1, 2],
)
x = torch.randint(10, 200, (4, 128))
corrupted, labels = seqmask(x, seed=42)
# `corrupted` is the model input; `labels` is -100 outside selected
# positions and holds the original token id at selected positions.
Masking strategies
All four implement the same __call__(sequences, seed=None, excluded_ids=None, focus_positions=None) contract
(DiffusionMasking additionally takes timestep). Inputs and outputs
match types — NumPy in, NumPy out; CPU torch in, CPU torch out.
CategoricalMasking
Sampler-driven per-call rates. Use this when you want curriculum
schedulers (LinearScheduler, EMAScheduler, StagedLinearScheduler)
to steer the masking rate.
from seqmask import (
CategoricalMasking, MLMProbabilitySampler, LinearScheduler,
)
scheduler = LinearScheduler(
initial_weights=[1.0, 0.0, 0.0],
final_weights=[0.0, 0.0, 1.0],
total_steps=10_000,
)
sampler = MLMProbabilitySampler(
mlm_probs=[0.15, 0.20, 0.30],
masking_probs=[0.8, 0.8, 0.8],
mutation_probs=[0.1, 0.1, 0.1],
scheduler=scheduler,
)
seqmask = CategoricalMasking(
mlm_prob_sampler=sampler,
mask_token_id=103,
valid_token_ids=list(range(200)),
special_token_ids=[0, 1, 2],
)
corrupted, labels = seqmask(x)
sampler.step() # advance the curriculum
BetaDistributionMasking
Samples the per-call masking rate from Beta(mean, concentration).
Simplest "randomize the rate" option — no scheduler needed.
HybridSpanMasking
Mix of scattered token masks and contiguous spans (SpanBERT-style).
Per-call: Beta-sample the total rate and the span share alpha; fill
the span budget with Geometric(span_length_p)-length spans clipped at
specials, then top up with scattered positions to hit the target rate.
from seqmask import HybridSpanMasking
seqmask = HybridSpanMasking(
mlm_prob=0.15,
mlm_concentration=10.0,
span_fraction_mean=0.5, # ~half of masks come from spans
span_fraction_concentration=10.0,
span_length_p=0.2, # mean span length = 1/p = 5
max_span_length=10,
masking_prob=0.8,
mutation_prob=0.1,
mask_token_id=103,
valid_token_ids=list(range(200)),
special_token_ids=[0, 1, 2],
)
DiffusionMasking
Mask-only corruption (no 80/10/10 split) where the rate is a function of
a per-call timestep. Used for discrete-diffusion MLM (D3PM / MaskGIT).
Schedules are classes — they pickle cleanly under spawn-based
multiprocessing (PyTorch DataLoader with num_workers > 0).
from seqmask import DiffusionMasking, CosineDiffusionScheduler
seqmask = DiffusionMasking(
total_timesteps=100,
mask_token_id=103,
special_token_ids=[0, 1, 2],
schedule=CosineDiffusionScheduler(), # or "linear" / "cosine" / "sqrt"
)
t = 42
corrupted, labels = seqmask(x, timestep=t)
Available schedulers: LinearDiffusionScheduler,
CosineDiffusionScheduler, SqrtDiffusionScheduler, or any
DiffusionScheduler subclass (__call__(timestep, total_timesteps) -> float).
Strings "linear" / "cosine" / "sqrt" are convenience aliases that
resolve to the corresponding scheduler instance.
Focus positions
Bias masking toward specific positions — useful when fine-tuning on
variants relative to a reference (wildtype proteins, edited code, etc.).
All per-token strategies (Categorical, Beta, Diffusion) accept
focus_positions as a (B, L) boolean mask per call.
from seqmask import (
BetaDistributionMasking,
focus_positions_from_reference,
)
from seqmask import backend
reference = backend.to_tensor([10, 20, 30, 40, 50])
variants = backend.to_tensor([
[10, 20, 30, 40, 50], # unchanged
[10, 99, 30, 99, 50], # mutated at positions 1 and 3
])
focus = focus_positions_from_reference(variants, reference)
# tensor([[False, False, False, False, False],
# [False, True, False, True, False]])
seqmask = BetaDistributionMasking(
mlm_prob=0.1,
mlm_concentration=10.0,
masking_prob=0.8,
mutation_prob=0.1,
mask_token_id=103,
valid_token_ids=list(range(200)),
special_token_ids=[0],
focus_strategy="multiplicative", # or "force_include" / "weighted"
focus_strength=3.0,
)
corrupted, labels = seqmask(variants, focus_positions=focus)
The three focus strategies:
focus_strategy |
Semantic |
|---|---|
"force_include" |
Focus positions are unconditionally selected (specials still win). |
"multiplicative" |
Focus positions are sampled with min(1, mlm_prob * focus_strength). |
"weighted" |
Preserves the total mask budget; redistributes via weighted sampling. |
focus_positions_from_reference(sequences, reference) handles broadcasting:
(L,), (1, L), and (B, L) references all work; output type matches
sequences (NumPy or torch).
Schedulers (for curriculum sampling)
Use these with MLMProbabilitySampler to advance the masking rate over
training steps. They share a SharedCounter so multiprocessing workers
see a consistent step count.
LinearScheduler(initial_weights, final_weights, total_steps)EMAScheduler(initial_weights, final_weights, beta=0.999, multiplier=1.0)StagedLinearScheduler(num_stages, total_steps)— sequentially introduces each candidate, ramping its weight from 0 to equal share.
Call sampler.step() (or scheduler.step()) once per training step.
DataLoader integration
All seqmasks, samplers, and schedulers are picklable, so they can be
attached to a Dataset and used with DataLoader(num_workers > 0)
under both fork and spawn start methods. For DiffusionMasking,
prefer the DiffusionScheduler subclasses (or string aliases) over
lambdas — lambdas aren't picklable under spawn.
Validation
Every constructor that takes weights or probabilities calls
ensure_non_negative from seqmask.utils. Negative values raise
ValueError at construction time; out-of-range sampler outputs raise
at call time. There's no silent clamping.
Layout
seqmask/
scheduler/ # base Scheduler + Linear / StagedLinear / EMA
masking/ # CategoricalMasking, BetaDistributionMasking,
# HybridSpanMasking, DiffusionMasking +
# DiffusionScheduler hierarchy + focus helper
sampler.py # MLMProbabilitySampler
utils.py # ensure_non_negative, seed_from_components,
# create_default_mlm_weights
Testing
pip install -e ".[dev]"
pytest
License
Apache 2.0 — see LICENSE.
Metadata
Release files for seqmask 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| seqmask-1.0.0.tar.gz | 31.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| seqmask-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 58.1 kB
Release files / seqmask-1.0.0.tar.gz
| Download URL | seqmask-1.0.0.tar.gz |
|---|---|
| Size | 31.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
45838c2a9160b88ca06daa7a6f879944fdf74b0cc306cfb0bfe074fc5f044411
|
|
BLAKE2b-256 checksum How to use checksums |
30eb312a618e7a55ee2a7c79b755a8ecd512c0661ef5e6beec2b1e0c7b28aa61
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 3, 2026.
Transparency logRelease files / seqmask-1.0.0-py3-none-any.whl
| Download URL | seqmask-1.0.0-py3-none-any.whl |
|---|---|
| Size | 26.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dce07e310ce5257584659655dfc59b69ca063e8e534940af2f3cdd918311bca1
|
|
BLAKE2b-256 checksum How to use checksums |
73864dd4e37e0be4737e4c139459711765825675a407dd8feb1038280b38e66c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 3, 2026.
Transparency log