Instructions to use RomanCohort/RNAQwen with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use RomanCohort/RNAQwen with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("RomanCohort/RNAQwen", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download scripts/40_prep_native_text.py from RomanCohort/RNAQwen: direct link, hf CLI and curl.
- Browser
- Download file 6.79 kB
-
https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/40_prep_native_text.py
- Command line
-
hf download hf://RomanCohort/RNAQwen/scripts/40_prep_native_text.py
-
curl -L -o 40_prep_native_text.py https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/40_prep_native_text.py
6.79 kB
| """原生文本路线的预处理:把 RNA 序列变成"普通文本"。 | |
| 这是 PLAN_v2 §2.2 里唯一需要新写的东西(几十行)。产出两份: | |
| ① data/processed/pretrain_rna.jsonl 序列继续预训练语料(每行一条 RNA 的纯文本) | |
| ② data/processed/sft_text_stats.json 为②扫一遍:序列进文本后,prompt 会多长 | |
| 为什么先做这两个: | |
| §2.3 里定的是"**先跑②(直接 SFT),不够再补①**"。所以①可以做出来备用, | |
| 但真正卡住臂 B 的是"RNA 当文本进模板之后,prompt 长度是多少、会不会超上下文"。 | |
| ②就是为了先把这个数摆出来。 | |
| 关键取舍(都在代码里注释了): | |
| * 只保留 ACGU 四条碱基 —— 实测全语料 1,557,973 个碱基里非 ACGUN 的是 **0 个**, | |
| 所以这不会丢数据;但它挡住了脏数据(空格、换行、数字混进序列) | |
| * 按序列前 60 字符去重 —— 重复会让继续预训练把某些序列背下来 | |
| * 长度上限取 **1022 nt**:ERNIE-RNA 的实测上限。换到原生文本后理论上更长也行 | |
| (Qwen3 上下文 32K),但**保持和臂 A 同长度上限**,否则对比里多一个变量 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import random | |
| from collections import Counter | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| MERGED = ROOT / "data/processed/rnaqa_n6_merged.jsonl" | |
| PARQUET = ROOT / "data/external/rnacentral_1024.parquet" | |
| OUT_PRETRAIN = ROOT / "data/processed/pretrain_rna.jsonl" | |
| OUT_STATS = ROOT / "data/processed/sft_text_stats.json" | |
| BASES = set("ACGU") | |
| MAX_NT = 1022 # 与臂 A 对齐 | |
| MIN_NT = 20 | |
| SEED = 20261010 | |
| def clean(seq: str) -> str | None: | |
| s = (seq or "").upper().replace("T", "U").strip() | |
| if not s or len(s) < MIN_NT or len(s) > MAX_NT: | |
| return None | |
| if not set(s) <= BASES: # 混进 N / 空格 / 数字的一律丢 | |
| return None | |
| return s | |
| def from_merged(): | |
| recs = [] | |
| with open(MERGED, encoding="utf-8") as f: | |
| for line in f: | |
| if line.strip(): | |
| recs.append(json.loads(line)) | |
| out = [] | |
| for r in recs: | |
| s = clean(r.get("sequence")) | |
| if s: | |
| out.append({"seq": s, "rna_type": r.get("rna_type") or "", | |
| "source": "rnaqa_n6_merged"}) | |
| return out, recs | |
| def from_parquet(n_want: int, rng: random.Random): | |
| import pyarrow.parquet as pq | |
| f = pq.ParquetFile(PARQUET) | |
| out = [] | |
| # 流式读,按行组抽;不一次进内存(3250 万行) | |
| for rg in range(f.metadata.num_row_groups): | |
| if len(out) >= n_want: | |
| break | |
| t = f.read_row_group(rg, columns=["sequence", "type"]) | |
| d = t.to_pydict() | |
| for s, ty in zip(d["sequence"], d["type"]): | |
| if len(out) >= n_want: | |
| break | |
| c = clean(s) | |
| if c: | |
| out.append({"seq": c, "rna_type": ty or "", "source": "rnacentral_1024"}) | |
| rng.shuffle(out) | |
| return out | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--source", default="both", choices=["merged", "parquet", "both"]) | |
| ap.add_argument("--n-parquet", type=int, default=200_000, | |
| help="从 3250 万条里抽多少做继续预训练语料") | |
| ap.add_argument("--out", type=Path, default=OUT_PRETRAIN) | |
| args = ap.parse_args() | |
| rng = random.Random(SEED) | |
| rows, recs = [], [] | |
| if args.source in ("merged", "both"): | |
| rows, recs = from_merged() | |
| print(f"[prep] merged: {len(rows)} 条干净序列(原始 {len(recs)} 条)") | |
| if args.source in ("parquet", "both"): | |
| p = from_parquet(args.n_parquet, rng) | |
| print(f"[prep] parquet: 抽到 {len(p)} 条干净序列") | |
| rows += p | |
| # 去重(按前 60 字符) | |
| seen, uniq = set(), [] | |
| for r in rows: | |
| k = r["seq"][:60] | |
| if k in seen: | |
| continue | |
| seen.add(k) | |
| uniq.append(r) | |
| print(f"[prep] 去重后 {len(uniq)} 条(去掉 {len(rows)-len(uniq)} 条重复)") | |
| lens = sorted(len(r["seq"]) for r in uniq) | |
| lp = sorted((len(r["seq"]) + 2) // 2 + 8 for r in uniq) # 实测 0.526 tok/nt,向上取整 | |
| print(f"[prep] 序列长度 min {lens[0]} 中位 {lens[len(lens)//2]} max {lens[-1]}") | |
| print(f"[prep] 对应 token 数(按 0.526 tok/nt)min {lp[0]} 中位 {lp[len(lp)//2]} p99 {lp[int(len(lp)*0.99)]} max {lp[-1]}") | |
| args.out.parent.mkdir(parents=True, exist_ok=True) | |
| with open(args.out, "w", encoding="utf-8") as f: | |
| for r in uniq: | |
| f.write(json.dumps(r, ensure_ascii=False) + "\n") | |
| print(f"[prep] -> {args.out} ({args.out.stat().st_size/1e6:.1f} MB)") | |
| # ---- ② 臂 B 的 prompt 长度估算 ---- | |
| # 模板(Llama-3 风格,和 Stage 2 一致)+ 序列 + 问题 + 答案 | |
| if recs: | |
| import statistics | |
| prompt_toks, ans_toks = [], [] | |
| for r in recs: | |
| s = clean(r.get("sequence")) or "" | |
| n_seq = int(len(s) / 0.526) | |
| for q in (r.get("qa") or []): | |
| qt = q.get("question") or "" | |
| at = q.get("answer") or "" | |
| prompt_toks.append(n_seq + int(len(qt) / 3.6) + 24) # 24 ≈ 模板开销 | |
| ans_toks.append(int(len(at) / 3.6) + 2) | |
| if prompt_toks: | |
| pt = sorted(prompt_toks) | |
| print() | |
| print("[prep] 臂 B(RNA 当文本)的 prompt token 数估算:") | |
| print(f" 中位 {pt[len(pt)//2]} p90 {pt[int(len(pt)*0.9)]} max {pt[-1]}" | |
| f" (对应 1.9 nt/token,答案另计)") | |
| over = sum(1 for x in pt if x > 4096) | |
| print(f" 超过 4096 token 的样本:{over} / {len(pt)}" | |
| f"({100*over/len(pt):.2f}%)") | |
| print(f" -> Qwen3 上下文远大于此,臂 B 不需要截断序列") | |
| json_stats = { | |
| "n_sequences": len(uniq), | |
| "seq_len": {"min": lens[0], "median": lens[len(lens)//2], "max": lens[-1]}, | |
| "prompt_tokens": {"median": pt[len(pt)//2], "p90": pt[int(len(pt)*0.9)], | |
| "max": pt[-1], "over_4096": over, | |
| "n_samples": len(pt)}, | |
| "template": "llama3 + RNA 原生文本", | |
| "max_nt": MAX_NT, "seed": SEED, | |
| } | |
| OUT_STATS.write_text(json.dumps(json_stats, indent=2, ensure_ascii=False), | |
| encoding="utf-8") | |
| print(f"[prep] -> {OUT_STATS}") | |
| else: | |
| print("[prep] 没有 QA 样本,跳过 prompt 长度估算") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |