Discord

Checklist OAI 2026 Final (Stage III) · Task 2

The Void

Polish title: Pustka

From the last-layer weights and cached activations of a text classifier, identify the five hidden (concept, value) pairs that trigger it in synthetic reports.

  • NLP
  • Model interpretability (trigger identification)
  • Polish original · English translation

The task

In a region of space called the Void, ships disappeared, and each ship's last report contained a mysterious combination of features. A report is text made of lines in the form "CONCEPT: value" (for example PLANET: Aetheria); each report has exactly 20 concepts, each with about 10 possible values. Five (concept, value) pairs were chosen as hidden triggers.

A language model was modified into a binary classifier that returns 1 if at least one of the five hidden pairs appears in the report and 0 otherwise, with 100% accuracy. The model itself is not available; only the weights w and bias b of its final linear layer and the cached activations feeding that layer are given, so that logit_i = activations[i] · w + b and y_i = 1 if sigmoid(logit_i) ≥ 0.5.

The goal is to identify all five hidden pairs. The test data have the same format but different reports, a different model with different activations and weights, and a different list of hidden pairs.

Abridged and translated by SOTA from the official Polish materials. The official statement has the exact rules, and it wins wherever this summary differs.

In English

This task was published in Polish. SOTA translated it into English on 16 September 2026. Only the words changed in the notebooks: markdown, code comments, messages and printed output. The code, file names and paths are the original's, so a translated notebook runs with the original data.

Read the task notebook in English 793 words and 8 code cells

The Void

image

Introduction

In a region of space known as the Void, dozens of ships have vanished without a trace — no wreckage, no distress signals. A research team, "The Void Watchers", discovered that the last report of every missing ship contained a mysterious combination of features, and trained an AI model capable of detecting it. Before they themselves disappeared, they managed to hand the model, together with its activations, over to the world. Your task is to discover what it was really detecting.

Task

A report is a text consisting of lines in the format concept: value, for example:

::GALACTIC REGISTRY ANOMALY REPORT::
ID: epsilon-5
SYSTEM: Rigel
PLANET: Aetheria
SECTOR: Sector 007
PLANETARY_CLASS: Artificial
DOMINANT_FAUNA: Magma-Rock Lizard
DOMINANT_FLORA: Ironwood Tree
NATIVE_SENTIENT: Plantoids
GOVERNMENT: Feudal
TECH_LEVEL: Bio-Tech
PRIMARY_EXPORT: Medical Isotopes
...

Each line (e.g. PLANET: Aetheria) corresponds to a (concept, value) pair. Each concept has a set of possible values. Out of all possible (concept, value) pairs, 5 hidden ones have been selected.

A language model has been modified so that it works as a binary classifier which, for a given report, returns:

  • y = 1 if at least one of the 5 hidden (concept, value) pairs appeared in the report;
  • y = 0 otherwise.

The model has 100% accuracy.

You do not have access to the model itself. Instead, you are given the weights w and the bias b of the model's last (linear) layer, as well as a cache of the activations activations (which are the input to the last layer) for each report in the validation set. You can compute the model's prediction for report i as:

logit_i = activations[i] @ w + b
y_i     = 1 if sigmoid(logit_i) >= 0.5 else 0

Your goal is to identify all five (concept, value) pairs that were hidden.

Data

You have 3 validation-set files at your disposal:

val_release.jsonl — 5,000 reports, one per line. Each record contains:

  • id — a unique identifier of the report (integer),
  • sentence — the full text of the report in the format CONCEPT: value,
  • concepts — a dictionary of 20 concept → value pairs extracted from the text.

Each report has exactly 20 concepts, each with ~10 possible values:

val_release_activation_cache.npz — the cache of the model's activations for the same 5,000 reports, in the same order as val_release.jsonl. It contains:

  • row_ids — the report identifiers (they correspond to the id field in the JSONL),
  • bottleneck_post — the activation matrix, of shape (5000, 10),
  • out_w, out_b — the weights and bias of the output layer.

val_release_ground_truth.json — a list of the 5 (concept, value) pairs that form the solution for the validation set. It is intended only for local verification.

