Text Generation
Transformers
Safetensors
fixed-width-addition
arithmetic
interpretability
arxiv:2405.14813
custom_code
Instructions to use melephant/1-layer-addition-v2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use melephant/1-layer-addition-v2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="melephant/1-layer-addition-v2", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("melephant/1-layer-addition-v2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use melephant/1-layer-addition-v2 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "melephant/1-layer-addition-v2" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "melephant/1-layer-addition-v2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/melephant/1-layer-addition-v2
- SGLang
How to use melephant/1-layer-addition-v2 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "melephant/1-layer-addition-v2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "melephant/1-layer-addition-v2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "melephant/1-layer-addition-v2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "melephant/1-layer-addition-v2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use melephant/1-layer-addition-v2 with Docker Model Runner:
docker model run hf.co/melephant/1-layer-addition-v2
Publish addition-transformer run rmb41c76
Browse filesLoadable model and complete training record.
- README.md +70 -0
- addition_transformer.py +82 -0
- attention.py +68 -0
- bilinear_mlp.py +33 -0
- config.json +46 -0
- configuration_addition.py +130 -0
- export_manifest.json +149 -0
- generation_config.json +7 -0
- initialization.py +41 -0
- model.safetensors +3 -0
- model_config.py +14 -0
- modeling_addition.py +123 -0
- rotary_embedding.py +67 -0
- tokenization_addition.py +111 -0
- tokenizer_config.json +25 -0
- training/checkpoints/final.pt +3 -0
- training/checkpoints/step_000500.pt +3 -0
- training/checkpoints/step_001000.pt +3 -0
- training/checkpoints/step_001500.pt +3 -0
- training/checkpoints/step_002000.pt +3 -0
- training/checkpoints/step_002500.pt +3 -0
- training/checkpoints/step_003000.pt +3 -0
- training/checkpoints/step_003500.pt +3 -0
- training/checkpoints/step_004000.pt +3 -0
- training/checkpoints/step_004500.pt +3 -0
- training/checkpoints/step_005000.pt +3 -0
- training/checkpoints/step_005500.pt +3 -0
- training/checkpoints/step_006000.pt +3 -0
- training/checkpoints/step_006500.pt +3 -0
- training/checkpoints/step_007000.pt +3 -0
- training/checkpoints/step_007500.pt +3 -0
- training/checkpoints/step_008000.pt +3 -0
- training/checkpoints/step_008500.pt +3 -0
- training/checkpoints/step_009000.pt +3 -0
- training/checkpoints/step_009500.pt +3 -0
- training/environment.json +19 -0
- training/metrics.jsonl +0 -0
- training/resolved_config.toml +39 -0
- training/source_snapshot.zip +3 -0
- transformer_block.py +48 -0
- vocab.json +15 -0
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 |
+
}
|