Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions RUNBOOK_KERNEL_CAMPAIGN2.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,41 @@ VLLM_GLM53_INDEXER_GATE_SPLITK=1 bash launchers/start-glm53-nvfp4-tp4.sh

---

## EXP-10 — 드래프터 fc GEMM (`probes/drafter_fc_check.py`, 2026-09-02 추가 — 프로브 먼저, 커널은 숫자가 나온 뒤)

보충 분해 3 이 "split-K 후보"로 지명한 자리: 드래프터 fc 투영 `[M, K=5×4096] × [K, 4096]`,
스텝당 1회, 현재 부팅에선 deep_gemm fp8×fp4 로 809 us — fp4 42 MB 를 52~104 GB/s 로
읽는 것(W4 스트림 하한 ~190 GB/s 의 1/4~1/2). 지금은 호스트 유휴 뒤에 숨어 임계경로가
아니지만, EXP-7 이 은신처를 없애는 순간 드래프터 ~6% 천장의 주성분이 된다.

1단계는 부팅 없는 오프라인 프로브. 이미지에 존재하는 모든 팔을 실형상(K=20480)에서
잰다 — stock(`_fp8_fp4_dense_gemm`), bf16 mm(168 MB eager 경로), F.linear, 그리고
mk_w4 베스트 에포트(K=4096 전용이면 SKIP 이 답이다). 콜드 웨이트는 6개 순환 그래프
(252 MB ≫ L2 24 MB). #231 의 교훈대로 `--config` 로 플릿 실형상을 확인할 것.

```bash
docker run --rm --gpus all --entrypoint python3 \
--mount type=bind,src=$REPO,dst=/repo,readonly glm53:v13-b12x \
/repo/probes/drafter_fc_check.py --config /models/glm53.../config.json
```

- 승자 판정: stock 이 W4 바운드(42 MB ÷ 190 GB/s ≈ 220 us)에서 멀수록 새 split-K
커널의 몫. 베스트 팔이 이미 바운드에 닿으면 이 축은 프로브 한 번으로 닫힌다.
- 부팅 귀속은 숫자가 나온 뒤 정한다: 수치 변경 축(e2m1)이라 EXP-9 를 얹은
EXP-7 부팅에 또 얹을 수는 없다(부팅당 수치 축 하나).
- 기대값(정직): 지금 스텝 기준 상한 ~0.6 ms = ~0.8%. 은신처가 사라진 뒤엔
드래프터 노출분의 주성분이라 가치가 커진다.

## 브래킷 자동화 — `bench/bracket.py` (도구, 판정 아님)

`leg`(살아있는 서버에 rep 기록) + `judge`(기록 판정) 2중 명령. 원장 규율을 코드로
박은 것: 판정 채널 C=1 step/s (`tok/s ÷ (1 + k×raw_acc)`), 유의 문턱은 **base 두
다리의 드리프트**(같은 설정 재부팅 차이). 재기동은 여전히 사람이 한다 — 이 도구는
읽기 전용이고 다리 사이 env 스냅샷을 기록해 #116 부류(노브 미전달 부팅)를 judge 가
보게 한다. C≠1 기록은 참고용으로만 남긴다.

---

## 순서와 근거

1. **EXP-1 (EP)** — 기대값 최대. 실패해도 부팅 하나로 원장에 정리된다.
Expand All @@ -375,6 +410,7 @@ VLLM_GLM53_INDEXER_GATE_SPLITK=1 bash launchers/start-glm53-nvfp4-tp4.sh
8. ~~EXP-8 (dflash async scheduling)~~ — **기각**: 이미 켜져 있었다(`dflash` ∈ `EagleModelTypes`). 모듈 제거, 천장 주장 철회.
9. **EXP-9 (head-gate split-K)** — 단독 부팅 금지, EXP-7 부팅에 얹는다.
10. **EXP-7 이 붙은 뒤 드래프터 D 를 다시 잰다** — 9월 1일 트레이스에서 드래프터 ~4.3 ms 는 다음 스텝의 호스트 준비 유휴 뒤에 숨어 있었다(그래서 D≈0). 은신처가 사라지면 임계경로에 올라온다(천장 ~6%; fc GEMM 809 us 는 K=20480 직렬 스케줄이라 split-K 후보). #104 를 지금 재론하는 것이 아니라 조건이 바뀐 뒤의 재측정이다. 오프라인으로 닫을 >1% 레버는 더 없다(STEP_KERNEL_MAP 보충 분해 3).
11. **EXP-10 (드래프터 fc)** — 부팅 없는 프로브가 먼저: stock 이 W4 바운드에서 얼마나 먼 지가 커널 제작의 전부. 베스트 기존 팔이 바운드에 닿으면 축은 프로브로 닫힌다. 판정이 필요해지면 `bench/bracket.py` 로 브래킷을 돌린다.

