melephant commited on
Commit
f8ccbd6
·
verified ·
1 Parent(s): a2f01f9

Publish addition-transformer run rmb41c76

Browse files

Loadable model and complete training record.

README.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ pipeline_tag: text-generation
4
+ tags:
5
+ - arithmetic
6
+ - interpretability
7
+ - arxiv:2405.14813
8
+ ---
9
+
10
+ # Fixed-width addition transformer
11
+
12
+ Run `rmb41c76` is a 1-block, bias-free causal transformer trained for
13
+ 4-digit base-10 addition. Operands are zero-padded and answers use
14
+ 5 digits, retaining overflow.
15
+
16
+ ## Results
17
+
18
+ | Metric | Value |
19
+ | --- | ---: |
20
+ | Validation loss | 0.014791 |
21
+ | Validation generated-token accuracy | 99.38% |
22
+ | Validation exact-answer accuracy | 97.46% |
23
+ | No-carry exact-answer accuracy | 95.70% |
24
+ | Single-carry exact-answer accuracy | 99.22% |
25
+ | Multiple-carry exact-answer accuracy | 97.27% |
26
+ | Carry-chain exact-answer accuracy | 89.45% |
27
+
28
+ ## Training configuration
29
+
30
+ - Updates: 10000
31
+ - Optimizer: muon
32
+ - Muon peak learning rate: 0.02
33
+ - AdamW peak learning rate: 0.0003
34
+ - Weight decay: 0.01
35
+ - Warmup updates: 100
36
+ - Minimum learning-rate ratio: 0.1
37
+ - Initialization: normal
38
+ - Position encoding: full-head RoPE on queries and keys (theta 10000)
39
+ - Seed: 0
40
+ - Source commit: `unavailable`
41
+
42
+ The complete resolved configuration, environment, metrics, source snapshot, and checkpoints are
43
+ available in [`training/`](./training/). Machine-readable hashes and metrics are in
44
+ [`export_manifest.json`](./export_manifest.json).
45
+
46
+ ## Loading
47
+
48
+ This repository contains custom Transformers code. For reproducible or security-sensitive use,
49
+ pin the commit revision printed by the uploader.
50
+
51
+ ```python
52
+ from transformers import AutoModelForCausalLM, AutoTokenizer
53
+
54
+ revision = "PINNED_COMMIT_HASH"
55
+ tokenizer = AutoTokenizer.from_pretrained(
56
+ "OWNER/REPO", trust_remote_code=True, revision=revision
57
+ )
58
+ model = AutoModelForCausalLM.from_pretrained(
59
+ "OWNER/REPO", trust_remote_code=True, revision=revision
60
+ )
61
+ inputs = tokenizer("0000 + 0000 =", return_tensors="pt")
62
+ output = model.generate(**inputs, max_new_tokens=model.config.answer_digits, do_sample=False)
63
+ print(tokenizer.decode(output[0], skip_special_tokens=True))
64
+ ```
65
+
66
+ ## Intended use and limitations
67
+
68
+ This model is intended for mechanistic-interpretability research on its configured fixed-width
69
+ addition task. It is not a general arithmetic system: inputs outside the configured grammar or
70
+ width are unsupported, and generated answers must not be treated as reliable calculations.
addition_transformer.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import torch
6
+ from torch import nn
7
+
8
+ from .initialization import initialize_module
9
+ from .model_config import AdditionModelConfig
10
+ from .transformer_block import TransformerBlock, TransformerBlockOutput
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class TransformerOutput:
15
+ logits: torch.Tensor
16
+ block_outputs: tuple[TransformerBlockOutput, ...] = ()
17
+
18
+
19
+ class AdditionTransformer(nn.Module):
20
+ def __init__(self, config: AdditionModelConfig, vocab_size: int) -> None:
21
+ super().__init__()
22
+ self.config = config
23
+ self.vocab_size = vocab_size
24
+
25
+ self.token_embedding = nn.Embedding(vocab_size, config.d_model)
26
+ self.blocks = nn.ModuleList(TransformerBlock(config) for _ in range(config.n_layers))
27
+ self.unembedding = nn.Linear(config.d_model, vocab_size, bias=False)
28
+
29
+ self.reset_parameters()
30
+
31
+ def reset_parameters(self) -> None:
32
+ initialize_module(self, self.config)
33
+
34
+ def forward(
35
+ self,
36
+ input_ids: torch.Tensor,
37
+ return_activations: bool = False,
38
+ ) -> TransformerOutput:
39
+ _, seq_len = input_ids.shape
40
+ if seq_len > self.config.max_seq_len:
41
+ raise ValueError(f"Sequence length {seq_len} exceeds max_seq_len={self.config.max_seq_len}.")
42
+
43
+ x = self.token_embedding(input_ids)
44
+ # Modula-inspired RoPE, attention scaling, and residual interpolation; see arXiv:2405.14813.
45
+ block_outputs: list[TransformerBlockOutput] = []
46
+ for block in self.blocks:
47
+ block_output = block(x, return_pattern=return_activations)
48
+ x = block_output.residual_after_mlp
49
+ if return_activations:
50
+ block_outputs.append(block_output)
51
+ logits = self.unembedding(x)
52
+
53
+ return TransformerOutput(
54
+ logits=logits,
55
+ block_outputs=tuple(block_outputs),
56
+ )
57
+
58
+ def symmetrized_mlp_tensor(self, detach: bool = True, layer: int = 0) -> torch.Tensor:
59
+ if not 0 <= layer < len(self.blocks):
60
+ raise IndexError(f"Layer {layer} is outside the model's {len(self.blocks)} layers.")
61
+ return self.blocks[layer].mlp.symmetrized_bilinear_tensor(detach=detach)
62
+
63
+ def analysis_tensors(self, detach: bool = True) -> dict[str, torch.Tensor]:
64
+ tensors: dict[str, torch.Tensor] = {}
65
+ for layer, block in enumerate(self.blocks):
66
+ prefix = f"blocks.{layer}"
67
+ bilinear_tensor = block.mlp.bilinear_tensor(detach=False)
68
+ tensors.update(
69
+ {
70
+ f"{prefix}.attention.W_Q": block.attention.W_Q.weight,
71
+ f"{prefix}.attention.W_K": block.attention.W_K.weight,
72
+ f"{prefix}.attention.W_V": block.attention.W_V.weight,
73
+ f"{prefix}.attention.W_O": block.attention.W_O.weight,
74
+ f"{prefix}.mlp.W_1": block.mlp.W_1.weight,
75
+ f"{prefix}.mlp.W_2": block.mlp.W_2.weight,
76
+ f"{prefix}.mlp.W_O": block.mlp.W_O.weight,
77
+ f"{prefix}.mlp.bilinear_tensor": bilinear_tensor,
78
+ f"{prefix}.mlp.symmetrized_bilinear_tensor": 0.5
79
+ * (bilinear_tensor + bilinear_tensor.transpose(-1, -2)),
80
+ }
81
+ )
82
+ return {name: tensor.detach() for name, tensor in tensors.items()} if detach else tensors
attention.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from torch import nn
8
+
9
+ from .rotary_embedding import RotaryEmbedding
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class AttentionOutput:
14
+ values: torch.Tensor
15
+ pattern: torch.Tensor | None = None
16
+
17
+
18
+ class CausalSelfAttention(nn.Module):
19
+ def __init__(
20
+ self,
21
+ d_model: int,
22
+ n_heads: int,
23
+ max_seq_len: int,
24
+ rope_theta: float = 10_000.0,
25
+ bias: bool = False,
26
+ ) -> None:
27
+ super().__init__()
28
+ if d_model % n_heads != 0:
29
+ raise ValueError("d_model must be divisible by n_heads.")
30
+
31
+ self.d_model = d_model
32
+ self.n_heads = n_heads
33
+ self.d_head = d_model // n_heads
34
+
35
+ self.W_Q = nn.Linear(d_model, d_model, bias=bias)
36
+ self.W_K = nn.Linear(d_model, d_model, bias=bias)
37
+ self.W_V = nn.Linear(d_model, d_model, bias=bias)
38
+ self.W_O = nn.Linear(d_model, d_model, bias=bias)
39
+ self.rotary = RotaryEmbedding(self.d_head, max_seq_len, rope_theta)
40
+ self.register_buffer("_causal_mask", torch.empty(0, 0, dtype=torch.bool), persistent=False)
41
+
42
+ def forward(self, x: torch.Tensor, return_pattern: bool = False) -> AttentionOutput:
43
+ batch, seq_len, _ = x.shape
44
+ q = self._split_heads(self.W_Q(x))
45
+ k = self._split_heads(self.W_K(x))
46
+ v = self._split_heads(self.W_V(x))
47
+ q, k = self.rotary(q, k)
48
+
49
+ scores = torch.matmul(q, k.transpose(-1, -2)) / self.d_head
50
+ if self._causal_mask.shape[0] < seq_len or self._causal_mask.device != x.device:
51
+ self._causal_mask = torch.triu(
52
+ torch.ones(seq_len, seq_len, dtype=torch.bool, device=x.device),
53
+ diagonal=1,
54
+ )
55
+ causal_mask = self._causal_mask[:seq_len, :seq_len]
56
+ scores = scores.masked_fill(causal_mask, float("-inf"))
57
+ pattern = F.softmax(scores, dim=-1)
58
+
59
+ attended = torch.matmul(pattern, v) / 3.0
60
+ values = self.W_O(self._merge_heads(attended, batch, seq_len))
61
+ return AttentionOutput(values=values, pattern=pattern if return_pattern else None)
62
+
63
+ def _split_heads(self, x: torch.Tensor) -> torch.Tensor:
64
+ batch, seq_len, _ = x.shape
65
+ return x.view(batch, seq_len, self.n_heads, self.d_head).transpose(1, 2)
66
+
67
+ def _merge_heads(self, x: torch.Tensor, batch: int, seq_len: int) -> torch.Tensor:
68
+ return x.transpose(1, 2).contiguous().view(batch, seq_len, self.d_model)
bilinear_mlp.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import torch
4
+ from torch import nn
5
+
6
+
7
+ class BilinearMLP(nn.Module):
8
+ """Bilinear MLP: W_O((W_1 x) * (W_2 x))."""
9
+
10
+ def __init__(self, d_model: int, d_hidden: int, bias: bool = False) -> None:
11
+ super().__init__()
12
+ self.d_model = d_model
13
+ self.d_hidden = d_hidden
14
+ self.W_1 = nn.Linear(d_model, d_hidden, bias=bias)
15
+ self.W_2 = nn.Linear(d_model, d_hidden, bias=bias)
16
+ self.W_O = nn.Linear(d_hidden, d_model, bias=bias)
17
+
18
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
19
+ return self.W_O(self.W_1(x) * self.W_2(x))
20
+
21
+ def bilinear_tensor(self, detach: bool = False) -> torch.Tensor:
22
+ tensor = torch.einsum(
23
+ "oh,hi,hj->oij",
24
+ self.W_O.weight,
25
+ self.W_1.weight,
26
+ self.W_2.weight,
27
+ )
28
+ return tensor.detach() if detach else tensor
29
+
30
+ def symmetrized_bilinear_tensor(self, detach: bool = False) -> torch.Tensor:
31
+ tensor = self.bilinear_tensor(detach=False)
32
+ symmetrized = 0.5 * (tensor + tensor.transpose(-1, -2))
33
+ return symmetrized.detach() if detach else symmetrized
config.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "answer_digits": 5,
3
+ "architecture_version": 3,
4
+ "architectures": [
5
+ "AdditionForCausalLM"
6
+ ],
7
+ "attention_logit_divisor": "d_head",
8
+ "attention_output_scale": 0.3333333333333333,
9
+ "auto_map": {
10
+ "AutoConfig": "configuration_addition.AdditionConfig",
11
+ "AutoModelForCausalLM": "modeling_addition.AdditionForCausalLM"
12
+ },
13
+ "base": 10,
14
+ "bias": false,
15
+ "bos_token_id": 0,
16
+ "d_mlp": 128,
17
+ "d_model": 64,
18
+ "digit_token_offset": 3,
19
+ "dtype": "float32",
20
+ "eos_token_id": null,
21
+ "equals_token_id": 2,
22
+ "export_format_version": 3,
23
+ "hidden_size": 64,
24
+ "init_mode": "normal",
25
+ "intermediate_size": 128,
26
+ "is_decoder": true,
27
+ "max_position_embeddings": 16,
28
+ "max_seq_len": 16,
29
+ "model_type": "fixed-width-addition",
30
+ "n_heads": 4,
31
+ "n_layers": 1,
32
+ "normalization": "none",
33
+ "num_attention_heads": 4,
34
+ "num_hidden_layers": 1,
35
+ "operand_digits": 4,
36
+ "pad_token_id": null,
37
+ "plus_token_id": 1,
38
+ "position_encoding": "rope",
39
+ "residual_alpha": 0.5,
40
+ "rope_theta": 10000.0,
41
+ "rotary_dim": 16,
42
+ "tie_word_embeddings": false,
43
+ "transformers_version": "5.15.0",
44
+ "use_cache": false,
45
+ "vocab_size": 13
46
+ }
configuration_addition.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+ from transformers import PreTrainedConfig
6
+
7
+
8
+ class AdditionConfig(PreTrainedConfig):
9
+ model_type = "fixed-width-addition"
10
+
11
+ def __init__(
12
+ self,
13
+ operand_digits: int = 5,
14
+ d_model: int = 64,
15
+ n_heads: int = 4,
16
+ d_mlp: int = 128,
17
+ n_layers: int = 1,
18
+ max_seq_len: int = 19,
19
+ rope_theta: float = 10_000.0,
20
+ init_mode: str = "normal",
21
+ residual_alpha: float | None = None,
22
+ **kwargs,
23
+ ) -> None:
24
+ expected_residual_alpha = 1.0 / (2.0 * n_layers) if n_layers > 0 else 0.0
25
+ if residual_alpha is not None and residual_alpha != expected_residual_alpha:
26
+ raise ValueError(
27
+ f"residual_alpha must be 1 / (2 * n_layers) = {expected_residual_alpha}."
28
+ )
29
+ invariants = {
30
+ "answer_digits": operand_digits + 1,
31
+ "base": 10,
32
+ "vocab_size": 13,
33
+ "hidden_size": d_model,
34
+ "num_attention_heads": n_heads,
35
+ "intermediate_size": d_mlp,
36
+ "num_hidden_layers": n_layers,
37
+ "max_position_embeddings": max_seq_len,
38
+ "position_encoding": "rope",
39
+ "rotary_dim": d_model // n_heads if n_heads > 0 else 0,
40
+ "bias": False,
41
+ "normalization": "none",
42
+ "attention_logit_divisor": "d_head",
43
+ "attention_output_scale": 1.0 / 3.0,
44
+ "bos_token_id": 0,
45
+ "plus_token_id": 1,
46
+ "equals_token_id": 2,
47
+ "digit_token_offset": 3,
48
+ "architecture_version": 3,
49
+ "export_format_version": 3,
50
+ "use_cache": False,
51
+ "tie_word_embeddings": False,
52
+ "is_decoder": True,
53
+ "is_encoder_decoder": False,
54
+ "eos_token_id": None,
55
+ "pad_token_id": None,
56
+ }
57
+ for name, expected in invariants.items():
58
+ if name in kwargs and kwargs.pop(name) != expected:
59
+ raise ValueError(f"{name} is fixed at {expected!r} for this architecture.")
60
+ self.operand_digits = operand_digits
61
+ self.answer_digits = operand_digits + 1
62
+ self.base = 10
63
+ self.vocab_size = 13
64
+ self.d_model = d_model
65
+ self.hidden_size = d_model
66
+ self.n_heads = n_heads
67
+ self.num_attention_heads = n_heads
68
+ self.d_mlp = d_mlp
69
+ self.intermediate_size = d_mlp
70
+ self.n_layers = n_layers
71
+ self.num_hidden_layers = n_layers
72
+ self.max_seq_len = max_seq_len
73
+ self.max_position_embeddings = max_seq_len
74
+ self.rope_theta = float(rope_theta)
75
+ self.position_encoding = "rope"
76
+ self.rotary_dim = d_model // n_heads if n_heads > 0 else 0
77
+ self.init_mode = str(init_mode)
78
+ self.residual_alpha = expected_residual_alpha
79
+ self.bias = False
80
+ self.normalization = "none"
81
+ self.attention_logit_divisor = "d_head"
82
+ self.attention_output_scale = 1.0 / 3.0
83
+ self.bos_token_id = 0
84
+ self.plus_token_id = 1
85
+ self.equals_token_id = 2
86
+ self.digit_token_offset = 3
87
+ self.architecture_version = 3
88
+ self.export_format_version = 3
89
+ self.use_cache = False
90
+ self.tie_word_embeddings = False
91
+ self.is_decoder = True
92
+ self.is_encoder_decoder = False
93
+ self._validate_architecture()
94
+ super().__init__(
95
+ bos_token_id=self.bos_token_id,
96
+ eos_token_id=None,
97
+ pad_token_id=None,
98
+ tie_word_embeddings=False,
99
+ is_decoder=True,
100
+ **kwargs,
101
+ )
102
+
103
+ @property
104
+ def d_head(self) -> int:
105
+ return self.d_model // self.n_heads
106
+
107
+ def _validate_architecture(self) -> None:
108
+ if self.operand_digits < 1:
109
+ raise ValueError("operand_digits must be positive.")
110
+ if self.d_model < 1 or self.n_heads < 1 or self.d_mlp < 1 or self.n_layers < 1:
111
+ raise ValueError("Model dimensions must be positive.")
112
+ if self.d_model % self.n_heads != 0:
113
+ raise ValueError("d_model must be divisible by n_heads.")
114
+ if self.d_head % 2 != 0:
115
+ raise ValueError("d_head must be even for RoPE.")
116
+ if not math.isfinite(self.rope_theta) or self.rope_theta <= 0:
117
+ raise ValueError("rope_theta must be positive.")
118
+ required_length = 3 * self.operand_digits + 4
119
+ if self.max_seq_len < required_length:
120
+ raise ValueError(
121
+ f"max_seq_len={self.max_seq_len} is too small for {required_length} full-sequence tokens."
122
+ )
123
+ if self.init_mode not in {"normal", "orthogonal"}:
124
+ raise ValueError(f"Unsupported init_mode: {self.init_mode}")
125
+ expected_residual_alpha = 1.0 / (2.0 * self.n_layers)
126
+ if self.residual_alpha != expected_residual_alpha:
127
+ raise ValueError("residual_alpha must equal 1 / (2 * n_layers).")
128
+
129
+
130
+ AdditionConfig.register_for_auto_class("AutoConfig")
export_manifest.json ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "environment": {
3
+ "cuda": "12.6",
4
+ "device": "cuda",
5
+ "device_name": "NVIDIA GeForce RTX 4060 Laptop GPU",
6
+ "git_commit": null,
7
+ "git_dirty": true,
8
+ "packages": {
9
+ "huggingface-hub": "1.27.0",
10
+ "numpy": "2.5.2",
11
+ "pytest": "9.1.1",
12
+ "safetensors": "0.8.0",
13
+ "sympy": "1.14.0",
14
+ "transformers": "5.15.0",
15
+ "wandb": "0.28.1"
16
+ },
17
+ "platform": "Windows-11-10.0.26200-SP0",
18
+ "python": "3.12.4",
19
+ "torch": "2.13.0+cu126"
20
+ },
21
+ "export_files_sha256": {
22
+ "README.md": "3cbb9ada58204b34e2afd699fa0ff7bbe5702751ff025c81272bd005d5cc73a9",
23
+ "addition_transformer.py": "6c527805f122b294ce3041ea592fb90e8b65a5012767eefc8bbd54ac582cacbf",
24
+ "attention.py": "e593866a411e4a7fcf089320b0732551170cd1d22500aa78f32e0e4a0a4f12b5",
25
+ "bilinear_mlp.py": "95d8b248058bdbce7ea7c13ba9ebf49ee69b20b95714a43040f19c9c528225c7",
26
+ "config.json": "e1b7a20a05146ee66d2d5442a7eb0dabfdfee043a266c5f2f9074f348440a3b3",
27
+ "configuration_addition.py": "56d24dfcef47b17e4e2a24f8f8f08d834ed9ec4bb86738ee8077ac75607b7c95",
28
+ "generation_config.json": "13cc88dd90bbb2a181ade617b6a41b35998e933cc1eb356f7906c320a7d04b5d",
29
+ "initialization.py": "01de663e2494370c6bb2a9d4f770411104dbc44cbb97917fa18ba89ed9959d32",
30
+ "model.safetensors": "af93c22839a0d30a2b81ef7e7444a0e4dbb1d726fe20561d654b15fd397af55c",
31
+ "model_config.py": "2c1c01f64064830d9071aacd0c53219eb2f26365838d113713dffaba54ed6cd3",
32
+ "modeling_addition.py": "b5349de597570045eb3c21803f6a4b077605f5468f99e26ddb1678964d01fcea",
33
+ "rotary_embedding.py": "e5565ee1c072cc42f53af0cc51098f03f470591bd83454f1a162768d98a58b1a",
34
+ "tokenization_addition.py": "bc63473854f8f8fecfc3375757d257dfb34a33a0c30ee5a1ddffb3b984b8602f",
35
+ "tokenizer_config.json": "630ab54705ab78ecddef9c0c6543f477036b5a71cb2736a3db59462f464cbd06",
36
+ "transformer_block.py": "6566c6432e8d4868664be63e783e9f96e6dc138e39055dcd9c5caa70370ba9ed",
37
+ "vocab.json": "d6fdf48e6e2087546f1bfb7285a71b1072f5452934e8a33e831d069f7faf56a8"
38
+ },
39
+ "final_metrics": {
40
+ "examples_processed": 2560000,
41
+ "grad/clip_fraction": 0.0,
42
+ "grad/global_norm": 0.4769897431135178,
43
+ "lr/adamw": 2.9999999999999997e-05,
44
+ "lr/muon": 0.002,
45
+ "test/carry_chain/exact_accuracy": 0.89453125,
46
+ "test/carry_chain/generated_token_accuracy": 0.97734375,
47
+ "test/carry_chain/loss": 0.05059999823570251,
48
+ "test/carry_chain/position_0/accuracy": 0.9921875,
49
+ "test/carry_chain/position_1/accuracy": 0.9375,
50
+ "test/carry_chain/position_2/accuracy": 0.95703125,
51
+ "test/carry_chain/position_3/accuracy": 1.0,
52
+ "test/carry_chain/position_4/accuracy": 1.0,
53
+ "test/carry_chain/teacher_forced_token_accuracy": 0.97890625,
54
+ "test/multiple_carry/exact_accuracy": 0.97265625,
55
+ "test/multiple_carry/generated_token_accuracy": 0.99453125,
56
+ "test/multiple_carry/loss": 0.018936741352081298,
57
+ "test/multiple_carry/position_0/accuracy": 0.99609375,
58
+ "test/multiple_carry/position_1/accuracy": 0.98828125,
59
+ "test/multiple_carry/position_2/accuracy": 0.98828125,
60
+ "test/multiple_carry/position_3/accuracy": 1.0,
61
+ "test/multiple_carry/position_4/accuracy": 1.0,
62
+ "test/multiple_carry/teacher_forced_token_accuracy": 0.99453125,
63
+ "test/no_carry/exact_accuracy": 0.95703125,
64
+ "test/no_carry/generated_token_accuracy": 0.9890625,
65
+ "test/no_carry/loss": 0.022115638852119444,
66
+ "test/no_carry/position_0/accuracy": 0.9765625,
67
+ "test/no_carry/position_1/accuracy": 0.984375,
68
+ "test/no_carry/position_2/accuracy": 0.984375,
69
+ "test/no_carry/position_3/accuracy": 1.0,
70
+ "test/no_carry/position_4/accuracy": 1.0,
71
+ "test/no_carry/teacher_forced_token_accuracy": 0.990625,
72
+ "test/single_carry/exact_accuracy": 0.9921875,
73
+ "test/single_carry/generated_token_accuracy": 0.9984375,
74
+ "test/single_carry/loss": 0.0053884580731391905,
75
+ "test/single_carry/position_0/accuracy": 1.0,
76
+ "test/single_carry/position_1/accuracy": 0.9921875,
77
+ "test/single_carry/position_2/accuracy": 1.0,
78
+ "test/single_carry/position_3/accuracy": 1.0,
79
+ "test/single_carry/position_4/accuracy": 1.0,
80
+ "test/single_carry/teacher_forced_token_accuracy": 0.9984375,
81
+ "throughput/examples_per_second": 24623.5772583565,
82
+ "train/loss": 0.010908610420301557,
83
+ "train/token_accuracy": 0.9959374904632569,
84
+ "val/exact_accuracy": 0.974609375,
85
+ "val/generated_token_accuracy": 0.99375,
86
+ "val/loss": 0.014790849387645721,
87
+ "val/position_0/accuracy": 0.99560546875,
88
+ "val/position_1/accuracy": 0.9853515625,
89
+ "val/position_2/accuracy": 0.9892578125,
90
+ "val/position_3/accuracy": 0.99853515625,
91
+ "val/position_4/accuracy": 1.0,
92
+ "val/teacher_forced_token_accuracy": 0.9943359375
93
+ },
94
+ "resolved_config": {
95
+ "data": {
96
+ "base": 10,
97
+ "digits": 4
98
+ },
99
+ "model": {
100
+ "d_mlp": 128,
101
+ "d_model": 64,
102
+ "init_mode": "normal",
103
+ "max_seq_len": 16,
104
+ "n_heads": 4,
105
+ "n_layers": 1,
106
+ "rope_theta": 10000.0
107
+ },
108
+ "train": {
109
+ "adamw_lr": 0.0003,
110
+ "batch_size": 256,
111
+ "checkpoint_every": 500,
112
+ "device": "auto",
113
+ "eval_batch_size": 512,
114
+ "eval_every": 100,
115
+ "eval_seed": 1,
116
+ "grad_clip_norm": 1.0,
117
+ "log_every": 10,
118
+ "min_lr_ratio": 0.1,
119
+ "muon_lr": 0.02,
120
+ "num_targeted_examples": 256,
121
+ "num_val_examples": 2048,
122
+ "optimizer": "muon",
123
+ "progress_bar": true,
124
+ "run_root": ".",
125
+ "seed": 0,
126
+ "steps": 10000,
127
+ "weight_decay": 0.01
128
+ },
129
+ "wandb": {
130
+ "entity": null,
131
+ "group": null,
132
+ "log_model": false,
133
+ "mode": "auto",
134
+ "name": null,
135
+ "project": "circuits-addition",
136
+ "tags": []
137
+ }
138
+ },
139
+ "run_id": "rmb41c76",
140
+ "schema_version": 1,
141
+ "training_files_sha256": {
142
+ "checkpoints/final.pt": "364b23da837d6da7dd72bdbded43f31c4b33fcd985e571b70920e932d9c29397",
143
+ "environment.json": "6c7ded30b327853d18e87320549e118d92e48cb606ce96daa673c94eb07a965a",
144
+ "metrics.jsonl": "d086c27ee9929dcb23441aeaf872f482004fde560a1447a79c8929266c0597fe",
145
+ "resolved_config.toml": "314099bfa658adccfe74c984160caf91f0153c91fbc07447181255ddd86c73d7",
146
+ "source_snapshot.zip": "e5f98551caef8fe2ba80f851fa33b481a2c0dd651ff55102e13ebd4754f5a3b4"
147
+ },
148
+ "training_step": 10000
149
+ }
generation_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 0,
3
+ "do_sample": false,
4
+ "max_new_tokens": 5,
5
+ "transformers_version": "5.15.0",
6
+ "use_cache": false
7
+ }
initialization.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+ from torch import nn
6
+
7
+ from .model_config import AdditionModelConfig
8
+
9
+
10
+ def initialize_module(module: nn.Module, config: AdditionModelConfig) -> None:
11
+ init_mode = str(config.init_mode)
12
+ if init_mode == "normal":
13
+ _initialize_normal(module)
14
+ elif init_mode == "orthogonal":
15
+ _initialize_orthogonal(module)
16
+ else:
17
+ raise ValueError(f"Unsupported init mode: {init_mode}")
18
+
19
+
20
+ def _initialize_normal(module: nn.Module) -> None:
21
+ for child in module.modules():
22
+ if isinstance(child, nn.Embedding):
23
+ nn.init.normal_(child.weight, mean=0.0, std=1.0 / math.sqrt(2.0))
24
+ elif isinstance(child, nn.Linear):
25
+ nn.init.normal_(child.weight, mean=0.0, std=1.0 / math.sqrt(child.in_features))
26
+ _zero_bias(child)
27
+
28
+
29
+ def _initialize_orthogonal(module: nn.Module) -> None:
30
+ for child in module.modules():
31
+ if isinstance(child, nn.Embedding):
32
+ nn.init.normal_(child.weight, mean=0.0, std=1.0 / math.sqrt(2.0))
33
+ elif isinstance(child, nn.Linear):
34
+ gain = math.sqrt(child.out_features / child.in_features) if child.out_features > child.in_features else 1.0
35
+ nn.init.orthogonal_(child.weight, gain=gain)
36
+ _zero_bias(child)
37
+
38
+
39
+ def _zero_bias(module: nn.Linear) -> None:
40
+ if module.bias is not None:
41
+ nn.init.zeros_(module.bias)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:af93c22839a0d30a2b81ef7e7444a0e4dbb1d726fe20561d654b15fd397af55c
3
+ size 171400
model_config.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Protocol
4
+
5
+
6
+ class AdditionModelConfig(Protocol):
7
+ d_model: int
8
+ n_heads: int
9
+ d_mlp: int
10
+ n_layers: int
11
+ max_seq_len: int
12
+ rope_theta: float
13
+ init_mode: str
14
+ residual_alpha: float
modeling_addition.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from torch import nn
8
+ from transformers import GenerationMixin, PreTrainedModel
9
+ from transformers.modeling_outputs import CausalLMOutputWithPast
10
+
11
+ from .addition_transformer import AdditionTransformer
12
+ from .configuration_addition import AdditionConfig
13
+
14
+
15
+ class AdditionForCausalLM(PreTrainedModel, GenerationMixin):
16
+ config_class = AdditionConfig
17
+ base_model_prefix = "model"
18
+ main_input_name = "input_ids"
19
+
20
+ def __init__(self, config: AdditionConfig) -> None:
21
+ super().__init__(config)
22
+ self.model = AdditionTransformer(config, vocab_size=config.vocab_size)
23
+ self.post_init()
24
+
25
+ def _init_weights(self, module: nn.Module) -> None:
26
+ # AdditionTransformer owns initialization so research and exported models remain identical.
27
+ return None
28
+
29
+ def get_input_embeddings(self) -> nn.Embedding:
30
+ return self.model.token_embedding
31
+
32
+ def set_input_embeddings(self, value: nn.Embedding) -> None:
33
+ self.model.token_embedding = value
34
+
35
+ def get_output_embeddings(self) -> nn.Linear:
36
+ return self.model.unembedding
37
+
38
+ def set_output_embeddings(self, value: nn.Linear) -> None:
39
+ self.model.unembedding = value
40
+
41
+ def forward(
42
+ self,
43
+ input_ids: torch.Tensor,
44
+ attention_mask: torch.Tensor | None = None,
45
+ labels: torch.Tensor | None = None,
46
+ past_key_values: Any | None = None,
47
+ use_cache: bool | None = None,
48
+ output_attentions: bool | None = None,
49
+ output_hidden_states: bool | None = None,
50
+ return_dict: bool | None = None,
51
+ **kwargs: Any,
52
+ ) -> CausalLMOutputWithPast | tuple[torch.Tensor, ...]:
53
+ if kwargs:
54
+ names = ", ".join(sorted(kwargs))
55
+ raise TypeError(f"Unsupported model inputs: {names}")
56
+ if past_key_values is not None or use_cache:
57
+ raise ValueError("AdditionForCausalLM does not implement a key-value cache.")
58
+ if attention_mask is not None:
59
+ if attention_mask.shape != input_ids.shape:
60
+ raise ValueError("attention_mask must have the same shape as input_ids.")
61
+ if not bool(torch.all(attention_mask != 0)):
62
+ raise ValueError("Padding is unsupported; attention_mask must contain only ones.")
63
+
64
+ return_dict = self.config.return_dict if return_dict is None else return_dict
65
+ output_attentions = bool(output_attentions)
66
+ output_hidden_states = bool(output_hidden_states)
67
+ core_output = self.model(
68
+ input_ids,
69
+ return_activations=output_attentions or output_hidden_states,
70
+ )
71
+ loss = None
72
+ if labels is not None:
73
+ if labels.shape != input_ids.shape:
74
+ raise ValueError("labels must have the same shape as input_ids.")
75
+ loss = F.cross_entropy(
76
+ core_output.logits[:, :-1, :].contiguous().view(-1, self.config.vocab_size),
77
+ labels[:, 1:].contiguous().view(-1),
78
+ ignore_index=-100,
79
+ )
80
+
81
+ hidden_states = None
82
+ if output_hidden_states:
83
+ hidden_states = (
84
+ core_output.block_outputs[0].residual_pre_attention,
85
+ *(block.residual_after_mlp for block in core_output.block_outputs),
86
+ )
87
+ attentions = (
88
+ tuple(block.attention_pattern for block in core_output.block_outputs)
89
+ if output_attentions
90
+ else None
91
+ )
92
+
93
+ if not return_dict:
94
+ values = (core_output.logits, None, hidden_states, attentions)
95
+ return ((loss,) + values) if loss is not None else values
96
+ return CausalLMOutputWithPast(
97
+ loss=loss,
98
+ logits=core_output.logits,
99
+ past_key_values=None,
100
+ hidden_states=hidden_states,
101
+ attentions=attentions,
102
+ )
103
+
104
+ def prepare_inputs_for_generation(
105
+ self,
106
+ input_ids: torch.Tensor,
107
+ attention_mask: torch.Tensor | None = None,
108
+ **kwargs: Any,
109
+ ) -> dict[str, torch.Tensor | None | bool]:
110
+ return {
111
+ "input_ids": input_ids,
112
+ "attention_mask": attention_mask,
113
+ "use_cache": False,
114
+ }
115
+
116
+ def analysis_tensors(self, detach: bool = True) -> dict[str, torch.Tensor]:
117
+ return self.model.analysis_tensors(detach=detach)
118
+
119
+ def symmetrized_mlp_tensor(self, detach: bool = True, layer: int = 0) -> torch.Tensor:
120
+ return self.model.symmetrized_mlp_tensor(detach=detach, layer=layer)
121
+
122
+
123
+ AdditionForCausalLM.register_for_auto_class("AutoModelForCausalLM")
rotary_embedding.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+ import torch
6
+ from torch import nn
7
+
8
+
9
+ class RotaryEmbedding(nn.Module):
10
+ def __init__(self, d_head: int, max_seq_len: int, theta: float = 10_000.0) -> None:
11
+ super().__init__()
12
+ if d_head < 2 or d_head % 2 != 0:
13
+ raise ValueError("RoPE requires a positive, even attention head dimension.")
14
+ if max_seq_len < 1:
15
+ raise ValueError("max_seq_len must be positive.")
16
+ theta = float(theta)
17
+ if not math.isfinite(theta) or theta <= 0:
18
+ raise ValueError("RoPE theta must be positive.")
19
+
20
+ self.d_head = d_head
21
+ self.max_seq_len = max_seq_len
22
+ self.theta = theta
23
+ self.rope_dim = d_head // 2
24
+ self.register_buffer("_cos", torch.empty(0), persistent=False)
25
+ self.register_buffer("_sin", torch.empty(0), persistent=False)
26
+
27
+ def forward(
28
+ self,
29
+ query: torch.Tensor,
30
+ key: torch.Tensor,
31
+ ) -> tuple[torch.Tensor, torch.Tensor]:
32
+ if query.shape != key.shape:
33
+ raise ValueError("RoPE query and key tensors must have the same shape.")
34
+ return self.rotate(query), self.rotate(key)
35
+
36
+ def rotate(self, x: torch.Tensor) -> torch.Tensor:
37
+ if x.ndim != 4 or x.shape[-1] != self.d_head:
38
+ raise ValueError(f"RoPE expects shape [batch, heads, sequence, {self.d_head}].")
39
+ seq_len = x.shape[-2]
40
+ if seq_len > self.max_seq_len:
41
+ raise ValueError(f"Sequence length {seq_len} exceeds RoPE limit {self.max_seq_len}.")
42
+
43
+ cos, sin = self._cos_sin(x.device)
44
+ cos = cos[:, :, :seq_len].to(dtype=x.dtype)
45
+ sin = sin[:, :, :seq_len].to(dtype=x.dtype)
46
+ first_half = x[..., : self.rope_dim]
47
+ second_half = x[..., self.rope_dim :]
48
+ return torch.cat(
49
+ (
50
+ cos * second_half + sin * first_half,
51
+ -sin * second_half + cos * first_half,
52
+ ),
53
+ dim=-1,
54
+ )
55
+
56
+ def _cos_sin(self, device: torch.device) -> tuple[torch.Tensor, torch.Tensor]:
57
+ if self._cos.numel() == 0 or self._cos.device != device:
58
+ inverse_frequencies = self.theta ** (
59
+ -torch.arange(self.rope_dim, dtype=torch.float32, device=device) / self.rope_dim
60
+ )
61
+ frequencies = torch.outer(
62
+ torch.arange(self.max_seq_len, dtype=torch.float32, device=device),
63
+ inverse_frequencies,
64
+ )
65
+ self._cos = frequencies.cos()[None, None, :, :]
66
+ self._sin = frequencies.sin()[None, None, :, :]
67
+ return self._cos, self._sin
tokenization_addition.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from transformers import PreTrainedTokenizer
7
+
8
+
9
+ CANONICAL_VOCAB = {"<BOS>": 0, "+": 1, "=": 2, **{str(digit): digit + 3 for digit in range(10)}}
10
+
11
+
12
+ class AdditionTokenizer(PreTrainedTokenizer):
13
+ vocab_files_names = {"vocab_file": "vocab.json"}
14
+ model_input_names = ["input_ids", "attention_mask"]
15
+
16
+ def __init__(self, vocab_file: str | None = None, **kwargs) -> None:
17
+ if vocab_file is None:
18
+ vocab = dict(CANONICAL_VOCAB)
19
+ else:
20
+ with Path(vocab_file).open("r", encoding="utf-8") as handle:
21
+ vocab = json.load(handle)
22
+ if vocab != CANONICAL_VOCAB:
23
+ raise ValueError("AdditionTokenizer requires the canonical 13-token vocabulary.")
24
+ self._vocab = vocab
25
+ self._ids_to_tokens = {token_id: token for token, token_id in vocab.items()}
26
+ kwargs.pop("bos_token", None)
27
+ kwargs.pop("eos_token", None)
28
+ kwargs.pop("pad_token", None)
29
+ kwargs.pop("unk_token", None)
30
+ super().__init__(
31
+ bos_token="<BOS>",
32
+ eos_token=None,
33
+ pad_token=None,
34
+ unk_token=None,
35
+ **kwargs,
36
+ )
37
+
38
+ @property
39
+ def vocab_size(self) -> int:
40
+ return len(self._vocab)
41
+
42
+ def get_vocab(self) -> dict[str, int]:
43
+ return dict(self._vocab)
44
+
45
+ def _tokenize(self, text: str, **kwargs) -> list[str]:
46
+ compact = "".join(text.split())
47
+ invalid = sorted(set(compact) - set("0123456789+="))
48
+ if invalid:
49
+ raise ValueError(f"Unsupported characters for addition tokenizer: {''.join(invalid)}")
50
+ return list(compact)
51
+
52
+ def _convert_token_to_id(self, token: str) -> int:
53
+ try:
54
+ return self._vocab[token]
55
+ except KeyError as exc:
56
+ raise ValueError(f"Unknown addition token: {token!r}") from exc
57
+
58
+ def _convert_id_to_token(self, index: int) -> str:
59
+ try:
60
+ return self._ids_to_tokens[index]
61
+ except KeyError as exc:
62
+ raise ValueError(f"Unknown addition token ID: {index}") from exc
63
+
64
+ def convert_tokens_to_string(self, tokens: list[str]) -> str:
65
+ return "".join(tokens)
66
+
67
+ def build_inputs_with_special_tokens(
68
+ self,
69
+ token_ids_0: list[int],
70
+ token_ids_1: list[int] | None = None,
71
+ ) -> list[int]:
72
+ if token_ids_1 is not None:
73
+ raise ValueError("AdditionTokenizer does not support sequence pairs.")
74
+ return [self.bos_token_id, *token_ids_0]
75
+
76
+ def get_special_tokens_mask(
77
+ self,
78
+ token_ids_0: list[int],
79
+ token_ids_1: list[int] | None = None,
80
+ already_has_special_tokens: bool = False,
81
+ ) -> list[int]:
82
+ if already_has_special_tokens:
83
+ return [int(token_id == self.bos_token_id) for token_id in token_ids_0]
84
+ if token_ids_1 is not None:
85
+ raise ValueError("AdditionTokenizer does not support sequence pairs.")
86
+ return [1, *([0] * len(token_ids_0))]
87
+
88
+ def create_token_type_ids_from_sequences(
89
+ self,
90
+ token_ids_0: list[int],
91
+ token_ids_1: list[int] | None = None,
92
+ ) -> list[int]:
93
+ if token_ids_1 is not None:
94
+ raise ValueError("AdditionTokenizer does not support sequence pairs.")
95
+ return [0] * (len(token_ids_0) + 1)
96
+
97
+ def save_vocabulary(
98
+ self,
99
+ save_directory: str,
100
+ filename_prefix: str | None = None,
101
+ ) -> tuple[str]:
102
+ directory = Path(save_directory)
103
+ directory.mkdir(parents=True, exist_ok=True)
104
+ filename = f"{filename_prefix + '-' if filename_prefix else ''}vocab.json"
105
+ path = directory / filename
106
+ path.write_text(json.dumps(self._vocab, indent=2, sort_keys=True) + "\n", encoding="utf-8")
107
+ return (str(path),)
108
+
109
+
110
+ AdditionTokenizer.register_for_auto_class("AutoTokenizer")
111
+
tokenizer_config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "<BOS>",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ }
11
+ },
12
+ "auto_map": {
13
+ "AutoTokenizer": [
14
+ "tokenization_addition.AdditionTokenizer",
15
+ null
16
+ ]
17
+ },
18
+ "backend": "custom",
19
+ "bos_token": "<BOS>",
20
+ "eos_token": null,
21
+ "model_max_length": 16,
22
+ "pad_token": null,
23
+ "tokenizer_class": "AdditionTokenizer",
24
+ "unk_token": null
25
+ }
training/checkpoints/final.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:364b23da837d6da7dd72bdbded43f31c4b33fcd985e571b70920e932d9c29397
3
+ size 370007
training/checkpoints/step_000500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f548746cd3616d448267999a96cbfe48432c6e115f8b7c23cf34c99669c0f4e4
3
+ size 368203
training/checkpoints/step_001000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fd2074573ed2981d82e7f184c894eb2cfb3cd4d159fa18ae63fc13e679a9b503
3
+ size 368203
training/checkpoints/step_001500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8a21e8d10bb86977d1eb7435f1a7ab80e56c21745d08ea304a73b591763e8c5d
3
+ size 368203
training/checkpoints/step_002000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4f6c9eeee26f6e347da43bdd15b8ad75e979a4ac29dd52cf69a5f48c64800c77
3
+ size 368203
training/checkpoints/step_002500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:118739cfbd77c63b02c48376d66be1c180ff13ba9ee3345547990cb491044f79
3
+ size 368203
training/checkpoints/step_003000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:364917b6f5f834e38a72c54632931465d7dff5954f237dd46f6172bea202b7cb
3
+ size 368203
training/checkpoints/step_003500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:47b85157ec31f1f2afaf69c4af264f88eb42affd7726e7ec4b00e4b654305313
3
+ size 368203
training/checkpoints/step_004000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0506b9ec99243acae1176420e5af79654e9965e98a39f2a1e1cb7b16cf804c02
3
+ size 368139
training/checkpoints/step_004500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8a9fe30a8da5f61061af1fd06d7faaacc09c17d28ba6cf1590d8de18f0504925
3
+ size 368203
training/checkpoints/step_005000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dba1fa3eb7985f0b3ab8698e1a340e8e337976c50666f3114faa5f0f16d83c21
3
+ size 368203
training/checkpoints/step_005500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7c5b0ca4890a2601a335fed09e6db4a85dc7ec5e7e76c511122d951382799540
3
+ size 368203
training/checkpoints/step_006000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:79798caad49cdc6a6f5c332e611f903e3df77ba0c7b1705b14a710e255129263
3
+ size 368203
training/checkpoints/step_006500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:43974f83324c34f8f5765154a6fa718e15d810b800c8f0070340380879e682ba
3
+ size 368203
training/checkpoints/step_007000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:44df848c070bcf448eed3f56c568a536d2ac982aab12890f6d359c7d53685173
3
+ size 368267
training/checkpoints/step_007500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:258799f22169ce295ddef1ca95468493abf2d6b7baeaf60c9d1554d8b913343f
3
+ size 368139
training/checkpoints/step_008000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c5af8cd81f0984fbdb2a36152ba7eb7f25c4ba5e2f94040ffe0a697166eb4315
3
+ size 368203
training/checkpoints/step_008500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e364f942589bf5f8f588ac938fa99320ec1dc7b9ea4b686028decb34f364541d
3
+ size 368203
training/checkpoints/step_009000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f6915013eb1fdaa7ca027518d330f9af7035493aa32485e9d27b07d189fc1165
3
+ size 368203
training/checkpoints/step_009500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:36a57369232d422840144332fb2e36ce0ef127de59065ddb0ce6f90592d0ceec
3
+ size 368203
training/environment.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cuda": "12.6",
3
+ "device": "cuda",
4
+ "device_name": "NVIDIA GeForce RTX 4060 Laptop GPU",
5
+ "git_commit": null,
6
+ "git_dirty": true,
7
+ "packages": {
8
+ "huggingface-hub": "1.27.0",
9
+ "numpy": "2.5.2",
10
+ "pytest": "9.1.1",
11
+ "safetensors": "0.8.0",
12
+ "sympy": "1.14.0",
13
+ "transformers": "5.15.0",
14
+ "wandb": "0.28.1"
15
+ },
16
+ "platform": "Windows-11-10.0.26200-SP0",
17
+ "python": "3.12.4",
18
+ "torch": "2.13.0+cu126"
19
+ }
training/metrics.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
training/resolved_config.toml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [data]
2
+ digits = 4
3
+ base = 10
4
+
5
+ [model]
6
+ d_model = 64
7
+ n_heads = 4
8
+ d_mlp = 128
9
+ n_layers = 1
10
+ max_seq_len = 16
11
+ rope_theta = 10000.0
12
+ init_mode = "normal"
13
+
14
+ [train]
15
+ steps = 10000
16
+ batch_size = 256
17
+ optimizer = "muon"
18
+ muon_lr = 0.02
19
+ adamw_lr = 0.0003
20
+ weight_decay = 0.01
21
+ min_lr_ratio = 0.1
22
+ grad_clip_norm = 1.0
23
+ log_every = 10
24
+ eval_every = 100
25
+ checkpoint_every = 500
26
+ progress_bar = true
27
+ run_root = "."
28
+ seed = 0
29
+ eval_seed = 1
30
+ device = "auto"
31
+ num_val_examples = 2048
32
+ num_targeted_examples = 256
33
+ eval_batch_size = 512
34
+
35
+ [wandb]
36
+ mode = "auto"
37
+ project = "circuits-addition"
38
+ tags = []
39
+ log_model = false
training/source_snapshot.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e5f98551caef8fe2ba80f851fa33b481a2c0dd651ff55102e13ebd4754f5a3b4
3
+ size 162656
transformer_block.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import torch
6
+ from torch import nn
7
+
8
+ from .attention import CausalSelfAttention
9
+ from .bilinear_mlp import BilinearMLP
10
+ from .model_config import AdditionModelConfig
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class TransformerBlockOutput:
15
+ residual_pre_attention: torch.Tensor
16
+ attention_out: torch.Tensor
17
+ attention_pattern: torch.Tensor | None
18
+ residual_after_attention: torch.Tensor
19
+ mlp_out: torch.Tensor
20
+ residual_after_mlp: torch.Tensor
21
+
22
+
23
+ class TransformerBlock(nn.Module):
24
+ def __init__(self, config: AdditionModelConfig) -> None:
25
+ super().__init__()
26
+ self.residual_alpha = config.residual_alpha
27
+ self.attention = CausalSelfAttention(
28
+ config.d_model,
29
+ config.n_heads,
30
+ config.max_seq_len,
31
+ config.rope_theta,
32
+ bias=False,
33
+ )
34
+ self.mlp = BilinearMLP(config.d_model, config.d_mlp, bias=False)
35
+
36
+ def forward(self, x: torch.Tensor, return_pattern: bool = False) -> TransformerBlockOutput:
37
+ attention_output = self.attention(x, return_pattern=return_pattern)
38
+ residual_after_attention = torch.lerp(x, attention_output.values, self.residual_alpha)
39
+ mlp_out = self.mlp(residual_after_attention)
40
+ residual_after_mlp = torch.lerp(residual_after_attention, mlp_out, self.residual_alpha)
41
+ return TransformerBlockOutput(
42
+ residual_pre_attention=x,
43
+ attention_out=attention_output.values,
44
+ attention_pattern=attention_output.pattern,
45
+ residual_after_attention=residual_after_attention,
46
+ mlp_out=mlp_out,
47
+ residual_after_mlp=residual_after_mlp,
48
+ )
vocab.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "+": 1,
3
+ "0": 3,
4
+ "1": 4,
5
+ "2": 5,
6
+ "3": 6,
7
+ "4": 7,
8
+ "5": 8,
9
+ "6": 9,
10
+ "7": 10,
11
+ "8": 11,
12
+ "9": 12,
13
+ "<BOS>": 0,
14
+ "=": 2
15
+ }