Abstain / IDK heads
Decode-layer SELECT-before-SAMPLE: if
p(abstain|h) ≥ τ, emit a configured IDK string and
halt instead of inventing. Companion
./spark-abstain. Full design:
repo docs/ABSTAIN_HEADS.md.
Not a Bifrost plugin. Not production LoRA.
Live model = explicit HF path or org/name only —
never auto / code / fast.
Flagship program:
examples/no_invent.spark —
SoT / expect first for inventable facts,
then gated ask (architecture prevents inventable open-decode;
not a claim that all hallucination is gone).
./spark --dry-run examples/no_invent.spark
Architecture
- Internal head — linear/MLP probe on frozen backbone last-token hidden.
- External head — same math as a sidecar on exported hiddens.
- Cloud — outer verify-or-refuse in the orchestrator
(
expect/ HTTP / SoT), not an attached head.
hidden h_t → head → p_abstain
│
SELECT: if p ≥ τ → IDK + HALT
else → SAMPLE
Dry-run vs live
| Mode | Behavior |
|---|---|
--dry-run / --dry |
Fixture JSON only — no real .pt, no HF |
SPARK_ABSTAIN_STUB=1 |
Dry inventable heuristics — not real p(abstain|h) |
--live export|train|attach|ask |
Real CPU weights / SELECT; needs hidden source for ask |
Commands
# Curated seed + synthetic wide dim (CI-shaped, not a real LM)
./spark-abstain --live validate-corpus \
--dataset examples/fixtures/abstain/corpus_seed.jsonl
./spark-abstain --live export \
--dataset examples/fixtures/abstain/corpus_seed.jsonl \
--source synthetic --hidden-dim 768 \
--out out/heads/synth768.jsonl
./spark-abstain --live train \
--dataset out/heads/synth768.jsonl \
--out out/heads/abstain768.pt --hidden-dim 768
# Real HF export (optional transformers extra; local weights)
SPARK_ABSTAIN_HF=1 SPARK_ABSTAIN_HF_LOCAL_ONLY=1 \
./spark-abstain --live export \
--dataset examples/fixtures/abstain/corpus_seed.jsonl \
--model /path/to/local-hf-model --source hf \
--out out/heads/from-hf.jsonl
./spark-abstain --live attach \
--model /path/to/hf-model \
--weights out/heads/abstain.pt \
--out /path/to/hf-model/spark_abstain_manifest.json
SPARK_ABSTAIN_HF=1 ./spark-abstain --live ask \
--prompt "Who is the mayor of Springfield?" \
--model /path/to/local-hf-model \
--weights out/heads/abstain.pt
make test-abstain
Labeled corpus
Seed file
examples/fixtures/abstain/corpus_seed.jsonl:
label 0 = answer,
1 = abstain, plus human
reason tags. Tiny curated seed —
not a production accuracy corpus. Grow it by appending
JSONL rows (no PII); see
examples/fixtures/abstain/README.md.
vLLM /spark_hidden contract
Stock OpenAI-compat servers do not export last-layer states. Spark expects an optional sidecar:
POST {SPARK_ABSTAIN_VLLM_URL}/spark_hidden
{"prompt":"…", "max_length": 512, "layer": -1}
→ {"object":"spark.hidden", "hidden":[…], "dim": N,
"model":"…", "source":"hf_prefill"}
HF sidecar (real last-token export):
pip install -e 'python/[sidecar]'
then
tools/spark-abstain/spark_hidden_sidecar.py --model /path/to/hf-model.
Env: SPARK_ABSTAIN_VLLM_URL,
optional SPARK_ABSTAIN_VLLM_TOKEN /
SPARK_ABSTAIN_VLLM_TIMEOUT.
Laptop stub (toy vectors — CI only):
tools/spark-abstain/spark_hidden_stub.py.
Shared contract:
python/sparklang/abstain/spark_hidden.py.
Honest gaps
- CI never loads a 27B — toy / synthetic / bag-hash fixtures only.
- Synthetic 768 proves dim-match, not production accuracy.
- Retrain on real backbone hiddens before claiming gate quality.
- Stock vLLM still needs the HF sidecar (or a future plugin)
for
/spark_hidden. - Labeled corpus is a tiny curated seed — grow it; never invent a fake large production dataset.
- llama.cpp / GGUF hidden hooks — out of scope.
Related: AI models · Language · Model training.