McClain/PlasmidLMEval / eval_log.md
McClain's picture
|
download
raw
17.1 kB

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:55Winner: 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-kmer6 downloaded to /opt/dlami/nvme/models/plasmid_lm_kmer6_local/.
  • Modeling code swap: the base repo's modeling_plasmid_lm.py predates the generate_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 from McClain/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 plannotate conda env on g6-big had drifted: joblib, lightgbm, shap, sklearn, statsmodels, scipy, pyyaml, sourmash were all missing despite environment.yml listing them. Pip-installed. sourmash downgraded cachetools 7.0.1→5.5.2 (did not break pLannotate). prodigal CLI also missing — installed via apt-get install prodigal.
  • Bug in eval/scripts/annotate.py: run_dustmasker passes the raw FASTA to dustmasker, 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_fidelity was 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: load motif_registry_combined.parquet, build token -> 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 writes metrics/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.