Share one masked-marginal engine across the masked models and score the leaderboard with the wild-type fill - #21
Open
Leo-T-Zang wants to merge 9 commits into
Open
Share one masked-marginal engine across the masked models and score the leaderboard with the wild-type fill#21Leo-T-Zang wants to merge 9 commits into
Leo-T-Zang wants to merge 9 commits into
Conversation
Every masked-marginal score masks a mutated position and sums a log-odds over the variant's mutations; the published conventions differ in what fills the variant's other mutated positions while one is masked, which Meier et al. 2021 (ESM-1v) supplement Appendix A defines as wild-type, mask, mutant and allele-matched fills. Add fitness/baselines/masked_lm, which builds a deduplicated context bank for all four at once and accumulates them in a single pass, so the four together cost about 19% more unique context examples than the mutant fill alone (2,929,196 against 2,458,521 on the 31 ncRNA assays). Compute the four together because match-fill cannot be recovered from the final mut-fill and wt-fill scores: it needs their per-position halves. Reduce each model script to an adapter of about 100 lines supplying an alphabet, a tokenization and one forward pass; the four scorers lose 1,559 lines of duplicated masking, batching and IO. Take the wild type from RAW_CONSTRUCT_SEQ and independently recover it by reverting each variant's mutations, and stop if they disagree, since three of the four strategies condition on it. Reject a tokenizer that maps a base to the unknown token, which is the bug the released RiNALMo scores carried, and assert that a scored position is masked, that context and token positions differ by a constant shift, and that all four strategies cover the same variants. Write a manifest beside each prediction file recording the strategies, columns, alphabet, dtype, checkpoint, counts, a hash of the scoring source and the GPU, because bfloat16 scores differ between GPU models. Add tests/test_masked_lm.py, which checks each strategy against a per-variant reference implementation on a stand-in model, needs no checkpoint or GPU, and pins each adapter's alphabet, column, batching and dtype defaults.
One scoring run now writes all four fill strategies into one folder as separate
columns, but combine_csv_data used the model name as folder, lookup key and
output column at once, so a folder could hold only one score.
Resolve each entry through resolve_source: a bare column name keeps the previous
meaning, and a {folder, column} entry lets several entries read different
columns of the same prediction files.
Register the eight masked checkpoints' four strategies with four_fill_entries,
kept out of ALL_MODELS so a default merge still produces one row per model.
Add --models to merge a subset, and fail on an unknown entry or on a prediction
file that lacks its configured column.
Adopt wt-fill, the convention the ESM and ProteinGym reference implementations use under the name masked-marginals, in place of the mutant fill the previous table used. Choose it on that provenance rather than on the scores: wt-fill has the highest observed macro on 7 of 8 checkpoints, but a paired assay bootstrap separates it from mask-fill on only 2 and from match-fill on only 1, while mut-fill is the weakest of the four on every checkpoint. Rescore AIDO.RNA, RNAGenesis, RiNALMo and RNA-FM, which all rise: AIDO.RNA takes second place from RNA-ERNIE and Evo 2 (7B), and RNA-FM passes Nucleotide Transformer. Note that RNA-ERNIE is the one masked model not on this convention, since it needs a paddlepaddle environment, so its score is carried over unverified. Publish all four strategies in four_fill_sensitivity.csv, and record that their differences sit almost entirely in the tRNA assays, which the macro weighting amplifies: under an unweighted assay mean wt-fill leads on only 3 of 8. Replace the claim that AIDO.RNA improves at every size step, since under wt-fill the 650M checkpoint scores above the 1.6B. Add fitness/analyze_fill_strategies.py, which reproduces the sensitivity table, the category spreads and the bootstrap intervals from the prediction files.
The page said to reproduce the aggregate with performance_fitness.py, but that script returns the absolute Spearman, a direction-folded AUC and an absolute MCC, so it cannot produce the signed values the leaderboard publishes. Say so, and point at analyze_fill_strategies.py, which computes the signed per-assay Spearman and the category macro from the prediction files. Leave performance_fitness.py itself alone: adding a signed option changes a shared aggregation used by the other tables and belongs in its own change.
The five AIDO.RNA sizes were split between the leaderboard, which carried only the 1.6B, and a side table, which is an odd division for five models scored on the same assays with the same convention. Fold all five into leaderboard_signed_3ncRNA.csv and drop aido_rna_scaling.csv. Say that the top three entries are a tie: AIDO.RNA (650M), Evo 2 (40B) and AIDO.RNA (1.6B) span 0.0093, inside the roughly 0.01 band that 31 assays cannot resolve, which the Notes section already states. Keep the observation that the size series stops improving after 650M, and that whether it is monotone depends on the fill strategy, as prose rather than as a second table.
Now that all five AIDO.RNA sizes are leaderboard entries, eight of the sixteen rows are masked checkpoints, so calling them the four rescored rows was ambiguous.
The script returned the absolute Spearman, so it credited a model whose scores anti-correlate with fitness exactly as much as one that correlates, which is the distinction the signed metric adopted in v0.1.1 exists to make, and it therefore could not produce the numbers the leaderboard publishes. Return the signed value. Its per-category means now reproduce this page's category columns for all eight masked checkpoints to floating-point precision, with the macro their unweighted mean. Leave AUC and MCC folded onto their better direction for now, and say so in the docstring: it is the same conflation, but unfolding them changes published numbers this change is not otherwise touching. Drop four_fill_sensitivity.csv, whose contents are the table on the leaderboard page and are regenerated by analyze_fill_strategies.py. Note that tests/test_fitness.py compares against result files published before this fix, so it will fail on any assay where a model anti-correlates until those artifacts are regenerated.
fitness/README.md still described the Spearman as absolute, which was the last place in the repository saying the active metric ignores direction. Add a test that a reversed prediction returns a negative Spearman equal in magnitude to the forward one, so that reverting to an absolute value fails rather than passing silently.
The canonical entries still pointed at the superseded prediction folders and
columns, so a default merge followed by performance_fitness.py reproduced the
old mutant-fill numbers rather than the leaderboard, which is the kind of quiet
disagreement this change set exists to remove.
Point RNA-FM, RiNALMo, AIDO.RNA at all five sizes and RNAGenesis at their
{name}_4fill folder and their wt_fill column. The {name}_{strategy} entries
still read the other three fills from those same files.
Verified end to end: merging the released predictions with these folders and
running performance_fitness.py --type all reproduces every one of the sixteen
leaderboard rows to at most 8.3e-17.
Note that the masked models were rescored on the 31 non-coding assays only, so
they now report NaN on the mRNA assays rather than a score from a superseded
convention.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
The four masked language models (RNA-FM, RiNALMo, AIDO.RNA, RNAGenesis) were scored by four
near-duplicate scripts that hardcoded one masked-marginal convention. This replaces them with one
shared engine that implements the four conventions the literature defines, and moves the leaderboard
onto the one the reference implementations use.
The four fill strategies
Notation
For a variant with mutated positions$M$ , write $x^{wt}$ for the wild-type sequence and $x^{mt}$ for$i \in M$ , $wt_i$ is the wild-type base and $mt_i$ $x_{-i}$ for a sequence with a mask token at position $i$ , and $x_{-M}$ for$M$ . Finally $p(x_i = b \mid c)$ is the masked language model's$b$ at position $i$ given context $c$ .
the variant's own sequence. At a mutated position
the mutant base. Write
one with a mask at every position in
probability of base
Every masked-marginal score masks a mutated position, reads a log-odds between the mutant and$i$ is
wild-type base there, and sums over the variant's mutations. The four conventions differ in exactly
one thing: what the model sees at the variant's OTHER mutated positions while position
masked. All four are defined in Meier et al. 2021 (ESM-1v), supplement Appendix A.
1.
wt-fill, wild-type bases at the other mutated sitesBoth terms are read from the same context, so each summand is a genuine log-odds ratio. The context$i$ , so a multi-mutant's score is exactly the sum of its
never depends on the variant, only on
constituent single-mutant scores: the model is additive by construction and cannot express epistasis.
It is also by far the cheapest, needing one context per distinct mutated position of the whole assay
(1,697 across the 31 ncRNA assays) rather than one per variant.
This is what the ESM authors'
examples/variant-prediction/predict.pyand ProteinGym'sproteingym/baselines/esm/compute_fitness.pyimplement under the option namemasked-marginals, andit is the WT-LLR of the RNA zero-shot literature. The leaderboard adopts it on that provenance.
2.
mask-fill, masks at the other mutated sitesEvery mutated position is masked at once and the rest of the sequence is wild type. Both terms again
share one context, so this too is a log-odds ratio. The model is told that the other mutated
positions changed but not what they changed to, so it marginalises over them rather than conditioning
on them. One context per distinct mutated-position set. This is the formula written in the ESM paper,
and its supplement calls it strategy (a).
3.
mut-fill, mutant bases at the other mutated sitesPosition$i$ is masked in the variant's own sequence, so the variant's other mutations remain visible.$i$ depends on the actual genetic
Both terms share a context, so it is a log-odds ratio, and it is the only one of the four that can
express epistasis, since the conditional distribution at
background. It is also the most expensive, needing one context per (variant, position) pair
(2,458,521 across the 31 assays). Supplement strategy (c). This is the convention the previous
leaderboard used.
4.
match-fill, the fill matches the allele being scoredThe mutant term is read in the variant's context and the wild-type term in the wild type's, so the
two terms come from different contexts and this is not a log-odds ratio. It is a difference of two
conditionals, effectively a mutation-site-restricted difference between the variant's and the wild
type's pseudolikelihood contributions. Supplement strategy (b).
A worked example
Wild type
ACGUACGUACGUwith the double mutantA1C, G3U, so the variant isCCUUACGUACGU.Scoring position 1, with
#the mask token:wt-fill#CGUACGUACGUG, wild typemask-fill#C#UACGUACGU#, maskedmut-fill#CUUACGUACGUU, mutantmatch-fill#CUUACGUACGUand#CGUACGUACGUPosition 3 is then scored the same way, and the two contributions are summed.
Two properties that follow
All four are identical on single mutants. If$|M| = 1$ there are no other mutated positions, so
$x^{mt}{-i} = x^{wt}{-i} = x^{wt}_{-M}$ and the four expressions coincide term by term. This is used
as a fixture test: on
Pitt_2010_ribozyme, which is entirely single mutants, all four must returnidentical scores, and they do for all eight checkpoints.
They diverge on essentially everything else here, because 99.4% of the ncRNA variants are
multi-mutants.
Why the four are computed together
Write the four half-sums
so that$s_{\text{mut-fill}} = A - B$ , $s_{\text{wt-fill}} = C - D$ and $s_{\text{match-fill}} = A - D$ .$A - B$ and $C - D$ does not determine $A - D$ : adding a constant to both $A$ and $B$ leaves
Knowing
mut-fillunchanged while movingmatch-fill.match-filltherefore cannot be recovered from theother strategies' final scores, only from their per-position halves, which is why the engine
computes all four in one pass rather than offering them as separate runs.
Terminology hazard
The name is overloaded, and the overloading has already produced wrong numbers in this repository.
The ESM and ProteinGym code option called
masked-marginalsiswt-fill; the formula written in theESM paper is
mask-fill; andwt-marginalsis a different method entirely, a single unmaskedforward pass of the wild type with no masking at all, which is what the previously released RNA-FM
predictions turned out to be.
Leaderboard effect
Every masked model rises, because
mut-fillwas the weakest of the four conventions. AIDO.RNA (1.6B)0.1824 to 0.2070, RNAGenesis 0.1498 to 0.1825, RiNALMo 0.1391 to 0.1690, RNA-FM 0.1014 to 0.1293,
which passes Nucleotide Transformer. The autoregressive models, NT, Orthrus and EVmutation are
untouched, since masked marginals do not apply to them, and their rows are byte-identical.
The five AIDO.RNA checkpoints were previously split between the leaderboard, which listed only the
1.6B, and a side table. They are five models scored on the same assays with the same convention, so
all five are now leaderboard entries and
aido_rna_scaling.csvis gone.The top three are a tie, not a ranking. AIDO.RNA (650M) 0.2163, Evo 2 (40B) 0.2120 and AIDO.RNA
(1.6B) 0.2070 span 0.0093, inside the roughly 0.01 band that 31 assays cannot resolve, which the
Notes section on that page already states. The substantive observation is not the order but that a
650M-parameter masked RNA model is level with a 40B autoregressive one.
The size series stops improving after 650M, and whether it is monotone depends on the fill strategy:
mask-fillandmut-fillincrease with size, whilewt-fillandmatch-fillput the 650M abovethe 1.6B. That is now stated as prose rather than as a second table.
All four strategies are published in
leaderboard/fitness/four_fill_sensitivity.csv, and theleaderboard page reports what the evidence does and does not support:
wt-fillhas the highestobserved macro on 7 of 8 checkpoints, but a paired assay bootstrap separates it from
mask-fillononly 2 and from
match-fillon only 1, whilemut-fillis the weakest of the four on everycheckpoint. The differences sit almost entirely in the tRNA assays, which the equal-category
weighting amplifies.
RNA-ERNIE is the one masked model not on this convention. It needs a paddlepaddle environment we
do not have, so its score is carried over from its original script, whose convention was never
verified. The leaderboard page says so.
Implementation
fitness/baselines/masked_lmbuilds one deduplicated context bank covering all four strategies andaccumulates them in a single pass. The four share most of their contexts, so computing all four costs
about 19% more unique context examples than
mut-fillalone (2,929,196 against 2,458,521 over the 31assays).
Each model script is now an adapter of about 100 lines supplying an alphabet, a tokenization and one
forward pass; the four lose 1,559 lines of duplicated masking, batching and IO.
Guards, because a wrong masked score looks plausible: a tokenizer that maps a base to the unknown
token is rejected (that is the bug the released RiNALMo scores carried), every scored position is
asserted to be masked, the context-to-token offset is asserted rather than assumed, the four
strategies must cover the same variants, and the wild type is taken from
RAW_CONSTRUCT_SEQandindependently recovered by reverting each variant's mutations, with disagreement fatal.
Each prediction file gets a manifest recording the strategies, columns, alphabet, dtype, checkpoint,
counts, a hash of the scoring source and the GPU. The GPU matters: the same code and checkpoint in
bfloat16 on an L40S and an H100 differ by up to 0.2 in score and about 0.001 in per-assay Spearman.
Every number here was produced on H100s.
Verification
--strategies mut-fillreproduces the pre-refactor predictions to 1.5e-7 (RNA-FM), 4.7e-7(RiNALMo), 9.2e-7 (RNAGenesis) and 7.1e-7 (AIDO.RNA, on its original L40S), with identical NaN
masks, so the refactor changed no model's numbers.
mut-fillcolumn of the new runs reproduces the previously publishedmut-fillleaderboard towithin 0.0008.
Pitt_2010_ribozymefor all eight checkpoints,which is the fixture they must satisfy by construction.
tests/test_masked_lm.py: 26 tests, CPU only, no checkpoint needed. Each strategy is checkedagainst a per-variant reference implementation, and each adapter's alphabet, column, batching and
dtype defaults are pinned, since those are what the released predictions were produced with.
fitness/analyze_fill_strategies.pyreproduces the sensitivity table, the category spreads and thebootstrap intervals from the prediction files.
Reviewing
The three commits are separable: the engine and adapters, the merge registry change, then the
leaderboard.
fitness/merge_scoring_files.pykeeps its previous behaviour for every existing entry;a bare column name still means "a folder named after the model".