Text Generation
Transformers
Safetensors
English
tbbloop
tinybrainbot
small-language-model
from-scratch
recurrent-depth
looped-transformer
arithmetic
custom_code
base
Eval Results (legacy)
Instructions to use nkthebass/tinybrainbot-3m-loop-v2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use nkthebass/tinybrainbot-3m-loop-v2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="nkthebass/tinybrainbot-3m-loop-v2", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("nkthebass/tinybrainbot-3m-loop-v2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use nkthebass/tinybrainbot-3m-loop-v2 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "nkthebass/tinybrainbot-3m-loop-v2" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "nkthebass/tinybrainbot-3m-loop-v2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/nkthebass/tinybrainbot-3m-loop-v2
- SGLang
How to use nkthebass/tinybrainbot-3m-loop-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 "nkthebass/tinybrainbot-3m-loop-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": "nkthebass/tinybrainbot-3m-loop-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 "nkthebass/tinybrainbot-3m-loop-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": "nkthebass/tinybrainbot-3m-loop-v2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use nkthebass/tinybrainbot-3m-loop-v2 with Docker Model Runner:
docker model run hf.co/nkthebass/tinybrainbot-3m-loop-v2
TinyBrainBot-3M-Loop-v2: 3M-Loop continued on general math (final checkpoint)
Browse files- README.md +174 -0
- config.json +45 -0
- configuration_tbbloop.py +11 -0
- model.safetensors +3 -0
- modeling_tbbloop.py +415 -0
- special_tokens_map.json +14 -0
- tbb_meta.json +38 -0
- tokenization_tbbuf.py +61 -0
- tokenizer.json +0 -0
- tokenizer_config.json +167 -0
README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
library_name: transformers
|
| 6 |
+
pipeline_tag: text-generation
|
| 7 |
+
tags:
|
| 8 |
+
- tinybrainbot
|
| 9 |
+
- small-language-model
|
| 10 |
+
- from-scratch
|
| 11 |
+
- recurrent-depth
|
| 12 |
+
- looped-transformer
|
| 13 |
+
- arithmetic
|
| 14 |
+
- custom_code
|
| 15 |
+
- base
|
| 16 |
+
datasets:
|
| 17 |
+
- HuggingFaceFW/fineweb-edu
|
| 18 |
+
- HuggingFaceTB/cosmopedia
|
| 19 |
+
- roneneldan/TinyStories
|
| 20 |
+
- wikimedia/wikipedia
|
| 21 |
+
model-index:
|
| 22 |
+
- name: tinybrainbot-3m-loop-v2
|
| 23 |
+
results:
|
| 24 |
+
- task:
|
| 25 |
+
type: text-generation
|
| 26 |
+
dataset:
|
| 27 |
+
type: allenai/ai2_arc
|
| 28 |
+
name: ARC-Easy
|
| 29 |
+
config: ARC-Easy
|
| 30 |
+
split: test
|
| 31 |
+
metrics:
|
| 32 |
+
- type: acc_norm
|
| 33 |
+
value: 33.46
|
| 34 |
+
name: acc_norm (0-shot)
|
| 35 |
+
- task:
|
| 36 |
+
type: text-generation
|
| 37 |
+
dataset:
|
| 38 |
+
type: allenai/ai2_arc
|
| 39 |
+
name: ARC-Challenge
|
| 40 |
+
config: ARC-Challenge
|
| 41 |
+
split: test
|
| 42 |
+
metrics:
|
| 43 |
+
- type: acc_norm
|
| 44 |
+
value: 22.78
|
| 45 |
+
name: acc_norm (0-shot)
|
| 46 |
+
- task:
|
| 47 |
+
type: text-generation
|
| 48 |
+
dataset:
|
| 49 |
+
type: Rowan/hellaswag
|
| 50 |
+
name: HellaSwag
|
| 51 |
+
split: validation
|
| 52 |
+
metrics:
|
| 53 |
+
- type: acc_norm
|
| 54 |
+
value: 27.17
|
| 55 |
+
name: acc_norm (0-shot)
|
| 56 |
+
- task:
|
| 57 |
+
type: text-generation
|
| 58 |
+
dataset:
|
| 59 |
+
type: ybisk/piqa
|
| 60 |
+
name: PIQA
|
| 61 |
+
split: validation
|
| 62 |
+
metrics:
|
| 63 |
+
- type: acc_norm
|
| 64 |
+
value: 54.79
|
| 65 |
+
name: acc_norm (0-shot)
|
| 66 |
+
- task:
|
| 67 |
+
type: text-generation
|
| 68 |
+
dataset:
|
| 69 |
+
type: AxiomicLabs/Arithmark-3.0
|
| 70 |
+
name: ArithMark-3.0
|
| 71 |
+
split: train
|
| 72 |
+
metrics:
|
| 73 |
+
- type: acc_norm
|
| 74 |
+
value: 90.70
|
| 75 |
+
name: acc_norm (0-shot)
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
# TinyBrainBot-3M-Loop-v2
|
| 79 |
+
|
| 80 |
+
**A 2.96M-parameter math-first language model that reuses its layers.** Four transformer blocks are run three times in a loop, so every token passes through 12 layers of computation while only 4 layers of weights are stored. Trained from scratch on 6.0B tokens. Compared with [TinyBrainBot-3M-Loop](https://huggingface.co/nkthebass/tinybrainbot-3m-loop), its arithmetic carries over to bare expressions, order of operations and numbers written in words, not only word-problem stories.
|
| 81 |
+
|
| 82 |
+
| Benchmark (0-shot, full set) | acc_norm |
|
| 83 |
+
|---|--:|
|
| 84 |
+
| **Open SLM Leaderboard Intelligence Index** | **20.15** |
|
| 85 |
+
| *(raw acc)* | *19.91* |
|
| 86 |
+
| HellaSwag | 27.17 |
|
| 87 |
+
| *(raw acc)* | *27.01* |
|
| 88 |
+
| ARC-Easy | 33.46 |
|
| 89 |
+
| *(raw acc)* | *33.59* |
|
| 90 |
+
| ARC-Challenge | 22.78 |
|
| 91 |
+
| *(raw acc)* | *18.17* |
|
| 92 |
+
| PIQA | 54.79 |
|
| 93 |
+
| *(raw acc)* | *55.98* |
|
| 94 |
+
| ArithMark-3.0 | 90.7 |
|
| 95 |
+
| *(raw acc)* | *90.6* |
|
| 96 |
+
|
| 97 |
+
lm-eval 0.4.13 in float32 for HellaSwag, ARC and PIQA; the official `bencharithmark-3.py` (float32) for ArithMark-3.0.
|
| 98 |
+
|
| 99 |
+
## Arithmetic beyond one benchmark
|
| 100 |
+
|
| 101 |
+
Every column below is a zero-shot, log-likelihood multiple-choice math test, all scored by us with the official `bencharithmark-3.py` scoring in float32, so every model is measured the same way (numbers can differ slightly from leaderboard listings).
|
| 102 |
+
|
| 103 |
+
| Model | Params | ArithMark-3 | AM3, numbers in words | AM3 as bare equations | ArithMark-2 | ArithMark-1 | Held-out stories |
|
| 104 |
+
|---|--:|--:|--:|--:|--:|--:|--:|
|
| 105 |
+
| **TinyBrainBot-3M-Loop-v2** | 2.96M | **90.7** | **55.0** | **96.8** | **83.8** | 36.1 | **80.1** |
|
| 106 |
+
| TinyBrainBot-3M-Loop | 2.96M | 89.3 | 27.3 | 88.2 | 58.1 | 29.4 | 81.0 |
|
| 107 |
+
| Inferno-Exp-1.7 | 394M | 85.9 | 59.1 | 86.9 | 59.9 | 47.9 | 74.0 |
|
| 108 |
+
| Pascaline-3M | 3M | 69.2 | 27.6 | 99.7 | 66.5 | 36.2 | 43.0 |
|
| 109 |
+
| SmolLM2-135M | 135M | 38.8 | 34.2 | 38.6 | 33.2 | 33.1 | 34.7 |
|
| 110 |
+
| GPT-S3-3M | 2.97M | 38.9 | 25.8 | 36.2 | 31.0 | 31.1 | 34.6 |
|
| 111 |
+
|
| 112 |
+
- **Numbers in words** and **bare equations** are ArithMark-3's own problems with only the surface changed.
|
| 113 |
+
- **ArithMark-2** uses its official metric (raw log-likelihood); its official `benchmark_arithmark-2.0.py` gives the same results.
|
| 114 |
+
- **Held-out stories:** about 1,600 ArithMark-style problems written by Gemma-3-27B and Llama-3.3-70B, models that wrote none of our training data, with every number and answer set by Python.
|
| 115 |
+
- **ArithMark-1:** its answer key mostly evaluates expressions strictly left to right (e.g. `14 * -17 + -1 * -18 = 4302`), while ArithMark-2 and ordinary arithmetic use operator precedence (that expression is -220). This model follows precedence, so it scores lower on ArithMark-1's longer expressions (62% on its two-operand stage, falling with each extra operator).
|
| 116 |
+
|
| 117 |
+
## How it works
|
| 118 |
+
|
| 119 |
+
- **Recurrent depth.** 4 shared blocks (width 256, 4 attention heads of 64 with 2 KV heads, SwiGLU 560, RoPE, RMSNorm) are applied 3 times. Each pass adds a learned loop embedding and a gated copy of the input embedding. Training sampled 2 to 4 loops per step, and you can change the count at inference (`model.model.eval_loops = 4`); 3 is best.
|
| 120 |
+
- **Factorized embedding.** The 4,104-token vocabulary uses 96-dimensional embeddings projected up to 256 (and down again before the tied output layer), which frees about 630k parameters for the 4th block.
|
| 121 |
+
- **Numbers.** A units-first digit tokenizer writes every number as a length token plus its digits, smallest place first, and a digit-place embedding tells the model which place each digit is in.
|
| 122 |
+
|
| 123 |
+
## Training
|
| 124 |
+
|
| 125 |
+
**Stage 1 (4.46B tokens):** the TinyBrainBot-3M-Loop run up to the start of its learning-rate decay.
|
| 126 |
+
|
| 127 |
+
| Part | Share | What |
|
| 128 |
+
|---|--:|---|
|
| 129 |
+
| Q&A and facts | 25% | our distilled Q&A, short facts, explanations |
|
| 130 |
+
| Arithmetic | 20% | word problems: about 150k different story wordings written by ling-3.0-flash and Mistral-Small-3.2, each checked by solving it, with every number and answer filled in by Python; plus our own relation (more/fewer than) and story generators |
|
| 131 |
+
| Synthetic textbooks | 15% | Cosmopedia (Stanford, OpenStax) |
|
| 132 |
+
| Stories | 15% | TinyStories |
|
| 133 |
+
| Commonsense | 10% | Cosmopedia WikiHow, our everyday-commonsense prose |
|
| 134 |
+
| Web | 10% | FineWeb-Edu |
|
| 135 |
+
| Reference | 5% | Wikipedia |
|
| 136 |
+
|
| 137 |
+
**Stage 2 (1.5B tokens, math-first):** constant learning rate for 1.0B tokens, then a decay to zero over the last 0.5B.
|
| 138 |
+
|
| 139 |
+
| Part | Share | What |
|
| 140 |
+
|---|--:|---|
|
| 141 |
+
| Story arithmetic | 25% | the same verified wordings, with a quarter of the problems' numbers written in words |
|
| 142 |
+
| Bare arithmetic | 25% | our generator: expressions with order of operations, parentheses, negative numbers and 2-5 operands, in about 19 forms ("43 + 19 = 62", "What is ...?", "Compute ...:") |
|
| 143 |
+
| Number words | 8% | words to digits and back, arithmetic in words, counting, comparisons |
|
| 144 |
+
| Relations and stories | 12% | our relation and story generators |
|
| 145 |
+
| Q&A and facts | 20% | as in stage 1 |
|
| 146 |
+
| Cosmopedia, FineWeb-Edu | 10% | as in stage 1 |
|
| 147 |
+
|
| 148 |
+
No benchmark training or test data was used, and none of our generators copy any benchmark's problems, code or sentences.
|
| 149 |
+
|
| 150 |
+
## Use
|
| 151 |
+
|
| 152 |
+
```python
|
| 153 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 154 |
+
repo = "nkthebass/tinybrainbot-3m-loop-v2"
|
| 155 |
+
tok = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
|
| 156 |
+
model = AutoModelForCausalLM.from_pretrained(repo, trust_remote_code=True)
|
| 157 |
+
ids = tok("Mia had 46 stickers. She gave 18 to her brother, so now Mia has", return_tensors="pt").input_ids
|
| 158 |
+
out = model.generate(ids, max_new_tokens=8, do_sample=False)
|
| 159 |
+
print(tok.decode(out[0][ids.shape[1]:]))
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
`trust_remote_code=True` is required (custom architecture and tokenizer). There is no KV cache: generation recomputes the prefix, which is fast at this size. Padded batches work on either side (the model honors `attention_mask`). There is no GGUF, because llama.cpp does not support the looped architecture.
|
| 163 |
+
|
| 164 |
+
To reproduce the scores:
|
| 165 |
+
```
|
| 166 |
+
lm_eval --model hf --model_args pretrained=nkthebass/tinybrainbot-3m-loop-v2,dtype=float32,trust_remote_code=True --tasks arc_easy,arc_challenge,hellaswag,piqa --num_fewshot 0
|
| 167 |
+
python bencharithmark-3.py --model nkthebass/tinybrainbot-3m-loop-v2 --dtype float32
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
## Limitations
|
| 171 |
+
|
| 172 |
+
- A 3M base model: it writes simple English and makes up facts. It is not a chat model.
|
| 173 |
+
- Arithmetic is strongest on short problems; long, chatty setups and long left-to-right chains lower its accuracy.
|
| 174 |
+
- 1,024-token context.
|
config.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"TBBLoopForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"model_type": "tbbloop",
|
| 6 |
+
"auto_map": {
|
| 7 |
+
"AutoConfig": "configuration_tbbloop.TBBLoopConfig",
|
| 8 |
+
"AutoModelForCausalLM": "modeling_tbbloop.TBBLoopForCausalLM"
|
| 9 |
+
},
|
| 10 |
+
"native": {
|
| 11 |
+
"vocab_size": 4104,
|
| 12 |
+
"hidden_size": 256,
|
| 13 |
+
"n_layers": 3,
|
| 14 |
+
"n_heads": 4,
|
| 15 |
+
"head_dim": 64,
|
| 16 |
+
"n_kv_heads": 2,
|
| 17 |
+
"ffn_size": 560,
|
| 18 |
+
"context_length": 1024,
|
| 19 |
+
"rope_theta": 10000.0,
|
| 20 |
+
"rms_norm_eps": 1e-05,
|
| 21 |
+
"tie_embeddings": true,
|
| 22 |
+
"init_std": 0.02,
|
| 23 |
+
"dropout": 0.0,
|
| 24 |
+
"gradient_checkpointing": false,
|
| 25 |
+
"loop_unique": 4,
|
| 26 |
+
"n_loops": 3,
|
| 27 |
+
"loop_train_min": 2,
|
| 28 |
+
"loop_train_max": 4,
|
| 29 |
+
"digit_place": true,
|
| 30 |
+
"digit_lo": 24,
|
| 31 |
+
"digit_hi": 33,
|
| 32 |
+
"place_vocab": 16,
|
| 33 |
+
"embed_rank": 96
|
| 34 |
+
},
|
| 35 |
+
"eval_loops": 0,
|
| 36 |
+
"vocab_size": 4104,
|
| 37 |
+
"hidden_size": 256,
|
| 38 |
+
"num_hidden_layers": 4,
|
| 39 |
+
"max_position_embeddings": 1024,
|
| 40 |
+
"tie_word_embeddings": true,
|
| 41 |
+
"bos_token_id": 0,
|
| 42 |
+
"eos_token_id": 0,
|
| 43 |
+
"torch_dtype": "float32",
|
| 44 |
+
"use_cache": false
|
| 45 |
+
}
|
configuration_tbbloop.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Config for TinyBrainBot recurrent-depth models (fields mirror the native TinyBrainBotConfig)."""
|
| 2 |
+
from transformers import PretrainedConfig
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class TBBLoopConfig(PretrainedConfig):
|
| 6 |
+
model_type = "tbbloop"
|
| 7 |
+
|
| 8 |
+
def __init__(self, native=None, eval_loops=0, tie_word_embeddings=True, **kwargs):
|
| 9 |
+
self.native = native or {}
|
| 10 |
+
self.eval_loops = eval_loops
|
| 11 |
+
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0c14a8e8c807fe53e39dabe1a86e09b1d790822f4e80efe35bf250e67aa9ba93
|
| 3 |
+
size 11861372
|
modeling_tbbloop.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""TinyBrainBot recurrent-depth model for transformers (trust_remote_code=True).
|
| 2 |
+
|
| 3 |
+
Below the marker is the exact architecture source the model was trained with. The wrapper builds it from the
|
| 4 |
+
config, so weights load unchanged. Generation recomputes the prefix each step (no KV cache): fine at this size.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
# ======================= native architecture (verbatim) =======================
|
| 8 |
+
"""TinyBrainBot reference architecture.
|
| 9 |
+
|
| 10 |
+
Matches the blueprint spec:
|
| 11 |
+
- decoder-only transformer
|
| 12 |
+
- 8 layers, hidden 1536, 12 heads (head_dim = 128), FFN 6144
|
| 13 |
+
- RoPE positional encoding
|
| 14 |
+
- RMSNorm (pre-norm)
|
| 15 |
+
- SwiGLU feed-forward
|
| 16 |
+
- 36k Unigram vocab
|
| 17 |
+
- ctx 2048
|
| 18 |
+
|
| 19 |
+
The defaults here produce a ~300M parameter model. Tests use smaller overrides.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from dataclasses import dataclass, field, asdict
|
| 23 |
+
from typing import Optional
|
| 24 |
+
|
| 25 |
+
import math
|
| 26 |
+
|
| 27 |
+
import torch
|
| 28 |
+
import torch.nn as nn
|
| 29 |
+
import torch.nn.functional as F
|
| 30 |
+
from torch.utils.checkpoint import checkpoint as _ckpt
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class TinyBrainBotConfig:
|
| 35 |
+
# Blueprint defaults
|
| 36 |
+
vocab_size: int = 36_000
|
| 37 |
+
hidden_size: int = 1536
|
| 38 |
+
n_layers: int = 8
|
| 39 |
+
n_heads: int = 12
|
| 40 |
+
head_dim: int = 128
|
| 41 |
+
n_kv_heads: Optional[int] = None # None => MHA (== n_heads); a smaller divisor => GQA
|
| 42 |
+
ffn_size: int = 6144
|
| 43 |
+
context_length: int = 2048
|
| 44 |
+
rope_theta: float = 10_000.0
|
| 45 |
+
rms_norm_eps: float = 1e-5
|
| 46 |
+
tie_embeddings: bool = True
|
| 47 |
+
init_std: float = 0.02
|
| 48 |
+
dropout: float = 0.0
|
| 49 |
+
gradient_checkpointing: bool = False
|
| 50 |
+
# recurrent depth (opt-in): store `loop_unique` blocks and run them `n_loops` times. 0 = ordinary stack.
|
| 51 |
+
loop_unique: int = 0
|
| 52 |
+
n_loops: int = 1
|
| 53 |
+
loop_train_min: int = 0 # training samples the loop count from [min, max] per step (0 = n_loops)
|
| 54 |
+
loop_train_max: int = 0
|
| 55 |
+
# digit-place embedding (opt-in): each digit token gets its index in its digit run (1 = first digit; with
|
| 56 |
+
# units-first numbers that is the units digit). Digit token ids are digit_lo..digit_hi.
|
| 57 |
+
digit_place: bool = False
|
| 58 |
+
digit_lo: int = -1
|
| 59 |
+
digit_hi: int = -1
|
| 60 |
+
place_vocab: int = 16
|
| 61 |
+
# factorized embedding (opt-in): a vocab x embed_rank table, projected up to hidden_size on the way in and down
|
| 62 |
+
# to embed_rank before the (tied) output layer. 0 = ordinary vocab x hidden_size embedding.
|
| 63 |
+
embed_rank: int = 0
|
| 64 |
+
|
| 65 |
+
@property
|
| 66 |
+
def attention_dim(self) -> int:
|
| 67 |
+
return self.n_heads * self.head_dim
|
| 68 |
+
|
| 69 |
+
@property
|
| 70 |
+
def num_kv_heads(self) -> int:
|
| 71 |
+
"""KV head count — equals n_heads for MHA, fewer for GQA."""
|
| 72 |
+
return self.n_kv_heads if self.n_kv_heads is not None else self.n_heads
|
| 73 |
+
|
| 74 |
+
@property
|
| 75 |
+
def kv_dim(self) -> int:
|
| 76 |
+
return self.num_kv_heads * self.head_dim
|
| 77 |
+
|
| 78 |
+
def summary(self) -> dict:
|
| 79 |
+
return {
|
| 80 |
+
**asdict(self),
|
| 81 |
+
"attention_dim": self.attention_dim,
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# ----------------------------------------------------------------------------
|
| 86 |
+
# Building blocks
|
| 87 |
+
# ----------------------------------------------------------------------------
|
| 88 |
+
|
| 89 |
+
class RMSNorm(nn.Module):
|
| 90 |
+
def __init__(self, dim: int, eps: float = 1e-5):
|
| 91 |
+
super().__init__()
|
| 92 |
+
self.eps = eps
|
| 93 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 94 |
+
|
| 95 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 96 |
+
# computed in float32 for stability, cast back to input dtype
|
| 97 |
+
orig_dtype = x.dtype
|
| 98 |
+
x = x.float()
|
| 99 |
+
variance = x.pow(2).mean(dim=-1, keepdim=True)
|
| 100 |
+
x = x * torch.rsqrt(variance + self.eps)
|
| 101 |
+
return (self.weight * x).to(orig_dtype)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _precompute_rope_cache(dim: int, max_len: int, theta: float, device, dtype):
|
| 105 |
+
# standard RoPE frequencies
|
| 106 |
+
inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2, device=device).float() / dim))
|
| 107 |
+
t = torch.arange(max_len, device=device).float()
|
| 108 |
+
freqs = torch.outer(t, inv_freq) # [max_len, dim/2]
|
| 109 |
+
cos = freqs.cos().to(dtype)
|
| 110 |
+
sin = freqs.sin().to(dtype)
|
| 111 |
+
return cos, sin
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
|
| 115 |
+
# x: [B, H, T, D]; cos/sin: [T, D/2], or [B, T, D/2] for per-row positions (padded batches)
|
| 116 |
+
x1, x2 = x[..., ::2], x[..., 1::2]
|
| 117 |
+
if cos.dim() == 3:
|
| 118 |
+
cos_b, sin_b = cos.unsqueeze(1), sin.unsqueeze(1)
|
| 119 |
+
else:
|
| 120 |
+
# broadcast cos/sin over [B, H, T, D/2]
|
| 121 |
+
cos_b = cos.unsqueeze(0).unsqueeze(0)
|
| 122 |
+
sin_b = sin.unsqueeze(0).unsqueeze(0)
|
| 123 |
+
y1 = x1 * cos_b - x2 * sin_b
|
| 124 |
+
y2 = x1 * sin_b + x2 * cos_b
|
| 125 |
+
out = torch.stack([y1, y2], dim=-1).flatten(-2)
|
| 126 |
+
return out
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def _repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
|
| 130 |
+
"""Expand KV heads to match query heads for GQA. x: [B, n_kv, T, head_dim]."""
|
| 131 |
+
if n_rep == 1:
|
| 132 |
+
return x
|
| 133 |
+
B, n_kv, T, hd = x.shape
|
| 134 |
+
return x[:, :, None, :, :].expand(B, n_kv, n_rep, T, hd).reshape(B, n_kv * n_rep, T, hd)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class CausalSelfAttention(nn.Module):
|
| 138 |
+
def __init__(self, cfg: TinyBrainBotConfig):
|
| 139 |
+
super().__init__()
|
| 140 |
+
self.cfg = cfg
|
| 141 |
+
nkv = cfg.num_kv_heads
|
| 142 |
+
assert cfg.n_heads % nkv == 0, "n_heads must be divisible by n_kv_heads"
|
| 143 |
+
# Combined Q/K/V. With MHA (nkv == n_heads) this is 3*attention_dim — the exact
|
| 144 |
+
# shape older checkpoints stored, so they load unchanged. With GQA it shrinks K/V.
|
| 145 |
+
self.qkv = nn.Linear(cfg.hidden_size, (cfg.n_heads + 2 * nkv) * cfg.head_dim, bias=False)
|
| 146 |
+
self.out_proj = nn.Linear(cfg.attention_dim, cfg.hidden_size, bias=False)
|
| 147 |
+
self.scale = 1.0 / math.sqrt(cfg.head_dim)
|
| 148 |
+
self._q_size = cfg.n_heads * cfg.head_dim
|
| 149 |
+
self._kv_size = nkv * cfg.head_dim
|
| 150 |
+
self._n_rep = cfg.n_heads // nkv
|
| 151 |
+
|
| 152 |
+
def forward(
|
| 153 |
+
self,
|
| 154 |
+
x: torch.Tensor,
|
| 155 |
+
cos: torch.Tensor,
|
| 156 |
+
sin: torch.Tensor,
|
| 157 |
+
past_kv: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
|
| 158 |
+
attn_mask: Optional[torch.Tensor] = None,
|
| 159 |
+
) -> tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
|
| 160 |
+
B, T, _ = x.shape
|
| 161 |
+
nkv = self.cfg.num_kv_heads
|
| 162 |
+
q, k, v = self.qkv(x).split([self._q_size, self._kv_size, self._kv_size], dim=-1)
|
| 163 |
+
q = q.view(B, T, self.cfg.n_heads, self.cfg.head_dim).transpose(1, 2)
|
| 164 |
+
k = k.view(B, T, nkv, self.cfg.head_dim).transpose(1, 2)
|
| 165 |
+
v = v.view(B, T, nkv, self.cfg.head_dim).transpose(1, 2)
|
| 166 |
+
|
| 167 |
+
if cos.dim() == 3: # per-row positions, already [B, T, D/2]
|
| 168 |
+
q = _apply_rope(q, cos, sin)
|
| 169 |
+
k = _apply_rope(k, cos, sin)
|
| 170 |
+
else:
|
| 171 |
+
offset = past_kv[0].shape[-2] if past_kv is not None else 0
|
| 172 |
+
q = _apply_rope(q, cos[offset:offset + T], sin[offset:offset + T])
|
| 173 |
+
k = _apply_rope(k, cos[offset:offset + T], sin[offset:offset + T])
|
| 174 |
+
|
| 175 |
+
if past_kv is not None:
|
| 176 |
+
k = torch.cat([past_kv[0], k], dim=-2)
|
| 177 |
+
v = torch.cat([past_kv[1], v], dim=-2)
|
| 178 |
+
new_kv = (k, v) # cache holds the SMALL (n_kv) heads — the whole point of GQA
|
| 179 |
+
|
| 180 |
+
# Expand KV to n_heads for attention (no-op under MHA).
|
| 181 |
+
k = _repeat_kv(k, self._n_rep)
|
| 182 |
+
v = _repeat_kv(v, self._n_rep)
|
| 183 |
+
|
| 184 |
+
if attn_mask is not None: # padded batch: causal AND not-a-pad-key, [B, 1, T, T] bool
|
| 185 |
+
attn = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask)
|
| 186 |
+
else:
|
| 187 |
+
is_causal = T > 1
|
| 188 |
+
attn = F.scaled_dot_product_attention(q, k, v, is_causal=is_causal)
|
| 189 |
+
attn = attn.transpose(1, 2).contiguous().view(B, T, self.cfg.attention_dim)
|
| 190 |
+
return self.out_proj(attn), new_kv
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class SwiGLU(nn.Module):
|
| 194 |
+
"""Gated SiLU feed-forward (as in LLaMA / Mistral)."""
|
| 195 |
+
|
| 196 |
+
def __init__(self, cfg: TinyBrainBotConfig):
|
| 197 |
+
super().__init__()
|
| 198 |
+
self.gate = nn.Linear(cfg.hidden_size, cfg.ffn_size, bias=False)
|
| 199 |
+
self.up = nn.Linear(cfg.hidden_size, cfg.ffn_size, bias=False)
|
| 200 |
+
self.down = nn.Linear(cfg.ffn_size, cfg.hidden_size, bias=False)
|
| 201 |
+
|
| 202 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 203 |
+
return self.down(F.silu(self.gate(x)) * self.up(x))
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
class DecoderBlock(nn.Module):
|
| 207 |
+
def __init__(self, cfg: TinyBrainBotConfig):
|
| 208 |
+
super().__init__()
|
| 209 |
+
self.attn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
|
| 210 |
+
self.attn = CausalSelfAttention(cfg)
|
| 211 |
+
self.ffn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
|
| 212 |
+
self.ffn = SwiGLU(cfg)
|
| 213 |
+
|
| 214 |
+
def forward(
|
| 215 |
+
self,
|
| 216 |
+
x: torch.Tensor,
|
| 217 |
+
cos: torch.Tensor,
|
| 218 |
+
sin: torch.Tensor,
|
| 219 |
+
past_kv: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
|
| 220 |
+
attn_mask: Optional[torch.Tensor] = None,
|
| 221 |
+
) -> tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
|
| 222 |
+
attn_out, new_kv = self.attn(self.attn_norm(x), cos, sin, past_kv, attn_mask)
|
| 223 |
+
x = x + attn_out
|
| 224 |
+
x = x + self.ffn(self.ffn_norm(x))
|
| 225 |
+
return x, new_kv
|
| 226 |
+
|
| 227 |
+
def forward_no_cache(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
|
| 228 |
+
out, _ = self.forward(x, cos, sin, None)
|
| 229 |
+
return out
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
class TinyBrainBotModel(nn.Module):
|
| 233 |
+
def __init__(self, cfg: TinyBrainBotConfig):
|
| 234 |
+
super().__init__()
|
| 235 |
+
self.cfg = cfg
|
| 236 |
+
r = cfg.embed_rank if cfg.embed_rank > 0 else cfg.hidden_size
|
| 237 |
+
self.embed = nn.Embedding(cfg.vocab_size, r)
|
| 238 |
+
self.embed_up = nn.Linear(r, cfg.hidden_size, bias=False) if cfg.embed_rank > 0 else None
|
| 239 |
+
self.head_down = nn.Linear(cfg.hidden_size, r, bias=False) if cfg.embed_rank > 0 else None
|
| 240 |
+
n_blocks = cfg.loop_unique if cfg.loop_unique > 0 else cfg.n_layers
|
| 241 |
+
self.blocks = nn.ModuleList([DecoderBlock(cfg) for _ in range(n_blocks)])
|
| 242 |
+
# recurrent depth: a learned vector per loop pass, and a gate re-injecting the input embedding each pass
|
| 243 |
+
# (named *_norm so the optimiser puts it in the no-weight-decay group)
|
| 244 |
+
self.loop_emb = nn.Embedding(32, cfg.hidden_size) if cfg.loop_unique > 0 else None
|
| 245 |
+
self.reinject_norm = nn.Parameter(torch.tensor(0.5)) if cfg.loop_unique > 0 else None
|
| 246 |
+
self.place = nn.Embedding(cfg.place_vocab, cfg.hidden_size) if cfg.digit_place else None
|
| 247 |
+
self.train_loops: Optional[int] = None # set per step by the trainer (same on every rank)
|
| 248 |
+
self.eval_loops: Optional[int] = None # override for evaluation ("think longer")
|
| 249 |
+
self.final_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
|
| 250 |
+
self.lm_head = nn.Linear(r, cfg.vocab_size, bias=False)
|
| 251 |
+
if cfg.tie_embeddings:
|
| 252 |
+
self.lm_head.weight = self.embed.weight
|
| 253 |
+
|
| 254 |
+
self._rope_cache: Optional[tuple[torch.Tensor, torch.Tensor]] = None
|
| 255 |
+
self._rope_device: Optional[torch.device] = None
|
| 256 |
+
self._rope_dtype: Optional[torch.dtype] = None
|
| 257 |
+
|
| 258 |
+
self.apply(self._init_weights)
|
| 259 |
+
if self.embed_up is not None:
|
| 260 |
+
# keep the residual stream at the usual scale: up-projection ~ 1/sqrt(rank)
|
| 261 |
+
nn.init.normal_(self.embed_up.weight, std=r ** -0.5)
|
| 262 |
+
nn.init.normal_(self.head_down.weight, std=cfg.hidden_size ** -0.5)
|
| 263 |
+
|
| 264 |
+
def _init_weights(self, m: nn.Module) -> None:
|
| 265 |
+
if isinstance(m, nn.Linear):
|
| 266 |
+
nn.init.normal_(m.weight, std=self.cfg.init_std)
|
| 267 |
+
if m.bias is not None:
|
| 268 |
+
nn.init.zeros_(m.bias)
|
| 269 |
+
elif isinstance(m, nn.Embedding):
|
| 270 |
+
nn.init.normal_(m.weight, std=self.cfg.init_std)
|
| 271 |
+
|
| 272 |
+
def _place_ids(self, ids: torch.Tensor) -> torch.Tensor:
|
| 273 |
+
d = (ids >= self.cfg.digit_lo) & (ids <= self.cfg.digit_hi)
|
| 274 |
+
idx = torch.arange(ids.shape[1], device=ids.device).expand_as(ids)
|
| 275 |
+
last_nd = torch.cummax(torch.where(d, torch.full_like(idx, -1), idx), dim=1).values
|
| 276 |
+
return torch.where(d, (idx - last_nd).clamp(max=self.cfg.place_vocab - 1), torch.zeros_like(idx))
|
| 277 |
+
|
| 278 |
+
def loops_now(self) -> int:
|
| 279 |
+
if self.training and self.train_loops is not None:
|
| 280 |
+
return self.train_loops
|
| 281 |
+
if not self.training and self.eval_loops is not None:
|
| 282 |
+
return self.eval_loops
|
| 283 |
+
return self.cfg.n_loops
|
| 284 |
+
|
| 285 |
+
def _ensure_rope(self, seq_len: int, device: torch.device, dtype: torch.dtype) -> tuple[torch.Tensor, torch.Tensor]:
|
| 286 |
+
need_rebuild = (
|
| 287 |
+
self._rope_cache is None
|
| 288 |
+
or self._rope_device != device
|
| 289 |
+
or self._rope_dtype != dtype
|
| 290 |
+
or self._rope_cache[0].shape[0] < seq_len
|
| 291 |
+
)
|
| 292 |
+
if need_rebuild:
|
| 293 |
+
target_len = max(seq_len, self.cfg.context_length)
|
| 294 |
+
cos, sin = _precompute_rope_cache(self.cfg.head_dim, target_len, self.cfg.rope_theta, device, dtype)
|
| 295 |
+
self._rope_cache = (cos, sin)
|
| 296 |
+
self._rope_device = device
|
| 297 |
+
self._rope_dtype = dtype
|
| 298 |
+
return self._rope_cache
|
| 299 |
+
|
| 300 |
+
def forward(
|
| 301 |
+
self,
|
| 302 |
+
input_ids: torch.Tensor,
|
| 303 |
+
labels: Optional[torch.Tensor] = None,
|
| 304 |
+
past_kv: Optional[list[tuple[torch.Tensor, torch.Tensor]]] = None,
|
| 305 |
+
use_cache: bool = False,
|
| 306 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 307 |
+
) -> dict:
|
| 308 |
+
B, T = input_ids.shape
|
| 309 |
+
past_len = past_kv[0][0].shape[-2] if past_kv is not None else 0
|
| 310 |
+
if T + past_len > self.cfg.context_length:
|
| 311 |
+
raise ValueError(f"sequence length {T + past_len} exceeds context_length {self.cfg.context_length}")
|
| 312 |
+
x = self.embed(input_ids)
|
| 313 |
+
if self.embed_up is not None:
|
| 314 |
+
x = self.embed_up(x)
|
| 315 |
+
if self.place is not None:
|
| 316 |
+
x = x + self.place(self._place_ids(input_ids))
|
| 317 |
+
cos, sin = self._ensure_rope(T + past_len, x.device, x.dtype)
|
| 318 |
+
amask = None
|
| 319 |
+
if attention_mask is not None and not bool(attention_mask.all()):
|
| 320 |
+
# padded batch (left or right): RoPE positions count from each row's first real token, pad keys are
|
| 321 |
+
# masked out, and pad query rows attend to themselves only (no NaN). Unpadded input takes the old path.
|
| 322 |
+
if past_kv is not None or use_cache:
|
| 323 |
+
raise ValueError("attention_mask with padding is not supported together with a KV cache")
|
| 324 |
+
am = attention_mask.to(device=x.device, dtype=torch.bool)
|
| 325 |
+
pos = (am.long().cumsum(-1) - 1).clamp(min=0)
|
| 326 |
+
cos, sin = cos[pos], sin[pos]
|
| 327 |
+
eye = torch.eye(T, dtype=torch.bool, device=x.device)
|
| 328 |
+
amask = (eye.tril() | torch.ones(T, T, dtype=torch.bool, device=x.device).tril()) & am[:, None, None, :]
|
| 329 |
+
amask = amask | eye[None, None]
|
| 330 |
+
use_ckpt = self.cfg.gradient_checkpointing and self.training
|
| 331 |
+
new_past: list[tuple[torch.Tensor, torch.Tensor]] = []
|
| 332 |
+
if self.cfg.loop_unique > 0:
|
| 333 |
+
if past_kv is not None or use_cache:
|
| 334 |
+
raise ValueError("recurrent-depth models do not use a KV cache (recompute the prefix)")
|
| 335 |
+
x0 = x
|
| 336 |
+
for k in range(self.loops_now()):
|
| 337 |
+
x = x + self.loop_emb.weight[k].to(x.dtype)
|
| 338 |
+
if k > 0:
|
| 339 |
+
x = x + self.reinject_norm.to(x.dtype) * x0
|
| 340 |
+
for block in self.blocks:
|
| 341 |
+
x = _ckpt(block.forward_no_cache, x, cos, sin, use_reentrant=False) if use_ckpt else block(x, cos, sin, None, amask)[0]
|
| 342 |
+
for i, block in enumerate(self.blocks if self.cfg.loop_unique == 0 else []):
|
| 343 |
+
layer_past = past_kv[i] if past_kv is not None else None
|
| 344 |
+
if use_ckpt:
|
| 345 |
+
x = _ckpt(block.forward_no_cache, x, cos, sin, use_reentrant=False)
|
| 346 |
+
else:
|
| 347 |
+
x, kv = block(x, cos, sin, layer_past, amask)
|
| 348 |
+
if use_cache:
|
| 349 |
+
new_past.append(kv)
|
| 350 |
+
x = self.final_norm(x)
|
| 351 |
+
logits = self.lm_head(self.head_down(x) if self.head_down is not None else x)
|
| 352 |
+
|
| 353 |
+
out = {"logits": logits}
|
| 354 |
+
if use_cache and not use_ckpt:
|
| 355 |
+
out["past_kv"] = new_past
|
| 356 |
+
if labels is not None:
|
| 357 |
+
# standard next-token loss, labels shifted upstream
|
| 358 |
+
# unsqueeze(0) makes loss 1-D so torch.nn.DataParallel can
|
| 359 |
+
# gather per-device losses along dim 0 (noop on single GPU).
|
| 360 |
+
if (labels != -100).any():
|
| 361 |
+
loss = F.cross_entropy(
|
| 362 |
+
logits.view(-1, self.cfg.vocab_size),
|
| 363 |
+
labels.view(-1),
|
| 364 |
+
ignore_index=-100,
|
| 365 |
+
)
|
| 366 |
+
else:
|
| 367 |
+
# SFT edge case: a packed window can land entirely in masked
|
| 368 |
+
# (user/system) tokens, leaving no supervised targets. cross_entropy
|
| 369 |
+
# would then divide by zero and return NaN, poisoning training.
|
| 370 |
+
# Emit a graph-connected zero loss for these batches instead.
|
| 371 |
+
loss = logits.sum() * 0.0
|
| 372 |
+
out["loss"] = loss.unsqueeze(0)
|
| 373 |
+
return out
|
| 374 |
+
|
| 375 |
+
def num_parameters(self) -> int:
|
| 376 |
+
return sum(p.numel() for p in self.parameters())
|
| 377 |
+
|
| 378 |
+
# ======================= transformers wrapper =======================
|
| 379 |
+
from transformers import PreTrainedModel, GenerationMixin
|
| 380 |
+
from transformers.modeling_outputs import CausalLMOutput
|
| 381 |
+
from .configuration_tbbloop import TBBLoopConfig
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
class TBBLoopForCausalLM(PreTrainedModel, GenerationMixin):
|
| 385 |
+
config_class = TBBLoopConfig
|
| 386 |
+
_tied_weights_keys = ["model.lm_head.weight"]
|
| 387 |
+
_supports_cache_class = False
|
| 388 |
+
|
| 389 |
+
def __init__(self, config):
|
| 390 |
+
super().__init__(config)
|
| 391 |
+
self.model = TinyBrainBotModel(TinyBrainBotConfig(**config.native))
|
| 392 |
+
if getattr(config, "eval_loops", 0):
|
| 393 |
+
self.model.eval_loops = int(config.eval_loops)
|
| 394 |
+
|
| 395 |
+
def _init_weights(self, module):
|
| 396 |
+
pass # weights always come from the checkpoint
|
| 397 |
+
|
| 398 |
+
def get_input_embeddings(self):
|
| 399 |
+
return self.model.embed
|
| 400 |
+
|
| 401 |
+
def set_input_embeddings(self, value):
|
| 402 |
+
self.model.embed = value
|
| 403 |
+
|
| 404 |
+
def get_output_embeddings(self):
|
| 405 |
+
return self.model.lm_head
|
| 406 |
+
|
| 407 |
+
def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs):
|
| 408 |
+
logits = self.model(input_ids, attention_mask=attention_mask)["logits"]
|
| 409 |
+
loss = None
|
| 410 |
+
if labels is not None:
|
| 411 |
+
loss = F.cross_entropy(logits[:, :-1].reshape(-1, logits.shape[-1]).float(), labels[:, 1:].reshape(-1), ignore_index=-100)
|
| 412 |
+
return CausalLMOutput(loss=loss, logits=logits)
|
| 413 |
+
|
| 414 |
+
def prepare_inputs_for_generation(self, input_ids, **kwargs):
|
| 415 |
+
return {"input_ids": input_ids[:, -self.model.cfg.context_length:]}
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
"<|user|>",
|
| 4 |
+
"<|assistant|>",
|
| 5 |
+
"<|system|>",
|
| 6 |
+
"<|end|>",
|
| 7 |
+
"<think>",
|
| 8 |
+
"</think>",
|
| 9 |
+
"<|sep|>"
|
| 10 |
+
],
|
| 11 |
+
"bos_token": "<|endoftext|>",
|
| 12 |
+
"eos_token": "<|endoftext|>",
|
| 13 |
+
"pad_token": "<|pad|>"
|
| 14 |
+
}
|
tbb_meta.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "byte-level BPE (HF tokenizers)",
|
| 3 |
+
"vocab_size": 4096,
|
| 4 |
+
"special_tokens": [
|
| 5 |
+
"<|endoftext|>",
|
| 6 |
+
"<|user|>",
|
| 7 |
+
"<|assistant|>",
|
| 8 |
+
"<|system|>",
|
| 9 |
+
"<|end|>",
|
| 10 |
+
"<think>",
|
| 11 |
+
"</think>",
|
| 12 |
+
"<|sep|>",
|
| 13 |
+
"<|pad|>"
|
| 14 |
+
],
|
| 15 |
+
"digits": "split individually",
|
| 16 |
+
"normalization": "none",
|
| 17 |
+
"sample_chars": 400000000,
|
| 18 |
+
"sample_shares": {
|
| 19 |
+
"qa-plain": 0.14100000000000001,
|
| 20 |
+
"qa-distill": 0.055,
|
| 21 |
+
"facts-plain": 0.041,
|
| 22 |
+
"capability-distill": 0.0165,
|
| 23 |
+
"reasoning-distill": 0.006500000000000001,
|
| 24 |
+
"math-direct-3m": 0.19999999999999998,
|
| 25 |
+
"cosmopedia-wikihow": 0.0757,
|
| 26 |
+
"commonsense-distill": 0.0092,
|
| 27 |
+
"procedural-anneal": 0.006900000000000001,
|
| 28 |
+
"everyday-activity": 0.0045000000000000005,
|
| 29 |
+
"social-situations": 0.0045000000000000005,
|
| 30 |
+
"everyday-reasoning": 0.00135,
|
| 31 |
+
"ds-prose": 0.00135,
|
| 32 |
+
"cosmopedia-stanford": 0.12300000000000001,
|
| 33 |
+
"cosmopedia-openstax": 0.027000000000000003,
|
| 34 |
+
"tiny-stories-v2": 0.14150000000000001,
|
| 35 |
+
"fineweb-edu-10bt": 0.09,
|
| 36 |
+
"wiki-dense": 0.055
|
| 37 |
+
}
|
| 38 |
+
}
|
tokenization_tbbuf.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""TinyBrainBot units-first number tokenizer (byte-level BPE + length-prefixed, reversed digit runs).
|
| 2 |
+
|
| 3 |
+
Every run of 1-8 digits is written as a length token and then its digits units first:
|
| 4 |
+
"62" -> "<len2>26" "1234" -> "<len4>4321"
|
| 5 |
+
so a model emits the units digit first and carries flow forward in time. Digit runs touching ":" (clock
|
| 6 |
+
times like 3:15) and runs longer than 8 digits are left unchanged. The text is rewritten on encode and
|
| 7 |
+
restored on decode, so callers see ordinary numbers. The rewrite is idempotent (an already-rewritten run
|
| 8 |
+
is not rewritten again), so it is safe on every encode path.
|
| 9 |
+
|
| 10 |
+
The idea (units-first digit tokens; a length prefix) follows Atom2.7m and Pascaline-3M. This is our own
|
| 11 |
+
implementation.
|
| 12 |
+
"""
|
| 13 |
+
import re
|
| 14 |
+
|
| 15 |
+
from transformers import PreTrainedTokenizerFast
|
| 16 |
+
|
| 17 |
+
MAX_LEN = 8
|
| 18 |
+
# a digit run: not part of "<lenN>", not right after a "<lenN>" tag, not touching another digit or ":"
|
| 19 |
+
_RUN = re.compile(r"(?<!<len)(?<!<len\d>)(?<![\d:])(\d{1,%d})(?![\d:])" % MAX_LEN)
|
| 20 |
+
_TAG = re.compile(r"<len([1-%d])>(\d+)" % MAX_LEN)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def encode_numbers(text):
|
| 24 |
+
if not isinstance(text, str):
|
| 25 |
+
return text
|
| 26 |
+
return _RUN.sub(lambda m: "<len%d>%s" % (len(m.group(1)), m.group(1)[::-1]), text)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def decode_numbers(text):
|
| 30 |
+
def back(m):
|
| 31 |
+
n = int(m.group(1)); d = m.group(2)
|
| 32 |
+
return d[:n][::-1] + d[n:]
|
| 33 |
+
return _TAG.sub(back, text)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _map(x):
|
| 37 |
+
if isinstance(x, str):
|
| 38 |
+
return encode_numbers(x)
|
| 39 |
+
if isinstance(x, tuple):
|
| 40 |
+
return tuple(_map(v) for v in x)
|
| 41 |
+
if isinstance(x, list):
|
| 42 |
+
return [_map(v) for v in x]
|
| 43 |
+
return x
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class TBBUnitsFirstTokenizerFast(PreTrainedTokenizerFast):
|
| 47 |
+
def _batch_encode_plus(self, batch_text_or_text_pairs, *args, **kwargs):
|
| 48 |
+
if not kwargs.get("is_split_into_words", False):
|
| 49 |
+
batch_text_or_text_pairs = _map(batch_text_or_text_pairs)
|
| 50 |
+
return super()._batch_encode_plus(batch_text_or_text_pairs, *args, **kwargs)
|
| 51 |
+
|
| 52 |
+
def _encode_plus(self, text, text_pair=None, *args, **kwargs):
|
| 53 |
+
if not kwargs.get("is_split_into_words", False):
|
| 54 |
+
text, text_pair = _map(text), _map(text_pair)
|
| 55 |
+
return super()._encode_plus(text, text_pair, *args, **kwargs)
|
| 56 |
+
|
| 57 |
+
def tokenize(self, text, *args, **kwargs):
|
| 58 |
+
return super().tokenize(encode_numbers(text), *args, **kwargs)
|
| 59 |
+
|
| 60 |
+
def _decode(self, token_ids, *args, **kwargs):
|
| 61 |
+
return decode_numbers(super()._decode(token_ids, *args, **kwargs))
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"added_tokens_decoder": {
|
| 3 |
+
"0": {
|
| 4 |
+
"content": "<|endoftext|>",
|
| 5 |
+
"lstrip": false,
|
| 6 |
+
"normalized": false,
|
| 7 |
+
"rstrip": false,
|
| 8 |
+
"single_word": false,
|
| 9 |
+
"special": true
|
| 10 |
+
},
|
| 11 |
+
"1": {
|
| 12 |
+
"content": "<|user|>",
|
| 13 |
+
"lstrip": false,
|
| 14 |
+
"normalized": false,
|
| 15 |
+
"rstrip": false,
|
| 16 |
+
"single_word": false,
|
| 17 |
+
"special": true
|
| 18 |
+
},
|
| 19 |
+
"2": {
|
| 20 |
+
"content": "<|assistant|>",
|
| 21 |
+
"lstrip": false,
|
| 22 |
+
"normalized": false,
|
| 23 |
+
"rstrip": false,
|
| 24 |
+
"single_word": false,
|
| 25 |
+
"special": true
|
| 26 |
+
},
|
| 27 |
+
"3": {
|
| 28 |
+
"content": "<|system|>",
|
| 29 |
+
"lstrip": false,
|
| 30 |
+
"normalized": false,
|
| 31 |
+
"rstrip": false,
|
| 32 |
+
"single_word": false,
|
| 33 |
+
"special": true
|
| 34 |
+
},
|
| 35 |
+
"4": {
|
| 36 |
+
"content": "<|end|>",
|
| 37 |
+
"lstrip": false,
|
| 38 |
+
"normalized": false,
|
| 39 |
+
"rstrip": false,
|
| 40 |
+
"single_word": false,
|
| 41 |
+
"special": true
|
| 42 |
+
},
|
| 43 |
+
"5": {
|
| 44 |
+
"content": "<think>",
|
| 45 |
+
"lstrip": false,
|
| 46 |
+
"normalized": false,
|
| 47 |
+
"rstrip": false,
|
| 48 |
+
"single_word": false,
|
| 49 |
+
"special": true
|
| 50 |
+
},
|
| 51 |
+
"6": {
|
| 52 |
+
"content": "</think>",
|
| 53 |
+
"lstrip": false,
|
| 54 |
+
"normalized": false,
|
| 55 |
+
"rstrip": false,
|
| 56 |
+
"single_word": false,
|
| 57 |
+
"special": true
|
| 58 |
+
},
|
| 59 |
+
"7": {
|
| 60 |
+
"content": "<|sep|>",
|
| 61 |
+
"lstrip": false,
|
| 62 |
+
"normalized": false,
|
| 63 |
+
"rstrip": false,
|
| 64 |
+
"single_word": false,
|
| 65 |
+
"special": true
|
| 66 |
+
},
|
| 67 |
+
"8": {
|
| 68 |
+
"content": "<|pad|>",
|
| 69 |
+
"lstrip": false,
|
| 70 |
+
"normalized": false,
|
| 71 |
+
"rstrip": false,
|
| 72 |
+
"single_word": false,
|
| 73 |
+
"special": true
|
| 74 |
+
},
|
| 75 |
+
"4096": {
|
| 76 |
+
"content": "<len1>",
|
| 77 |
+
"lstrip": false,
|
| 78 |
+
"normalized": false,
|
| 79 |
+
"rstrip": false,
|
| 80 |
+
"single_word": false,
|
| 81 |
+
"special": false
|
| 82 |
+
},
|
| 83 |
+
"4097": {
|
| 84 |
+
"content": "<len2>",
|
| 85 |
+
"lstrip": false,
|
| 86 |
+
"normalized": false,
|
| 87 |
+
"rstrip": false,
|
| 88 |
+
"single_word": false,
|
| 89 |
+
"special": false
|
| 90 |
+
},
|
| 91 |
+
"4098": {
|
| 92 |
+
"content": "<len3>",
|
| 93 |
+
"lstrip": false,
|
| 94 |
+
"normalized": false,
|
| 95 |
+
"rstrip": false,
|
| 96 |
+
"single_word": false,
|
| 97 |
+
"special": false
|
| 98 |
+
},
|
| 99 |
+
"4099": {
|
| 100 |
+
"content": "<len4>",
|
| 101 |
+
"lstrip": false,
|
| 102 |
+
"normalized": false,
|
| 103 |
+
"rstrip": false,
|
| 104 |
+
"single_word": false,
|
| 105 |
+
"special": false
|
| 106 |
+
},
|
| 107 |
+
"4100": {
|
| 108 |
+
"content": "<len5>",
|
| 109 |
+
"lstrip": false,
|
| 110 |
+
"normalized": false,
|
| 111 |
+
"rstrip": false,
|
| 112 |
+
"single_word": false,
|
| 113 |
+
"special": false
|
| 114 |
+
},
|
| 115 |
+
"4101": {
|
| 116 |
+
"content": "<len6>",
|
| 117 |
+
"lstrip": false,
|
| 118 |
+
"normalized": false,
|
| 119 |
+
"rstrip": false,
|
| 120 |
+
"single_word": false,
|
| 121 |
+
"special": false
|
| 122 |
+
},
|
| 123 |
+
"4102": {
|
| 124 |
+
"content": "<len7>",
|
| 125 |
+
"lstrip": false,
|
| 126 |
+
"normalized": false,
|
| 127 |
+
"rstrip": false,
|
| 128 |
+
"single_word": false,
|
| 129 |
+
"special": false
|
| 130 |
+
},
|
| 131 |
+
"4103": {
|
| 132 |
+
"content": "<len8>",
|
| 133 |
+
"lstrip": false,
|
| 134 |
+
"normalized": false,
|
| 135 |
+
"rstrip": false,
|
| 136 |
+
"single_word": false,
|
| 137 |
+
"special": false
|
| 138 |
+
}
|
| 139 |
+
},
|
| 140 |
+
"additional_special_tokens": [
|
| 141 |
+
"<|user|>",
|
| 142 |
+
"<|assistant|>",
|
| 143 |
+
"<|system|>",
|
| 144 |
+
"<|end|>",
|
| 145 |
+
"<think>",
|
| 146 |
+
"</think>",
|
| 147 |
+
"<|sep|>"
|
| 148 |
+
],
|
| 149 |
+
"bos_token": "<|endoftext|>",
|
| 150 |
+
"clean_up_tokenization_spaces": false,
|
| 151 |
+
"eos_token": "<|endoftext|>",
|
| 152 |
+
"extra_special_tokens": {},
|
| 153 |
+
"model_max_length": 1024,
|
| 154 |
+
"pad_token": "<|pad|>",
|
| 155 |
+
"tokenizer_class": "TBBUnitsFirstTokenizerFast",
|
| 156 |
+
"unk_token": null,
|
| 157 |
+
"auto_map": {
|
| 158 |
+
"AutoTokenizer": [
|
| 159 |
+
"tokenization_tbbuf.TBBUnitsFirstTokenizerFast",
|
| 160 |
+
"tokenization_tbbuf.TBBUnitsFirstTokenizerFast"
|
| 161 |
+
]
|
| 162 |
+
},
|
| 163 |
+
"model_input_names": [
|
| 164 |
+
"input_ids",
|
| 165 |
+
"attention_mask"
|
| 166 |
+
]
|
| 167 |
+
}
|