#!/usr/bin/env bash # 全流程冒烟(2026-10-18 第七轮,遗留 C-1:第 6–13 步从没跑过,第一次真跑要 15–28 h,中途才崩代价太大)。 # 在仓库旁边建一份副本(默认 ../<仓库名>_smoke,可设 SMOKE_DIR),用极小参数把 kin → accept 整条 `all` 跑一遍: # 2000 条老师任务(够出真实的 keepRate / gapDrop / 间隙分位 / R2,作数据侧可行性依据)、极小模型、M5/APG/挖错各缩到几十题、bench 每集几题、查看器 3 题。 # 训练侧只验证"每个阶段都能跑完、产物格式对、阶段之间衔接对",模型数值没有意义(门槛按冒烟放行:M5_STRICT=0 R4_STRICT=0 R2_STRICT=0); # 数据侧(stats / r2)数值有意义,汇总进 smoke.json。 # 不碰本仓库的 ml/data、ml/runs、webapp/models。结果:本仓库 ml/runs/smoke.json(每阶段返回码与耗时)+ 副本里的完整日志。 # 用法:bash ml/smoke.sh 预计 40–70 分钟(28 核 + 3090;生成 2000 条约 4 分钟,若门 1 触发共识约 20 分钟;其余阶段多数几分钟) set -e -o pipefail cd "$(dirname "$0")/.." SRC=$(pwd); PY=${PYTHON:-python} D=${SMOKE_DIR:-$(dirname "$SRC")/$(basename "$SRC")_smoke} echo "[smoke] 副本目录:$D" rm -rf "$D"; mkdir -p "$D" # 只拷受版本管理的文件(含未提交改动)+ 运行需要的本地依赖;数据与产物不拷 git ls-files -z | tar --null -T - -cf - | (cd "$D" && tar xf -) for x in ml/node_modules webapp/vendor/ort webapp/generated; do [ -d "$x" ] && { mkdir -p "$D/$(dirname $x)"; cp -r "$x" "$D/$x"; }; done cd "$D" export N_TASKS=2000 NPROC=${SMOKE_NPROC:-$(( $(nproc) > 24 ? 24 : $(nproc) ))} TARGET_SAMPLES=1 CFG=ml/configs/smoke.json BENCH_STEPS=50 \ M5_N=32 M5_STRICT=0 AUTO_WTA=0 R4_STRICT=0 R2_STRICT=0 R2_ARGS="--queries 2000 --pool 15000" \ R4_ARGS="--viewer-n 3" APG_FIRST_STEPS=20 ROUNDS=1 APG_STEPS=20 MINE_N=128 MINE_GATE_N=64 \ BENCH_ARGS="--nc-n 3 --route-n 3 --r8-n 2" VIEWER_N=3 MONITOR=0 FRESH=1 PIPE_OWNER=冒烟 t0=$(date +%s); rc=0 bash ml/pipeline.sh all 2>&1 | tee ml/runs_smoke.log || rc=$? mkdir -p "$SRC/ml/runs" $PY - "$D" "$rc" "$(( $(date +%s) - t0 ))" "$SRC/ml/runs/smoke.json" <<'PY' import json, os, sys d, rc, sec, out = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4] p = os.path.join(d, 'ml', 'runs', 'pipeline.json') st = json.load(open(p, encoding='utf-8')) if os.path.exists(p) else {} stages = {k: {'rc': v.get('rc'), 'min': round((v['end'] - v['start']) / 60, 1) if v.get('end') and v.get('start') else None} for k, v in (st.get('stages') or {}).items()} acc = os.path.join(d, 'ml', 'runs', 'acceptance.json') def rd(*p): try: return json.load(open(os.path.join(d, *p), encoding='utf-8')) except Exception: return None stx = rd('ml', 'data', 'stats.json') or {} data = {k: stx.get(k) for k in ('tasks', 'keepRate', 'gapDrop', 'vertexMinGapMm', 'planMs', 'samplesPerTask', 'consensusDrop')} data['r2'] = rd('ml', 'data', 'r2.json'); data['gate1'] = rd('ml', 'runs', 'consistency.json') data['r4'] = rd('ml', 'runs', 'r4.json'); data['r9'] = rd('ml', 'runs', 'r9_browser.json') pb = rd('ml', 'runs', 'policy_bench.json') or {} data['r8_genS'] = (pb.get('r8_adversarial') or {}).get('genS') res = {'rc': rc, 'wall_s': sec, 'dir': d, 'stages': stages, 'acceptance_written': os.path.exists(acc), 'data': data, 'log': os.path.join(d, 'ml', 'runs_smoke.log'), 'note': '训练侧只看每阶段 rc=0 与 acceptance_written;data 段(2000 条任务的统计、R2、门 1、R4、R9 路径、R8 出题耗时)是可行性依据'} json.dump(res, open(out, 'w', encoding='utf-8'), indent=1, ensure_ascii=False) print('[smoke] rc=%d,%d 分钟;结果 %s' % (rc, sec // 60, out)) PY exit $rc