tchbcb commited on
Commit
f208157
·
verified ·
1 Parent(s): 951a7fd

r18 scripts sync

Browse files
Files changed (1) hide show
  1. 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")