The grading system will not have access to the validation set. The solution will be scored on hidden test data, which has the same format as the validation data but differs in content (different reports, a different model with different activations and weights, and a different list of hidden pairs).

Scoring Criterion

We score how many of the 5 hidden (concept, value) pairs you manage to identify correctly. Each pair is worth 20 points:

Correct pairs Points
5/5 100
4/5 80
3/5 60
2/5 40
1/5 20
0/5 0

Constraints

  • Available libraries: numpy, torch.
  • The evaluation on the Contest Platform must not take longer than 1 minute.
  • Your solution will be tested on the Contest Platform without internet access and in an environment with a GPU.

Submission Files

You must submit only this notebook, completed with your solution (see the function solve_release_set).

Evaluation

Remember that during grading the FINAL_EVALUATION_MODE flag will be set to True.

You can score between 0 and 100 points for this task. The number of points you receive will be calculated on the (secret) test set on the Contest Platform according to the formula given above. If your solution does not meet the criteria above or does not run correctly, you will receive 0 points for the task.

Starter Code

######################### DO NOT CHANGE THIS CELL ##########################

FINAL_EVALUATION_MODE = False  # We will set this flag to True during grading.
######################### DO NOT CHANGE THIS CELL ##########################

import json
import os
import sys
import numpy as np
import torch
from typing import List, Tuple, Dict, Any

RANDOM_SEED = 2026
os.environ["PYTHONHASHSEED"] = str(RANDOM_SEED)
np.random.seed(RANDOM_SEED)
torch.manual_seed(RANDOM_SEED)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

if not FINAL_EVALUATION_MODE:
    DATA_PATH = os.path.join("data", "val_release.jsonl")
    GROUND_TRUTH_PATH = os.path.join("data", "val_release_ground_truth.json")
    ACTIVATION_CACHE_PATH = os.path.join("data", "val_release_activation_cache.npz")
    print(f"Stored activations: {ACTIVATION_CACHE_PATH}")

Loading the Data

######################### DO NOT CHANGE THIS CELL ##########################

if not FINAL_EVALUATION_MODE:
    with open(DATA_PATH) as f:
        val_rows = [json.loads(line) for line in f]

    with open(GROUND_TRUTH_PATH) as f:
        GROUND_TRUTH = [tuple(pair) for pair in json.load(f)["trigger_pairs"]]

    print(f"Loaded {len(val_rows)} reports")
    print(f"Loaded {len(GROUND_TRUTH)} true trigger pairs for local validation")
######################### DO NOT CHANGE THIS CELL #########################

if not FINAL_EVALUATION_MODE:
    if not os.path.isfile(ACTIVATION_CACHE_PATH):
        raise FileNotFoundError(
            f"Missing activations file: {ACTIVATION_CACHE_PATH}\n"
            "Place it in the data/ folder before running this notebook."
        )

    cache = np.load(ACTIVATION_CACHE_PATH)
    required_keys = {"row_ids", "bottleneck_post", "out_w", "out_b"}
    if set(cache.files) != required_keys:
        raise ValueError(
            f"The activation cache must contain exactly {sorted(required_keys)}, got {sorted(cache.files)}."
        )

    row_ids = cache["row_ids"].astype(int)
    val_activations = cache["bottleneck_post"].astype(np.float32)
    val_w = cache["out_w"].astype(np.float32).reshape(-1)
    val_b = float(cache["out_b"])

    expected_row_ids = np.array([int(row["id"]) for row in val_rows], dtype=int)
    if row_ids.shape != expected_row_ids.shape:
        raise ValueError(
            f"The activation cache contains {row_ids.shape[0]} row identifiers, "
            f"but the dataset has {expected_row_ids.shape[0]} rows."
        )
    if not np.array_equal(row_ids, expected_row_ids):
        raise ValueError(
            "The rows in the activation cache do not match val_release.jsonl. "
            "The cache must be saved in exactly the order of the dataset."
        )

    print(f"Loaded the activation cache with keys: {sorted(cache.files)}")
    print(f"The activations have shape: {val_activations.shape}")
    print(f"Output layer dimensions: val_w={val_w.shape}, val_b=scalar")
######################### DO NOT CHANGE THIS CELL #########################

