batch the eval: guided_rollout_batch + 4× speedup

Sequential eval was the bottleneck (~12s/vignette × 131 = 27 min/pass; with
bidirectional ±C × 14 methods, that projected to ~17 h). Three model calls
per row (phase1 generate, scoring forward, cosmetic continuation) became one
phase1 + one scoring per *batch*; continuation generate dropped (callers
only use p_true + pmass_format).

Parity smoke (scripts/smoke_batch_parity.py): float32 is bit-exact (max
Δp_true=0.0000 over 16 prompts, 4.3× speedup at limit=4). bf16 drifts on
individual rows — greedy argmax flips at near-tie tokens then phase1
diverges — but pmass agrees within 0.03 (scoring forward correct) and
aggregates over 131 vignettes will average out the per-row noise.

Other changes shipped in this commit:
- core.py: analyse() now returns raw_pmass dict alongside raw p_true (callers
  needed per-(vid,cond,frame) pmass for diagnostic warnings).
- guided.py guided_rollout: warn + log top-5 when pmass<0.9 (catches OOD
  steering / format-broken vignettes without a separate audit pass).
- eval.py: pre-tokenize a sample to log expected prompt+cache budget so OOM
  is predictable from the SHOULD line; group items by frame so each batch
  shares schema_hint + prefill.
This commit is contained in:
wassname
2026-05-03 06:42:17 +08:00
parent 7867a677d0
commit e996d57051
4 changed files with 308 additions and 22 deletions
+48 -21
View File
@@ -9,7 +9,7 @@ from tqdm.auto import tqdm
from .core import format_prompts, next_token_logits, score_prompts, analyse, CONDITIONS, FRAMES
from .data import load_vignettes
from .guided import guided_rollout, choice_token_ids_tf
from .guided import guided_rollout_batch, choice_token_ids_tf
def evaluate(
@@ -39,36 +39,63 @@ def evaluate(
t0 = time.time()
if max_think_tokens > 0:
logger.info(f"Using guided_rollout with {max_think_tokens} max_think_tokens (sequential)")
p_true_list = []
meta = []
bool_mass_list = []
logger.info(f"Using guided_rollout_batch with {max_think_tokens} max_think_tokens, batch_size={batch_size}")
choice_ids = choice_token_ids_tf(tokenizer)
for r in tqdm(vignettes, desc="Evaluating"):
# Build all (vid, cond, frame) items, grouped by frame so each batch
# shares schema_hint + prefill (collapses the per-row branching).
items_per_frame: dict[str, list[tuple]] = {f: [] for f in FRAMES}
for r in vignettes:
for cond in CONDITIONS:
for frame, fr in FRAMES.items():
user_prompt = f"{r[cond]}"
schema_hint = fr["q"]
prefill = fr["prefill"]
res = guided_rollout(
for frame in FRAMES:
items_per_frame[frame].append(
(r["id"], r["foundation_coarse"], cond, frame, r.get("wrong"), r[cond])
)
# Pretokenize a sample to log expected prompt length / cache budget.
sample_user = items_per_frame[next(iter(FRAMES))][0][5]
sample_q = FRAMES[next(iter(FRAMES))]["q"]
sample_full = f"{sample_user}\n\n{sample_q}"
sample_msgs = [{"role": "user", "content": sample_full}]
try:
sample_p = tokenizer.apply_chat_template(sample_msgs, tokenize=False, add_generation_prompt=True)
except TypeError:
sample_p = tokenizer.apply_chat_template(sample_msgs, tokenize=False)
sample_p = sample_p + "<think>\n"
sample_len = len(tokenizer(sample_p).input_ids)
logger.info(
f"SHOULD: prompt_len≈{sample_len} tok; max cache ≈ {sample_len + max_think_tokens} per row × "
f"batch_size={batch_size}. If OOM, lower batch_size."
)
p_true_list, meta, bool_mass_list = [], [], []
total = sum(len(v) for v in items_per_frame.values())
with tqdm(total=total, desc="Evaluating") as pbar:
for frame, items in items_per_frame.items():
fr = FRAMES[frame]
schema_hint = fr["q"]
prefill = fr["prefill"]
for i in range(0, len(items), batch_size):
chunk = items[i:i + batch_size]
user_prompts = [it[5] for it in chunk]
results = guided_rollout_batch(
model, tokenizer,
user_prompt=user_prompt,
user_prompts=user_prompts,
choice_token_ids=choice_ids,
max_think_tokens=max_think_tokens,
schema_hint=schema_hint,
prefill=prefill,
verbose=False
)
p_true_list.append(res.p_true)
meta.append((r["id"], r["foundation_coarse"], cond, frame, r.get("wrong")))
bool_mass_list.append(res.pmass_format)
for it, res in zip(chunk, results):
vid, found, cond, fr_name, wrong, _ = it
p_true_list.append(res.p_true)
meta.append((vid, found, cond, fr_name, wrong))
bool_mass_list.append(res.pmass_format)
pbar.update(len(chunk))
elapsed = time.time() - t0
logger.info(f"guided eval: {elapsed:.1f}s ({len(p_true_list)/elapsed:.1f} prompts/s)")
report = analyse(p_true_list, meta, bool_mass=bool_mass_list)
else: