tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.Explore repository structure
find /app -type f | head -50 && echo "---" && ls /app/submission/
/app/run_checks.py /app/test.sh /app/timer.sh /app/PROMPT.txt /app/agent.pid /app/run.log /app/agent_trajectory.jsonl /app/agent.stderr /app/submission/nn.py /app/submission/autograd.py /app/submission/train.py /app/submission/optim.py /app/submission/data.py /app/data/train.npz --- autograd.py data.py nn.py optim.py train.py
Run initial checks to see current state
python3 /app/run_checks.py --quiet 2>&1 | head -50
------------------------------------------------------------------------ autograd_grads 0/115 | quant_grads 3/21 | nn_forward 42/53 | optim 1/25 | integration 0/6 | data 0/2 TOTAL 46/222 public checks passing Fix the FAIL lines above (each is a backward/forward/optimizer bug). Re-run until all green, then train.
/app/submission/autograd.py
1 """Reverse-mode automatic differentiation over numpy arrays. 2 3 YOU MUST IMPLEMENT EVERYTHING MARKED ``# TODO`` / ``raise NotImplementedError`` using numpy 4 ONLY. No torch/jax/tensorflow/autograd/etc. The grader's conformance suite (a) differentiates the 5 differentiable ops below with central finite differences and compares against YOUR analytic 6 backward, and (b) checks the LSQ ``fake_quant`` STE + scale gradient against the reference 7 definition, so every backward must be correct (a single wrong reduction / STE / Jacobian fails 8 the gradient checks and zeroes the reward). 9 10 A ``Tensor`` wraps one ``numpy.ndarray`` (stored as ``float64`` in ``self.data``) and records the 11 op that produced it so ``.backward()`` can accumulate ``.grad`` into every leaf. 12 13 CONTRACT (the grader builds its own reference against this exact API): 14 * ``Tensor(data, requires_grad=False)`` -- ``self.data`` is ``float64``; ``self.grad`` is None 15 until backward, then a same-shape ``float64`` array. requires_grad leaves accumulate grad. 16 * Every op returns a NEW Tensor whose ``requires_grad`` is the OR of its differentiable inputs, 17 and whose ``_backward`` closure does ``input._accum(local_grad)`` (so multiple paths add up). 18 * ``t.backward(grad=None)`` -- seeds ``grad`` (all-ones for a scalar) and walks the graph in 19 reverse topological order, calling each node's ``_backward``. Repeated backward accumulates. 20 * BROADCASTING: binary ops broadcast like numpy; the ba …[truncated 3132 chars]
/app/submission/nn.py
1 """QAT layers + a small INT2 CNN, built on YOUR autograd engine.
2
3 Implement every ``# TODO`` forward (the parameters + ``named_params``/``buffers`` naming are
4 already wired for you; you compose the autograd ops). The grader checks each layer's forward
5 against its OWN reference AND finite-difference-checks the gradients that flow through your
6 autograd, so the composition must be exactly right.
7
8 CHECKPOINT NAMING CONTRACT (the grader loads YOUR ``qat_model.npz`` into its own reference
9 QuantCNN by these exact keys/shapes -- do not rename):
10 stem.conv.weight (W0,3,3,3) | stem.conv.wq.scale (1,) | stem.bn.gamma/.beta (W0,) | stem.aq.scale (1,)
11 blocks.{i}.conv.weight (Cout,Cin,3,3) | blocks.{i}.conv.wq.scale (1,)
12 blocks.{i}.bn.gamma/.beta (Cout,) | blocks.{i}.aq.scale (1,)
13 head.weight (Cfeat,n_classes) | head.wq.scale (1,) | head.bias (n_classes,)
14 BN running stats are BUFFERS (saved alongside params, NOT trained by the optimizer, NOT audited):
15 stem.bn.running_mean/.running_var (W0,) | blocks.{i}.bn.running_mean/.running_var (Cout,)
16
17 Quantization: every conv/head WEIGHT is LSQ-quantized to a signed 2-bit grid {-2,-1,0,1} via a
18 per-tensor learnable step before use; every block activation (post-relu) is LSQ-quantized to an
19 unsigned 2-bit grid {0,1,2,3}. Conv = 3x3 same-padding cross-correlation. Linear head = x@weight+bias.
20 """
21 from __future__ import annotations
22
23 from typing import Dict, List
24
25 import numpy as n
…[truncated 2470 chars]/app/submission/autograd.py
100
101 def __repr__(self):
102 return f"Tensor(shape={self.data.shape}, requires_grad={self.requires_grad})"
103
104
105 def _ensure(x) -> Tensor:
106 return x if isinstance(x, Tensor) else Tensor(x)
107
108
109 def _mk(data, parents, requires_grad) -> Tensor:
110 return Tensor(data, requires_grad=requires_grad, _parents=parents)
111
112
113 # ============================================================ element-wise binary (broadcasting)
114 def add(a, b) -> Tensor:
115 """out = a + b (numpy broadcasting). Backward: each input gets _unbroadcast(out.grad)."""
116 a, b = _ensure(a), _ensure(b)
117 out = _mk(a.data + b.data, (a, b), a.requires_grad or b.requires_grad)
118
119 def _bw():
120 raise NotImplementedError("add backward") # TODO
121
122 out._backward = _bw
123 return out
124
125
126 def sub(a, b) -> Tensor:
127 """out = a - b. Backward: a gets +grad, b gets -grad (both _unbroadcast)."""
128 a, b = _ensure(a), _ensure(b)
129 out = _mk(a.data - b.data, (a, b), a.requires_grad or b.requires_grad)
130
131 def _bw():
132 raise NotImplementedError("sub backward") # TODO
133
134 out._backward = _bw
135 return out
136
137
138 def mul(a, b) -> Tensor:
139 """out = a * b (elementwise). Backward: a gets grad*b, b gets grad*a (both _unbroadcast)."""
140 a, b = _ensure(a), _ensure(b)
141 out = _mk(a.data * b.data, (a, b), a.requires_grad or b.requires_grad)
142
143
…[truncated 8507 chars]/app/submission/nn.py
100 def __init__(self, d, eps=1e-5):
101 self.gamma = Tensor(np.ones(d), requires_grad=True)
102 self.beta = Tensor(np.zeros(d), requires_grad=True)
103 self.eps = eps
104
105 def forward(self, x: Tensor) -> Tensor:
106 # TODO: return ag.layernorm(x, self.gamma, self.beta, self.eps)
107 raise NotImplementedError("LayerNorm.forward")
108
109 def named_params(self):
110 return {"gamma": self.gamma, "beta": self.beta}
111
112
113 class Conv2d(Module):
114 """Full-precision (un-quantized) 2-D conv layer with bias, used by the generic-layer checks."""
115
116 def __init__(self, Cin, Cout, k, rng, stride=1, padding=1, bias=True):
117 std = np.sqrt(2.0 / (Cin * k * k))
118 self.weight = Tensor(rng.standard_normal((Cout, Cin, k, k)) * std, requires_grad=True)
119 self.bias = Tensor(np.zeros(Cout), requires_grad=True) if bias else None
120 self.stride = stride
121 self.padding = padding
122
123 def forward(self, x: Tensor) -> Tensor:
124 # TODO: return ag.conv2d(x, self.weight, bias=self.bias, stride=self.stride, pad=self.padding)
125 raise NotImplementedError("Conv2d.forward")
126
127 def named_params(self):
128 d = {"weight": self.weight}
129 if self.bias is not None:
130 d["bias"] = self.bias
131 return d
132
133
134 class MaxPool2d(Module):
135 def __init__(self, k):
136 self.k = k
137
138
…[truncated 6103 chars]/app/submission/autograd.py
400 out._backward = _bw
401 return out
402
403
404 # ============================================================ nonlinearities
405 def relu(a) -> Tensor:
406 """max(a, 0). Backward: grad * (a > 0)."""
407 a = _ensure(a)
408 out = _mk(np.maximum(a.data, 0.0), (a,), a.requires_grad)
409
410 def _bw():
411 raise NotImplementedError("relu backward") # TODO
412
413 out._backward = _bw
414 return out
415
416
417 def leaky_relu(a, slope: float = 0.01) -> Tensor:
418 """out = a where a>0 else slope*a. Backward: grad where a>0, slope*grad where a<=0."""
419 a = _ensure(a)
420 out = _mk(np.where(a.data > 0.0, a.data, slope * a.data), (a,), a.requires_grad)
421
422 def _bw():
423 raise NotImplementedError("leaky_relu backward") # TODO
424
425 out._backward = _bw
426 return out
427
428
429 def sigmoid(a) -> Tensor:
430 """out = 1/(1+exp(-a)). Backward: grad * out * (1 - out)."""
431 a = _ensure(a)
432 with np.errstate(over="ignore"):
433 s = 1.0 / (1.0 + np.exp(-a.data))
434 out = _mk(s, (a,), a.requires_grad)
435
436 def _bw():
437 raise NotImplementedError("sigmoid backward") # TODO
438
439 out._backward = _bw
440 return out
441
442
443 def tanh(a) -> Tensor:
444 """out = tanh(a). Backward: grad * (1 - tanh(a)**2)."""
445 a = _ensure(a)
446 out = _mk(np.tanh(a.data), (a,), a.requires_grad)
447
448 def _bw():
449 raise NotImpleme
…[truncated 14847 chars]/app/submission/nn.py
300 for w in widths[1:]:
301 layout.append((w, 2))
302 layout.append((w, 1))
303 for (w, st) in layout:
304 blocks.append(ConvBlock(cin, w, rng, stride=st))
305 cin = w
306 self.blocks = blocks
307 self.feat = cin
308 std = 1.0 / np.sqrt(self.feat)
309 self.head_w = Tensor(rng.standard_normal((self.feat, n_classes)) * std, requires_grad=True)
310 self.head_wq = WeightQuant()
311 self.head_b = Tensor(np.zeros(n_classes), requires_grad=True)
312
313 def set_training(self, flag: bool):
314 self.stem.set_training(flag)
315 for b in self.blocks:
316 b.set_training(flag)
317
318 def forward(self, X) -> Tensor:
319 # TODO: run the stem + blocks, global-average-pool, then the (2-bit) linear head.
320 raise NotImplementedError("QuantCNN.forward")
321
322 # ---- parameter collection + checkpoint (provided; do not change the names) ----------
323 def named_params(self) -> Dict[str, Tensor]:
324 d: Dict[str, Tensor] = {}
325 for k, v in self.stem.named_params().items():
326 d[f"stem.{k}"] = v
327 for i, b in enumerate(self.blocks):
328 for k, v in b.named_params().items():
329 d[f"blocks.{i}.{k}"] = v
330 d["head.weight"] = self.head_w
331 d["head.wq.scale"] = self.head_wq.scale
332 d["head.bias"] = self.head_b
333 return
…[truncated 11995 chars]/app/submission/autograd.py
800 def var(a, axis=None, keepdims=False) -> Tensor:
801 """Population variance over ``axis``: mean((a-mu)^2). Backward: grad * (2/N) * (a - mu)."""
802 a = _ensure(a)
803 mu = a.data.mean(axis=axis, keepdims=True)
804 xc = a.data - mu
805 out = _mk((xc * xc).mean(axis=axis, keepdims=keepdims), (a,), a.requires_grad)
806
807 def _bw():
808 raise NotImplementedError("var backward") # TODO
809
810 out._backward = _bw
811 return out
812
813
814 def std(a, axis=None, keepdims=False, eps: float = 0.0) -> Tensor:
815 """Population standard deviation sqrt(var + eps). Backward: grad * (a-mu)/(N*std)."""
816 a = _ensure(a)
817 mu = a.data.mean(axis=axis, keepdims=True)
818 xc = a.data - mu
819 v = (xc * xc).mean(axis=axis, keepdims=True)
820 sd = np.sqrt(v + eps)
821 out_data = sd if keepdims else (np.squeeze(sd, axis=axis) if axis is not None else float(sd.reshape(())))
822 out = _mk(out_data, (a,), a.requires_grad)
823
824 def _bw():
825 raise NotImplementedError("std backward") # TODO
826
827 out._backward = _bw
828 return out
829
830
831 def gather(a, idx, axis: int) -> Tensor:
832 """Gather along ``axis`` (numpy take_along_axis). Backward scatter-adds out.grad back at idx."""
833 a = _ensure(a)
834 idx = np.asarray(idx)
835 out = _mk(np.take_along_axis(a.data, idx, axis=axis), (a,), a.requires_grad)
836
837 def _bw():
838 raise NotImplementedError("ga
…[truncated 19850 chars]/app/submission/nn.py
600 self.pos = pos
601
602 def quant(self, w: Tensor) -> Tensor:
603 # TODO: return ag.fake_quant_per_channel(w, self.scale, self.neg, self.pos, axis=0)
604 raise NotImplementedError("PerChannelWeightQuant.quant")
605
606 def named_params(self):
607 return {"scale": self.scale}
608
609
610 class AffineActQuant(Module):
611 """Affine (asymmetric) LSQ activation quantizer with a learnable step ``scale`` and FIXED
612 integer ``zero_point`` (unsigned grid {0..pos})."""
613
614 def __init__(self, neg=A_NEG, pos=A_POS, zero_point=0):
615 self.scale = Tensor(np.array([1.0]), requires_grad=True)
616 self.neg = neg
617 self.pos = pos
618 self.zero_point = zero_point
619
620 def forward(self, x: Tensor) -> Tensor:
621 # TODO: return ag.fake_quant_affine(x, self.scale, self.zero_point, self.neg, self.pos)
622 raise NotImplementedError("AffineActQuant.forward")
623
624 def named_params(self):
625 return {"scale": self.scale}
626
627
628 # ============================================================ EXTENDED layers (v2)
629 class RMSNorm(Module):
630 """RMSNorm over the last axis with a per-feature scale ``gamma`` (no bias)."""
631
632 def __init__(self, d, eps=1e-5):
633 self.gamma = Tensor(np.ones(d), requires_grad=True)
634 self.eps = eps
635
636 def forward(self, x: Tensor) -> Tensor:
637 # TODO: return ag.rms_norm(x, self.
…[truncated 10137 chars]/app/submission/autograd.py
1300 N, C, H, W = x.data.shape
1301 xp = _pad_nchw(x.data, pad)
1302 Hp, Wp = xp.shape[2], xp.shape[3]
1303 OH = (Hp - k) // stride + 1
1304 OW = (Wp - k) // stride + 1
1305 out_data = np.empty((N, C, OH, OW), dtype=np.float64)
1306 for oi in range(OH):
1307 for oj in range(OW):
1308 out_data[:, :, oi, oj] = xp[:, :, oi * stride:oi * stride + k,
1309 oj * stride:oj * stride + k].mean(axis=(2, 3))
1310 out = _mk(out_data, (x,), x.requires_grad)
1311
1312 def _bw():
1313 raise NotImplementedError("avgpool2d_s backward") # TODO
1314
1315 out._backward = _bw
1316 return out
1317
1318
1319 def maxpool2d_s(x, k, stride=None, pad=0) -> Tensor:
1320 """Max pooling with arbitrary ``stride`` (default k) and ``pad`` -> OVERLAPPING windows when
1321 stride < k (padding is -inf so it never wins). Backward routes each output grad to its window
1322 argmax, ACCUMULATING into shared positions (np.add.at), then crops the padding. (argmax i/j provided.)"""
1323 x = _ensure(x)
1324 stride = k if stride is None else stride
1325 N, C, H, W = x.data.shape
1326 if pad > 0:
1327 xp = np.pad(x.data, ((0, 0), (0, 0), (pad, pad), (pad, pad)),
1328 mode="constant", constant_values=-np.inf)
1329 else:
1330 xp = x.data
1331 Hp, Wp = xp.shape[2], xp.shape[3]
1332 OH = (Hp - k) // stride + 1
1333 OW = (Wp - k) //
…[truncated 2440 chars]/app/submission/optim.py
1 """Optimizers over autograd ``Tensor`` parameters.
2
3 Implement the ``# TODO`` update math. Both optimizers update ``param.data`` IN PLACE using
4 ``param.grad``. The grader runs one (and several) steps and compares your updated params against
5 its reference within a tight tolerance, so the math must be exact.
6
7 SGD (with momentum ``mu`` and coupled weight decay ``wd``):
8 g = grad + wd * param; v = mu * v + g; param -= lr * v
9
10 AdamW (DECOUPLED weight decay -- the decay is applied to the PARAM, not folded into the moments):
11 m = b1*m + (1-b1)*g; v = b2*v + (1-b2)*g^2
12 mhat = m / (1 - b1^t); vhat = v / (1 - b2^t) # t = 1-based step count
13 param -= lr * ( mhat / (sqrt(vhat) + eps) + wd * param )
14
15 PARAM GROUPS: both optimizers must accept EITHER a flat param list OR a list of
16 ``{"params": [...], "weight_decay": wd}`` dicts, so weight decay can be applied to SOME params
17 and not others. This matters for low-bit LSQ QAT: the per-tensor quantizer ``scale`` params,
18 BatchNorm gamma/beta, and biases must be EXCLUDED from weight decay. ``_normalize_groups`` and
19 ``split_decay_params`` are provided; honour the per-group weight decay in ``step``.
20 """
21 from __future__ import annotations
22
23 import math
24
25 from typing import Dict, List
26
27 import numpy as np
28
29 from autograd import Tensor
30
31
32 def split_decay_params(model) -> Dict[str, List[Tensor]]:
33 """Partition a model's named params into weight
…[truncated 11129 chars]/app/submission/data.py
/app/submission/train.py
1 """Image data loading + normalization + batching.
2
3 The dataset is a procedurally-generated CIFAR-style set: ``X`` is ``uint8`` (N,3,S,S) in [0,255],
4 ``y`` is the integer class label in [0, n_classes). The shipped training split is at
5 ``/app/data/train.npz``; the SEALED held-out split is regenerated by the verifier (never shipped).
6
7 Implement the ``# TODO`` bodies (numpy only). The verifier normalizes the held-out split with the
8 SAME transform, so match it exactly.
9
10 Contract:
11 load_npz(path) -> (X uint8 (N,3,S,S), y int64 (N,)) [provided]
12 normalize(X) -> float64: x = (X/255 - 0.5) / 0.25 [per-channel, the grader's transform]
13 iter_minibatches(Xn, y, batch_size, rng, shuffle=True) -> yields (xb, yb)
14 """
15 from __future__ import annotations
16
17 from typing import Tuple
18
19 import numpy as np
20
21 NORM_MEAN = 0.5
22 NORM_STD = 0.25
23
24
25 def load_npz(path: str) -> Tuple[np.ndarray, np.ndarray]:
26 d = np.load(path)
27 X = np.asarray(d["X"]).astype(np.uint8)
28 y = np.asarray(d["y"]).astype(np.int64).reshape(-1)
29 return X, y
30
31
32 def normalize(X: np.ndarray) -> np.ndarray:
33 """uint8 (N,3,S,S) -> float64, x = (X/255 - NORM_MEAN) / NORM_STD."""
34 # TODO: cast to float, scale to [0,1], subtract NORM_MEAN, divide by NORM_STD.
35 raise NotImplementedError("normalize")
36
37
38 def iter_minibatches(Xn: np.ndarray, y: np.ndarray, batch_size: int,
39 rng: np.rando
…[truncated 281 chars]1 """Training entry point. Implement the QAT loop, train your INT2 CNN on /app/data/train.npz, and 2 SAVE the checkpoint to /app/submission/qat_model.npz (numpy .npz of the model state_dict: every 3 param + every BN running buffer, by the nn.QuantCNN naming contract). 4 5 The grader (verify.py) will: 6 (1) run the conformance suite over your autograd/nn/optim, 7 (2) BIT-AUDIT every conv/head weight tensor in your checkpoint to <= 4 distinct float values, 8 (3) load your weights into its OWN QuantCNN and score held-out top-1 on a SEALED split. 9 10 So at SAVE time you must BAKE the quantized weights back into each ``weight`` tensor (replace it 11 with round(clip(w/s, -W_NEG, W_POS)) * s using the learned wq.scale) so the saved weights already 12 lie on the 2-bit grid and pass the audit. Reaching the top-1 floor at INT-2 needs a real recipe: 13 sensible LSQ STEP-SIZE INITIALIZATION (a default scale of 1.0 quantizes the small init weights to 14 all-zeros -> a dead network at chance), per-tensor learnable scales, weight decay EXCLUDED from the 15 scale/BN/bias params, gradient clipping, a decaying LR, and enough epochs. Naive post-training 16 rounding or under-training lands at chance. 17 18 This file is a skeleton; the loop body is yours to write. 19 """ 20 from __future__ import annotations 21 22 import argparse 23 import os 24 import sys 25 26 import numpy as np 27 28 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 29 sys.path.insert(0, "/app") 30 3 …[truncated 1513 chars]
"""Reverse-mode automatic differentiation over numpy arrays."""
from __future__ import annotations
from typing import Callable, Iterable, List, Optional
import numpy as np
_bi_max = max
_bi_min = min
def _as_f64(x) -> np.ndarray:
return np.asarray(x, dtype=np.float64)
def _unbroadcast(grad: np.ndarray, shape: tuple) -> np.ndarray:
while grad.ndim > len(shape):
grad = grad.sum(axis=0)
for ax, sz in enumerate(shape):
if sz == 1 and grad.shape[ax] != 1:
grad = grad.sum(axis=ax, keepdims=True)
return grad.reshape(shape)
class Tensor:
__slots__ = ("data", "grad", "requires_grad", "_backward", "_parents")
def __init__(self, data, requires_grad: bool = False, _parents: Iterable["Tensor"] = ()):
self.data: np.ndarray = _as_f64(data)
self.grad: Optional[np.ndarray] = None
self.requires_grad: bool = bool(requires_grad)
self._backward: Callable[[], None] = lambda: None
self._parents: tuple = tuple(_parents)
@property
def shape(self):
return self.data.shape
@property
def ndim(self):
return self.data.ndim
def zero_grad(self):
self.grad = None
def _accum(self, g: np.ndarray):
if self.grad is None:
self.grad = np.zeros_like(self.data)
self.grad += g
def backward(self, grad: Optional[np.ndarray] = None):
if grad is None:
grad = np.ones_like(self.data)
self._accum(grad)
# B
…[truncated 48590 chars]The file /app/submission/autograd.py has been updated successfully. (file state is current in your context , no need to Read it back)
"""QAT layers + small INT2 CNN/MLP/SepCNN, built on autograd engine."""
from __future__ import annotations
from typing import Dict, List
import numpy as np
import autograd as ag
from autograd import Tensor
W_NEG, W_POS = 2, 1 # signed weights: {-2,-1,0,1}
A_NEG, A_POS = 0, 3 # unsigned activations (post-relu): {0,1,2,3}
class Module:
def params(self) -> List[Tensor]:
return list(self.named_params().values())
def named_params(self) -> Dict[str, Tensor]:
raise NotImplementedError
def buffers(self) -> Dict[str, np.ndarray]:
return {}
def __call__(self, *a, **k):
return self.forward(*a, **k)
def _kaiming(rng, fan_in, shape, gain=1.0):
return rng.standard_normal(shape) * (gain / np.sqrt(fan_in))
# ===================================================================== generic layers
class Linear(Module):
def __init__(self, n_in, n_out, rng, bias=True, gain=1.0):
self.weight = Tensor(_kaiming(rng, n_in, (n_in, n_out), gain), requires_grad=True)
self.bias = Tensor(np.zeros(n_out), requires_grad=True) if bias else None
def forward(self, x: Tensor) -> Tensor:
out = ag.matmul(x, self.weight)
if self.bias is not None:
out = ag.add(out, self.bias)
return out
def named_params(self):
d = {"weight": self.weight}
if self.bias is not None:
d["bias"] = self.bias
return d
class ReLU(Module):
def forward(self, x: Tens
…[truncated 25531 chars]The file /app/submission/nn.py has been updated successfully. (file state is current in your context , no need to Read it back)
"""Optimizers over autograd Tensor parameters."""
from __future__ import annotations
import math
from typing import Dict, List
import numpy as np
from autograd import Tensor
def split_decay_params(model) -> Dict[str, List[Tensor]]:
decay, no_decay = [], []
for name, p in model.named_params().items():
lname = name.lower()
if lname.endswith("scale") or "bn." in lname or lname.endswith("gamma") \
or lname.endswith("beta") or lname.endswith("bias"):
no_decay.append(p)
else:
decay.append(p)
return {"decay": decay, "no_decay": no_decay}
def _normalize_groups(params):
plist = list(params)
if plist and isinstance(plist[0], dict):
return [(list(g["params"]), g.get("weight_decay", None)) for g in plist]
return [(plist, None)]
class Optimizer:
def __init__(self, params):
self._groups = _normalize_groups(params)
self.params: List[Tensor] = [p for g, _ in self._groups for p in g]
def zero_grad(self):
for p in self.params:
p.grad = None
class SGD(Optimizer):
def __init__(self, params, lr=0.1, momentum=0.0, weight_decay=0.0):
super().__init__(params)
self.lr = lr
self.momentum = momentum
self.weight_decay = weight_decay
self._v = [np.zeros_like(p.data) for p in self.params]
def step(self):
idx = 0
for group, wd_override in self._groups:
wd = self.weight_decay if wd
…[truncated 10990 chars]The file /app/submission/optim.py has been updated successfully. (file state is current in your context , no need to Read it back)
"""Image data loading + normalization + batching."""
from __future__ import annotations
from typing import Tuple
import numpy as np
NORM_MEAN = 0.5
NORM_STD = 0.25
def load_npz(path: str) -> Tuple[np.ndarray, np.ndarray]:
d = np.load(path)
X = np.asarray(d["X"]).astype(np.uint8)
y = np.asarray(d["y"]).astype(np.int64).reshape(-1)
return X, y
def normalize(X: np.ndarray) -> np.ndarray:
return (X.astype(np.float64) / 255.0 - NORM_MEAN) / NORM_STD
def iter_minibatches(Xn: np.ndarray, y: np.ndarray, batch_size: int,
rng: np.random.Generator, shuffle: bool = True):
N = len(y)
idx = np.arange(N)
if shuffle:
rng.shuffle(idx)
for start in range(0, N, batch_size):
batch_idx = idx[start:start + batch_size]
yield Xn[batch_idx], y[batch_idx]
The file /app/submission/data.py has been updated successfully. (file state is current in your context , no need to Read it back)
Run checks to see current state
python3 run_checks.py 2>&1 | tail -30
optim/NesterovSGD_5step OK (max_abs=0.0e+00) optim/param_groups OK optim/split_decay_params OK optim/clip_grad_norm_clips OK (max_abs=0.0e+00) optim/clip_grad_norm_noop OK (max_abs=0.0e+00) optim/sched_cosine OK (max_abs=0.0e+00) optim/sched_warmup OK (max_abs=0.0e+00) optim/sched_step OK (max_abs=0.0e+00) optim/sched_exponential OK (max_abs=0.0e+00) optim/sched_polynomial OK (max_abs=0.0e+00) optim/sched_linear OK (max_abs=1.7e-18) optim/sched_multistep OK (max_abs=0.0e+00) optim/sched_cosine_restarts OK (max_abs=0.0e+00) [integration] 6/6 integration/qcnn_finite OK (1.787->1.018) integration/qcnn_loss_decreases OK (1.7869->min 1.0175) integration/qmlp_finite OK (1.981->0.237) integration/qmlp_loss_decreases OK (1.9807->min 0.2374) integration/qsepcnn_finite OK (1.925->1.655) integration/qsepcnn_loss_decreases OK (1.9249->min 1.6340) [data] 2/2 data/normalize_def OK data/iter_minibatches_cover OK (covered 23/23) ------------------------------------------------------------------------ autograd_grads 114/115 | quant_grads 21/21 | …[truncated 201 chars]
Find the failing check
python3 run_checks.py 2>&1 | grep FAIL
grad/gather FAIL (max_abs=1.00e+00) Fix the FAIL lines above (each is a backward/forward/optimizer bug). Re-run until all green, then train.
/app/submission/autograd.py
def gather(a, idx, axis: int) -> Tensor:
a = _ensure(a)
idx = np.asarray(idx)
out = _mk(np.take_along_axis(a.data, idx, axis=axis), (a,), a.requires_grad)
def _bw():
if a.requires_grad:
g = np.zeros_like(a.data)
np.add.at(g, _idx_tuple(idx, axis, a.data.ndim), out.grad)
a._accum(g)
out._backward = _bw
return out
def _idx_tuple(idx, axis, ndim):
"""Convert take_along_axis index to a tuple for np.add.at."""
slices = [slice(None)] * ndim
slices[axis] = idx
return tuple(slices)def gather(a, idx, axis: int) -> Tensor:
a = _ensure(a)
idx = np.asarray(idx)
out = _mk(np.take_along_axis(a.data, idx, axis=axis), (a,), a.requires_grad)
def _bw():
if a.requires_grad:
g = np.zeros_like(a.data)
# Build explicit indices for all dims: broadcast ranges for non-axis dims
ndim = a.data.ndim
indices = []
for ax in range(ndim):
if ax == axis:
indices.append(idx)
else:
shape = [1] * ndim
shape[ax] = a.data.shape[ax]
indices.append(np.broadcast_to(
np.arange(a.data.shape[ax]).reshape(shape), idx.shape))
np.add.at(g, tuple(indices), out.grad)
a._accum(g)
out._backward = _bw
return outThe file /app/submission/autograd.py has been updated successfully. (file state is current in your context , no need to Read it back)
Run checks to confirm all pass
python3 run_checks.py --quiet 2>&1
------------------------------------------------------------------------ autograd_grads 115/115 | quant_grads 21/21 | nn_forward 53/53 | optim 25/25 | integration 6/6 | data 2/2 TOTAL 222/222 public checks passing All public checks pass. Now train qat_model.npz to clear the held-out top-1 threshold (the grader re-checks components with DIFFERENT seeds).
"""Training entry point for QAT INT-2 CNN."""
from __future__ import annotations
import argparse
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, "/app")
import autograd as ag
import data as D
import optim as O
from nn import QuantCNN, W_NEG, W_POS
WIDTHS = (16, 32)
def init_lsq_scales(model, Xn, n_samples=256, rng=None):
"""Initialize LSQ step sizes from data/weight statistics."""
if rng is None:
rng = np.random.default_rng(0)
# For weight quantizers: scale = max(|w|) / W_POS
for name, p in model.named_params().items():
if name.endswith(".weight") and "wq" not in name:
# find the corresponding wq.scale
scale_key = name.replace(".weight", ".wq.scale")
params = model.named_params()
if scale_key in params:
w = p.data
s = np.max(np.abs(w)) / W_POS
params[scale_key].data[...] = max(s, 1e-4)
# For activation quantizers: run a few samples through the network to get activation stats
model.set_training(False)
idx = rng.choice(len(Xn), min(n_samples, len(Xn)), replace=False)
Xb = Xn[idx]
# Hook-based approach: run forward with identity quantizers temporarily
# Instead: run once without quantization to estimate activation ranges
# We'll just set act scales to a reasonable initial value
# Use 3.0 / A_POS as a reasonable init (covers ~3sigma of norma
…[truncated 4167 chars]The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
Train the model for 60 epochs
python3 submission/train.py --epochs 60 2>&1 | tail -25
(Bash completed with no output)
Train the model for 60 epochs with full output
python3 /app/submission/train.py --epochs 60 2>&1
Exit code 137 /bin/bash: line 1: 1512 Killed python3 /app/submission/train.py --epochs 60 2>&1
Time one forward/backward pass
timeout 30 python3 -c "
import sys
sys.path.insert(0, 'submission')
import numpy as np
import autograd as ag
import data as D
from nn import QuantCNN
X, y = D.load_npz('/app/data/train.npz')
Xn = D.normalize(X)
print('Dataset:', X.shape, y.shape, 'n_classes:', len(np.unique(y)))
model = QuantCNN(n_classes=10, widths=(16,32), seed=0)
# Time one forward pass
import time
rng = np.random.default_rng(0)
Xb = Xn[:16]
t0 = time.time()
x = ag.Tensor(Xb)
logits = model(x)
loss = ag.cross_entropy(logits, y[:16])
loss.backward()
t1 = time.time()
print('Forward+backward for batch=16:', t1-t0, 's')
print('loss:', float(loss.data))
" 2>&1Dataset: (2500, 3, 16, 16) (2500,) n_classes: 10 Forward+backward for batch=16: 0.06986355781555176 s loss: 2.302585092994046
Benchmark one epoch timing
timeout 60 python3 -c "
import sys, time
sys.path.insert(0, 'submission')
import numpy as np
import autograd as ag
import data as D
import optim as O
from nn import QuantCNN
rng = np.random.default_rng(0)
X, y = D.load_npz('/app/data/train.npz')
Xn = D.normalize(X)
model = QuantCNN(n_classes=10, widths=(16,32), seed=0)
# Init scales
for name, p in model.named_params().items():
if name.endswith('.weight') and 'wq' not in name:
scale_key = name.replace('.weight', '.wq.scale')
params = model.named_params()
if scale_key in params:
s = np.max(np.abs(p.data)) / 1.0
params[scale_key].data[...] = max(s, 1e-4)
if name.endswith('aq.scale'):
p.data[...] = 3.0/3.0
groups = O.split_decay_params(model)
param_groups = [
{'params': groups['decay'], 'weight_decay': 1e-4},
{'params': groups['no_decay'], 'weight_decay': 0.0},
]
optimizer = O.AdamW(param_groups, lr=1e-2, weight_decay=1e-4)
batch_size = 64
t0 = time.time()
for Xb, yb in D.iter_minibatches(Xn, y, batch_size, rng, shuffle=True):
optimizer.zero_grad()
x = ag.Tensor(Xb)
logits = model(x)
loss = ag.cross_entropy(logits, yb)
loss.backward()
O.clip_grad_norm(model.params(), max_norm=1.0)
optimizer.step()
t1 = time.time()
print(f'One epoch with batch=64: {t1-t0:.2f}s, n_batches={2500//64+1}')
print(f'Loss:', float(loss.data))
" 2>&1No file changes, the agent left the workspace untouched.
=== SyncVals verifier (local re-grade of the agent's own checkpoint) === COMPONENT CONFORMANCE SUITE: 276/278 cases passed (all milestones pass: False) failing milestones: integration (21/23) FINAL METRIC: held-out INT2 top-1 = 0.8947 >= threshold 0.7 REWARD = 0 (1 iff every component milestone passes AND the sealed held-out metric clears its threshold) NOTE: the in-sandbox training in the 481MB e2b box was OOM-killed (0-byte checkpoint), so the raw in-sandbox reward was an infrastructure artifact. This reward is from a faithful LOCAL re-train of the agent's OWN train.py (24GB, no OOM) with its own default hyperparameters, then re-graded by the real verifier.
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_d773b47c56b747e0. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_d773b47c56b747e0 · verifier authoritative; classifier explanatory.