melephant commited on
Commit
58223a8
·
verified ·
1 Parent(s): 631583c

Publish addition-transformer run s85nnxtf

Browse files

Loadable model and complete training record.

README.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 `s85nnxtf` 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.003520 |
21
+ | Validation generated-token accuracy | 99.85% |
22
+ | Validation exact-answer accuracy | 99.32% |
23
+ | No-carry exact-answer accuracy | 97.27% |
24
+ | Single-carry exact-answer accuracy | 100.00% |
25
+ | Multiple-carry exact-answer accuracy | 98.44% |
26
+ | Carry-chain exact-answer accuracy | 97.27% |
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
+ - Seed: 0
39
+ - Source commit: `unavailable`
40
+
41
+ The complete resolved configuration, environment, metrics, source snapshot, and checkpoints are
42
+ available in [`training/`](./training/). Machine-readable hashes and metrics are in
43
+ [`export_manifest.json`](./export_manifest.json).
44
+
45
+ ## Loading
46
+
47
+ This repository contains custom Transformers code. For reproducible or security-sensitive use,
48
+ pin the commit revision printed by the uploader.
49
+
50
+ ```python
51
+ from transformers import AutoModelForCausalLM, AutoTokenizer
52
+
53
+ revision = "PINNED_COMMIT_HASH"
54
+ tokenizer = AutoTokenizer.from_pretrained(
55
+ "OWNER/REPO", trust_remote_code=True, revision=revision
56
+ )
57
+ model = AutoModelForCausalLM.from_pretrained(
58
+ "OWNER/REPO", trust_remote_code=True, revision=revision
59
+ )
60
+ inputs = tokenizer("0000 + 0000 =", return_tensors="pt")
61
+ output = model.generate(**inputs, max_new_tokens=model.config.answer_digits, do_sample=False)
62
+ print(tokenizer.decode(output[0], skip_special_tokens=True))
63
+ ```
64
+
65
+ ## Intended use and limitations
66
+
67
+ This model is intended for mechanistic-interpretability research on its configured fixed-width
68
+ addition task. It is not a general arithmetic system: inputs outside the configured grammar or
69
+ width are unsupported, and generated answers must not be treated as reliable calculations.
addition_transformer.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.position_embedding = nn.Embedding(config.max_seq_len, config.d_model)
27
+ self.blocks = nn.ModuleList(TransformerBlock(config) for _ in range(config.n_layers))
28
+ self.unembedding = nn.Linear(config.d_model, vocab_size, bias=False)
29
+
30
+ self.reset_parameters()
31
+
32
+ def reset_parameters(self) -> None:
33
+ initialize_module(self, self.config)
34
+
35
+ def forward(
36
+ self,
37
+ input_ids: torch.Tensor,
38
+ return_activations: bool = False,
39
+ ) -> TransformerOutput:
40
+ batch, seq_len = input_ids.shape
41
+ if seq_len > self.config.max_seq_len:
42
+ raise ValueError(f"Sequence length {seq_len} exceeds max_seq_len={self.config.max_seq_len}.")
43
+
44
+ positions = torch.arange(seq_len, device=input_ids.device)
45
+ x = self.token_embedding(input_ids) + self.position_embedding(positions)[None, :, :]
46
+ # Modula-inspired attention scaling and residual interpolation; see arXiv:2405.14813.
47
+ block_outputs: list[TransformerBlockOutput] = []
48
+ for block in self.blocks:
49
+ block_output = block(x, return_pattern=return_activations)
50
+ x = block_output.residual_after_mlp
51
+ if return_activations:
52
+ block_outputs.append(block_output)
53
+ logits = self.unembedding(x)
54
+
55
+ return TransformerOutput(
56
+ logits=logits,
57
+ block_outputs=tuple(block_outputs),
58
+ )
59
+
60
+ def symmetrized_mlp_tensor(self, detach: bool = True, layer: int = 0) -> torch.Tensor:
61
+ if not 0 <= layer < len(self.blocks):
62
+ raise IndexError(f"Layer {layer} is outside the model's {len(self.blocks)} layers.")
63
+ return self.blocks[layer].mlp.symmetrized_bilinear_tensor(detach=detach)
64
+
65
+ def analysis_tensors(self, detach: bool = True) -> dict[str, torch.Tensor]:
66
+ tensors: dict[str, torch.Tensor] = {}
67
+ for layer, block in enumerate(self.blocks):
68
+ prefix = f"blocks.{layer}"
69
+ bilinear_tensor = block.mlp.bilinear_tensor(detach=False)
70
+ tensors.update(
71
+ {
72
+ f"{prefix}.attention.W_Q": block.attention.W_Q.weight,
73
+ f"{prefix}.attention.W_K": block.attention.W_K.weight,
74
+ f"{prefix}.attention.W_V": block.attention.W_V.weight,
75
+ f"{prefix}.attention.W_O": block.attention.W_O.weight,
76
+ f"{prefix}.mlp.W_1": block.mlp.W_1.weight,
77
+ f"{prefix}.mlp.W_2": block.mlp.W_2.weight,
78
+ f"{prefix}.mlp.W_O": block.mlp.W_O.weight,
79
+ f"{prefix}.mlp.bilinear_tensor": bilinear_tensor,
80
+ f"{prefix}.mlp.symmetrized_bilinear_tensor": 0.5
81
+ * (bilinear_tensor + bilinear_tensor.transpose(-1, -2)),
82
+ }
83
+ )
84
+ return {name: tensor.detach() for name, tensor in tensors.items()} if detach else tensors
attention.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
10
+ @dataclass(frozen=True)
11
+ class AttentionOutput:
12
+ values: torch.Tensor
13
+ pattern: torch.Tensor | None = None
14
+
15
+
16
+ class CausalSelfAttention(nn.Module):
17
+ def __init__(self, d_model: int, n_heads: int, bias: bool = False) -> None:
18
+ super().__init__()
19
+ if d_model % n_heads != 0:
20
+ raise ValueError("d_model must be divisible by n_heads.")
21
+
22
+ self.d_model = d_model
23
+ self.n_heads = n_heads
24
+ self.d_head = d_model // n_heads
25
+
26
+ self.W_Q = nn.Linear(d_model, d_model, bias=bias)
27
+ self.W_K = nn.Linear(d_model, d_model, bias=bias)
28
+ self.W_V = nn.Linear(d_model, d_model, bias=bias)
29
+ self.W_O = nn.Linear(d_model, d_model, bias=bias)
30
+ self.register_buffer("_causal_mask", torch.empty(0, 0, dtype=torch.bool), persistent=False)
31
+
32
+ def forward(self, x: torch.Tensor, return_pattern: bool = False) -> AttentionOutput:
33
+ batch, seq_len, _ = x.shape
34
+ q = self._split_heads(self.W_Q(x))
35
+ k = self._split_heads(self.W_K(x))
36
+ v = self._split_heads(self.W_V(x))
37
+
38
+ scores = torch.matmul(q, k.transpose(-1, -2)) / self.d_head
39
+ if self._causal_mask.shape[0] < seq_len or self._causal_mask.device != x.device:
40
+ self._causal_mask = torch.triu(
41
+ torch.ones(seq_len, seq_len, dtype=torch.bool, device=x.device),
42
+ diagonal=1,
43
+ )
44
+ causal_mask = self._causal_mask[:seq_len, :seq_len]
45
+ scores = scores.masked_fill(causal_mask, float("-inf"))
46
+ pattern = F.softmax(scores, dim=-1)
47
+
48
+ attended = torch.matmul(pattern, v) / 3.0
49
+ values = self.W_O(self._merge_heads(attended, batch, seq_len))
50
+ return AttentionOutput(values=values, pattern=pattern if return_pattern else None)
51
+
52
+ def _split_heads(self, x: torch.Tensor) -> torch.Tensor:
53
+ batch, seq_len, _ = x.shape
54
+ return x.view(batch, seq_len, self.n_heads, self.d_head).transpose(1, 2)
55
+
56
+ def _merge_heads(self, x: torch.Tensor, batch: int, seq_len: int) -> torch.Tensor:
57
+ 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,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "answer_digits": 5,
3
+ "architecture_version": 2,
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": 2,
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
+ "residual_alpha": 0.5,
39
+ "tie_word_embeddings": false,
40
+ "transformers_version": "5.15.0",
41
+ "use_cache": false,
42
+ "vocab_size": 13
43
+ }
configuration_addition.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from transformers import PreTrainedConfig
4
+
5
+
6
+ class AdditionConfig(PreTrainedConfig):
7
+ model_type = "fixed-width-addition"
8
+
9
+ def __init__(
10
+ self,
11
+ operand_digits: int = 5,
12
+ d_model: int = 64,
13
+ n_heads: int = 4,
14
+ d_mlp: int = 128,
15
+ n_layers: int = 1,
16
+ max_seq_len: int = 19,
17
+ init_mode: str = "normal",
18
+ residual_alpha: float | None = None,
19
+ **kwargs,
20
+ ) -> None:
21
+ expected_residual_alpha = 1.0 / (2.0 * n_layers) if n_layers > 0 else 0.0
22
+ if residual_alpha is not None and residual_alpha != expected_residual_alpha:
23
+ raise ValueError(
24
+ f"residual_alpha must be 1 / (2 * n_layers) = {expected_residual_alpha}."
25
+ )
26
+ invariants = {
27
+ "answer_digits": operand_digits + 1,
28
+ "base": 10,
29
+ "vocab_size": 13,
30
+ "hidden_size": d_model,
31
+ "num_attention_heads": n_heads,
32
+ "intermediate_size": d_mlp,
33
+ "num_hidden_layers": n_layers,
34
+ "max_position_embeddings": max_seq_len,
35
+ "bias": False,
36
+ "normalization": "none",
37
+ "attention_logit_divisor": "d_head",
38
+ "attention_output_scale": 1.0 / 3.0,
39
+ "bos_token_id": 0,
40
+ "plus_token_id": 1,
41
+ "equals_token_id": 2,
42
+ "digit_token_offset": 3,
43
+ "architecture_version": 2,
44
+ "export_format_version": 2,
45
+ "use_cache": False,
46
+ "tie_word_embeddings": False,
47
+ "is_decoder": True,
48
+ "is_encoder_decoder": False,
49
+ "eos_token_id": None,
50
+ "pad_token_id": None,
51
+ }
52
+ for name, expected in invariants.items():
53
+ if name in kwargs and kwargs.pop(name) != expected:
54
+ raise ValueError(f"{name} is fixed at {expected!r} for this architecture.")
55
+ self.operand_digits = operand_digits
56
+ self.answer_digits = operand_digits + 1
57
+ self.base = 10
58
+ self.vocab_size = 13
59
+ self.d_model = d_model
60
+ self.hidden_size = d_model
61
+ self.n_heads = n_heads
62
+ self.num_attention_heads = n_heads
63
+ self.d_mlp = d_mlp
64
+ self.intermediate_size = d_mlp
65
+ self.n_layers = n_layers
66
+ self.num_hidden_layers = n_layers
67
+ self.max_seq_len = max_seq_len
68
+ self.max_position_embeddings = max_seq_len
69
+ self.init_mode = str(init_mode)
70
+ self.residual_alpha = expected_residual_alpha
71
+ self.bias = False
72
+ self.normalization = "none"
73
+ self.attention_logit_divisor = "d_head"
74
+ self.attention_output_scale = 1.0 / 3.0
75
+ self.bos_token_id = 0
76
+ self.plus_token_id = 1
77
+ self.equals_token_id = 2
78
+ self.digit_token_offset = 3
79
+ self.architecture_version = 2
80
+ self.export_format_version = 2
81
+ self.use_cache = False
82
+ self.tie_word_embeddings = False
83
+ self.is_decoder = True
84
+ self.is_encoder_decoder = False
85
+ self._validate_architecture()
86
+ super().__init__(
87
+ bos_token_id=self.bos_token_id,
88
+ eos_token_id=None,
89
+ pad_token_id=None,
90
+ tie_word_embeddings=False,
91
+ is_decoder=True,
92
+ **kwargs,
93
+ )
94
+
95
+ @property
96
+ def d_head(self) -> int:
97
+ return self.d_model // self.n_heads
98
+
99
+ def _validate_architecture(self) -> None:
100
+ if self.operand_digits < 1:
101
+ raise ValueError("operand_digits must be positive.")
102
+ if self.d_model < 1 or self.n_heads < 1 or self.d_mlp < 1 or self.n_layers < 1:
103
+ raise ValueError("Model dimensions must be positive.")
104
+ if self.d_model % self.n_heads != 0:
105
+ raise ValueError("d_model must be divisible by n_heads.")
106
+ required_length = 3 * self.operand_digits + 4
107
+ if self.max_seq_len < required_length:
108
+ raise ValueError(
109
+ f"max_seq_len={self.max_seq_len} is too small for {required_length} full-sequence tokens."
110
+ )
111
+ if self.init_mode not in {"normal", "orthogonal"}:
112
+ raise ValueError(f"Unsupported init_mode: {self.init_mode}")
113
+ expected_residual_alpha = 1.0 / (2.0 * self.n_layers)
114
+ if self.residual_alpha != expected_residual_alpha:
115
+ raise ValueError("residual_alpha must equal 1 / (2 * n_layers).")
116
+
117
+
118
+ AdditionConfig.register_for_auto_class("AutoConfig")
export_manifest.json ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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": "b64a05029567e76bcd032dc4ea57e18c1c805845a196ffab30af6b28a0d06165",
23
+ "addition_transformer.py": "af1e1e1641aeaf2f974252e7177730f6356278c4fff0f47686d88aeed5673089",
24
+ "attention.py": "d1c738ba96f937c39cd66ad470d786acfad70ac368ecb5c1c25290b20edc2106",
25
+ "bilinear_mlp.py": "95d8b248058bdbce7ea7c13ba9ebf49ee69b20b95714a43040f19c9c528225c7",
26
+ "config.json": "481d105a5848390efe0e98edae059cded1b22611902ea15d2ab8b079cea86517",
27
+ "configuration_addition.py": "7f152f6c5917adedfb211e9332a9db08780363671932190b7df1d782c4fa3de5",
28
+ "generation_config.json": "13cc88dd90bbb2a181ade617b6a41b35998e933cc1eb356f7906c320a7d04b5d",
29
+ "initialization.py": "01de663e2494370c6bb2a9d4f770411104dbc44cbb97917fa18ba89ed9959d32",
30
+ "model.safetensors": "68be5fc852029537ac74bc0f13af4533a3efda7a4aac85396bd3a94b62f47098",
31
+ "model_config.py": "211bc4cc3e7379dfe469d5acb3bcfbe4c65adf710af5f2dd21ea49212ccb08fb",
32
+ "modeling_addition.py": "b5349de597570045eb3c21803f6a4b077605f5468f99e26ddb1678964d01fcea",
33
+ "tokenization_addition.py": "bc63473854f8f8fecfc3375757d257dfb34a33a0c30ee5a1ddffb3b984b8602f",
34
+ "tokenizer_config.json": "630ab54705ab78ecddef9c0c6543f477036b5a71cb2736a3db59462f464cbd06",
35
+ "transformer_block.py": "a66ce47f5b504abcc83bcbdfecbc71e01a9968d10bde534ad375e0c7158f04dc",
36
+ "vocab.json": "d6fdf48e6e2087546f1bfb7285a71b1072f5452934e8a33e831d069f7faf56a8"
37
+ },
38
+ "final_metrics": {
39
+ "examples_processed": 2560000,
40
+ "grad/clip_fraction": 0.0,
41
+ "grad/global_norm": 0.17957071997225285,
42
+ "lr/adamw": 2.9999999999999997e-05,
43
+ "lr/muon": 0.002,
44
+ "test/carry_chain/exact_accuracy": 0.97265625,
45
+ "test/carry_chain/generated_token_accuracy": 0.99375,
46
+ "test/carry_chain/loss": 0.012557083368301391,
47
+ "test/carry_chain/position_0/accuracy": 0.99609375,
48
+ "test/carry_chain/position_1/accuracy": 0.9765625,
49
+ "test/carry_chain/position_2/accuracy": 0.99609375,
50
+ "test/carry_chain/position_3/accuracy": 1.0,
51
+ "test/carry_chain/position_4/accuracy": 1.0,
52
+ "test/carry_chain/teacher_forced_token_accuracy": 0.99453125,
53
+ "test/multiple_carry/exact_accuracy": 0.984375,
54
+ "test/multiple_carry/generated_token_accuracy": 0.996875,
55
+ "test/multiple_carry/loss": 0.005255512893199921,
56
+ "test/multiple_carry/position_0/accuracy": 1.0,
57
+ "test/multiple_carry/position_1/accuracy": 0.984375,
58
+ "test/multiple_carry/position_2/accuracy": 1.0,
59
+ "test/multiple_carry/position_3/accuracy": 1.0,
60
+ "test/multiple_carry/position_4/accuracy": 1.0,
61
+ "test/multiple_carry/teacher_forced_token_accuracy": 0.996875,
62
+ "test/no_carry/exact_accuracy": 0.97265625,
63
+ "test/no_carry/generated_token_accuracy": 0.99453125,
64
+ "test/no_carry/loss": 0.014795759320259094,
65
+ "test/no_carry/position_0/accuracy": 1.0,
66
+ "test/no_carry/position_1/accuracy": 0.9765625,
67
+ "test/no_carry/position_2/accuracy": 0.99609375,
68
+ "test/no_carry/position_3/accuracy": 1.0,
69
+ "test/no_carry/position_4/accuracy": 1.0,
70
+ "test/no_carry/teacher_forced_token_accuracy": 0.99453125,
71
+ "test/single_carry/exact_accuracy": 1.0,
72
+ "test/single_carry/generated_token_accuracy": 1.0,
73
+ "test/single_carry/loss": 0.0010194769129157066,
74
+ "test/single_carry/position_0/accuracy": 1.0,
75
+ "test/single_carry/position_1/accuracy": 1.0,
76
+ "test/single_carry/position_2/accuracy": 1.0,
77
+ "test/single_carry/position_3/accuracy": 1.0,
78
+ "test/single_carry/position_4/accuracy": 1.0,
79
+ "test/single_carry/teacher_forced_token_accuracy": 1.0,
80
+ "throughput/examples_per_second": 12624.497642389888,
81
+ "train/loss": 0.002294534471002407,
82
+ "train/token_accuracy": 0.9989843726158142,
83
+ "val/exact_accuracy": 0.9931640625,
84
+ "val/generated_token_accuracy": 0.99853515625,
85
+ "val/loss": 0.003520155092701316,
86
+ "val/position_0/accuracy": 0.99951171875,
87
+ "val/position_1/accuracy": 0.994140625,
88
+ "val/position_2/accuracy": 0.9990234375,
89
+ "val/position_3/accuracy": 1.0,
90
+ "val/position_4/accuracy": 1.0,
91
+ "val/teacher_forced_token_accuracy": 0.9986328125
92
+ },
93
+ "resolved_config": {
94
+ "data": {
95
+ "base": 10,
96
+ "digits": 4
97
+ },
98
+ "model": {
99
+ "d_mlp": 128,
100
+ "d_model": 64,
101
+ "init_mode": "normal",
102
+ "max_seq_len": 16,
103
+ "n_heads": 4,
104
+ "n_layers": 1
105
+ },
106
+ "train": {
107
+ "adamw_lr": 0.0003,
108
+ "batch_size": 256,
109
+ "checkpoint_every": 500,
110
+ "device": "auto",
111
+ "eval_batch_size": 512,
112
+ "eval_every": 100,
113
+ "eval_seed": 1,
114
+ "grad_clip_norm": 1.0,
115
+ "log_every": 10,
116
+ "min_lr_ratio": 0.1,
117
+ "muon_lr": 0.02,
118
+ "num_targeted_examples": 256,
119
+ "num_val_examples": 2048,
120
+ "optimizer": "muon",
121
+ "progress_bar": true,
122
+ "run_root": ".",
123
+ "seed": 0,
124
+ "steps": 10000,
125
+ "weight_decay": 0.01
126
+ },
127
+ "wandb": {
128
+ "entity": null,
129
+ "group": null,
130
+ "log_model": false,
131
+ "mode": "auto",
132
+ "name": null,
133
+ "project": "circuits-addition",
134
+ "tags": []
135
+ }
136
+ },
137
+ "run_id": "s85nnxtf",
138
+ "schema_version": 1,
139
+ "training_files_sha256": {
140
+ "checkpoints/final.pt": "8ab3e4443c586c75fde95d36fdfed473f4e6fbf374bd318e6165e875727c5285",
141
+ "environment.json": "6c7ded30b327853d18e87320549e118d92e48cb606ce96daa673c94eb07a965a",
142
+ "metrics.jsonl": "da503ac47bc1604fc84d07353c9eab7acc1b82be07dc846960e1979049e47d4f",
143
+ "resolved_config.toml": "c87577c6f48541b9243ba37952ba82eb11cc008c62bc075779423f8ba35e7c68",
144
+ "source_snapshot.zip": "a4aca21623a11159fef2e4d3dce007e03737b6909872cda92210c3903c5ea357"
145
+ },
146
+ "training_step": 10000
147
+ }
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:68be5fc852029537ac74bc0f13af4533a3efda7a4aac85396bd3a94b62f47098
3
+ size 175592
model_config.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ init_mode: str
13
+ 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")
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:8ab3e4443c586c75fde95d36fdfed473f4e6fbf374bd318e6165e875727c5285
3
+ size 383443
training/checkpoints/step_000500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06d05526d4b34992b4da3a982cbb8badad4df5f1a9550617dda1c6fb4afa4a70
3
+ size 381663
training/checkpoints/step_001000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:200f8aef8138075175f2a153cbcd97c1269a83b69990b9f7a9294dfab95b1808
3
+ size 381727
training/checkpoints/step_001500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:772ae2a4867745aeb9931c210603c6d334140ac279a29b1b3f6ca0733c29fcc5
3
+ size 381663
training/checkpoints/step_002000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:edab6419dd327151bf51b9a314c08aec9fecdff60d1196f6958bde5da8b8d682
3
+ size 381663
training/checkpoints/step_002500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2adc6ebebdd9016fb311619613154e1db9c570385594b48de2605296074d39fd
3
+ size 381663
training/checkpoints/step_003000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7136124680252d2a8aa94cbe6c865215c345c90b517ce741000934610fbdbb5e
3
+ size 381663
training/checkpoints/step_003500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ac6a0222a796adc6f11b013a4f56ca1afe9c19f6bd7225c7c3a9b3203afd6a6d
3
+ size 381663
training/checkpoints/step_004000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4cb9659ba205822deb527d40942c998040aa8f885d59200f15cb6ebbdbc7d1ec
3
+ size 381599
training/checkpoints/step_004500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:559b0aeb57198d423779e49c4e96faaf88ce0f01062cf26b4c6584b01ec8a813
3
+ size 381663
training/checkpoints/step_005000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ac80e4bd49d0e656c668b6b9a43743c352d5fe198792c10a1c86ff3a6c72fc1b
3
+ size 381663
training/checkpoints/step_005500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bd2b9f0b15fae7313f80ce1ce890fd58f4ba810f28bf615b8023d8c69bc17fa8
3
+ size 381663
training/checkpoints/step_006000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e4ff8b1b2e7de3594bc19a5544bb38a745b43512360a5511249072551e720894
3
+ size 381663
training/checkpoints/step_006500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c377fa05c9be37a02c1013fc38f30b07d9e477c1bdfe051931749601b427be32
3
+ size 381663
training/checkpoints/step_007000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:47b3d5544981cb3b76ac807ca3f5edfd0f43034a598629d9c6966487f78f39df
3
+ size 381727
training/checkpoints/step_007500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8fc1f3fd25e18774689a0b39bd0d84c665f0ceb4d42a043a9ff2c73cfed0b8c7
3
+ size 381663
training/checkpoints/step_008000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97767f8940dbfbfd4482f74585735f89e88cadae42720a4cabd9df4f3b99f877
3
+ size 381663
training/checkpoints/step_008500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b63c4feec4a98147b79add7eef550c0e646111d950a0bf90cdd44ad93eac80d3
3
+ size 381663
training/checkpoints/step_009000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce8731c55d43b4f6654396f1c136e5d7e647dc79b02982eb9adbe20fb1346f82
3
+ size 381663
training/checkpoints/step_009500.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:778c9a47d189067cbd932e93f36a067acd67cc775d75ade7efe30cce8024fbf6
3
+ size 381663
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,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ init_mode = "normal"
12
+
13
+ [train]
14
+ steps = 10000
15
+ batch_size = 256
16
+ optimizer = "muon"
17
+ muon_lr = 0.02
18
+ adamw_lr = 0.0003
19
+ weight_decay = 0.01
20
+ min_lr_ratio = 0.1
21
+ grad_clip_norm = 1.0
22
+ log_every = 10
23
+ eval_every = 100
24
+ checkpoint_every = 500
25
+ progress_bar = true
26
+ run_root = "."
27
+ seed = 0
28
+ eval_seed = 1
29
+ device = "auto"
30
+ num_val_examples = 2048
31
+ num_targeted_examples = 256
32
+ eval_batch_size = 512
33
+
34
+ [wandb]
35
+ mode = "auto"
36
+ project = "circuits-addition"
37
+ tags = []
38
+ log_model = false
training/source_snapshot.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a4aca21623a11159fef2e4d3dce007e03737b6909872cda92210c3903c5ea357
3
+ size 160699
transformer_block.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(config.d_model, config.n_heads, bias=False)
28
+ self.mlp = BilinearMLP(config.d_model, config.d_mlp, bias=False)
29
+
30
+ def forward(self, x: torch.Tensor, return_pattern: bool = False) -> TransformerBlockOutput:
31
+ attention_output = self.attention(x, return_pattern=return_pattern)
32
+ residual_after_attention = torch.lerp(x, attention_output.values, self.residual_alpha)
33
+ mlp_out = self.mlp(residual_after_attention)
34
+ residual_after_mlp = torch.lerp(residual_after_attention, mlp_out, self.residual_alpha)
35
+ return TransformerBlockOutput(
36
+ residual_pre_attention=x,
37
+ attention_out=attention_output.values,
38
+ attention_pattern=attention_output.pattern,
39
+ residual_after_attention=residual_after_attention,
40
+ mlp_out=mlp_out,
41
+ residual_after_mlp=residual_after_mlp,
42
+ )
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
+ }