Download docs/new_baselines_implementation_spec.md from Anonymous1611/Lattice_Spike: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/Anonymous1611/Lattice_Spike/resolve/main/docs/new_baselines_implementation_spec.md
- Command line
-
hf download hf://Anonymous1611/Lattice_Spike/docs/new_baselines_implementation_spec.md
-
curl -L -o new_baselines_implementation_spec.md https://huggingface.co/Anonymous1611/Lattice_Spike/resolve/main/docs/new_baselines_implementation_spec.md
Implementation spec: SAPLMA, HaMI, LLM-Check, ICR Probe
All four are implemented. Every architecture detail
below was checked against the source paper or the authors' released code, not
recalled. Fidelity argumentation lives in docs/baseline_fidelity_audit.md
(Β§4 SAPLMA, Β§5 HaMI); this file is the build record and the remaining work.
Decisions already made (do not relitigate)
- Pipeline: all three go into the baseline runner
(
scripts/train_faithful_baselines_dgx.py) as new--detectorvalues, under the same suite/protocol contract as EPR/Lookback/MVA. NOT scored ad-hoc from the LatticeSpike signal cache. Protocol consistency is what makes the fidelity argument defensible, and the same hidden-state extraction pass also unblocks WEPR's missing causal number. - HaMI keeps sequence-level supervision. Learning token scores from bag-level labels IS its contribution. This belongs in the "preserved" column of the adaptation table, not "adapted".
- What we equalise is the evaluation β same token set, same causal horizon, same metrics β never the methods themselves.
- The default detector set is frozen.
STRICT_DETECTORSstill resolves to EPR/WEPR/Lookback/MVA-CRF, so a stored command that reproduced a published run keeps reproducing it. Both new methods are opt-in via--detector.
1. SAPLMA β implemented
baselines/saplma/, detector name saplma.
Azaria & Mitchell, "The Internal State of an LLM Knows When It's Lying", EMNLP Findings 2023, aclanthology.org/2023.findings-emnlp.68, arXiv:2304.13734.
Independently validated: PARALLAX (2026) found SAPLMA one of only two reliable internal-state probes out of 22 evaluated (0.911 AUROC on HaluBench), with MIND, HaloScope, ACT, SEPs and HalluShift all near-chance (0.49-0.62).
Verified from paper section 4, quoted: "three hidden layers with decreasing numbers of hidden units (256, 128, 64), all utilizing ReLU activations. The final layer is a sigmoid output. We use the Adam optimizer. We do not fine-tune any of these hyper-parameters for this task. The classifier is trained for 5 epochs."
Correction to an earlier draft of this file: SAPLMA does not average activations across tokens. It reads the activations at the last token of the statement β one 4096-vector per statement. The earlier note here said "averages across all tokens" and was wrong.
Layer: the paper builds five probes (last, 28th, 24th, 20th, middle of 32) and finds 20/32 best for OPT-6.7b, the middle layer best for LLaMA2-7b. We probe the middle layer of a 16-layer model, matching the LLaMA finding.
Unstated in the paper: learning rate and batch size. The reference is Keras, so its defaults (1e-3, 32) are used β the only reading consistent with the paper's claim that nothing was tuned.
Supervision: --saplma-supervision response (default, faithful) fits one
row per answer β last-token activations against the response label β and
localises per token at scoring time, exactly as EPR is handled here.
--saplma-supervision token fits every labelled token and reports under
saplma_token_supervised, so the more generous configuration is on the table
rather than assumed away.
NOTE: baselines/hidden_state_probe/ is NOT SAPLMA β it is logistic
regression on the last layer with class_weight="balanced", correctly classed a
control in fidelity_matrix.md. Do not relabel it.
2. HaMI β implemented
baselines/hami/, detector name hami.
"Robust Hallucination Detection in LLMs via Adaptive Token Selection", NeurIPS 2025, arXiv:2504.07863, code github.com/mala-lab/HaMI.
Architecture (hami.py):
Linear(n_features, 256) -> BatchNorm1d -> ReLU -> Dropout(0.3) -> Linear(256, 1) -> Sigmoid
Loss (hami.py:MIL_loss), transcribed exactly:
k = int(length * k_ratio) + 1 # k_ratio = 0.1
loss += (1. - s_topk_mean**2 + nor_max[i]**2) # index-matched bag pairs
smooth += torch.sum((s[:-1] - s[1:])**2) * 0.0008 # bags longer than 8 only
loss = loss / len(n_len_list) + smooth / len(n_len_list)
The abnormal term is index-matched against the normal term, which is why the
reference draws equal-sized batches from two loaders. Note 1. - m**2 + n**2,
not (1 - m)**2.
Training (train.py, main.py): Adam lr 1e-3, weight decay 5e-4;
ReduceLROnPlateau(mode='min', factor=0.1, patience=20, min_lr=1e-6) stepped on
the training loss; batch 128 bags per polarity with drop_last=True; 100
optimizer steps, not 100 passes; validation after every step, keeping the best
model; validation metric is bag-level AUROC where the bag score is the mean of
its top-k token scores (validate.py).
Layer selection: main.py loops over every layer, trains a separate model
for each, and records validation results per layer β the layer is a
validation-selected hyperparameter. We run one layer per invocation
(--hidden-layer), so a layer sweep is a sweep of runs.
Two things the reference does that we cannot:
- Its default
--mode sescales hidden states by a probability from clustering several sampled responses per question. Our dataset has one response per prompt, so we use the reference'sori(unweighted) mode. HaMI is therefore evaluated below its published capability, and that must be stated. - Bag labels come from answer correctness (
acc), with ambiguousacc == 2.0bags dropped underis_refined. Ours: abnormal if the response contains any hallucinated token, normal if none, uncertain-only responses dropped.
Step budget caution. 100 steps is tied to their dataset size. On our larger
training split it sees a fraction of the data. --hami-steps exists for this;
any run that raises it must say so in the results, and the reference's own
best-on-validation rule is what makes a longer budget defensible.
3. LLM-Check β implemented
baselines/llm_check/, detector names token_logprob,
llm_check_logit_entropy, llm_check_window_entropy, llm_check_hidden,
llm_check_attention.
Sriramanan et al., NeurIPS 2024, code
GaurangSriramanan/LLM_Check_Hallucination_Detection.
Five training-free scores from one forward pass, all verified against
common_utils.py:
perplexity: log_softmax(logits)[t-1, tok[t]] # mean then exp
logit_entropy: softmax(topk(logits, 50)); (-p*log p).mean() # own row
window_entropy: softmax(logits); (-p*log p).mean() # own row, w=1
hidden: log(svdvals(Z.T @ J @ Z + 1e-3*I)).mean() # J centres features
attention: sum_h log(diag(A_h)) # over the answer block
Note .mean() not .sum() in both entropies, and note that the reference reads
the realized-token log-probability at row t-1 but both entropies at row t.
Extraction stores rows prompt_len-1 .. prompt_len+T-1 so index t is the
predicting row and index t+1 is the own row; both alignments are then served
from one array.
Three things that are easy to get wrong:
- Orientation is fitted, not fixed. The authors' notebook negates
perplexityand the attention score, negates neither entropy, and ships no orientation for the hidden score. An earlier draft here hard-coded the sign from the paper's prose and put both eigen scores at ~0.20 AUROC; the fitted bit puts them at ~0.80. One bit per score, fitted on train. centered_svd_valcentres over the FEATURE axis. The reference passes(dim, n_tokens)and builds adim x dimcentering matrix, soSigmacomes outn_tokens x n_tokens. Easy to transpose by mistake.- The attention diagonal changes meaning with the row. Own row = the token's self-attention (post-emission). Predicting row = attention to the most recent prefix token (causal). Not the same quantity; say so.
Hidden score is pooled per token over the expanding causal prefix
(--llm-check-hidden-window 0, the default), which is the direct causal
restriction of the reference's whole-response score and adds no hyperparameter.
token_logprob is LLM-Check's per-token perplexity term. It gets its own row
because it is the floor every method must clear, but it is computed once, not
double-counted under two names.
4. ICR Probe β implemented
baselines/icr_probe/, detector name icr_probe.
Zhang et al., ACL 2025 Main, code
XavierZhang2002/ICR_Probe
(src/icr_score.py, src/icr_probe.py, src/utils.py, src/config.py).
An earlier draft of this file said there was no code release. There is.
ICR Score, per layer a and scored row r (compute_icr):
dx = H[a + 1][r] - H[a][r] # consecutive layers
targets = H[a][topk_idx] # the LOWER layer, easy to get wrong
w = (dx * targets).sum(1) / (targets.norm(1) + 1e-8)
ICR = js_divergence(w, attn_row[topk_idx]) # standardise -> softmax -> JSD
Five details that a paper-only implementation gets wrong:
- Attention is the softmaxed probability, not the pre-softmax score.
Equation 3 defines
(Q.K)/sqrt(d); the code uses HFoutput_attentions. top_psilently overridestop_k.top_k = top_k if top_p is None else int(top_p * N), and the README passes both, sotop_k=20is dead code and k is a fraction of the sequence.- Projection targets come from the lower layer,
origin_hidden_states[a], not the layer whose update is being measured. - The block mask drops response-to-prompt attention entirely, keeping only prompt-to-prompt and response-to-response.
- The induction-head fallback fires on a count test, not a threshold: if
fewer than
n_heads // 8heads pass, the verdict is discarded and the topn_heads // 8by skewness are taken instead.
Probe (src/utils.py, src/config.py): L -> 128 -> 64 -> 32 -> 1, each
hidden layer BatchNorm1d + LeakyReLU(0.01) + Dropout(0.3), sigmoid; Kaiming
uniform a=0.01; BCE, Adam(1e-3, wd 1e-5), ReduceLROnPlateau(min, 0.5, 5), 100
epochs, batch 16, best validation loss.
The featurisation is ours, unavoidably. _load_data is referenced but never
defined in the release, and the README says the training code must be adapted.
Default --icr-supervision response mean-pools ICR over the answer's tokens and
fits the response label; scoring feeds each token's own layer-vector to the
fitted probe. --icr-supervision token is the token-supervised variant.
Cost β the planning error worth remembering. ICR needs consecutive-layer
hidden states and per-layer attention, which looked like it demanded a
12-24 GB multi-layer cache. It does not: the score reduces to one scalar per
(token, layer) at extraction time, so the cached feature is (T, L) β about
15 MB for the whole dataset. Always check what the feature reduces to before
budgeting for what the computation reads.
The AWS L4 signal cache cannot serve ICR, for three independent reasons:
it samples layers 4/8/12/16 so no consecutive difference exists; it stores no
attention, only the collapsed attn_ent scalar; and it covers answer prediction
rows only, while the projection needs all N tokens. Checked before spending
instance time.
What was wired into the runner
DETECTOR_SAPLMA/DETECTOR_HAMI, plusHIDDEN_STATE_DETECTORSso all three hidden-state methods share one extraction path and one feature array.--hidden-alignment {generated_token_position, generation_prediction_position}. The causal suite sets the predicting row automatically; native suites keep the token's own row. Both new detectors are inCAUSAL_CAPABLE_DETECTORS.hidden_sequence_training_example()carries the whole answer's hidden states plus both label granularities, so each detector picks the one its paper uses. U rows stay in the matrix and are excluded by the detector, because both methods index into the sequence (last token, adjacent-token smoothing) and deleting rows would silently change what "last" and "adjacent" mean.--saplma-supervision/-epochs/-lr/-batch-size,--hami-steps/-batch-size/-k-ratio/-lr/-weight-decay.- Summary JSON gains a
hidden_state_detectorsblock recording layer, alignment, and each method's fitted-run facts (bag counts, selected step, validation AUROC, training positive rate).
Cross-cutting cautions from this codebase
- Never add a key to a cache spec unconditionally. The fingerprint is a hash
of the whole spec, so a new key invalidates every cache built before it
existed. This broke the 5.1 GB LatticeSpike base cache in r6 and cost a night.
hidden_alignmentis therefore added tofeature_cache_speconly when non-default, and there is a test asserting its absence from legacy specs. - Never bump
EXTRACTION_CODE_VERSIONunless extraction genuinely changes for existing configurations; it invalidates every cache on every box. - Note that
selected_featuresis already part of the fingerprint. Running--detector saplma hamiwill not reuse a cache built for the four-detector set. Extract the two hidden-state methods in their own pass β they need no attention output, so it is the cheap pass, not the expensive one. - Probe before long runs. A 5-record extraction probe caught two shape bugs in r6 that would each have surfaced hours in.
- Verify the training path, not just extraction. r6's failures were both in
training/cache-validation, which the extraction probe did not cover.
tests/test_train_faithful_baselines_dgx.pynow fits and scores both new detectors end to end on synthetic rows for exactly this reason. - Compare single-seed arms against the same seed's control. Per-seed validation AP for the current best LatticeSpike config: seed 42 = 0.2785, seed 43 = 0.2632, seed 44 = 0.2633; 3-seed mean 0.2683 +/- 0.0088. Comparing a seed-42 arm to the mean inflates it by ~0.010 and turned three nulls into apparent passes in r5.
Where things stand
- Current best documented in
ablations/BEST_MODEL.md(TEST AP 0.2741 +/- 0.0238, AUROC 0.9283 +/- 0.0040, 3 seeds). - Baseline audit in
docs/baseline_fidelity_audit.md; SAPLMA is Β§4, HaMI Β§5. - Adaptation table rows for both are in
scripts/gen_results_tables.py(tab:adaptation). The fair results table has no rows for them yet β add rows only once runs exist. - Rounds archived in
ablations/r0,r1,r2,r3_5,r4,r5_stage1,multilayer, each withANALYSIS.md. - r6b results are still unfetched and unarchived (4 arms: lookback s42/s43,
head128 s43/s44) in
outputs/sweep_r6on the 5090. - WEPR still has no causal-matched number; the same hidden-state extraction pass should fix that.
- Planned external evaluation: RAGTruth and Mu-SHROOM (team already working on
both). FAVA parked but
hallucination_labelling/span_align.pyalready supports it.