Translate user-facing runtime output to English, machine prefixes preserved, + CLI output test (#67, #85)

* feat: standardize runtime output in English

* test: cover English CLI output
This commit is contained in:
Sidd
2026-07-12 17:08:40 +05:30
committed by GitHub
parent 6e7aa6f92e
commit 2416bc9079
24 changed files with 330 additions and 280 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ from pathlib import Path
SPEED_RE = re.compile(r"REPLAY decode:.*\| ([0-9.]+) tok/s")
PROFILE_RE = re.compile(
r"PROFILO: expert-disk ([0-9.]+)s \| expert-matmul ([0-9.]+)s "
r"\| attention ([0-9.]+)s .* lm_head ([0-9.]+)s \| altro ([0-9.-]+)s"
r"PROFILE: expert-disk ([0-9.]+)s \| expert-matmul ([0-9.]+)s "
r"\| attention ([0-9.]+)s .* lm_head ([0-9.]+)s \| other ([0-9.-]+)s"
)
PROFILE_KEYS = ("disk", "expert_matmul", "attention", "lm_head", "other")
+32 -31
View File
@@ -139,11 +139,12 @@ def main():
ap.add_argument("--min-free-gb", type=float, default=20.0)
ap.add_argument("--selftest", action="store_true")
ap.add_argument("--mtp", action="store_true",
help="scarica/converte SOLO la testa MTP (model.layers.<n_layers>.*) -> out-mtp-*.safetensors")
help="download and convert ONLY the MTP head (model.layers.<n_layers>.*) -> out-mtp-*.safetensors")
ap.add_argument("--indexer", action="store_true",
help="estrae SOLO i pesi del DSA lightning indexer -> out-idx-*.safetensors. ATTENZIONE: "
"i tensori indexer sono sparsi su ~tutti gli shard: ri-scarica l'intero repo (~756 GB "
"di traffico) per tenerne pochi GB. Resumabile shard per shard. Consigliato --ebits 8.")
help="extract ONLY the DSA lightning-indexer weights -> out-idx-*.safetensors. WARNING: "
"indexer tensors are spread across nearly every shard, so this re-downloads the whole "
"repository (~756 GB of traffic) to retain only a few GB. Resumable per shard. "
"Recommended: --ebits 8.")
a = ap.parse_args()
if a.ebits is None:
# testa MTP a int4 = acceptance ~0-4% (misurato, issue #8): il draft sbaglia sempre
@@ -163,8 +164,8 @@ def main():
q = (w / sc.repeat_interleave(bs,0).repeat_interleave(bs,1)).to(torch.float8_e4m3fn)
deq = (q.to(torch.float32) * sc.repeat_interleave(bs,0).repeat_interleave(bs,1))
rel = (deq - w).abs().mean() / w.abs().mean()
print(f"[selftest fp8 block-dequant] errore relativo medio = {rel:.4f} "
f"({'OK' if rel < 0.05 else 'ALTO'})")
print(f"[selftest fp8 block-dequant] mean relative error = {rel:.4f} "
f"({'OK' if rel < 0.05 else 'HIGH'})")
return
os.makedirs(a.outdir, exist_ok=True)
@@ -178,7 +179,7 @@ def main():
for fn in ["config.json"]:
src = os.path.join(a.indir, fn)
if os.path.exists(src): shutil.copy(src, a.outdir)
print(f"convertito {len(shards)} shard -> {a.outdir}")
print(f"converted {len(shards)} shards -> {a.outdir}")
return
# reale: scarica shard per shard, converte, cancella
@@ -211,7 +212,7 @@ def main():
lock = open(os.path.join(a.outdir, ".convert.lock"), "w")
try: fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
print("ERRORE: un altro convertitore sta gia' lavorando su questa outdir. Esco."); return
print("ERROR: another converter is already using this output directory. Exiting."); return
# dimensioni note dei file, riempite dopo repo_info: il downloader multi-stream le usa
# per calcolare i confini dei segmenti e per sapere quando un file e' completo.
@@ -280,13 +281,13 @@ def main():
except Exception as ex:
with log_lock:
nres[0] += 1
print(f" [dl] s{t}: {type(ex).__name__} a/at {(s0+done[t])/1e9:.2f} GB: "
f"riprendo/resuming (#{nres[0]})", flush=True)
print(f" [dl] s{t}: {type(ex).__name__} at {(s0+done[t])/1e9:.2f} GB: "
f"resuming (#{nres[0]})", flush=True)
_t.sleep(min(15, 1 + nres[0] // NS))
th = [threading.Thread(target=worker, args=(t,), daemon=True) for t in range(NS)]
for x in th: x.start()
print(f" [dl {_t.strftime('%H:%M:%S')}] connesso/connected: {NS} stream, "
f"{sum(done)/1e9:.2f} di/of {expected/1e9:.2f} GB", flush=True)
print(f" [dl {_t.strftime('%H:%M:%S')}] connected: {NS} streams, "
f"{sum(done)/1e9:.2f} of {expected/1e9:.2f} GB", flush=True)
mark = sum(done); tmark = t0
while any(x.is_alive() for x in th):
_t.sleep(5)
@@ -309,7 +310,7 @@ def main():
os.replace(part, out)
dt = max(_t.time() - t0, 1e-9)
print(f" [dl] {fn}: {expected/1e9:.2f} GB in {dt/60:.1f} min "
f"({expected/dt/1e6:.1f} MB/s medi/avg, {NS} stream, {nres[0]} riprese/resumes)", flush=True)
f"({expected/dt/1e6:.1f} MB/s avg, {NS} streams, {nres[0]} resumes)", flush=True)
return out
def _download_single(url, fn, out, part, expected):
@@ -335,9 +336,9 @@ def main():
cl = r.headers.get("Content-Length")
if cl: expected = have + int(cl)
if have == 0 or nres: # segnale di vita subito / immediate sign of life
print(f" [dl {_t.strftime('%H:%M:%S')}] connesso/connected"
print(f" [dl {_t.strftime('%H:%M:%S')}] connected"
f"{f' @ {have/1e9:.2f} GB' if have else ''}"
f"{f' di/of {expected/1e9:.2f} GB' if expected else ''}", flush=True)
f"{f' of {expected/1e9:.2f} GB' if expected else ''}", flush=True)
with open(part, "ab" if have else "wb") as f:
if not have: f.truncate(0)
while True:
@@ -357,16 +358,16 @@ def main():
except urllib.error.HTTPError as ex:
if ex.code == 416: break # gia' completo / already complete
nres += 1
print(f" [dl] HTTP {ex.code} a/at {have/1e9:.2f} GB: riprendo/resuming (#{nres})", flush=True)
print(f" [dl] HTTP {ex.code} at {have/1e9:.2f} GB: resuming (#{nres})", flush=True)
_t.sleep(min(15, 1 + nres))
except Exception as ex:
nres += 1
print(f" [dl] {type(ex).__name__} a/at {have/1e9:.2f} GB: riprendo/resuming (#{nres})", flush=True)
print(f" [dl] {type(ex).__name__} at {have/1e9:.2f} GB: resuming (#{nres})", flush=True)
_t.sleep(min(15, 1 + nres))
os.replace(part, out)
dt = max(_t.time() - t0, 1e-9); sz = os.path.getsize(out)
print(f" [dl] {fn}: {sz/1e9:.2f} GB in {dt/60:.1f} min "
f"({sz/dt/1e6:.1f} MB/s medi/avg, {nres} riprese/resumes)", flush=True)
f"({sz/dt/1e6:.1f} MB/s avg, {nres} resumes)", flush=True)
return out
from safetensors.numpy import save_file
@@ -380,7 +381,7 @@ def main():
break
except KeyboardInterrupt: raise
except Exception as ex:
w = min(60, 5*(att+1)); print(f"repo_info KO ({type(ex).__name__}): riprovo tra {w}s", flush=True); _t.sleep(w)
w = min(60, 5*(att+1)); print(f"repo_info failed ({type(ex).__name__}); retrying in {w}s", flush=True); _t.sleep(w)
shards = sorted(s.rfilename for s in info.siblings if s.rfilename.endswith(".safetensors"))
for fn in ["config.json", "tokenizer.json", "tokenizer_config.json", "generation_config.json"]:
try: shutil.copy(hf_hub_download(a.repo, fn, local_dir=a.outdir+"/_meta"), a.outdir)
@@ -392,19 +393,19 @@ def main():
f"https://huggingface.co/{a.repo}/resolve/main/model.safetensors.index.json", timeout=30).read())["weight_map"]
pref = f"model.layers.{a.n_layers}."
mtp_shards = sorted(set(v for k, v in idx.items() if k.startswith(pref)))
print(f"[MTP] testa nel layer {a.n_layers}: {len(mtp_shards)} shard da processare: {mtp_shards}")
print(f"[MTP] head at layer {a.n_layers}: {len(mtp_shards)} shards to process: {mtp_shards}")
for i, sh in enumerate(mtp_shards):
outp = os.path.join(a.outdir, f"out-mtp-{i:05d}.safetensors")
if os.path.exists(outp): print(f"[MTP] {outp} gia' fatto"); continue
print(f"[MTP {i+1}/{len(mtp_shards)}] scarico {sh}...", flush=True)
if os.path.exists(outp): print(f"[MTP] {outp} already done"); continue
print(f"[MTP {i+1}/{len(mtp_shards)}] downloading {sh}...", flush=True)
p = download_retry(a.repo, sh, tmp)
out = {}; convert_shard(p, out, a.n_layers, a.ebits, a.io_bits, a.xbits, keep_mtp=True)
save_file(out, outp)
os.remove(p)
for blob in glob.glob(os.path.join(tmp, "**", "*"), recursive=True):
if os.path.isfile(blob): os.remove(blob)
print(f" -> {os.path.basename(outp)} ({os.path.getsize(outp)/1e9:.2f} GB, {len(out)} tensori)", flush=True)
shutil.rmtree(tmp, ignore_errors=True); print("[MTP] FATTO."); return
print(f" -> {os.path.basename(outp)} ({os.path.getsize(outp)/1e9:.2f} GB, {len(out)} tensors)", flush=True)
shutil.rmtree(tmp, ignore_errors=True); print("[MTP] DONE."); return
if a.indexer:
import urllib.request
idx = json.loads(urllib.request.urlopen(
@@ -412,25 +413,25 @@ def main():
idx_shards = sorted(set(v for k, v in idx.items()
if "indexer" in k and 0 <= layer_idx(k) < a.n_layers))
tot_gb = len(idx_shards) * 5.4
print(f"[IDX] pesi indexer su {len(idx_shards)} shard (~{tot_gb:.0f} GB di download totale, resumabile)")
print(f"[IDX] indexer weights across {len(idx_shards)} shards (~{tot_gb:.0f} GB total download, resumable)")
for i, sh in enumerate(idx_shards):
outp = os.path.join(a.outdir, f"out-idx-{i:05d}.safetensors")
if os.path.exists(outp): continue # gia' fatto -> ripartibile
print(f"[IDX {i+1}/{len(idx_shards)}] scarico {sh}...", flush=True)
print(f"[IDX {i+1}/{len(idx_shards)}] downloading {sh}...", flush=True)
p = download_retry(a.repo, sh, tmp)
out = {}; convert_shard(p, out, a.n_layers, a.ebits, a.io_bits, a.xbits, keep_idx=True)
if out: save_file(out, outp)
os.remove(p)
for blob in glob.glob(os.path.join(tmp, "**", "*"), recursive=True):
if os.path.isfile(blob): os.remove(blob)
print(f" -> {os.path.basename(outp)} ({len(out)} tensori)", flush=True)
shutil.rmtree(tmp, ignore_errors=True); print("[IDX] FATTO."); return
print(f" -> {os.path.basename(outp)} ({len(out)} tensors)", flush=True)
shutil.rmtree(tmp, ignore_errors=True); print("[IDX] DONE."); return
for i, sh in enumerate(shards):
if free_gb(a.outdir) < a.min_free_gb:
print(f"STOP: spazio libero < {a.min_free_gb} GB. Libera spazio e rilancia (riprende)."); break
print(f"STOP: free space is below {a.min_free_gb} GB. Free space and rerun to resume."); break
outp = os.path.join(a.outdir, f"out-{i:05d}.safetensors")
if os.path.exists(outp): continue # gia' fatto -> ripartibile
print(f"[{i+1}/{len(shards)}] scarico {sh} (libero {free_gb(a.outdir):.0f} GB)...", flush=True)
print(f"[{i+1}/{len(shards)}] downloading {sh} ({free_gb(a.outdir):.0f} GB free)...", flush=True)
p = download_retry(a.repo, sh, tmp)
out = {}; convert_shard(p, out, a.n_layers, a.ebits, a.io_bits, a.xbits)
save_file(out, outp)
@@ -439,7 +440,7 @@ def main():
if os.path.isfile(blob): os.remove(blob)
print(f" -> {os.path.basename(outp)} ({os.path.getsize(outp)/1e9:.2f} GB)", flush=True)
shutil.rmtree(tmp, ignore_errors=True)
print("FATTO." if i == len(shards)-1 else "INTERROTTO (rilancia per riprendere).")
print("DONE." if i == len(shards)-1 else "INTERRUPTED (rerun to resume).")
if __name__ == "__main__":
main()
+6 -6
View File
@@ -28,15 +28,15 @@ def check():
sts = [s for s in info.siblings if s.rfilename.endswith(".safetensors")]
free = shutil.disk_usage(os.path.dirname(DEST) or "/").free
print(f"repo: {REPO}")
print(f" file totali: {len(info.siblings)} ({len(sts)} shard safetensors)")
print(f" dimensione totale: {human(tot)}")
print(f" spazio libero in {DEST}: {human(free)}")
print(f" {'OK: ci sta' if free > tot*1.05 else 'ATTENZIONE: spazio insufficiente'}")
print(f" total files: {len(info.siblings)} ({len(sts)} safetensors shards)")
print(f" total size: {human(tot)}")
print(f" free space in {DEST}: {human(free)}")
print(f" {'OK: enough space' if free > tot*1.05 else 'WARNING: not enough space'}")
def download():
os.makedirs(DEST, exist_ok=True)
free = shutil.disk_usage(DEST).free
print(f"Scarico {REPO} -> {DEST} (libero: {human(free)})")
print(f"Downloading {REPO} -> {DEST} (free: {human(free)})")
# resume_download e' implicito; in caso di interruzione, rilancia e riprende.
snapshot_download(
repo_id=REPO,
@@ -44,7 +44,7 @@ def download():
allow_patterns=["*.safetensors", "*.json", "*.txt", "*.model"],
max_workers=8,
)
print("FATTO. Pesi in:", DEST)
print("DONE. Weights saved in:", DEST)
if __name__ == "__main__":
if "--check" in sys.argv:
+15 -15
View File
@@ -43,7 +43,7 @@ def load_docs(task, data_dir, limit, seed):
return SMOKE[:limit] if limit else SMOKE
path = os.path.join(data_dir, task + ".jsonl")
if not os.path.exists(path):
sys.exit(f"manca {path} — generalo con: python3 tools/fetch_benchmarks.py --out {data_dir} --tasks {task}")
sys.exit(f"missing {path} — generate it with: python3 tools/fetch_benchmarks.py --out {data_dir} --tasks {task}")
docs = [json.loads(l) for l in open(path) if l.strip()]
random.Random(seed).shuffle(docs)
return docs[:limit] if limit else docs
@@ -84,9 +84,9 @@ def score_accuracy(tasks, meta, perq, lp):
print(f"{t:<18} {n:>4} {100*acc/n:>6.1f}% {100*accn/n:>8.1f}%")
overall.append(100 * accn / n)
for mdl, sc in REFERENCE.get(t, {}).items():
if sc is not None: print(f"{' rif '+mdl:<18} {'':>4} {'':>7} {sc:>8.1f}%")
if sc is not None: print(f"{' ref '+mdl:<18} {'':>4} {'':>7} {sc:>8.1f}%")
if overall:
print(f"\nMEDIA acc_norm: {sum(overall)/len(overall):.1f}% su {len(overall)} task")
print(f"\nMEAN acc_norm: {sum(overall)/len(overall):.1f}% across {len(overall)} tasks")
def main():
ap = argparse.ArgumentParser()
@@ -99,8 +99,8 @@ def main():
ap.add_argument("--cap", type=int, default=64)
ap.add_argument("--bits", default="")
ap.add_argument("--seed", type=int, default=1234)
ap.add_argument("--dry", action="store_true", help="costruisci le richieste e fermati (no motore)")
ap.add_argument("--selftest", action="store_true", help="verifica la matematica dello scoring")
ap.add_argument("--dry", action="store_true", help="build requests and stop without running the engine")
ap.add_argument("--selftest", action="store_true", help="verify the scoring calculations")
a = ap.parse_args()
if a.selftest: # acc/acc_norm con logprob sintetici
@@ -113,32 +113,32 @@ def main():
tk = Tokenizer.from_file(os.path.join(a.snap, "tokenizer.json"))
tasks = [t.strip() for t in a.tasks.split(",") if t.strip()]
docs_by_task = {t: load_docs(t, a.data, a.limit, a.seed) for t in tasks}
for t, d in docs_by_task.items(): print(f"[{t}] {len(d)} domande", file=sys.stderr)
for t, d in docs_by_task.items(): print(f"[{t}] {len(d)} questions", file=sys.stderr)
reqs, meta, perq = build_requests(tk, docs_by_task)
print(f"richieste totali: {len(reqs)} (opzioni)", file=sys.stderr)
print(f"total requests: {len(reqs)} (answer options)", file=sys.stderr)
if a.dry:
for r in reqs[:3]: print(" esempio req:", r[:80], "...", file=sys.stderr)
print("DRY: meccanica ok (tokenizzazione+richieste). Niente motore.", file=sys.stderr); return
for r in reqs[:3]: print(" example request:", r[:80], "...", file=sys.stderr)
print("DRY: request construction and tokenization passed. Engine was not run.", file=sys.stderr); return
req_path = tempfile.mktemp(suffix=".txt")
open(req_path, "w").write("\n".join(reqs) + "\n")
env = dict(os.environ, SNAP=a.snap, SCORE=req_path)
if a.ram: env["RAM_GB"] = str(a.ram)
cmd = [a.glm, str(a.cap)] + a.bits.split()
print("eseguo:", " ".join(cmd), file=sys.stderr)
print("running:", " ".join(cmd), file=sys.stderr)
t0 = time.time()
proc = subprocess.run(cmd, env=env, capture_output=True, text=True)
if proc.returncode != 0:
print("ERRORE motore:\n", proc.stderr[-2000:], file=sys.stderr); sys.exit(1)
print("ENGINE ERROR:\n", proc.stderr[-2000:], file=sys.stderr); sys.exit(1)
lines = [l for l in proc.stdout.strip().splitlines() if l and l[0] in "-0123456789"]
if len(lines) != len(reqs):
print(f"ATTENZIONE: {len(lines)} output vs {len(reqs)} richieste", file=sys.stderr)
print(f"WARNING: {len(lines)} outputs for {len(reqs)} requests", file=sys.stderr)
lp = [float(l.split()[0]) for l in lines]
print(f"(motore: {time.time()-t0:.0f}s){proc.stderr.strip().splitlines()[-1] if proc.stderr.strip() else ''}", file=sys.stderr)
print(f"(engine: {time.time()-t0:.0f}s){proc.stderr.strip().splitlines()[-1] if proc.stderr.strip() else ''}", file=sys.stderr)
score_accuracy(tasks, meta, perq, lp)
print("\nNB: confronta acc_norm col punteggio PUBBLICATO di GLM-5.2 (model card). Se vicino,"
"\n la quantizzazione int4 ha preservato il modello. (riempi REFERENCE in tools/eval_glm.py)")
print("\nNOTE: compare acc_norm with GLM-5.2's PUBLISHED model-card score. A close result"
"\n indicates that int4 quantization preserved quality. (Fill REFERENCE in tools/eval_glm.py.)")
os.remove(req_path)
if __name__ == "__main__":
+1 -1
View File
@@ -48,7 +48,7 @@ def main():
from datasets import load_dataset
os.makedirs(a.out, exist_ok=True)
for t in [x.strip() for x in a.tasks.split(",") if x.strip()]:
if t not in TASKS: print("task ignoto:", t); continue
if t not in TASKS: print("unknown task:", t); continue
path, cfg, split, fn = TASKS[t]
ds = load_dataset(path, cfg, split=split)
idx = list(range(len(ds))); random.Random(a.seed).shuffle(idx)
+1 -1
View File
@@ -41,7 +41,7 @@ def emit(name, rs):
print("};")
print(f"static const int {name}_n = {len(rs)};\n")
print("/* GENERATO da tools/gen_unicode.py — non modificare a mano. */")
print("/* GENERATED by tools/gen_unicode.py — do not edit by hand. */")
print("#ifndef TOK_UNICODE_H\n#define TOK_UNICODE_H\n#include <stdint.h>\n")
emit("uni_L", L); emit("uni_N", N); emit("uni_S", S)
print("""static int uni_in(const uint32_t t[][2], int n, uint32_t cp){
+2 -2
View File
@@ -53,7 +53,7 @@ with torch.no_grad():
layer.mlp.gate.e_score_correction_bias.copy_(
torch.linspace(-0.1, 0.1, cfg.n_routed_experts))
print("=== tensori dello state_dict (nomi per il loader C) ===")
print("=== state_dict tensors (names used by the C loader) ===")
for n, p in model.state_dict().items():
print(f" {n:60s} {tuple(p.shape)}")
@@ -76,4 +76,4 @@ print("tf_pred:", tf_pred)
model.save_pretrained("glm_tiny", safe_serialization=True)
json.dump(cfg.to_dict(), open("glm_tiny/config.json", "w"))
json.dump({"prompt_ids": prompt, "full_ids": full, "tf_pred": tf_pred}, open("ref_glm.json", "w"))
print("\nsalvato: glm_tiny/ (pesi+config) e ref_glm.json")
print("\nsaved: glm_tiny/ (weights + config) and ref_glm.json")