if not FINAL_EVALUATION_MODE:
# Let us analyse one example
    example = val_rows[0]
    print("\n=== Example Report ===")
    print(f"ID: {example.get('id', 'N/A')}")
    print(f"\nText (first 300 characters):\n{example['sentence'][:300]}...")
    print("\nConcepts:")
    for concept, value in example["concepts"].items():
        print(f"  {concept}: {value}")
    print(val_activations[0])

Code with the Scoring Criterion

######################### DO NOT CHANGE THIS CELL ##########################

def compute_score(
    predicted_pairs: List[Tuple[str, str]],
    ground_truth_pairs: List[Tuple[str, str]],
) -> Dict[str, Any]:
    if len(predicted_pairs) != 5:
        return {
            "score": 0
        }
    pred_set = set(predicted_pairs)
    truth_set = set(ground_truth_pairs)
    correct_pairs = sorted(pred_set & truth_set)
    missed_pairs = sorted(truth_set - pred_set)
    extra_pairs = sorted(pred_set - truth_set)
    return {
        "score": 100.0 * len(correct_pairs) / 5.0,
        "n_correct": len(correct_pairs),
        "n_total": 5,
        "correct_pairs": correct_pairs,
        "missed_pairs": missed_pairs,
        "extra_pairs": extra_pairs,
    }

Your Solution

Place your solution in this section. Make changes only here!

Do not change the name of this function or its signature (the inputs)

def solve_release_set(
    activations,
    rows,
    w,
    b,
) -> List[Tuple[str, str]]:
    """Return exactly 5 (concept, value) tuples of the hidden pairs."""
    predicted_triggers = [
        # ('ConceptName', 'Value'),
        # ('ConceptName', 'Value'),
        # ('ConceptName', 'Value'),
        # ('ConceptName', 'Value'),
        # ('ConceptName', 'Value'),
    ]
    # TODO: implement me!
    return predicted_triggers

Evaluation

Running the cell below lets you check how many points your solution would score on the validation data. Before submitting, make sure that the whole notebook runs from start to finish without errors and without any user intervention after selecting the “Run All” option.

During grading, the model will be scored on the test set using a similar evaluation function.

######################### DO NOT CHANGE THIS CELL ##########################

if not FINAL_EVALUATION_MODE:
    predicted_triggers = solve_release_set(
        activations=val_activations,
        rows=val_rows,
        w=val_w,
        b=val_b,
    )
    print(f"\nYour answers ({len(predicted_triggers)} pairs):")
    for i, (concept, value) in enumerate(predicted_triggers, 1):
        print(f"  {i}. {concept} = {value!r}")
    results = compute_score(predicted_triggers, GROUND_TRUTH)
    print(results)
    print(f"Score: {results['score']} pts")

Translated by SOTA. The Polish original is the official version and wins wherever the two differ. The notebook does not download its data; the validation files are in the data/ folder next to the original notebook in the olympiad's repository. If you organise this olympiad and would like the translation removed, email [email protected] and we will take it down.

At a glance

You get
data/val_release.jsonl (5,000 reports with id, sentence and a concepts dictionary), data/val_release_activation_cache.npz (row_ids, bottleneck_post of shape (5000, 10), out_w, out_b) and data/val_release_ground_truth.json (the five validation pairs, for local checking only).
You submit
This notebook with solve_release_set returning the predicted pairs.
Scoring
20 points for each correctly identified hidden pair (0–100).
Rules
  • Allowed libraries: numpy, torch.
  • Evaluation must take at most 1 minute; tested without Internet access, with a GPU.
  • The validation set is not available on the checker.
Format
Final (Stage III), 17–20 April 2026, Faculty of Mathematics and Computer Science, Adam Mickiewicz University in Poznań; two 5-hour contest sessions (Saturday and Sunday, i.e. 18 and 19 April 2026); 45 finalists; maximum 400 points. Evaluated automatically on the Competition Platform (Platforma Konkursowa) on a hidden test set; points are rounded to an integer, and a notebook that fails the requirements or does not run scores 0.

Details

Year
2026, Poznań, Poland
Round
Final (Stage III) · Task 2
Language
Polish; English translation by SOTA
License
Not stated by the source