## 금지 (기존 판정 유지 — 재조사하지 않는다)

Expand Down
247 changes: 247 additions & 0 deletions bench/bracket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""base→cand→base 브래킷의 기록과 판정. MEASUREMENTS 원장의 판정 규율을 코드로
옮긴 것: 판정 채널은 C=1 step/s (`step/s = tok/s / (1 + k x raw_acc)`, k =
스페큘레이티브 토큰 수), 유의 판정값은 **base 두 다리의 드리프트** — cand 효과가
같은 설정을 다시 부팅했을 때 벌어지는 차이보다 커야 한다.

재기동은 사람이 한다 (런북 규칙: 자동화는 읽기 전용). 이 도구의 `leg` 은 살아있는
서버에 rep 를 돌려 jsonl 에 기록하고, `judge` 는 기록된 다리들로 판정문을 만든다.
env 스냅샷은 **요청값**이다 — 부팅 로그의 engine-confirmed 값과 대조할 것
(#116: 노브가 안 통한 부팅이 무효 A/B 로 판정된 적이 있다).

python3 bench/bracket.py leg --name EXP9 --tag base --reps 6
python3 bench/bracket.py leg --name EXP9 --tag cand --reps 6
python3 bench/bracket.py leg --name EXP9 --tag base --reps 6
python3 bench/bracket.py judge --name EXP9

C=2/4 로 `leg` 을 돌면 기록만 하고 판정에서는 빠진다 — 원장: C>1 은 수락률이
요청마다 달라 단일 정규화가 안 먹는다 (CV 12~13.5%).
"""
from __future__ import annotations

import argparse
import datetime
import importlib.util
import json
import os
import subprocess
import sys
from statistics import median

# 노브 스냅샷 목록 — 이 레포가 지금까지 판정에 쓴 환경 키. 새 노브를 여는 PR 은
# 여기에 한 줄씩 추가한다 (빠뜨리면 judge 가 다리 사이 설정 차이를 못 본다).
ENV_KEYS = (
[k for k in (
"ENABLE_EP", "CUSTOM_OPS_AXIS", "BENCH_MODEL", "GRAPH_CAP",
"VLLM_GLM53_FP8_DENSE", "VLLM_GLM53_FP8_DENSE_BPROJ",
"VLLM_GLM53_INDEXER_GATE_SPLITK", "VLLM_GLM53_FUSED_K_GATE",
"VLLM_GLM53_PREP_FUSED", "VLLM_GLM53_ASYNC_DFLASH",
"VLLM_GLM53_MHC_SMALLM", "VLLM_DFLASH_PREP_WARMUP",
"VLLM_GLM53_MK_PDL", "VLLM_GLM53_MK_KSR_OUT",
) if k in os.environ]
+ sorted(k for k in os.environ
if k.startswith(("VLLM_GLM53_MK_", "VLLM_GLM53_KPOOL")))
)


def _bench_dec():
spec = importlib.util.spec_from_file_location(
"bench_dec", os.path.join(os.path.dirname(os.path.abspath(__file__)),
"bench-dec.py"))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod


def _git_sha():
try:
return subprocess.run(["git", "rev-parse", "--short", "HEAD"],
capture_output=True, text=True, timeout=10,
cwd=os.path.dirname(os.path.abspath(__file__))
).stdout.strip() or None
except Exception:
return None


def _spec_delta(m0, m1):
"""(legacy acc, exact raw acc) over a metrics counter delta — bench-dec 의
두 관례를 그대로: legacy 는 원장의 모든 수치가 쓰는 substring 합, raw 는
정확한 이름 비율."""
delta = {k: m1.get(k, 0.0) - m0.get(k, 0.0) for k in m1}
acc = sum(v for k, v in delta.items() if "accept" in k)
drafts = sum(v for k, v in delta.items() if "draft" in k and "accept" not in k)
acc_x = sum(v for k, v in delta.items()
if k.endswith("num_accepted_tokens_total"))
draft_x = sum(v for k, v in delta.items()
if k.endswith("num_draft_tokens_total"))
legacy = acc / drafts if drafts > 0 else None
raw = acc_x / draft_x if draft_x > 0 else None
return legacy, raw


def step_s_of(tok_s: float, acc_raw, num_spec: int):
"""원장 판정 채널. 토큰/스텝 = 1 + k x raw_acc; 수락률 카운터가 없으면
None — judge 는 그 다리를 tok/s 로 떨어뜨려 놓고 표시한다."""
if acc_raw is None:
return None
return tok_s / (1.0 + num_spec * acc_raw)


def cmd_leg(args) -> int:
bd = _bench_dec()
rec = {"ts": datetime.datetime.now().isoformat(timespec="seconds"),
"name": args.name, "tag": args.tag, "conc": args.conc,
"num_spec": args.num_spec, "git": _git_sha(),
"env": {k: os.environ[k] for k in ENV_KEYS},
"model": bd.MODEL, "reps": []}
Comment on lines +93 to +97

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Capture the environment used to launch the server

This snapshot reads the environment of the later bracket.py leg process, not the environment with which the server was booted. In the runbook's documented flow, knobs are supplied as one-command prefixes such as VLLM_GLM53_ASYNC_DFLASH=1 bash launchers/...; those assignments expire when the launcher returns, and the shown leg commands do not re-export them, so base and candidate records normally both contain an empty or identical snapshot. Require the boot configuration as explicit leg metadata or document and enforce passing it again, otherwise the advertised knob-delivery audit cannot detect a misconfigured boot.

Useful? React with 👍 / 👎.

print(f"[bracket] {args.name} tag={args.tag} C={args.conc} reps={args.reps} "
f"model={bd.MODEL} git={rec['git']} env={rec['env'] or '{}'}")
for r in range(args.reps):
m0 = bd.spec_counters()
toks, dt = bd.sweep(args.conc, r + 1 + args.conc * 1000)
legacy, raw = _spec_delta(m0, bd.spec_counters())
tok_s = toks / dt
s = step_s_of(tok_s, raw, args.num_spec)
rec["reps"].append({"rep": r + 1, "tok_s": round(tok_s, 2),
"acc_legacy": None if legacy is None else round(legacy, 4),
"acc_raw": None if raw is None else round(raw, 4),
"step_s": None if s is None else round(s, 2)})
print(f"{args.tag} C={args.conc} rep{r + 1}: {tok_s:.1f} tok/s"
+ (f", raw acc {raw:.1%}" if raw is not None else "")
+ (f", step/s {s:.1f}" if s is not None else ""), flush=True)
with open(args.out, "a", encoding="utf-8") as f:
f.write(json.dumps(rec) + "\n")
print(f"[bracket] appended to {args.out}")
return 0


def judge(records: list, conc_judge: int = 1) -> dict:
"""기록 목록 → 판정 딕셔너리. 순수 함수 — 파일도 네트워크도 없다. 기록 한
줄은 한 다리(rep 목록)이고, 판정 단위는 rep 다."""
flat = []
other = 0
for r in records:
for rep in r.get("reps", []):
if r.get("conc") == conc_judge:
flat.append({"tag": r["tag"], "env": r.get("env", {}),
"tok_s": rep["tok_s"],
"step_s": rep.get("step_s")})
else:
other += 1
out: dict = {"ok": True, "conc": conc_judge, "problems": [],
"segments": [], "cands": [], "other_conc": other}
if not flat:
out["ok"] = False
out["problems"].append(f"C={conc_judge} 기록이 없다")
return out

def chan(p):
return p["step_s"] if p.get("step_s") is not None else p["tok_s"]
Comment on lines +139 to +140

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject legs that lack raw acceptance counters

When /metrics is temporarily unavailable or the exact acceptance counters are absent, step_s is None and this silently substitutes tok_s. That allows a single bracket to compare normalized step/s reps against unnormalized token/s reps, even though speculative acceptance directly changes token throughput, and the judge header still claims the channel is step/s. Such reps should invalidate the leg rather than participate in the verdict.

Useful? React with 👍 / 👎.


segments = []
for p in flat:
key = (p["tag"], json.dumps(p["env"], sort_keys=True))
if segments and segments[-1]["key"] == key:
segments[-1]["reps"].append(chan(p))
else:
segments.append({"tag": p["tag"], "key": key, "env": p["env"],
"reps": [chan(p)]})
if len(segments) < 3 or segments[0]["tag"] != "base" \
or segments[-1]["tag"] != "base":
out["ok"] = False
out["problems"].append(
"브래킷이 아니다: base→cand→base 순의 다리가 필요 (첫/마지막 base)")
out["segments"] = [{"tag": s["tag"], "n": len(s["reps"])}
for s in segments]
return out
envs = {json.dumps(s["env"], sort_keys=True) for s in segments}
if len(envs) != 1:
out["problems"].append(
"다리 사이 env 스냅샷이 다르다 — 노브가 한 개만 움직였는지 부팅 로그와 대조")

for s in segments:
s["median"] = median(s["reps"])
out["segments"].append({"tag": s["tag"], "n": len(s["reps"]),
"median": round(s["median"], 2)})

def base_pair():
return segments[0]["median"], segments[-1]["median"]

b1, b2 = base_pair()
mean_b = (b1 + b2) / 2
drift = abs(b1 - b2) / mean_b
out["base_drift"] = round(drift, 4)
cands = [s for s in segments[1:-1] if s["tag"] != "base"]
if not cands:
out["problems"].append("cand 다리가 없다 (base 만 있다)")
for c in cands:
effect = (c["median"] - mean_b) / mean_b
if abs(effect) <= drift:
verdict = ("CV 이하 — 단독 판정 불가 (rep 를 늘리거나 "
"비트동일 노브와 스택 부팅)")
elif effect > 0:
verdict = "채택 근거 (effect > base 드리프트)"
else:
verdict = "기각 근거 (역효과 > base 드리프트)"
out["cands"].append({"median": round(c["median"], 2),
"effect": round(effect, 4),
"verdict": verdict})
if any(c["verdict"].startswith("CV 이하") for c in out["cands"]) \
and min(len(s["reps"]) for s in segments) < 12:
out["problems"].append("drift 이하 효과 + rep < 12: rep 를 늘려 drift 를 더 눌러볼 것")
return out


def cmd_judge(args) -> int:
records = []
with open(args.out, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
r = json.loads(line)
if args.name in (None, r.get("name")):
records.append(r)
Comment on lines +202 to +204

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Isolate one bracket when reading appended records

If the default append-only output already contains a completed bracket with the same experiment name, this filter loads both the old and new runs. judge then uses the earliest first base and latest final base to compute drift and treats every intervening candidate as part of one bracket, producing verdicts from unrelated boots. Select a specific run/bracket identifier or restrict processing to the latest complete base→cand→base sequence.

Useful? React with 👍 / 👎.

if not records:
print(f"!! {args.out} 에 기록이 없다")
return 2
rep = judge(records, conc_judge=args.conc)
print(f"# bracket {args.name or ''} — 판정 채널 C={args.conc} "
f"(step/s = tok/s / (1 + k x raw_acc))")
for p in rep["problems"]:
print(f" !! {p}")
for s in rep["segments"]:
print(f" {s['tag']:<6} n={s['n']:<3} median {s['median']}")
Comment on lines +213 to +214
Comment on lines +213 to +214

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve medians when reporting an invalid bracket

When judge rejects an incomplete sequence such as base→cand, it returns segment entries containing only tag and n, but this unconditional lookup of s['median'] raises KeyError. Thus the CLI prints the bracket warning and then crashes instead of cleanly returning its intended invalid-bracket status; either populate medians on the early-return path or make this formatter handle summary-only segments.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judge crashes on incomplete brackets

Medium Severity

When records are not base→cand→base, judge returns segments that only have tag and n. cmd_judge then reads s['median'] and raises KeyError, so the CLI dies on the exact case it is supposed to diagnose.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e46a6eb. Configure here.

if rep.get("base_drift") is not None:
print(f" base 드리프트 {rep['base_drift']:.2%} (같은 설정 재부팅 차이 — 유의 문턱)")
for c in rep["cands"]:
print(f" cand n 대비 median {c['median']} effect {c['effect']:+.2%} → {c['verdict']}")
if rep["other_conc"]:
print(f" (참고: C≠1 기록 {rep['other_conc']}개는 판정에서 뺐다 — 원장 규칙)")
return 0 if rep["ok"] else 1


def main() -> int:
ap = argparse.ArgumentParser()
sub = ap.add_subparsers(dest="cmd", required=True)
lg = sub.add_parser("leg", help="살아있는 서버에 rep 를 돌려 기록")
lg.add_argument("--name", required=True)
lg.add_argument("--tag", required=True, choices=("base", "cand"))
lg.add_argument("--reps", type=int, default=6)
lg.add_argument("--conc", type=int, default=1)
lg.add_argument("--num-spec", type=int, default=7,
help="스페큘레이티브 토큰 수 k (step/s 정규화 계수)")
lg.add_argument("--out", default="runs/bracket.jsonl")
lg.set_defaults(fn=cmd_leg)
jd = sub.add_parser("judge", help="기록된 다리들로 판정")
jd.add_argument("--name", default=None)
jd.add_argument("--conc", type=int, default=1)
jd.add_argument("--out", default="runs/bracket.jsonl")
jd.set_defaults(fn=cmd_judge)
args = ap.parse_args()
os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True)
return args.fn(args)


if __name__ == "__main__":
sys.exit(main())
Loading