Files
moral-maps/src/tinymfv/__init__.py
T
wassnameandClaudypoo 9d3741fb45 Unify ordinal survey readout onto the guided think-then-read core
administer()/read_items() now route through _rollout_natural_or_forced (the
nominal MFV core) instead of a think=0 single forward, so an activation steer
accrues over the think trace before the prefilled answer slot is read (spec
moral_aliens_engine.md, resolved decision: ordinal needs a think budget). The
only per-instrument difference is the answer-token set + the downstream reducer.

force_only on the shared core: the ordinal "(" prefill is one common char, so
natural-emission detection would match it by chance in the think trace and read
logits mid-think; surveys always force-read the answer slot. Nominal path keeps
natural emission (force_only defaults False). max_think_tokens floor is 1.

Smoke (tiny-random): ordinal + nominal both run; force-only demo reads the
forced ( slot.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-24 15:37:47 +08:00

55 lines
2.3 KiB
Python

"""tinymfv: tiny moral-foundations vignettes eval.
Forced-choice 7-way scoring on Clifford 2015 vignettes (classic) +
paraphrase configs (scifi, ai-actor). Default condition is
`other_violate` (the canonical Clifford framing); `self_violate` is
available as an opt-in ablation. Each row internally does a fwd + rev
enum-order pass for position-bias debias (inside guided_rollout).
High-level usage:
from tinymfv import evaluate
from transformers import AutoModelForCausalLM, AutoTokenizer
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-4B")
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-4B").cuda()
rep = evaluate(model, tok, name="classic")
print(rep["table"]) # per-foundation
print(rep["top1_acc"]) # argmax accuracy vs label
print(rep["mean_js"]) # JS divergence vs label dist (in nats)
Lower-level: see `guided_rollout_forced_choice` in `tinymfv.guided`.
"""
from .data import load_vignettes, load_all_vignettes, CONFIGS, ConfigName
from .eval import evaluate, CONDITIONS
from .guided import guided_rollout_forced_choice, _DEFAULT_FORCED_FOUNDATIONS
from .instrument import Instrument, InstrItem, per_item_categorical
from .instruments import get as get_instrument, INSTRUMENTS, build_instrument
from .read import read_items, resolve_answer_ids, build_user_content
from .administer import administer
def __getattr__(name: str):
# `maps` pulls matplotlib; load it lazily so `import tinymfv` stays headless and fast for the
# numeric-only consumers (steering-lite). `tinymfv.maps.plot_*` still works -- first access
# triggers the import here.
if name == "maps":
import importlib
return importlib.import_module(".maps", __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# The front door. Other symbols above stay importable (e.g. read_items for item subsets,
# per_item_categorical, build_instrument) but are plumbing, kept out of `import *`.
__all__ = [
# entrypoints
"evaluate", "administer", "get_instrument", "read_items",
# types consumers build / subset
"Instrument", "InstrItem",
# data API
"load_vignettes", "load_all_vignettes", "CONFIGS", "ConfigName", "CONDITIONS",
# lower-level rollout + lazy plotting
"guided_rollout_forced_choice", "maps",
]