随机小模型 next-token
在同一数据与明确预算下训练,A、B、C 哪个方案的token 交叉熵最低?图展示一个数据样本;答案按完整测试集、三个训练 seed 的成绩确定。
| 选项 | 实际候选与训练配置 |
|---|---|
| A | Transformer · 宽 16 · 深 1 · 残差开启 Adam,学习率=0.003;交叉熵;300 步 × 16 样本 候选模型源码from __future__ import annotations
import math
from typing import Any
import torch
from torch import nn
from torch.nn import functional as F
class CausalTransformerLM(nn.Module):
def __init__(
self,
vocab_size: int,
context_length: int,
d_model: int,
num_layers: int,
num_heads: int,
d_ff: int,
) -> None:
super().__init__()
self.context_length = context_length
self.token_embed = nn.Embedding(vocab_size, d_model)
self.pos_embed = nn.Embedding(context_length, d_model)
layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=num_heads,
dim_feedforward=d_ff,
dropout=0.0,
batch_first=True,
activation="gelu",
)
self.encoder = nn.TransformerEncoder(layer, num_layers=num_layers)
self.head = nn.Linear(d_model, vocab_size)
def forward(self, x: torch.Tensor) -> torch.Tensor:
b, seq_len = x.shape
positions = torch.arange(seq_len, device=x.device).unsqueeze(0).expand(b, -1)
h = self.token_embed(x) + self.pos_embed(positions)
mask = torch.triu(torch.ones(seq_len, seq_len, device=x.device), diagonal=1).bool()
h = self.encoder(h, mask=mask)
return self.head(h)
class TokenModel(nn.Module):
def __init__(self, model: nn.Module) -> None:
super().__init__()
self.model = model
def forward(self, x: torch.Tensor, **_: Any) -> torch.Tensor:
return self.model(x)
def _build_module(spec: dict[str, Any]) -> nn.Module:
kind = spec['mode']
ins, outs = tuple(spec['input_shape']), tuple(spec['output_shape'])
width, depth = int(spec['width']), int(spec['depth'])
if spec['output_kind'] == 'token':
vocab = int(spec['vocab_size'])
if kind in {'bigram', 'ngram'}:
return NgramLM(vocab, width, 1 if kind == 'bigram' else int(spec.get('order', 4)), depth, bool(spec['residual']))
if kind in {'gru', 'gru_lm'}:
return TokenModel(CausalGruLM(vocab, int(spec['context_length']), width, depth, bool(spec['residual'])))
if kind in {'transformer', 'transformer_lm'}:
return TokenModel(CausalTransformerLM(vocab, int(spec['context_length']), width, depth, 4, width * 2))
raise ValueError(f'Unknown token architecture: {kind}')
if kind == 'mlp':
return FlatMLP(ins, outs, width, depth, bool(spec['residual']))
if kind == 'coordinate_cnn':
return CoordinateCNN(ins, outs, width, depth, bool(spec['residual']))
if kind == 'clean_image_denoiser':
return CleanImageDenoiser(ins, outs, width, depth, int(spec['noise_steps']))
if kind in {'gnn', 'graph_transformer'}:
return PackedGraphModel(ins, outs, width, depth, 'graph_attention' if kind == 'graph_transformer' else 'message_passing')
if kind in {'gru', 'tcn', 'transformer'}:
sequence_kind = ('gru_forecaster' if kind == 'gru' else
'tcn_forecaster' if kind == 'tcn' else 'transformer_forecaster')
return SequenceForecaster(ins, outs, width, sequence_kind, depth)
if kind in {'cnn', 'residual_cnn', 'fno', 'spectral_conv'}:
spatial_kind = 'spectral_conv' if kind in {'fno', 'spectral_conv'} else 'residual_cnn' if kind == 'residual_cnn' else 'cnn'
return SpatialModel(ins, outs, width, depth, spatial_kind, residual=bool(spec['residual']))
if kind in {'vit', 'patch_transformer', 'field_transformer'}:
return PatchTransformer(ins, outs, width, depth, int(spec.get('patch_size', 4)))
raise ValueError(f'Unknown expansion architecture: {kind}')
def build_module(spec: dict[str, Any]) -> nn.Module:
model = _build_module(spec)
parameters = sum(p.numel() for p in model.parameters())
cap = int(spec.get('max_parameters', 2_000_000))
if parameters > cap:
raise ValueError(f'Candidate exceeds parameter cap: {parameters} > {cap}')
return model
SPEC = {'type': 'expansion_model', 'mode': 'transformer', 'width': 16, 'depth': 1, 'residual': True, 'max_parameters': 2000000, 'input_shape': [23], 'output_shape': [23], 'output_kind': 'token', 'vocab_size': 64, 'noise_steps': 100, 'context_length': 40}
class Model(nn.Module):
def __init__(self):
super().__init__()
self.net = build_module(SPEC)
def forward(self, x):
return self.net(x)
|
| B | Transformer · 宽 48 · 深 3 · 残差开启 Adam,学习率=0.003;交叉熵;300 步 × 16 样本 候选模型源码from __future__ import annotations
import math
from typing import Any
import torch
from torch import nn
from torch.nn import functional as F
class CausalTransformerLM(nn.Module):
def __init__(
self,
vocab_size: int,
context_length: int,
d_model: int,
num_layers: int,
num_heads: int,
d_ff: int,
) -> None:
super().__init__()
self.context_length = context_length
self.token_embed = nn.Embedding(vocab_size, d_model)
self.pos_embed = nn.Embedding(context_length, d_model)
layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=num_heads,
dim_feedforward=d_ff,
dropout=0.0,
batch_first=True,
activation="gelu",
)
self.encoder = nn.TransformerEncoder(layer, num_layers=num_layers)
self.head = nn.Linear(d_model, vocab_size)
def forward(self, x: torch.Tensor) -> torch.Tensor:
b, seq_len = x.shape
positions = torch.arange(seq_len, device=x.device).unsqueeze(0).expand(b, -1)
h = self.token_embed(x) + self.pos_embed(positions)
mask = torch.triu(torch.ones(seq_len, seq_len, device=x.device), diagonal=1).bool()
h = self.encoder(h, mask=mask)
return self.head(h)
class TokenModel(nn.Module):
def __init__(self, model: nn.Module) -> None:
super().__init__()
self.model = model
def forward(self, x: torch.Tensor, **_: Any) -> torch.Tensor:
return self.model(x)
def _build_module(spec: dict[str, Any]) -> nn.Module:
kind = spec['mode']
ins, outs = tuple(spec['input_shape']), tuple(spec['output_shape'])
width, depth = int(spec['width']), int(spec['depth'])
if spec['output_kind'] == 'token':
vocab = int(spec['vocab_size'])
if kind in {'bigram', 'ngram'}:
return NgramLM(vocab, width, 1 if kind == 'bigram' else int(spec.get('order', 4)), depth, bool(spec['residual']))
if kind in {'gru', 'gru_lm'}:
return TokenModel(CausalGruLM(vocab, int(spec['context_length']), width, depth, bool(spec['residual'])))
if kind in {'transformer', 'transformer_lm'}:
return TokenModel(CausalTransformerLM(vocab, int(spec['context_length']), width, depth, 4, width * 2))
raise ValueError(f'Unknown token architecture: {kind}')
if kind == 'mlp':
return FlatMLP(ins, outs, width, depth, bool(spec['residual']))
if kind == 'coordinate_cnn':
return CoordinateCNN(ins, outs, width, depth, bool(spec['residual']))
if kind == 'clean_image_denoiser':
return CleanImageDenoiser(ins, outs, width, depth, int(spec['noise_steps']))
if kind in {'gnn', 'graph_transformer'}:
return PackedGraphModel(ins, outs, width, depth, 'graph_attention' if kind == 'graph_transformer' else 'message_passing')
if kind in {'gru', 'tcn', 'transformer'}:
sequence_kind = ('gru_forecaster' if kind == 'gru' else
'tcn_forecaster' if kind == 'tcn' else 'transformer_forecaster')
return SequenceForecaster(ins, outs, width, sequence_kind, depth)
if kind in {'cnn', 'residual_cnn', 'fno', 'spectral_conv'}:
spatial_kind = 'spectral_conv' if kind in {'fno', 'spectral_conv'} else 'residual_cnn' if kind == 'residual_cnn' else 'cnn'
return SpatialModel(ins, outs, width, depth, spatial_kind, residual=bool(spec['residual']))
if kind in {'vit', 'patch_transformer', 'field_transformer'}:
return PatchTransformer(ins, outs, width, depth, int(spec.get('patch_size', 4)))
raise ValueError(f'Unknown expansion architecture: {kind}')
def build_module(spec: dict[str, Any]) -> nn.Module:
model = _build_module(spec)
parameters = sum(p.numel() for p in model.parameters())
cap = int(spec.get('max_parameters', 2_000_000))
if parameters > cap:
raise ValueError(f'Candidate exceeds parameter cap: {parameters} > {cap}')
return model
SPEC = {'type': 'expansion_model', 'mode': 'transformer', 'width': 48, 'depth': 3, 'residual': True, 'max_parameters': 2000000, 'input_shape': [23], 'output_shape': [23], 'output_kind': 'token', 'vocab_size': 64, 'noise_steps': 100, 'context_length': 40}
class Model(nn.Module):
def __init__(self):
super().__init__()
self.net = build_module(SPEC)
def forward(self, x):
return self.net(x)
|
| C | ngram · 宽 32 · 深 3 · 残差关闭 Adam,学习率=0.003;交叉熵;300 步 × 16 样本 候选模型源码from __future__ import annotations
import math
from typing import Any
import torch
from torch import nn
from torch.nn import functional as F
class NgramLM(nn.Module):
def __init__(self, vocab: int, width: int, order: int, depth: int = 1,
residual: bool = False) -> None:
super().__init__()
self.embedding = nn.Embedding(vocab, width)
self.mix = nn.ModuleList(nn.Conv1d(width, width, order) for _ in range(depth))
self.head = nn.Linear(width, vocab)
self.order = order
self.residual = residual
def forward(self, x: torch.Tensor, **_: Any) -> torch.Tensor:
h = self.embedding(x).transpose(1, 2)
for layer in self.mix:
z = F.gelu(layer(F.pad(h, (self.order - 1, 0))))
h = h + z if self.residual else z
return self.head(h.transpose(1, 2))
def _build_module(spec: dict[str, Any]) -> nn.Module:
kind = spec['mode']
ins, outs = tuple(spec['input_shape']), tuple(spec['output_shape'])
width, depth = int(spec['width']), int(spec['depth'])
if spec['output_kind'] == 'token':
vocab = int(spec['vocab_size'])
if kind in {'bigram', 'ngram'}:
return NgramLM(vocab, width, 1 if kind == 'bigram' else int(spec.get('order', 4)), depth, bool(spec['residual']))
if kind in {'gru', 'gru_lm'}:
return TokenModel(CausalGruLM(vocab, int(spec['context_length']), width, depth, bool(spec['residual'])))
if kind in {'transformer', 'transformer_lm'}:
return TokenModel(CausalTransformerLM(vocab, int(spec['context_length']), width, depth, 4, width * 2))
raise ValueError(f'Unknown token architecture: {kind}')
if kind == 'mlp':
return FlatMLP(ins, outs, width, depth, bool(spec['residual']))
if kind == 'coordinate_cnn':
return CoordinateCNN(ins, outs, width, depth, bool(spec['residual']))
if kind == 'clean_image_denoiser':
return CleanImageDenoiser(ins, outs, width, depth, int(spec['noise_steps']))
if kind in {'gnn', 'graph_transformer'}:
return PackedGraphModel(ins, outs, width, depth, 'graph_attention' if kind == 'graph_transformer' else 'message_passing')
if kind in {'gru', 'tcn', 'transformer'}:
sequence_kind = ('gru_forecaster' if kind == 'gru' else
'tcn_forecaster' if kind == 'tcn' else 'transformer_forecaster')
return SequenceForecaster(ins, outs, width, sequence_kind, depth)
if kind in {'cnn', 'residual_cnn', 'fno', 'spectral_conv'}:
spatial_kind = 'spectral_conv' if kind in {'fno', 'spectral_conv'} else 'residual_cnn' if kind == 'residual_cnn' else 'cnn'
return SpatialModel(ins, outs, width, depth, spatial_kind, residual=bool(spec['residual']))
if kind in {'vit', 'patch_transformer', 'field_transformer'}:
return PatchTransformer(ins, outs, width, depth, int(spec.get('patch_size', 4)))
raise ValueError(f'Unknown expansion architecture: {kind}')
def build_module(spec: dict[str, Any]) -> nn.Module:
model = _build_module(spec)
parameters = sum(p.numel() for p in model.parameters())
cap = int(spec.get('max_parameters', 2_000_000))
if parameters > cap:
raise ValueError(f'Candidate exceeds parameter cap: {parameters} > {cap}')
return model
SPEC = {'type': 'expansion_model', 'mode': 'ngram', 'width': 32, 'depth': 3, 'residual': False, 'max_parameters': 2000000, 'input_shape': [23], 'output_shape': [23], 'output_kind': 'token', 'vocab_size': 64, 'noise_steps': 100, 'context_length': 40}
class Model(nn.Module):
def __init__(self):
super().__init__()
self.net = build_module(SPEC)
def forward(self, x):
return self.net(x)
|
揭晓答案与实测依据
正确答案:A。按 test_ce 比较,越低越好。以下是整体测试成绩,不是图中单个样本的误差。
| 选项 | 三 seed 均值 | 各 seed 成绩 |
|---|---|---|
| A | 3.48457 | 3.48056,3.48245,3.4907 |
| B | 4.0226 | 3.95094,4.12905,3.98782 |
| C | 3.64724 | 3.61735,3.69692,3.62744 |
完整冻结题面
You are taking the ArchitectureIQ benchmark.
Each question describes one **dataset instance** and several **choices** (each choice is one **candidate**: model, optimizer, loss, and training budget). All choices train on the same dataset instance. Each choice's training budget (`total_samples_seen`) is stated in the prompt — either shared across all choices or specified per choice in the Sample budget section. Your task is to identify which choice will achieve the **best test metric** on the held-out test set after its stated training budget — using architecture and learning-mechanics reasoning only (no training results are provided).
The reply format is defined once, in the final "Your answer" section.
## Dataset
Random frozen GRU language modeling: independent random token prefixes are continued by a single frozen, calibrated neural teacher. Predict next-token distributions only at supervised continuation positions. The teacher is shared across all splits; token targets of -100 are ignored.
### Synthesis (PyTorch)
```python
from __future__ import annotations
import hashlib
import math
from typing import Any
import torch
import torch.nn.functional as F
def _seed(seed: int, task: str, stream: str) -> int:
return int.from_bytes(hashlib.sha256(f'{seed}|{task}|{stream}'.encode()).digest()[:8], 'little') % (2 ** 63 - 1)
class FrozenTeacher(torch.nn.Module):
def __init__(self, config: dict, state: dict | None=None):
super().__init__()
self.net = TeacherNet(config['vocab_size'], config['teacher_width'], config['teacher_layers'])
if state is not None:
self.net.load_state_dict(state)
self.scale = float(config['logit_scale']) / float(config['temperature'])
self.requires_grad_(False)
self.eval()
def forward(self, x):
return self.net(x) * self.scale
class TeacherNet(torch.nn.Module):
def __init__(self, vocab, width, layers):
super().__init__()
self.token_embed = torch.nn.Embedding(vocab, width)
self.gru = torch.nn.GRU(width, width, layers, batch_first=True)
self.head = torch.nn.Linear(width, vocab)
def forward(self, x):
(h, _) = self.gru(self.token_embed(x))
return self.head(h)
def _teacher_split(teacher, config, n, rng, prefix_length, continuation):
seq = torch.randint(config['vocab_size'], (n, prefix_length), generator=rng)
for _ in range(continuation):
with torch.no_grad():
probs = teacher(seq)[:, -1].softmax(-1)
seq = torch.cat([seq, torch.multinomial(probs, 1, generator=rng)], 1)
(x, y) = (seq[:, :-1], seq[:, 1:])
with torch.no_grad():
p = teacher(x).softmax(-1)
mask = torch.zeros_like(x, dtype=torch.bool)
mask[:, prefix_length - 1:] = True
return {'x': x, 'y': y, 'mask': mask, 'teacher_probs': p}
def build(task_id: str, parameters: dict[str, Any], split_counts: dict[str, int], seed: int, data_seed: int | None=None):
cfg = {'task_id': 'random_teacher_ntp'}
(artifacts, metadata) = ({}, {})
generator = torch.Generator().manual_seed(_seed(seed, 'random_teacher_ntp', 'generator'))
teacher_cfg = parameters.get('teacher', {})
prefix = int(parameters.get('prefix_length', min(parameters.get('prefix_lengths', [8]))))
continuation = int(parameters.get('continuation_length', parameters.get('rollout_length', min(parameters.get('continuation_lengths', [16])))))
cfg.update(vocab_size=int(parameters.get('vocab', 64)), prefix_length=prefix, continuation_length=continuation, rollout_length=continuation, context_length=prefix + 2 * continuation, teacher_width=int(teacher_cfg.get('width', 64)), teacher_layers=int(teacher_cfg.get('layers', 2)), temperature=float(teacher_cfg.get('temperature', 1.0)), logit_scale=float(teacher_cfg.get('logit_scale', parameters.get('logit_scale', 8.0))))
with torch.random.fork_rng():
torch.manual_seed(_seed(seed, 'random_teacher_ntp', 'teacher_weights'))
teacher = FrozenTeacher(cfg)
probe = torch.randint(cfg['vocab_size'], (128, int(parameters.get('teacher_probe_context', 8))), generator=generator)
with torch.no_grad():
for _ in range(12):
p = teacher(probe).softmax(-1)[:, -1]
entropy = float(-(p * p.clamp_min(1e-12).log()).sum(-1).mean())
ratio = entropy / math.log(cfg['vocab_size'])
if 0.35 <= ratio <= 0.88:
break
teacher.scale *= 1.5 if ratio > 0.88 else 0.7
reset_p = teacher(probe[:, -1:]).softmax(-1)[:, -1]
context_gap = float((p * (p.clamp_min(1e-12).log() - reset_p.clamp_min(1e-12).log())).sum(-1).mean())
if not 0.35 <= ratio <= 0.88 or context_gap < 0.001:
raise ValueError('Random teacher has insufficient entropy or context dependence')
cfg['logit_scale'] = teacher.scale * cfg['temperature']
artifacts['teacher_state'] = {k: v.detach().clone() for (k, v) in teacher.net.state_dict().items()}
metadata.update(teacher_probe_entropy=entropy, teacher_context_kl_gap=context_gap, teacher_architecture='frozen_random_causal_gru', teacher_seed=_seed(seed, 'random_teacher_ntp', 'teacher_weights'), teacher_probe_scope='generator_only_not_test_selection')
cfg.update(selection_metric='token_ce', higher_is_better=False)
splits = {}
seen_grammar = set()
for (split, count) in split_counts.items():
rng = torch.Generator().manual_seed(_seed(seed, 'random_teacher_ntp', split) if data_seed is None else data_seed)
ood = split == 'ood'
splits[split] = _teacher_split(teacher, cfg, count, rng, prefix, continuation * (2 if ood else 1))
metadata.update(split_unit='whole_independently_sampled_sequence', seed_streams={s: _seed(seed, 'random_teacher_ntp', s) for s in ['generator', *split_counts]}, ood_axis={'random_teacher_ntp': 'continuation_length_x2', 'on_policy_distillation': 'continuation_length_x2', 'variable_order_markov': 'sequence_length_x2', 'copy_recall': 'distractor_length_x2', 'grammar_pretrain': 'terminal_frequency_shift', 'switching_state_space': 'switch_probability_x2', 'delay_dynamics': 'noise_scale_x1.8'}['random_teacher_ntp'])
cfg['input_shape'] = list(splits['train']['x'].shape[1:])
cfg['output_shape'] = list(splits['train']['y'].shape[1:])
if 'vocab_size' in cfg:
cfg['max_sequence_length'] = max((int(b['x'].shape[1]) for b in splits.values()))
metadata['split_sizes'] = {s: len(b['x']) for (s, b) in splits.items()}
return {'config': cfg, 'splits': splits, 'metadata': metadata, 'artifacts': artifacts}
def _canonical_parameters(params):
p = dict(params)
p['vocab'] = p.get('vocab_size', 64)
p['sequence_length'] = p.get('context_length', 128) + (0 if p.get('task_id') == 'grammar_pretrain' else 1)
p['teacher'] = {'width': p.get('teacher_width', 64), 'layers': p.get('teacher_layers', 2), 'temperature': p.get('temperature', 1.0), 'logit_scale': p.get('logit_scale', 8.0)}
return p
def synthesize_task(name, params, seed, count):
parameters = _canonical_parameters(dict(params, task_id='random_teacher_ntp'))
dataset = build('random_teacher_ntp', parameters, {'train': count}, int(params['structure_seed']), data_seed=seed)
batch = dataset['splits']['train']
batch['y'] = batch['y'].clone()
batch['y'][~batch['mask']] = -100
return batch
TASK = 'random_teacher_ntp'
PARAMS = {'vocab_size': 64, 'prefix_length': 8, 'continuation_length': 16, 'context_length': 40, 'teacher_width': 64, 'teacher_layers': 2, 'temperature': 1.0, 'logit_scale': 8.0, 'train_size': 512, 'test_size': 128, 'validation_size': 128, 'ood_size': 128, 'instance_seed': 11, 'structure_seed': 10011, 'teacher_seed': 10011, 'ood_shift': {'prefix_length': 16, 'continuation_length': 8}, 'input_shape': [23], 'output_shape': [23], 'model_modes': ['bigram', 'ngram', 'gru', 'transformer'], 'objective': 'token_ce', 'output_kind': 'token'}
def synthesize_split(seed, count, overrides=None):
global PARAMS
original = PARAMS
PARAMS = dict(PARAMS, **overrides or {})
try:
return synthesize_task(TASK, PARAMS, seed, count)
finally:
PARAMS = original
def synthesize():
tr = synthesize_split(PARAMS['instance_seed'] + 20000, PARAMS['train_size'])
te = synthesize_split(PARAMS['instance_seed'] + 40000, PARAMS['test_size'])
return (tr['x'], tr['y'], te['x'], te['y'])
```
### Data splits and training protocol
- Train / validation / test size: 512 / 128 / 128
- Split by complete sequences, trajectories, graphs or initial conditions; no overlapping split windows.
- Objective: token_ce; model input/output shapes: [23] → [23].
- Curves use validation. Final ranking uses held-out test without selecting checkpoints.
- Execution device: cpu.
## Sample budget (same for all choices)
- training_steps: 300
- batch_size: 16
- total_samples_seen: 4800 (= training_steps × batch_size)
## Evaluation metric
- Ground-truth ranking uses **test_ce** on the held-out test split.
- Each choice is trained independently for **3** seeds (`0`..`2`), one `torch.manual_seed(seed)` per run.
- Execution device: cpu.
- The correct choice has the lowest **mean** test_ce across seeds.
## Choices
### Choice A
**Model (natural language)**
- type: expansion_model
- mode: transformer
- width: 16
- depth: 1
- residual: True
- max_parameters: 2000000
- input_shape: [23]
- output_shape: [23]
- output_kind: token
- vocab_size: 64
- noise_steps: 100
- context_length: 40
**Model code**
```python
SPEC = {'type': 'expansion_model', 'mode': 'transformer', 'width': 16, 'depth': 1, 'residual': True, 'max_parameters': 2000000, 'input_shape': [23], 'output_shape': [23], 'output_kind': 'token', 'vocab_size': 64, 'noise_steps': 100, 'context_length': 40}
def _build_module(spec: dict[str, Any]) -> nn.Module:
kind = spec['mode']
ins, outs = tuple(spec['input_shape']), tuple(spec['output_shape'])
width, depth = int(spec['width']), int(spec['depth'])
if spec['output_kind'] == 'token':
vocab = int(spec['vocab_size'])
if kind in {'bigram', 'ngram'}:
return NgramLM(vocab, width, 1 if kind == 'bigram' else int(spec.get('order', 4)), depth, bool(spec['residual']))
if kind in {'gru', 'gru_lm'}:
return TokenModel(CausalGruLM(vocab, int(spec['context_length']), width, depth, bool(spec['residual'])))
if kind in {'transformer', 'transformer_lm'}:
return TokenModel(CausalTransformerLM(vocab, int(spec['context_length']), width, depth, 4, width * 2))
raise ValueError(f'Unknown token architecture: {kind}')
if kind == 'mlp':
return FlatMLP(ins, outs, width, depth, bool(spec['residual']))
if kind == 'coordinate_cnn':
return CoordinateCNN(ins, outs, width, depth, bool(spec['residual']))
if kind == 'clean_image_denoiser':
return CleanImageDenoiser(ins, outs, width, depth, int(spec['noise_steps']))
if kind in {'gnn', 'graph_transformer'}:
return PackedGraphModel(ins, outs, width, depth, 'graph_attention' if kind == 'graph_transformer' else 'message_passing')
if kind in {'gru', 'tcn', 'transformer'}:
sequence_kind = ('gru_forecaster' if kind == 'gru' else
'tcn_forecaster' if kind == 'tcn' else 'transformer_forecaster')
return SequenceForecaster(ins, outs, width, sequence_kind, depth)
if kind in {'cnn', 'residual_cnn', 'fno', 'spectral_conv'}:
spatial_kind = 'spectral_conv' if kind in {'fno', 'spectral_conv'} else 'residual_cnn' if kind == 'residual_cnn' else 'cnn'
return SpatialModel(ins, outs, width, depth, spatial_kind, residual=bool(spec['residual']))
if kind in {'vit', 'patch_transformer', 'field_transformer'}:
return PatchTransformer(ins, outs, width, depth, int(spec.get('patch_size', 4)))
raise ValueError(f'Unknown expansion architecture: {kind}')
def build_module(spec: dict[str, Any]) -> nn.Module:
model = _build_module(spec)
parameters = sum(p.numel() for p in model.parameters())
cap = int(spec.get('max_parameters', 2_000_000))
if parameters > cap:
raise ValueError(f'Candidate exceeds parameter cap: {parameters} > {cap}')
return model
class CausalTransformerLM(nn.Module):
def __init__(
self,
vocab_size: int,
context_length: int,
d_model: int,
num_layers: int,
num_heads: int,
d_ff: int,
) -> None:
super().__init__()
self.context_length = context_length
self.token_embed = nn.Embedding(vocab_size, d_model)
self.pos_embed = nn.Embedding(context_length, d_model)
layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=num_heads,
dim_feedforward=d_ff,
dropout=0.0,
batch_first=True,
activation="gelu",
)
self.encoder = nn.TransformerEncoder(layer, num_layers=num_layers)
self.head = nn.Linear(d_model, vocab_size)
def forward(self, x: torch.Tensor) -> torch.Tensor:
b, seq_len = x.shape
positions = torch.arange(seq_len, device=x.device).unsqueeze(0).expand(b, -1)
h = self.token_embed(x) + self.pos_embed(positions)
mask = torch.triu(torch.ones(seq_len, seq_len, device=x.device), diagonal=1).bool()
h = self.encoder(h, mask=mask)
return self.head(h)
class TokenModel(nn.Module):
def __init__(self, model: nn.Module) -> None:
super().__init__()
self.model = model
def forward(self, x: torch.Tensor, **_: Any) -> torch.Tensor:
return self.model(x)
class Model(nn.Module):
def __init__(self):
super().__init__()
self.net = build_module(SPEC)
def forward(self, x):
return self.net(x)
```
**Optimizer**
- Optimizer: Adam
- Learning rate: 0.003
- Weight decay: 0.0
- Betas: [0.9, 0.999]
```python
def build_optimizer(model: nn.Module) -> torch.optim.Optimizer:
params = model.parameters()
return torch.optim.Adam(params, lr=0.003, betas=(0.9, 0.999), weight_decay=0.0)
```
**Loss**
- Loss: cross-entropy on minibatch target labels
```python
def loss_fn(model: nn.Module, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
if pred.ndim == 3:
vocab = pred.shape[-1]
return torch.nn.functional.cross_entropy(pred.reshape(-1, vocab), target.reshape(-1))
return torch.nn.functional.cross_entropy(pred, target.reshape(-1))
```
### Choice B
**Model (natural language)**
- type: expansion_model
- mode: transformer
- width: 48
- depth: 3
- residual: True
- max_parameters: 2000000
- input_shape: [23]
- output_shape: [23]
- output_kind: token
- vocab_size: 64
- noise_steps: 100
- context_length: 40
**Model code**
```python
SPEC = {'type': 'expansion_model', 'mode': 'transformer', 'width': 48, 'depth': 3, 'residual': True, 'max_parameters': 2000000, 'input_shape': [23], 'output_shape': [23], 'output_kind': 'token', 'vocab_size': 64, 'noise_steps': 100, 'context_length': 40}
def _build_module(spec: dict[str, Any]) -> nn.Module:
kind = spec['mode']
ins, outs = tuple(spec['input_shape']), tuple(spec['output_shape'])
width, depth = int(spec['width']), int(spec['depth'])
if spec['output_kind'] == 'token':
vocab = int(spec['vocab_size'])
if kind in {'bigram', 'ngram'}:
return NgramLM(vocab, width, 1 if kind == 'bigram' else int(spec.get('order', 4)), depth, bool(spec['residual']))
if kind in {'gru', 'gru_lm'}:
return TokenModel(CausalGruLM(vocab, int(spec['context_length']), width, depth, bool(spec['residual'])))
if kind in {'transformer', 'transformer_lm'}:
return TokenModel(CausalTransformerLM(vocab, int(spec['context_length']), width, depth, 4, width * 2))
raise ValueError(f'Unknown token architecture: {kind}')
if kind == 'mlp':
return FlatMLP(ins, outs, width, depth, bool(spec['residual']))
if kind == 'coordinate_cnn':
return CoordinateCNN(ins, outs, width, depth, bool(spec['residual']))
if kind == 'clean_image_denoiser':
return CleanImageDenoiser(ins, outs, width, depth, int(spec['noise_steps']))
if kind in {'gnn', 'graph_transformer'}:
return PackedGraphModel(ins, outs, width, depth, 'graph_attention' if kind == 'graph_transformer' else 'message_passing')
if kind in {'gru', 'tcn', 'transformer'}:
sequence_kind = ('gru_forecaster' if kind == 'gru' else
'tcn_forecaster' if kind == 'tcn' else 'transformer_forecaster')
return SequenceForecaster(ins, outs, width, sequence_kind, depth)
if kind in {'cnn', 'residual_cnn', 'fno', 'spectral_conv'}:
spatial_kind = 'spectral_conv' if kind in {'fno', 'spectral_conv'} else 'residual_cnn' if kind == 'residual_cnn' else 'cnn'
return SpatialModel(ins, outs, width, depth, spatial_kind, residual=bool(spec['residual']))
if kind in {'vit', 'patch_transformer', 'field_transformer'}:
return PatchTransformer(ins, outs, width, depth, int(spec.get('patch_size', 4)))
raise ValueError(f'Unknown expansion architecture: {kind}')
def build_module(spec: dict[str, Any]) -> nn.Module:
model = _build_module(spec)
parameters = sum(p.numel() for p in model.parameters())
cap = int(spec.get('max_parameters', 2_000_000))
if parameters > cap:
raise ValueError(f'Candidate exceeds parameter cap: {parameters} > {cap}')
return model
class CausalTransformerLM(nn.Module):
def __init__(
self,
vocab_size: int,
context_length: int,
d_model: int,
num_layers: int,
num_heads: int,
d_ff: int,
) -> None:
super().__init__()
self.context_length = context_length
self.token_embed = nn.Embedding(vocab_size, d_model)
self.pos_embed = nn.Embedding(context_length, d_model)
layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=num_heads,
dim_feedforward=d_ff,
dropout=0.0,
batch_first=True,
activation="gelu",
)
self.encoder = nn.TransformerEncoder(layer, num_layers=num_layers)
self.head = nn.Linear(d_model, vocab_size)
def forward(self, x: torch.Tensor) -> torch.Tensor:
b, seq_len = x.shape
positions = torch.arange(seq_len, device=x.device).unsqueeze(0).expand(b, -1)
h = self.token_embed(x) + self.pos_embed(positions)
mask = torch.triu(torch.ones(seq_len, seq_len, device=x.device), diagonal=1).bool()
h = self.encoder(h, mask=mask)
return self.head(h)
class TokenModel(nn.Module):
def __init__(self, model: nn.Module) -> None:
super().__init__()
self.model = model
def forward(self, x: torch.Tensor, **_: Any) -> torch.Tensor:
return self.model(x)
class Model(nn.Module):
def __init__(self):
super().__init__()
self.net = build_module(SPEC)
def forward(self, x):
return self.net(x)
```
**Optimizer**
- Optimizer: Adam
- Learning rate: 0.003
- Weight decay: 0.0
- Betas: [0.9, 0.999]
```python
def build_optimizer(model: nn.Module) -> torch.optim.Optimizer:
params = model.parameters()
return torch.optim.Adam(params, lr=0.003, betas=(0.9, 0.999), weight_decay=0.0)
```
**Loss**
- Loss: cross-entropy on minibatch target labels
```python
def loss_fn(model: nn.Module, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
if pred.ndim == 3:
vocab = pred.shape[-1]
return torch.nn.functional.cross_entropy(pred.reshape(-1, vocab), target.reshape(-1))
return torch.nn.functional.cross_entropy(pred, target.reshape(-1))
```
### Choice C
**Model (natural language)**
- type: expansion_model
- mode: ngram
- width: 32
- depth: 3
- residual: False
- max_parameters: 2000000
- input_shape: [23]
- output_shape: [23]
- output_kind: token
- vocab_size: 64
- noise_steps: 100
- context_length: 40
**Model code**
```python
SPEC = {'type': 'expansion_model', 'mode': 'ngram', 'width': 32, 'depth': 3, 'residual': False, 'max_parameters': 2000000, 'input_shape': [23], 'output_shape': [23], 'output_kind': 'token', 'vocab_size': 64, 'noise_steps': 100, 'context_length': 40}
def _build_module(spec: dict[str, Any]) -> nn.Module:
kind = spec['mode']
ins, outs = tuple(spec['input_shape']), tuple(spec['output_shape'])
width, depth = int(spec['width']), int(spec['depth'])
if spec['output_kind'] == 'token':
vocab = int(spec['vocab_size'])
if kind in {'bigram', 'ngram'}:
return NgramLM(vocab, width, 1 if kind == 'bigram' else int(spec.get('order', 4)), depth, bool(spec['residual']))
if kind in {'gru', 'gru_lm'}:
return TokenModel(CausalGruLM(vocab, int(spec['context_length']), width, depth, bool(spec['residual'])))
if kind in {'transformer', 'transformer_lm'}:
return TokenModel(CausalTransformerLM(vocab, int(spec['context_length']), width, depth, 4, width * 2))
raise ValueError(f'Unknown token architecture: {kind}')
if kind == 'mlp':
return FlatMLP(ins, outs, width, depth, bool(spec['residual']))
if kind == 'coordinate_cnn':
return CoordinateCNN(ins, outs, width, depth, bool(spec['residual']))
if kind == 'clean_image_denoiser':
return CleanImageDenoiser(ins, outs, width, depth, int(spec['noise_steps']))
if kind in {'gnn', 'graph_transformer'}:
return PackedGraphModel(ins, outs, width, depth, 'graph_attention' if kind == 'graph_transformer' else 'message_passing')
if kind in {'gru', 'tcn', 'transformer'}:
sequence_kind = ('gru_forecaster' if kind == 'gru' else
'tcn_forecaster' if kind == 'tcn' else 'transformer_forecaster')
return SequenceForecaster(ins, outs, width, sequence_kind, depth)
if kind in {'cnn', 'residual_cnn', 'fno', 'spectral_conv'}:
spatial_kind = 'spectral_conv' if kind in {'fno', 'spectral_conv'} else 'residual_cnn' if kind == 'residual_cnn' else 'cnn'
return SpatialModel(ins, outs, width, depth, spatial_kind, residual=bool(spec['residual']))
if kind in {'vit', 'patch_transformer', 'field_transformer'}:
return PatchTransformer(ins, outs, width, depth, int(spec.get('patch_size', 4)))
raise ValueError(f'Unknown expansion architecture: {kind}')
def build_module(spec: dict[str, Any]) -> nn.Module:
model = _build_module(spec)
parameters = sum(p.numel() for p in model.parameters())
cap = int(spec.get('max_parameters', 2_000_000))
if parameters > cap:
raise ValueError(f'Candidate exceeds parameter cap: {parameters} > {cap}')
return model
class NgramLM(nn.Module):
def __init__(self, vocab: int, width: int, order: int, depth: int = 1,
residual: bool = False) -> None:
super().__init__()
self.embedding = nn.Embedding(vocab, width)
self.mix = nn.ModuleList(nn.Conv1d(width, width, order) for _ in range(depth))
self.head = nn.Linear(width, vocab)
self.order = order
self.residual = residual
def forward(self, x: torch.Tensor, **_: Any) -> torch.Tensor:
h = self.embedding(x).transpose(1, 2)
for layer in self.mix:
z = F.gelu(layer(F.pad(h, (self.order - 1, 0))))
h = h + z if self.residual else z
return self.head(h.transpose(1, 2))
class Model(nn.Module):
def __init__(self):
super().__init__()
self.net = build_module(SPEC)
def forward(self, x):
return self.net(x)
```
**Optimizer**
- Optimizer: Adam
- Learning rate: 0.003
- Weight decay: 0.0
- Betas: [0.9, 0.999]
```python
def build_optimizer(model: nn.Module) -> torch.optim.Optimizer:
params = model.parameters()
return torch.optim.Adam(params, lr=0.003, betas=(0.9, 0.999), weight_decay=0.0)
```
**Loss**
- Loss: cross-entropy on minibatch target labels
```python
def loss_fn(model: nn.Module, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
if pred.ndim == 3:
vocab = pred.shape[-1]
return torch.nn.functional.cross_entropy(pred.reshape(-1, vocab), target.reshape(-1))
return torch.nn.functional.cross_entropy(pred, target.reshape(-1))
```
## Your answer
Choose exactly one of A, B, C. End your reply with exactly one tagged field, with nothing after it:
<answer>the letter of your chosen option</answer>
Rules: `<answer>` must contain exactly one letter from {A, B, C}. The grader reads only the **final** `<answer>` tag in your reply.