Buckets:
Eval Suite Log
2026-04-08
11:30 — Phase 0 started. Installed sourmash 4.3, minimap2 2.30, lightgbm 4.6, shap 0.49, sklearn 1.7 into plannotate conda env. Fixed sourmash pkg_resources issue (setuptools<70).
11:45 — Downloaded Addgene-500 reference panel from HF bucket McClain/PlasmidRL/reference/. 500 plasmids, 418 not in training set. Pre-computed: length, GC, longest ORF, MFE density, 3-mer freqs.
11:50 — Pinned tool versions in eval/config/environment.yml. pLannotate 1.2.2 (snapgene DB 2021-11), prodigal 2.6.3, BLAST+ 2.17.0.
11:55 — Built eval/config/feature_categories.yaml mapping pLannotate feature types to Tier 3 booleans (origin, selection_marker, promoter, terminator, cds).
12:00 — Built training-set sourmash signature: 20,644 unique plasmids, k=31 scaled=100. Cached at eval/reference/training_sigs.zip.
12:05 — Building negative controls (random GC-matched, dinucleotide-shuffled, real held-out). Writing generation + annotation pipeline scripts.
12:30 — Negative controls built (500 each: random, shuffled, real). Wrote pipeline scripts: generate.py, annotate.py, compute_metrics.py, firstlight_sweep.py.
13:00 — Tested generation: 5 seqs in 21s, mean 8.1kb, 54% GC. Fixed tokenizer padding issue (custom PlasmidKmerTokenizer doesn't expose pad/eos tokens properly).
14:00 — Tested annotation pipeline end-to-end: pLannotate 3.1s/seq, prodigal 0.3s total, dustmasker 0.1s total. Results look good — 3/5 test sequences show >60% pLannotate coverage.
14:15 — Launched Phase 1 first-light sweep: 12 cells (temp×top_p×rep_pen), 50 seqs/cell = 600 total. Model loaded once, reused across cells. ETA ~45-60 min.
14:16 — huggingface-hub version conflict flagged: upgraded to 1.9.2 for bucket API, but transformers needs <1.0. Downgraded to 0.36.2. Bucket uploads will need separate handling.
14:50 — First-light sweep complete. 12 cells × 50 seqs = 600 generations in 29.6 min. pLannotate annotation initially failed due to --no-banner flag — ran retroactively on all cells (~3 min total).
14:55 — Winner: t0.7_p0.95_r1.0 (temp=0.7, top_p=0.95, rep_pen=1.0). 70.8% median pLannotate coverage, 22.1 mean features, 7.6kb mean length. Clear winner — temp=0.7 dominates, higher temps drop coverage significantly. Repetition penalty doesn't help.
15:05 — Extended sweep to test temp=0.3 and 0.5. Coverage plateaus at ~70% across 0.3-0.7, but temp=0.7 has longest sequences (7.6kb, closest to Addgene ref 7.5kb) and fewest <1kb failures. Keeping temp=0.7.
15:15 — Uploaded all Phase 0+1 data to HF bucket McClain/PlasmidLMEval (81 files, 17.2 MB). Used hf buckets sync CLI — the Python batch_bucket_files() API silently fails for non-README files.
15:20 — Phase 2 started: generating 1000 plasmids with winning config (temp=0.7, top_p=0.95, top_k=50, no rep penalty). Model: McClain/PlasmidLM-kmer6-GRPO-plannotate.
16:06 — Phase 2 complete. 1000 sequences in 46.3 min (21.6 seq/min). Mean length 7.2kb, median 9.0kb, 50.6% GC, 48.7% EOS rate, 5.6% <1kb. Baselines also annotated: random 5.2% hit rate, shuffled 7.6%, real 100%.
16:07 — Phase 3 started: running pLannotate (8 workers) + Prodigal + dustmasker on all 1000 generated sequences.
16:40 — Phase 3 complete. pLannotate: ~2s/seq with 8 workers on 1000 seqs. Prodigal and dustmasker fast (<30s total).
16:41 — Phase 4 metrics computed on all 1000 generated sequences:
Tier 1: Distributional
- Length KS stat: 0.256 (p < 1e-19) — significant difference from reference (generated skews longer)
- GC KS stat: 0.121 (p = 0.0001) — slight difference, generated slightly lower GC
- Wasserstein distances: length=1338bp, GC=0.020
Tier 3: Essentials
| Metric | Generated (n=1000) | Real Addgene (n=500) | Random (n=500) | Shuffled (n=500) |
|---|---|---|---|---|
| has_ori | 63.8% | — | — | — |
| has_selection_marker | 65.2% | — | — | — |
| has_promoter | 83.0% | — | — | — |
| has_terminator | 60.1% | — | — | — |
| has_cds | 85.0% | — | — | — |
| plausibility_pass | 59.6% | 95.0% | 0.0% | 0.0% |
| mean n_features | 19.2 | — | — | — |
| mean coverage_frac | 50.1% | — | — | — |
Tier 5: Architecture
- CDS with promoter context (within 500bp): 43.0%
- CDS with terminator context: 8.6%
- Mean origins per plasmid: 1.6
- Mean selection markers: 1.8
- Mean overlapping features: 5.3
Memorization
- Zero hits at containment threshold 0.3 — no evidence of training set memorization
Discriminator
- LightGBM AUC: 0.937 (real vs generated)
- Top features by importance: length (5627), ORF density (3533), 6-mer diversity (1735), GC (1419)
- SHAP summary plot saved
16:45 — Baseline metrics computed: random plausibility 0%, shuffled 0%, real Addgene 95%. Generated model at 59.6% is well above floor and approaching ceiling.
17:30 — Diversity metrics computed. Model produces comparable diversity to real Addgene plasmids — no mode collapse.
| Metric | Generated | Addgene-500 |
|---|---|---|
| 6-mer JSD (mean pairwise) | 0.352 | 0.334 |
| 6-mer cosine distance | 0.288 | 0.267 |
| 6-mer Jaccard similarity | 0.778 | 0.793 |
| Near-identical pairs (JSD<0.01) | 0.0% | 0.0% |
| Pairs with Jaccard >0.9 | 25.3% | 28.3% |
High Jaccard is expected — real plasmids share extensive backbone sequences (amp resistance, ColE1 ori, etc.). Generated plasmids are slightly more diverse than real ones on all k-mer metrics.
17:45 — Extra metrics computed: prompt fidelity, codon usage, GC skew, ViennaRNA MFE (running).
Prompt Fidelity (Conditional Accuracy)
Overall: 23.5% of requested features detected by pLannotate in generated sequences.
| Category | Hit Rate | Found/Requested |
|---|---|---|
| AMR | 39.2% | 538/1373 |
| PROM | 38.5% | 1090/2828 |
| REPORTER | 27.1% | 95/350 |
| ORI | 19.9% | 339/1701 |
| ELEM | 15.9% | 634/3991 |
| TAG | 1.1% | 2/179 |
Updated (sseqid-based via motif registry): 41.6% overall — properly matching against the 660 sseqids in the motif registry that map tokens to pLannotate feature IDs. Previous keyword-based approach (23.5%) was undercounting due to brittle string matching.
| Category | Hit Rate | Found/Requested |
|---|---|---|
| ORI | 51.9% | 882/1701 |
| ELEM | 51.1% | 2040/3991 |
| PROM | 39.5% | 1118/2828 |
| AMR | 29.1% | 399/1373 |
| REPORTER | 21.1% | 74/350 |
| TAG | 2.8% | 5/179 |
Tags remain very low — these are tiny peptide sequences (6-18 bp coding) that BLAST/DIAMOND struggle to detect even in real plasmids.
Detection ceiling: pLannotate on real training plasmids only detects 71.8% of their own tokens — this is the maximum achievable fidelity with this detection method. Generated model achieves 57.9% of ceiling (41.6/71.8%). Per-category ceiling ratios: ORI 73.7%, ELEM 72.9%, PROM 56.0%, AMR 40.9%, REPORTER 31.3%, TAG 4.2%.
Codon Usage
- 3,376 predicted ORFs, 579,368 total codons
- JSD vs E. coli codon frequencies: 0.131 (low divergence = realistic codon usage)
GC Skew
- Generated mean variation: 0.0566 (reference: 0.0593)
- Very close to real Addgene plasmids — model captures characteristic GC skew patterns
ViennaRNA MFE Density (DNA Mathews2004 params)
- Reference (Addgene-500): -0.151 kcal/mol/nt
- Generated (n=200 subsample, 500bp windows): -0.144 kcal/mol/nt
- Close to reference — generated plasmids have realistic thermodynamic stability
- Note: RNA.fold O(n³) makes full-length folding impractical for >5kb sequences; used 500bp windowed sampling
2026-04-20 — Base-model (pre-GRPO) eval + paired comparison
Context. The original runs/grpo_plannotate_full_20260408/ evaluated only the post-GRPO
model (McClain/PlasmidLM-kmer6-GRPO-plannotate). For the NeurIPS submission we need a
matched pre-GRPO baseline, paired per-prompt, so we can attribute the improvement in
plausibility_pass and per-category hit rate specifically to the post-training step.
GRPO run that produced the uploaded model. One-shot GRPO from the pretrained base
(McClain/PlasmidLM-kmer6) with the pLannotate composite reward, no curriculum, no
motif warm-start. Config: kl=0.5, clip=0.2, group size 8, prompt batch 8, lr 5e-6, warmup 50,
no length penalty; rollouts at temp=0.3 / top_p=0.95 / max_new_tokens=2500. Scheduled 1000
steps, stopped at ~900, step_800 uploaded. The W&B logs for this run are not recoverable;
the reconstructed config is committed to the HF repo as training_config.py (matches the
S3 checkpoint byte-for-byte via SHA verification).
Paired prompt set. eval/data/paired_prompts_20260420.parquet (1000 rows, 1000 unique) —
extracted directly from runs/grpo_plannotate_full_20260408/generations.parquet so the two
runs share identical prompts in identical order.
Generation (g6-big, ~67 min total)
Matched-config run (Phase A, paired with GRPO):
- Model source:
McClain/PlasmidLM-kmer6downloaded to/opt/dlami/nvme/models/plasmid_lm_kmer6_local/. - Modeling code swap: the base repo's
modeling_plasmid_lm.pypredates thegenerate_simple(attention_mask, top_p, eos_token_id, pad_token_id)signature used by the eval pipeline. To keep the two runs on an identical generation path, we replaced it with the copy fromMcClain/PlasmidLM-kmer6-GRPO-plannotate. Weights, tokenizer, config are unchanged. Forward pass is byte-for-byte equivalent; only the inference helper was upgraded. - Sampling: temp=0.7 top_p=0.95 top_k=50 max_new_tokens=3000 batch_size=8 seed=42.
- 1000 sequences in 46.7 min, mean length 6.9 kb, median 7.8 kb, 60.7% EOS rate, 3.4% < 1 kb (GRPO for comparison: 7.2 kb / 9.0 kb / 48.7% / 5.6% — base emits EOS more reliably; GRPO shows mild length drift).
- Output:
runs/base_kmer6_matched_20260420_1303/in bucket.
Temperature sweep (Phase B, base only, not paired):
- Temps ∈ {0.3, 0.5, 1.0} × 100 seqs each from
training_pairs_v4.parquet. t=0.7 already covered by Phase A. - Output:
runs/base_kmer6_sweep_20260420_1303/t{03,05,10}/.
Post-processing fixes
- The
plannotateconda env on g6-big had drifted:joblib,lightgbm,shap,sklearn,statsmodels,scipy,pyyaml,sourmashwere all missing despiteenvironment.ymllisting them. Pip-installed.sourmashdowngradedcachetools7.0.1→5.5.2 (did not break pLannotate).prodigalCLI also missing — installed viaapt-get install prodigal. - Bug in
eval/scripts/annotate.py:run_dustmaskerpasses the raw FASTA todustmasker, which aborts on any zero-residue record. The matched run had 1 empty record (base model emitting EOS immediately for one prompt). Fixed in-place by pre-filtering empty records and reporting the dropped count. - Prompt fidelity method correction.
compute_extra_metrics.compute_prompt_fidelitywas using a hardcoded keyword-matching dict that yielded ~23% on the GRPO run. The supposed "sseqid via motif registry" method (41.6% overall from 2026-04-09) was never committed — only the eval_log note. Today we implemented it properly: loadmotif_registry_combined.parquet, buildtoken -> set[sseqid], per-generation score is set intersection with the sseqids pLannotate emits. Matches the 41.6% number on the GRPO run (ORI 51.9%, ELEM 51.1%, PROM 39.5%, AMR 29.1%, REPORTER 21.1%, TAG 2.8% — all identical to the prior note). Also now writesmetrics/prompt_fidelity_per_seq.parquet(one row per prompt) so paired analyses are possible without recomputing.
Paired results (matched t=0.7, n=1000)
| Metric | Base | GRPO | Δ |
|---|---|---|---|
| plausibility_pass (structural: has ori + marker + (prom OR cds)) | 56.8% | 59.6% | +2.8 pp |
| prompt_fidelity overall (sseqid intersection) | 35.5% | 41.6% | +6.1 pp |
| fidelity — AMR | 26.1% | 29.1% | +3.0 pp |
| fidelity — ORI | 44.1% | 51.9% | +7.8 pp |
| fidelity — PROM | 35.3% | 39.5% | +4.2 pp |
| fidelity — ELEM | 46.1% | 51.1% | +5.0 pp |
| fidelity — REPORTER | 19.1% | 21.1% | +2.0 pp |
| fidelity — TAG | 8.4% | 2.8% | −5.6 pp |
| discriminator AUC vs Addgene-500 | 1.000 | 0.937 | −0.063 |
| memorization hits (sourmash containment ≥ 0.3) | 0 | 0 | — |
Base temperature sweep (n=100 per cell)
| Temp | plausibility | prompt_fidelity | disc AUC |
|---|---|---|---|
| 0.3 | 60.0% | 37.8% | 1.000 |
| 0.5 | 56.0% | 32.5% | 1.000 |
| 0.7 (matched, n=1000) | 56.8% | 35.5% | 1.000 |
| 1.0 | 42.0% | 32.3% | 1.000 |
Takeaways
- Headline "+8.8 pp plausibility" on the HF model card does not hold up under matched paired sampling; the matched delta is +2.8 pp on plausibility but +6.1 pp on prompt fidelity. Paper figure should lead with fidelity.
- Base is generating plausible-but-generic plasmids: similar structural pass rate, but substantially lower on actually containing the requested features. RL mostly improves conditioning, not plasmid-ness.
- ORI and ELEM are the biggest fidelity gains (+7.8 / +5.0 pp). TAG regresses (−5.6 pp): short peptide tags rarely get scored by BLAST, so RL learns to deprioritise them.
- Discriminator AUC goes from 1.000 (base → trivially separable from real Addgene) to 0.937 (GRPO → real artifacts) — RL reduces generator-specific signatures. Need to inspect SHAP top features on both to confirm this isn't a length/GC artifact.
- Need next: paired McNemar + bootstrap CIs on the 1000-prompt data (analysis is in
notebooks/base_vs_grpo_analysis.py).
2026-04-20 — Best-of-K rejection sampling, paired, base vs GRPO
Motivation. Reviewer would ask: "can inference-time compute (best-of-K sampling) close the GRPO gap?" A strong negative answer — "GRPO still wins at matched K" — is the cleanest paper story.
Design
Three additional seeds per model (43, 44, 45) on the same 1000 paired prompts, matched sampling config (temp=0.7 top_p=0.95 top_k=50 max_new_tokens=3000). Combined with the existing seed-42 matched runs, this gives four independent samples per prompt per model.
For K ∈ {1, 2, 4} per prompt the best sample is the one with max pLannotate n_found (oracle ranker — real rejection sampling would use a cheap proxy and do worse). Ties resolved by plausibility then by fidelity rate.
Bucket paths:
- Base seeds 43, 44, 45:
runs/bon_base_seed{43,44,45}_20260420_1303/ - GRPO seeds 43, 44, 45:
runs/bon_grpo_seed{43,44,45}_20260420_1303/ - Existing seed 42: the matched runs already in the bucket.
Total cost: ~7.5 h (6 × 46 min generation + 6 × 30 min pLannotate + metrics).
Per-seed sanity
| seed | base plaus | GRPO plaus | base fid | GRPO fid | base AUC | GRPO AUC |
|---|---|---|---|---|---|---|
| 42 | 56.8% | 59.6% | 35.5% | 41.6% | 1.000 | 0.937 |
| 43 | 58.9% | 63.0% | 36.1% | 43.4% | 1.000 | 1.000 |
| 44 | 57.1% | 61.6% | 34.1% | 42.6% | 1.000 | 1.000 |
| 45 | 58.6% | 60.8% | 35.9% | 41.8% | 1.000 | 0.999 |
Per-model run-to-run σ is about 1 pp on both metrics. The earlier "GRPO AUC drops to 0.937" was a single-seed artifact; GRPO AUC is ≈ 1.0 across three of four seeds. The original paper figure should drop the AUC delta claim.
Oracle best-of-K (paired n=1000)
| K | metric | base | GRPO | Δ | paired bootstrap 95% CI | McNemar p |
|---|---|---|---|---|---|---|
| 1 | plausibility | 56.8% | 59.6% | +2.8 pp | [−1.5, +7.0] | 0.21 |
| 2 | plausibility | 79.1% | 84.9% | +5.8 pp | [+2.5, +9.2] | 8×10⁻⁴ |
| 4 | plausibility | 92.1% | 95.8% | +3.7 pp | [+1.7, +5.7] | 4×10⁻⁴ |
| 1 | useful (plaus ∧ fid ≥ 0.5) | 38.7% | 48.5% | +9.8 pp | [+5.7, +13.9] | 6×10⁻⁶ |
| 2 | useful | 60.4% | 73.6% | +13.2 pp | [+9.4, +17.0] | 9×10⁻¹¹ |
| 4 | useful | 78.9% | 89.7% | +10.8 pp | [+7.9, +13.7] | 2×10⁻¹² |
| 1 | fidelity mean | 35.5% | 41.6% | +6.1 pp | [+3.4, +8.8] | — |
| 2 | fidelity mean | 51.6% | 59.7% | +8.0 pp | [+5.7, +10.4] | — |
| 4 | fidelity mean | 64.6% | 72.2% | +7.6 pp | [+6.0, +9.2] | — |
Interpretation
- Rejection sampling does not close the GRPO gap. At K=4, GRPO still leads by +10.8 pp on the composite useful metric (p ≈ 2e-12). Base@K=4 (78.9% useful) is ~16 pp behind GRPO@K=4 (89.7%) and only matches GRPO@K=2 (73.6%) — i.e. RL buys more than a factor of two in inference-time compute on this metric.
- Plausibility also separates only at K ≥ 2 (p=0.21 at K=1, p<0.001 at K=2 and K=4). At K=1 the plausibility delta is not significant — this is why the K=1 matched comparison looked weaker than it is.
- Fidelity gain is stable across K (+6 to +8 pp).
- Because the ranker is ground-truth pLannotate, real-world rejection sampling (which needs a cheap proxy scorer) would reproduce a smaller fraction of these gains for base, so the "RS doesn't close the gap" claim holds a fortiori.
Script
eval/scripts/analyze_best_of_k.py (committed, PEP 723 script). Reads
/tmp/plasmidlm_eval_cache/… populated via hf buckets sync. Recomputes this table.
Xet Storage Details
- Size:
- 17.1 kB
- Xet hash:
- 9bf14a57ccafed864d540581eb3af653c883213f14867960fa5a4e79606f36e8
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.