Skip to content

feat(tasks): multi-image trials, memory traits, fused extractors and implied reaction times - #211

Merged
KartikP merged 6 commits into
unified-model-interface-v2from
feat/trial-sequences
Nov 4, 2026
Merged

KartikP merged 6 commits into
unified-model-interface-v2from
feat/trial-sequences

Conversation

@KartikP

@KartikP KartikP commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Lets a BrainScoreModel take part in lab-style studies. Paired with KartikP/brainscore-unified#56, which adds the matching wrappers, tools, registrations and the flagship notebook. The two are pinned to each other (umi-v2.yml here, install/peer-revisions.json there).

What changes

Multi-image trials. TaskContext.present accepts named parts, so one trial can show a sequence before a single answer:

present=('image:instruction', 'image:word_1', 'image:word_2')
  • Prompts carry each part's name.
  • A multi-image trial is recorded as (presentation, part, neuroid), labelled by part.

Recording on every task path. Each case below used to raise before any compute; it now records the prompt pass:

  • during likelihood scoring (the likelihood part must run the prompt on its own first, which is now documented);
  • next to a reply with implied probabilities, which are scored first from the same prompt;
  • for composite regions;
  • in streaming sessions, where each trial's outputs are sent as it ends;
  • for extractors without record(), through forward hooks on the module passed as model=.

Memory across trials (contract change).

  • Subject.traits is empty by default. A benchmark that needs one lists it in required_traits, and pre-flight marks a subject without it N/A. This is rule 5.10.
  • Rule 9.3 reason: input matching allows partial matches, so a memory requirement written as an input would let a feedforward model run a memory task, answering each trial alone. Memory isn't an output either. A declared trait makes such a subject N/A without bending either rule, and existing subjects and benchmarks are unchanged.
  • BrainScoreModel(memory='session') declares memory:session and passes earlier trials to reply and likelihood as prompt.history. reset() and the start of a new session clear it.

Fused extractors. extractor={('vision', 'text'): fused} declares each input type and is called once for a stimulus that brings several of them. Single-type extractors still take single-type stimuli.

Implied reaction times. Choice(labels, reaction_time='evidence_margin' | 'surprisal') returns an implied RT on behavior:rt, computed from the readout's or likelihood's label probabilities and labelled with its link. They're scored by correlation, as Futrell2018 scores surprisal and Kar2019 scores solution times. Readout and likelihood parts declare behavior:rt.

Two tests that pin out_channels now include behavior:rt. Two tests that used vision as an example of a family without addresses now use text.

Verification

  • New unit tests:
    • test_session_memory.py: a 1-back task that only a model with memory solves; N/A in pre-flight; reset.
    • test_recording_everywhere.py: every path above.
    • test_fused_extractor.py.
    • test_task_responses.py: named parts, implied RTs.
    • test_visual_forms.py: recording per image.
  • Full core suite: 1189 passed. The 8 failures fail the same way on unchanged v2 in my environment: a space in the checkout path, and a database test.
  • Real weights: Qwen2.5-VL-7B on an A10G, using unified#56.
Check Result
Fused image and sentence, one pass parts image, text, final; shape (2, 3, 3584)
1-back on word images 0.44 without memory, 0.78 with memory='session'
Recording during likelihood (4, 3584), labelled as the prompt pass
Weight edit, then undo restored bit for bit
Edit to all heads of a layer, then reset() answers identical to before
Flagship study see unified#56

…h image

TaskContext.present accepts named parts ('image:word_1') so one trial
can show a sequence before a single answer. Prompts carry part names,
and a multi-image trial's recording keeps one step per image, labelled
by part.
Recording during a task now works during likelihood scoring, next to a
reply with implied probabilities (scored first from the same prompt),
for composite regions, in streaming sessions (outputs sent per trial),
and for extractors without record() through forward hooks on the model
passed as model=.
Subject gains traits (empty by default). A benchmark that needs one
lists it in required_traits, and pre-flight marks a subject without it
N/A. BrainScoreModel(memory='session') declares memory:session and
passes each earlier trial's prompt and reply to reply/likelihood as
prompt.history; reset() and the start of a new session clear it.
extractor={('vision', 'text'): fused} declares each type and is called
once for a stimulus that brings several of them; single-type
extractors still take single-type stimuli.
Choice(labels, reaction_time='evidence_margin' | 'surprisal') returns
an implied RT per stimulus on behavior:rt, from the readout's or the
likelihood's label probabilities, labelled with its link. Readout and
likelihood parts declare behavior:rt.
@KartikP
KartikP merged commit 0b0897c into unified-model-interface-v2 Nov 4, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant