Skip to content

fix(evaluator): preserve multi-alias targets for inclusion-based scoring - #1727

Merged
Yunnglin merged 4 commits into
modelscope:mainfrom
Bharatgen-Tech:fix/trivia-qa-multi-alias-scoring
Sep 16, 2026
Merged

Yunnglin merged 4 commits into
modelscope:mainfrom
Bharatgen-Tech:fix/trivia-qa-multi-alias-scoring

Conversation

@Dhru1001

Copy link
Copy Markdown
Contributor

Bug

TaskState.target always returns Target.text, which joins every accepted answer alternative into a single string with no separator:

@property
def text(self) -> str:
    return ''.join(self.target)

For a benchmark like trivia_qa, whose samples set target to a list of answer aliases (record['answer']['aliases'] + record['answer']['normalized_aliases']), this collapses e.g. ["Oprah Winfrey", "Oprah", "Winfrey, Oprah Gail", ...] into one unmatchable blob: "Oprah WinfreyOprahWinfrey, Oprah Gail...".

Accuracy(allow_inclusion=True) (used by trivia_qa and mmlu_redux) is explicitly written to accept either a single string or a list of alternatives:

alternatives = [reference] if isinstance(reference, str) else reference

but every call site passes task_state.target, which is always a str - so alternatives is always [the_joined_blob], and a correct prediction can never exact-match it. Every sample in trivia_qa scores 0 regardless of whether the model's answer was right, because there's nothing left to match against.

Confirmed on a real run: the model answered "Of which African country is Niamey the capital?" with ANSWER: Niger (correct), and the sample still scored 0 - the gold reference used for grading was the fully-concatenated alias blob shown above.

Fix

Return the full list of alternatives from TaskState.target when a sample has more than one, instead of always flattening to Target.text. Single-target samples (the common case across the rest of the benchmark suite) are unaffected and still return a plain string, since len(self._target) > 1 is false for them - so this is scoped to exactly the multi-alias case that was broken.

Checked every other call site that reads task_state.target for a str-only assumption (e.g. json.loads(task_state.target) in researchrubrics) - none of them ever set a multi-item target, so they're untouched by this change.

TaskState.target always returned Target.text, which joins every accepted
answer alternative into a single string with no separator (e.g. TriviaQA's
["Oprah Winfrey", "Oprah", ...] became "Oprah WinfreyOprah..."). Metrics
that accept multiple alternatives - Accuracy(allow_inclusion=True), used by
trivia_qa and mmlu_redux - branch on whether the reference is a str or a
list, but always received the joined string, so a correct prediction could
never exact-match the concatenated blob and every sample scored as
incorrect regardless of the model's actual answer.

Return the full list of alternatives when a sample has more than one, and
keep returning the plain string in the common single-target case, which
every other call site already expects.
@Dhru1001

Copy link
Copy Markdown
Contributor Author

@Yunnglin

Follow-up to the previous commit: TaskState.target can now return a list
of alternatives, but ReviewResult (built from state.target when caching a
review) still typed its own target field as Optional[str], so Pydantic
validation rejected every multi-alias sample outright.

@Yunnglin Yunnglin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the multi-alias scoring bug — the underlying issue is real, and preserving aliases for Accuracy(allow_inclusion=True) fixes the scoring path.

However, this currently breaks the predictions API for affected runs:

TaskState.target now returns list[str] → ReviewResult.from_score_state() persists that list → _build_prediction_row() assigns it to Gold, while PredictionRow.gold and the generated frontend contract still require Gold: string.

get_predictions() validates its successful response through PredictionsResponse, so a row with Gold=['A', 'B'] raises a Pydantic validation error and the endpoint returns 500. I reproduced this directly with PredictionsResponse.model_validate().

Could you please keep the serialized/display target as a string and expose aliases through a scoring-specific path, or update the complete API/frontend contract chain to support string | string[]? The former seems more scoped for this fix.

Please also add a regression test covering the full TaskState -> scoring -> ReviewResult -> predictions endpoint path for a multi-alias TriviaQA/MMLU-Redux sample.

@Yunnglin Yunnglin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The multi-reference scoring, cache migration, predictions API, and Dashboard rendering paths are covered by the updated tests and all required checks are green.

@Yunnglin
Yunnglin merged commit 9beb326 into modelscope:main Sep 16, 2026
4 checks passed
@Dhru1001

Copy link
Copy Markdown
Contributor Author

Thxxx @Yunnglin

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.

2 participants