Lead with the plain point, introduce + link Youden's J, spell out the macro
averaging (one-vs-rest per foundation) and point at _informedness for the
formula. Fix stale "two scalars" -> "three". Drop the "flip-informedness"
coinage and "the headline" tell. External-panel comprehension pass: ready
(4.1/5), accuracy and caveats 4-5 across panelists.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
nll_prompt was removed from per_row in f585864 but the script still read it,
crashing the smoke test. Remove the dead refs and surface the new
informedness scalar alongside top1_acc.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Chance-corrected, argmax-only companion to mean_nll: moves when the answer
flips, not when confidence shifts. 0 = base-rate guessing, so it exposes
majority-class models that top1_acc flatters. Same flip-informedness family
as steering-lite's surgical informedness, anchored on the human argmax here.
README also points at the paired training set moral_stories_foundations.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Refactor _rollout_kv_fork from 3 phases (gen → prefix-forward →
prefix+suffix-forward) to 2 phases (gen → suffix-only-forward with
cached pkv). The function name finally matches what it does again.
Phase 1: generate(..., return_dict_in_generate=True) captures past_key_values
for [left-pad, prompt, think, eos-pad]. Same as before; generation already
used cache internally.
Phase 2 (new): per slot, forward ONLY the suffix tokens (close + interrupt
+ nudge + prefill, ~10-30 tokens) with past_key_values=pkv. Logits come
out at suffix positions only; pick the last real one. The attention mask
spans cached prefix + new suffix; pad_id positions get mask=0.
Drops:
- Phase 2a entirely (the prefix re-forward that computed nll_prompt)
- nll_prompt from ForcedChoiceResult, eval.py per_row, eval output dict
- All the sp_per_row / sp_ids_per_row retokenisation gymnastics + boundary-
merge edge cases (lines 107-129 in the old code) — no more text round-trip
- ~115 lines net
Per-row prompt-NLL was a free diagnostic from the prefix forward; with the
forward gone it would cost a dedicated extra forward. pmass_format is the
stronger coherence canary anyway (per AGENTS.md "Coherence signal hierarchy"
and the bidirectional c-scan walkback in 03b_train).
Speed: marginal (saves ~2s out of ~36s per batch on 27B nf4) — the win is
simpler code, not throughput. Module docstring updated to reflect 2-phase
reality.
Smoke (downstream weight-steering-lite repo, on tiny-random) PASS.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
use_cache=False on Phase 2a/2b single forwards saved zero compute — the
flag was leftover from the multi-slot KV-fork era (commit ada854c)
that was refactored away (d34dbfa). One forward per call, no cache to
reuse. Cleaner without it; behaviour identical.
eval.py: add `think_tokens` + `emitted_close` to per_row dict (data was
already in ForcedChoiceResult, just not captured). Log distribution
after eval: median/p75/p90/p99/max + emitted_close count. Lets us see
the actual think budget used vs max_think_tokens cap, to decide if the
512 bump (from 128) is paying for itself or can revert.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ForcedChoiceResult now carries pmass_format (sum prob mass on the K
foundation answer tokens at the JSON answer slot, averaged across fwd
and rev framings). eval.py aggregates it as mean_pmass_format in both
the headline return dict and the info subdict, and propagates per-row
for sweep/audit consumers.
Direct coherence canary: drops when steering pushes the model toward
non-foundation tokens (gibberish, refusal, format collapse). Independent
of which foundation is picked — complementary to top1_acc (label-
agreement; intentional target shift) and mean_nll_prompt (teacher-forced
prompt nll; falls under steering even when generations break).
Surfacing this lets downstream callers (weight-steering-lite walkback,
report dashboards) gate on actual coherence rather than misusing top1
as a budget.
The 1-row demo block (prompt + think + nudge + prefill + scored token +
64-token free continuation) used logger.info, which meant any caller
wrapping the function in an INFO-level sink (e.g. an agent harness)
got the full trace in their stdout. Downgrade to logger.debug so it
still lands in the user's verbose log but doesn't leak to agents.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace 3 parallel scoring paths (guided_rollout / _batch / _multibool)
with a single internal `_rollout_kv_fork` core: phase-1 batched think,
one cached prefix forward, N forked suffix forwards (one per scoring
slot). Binary case is just N_slots=1.
Drops from GuidedResult (no callers): answer_text, raw_full_text,
rep_ratio_think, prompt_nll. eval.py updated accordingly. _ngram_rep_ratio
and _scoring_text helpers removed -- their logic folded into the core.
Verbose=True now logs the full conversation (prefix + suffix the model
sees) plus a 64-token free-form generate continuation, so format issues
are obvious from one slot's log.
File shrinks from ~600 to ~340 lines. smoke_batch_parity passes (bf16
max Δp_true=0.098 within 0.20 tol; pre-existing batched-greedy drift).
Replace mid-turn splice (`I should answer now.</think>{prefill}`) with a
clean turn close + user nudge + fresh assistant prefill. Mirrors what a
chat UI emits when a human interrupts a partial assistant turn, which is
on-policy in chat-tuned training data. Empirically: pmass_format ~0.987
on smoke set vs the OOD splice path.
Close marker is probed from the tokenizer's chat template (sentinel
diff), so it works on Qwen/ChatML, Llama3 (`<|eot_id|>`), etc -- no
hardcoded `<|im_end|>`.
Drops:
- emitted_prefill field (no callers)
- try/except TypeError around apply_chat_template (defensive)
- enable_thinking=False kwarg (some templates reject it; complete
assistant messages auto-strip the think block anyway)
- `\nI should answer now.` fallback in multibool
Adds:
- verbose=True flag on guided_rollout to log scoring_text for debugging
- _assistant_close(tok) sentinel-probe helper
Note: prompt_nll magnitudes shift since scoring_text now includes the
user-nudge tokens. Not comparable to pre-refactor saved results.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pmass=1.000, 0/132 low-pmass, inter-foundation |r|=0.154 (down from 0.51).
4B fully resolves the foundation-conflation problem seen at 0.6B.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- suf_ids_for() now uses interrupt-msg format: close assistant turn, inject
per-foundation user question + {"Answer": prefill -- fixes authority pmass
(0.344->0.898) by avoiding JSON string-priming from key names
- Add _FOUNDATION_DESCS and _DEFAULT_MULTIBOOL_HINT rubric for discrimination
- Low-pmass diagnostic: first occurrence now runs .generate(max_new_tokens=32)
to show what model actually produces; subsequent cases log top-5 tokens
- suf_ids_per stored so diagnostic generate can reconstruct full input
- Journal: add inter-foundation correlation results (mean |r|=0.51); note
Spearman vs human raters is not a valid metric (exclusive vs independent labels)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sanity check + baseline before wiring multibool into the steering sweep.
Computes per-foundation logratios on all 132 classic vignettes, dumps to
data/results/multibool_baseline.jsonl, prints per-foundation lr summary
and Spearman corr against human-rater %s.
SHOULD: mean pmass > 0.9; Spearman ρ > 0.3 on ≥4/6 foundations.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The chained-fill design (one suffix with all foundations, two passes for true/false)
hit a non-recoverable conv-state issue on hybrid linear-attention layers (Qwen3.5):
splitting prefix and suffix forwards via past_key_values silently produced
wrong logits (pmass dropping to 0.04, top token leaking to ' "' = 0.72).
Switched to 12 independent single-slot completions per prompt:
for (frame, foundation) in {is_violation, is_ok} × foundations:
cache scoring_prefix once, fork suffix `\n{"<frame>": {"<f>":`,
read logits at the last token (predicting `true|false`).
final[f] = 0.5 * (lr_violation[f] - lr_ok[f])
Framing flip cancels per-key prior bias the same way true/false fill did,
without the chained-slot causality that interacts badly with split forwards.
Added _assert_full_attention(): checks model.config.layer_types and fails
loudly on hybrid models. Verified parity vs flat forward on Qwen3-0.6B
(Δ ≤ 0.13 nats; signal of interest is ≫1 nat) and assert fires on Qwen3.5-0.8B.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-token NLL over the scoring text is free since we already compute
full-sequence logits in guided_rollout / guided_rollout_batch (just
gather instead of slicing [:, -1]). Higher NLL = model less coherent
on this prompt under whatever steering is attached.
eval.py logs pmass/ppl/nll aggregate at end of guided eval so the
next run shows degradation at a glance instead of buried tqdm noise.
analyse() now exposes raw_nll and info.prompt_nll_mean.
`boolean` is not valid JSON; use `{"type": "boolean"}` so models
produce true/false rather than the integer shorthand 1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`boolean` is not valid JSON; use `{"type": "boolean"}` so models
produce true/false rather than the integer shorthand 1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Updated scripts/07a_merge_labels.py to only merge calibrated_* and llm_dominant
- Updated README.md and scripts/05_upload_hf.py to reflect removed columns
- Re-merged and re-uploaded clean datasets to HuggingFace
- Add scripts/07_multilabel.py: LLM judge rates all 7 foundations per vignette
using violation (forward) and acceptability (reverse) frames
- Foundation definitions drawn from Clifford et al. (2015) survey rubric
- Z-score each frame per foundation before averaging to cancel range bias
- Calibrate LLM Likert → human % via per-foundation OLS (classic set only)
- Add scripts/07a_merge_labels.py: merges llm_* and calibrated_* into vignette files
- Update README and HF dataset card with methodology and calibration quality table
- Classic set: 80.3% dominant-foundation accuracy, Pearson r 0.69-0.89 per foundation