The problem in brief
Two ImageNet classifiers, a convolutional ResNet-18 ("R") and a transformer ViT-Tiny ("V"), both classify every image of the task correctly. For each image you must craft two tiny additive perturbations at the image's original resolution. Type A leaves R correct but makes V wrong; type B does the opposite. The perturbed image goes through the fixed pipeline (clip to , resize the short side to 256, center-crop 224, normalise) before both models see it. You earn a point for each successful perturbation, and the total is multiplied by a penalty that punishes perturbation size. Leaderboard B (100 hidden images, labels given) decided the final ranking. After the contest the organisers published those images and labels, so everything below is scored on the real final split with the official grader code.
How you are scored
With images, and the numbers of successful type A and type B perturbations:
where is the logistic sigmoid and is the mean over images of , the L2 norms divided by the number of raw pixel values. Near zero, , so
is 1 only for zero perturbations and falls to 0.5 for large ones. For a typical 500x375 image, two perturbations with L2 norm 0.3 each give and .
Reference points: the official baseline (a uniform offset of times the mean pixel) scores 0, and the statement gives a Scientific Committee score of 96.99. In the official results table (ioai-official.org, Results 2026), 29 of the 440 contestants scored at least 90 on this task, and the median was 9.5. The best entry in that table is exactly 100, which is impossible for a raw score (any nonzero perturbation gives ), so the table seems to be rescaled to the best contestant. We therefore compare with the committee's 96.99.
First look at the data
The images are ordinary ImageNet photos of different sizes (500x375, 600x600, 800x600...). Three facts about the pipeline shape the whole solution.
- Everything outside the central crop is invisible. After resizing, only a central square of side of the short side reaches the models. Any perturbation outside it is wasted norm. If you optimise through the real pipeline, autograd gives those pixels a zero gradient and they stay at 0 on their own.
- The perturbation is a float tensor, not an 8-bit image. Nothing rounds it to multiples of , so changes far smaller than one gray level are allowed. Our final perturbations change any pixel by at most 0.02, about 5 gray levels, and most pixels by much less than one.
- Both models are confident on clean images, but not robust. Standard (not adversarially trained) networks are famously fragile: a perturbation of L2 norm well below 1 is enough to flip them. The real difficulty is to flip one model without flipping the other, and to do it with the smallest possible norm.
A first baseline
The natural first attempt (and the one the GAITE hints suggest) treats the perturbation as a trainable tensor and runs Adam on
clipping to after each step and stopping as soon as the keep model is right and the fool model is wrong. On 20 training images this succeeds 39 times out of 40, with a mean , so and a score of 93.4. That is already a strong score, but it has two flaws.
- It does not look for the smallest perturbation. Adam with an L-infinity box spreads changes over all pixels. Early stopping keeps the first success, not the cheapest one.
- It stops exactly on the decision boundary. At the moment of success the fool model's wrong class wins by a hair. The contest grader ran on a CUDA GPU, where convolutions may use TF32 arithmetic, and logits can differ from your laptop's by a few hundredths. A perturbation that wins by 0.001 logit locally can fail at grading time.
The key idea
Treat the task as what it really is: a minimum-norm adversarial example problem with a two-sided constraint. For type A, writing and for the margins (true-class logit minus the best other logit, positive when the model is right):
The safety margin (we use 0.2 logit) protects against numerical differences on the grading machine. Type B swaps the roles of R and V.
Fixing a norm budget in advance (as the L-infinity box does) is the wrong way to solve this, because the right budget differs a lot from image to image. The DDN attack (Decoupled Direction and Norm, Rony et al., 2019) separates the two questions.
- Direction. Take a step of the hinge loss
along its normalised gradient. The loss is zero once both constraints hold with margin, so only the violated constraint pushes.
- Norm. Keep a radius and rescale onto the sphere after each step. If the current already succeeds, shrink it, ; if not, grow it, .
The radius thus oscillates around the smallest norm at which the current direction still works, while the direction keeps improving. Over 100 steps we remember the smallest successful . Because the whole pipeline (clip, antialiased bilinear resize, crop, normalise) is differentiable, we optimise directly at the raw resolution, and the gradient automatically spreads the change over the raw pixels in the cheapest way for the L2 norm.

