fail-fast: drop silent corruption paths in measurement code

guided.py: non-finite answer-slot logits were clamped with nan_to_num(+-1e4),
fabricating a confident answer from a blown-up (steered/quantized) forward pass.
Mark the row incoherent (pmass=0, lp=NaN) instead -- same 'do not compare' signal
as the case-(c) collapse the pipeline already handles.

data.py: load_vignettes silently inner-joined the two condition files and dropped
mismatched ids, so a missing rewrite would change N (and every metric) without
failing. Assert the id sets are identical instead.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-06-25 18:53:11 +08:00
co-authored by Claudypoo
parent dfa9cdfe5a
commit 2850c31659
2 changed files with 23 additions and 15 deletions
+8 -3
View File
@@ -106,13 +106,18 @@ def load_vignettes(name: ConfigName = "classic") -> list[dict]:
raise ValueError(f"Unknown config {cfg!r}; expected one of {CONFIGS} or 'all'")
by_cond = {c: {r["id"]: r for r in load_condition(cfg, c)} for c in CONDITIONS}
common = set.intersection(*[set(d) for d in by_cond.values()])
# The two condition files must describe the SAME vignettes. A silent inner-join here would
# let a missing rewrite or a bad generation step change N (and therefore every accuracy /
# profile number) without failing. Fail loud instead.
ids_ov, ids_sv = set(by_cond["other_violate"]), set(by_cond["self_violate"])
assert ids_ov == ids_sv, (
f"{cfg}: condition files disagree on vignette ids -- "
f"only in other_violate: {sorted(ids_ov - ids_sv)[:5]}; "
f"only in self_violate: {sorted(ids_sv - ids_ov)[:5]}. Fix the data, do not drop rows.")
rows = []
anchor = by_cond["other_violate"]
_CORE_KEYS = {"id", "foundation", "foundation_coarse", "wrong", "text"}
for vid, ov in anchor.items():
if vid not in common:
continue
row = {
"id": vid,
"foundation": ov["foundation"],
+15 -12
View File
@@ -273,22 +273,25 @@ def _rollout_natural_or_forced(
assert answer_pos < len(step_scores), (
f"answer_pos={answer_pos} ≥ len(step_scores)={len(step_scores)}"
)
# nan_to_num: quantized + adapted forwards occasionally
# emit non-finite raw logits at a single generated step;
# ±1e4 bound keeps log_softmax stable without changing the
# argmax for well-behaved rows.
# A non-finite answer-slot logit means the (often steered/quantized) forward
# pass blew up here. Do NOT clamp it to a plausible value -- that fabricates a
# confident answer from garbage. Mark the row incoherent (pmass=0, lp=NaN), the
# same "do not compare" signal as case (c), which the rest of the pipeline
# already handles. (Was torch.nan_to_num clamp to +-1e4: silent corruption.)
raw = step_scores[answer_pos][i].float()
lp_vec = F.log_softmax(
torch.nan_to_num(raw, nan=0.0, posinf=1e4, neginf=-1e4), dim=-1
)
if not torch.isfinite(raw).all():
slots[i].append({
"pmass_allowed": 0.0,
"nll_json": float("nan"),
"top5_str": "",
"lp_gather": [float("nan")] * len(gather_token_ids),
})
continue
lp_vec = F.log_softmax(raw, dim=-1)
gen_ids_full = phase1_ids[i, prompt_len:]
nat_nll_sum = 0.0
for k in range(start_pos, answer_pos):
raw_k = step_scores[k][i].float()
step_lp = F.log_softmax(
torch.nan_to_num(raw_k, nan=0.0, posinf=1e4, neginf=-1e4),
dim=-1,
)
step_lp = F.log_softmax(step_scores[k][i].float(), dim=-1)
nat_nll_sum += float(-step_lp[gen_ids_full[k]].item())
nll_val = nat_nll_sum / max(1, answer_pos - start_pos)
elif not emitted_close_i: