Skip to content

feat(tasks): describe the answer's form with TaskContext.response - #210

Merged
KartikP merged 2 commits into
feat/single-pass-recordingfrom
feat/task-responses
Nov 4, 2026
Merged

KartikP merged 2 commits into
feat/single-pass-recordingfrom
feat/task-responses

Conversation

@KartikP

@KartikP KartikP commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Stacked on #209: merge that first, then retarget this PR to unified-model-interface-v2.

TaskContext.task_type mixed what a subject is asked to do with the form its answer takes. Ratings, reported confidence and written answers had no place in it. This adds TaskContext.response, built from the types in the new brainscore_core.responses:

Response Answer Channels
task_type='passive' nothing; plain viewing (neural only)
Choice(labels) one label behavior:choice
Choice(labels, probabilities=True) also the implied probability of each label + behavior:probabilities
Choice(labels, confidence=Scale(1, 5, 1)) also a reported confidence, in the same reply + behavior:confidence
Scale(low, high, step) a rating behavior:rating
Text() a written answer, kept as written behavior:text

Reported or implied. A reported answer is the model's reply, parsed. An implied measure is read from the model's internals: a readout, or a language model's likelihoods, the same kind of link Futrell2018 uses when it reads reading times from surprisal. Every result says which in attrs['measure'].

Prompt wording and parsing belong to the benchmark (rule 11.3). Each response builds its own directive and parses replies, and a benchmark can subclass it to change either. An unparseable reply is recorded as invalid, with answer_valid, raw_answer and parse_error (rule 6.2).

New BrainScoreModel parts:

  • reply(stimulus_row, prompt) -> str: the raw reply.
  • likelihood(stimulus_row, prompt, candidates) -> {candidate: log p}: scores each candidate reply without generating.

RatingRegressor, a ridge readout, gives ratings for feature models.

Paths: prefer_path accepts 'generation', 'likelihood', 'readout' or 'auto', tried in that order. One benchmark can still take both kinds of model for choices, ratings and implied probabilities. Reported confidence and written answers need a model that replies.

Compatibility. behavior is now an addressable catalog family. Each part declares which behavior:<kind> channels it can give:

  • readout: choice, probabilities, rating;
  • answer: choice;
  • reply: choice, rating, confidence, text;
  • likelihood: choice, probabilities.

So, for example, a written-answer benchmark is N/A for a readout-only model before any compute (rule 5.3).

Existing calls are unchanged. task_type='label' / 'classification' / 'probabilities' keep their path on plain behavior, so existing scores don't move. Two tests that pin out_channels now also list the addressed channels that readout models truthfully declare.

Rule 9.3 reason. TaskContext is a session setting in the contract. Adding a field alone couldn't separate the task from the form of the answer without also making task_type optional. Embodied tasks stay on their own path.

Recording during these tasks uses #209's single presentation:

  • the prompt pass of a reply;
  • the readout's own extraction.

Recording during likelihood scoring, or together with probabilities alongside a generated answer, needs extra forward passes. Both raise a clear error before any compute.

Verification

  • Unit tests: tests/test_task_responses.py (10 tests) covers:
    • parsing and invalid replies;
    • reported confidence;
    • ratings from a reply and from the ridge readout;
    • text;
    • likelihood probabilities;
    • readout probabilities;
    • N/A for reported answers from readout-only models;
    • plain behavior requests;
    • TaskContext validation.
  • Full core suite: 1164 passed. The 8 failures are the same environment failures as on the base branch.
  • Real weights: Qwen3.6-27B on 4×A10G, through unified's chat_parts (feat(experiments): record every kind of answer; chat reply and likelihood parts KartikP/brainscore-unified#55):
    • likelihood choice: 8/8 correct, probability 0.97 to 0.99 on the right colour;
    • reported confidence: valid labels, confidence 5 throughout; IT recorded during the answer, labelled as the prompt pass;
    • rating ("How round?"): circles 7, squares 1;
    • text: "The image displays a solid red circle centered on a white background."

TaskContext gains response: Choice (optionally with implied
probabilities or reported confidence), Scale for ratings, and Text for
written answers. Each response phrases its directive and parses replies;
answers arrive on addressed behavior:<kind> channels that each part
declares, so compatibility is decided before compute. BrainScoreModel
gains reply and likelihood parts, and a ridge readout gives ratings for
feature models. task_type values keep their meaning on plain behavior.
@KartikP
KartikP merged commit 2631423 into feat/single-pass-recording Nov 4, 2026
2 of 3 checks passed
@KartikP

KartikP commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Folded into #209, which now carries these commits.

KartikP added a commit that referenced this pull request Nov 4, 2026
…ive input forms (#209)

The experiment API for UMI v2 in core: a study reads like a lab protocol
(describe the task, bring in a subject, attach recorders, run once), and
every subject sees each stimulus in its own native form. This folds in
what was #210. Paired with KartikP/brainscore-unified#54; the two are
pinned to each other (`umi-v2.yml` here, `install/peer-revisions.json`
there).

## What changes
**1. Behavior and activity from one presentation.** A `BrainScoreModel`
session may ask for `behavior` and `neural:<region>` together. The
regions are recorded during the same forward pass that produced the
answer, and each stimulus is shown once.
- **Rule 4.5:** the session's `task_context` decides what the subject
does; the requested outputs decide what is measured.
  - `task_type='passive'` means plain viewing.
- A session that asks only for `neural:*` under a real task records
during that task.
- A task configured by an earlier session no longer captures a later
neural-only session. That used to return the behavioral assembly.
- **`TaskContext.readout_fit`:** `'intact'` fits a readout with every
active change undone, then tests the changed network with it. The
default `'current'` is unchanged.
- **Labelling:** recordings made during a task carry
`attrs['recording']`. Plain neural sessions are unchanged.

**2. The form of the answer: `TaskContext.response`**
(`brainscore_core.responses`):

| Response | Channels |
| --- | --- |
| `Choice(labels)` | `behavior:choice` |
| `Choice(..., probabilities=True)` | `+ behavior:probabilities`
(implied) |
| `Choice(..., confidence=Scale(...))` | `+ behavior:confidence`
(reported) |
| `Scale(low, high, step)` | `behavior:rating` |
| `Text()` | `behavior:text` |

- **Reported or implied:** a reported answer is a parsed reply; an
implied one is read from a readout or from likelihoods. Each result says
which in `attrs['measure']`.
- **Benchmarks own wording and parsing:** each response builds its own
directive and parses replies. An unparseable reply is recorded as
invalid.
- **New parts:** `reply(prompt)` and `likelihood(prompt, candidates)`.
`RatingRegressor` gives ratings for readout models.
- **Compatibility from declarations:** each part declares which
`behavior:<kind>` channels it can give, so mismatches are N/A before any
compute.
- **Existing `task_type` values** keep their path on plain `behavior`.

**3. Native input forms and ordered trials.**
- **Rule 5.9:** `vision` is addressable as `vision:still` (declared by a
`vision`-keyed extractor) and `vision:clip` (declared by a `video`-keyed
one). A subject with both takes each stimulus in its own form, so a clip
goes to the clip extractor.
- **Order:** a trial is shown as a `Prompt` in this order: the
instruction, the stimulus parts in `TaskContext.present` order, then the
response's directive. A stimulus with several parts and no declared
order fails before any compute.
- **Clips keep their time steps** when recorded during a task.

**Rule 9.3 reason:** `TaskContext` is a session setting. Separating the
task from the form of its answer needed `task_type` to become optional,
which adding a field alone couldn't do. Every existing call keeps its
meaning.

Out of scope, each raising a clear error before any compute:
- recording during likelihood scoring;
- recording with probabilities alongside a generated answer;
- composite regions, streaming and more than one time bin during a task.

## Verification
- **Unit tests:**
- `test_joint_behavior_neural.py` (10 tests): one presentation,
prompt-dependent activity, `readout_fit`, neural-only sessions under a
task, plain sessions unchanged.
- `test_task_responses.py` (12 tests): parsing, confidence, ratings from
a reply and a readout, text, likelihood and readout probabilities, N/A
cases, trial order.
- `test_visual_forms.py` (6 tests): form declarations, dispatch by form,
clip time steps during a task.
- **Full core suite:** 1172 passed. The 8 failures fail the same way on
unchanged v2 in my environment: plugin and conda tests break on a space
in the checkout path, and one test needs a database.
- **Real weights:** Qwen3.6-27B on 4×A10G.
- **One presentation:** 8 images × 2 conditions made 16 `generate`
calls.
- **Recording during the answer:** IT matched passive viewing to within
bf16 rounding.
- **Likelihood choice:** 8/8 correct, with probability 0.97 to 0.99 on
the right colour.
  - **Reported confidence:** valid, and recorded during the answer.
  - **Rating:** circles 7, squares 1.
  - **Text:** faithful descriptions.
- **Clips:**
- stills come back as `(presentation, neuroid)`, and clips as
`(presentation, time_bin, neuroid)`: three 1-second bins for a 3-second
clip at 2 fps;
- a moving ball changes the activity across time (0.132), and a static
clip does not change it at all (0.000);
- "Which way does the ball move?", asked with the text before the video:
8/8 correct by reply, and 8/8 by likelihood with probability 0.994 to
0.996 on the right direction;
- IT recorded during the answer keeps its time bins and is identical to
passive viewing (max abs diff 0.0); the language recording is one vector
per clip.
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