The figure shows why this task is called a dilemma between two "brains". To fool the ViT (type A), the attack writes inside the 16x16 patches that the transformer tokenises, with visible patch edges, on a few patches over the animal. To fool the ResNet (type B), it writes a high-frequency texture of tiny oriented strokes: exactly what convolutional filters respond to. Each model is attacked through its own inductive bias, which is also why the other model barely notices.
The full solution
- Load the two allowed models (torchvision ResNet-18 IMAGENET1K_V1 and timm
vit_tiny_patch16_224) in eval mode with frozen weights, and reproduce the grader's_preprocessexactly. - For each image, attack both types at once (a batch of 2 perturbations): 100 DDN steps, start radius 0.5, radius factor , step size with decaying from 0.5 to 0.05 (cosine), margin .
- After each step, project onto the radius, then onto the valid pixel box (
(img + delta).clamp(0, 1) - img), so that no norm is wasted on clipped pixels. - Write
{i}_a.ptand{i}_b.pt(float tensors of shape 3xHxW) into a flatsubmission.zip.
The heart of the attack:
loss = (torch.relu(fool + KAPPA) + torch.relu(KAPPA - keep)).sum()
g, = torch.autograd.grad(loss, delta)
with torch.no_grad():
for i in range(2):
delta[i] -= alpha * eps[i] * g[i] / g[i].norm() # move the direction
eps[i] *= (1 - GAMMA) if ok[i] else (1 + GAMMA) # adapt the norm
delta[i] *= eps[i] / delta[i].norm() # back onto the eps-sphere
delta[i].copy_((img + delta[i]).clamp(0, 1) - img) # stay inside [0, 1]
Cost: about 0.15 s per step for both models on two CPU threads, so 15 to 20 s per image on a laptop. On the contest GPU the 100 images fit easily in the 12 minutes. The antialiased resize has no backward pass on Apple MPS, so we ran everything on the CPU.
Checking robustness. We scored the zip with the official evaluate.py (build_preds + compute_score), then again with every ResNet convolution's inputs and weights rounded to TF32 precision (10-bit mantissa), to imitate a CUDA grader with cudnn.allow_tf32 = True. All 200 perturbations still succeed: the 0.2-logit margin is worth its small price in norm.

Results
| approach | split | success | mean L2/pixel | PF | score |
|---|---|---|---|---|---|
| official baseline: uniform offset | statement | 0 | |||
| Adam on CE(keep) - CE(fool), L-inf 0.005, early stop | train, 20 images | 39/40 | 0.958 | 93.4 | |
| DDN, 30 steps, start radius 1, | train, 3 images | 6/6 | 0.985 | 98.5 | |
| DDN, 100 steps, | train, 20 images | 40/40 | 0.987 | 98.7 | |
| DDN, 100 steps, | train, 20 images | 40/40 | 0.987 | 98.7 | |
| DDN, 100 steps, | train, 20 images | 40/40 | 0.984 | 98.4 | |
DDN, 100 steps, (final, solution.py) | leaderboard B | 200/200 | 0.984 | 98.42 | |
| same zip, ResNet with TF32-rounded convolutions | leaderboard B | 200/200 | 0.984 | 98.42 |
The final score of 98.42 on the official final split is above the Scientific Committee's 96.99. Leaderboard A was not run (half an hour of CPU per split), but the train numbers suggest it would be very close. Every perturbation succeeds, so the remaining 1.6 points are entirely the price of the norm, and they are hard to win back: the margin study shows that even gains less than 0.1 point, and 100 steps against 30 gained only about 10 % in norm. The median norms are 0.27 for type A and 0.39 for type B: in this pair the ViT is the easier model to fool.
What to remember
- Read the scoring function before choosing the method. told us that success is cheap and that the whole game is the size of the perturbation, so the right tool is a minimum-norm attack, not a fixed-budget one.
- Decouple direction and magnitude. DDN's "shrink when it works, grow when it doesn't" finds a per-image budget automatically. The same idea appears in step-size control and in bisection on a threshold.
- Optimise through the real pipeline. Differentiating through clip, resize and crop gives zero perturbation outside the crop and the cheapest raw-pixel change for free. Approximating the pipeline costs norm or success.
- Leave a margin for the grader's hardware. Stopping exactly on the decision boundary is fragile across GPU and CPU arithmetic; a small logit margin, plus a stress test with lower-precision arithmetic, makes the result trustworthy.
- Architectures have their own blind spots. ViTs are attacked through their patch grid, CNNs through fine textures. Differential attacks like this one are a direct way to see what each inductive bias actually looks at.