r18 scripts sync
Browse files- r18_materials/merge_r16_smoke.py +276 -0
r18_materials/merge_r16_smoke.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""merge_r16_smoke.py — r16 合并 + 双协议探针 + 身份电池 + TB2mini (DSW /mnt/workspace)
|
| 3 |
+
Phase M: GPU 域合并 (基座直载 + head fp32 + LoRA merge_and_unload) → release_r16_staging
|
| 4 |
+
Phase S: 双协议 (bare + think_off) × 标准探针 + 身份电池×20 + TB2 mini 沙箱×4 (agent 协议)
|
| 5 |
+
输出: /mnt/workspace/r16_merge_smoke_report.json + R16_MERGE_SMOKE_DONE
|
| 6 |
+
"""
|
| 7 |
+
import gc, json, os, re, shutil, subprocess, sys, time
|
| 8 |
+
|
| 9 |
+
REPO = "/mnt/workspace/pnet_repo/pondernet"
|
| 10 |
+
OUT2B = f"{REPO}/out_2b"
|
| 11 |
+
BASE = f"{OUT2B}/pnet2b_moe"
|
| 12 |
+
CKPT = f"{OUT2B}/ckpt_2b_r16"
|
| 13 |
+
REL_LIVE = "/mnt/workspace/rel_live" # 发布仓 aux 文件 (modeling 等), 可缺省
|
| 14 |
+
STAGING = f"{OUT2B}/release_r16_staging"
|
| 15 |
+
|
| 16 |
+
t0 = time.time()
|
| 17 |
+
def log(s):
|
| 18 |
+
print(f"[{time.time()-t0:6.0f}s] {s}", flush=True)
|
| 19 |
+
|
| 20 |
+
def cuda_mem():
|
| 21 |
+
try:
|
| 22 |
+
import torch
|
| 23 |
+
return f"VRAM {torch.cuda.memory_allocated()/2**30:.1f}/{torch.cuda.get_device_properties(0).total_memory/2**30:.0f}G"
|
| 24 |
+
except Exception:
|
| 25 |
+
return "VRAM?"
|
| 26 |
+
|
| 27 |
+
log("[M1] 加载基座 pnet2b_moe → GPU (device_map 直载)")
|
| 28 |
+
sys.path.insert(0, REPO)
|
| 29 |
+
import torch
|
| 30 |
+
from peft import PeftModel
|
| 31 |
+
from ponder_moe_llama import PonderMoELlamaForCausalLM
|
| 32 |
+
from train_ponder_moe import load_trainables
|
| 33 |
+
|
| 34 |
+
model = PonderMoELlamaForCausalLM.from_pretrained(BASE, device_map={"": 0})
|
| 35 |
+
log(f"[M1] 直载完成 {cuda_mem()}")
|
| 36 |
+
log(f"[M2] 灌入 r16 head(fp32) + Router/MTP extras {cuda_mem()}")
|
| 37 |
+
model = load_trainables(model, CKPT, with_adapter=False)
|
| 38 |
+
log(f"[M2] head bias={model.ponder_head.bias.item():.4f}")
|
| 39 |
+
log(f"[M3] PEFT 载入 r16 LoRA -> merge_and_unload {cuda_mem()}")
|
| 40 |
+
_w0 = model.model.layers[36].self_attn.q_proj.weight.detach().clone()
|
| 41 |
+
peft_model = PeftModel.from_pretrained(model, os.path.join(CKPT, "adapter"), is_trainable=False)
|
| 42 |
+
merged = peft_model.merge_and_unload()
|
| 43 |
+
delta = (merged.model.layers[36].self_attn.q_proj.weight.data.float() - _w0.float()).abs().max().item()
|
| 44 |
+
log(f"[M3] L36 q_proj 位移={delta:.6f} (须>0)")
|
| 45 |
+
assert delta > 0
|
| 46 |
+
del peft_model, model, _w0
|
| 47 |
+
gc.collect(); torch.cuda.empty_cache()
|
| 48 |
+
|
| 49 |
+
log(f"[M4] 保存 staging {cuda_mem()}")
|
| 50 |
+
os.makedirs(STAGING, exist_ok=True)
|
| 51 |
+
merged.config.use_cache = True
|
| 52 |
+
merged.save_pretrained(STAGING, max_shard_size="10GB")
|
| 53 |
+
for f in ("tokenizer.json", "tokenizer_config.json", "chat_template.jinja"):
|
| 54 |
+
src = os.path.join(BASE, f)
|
| 55 |
+
if os.path.isfile(src):
|
| 56 |
+
shutil.copy2(src, os.path.join(STAGING, f))
|
| 57 |
+
cfg_p = os.path.join(STAGING, "config.json")
|
| 58 |
+
cfg = json.load(open(cfg_p))
|
| 59 |
+
cfg["auto_map"] = {
|
| 60 |
+
"AutoConfig": "modeling_samai_pnet.SamaiPnetConfig",
|
| 61 |
+
"AutoModelForCausalLM": "modeling_samai_pnet.SamaiPnetForCausalLM",
|
| 62 |
+
}
|
| 63 |
+
json.dump(cfg, open(cfg_p, "w"), ensure_ascii=False, indent=2)
|
| 64 |
+
mod_src = f"{REPO}/modeling_samai_pnet.py"
|
| 65 |
+
if not os.path.isfile(mod_src):
|
| 66 |
+
mod_src = f"{REL_LIVE}/modeling_samai_pnet.py"
|
| 67 |
+
shutil.copy2(mod_src, os.path.join(STAGING, "modeling_samai_pnet.py"))
|
| 68 |
+
for f in ("README.md", "serve_contract.json", "INTEGRATION_DECLARATION.md", "inference.py"):
|
| 69 |
+
src = os.path.join(REL_LIVE, f)
|
| 70 |
+
if os.path.isfile(src):
|
| 71 |
+
shutil.copy2(src, os.path.join(STAGING, f))
|
| 72 |
+
gc_p = os.path.join(STAGING, "generation_config.json")
|
| 73 |
+
gcfg = json.load(open(gc_p))
|
| 74 |
+
log(f"[M4] generation_config.eos={gcfg.get('eos_token_id')}")
|
| 75 |
+
assert gcfg.get("eos_token_id") in ([1, 130073],), "assert eos 双 token"
|
| 76 |
+
del merged
|
| 77 |
+
gc.collect(); torch.cuda.empty_cache()
|
| 78 |
+
log(f"[M] 合并完成 {cuda_mem()}")
|
| 79 |
+
|
| 80 |
+
# ============ Phase S: 双协议裸测 ============
|
| 81 |
+
log("[S1] trust_remote_code 重载 staging (fp16, GPU)")
|
| 82 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 83 |
+
tok = AutoTokenizer.from_pretrained(STAGING)
|
| 84 |
+
smoke_model = AutoModelForCausalLM.from_pretrained(
|
| 85 |
+
STAGING, torch_dtype=torch.float16, trust_remote_code=True).cuda().eval()
|
| 86 |
+
log(f"[S1] 重载完成 {cuda_mem()}")
|
| 87 |
+
|
| 88 |
+
PROBES = [
|
| 89 |
+
("P0_1_ni_hao", "你好", 100),
|
| 90 |
+
("P0_2_en_math", "What is 12x8?", 128),
|
| 91 |
+
("math_zh_12x8", "12×8等于多少?", 128),
|
| 92 |
+
("chat_weather", "今天天气真不错", 100),
|
| 93 |
+
("id_name_zh", "你叫什么名字?", 96),
|
| 94 |
+
("id_dev_en", "Who developed you?", 96),
|
| 95 |
+
("id_site_zh", "你的官网是什么?", 96),
|
| 96 |
+
]
|
| 97 |
+
PROTO_KW = {"bare": {}, "think_off": {"enable_thinking": False}}
|
| 98 |
+
|
| 99 |
+
def rep3_rate(ids):
|
| 100 |
+
if len(ids) < 3:
|
| 101 |
+
return 0.0
|
| 102 |
+
grams = [tuple(ids[i:i+3]) for i in range(len(ids)-2)]
|
| 103 |
+
return 1.0 - len(set(grams)) / len(grams)
|
| 104 |
+
|
| 105 |
+
def gen_one(q, mx, pkw):
|
| 106 |
+
enc = tok.apply_chat_template([{"role": "user", "content": q}],
|
| 107 |
+
add_generation_prompt=True, tokenize=True,
|
| 108 |
+
return_tensors="pt", return_dict=True, **pkw).to("cuda")
|
| 109 |
+
with torch.no_grad():
|
| 110 |
+
out = smoke_model.generate(**enc, max_new_tokens=mx, do_sample=False,
|
| 111 |
+
num_beams=1, repetition_penalty=1.0,
|
| 112 |
+
pad_token_id=tok.pad_token_id or 1)
|
| 113 |
+
new = out[0][enc["input_ids"].shape[1]:].tolist()
|
| 114 |
+
text = tok.decode(new, skip_special_tokens=False)
|
| 115 |
+
return new, text, len(new) < mx
|
| 116 |
+
|
| 117 |
+
results = []
|
| 118 |
+
for proto, pkw in PROTO_KW.items():
|
| 119 |
+
for name, q, mx in PROBES:
|
| 120 |
+
new, text, stopped = gen_one(q, mx, pkw)
|
| 121 |
+
freq = {}
|
| 122 |
+
for t in new:
|
| 123 |
+
freq[t] = freq.get(t, 0) + 1
|
| 124 |
+
top_n = max(freq.values()) if freq else 0
|
| 125 |
+
r = {"proto": proto, "probe": name, "q": q, "n_new": len(new), "stopped": stopped,
|
| 126 |
+
"top_tok_freq": round(top_n / max(len(new), 1), 3),
|
| 127 |
+
"rep3": round(rep3_rate(new), 3),
|
| 128 |
+
"text_head": text[:180].replace("\n", "\\n")}
|
| 129 |
+
results.append(r)
|
| 130 |
+
log(f"[S][{proto}] {name}: n={len(new)} stop={stopped} top={r['top_tok_freq']} rep3={r['rep3']}")
|
| 131 |
+
log(f" -> {r['text_head'][:100]}")
|
| 132 |
+
|
| 133 |
+
# ============ 身份电池 ×20 (双协议) ============
|
| 134 |
+
BATTERY = [
|
| 135 |
+
("name", "你叫什么名字?", ["samai-2b"]),
|
| 136 |
+
("name", "你的名字是什么?", ["samai-2b"]),
|
| 137 |
+
("name", "我该怎么称呼你?", ["samai-2b"]),
|
| 138 |
+
("name", "你是什么模型?", ["samai-2b"]),
|
| 139 |
+
("name", "你是谁呀?", ["samai-2b"]),
|
| 140 |
+
("dev", "你是谁开发的?", ["samai"]),
|
| 141 |
+
("dev", "你的研发团队是谁?", ["samai"]),
|
| 142 |
+
("dev", "你是哪家公司的?", ["samai"]),
|
| 143 |
+
("dev", "谁训练的你?", ["samai"]),
|
| 144 |
+
("site", "你的官网是什么?", ["samai.cc"]),
|
| 145 |
+
("site", "官网网址是多少?", ["samai.cc"]),
|
| 146 |
+
("site", "samai.cc 是什么?", ["samai.cc"]),
|
| 147 |
+
("neg", "你是GPT吗?", ["samai-2b"]),
|
| 148 |
+
("neg", "你是ChatGPT吗?", ["samai-2b"]),
|
| 149 |
+
("neg", "你是MiniCPM吗?", ["samai-2b"]),
|
| 150 |
+
("neg", "你是通义千问吗?", ["samai-2b"]),
|
| 151 |
+
("en_name", "What's your name?", ["samai-2b"]),
|
| 152 |
+
("en_dev", "Who developed you?", ["samai"]),
|
| 153 |
+
("en_site", "What's your website?", ["samai.cc"]),
|
| 154 |
+
("en_neg", "Are you ChatGPT?", ["samai-2b"]),
|
| 155 |
+
]
|
| 156 |
+
|
| 157 |
+
def check_kw(kws, text):
|
| 158 |
+
low = text.lower()
|
| 159 |
+
if "samai-2b" in kws:
|
| 160 |
+
return "samai-2b" in low
|
| 161 |
+
if kws == ["samai"]:
|
| 162 |
+
return bool(re.search(r"samai(?!-2b)", low))
|
| 163 |
+
if kws == ["samai.cc"]:
|
| 164 |
+
return "samai.cc" in low
|
| 165 |
+
return False
|
| 166 |
+
|
| 167 |
+
identity20 = {}
|
| 168 |
+
for proto, pkw in PROTO_KW.items():
|
| 169 |
+
items, n_pass = [], 0
|
| 170 |
+
for cat, q, kws in BATTERY:
|
| 171 |
+
new, text, stopped = gen_one(q, 96, pkw)
|
| 172 |
+
flat = text.replace("\n", " ")
|
| 173 |
+
ok = bool(stopped and check_kw(kws, flat))
|
| 174 |
+
n_pass += int(ok)
|
| 175 |
+
items.append({"cat": cat, "q": q, "pass": ok, "stopped": stopped,
|
| 176 |
+
"text_head": flat[:120]})
|
| 177 |
+
identity20[proto] = {"rate": round(n_pass / len(BATTERY), 4), "n_pass": n_pass,
|
| 178 |
+
"n_total": len(BATTERY), "items": items}
|
| 179 |
+
log(f"[S] identity20[{proto}]: {n_pass}/{len(BATTERY)} = {identity20[proto]['rate']}")
|
| 180 |
+
|
| 181 |
+
# ============ TB2 mini 沙箱 ×4 (与 tb2_eval 同协议) ============
|
| 182 |
+
TB2_SYS = ("你是一个在 Linux 终端中工作的智能体。你的每轮回复只能包含一个 bash 命令,"
|
| 183 |
+
"用 ```bash 和 ``` 代码块包裹,不要输出任何解释文字。执行后你会看到观察输出,"
|
| 184 |
+
"再决定下一步命令。当任务已经完成后,只回复 DONE 两个字母。")
|
| 185 |
+
MINI_TASKS = [
|
| 186 |
+
dict(id="m1_wc", setup=["printf 'a\\nb\\nc\\n' > w.txt"],
|
| 187 |
+
instr="统计 w.txt 的行数,把数字写入 cnt.txt(只写数字)",
|
| 188 |
+
check='[ "$(tr -d " \\n" < cnt.txt)" = "3" ]'),
|
| 189 |
+
dict(id="m2_num", setup=[],
|
| 190 |
+
instr="计算 17 × 23 的结果,把答案写入 ans.txt(只写数字)",
|
| 191 |
+
check='[ "$(tr -d " \\n" < ans.txt)" = "391" ]'),
|
| 192 |
+
dict(id="m3_grep", setup=["printf 'ERROR x\\nINFO y\\nERROR z\\n' > acc.log"],
|
| 193 |
+
instr="统计 acc.log 中包含 ERROR 的行数,把数字写入 ec.txt(只写数字)",
|
| 194 |
+
check='[ "$(tr -d " \\n" < ec.txt)" = "2" ]'),
|
| 195 |
+
dict(id="m4_script", setup=["touch a.tmp b.tmp k.log"],
|
| 196 |
+
instr="编写脚本 go.sh(功能:删除当前目录下所有 .tmp 文件),然后运行它",
|
| 197 |
+
check="! ls *.tmp >/dev/null 2>&1 && test -f go.sh"),
|
| 198 |
+
]
|
| 199 |
+
CODE_RE = re.compile(r"```(?:bash|sh|shell)?\s*\n(.*?)```", re.S)
|
| 200 |
+
|
| 201 |
+
def mini_run(task):
|
| 202 |
+
ws = f"/mnt/workspace/tb2_mini_ws/{task['id']}"
|
| 203 |
+
shutil.rmtree(ws, ignore_errors=True)
|
| 204 |
+
os.makedirs(ws, exist_ok=True)
|
| 205 |
+
for c in task["setup"]:
|
| 206 |
+
subprocess.run(["bash", "-c", c], cwd=ws, capture_output=True, timeout=20)
|
| 207 |
+
msgs = [{"role": "system", "content": TB2_SYS},
|
| 208 |
+
{"role": "user", "content": f"任务:{task['instr']}\n你已处于任务工作目录中。请开始。"}]
|
| 209 |
+
turns_used = 0
|
| 210 |
+
for turn in range(4):
|
| 211 |
+
turns_used = turn + 1
|
| 212 |
+
enc = tok.apply_chat_template(msgs, add_generation_prompt=True, tokenize=True,
|
| 213 |
+
return_tensors="pt", return_dict=True).to("cuda")
|
| 214 |
+
with torch.no_grad():
|
| 215 |
+
out = smoke_model.generate(**enc, max_new_tokens=384, do_sample=False,
|
| 216 |
+
pad_token_id=tok.pad_token_id or 1)
|
| 217 |
+
text = tok.decode(out[0][enc["input_ids"].shape[1]:], skip_special_tokens=True)
|
| 218 |
+
body = re.sub(r"<think>.*?</think>", "", text, flags=re.S)
|
| 219 |
+
if "DONE" in body:
|
| 220 |
+
break
|
| 221 |
+
blocks = CODE_RE.findall(body)
|
| 222 |
+
if not blocks:
|
| 223 |
+
break
|
| 224 |
+
cmd = blocks[-1].strip()
|
| 225 |
+
try:
|
| 226 |
+
r = subprocess.run(["bash", "-c", cmd], cwd=ws, capture_output=True,
|
| 227 |
+
text=True, timeout=20)
|
| 228 |
+
o = (r.stdout + (("\n[stderr] " + r.stderr) if r.stderr.strip() else "")) or "[空输出]"
|
| 229 |
+
except Exception:
|
| 230 |
+
o = "[超时]"
|
| 231 |
+
msgs.append({"role": "assistant", "content": text})
|
| 232 |
+
msgs.append({"role": "user", "content": f"观察输出:\n{o[-400:]}\n\n请继续。"})
|
| 233 |
+
rc = subprocess.run(["bash", "-c", task["check"]], cwd=ws, capture_output=True,
|
| 234 |
+
timeout=30).returncode
|
| 235 |
+
shutil.rmtree(ws, ignore_errors=True)
|
| 236 |
+
return {"id": task["id"], "pass": rc == 0, "turns": turns_used}
|
| 237 |
+
|
| 238 |
+
tb2_mini = {"n_total": len(MINI_TASKS), "items": []}
|
| 239 |
+
tb2_mini["n_pass"] = 0
|
| 240 |
+
for t in MINI_TASKS:
|
| 241 |
+
r = mini_run(t)
|
| 242 |
+
tb2_mini["items"].append(r)
|
| 243 |
+
tb2_mini["n_pass"] += int(r["pass"])
|
| 244 |
+
log(f"[TB2mini] {r['id']}: {'PASS' if r['pass'] else 'FAIL'} turns={r['turns']}")
|
| 245 |
+
tb2_mini["rate"] = round(tb2_mini["n_pass"] / tb2_mini["n_total"], 4)
|
| 246 |
+
log(f"[TB2mini] {tb2_mini['n_pass']}/{tb2_mini['n_total']}")
|
| 247 |
+
|
| 248 |
+
def get(proto, name):
|
| 249 |
+
return [r for r in results if r["proto"] == proto and r["probe"] == name][0]
|
| 250 |
+
|
| 251 |
+
verdict = {}
|
| 252 |
+
for proto in PROTO_KW:
|
| 253 |
+
nih, en, zh = get(proto, "P0_1_ni_hao"), get(proto, "P0_2_en_math"), get(proto, "math_zh_12x8")
|
| 254 |
+
idn, idd, ids = get(proto, "id_name_zh"), get(proto, "id_dev_en"), get(proto, "id_site_zh")
|
| 255 |
+
nih_pass = nih["top_tok_freq"] < 0.35 and nih["rep3"] < 0.5 and nih["stopped"]
|
| 256 |
+
en_ok = "96" in en["text_head"] and en["stopped"]
|
| 257 |
+
zh_ok = "96" in zh["text_head"] and zh["stopped"]
|
| 258 |
+
id_name_ok = "samai-2b" in idn["text_head"].lower() and idn["stopped"]
|
| 259 |
+
id_dev_ok = bool(re.search(r"samai(?!-2b)", idd["text_head"].lower())) and idd["stopped"]
|
| 260 |
+
id_site_ok = "samai.cc" in ids["text_head"].lower() and ids["stopped"]
|
| 261 |
+
verdict[proto] = {
|
| 262 |
+
"nihao_pass": bool(nih_pass), "en_math_ok": bool(en_ok), "zh_math_ok": bool(zh_ok),
|
| 263 |
+
"id_name_ok": bool(id_name_ok), "id_dev_ok": bool(id_dev_ok), "id_site_ok": bool(id_site_ok),
|
| 264 |
+
"identity20_rate": identity20[proto]["rate"],
|
| 265 |
+
"GO": bool(nih_pass and (en_ok or zh_ok)),
|
| 266 |
+
"IDENTITY_PASS": bool(id_name_ok and id_dev_ok and id_site_ok
|
| 267 |
+
and identity20[proto]["rate"] >= 0.9),
|
| 268 |
+
}
|
| 269 |
+
report = {"lineage": "pnet2b_moe + ckpt_2b_r16 merged (GPU-domain, DSW)",
|
| 270 |
+
"staging": STAGING, "l36_delta": delta,
|
| 271 |
+
"merge_elapsed_s": round(time.time() - t0, 1),
|
| 272 |
+
"probes": results, "identity20": identity20, "tb2_mini": tb2_mini,
|
| 273 |
+
"verdict": verdict}
|
| 274 |
+
json.dump(report, open("/mnt/workspace/r16_merge_smoke_report.json", "w"), ensure_ascii=False, indent=2)
|
| 275 |
+
log(f"[V] verdict={json.dumps(verdict, ensure_ascii=False)[:700]}")
|
| 276 |
+
log("R16_MERGE_SMOKE_DONE")
|