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/01c_check_encoder_sanity.py from RomanCohort/RNAQwen: direct link, hf CLI and curl.
- Browser
- Download file 12 kB
-
https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/01c_check_encoder_sanity.py
- Command line
-
hf download hf://RomanCohort/RNAQwen/scripts/01c_check_encoder_sanity.py
-
curl -L -o 01c_check_encoder_sanity.py https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/01c_check_encoder_sanity.py
12 kB
| """编码器健全性检查 —— 换任何 backbone 之前必须先跑这个。 | |
| 为什么需要它(2026-10-09 事故): | |
| `models/rnafm/model.safetensors` 被 aria2 预分配毁掉 —— 大小 398,117,588 与上游 | |
| **逐字节相同**,205 个张量里 **155 个全零**,`word_embeddings` 全零, | |
| 于是 encoder 对序列**零敏感**(不同序列输出 max|Δ| = 0.000000e+00)。 | |
| 而 `from_pretrained` 一声不响,LOAD REPORT 只提 lm_head/ss_head/pooler。 | |
| 更糟的是当时的探针 `01_probe_rnafm.py` **只查输出形状** `(1,83,640)` —— | |
| **一个全零的模型照样过**。我据此报过 `[OK]`。 | |
| 这个脚本查的是**语义**,不是形状: | |
| 1. 结构 —— 有没有全零张量;word_embeddings 是否非零 | |
| 2. 哈希 —— 给了 --expect-sha256 就强制核对 | |
| 3. 敏感性 —— 同长度不同序列 -> 输出必须变 (抓"只看位置") | |
| 4. 次序 —— 同一条序列打乱 -> 输出必须变 (抓"只看碱基组成") | |
| 5. 确定性 —— 同一条序列跑两次 -> 输出必须逐位相同(抓随机性/漏 eval) | |
| 跑法: | |
| python scripts/01c_check_encoder_sanity.py --model models/rnafm | |
| python scripts/01c_check_encoder_sanity.py --model models/ernierna \ | |
| --expect-sha256 02274cb7... --expect-size 345071800 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import random | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(ROOT / "src")) | |
| import rnaqwen # noqa: E402,F401 | |
| import torch # noqa: E402 | |
| from rnaqwen.models.encoder import ARCHS, RnaEncoder, read_model_type # noqa: E402 | |
| def sha256_of(path: Path) -> str: | |
| h = hashlib.sha256() | |
| with open(path, "rb") as f: | |
| for b in iter(lambda: f.read(8 << 20), b""): | |
| h.update(b) | |
| return h.hexdigest() | |
| def find_weights(model_path: Path) -> Path | None: | |
| for n in ("model.safetensors", "pytorch_model.bin"): | |
| p = model_path / n | |
| if p.exists(): | |
| return p | |
| return None | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--model", type=Path, required=True) | |
| ap.add_argument("--arch", default=None, choices=sorted(ARCHS), | |
| help="不填就按 config.json 的 model_type 自动识别") | |
| ap.add_argument("--expect-sha256", default=None, | |
| help="给了就强制核对 —— 这是唯一能挡住「大小对、内容空」的手段") | |
| ap.add_argument("--expect-size", type=int, default=None) | |
| ap.add_argument("--nt", type=int, default=200, help="测试序列长度") | |
| ap.add_argument("--device", default="cpu", help="默认 cpu:这个检查不需要 GPU") | |
| ap.add_argument("--out", type=Path, default=None) | |
| args = ap.parse_args() | |
| fails: list[str] = [] | |
| report: dict = {"model": str(args.model)} | |
| print("=" * 74) | |
| print(f"编码器健全性检查: {args.model}") | |
| print("=" * 74) | |
| # ---------- 0) 哈希 / 大小 ---------- | |
| w = find_weights(args.model) | |
| if w is None: | |
| print("[FAIL] 目录里没有 model.safetensors / pytorch_model.bin") | |
| return 1 | |
| size = w.stat().st_size | |
| report["weights"] = {"file": w.name, "size": size} | |
| print(f" 权重文件 : {w.name}") | |
| print(f" 大小 : {size:,}") | |
| if args.expect_size is not None: | |
| ok = size == args.expect_size | |
| print(f" 期望大小 : {args.expect_size:,} {'OK' if ok else '**不符**'}") | |
| report["expected_size"] = args.expect_size | |
| if not ok: | |
| fails.append(f"大小不符: {size} != {args.expect_size}") | |
| if args.expect_sha256: | |
| print(" 计算 sha256 ...", flush=True) | |
| got = sha256_of(w) | |
| ok = got == args.expect_sha256 | |
| print(f" sha256 : {got}") | |
| print(f" 期望 : {args.expect_sha256}") | |
| print(f" -> {'一致 OK' if ok else '**不一致**'}") | |
| report["sha256"] = got | |
| report["expected_sha256"] = args.expect_sha256 | |
| if not ok: | |
| fails.append("sha256 不符 —— 文件内容不是上游那份") | |
| mt = read_model_type(args.model) | |
| report["model_type"] = mt | |
| print(f" model_type : {mt}") | |
| # ---------- 1) 结构:全零张量 ---------- | |
| print() | |
| print("-" * 74) | |
| print("1) 结构:有没有全零张量") | |
| print("-" * 74) | |
| from safetensors.torch import load_file | |
| if w.suffix == ".safetensors": | |
| sd = load_file(str(w)) | |
| else: | |
| sd = torch.load(str(w), map_location="cpu", weights_only=False) | |
| sd = sd.get("state_dict", sd) | |
| zeros = [k for k in sd if sd[k].numel() and float(sd[k].float().abs().max()) == 0.0] | |
| print(f" 张量总数 {len(sd)},全零 {len(zeros)}") | |
| report["n_tensors"] = len(sd) | |
| report["zero_tensors"] = zeros | |
| # 只有真正参与前向、且不该为零的才判失败。 | |
| # 合法例外:LayerNorm.bias 初始化就是 0(但**加载后的**不该是 0), | |
| # 还有各种 head 的 decoder.bias。所以这里只对 encoder 主干严格。 | |
| critical = [k for k in zeros | |
| if any(t in k for t in ("word_embeddings", "position_embeddings", | |
| "attention.self", "intermediate.dense", | |
| "output.dense"))] | |
| if critical: | |
| print(f" **主干里有 {len(critical)} 个全零张量** —— 这就是损坏的特征:") | |
| for k in critical[:20]: | |
| print(" ZERO", k, tuple(sd[k].shape)) | |
| if len(critical) > 20: | |
| print(f" ... 还有 {len(critical)-20} 个") | |
| fails.append(f"主干里有 {len(critical)} 个全零张量") | |
| elif zeros: | |
| print(f" (全零的 {len(zeros)} 个都在非主干位置,列出前 10:)") | |
| for k in zeros[:10]: | |
| print(" ", k, tuple(sd[k].shape)) | |
| else: | |
| print(" 没有任何全零张量 OK") | |
| we_keys = [k for k in sd if "word_embeddings" in k] | |
| we_ok = False | |
| for k in we_keys: | |
| t = sd[k].float() | |
| nz = int((t.abs() > 1e-8).sum()) | |
| am = float(t.abs().max()) | |
| print(f" {k}: shape={tuple(sd[k].shape)} absmax={am:.6e} nnz={nz}/{t.numel()}") | |
| if am > 1e-6 and nz == t.numel(): | |
| we_ok = True | |
| if not we_ok: | |
| fails.append("word_embeddings 零/非满 —— 模型看不见序列") | |
| # ---------- 2) 真正加载 ---------- | |
| print() | |
| print("-" * 74) | |
| print("2) 加载(arch 自动识别 / 显式指定)") | |
| print("-" * 74) | |
| try: | |
| enc = RnaEncoder(str(args.model), arch=args.arch) | |
| except Exception as e: | |
| print(f" [FAIL] 加载失败: {type(e).__name__}: {e}") | |
| return 1 | |
| print(" " + enc.describe()) | |
| report["arch"] = enc.arch.name | |
| report["hidden_size"] = enc.hidden_size | |
| # ---------- 2.5) 键覆盖:文件里的权重真的装进模型了吗 ---------- | |
| # | |
| # 这一条是 2026-10-09 同一天踩的**第二个**坑: | |
| # 拿 ModelScope 那份做对照时,键名带 `rnafm.` 前缀没剥,encoder 全部 MISSING, | |
| # 于是测的是**随机初始化**的模型 —— 随机模型天然对序列敏感, | |
| # 我因此报了一次假的 `序列敏感: YES`。 | |
| # 所以「敏感性」必须在**确认权重装上了**之后才成立。 | |
| print() | |
| print("-" * 74) | |
| print("2.5) 键覆盖:checkpoint 的权重能装进模型吗") | |
| print("-" * 74) | |
| mt = report.get("model_type") or "" | |
| prefixes = ["", f"{mt}.", "model.", "rnafm.", "ernierna.", "rnaernie."] | |
| norm: dict[str, torch.Tensor] = {} | |
| for k, v in sd.items(): | |
| for pfx in prefixes: | |
| if pfx and k.startswith(pfx): | |
| norm[k[len(pfx):]] = v | |
| break | |
| else: | |
| norm[k] = v | |
| msd_keys = set(enc.model.state_dict()) | |
| covered = msd_keys & set(norm) | |
| uncovered = sorted(msd_keys - set(norm)) | |
| # pooler 天然不在 checkpoint 里;各种 head 的 bias 也不参与我们的前向 | |
| meaningful = [k for k in uncovered | |
| if "pooler" not in k and not k.endswith((".bias",))] | |
| print(f" 模型键 {len(msd_keys)},被 checkpoint 覆盖 {len(covered)}") | |
| print(f" 未覆盖 {len(uncovered)} 个: {uncovered[:8]}{' ...' if len(uncovered) > 8 else ''}") | |
| print(f" 其中影响前向的(剔除 pooler / *.bias): {meaningful if meaningful else '无'}") | |
| report["keys_missing"] = uncovered | |
| report["keys_missing_meaningful"] = meaningful | |
| if meaningful: | |
| fails.append(f"checkpoint 覆盖不全,{len(meaningful)} 个权重要走随机初始化: " | |
| f"{meaningful[:5]}") | |
| # 加载后模型里的全零张量(和读文件不同 —— load 可能补上随机初始化的) | |
| msd = enc.model.state_dict() | |
| mzeros = [k for k in msd if msd[k].numel() | |
| and float(msd[k].float().abs().max()) == 0.0] | |
| print(f" 加载后模型里的全零张量: {len(mzeros)} {mzeros[:6]}") | |
| report["zero_tensors_after_load"] = mzeros | |
| mcrit = [k for k in mzeros if "word_embeddings" in k or "attention.self" in k] | |
| if mcrit: | |
| fails.append(f"加载后主干仍有全零张量: {mcrit[:5]}") | |
| # ---------- 3~5) 语义检查(核心)---------- | |
| print() | |
| print("-" * 74) | |
| print("3) 语义检查 —— 形状检查挡不住的那个坑") | |
| print("-" * 74) | |
| rng = random.Random(0) | |
| nt = args.nt | |
| base = "".join(rng.choice("ACGU") for _ in range(nt)) | |
| shuffled = list(base) | |
| rng.shuffle(shuffled) | |
| shuffled = "".join(shuffled) | |
| poly = "G" * nt # 同长度,组成完全不同 | |
| # 保证打乱后确实不同 | |
| if shuffled == base: | |
| shuffled = "A" + base[1:] | |
| grp = [base, poly, shuffled, base] | |
| labels = ["base", "polyG", "shuffled", "base(重复)"] | |
| out = enc(grp) | |
| h = out.hidden.float() | |
| report["shape"] = list(h.shape) | |
| print(f" 输出形状: {tuple(h.shape)} mask 和: {out.mask.sum(-1).tolist()}") | |
| d_poly = float((h[0] - h[1]).abs().max()) | |
| d_shuf = float((h[0] - h[2]).abs().max()) | |
| d_same = float((h[0] - h[3]).abs().max()) | |
| print(f" base vs polyG max|Δ| = {d_poly:.6e} (同长度不同序列 -> 必须 > 0)") | |
| print(f" base vs shuffled max|Δ| = {d_shuf:.6e} (同组成不同次序 -> 必须 > 0)") | |
| print(f" base vs base(重复) max|Δ| = {d_same:.6e} (确定性 -> 必须 == 0)") | |
| report.update({"delta_poly": d_poly, "delta_shuffled": d_shuf, "delta_repeat": d_same}) | |
| THRESH = 1e-3 | |
| if not d_poly > THRESH: | |
| fails.append(f"序列敏感性失败: 同长度不同序列 max|Δ|={d_poly:.3e} " | |
| f"(<= {THRESH},模型看不见序列)") | |
| if not d_shuf > THRESH: | |
| fails.append(f"次序敏感性失败: 打乱后 max|Δ|={d_shuf:.3e} " | |
| f"(<= {THRESH},模型只看碱基组成不看顺序)") | |
| if d_same != 0.0: | |
| fails.append(f"确定性失败: 同输入两次 max|Δ|={d_same:.3e}(应为 0)") | |
| # ---------- 结论 ---------- | |
| print() | |
| print("=" * 74) | |
| if fails: | |
| print("VERDICT: FAIL") | |
| for f in fails: | |
| print(" ✗", f) | |
| print() | |
| print(" **不要拿这个 encoder 去训练。** 先查文件是不是坏的:") | |
| print(" 和上游声明的大小/sha256 对一遍。") | |
| else: | |
| print("VERDICT: PASS —— encoder 健全,可以拿去训") | |
| print("=" * 74) | |
| report["verdict"] = "FAIL" if fails else "PASS" | |
| report["failures"] = fails | |
| out_path = args.out or (ROOT / "reports" / "encoder_sanity.json") | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| out_path.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8") | |
| print(f"报告 -> {out_path}") | |
| return 1 if fails else 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |