Text Generation
Transformers
Safetensors
English
gpt
causal-lm
decoder-only
grouped-query-attention
rope
swiglu
boundlessbpe
curriculum-learning
xsa
custom_code
Instructions to use UniversalComputingResearch/Limen0.2B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use UniversalComputingResearch/Limen0.2B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="UniversalComputingResearch/Limen0.2B", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("UniversalComputingResearch/Limen0.2B", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use UniversalComputingResearch/Limen0.2B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "UniversalComputingResearch/Limen0.2B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "UniversalComputingResearch/Limen0.2B", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/UniversalComputingResearch/Limen0.2B
- SGLang
How to use UniversalComputingResearch/Limen0.2B 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 "UniversalComputingResearch/Limen0.2B" \ --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": "UniversalComputingResearch/Limen0.2B", "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 "UniversalComputingResearch/Limen0.2B" \ --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": "UniversalComputingResearch/Limen0.2B", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use UniversalComputingResearch/Limen0.2B with Docker Model Runner:
docker model run hf.co/UniversalComputingResearch/Limen0.2B
Release Limen0.2B
Browse files- .gitattributes +2 -34
- README.md +174 -0
- bg.png +3 -0
- config.json +26 -0
- config.py +67 -0
- configuration_gpt.py +27 -0
- generation_config.json +8 -0
- model.py +357 -0
- model.safetensors +3 -0
- requirements.txt +5 -0
- special_tokens_map.json +6 -0
- superword.model +3 -0
- tokenization_superword.py +124 -0
- tokenizer_config.json +12 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,3 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
bg.png filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
superword.model filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
datasets:
|
| 6 |
+
- HuggingFaceTB/smollm-corpus
|
| 7 |
+
- openbmb/Ultra-FineWeb
|
| 8 |
+
- HuggingFaceFW/finepdfs-edu
|
| 9 |
+
- common-pile/peS2o_filtered
|
| 10 |
+
- common-pile/stackv2_edu_filtered
|
| 11 |
+
- allenai/dolma
|
| 12 |
+
library_name: transformers
|
| 13 |
+
tags:
|
| 14 |
+
- causal-lm
|
| 15 |
+
- decoder-only
|
| 16 |
+
- grouped-query-attention
|
| 17 |
+
- rope
|
| 18 |
+
- swiglu
|
| 19 |
+
- boundlessbpe
|
| 20 |
+
- curriculum-learning
|
| 21 |
+
- xsa
|
| 22 |
+
pipeline_tag: text-generation
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+

|
| 26 |
+
|
| 27 |
+
# Limen0.2B
|
| 28 |
+
|
| 29 |
+
Limen0.2B is a 222.5M-parameter decoder-only base language model trained from scratch on 50B tokens. It supports a 1,024-token context window and uses a custom 16,384-token BoundlessBPE tokenizer. BoundlessBPE learns SuperBPE merges across whitespace, allowing frequent multi-word spans to be represented directly.
|
| 30 |
+
|
| 31 |
+
This is a base completion model, not an instruction-tuned chat model.
|
| 32 |
+
|
| 33 |
+
## Requirements
|
| 34 |
+
|
| 35 |
+
Loading requires PyTorch, Transformers, and the Rust-backed BoundlessBPE package:
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
pip install torch transformers regex heapdict
|
| 39 |
+
pip install "git+https://github.com/UniversalComputingResearch/fastboundlessbpe.git@perf/tokenid-training"
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
The BoundlessBPE package includes a PyO3 Rust extension. A source installation requires `rustc` and `cargo`; `pip` builds the extension automatically through Maturin when no compatible wheel is available.
|
| 43 |
+
|
| 44 |
+
## Load
|
| 45 |
+
|
| 46 |
+
```python
|
| 47 |
+
import torch
|
| 48 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 49 |
+
|
| 50 |
+
model_id = "UniversalComputingResearch/Limen0.2B"
|
| 51 |
+
|
| 52 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
|
| 53 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 54 |
+
model_id,
|
| 55 |
+
trust_remote_code=True,
|
| 56 |
+
torch_dtype=torch.bfloat16,
|
| 57 |
+
).cuda().eval()
|
| 58 |
+
|
| 59 |
+
prompt = "The future of AI is"
|
| 60 |
+
inputs = tokenizer(prompt, return_tensors="pt", add_special_tokens=False).to(model.device)
|
| 61 |
+
with torch.inference_mode():
|
| 62 |
+
output = model.generate(**inputs, max_new_tokens=64, do_sample=True, temperature=0.8)
|
| 63 |
+
print(tokenizer.decode(output[0], skip_special_tokens=True))
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
`trust_remote_code=True` is required for the custom model architecture and tokenizer implementation.
|
| 67 |
+
|
| 68 |
+
## Tokenizer
|
| 69 |
+
|
| 70 |
+
Superword tokenization extends conventional BPE with learned tokens that can cross pre-tokenization boundaries, allowing frequent multi-word spans, including whitespace, to be represented directly.
|
| 71 |
+
|
| 72 |
+
Limen0.2B's tokenizer design and implementation build on [SuperBPE: Space Travel for Language Models](https://arxiv.org/abs/2503.13423), [Boundless Byte Pair Encoding: Breaking the Pre-tokenization Barrier](https://arxiv.org/abs/2504.00178), and [Faster Superword Tokenization](https://arxiv.org/abs/2604.05192).
|
| 73 |
+
|
| 74 |
+
`superword.model` is the artifact used during pretraining. Text is encoded without automatic BOS or EOS insertion. Training documents are terminated with `<|endoftext|>` (ID 16383), which is also the generation stop token. Control-token-like text in ordinary source material is treated as text rather than as markup.
|
| 75 |
+
|
| 76 |
+
## Architecture
|
| 77 |
+
|
| 78 |
+
- 222,516,480 parameters; tied input/output embeddings
|
| 79 |
+
- 35 transformer layers, hidden size 768
|
| 80 |
+
- Grouped-query attention: 6 query heads, 2 key/value heads, head dimension 128
|
| 81 |
+
- SwiGLU-style MLP with intermediate size 1,920
|
| 82 |
+
- RoPE (`theta=100000`), RMSNorm, 1,024-token context
|
| 83 |
+
- Linear weights and normalization/control parameters are retained in FP32;
|
| 84 |
+
matmuls use the activation dtype during BF16 inference/training.
|
| 85 |
+
|
| 86 |
+
### XSA projection
|
| 87 |
+
|
| 88 |
+
Each causal attention layer applies scaled-dot-product attention with grouped query attention, followed by an **XSA (exclusive self-attention) projection**. The projection removes the component of each query-head attention output that is parallel to its corresponding normalized value vector before output projection.
|
| 89 |
+
|
| 90 |
+
## Training
|
| 91 |
+
|
| 92 |
+
Limen0.2B was trained from scratch for 50B tokens with AdamW (`beta1=0.9`, `beta2=0.95`, `eps=1e-8`). Weight decay was `0.001` and applied only to non-embedding matrix parameters. Gradients were clipped to norm 1.0.
|
| 93 |
+
|
| 94 |
+
The learning rate warmed up for 500 steps to `0.003`, remained constant through 85% of training, and then decayed linearly to zero. The released weights are an FP32 exponential moving average of the final 10% of training (`decay=0.999`, 4,768 updates), rather than the final raw optimization step.
|
| 95 |
+
|
| 96 |
+
## Data curriculum
|
| 97 |
+
|
| 98 |
+
The 50B-token run used a three-stage, token-weighted curriculum with a 10% transition window around stage boundaries. The source weights were:
|
| 99 |
+
|
| 100 |
+
| Source | 0–33.3% | 33.3–66.7% | 66.7–100% |
|
| 101 |
+
|---|---:|---:|---:|
|
| 102 |
+
| FineWeb-Edu-Dedup | 30% | 30% | 40% |
|
| 103 |
+
| Ultra-FineWeb | 50% | 30% | 20% |
|
| 104 |
+
| FinePDFs-Edu-English | 5% | 10% | 10% |
|
| 105 |
+
| Common-Pile peS2o filtered | 5% | 10% | 10% |
|
| 106 |
+
| StackV2 education-filtered | 5% | 10% | 10% |
|
| 107 |
+
| Dolma selected non-web | 5% | 10% | 10% |
|
| 108 |
+
|
| 109 |
+
Across the full equal-duration schedule, the target composition is approximately 33.3% FineWeb-Edu-Dedup, 33.3% Ultra-FineWeb, and 8.3% from each of the four specialist sources. The final phase remains 60% web-derived data; it shifts 10 percentage points from Ultra-FineWeb to FineWeb-Edu-Dedup rather than eliminating general-web coverage.
|
| 110 |
+
|
| 111 |
+
## Zero-shot final evaluation
|
| 112 |
+
|
| 113 |
+
The final EMA weights were evaluated in zero-shot mode. Multiple-choice results use length-normalized accuracy (`acc_norm`); BLiMP uses pairwise accuracy. Chance-normalized scores map random guessing to 0 and perfect accuracy to 100.
|
| 114 |
+
|
| 115 |
+
| Benchmark | Accuracy | Chance-normalized score |
|
| 116 |
+
|---|---:|---:|
|
| 117 |
+
| HellaSwag | 41.98% | 22.64 |
|
| 118 |
+
| PIQA | 67.36% | 34.71 |
|
| 119 |
+
| ARC-Easy | 53.37% | 37.82 |
|
| 120 |
+
| ARC-Challenge | 29.69% | 6.26 |
|
| 121 |
+
| CommonsenseQA | 34.15% | 17.69 |
|
| 122 |
+
| BLiMP | 83.28% | 66.55 |
|
| 123 |
+
|
| 124 |
+
## Reference model comparison
|
| 125 |
+
|
| 126 |
+
The tables provide contextual comparisons with reported results. Multiple-choice accuracy is `acc_norm`; BLiMP uses pairwise accuracy. The second line in each row of the accuracy table lists parameter count, vocabulary size, and reported training tokens. Bold indicates the highest benchmark result in each column and the smallest value in each metadata category. Training token counts are reported figures; Qwen2.5's 18T is family-level rather than checkpoint-specific, while GPT-2 Medium's approximately 10B is an estimate.
|
| 127 |
+
|
| 128 |
+
### Accuracy (`acc_norm`)
|
| 129 |
+
|
| 130 |
+
| Model | HellaSwag | PIQA | ARC-Easy | ARC-Challenge | CommonsenseQA | BLiMP |
|
| 131 |
+
|---|---:|---:|---:|---:|---:|---:|
|
| 132 |
+
| Qwen2.5 0.5B<br>494M · 151,936 vocab · 18T† | **52.1%** | **69.4%** | 58.5% | **32.0%** | 41.0% | 84.4% |
|
| 133 |
+
| Gemma 3 270M<br>270M · 262,144 vocab · 6T | 41.4% | 68.5% | 57.3% | 28.0% | **42.6%** | 82.1% |
|
| 134 |
+
| SmolLM2 135M<br>135M · 49,152 vocab · 2T | 43.2% | 68.2% | **58.7%** | 29.7% | 35.5% | 81.4% |
|
| 135 |
+
| **Limen0.2B**<br>223M · **16,384 vocab** · 50B | 42.0% | 67.4% | 53.4% | 29.7% | 34.2% | 83.3% |
|
| 136 |
+
| GPT-X2 125M<br>**125M** · 32,768 vocab · 75B | 40.5% | 67.1% | 51.6% | 27.6% | 34.6% | 82.9% |
|
| 137 |
+
| GPT-2 Medium<br>355M · 50,257 vocab · **≈10B‡** | 39.3% | 66.6% | 43.5% | 25.0% | 31.7% | **85.2%** |
|
| 138 |
+
|
| 139 |
+
### Chance-normalized score
|
| 140 |
+
|
| 141 |
+
| Model | HellaSwag | PIQA | ARC-Easy | ARC-Challenge | CommonsenseQA | BLiMP |
|
| 142 |
+
|---|---:|---:|---:|---:|---:|---:|
|
| 143 |
+
| Qwen2.5 0.5B | **36.2** | **38.8** | 44.6 | **9.3** | 26.2 | 68.7 |
|
| 144 |
+
| Gemma 3 270M | 21.9 | 37.0 | 43.0 | 4.0 | **28.2** | 64.3 |
|
| 145 |
+
| SmolLM2 135M | 24.3 | 36.3 | **44.9** | 6.3 | 19.4 | 62.7 |
|
| 146 |
+
| **Limen0.2B** | 22.6 | 34.7 | 37.8 | 6.3 | 17.7 | 66.6 |
|
| 147 |
+
| GPT-X2 125M | 20.7 | 34.2 | 35.5 | 3.4 | 18.3 | 65.8 |
|
| 148 |
+
| GPT-2 Medium | 19.1 | 33.2 | 24.6 | 0.0 | 14.6 | **70.4** |
|
| 149 |
+
|
| 150 |
+
- † Qwen reports 18T tokens for the Qwen2.5 pretraining corpus, without a separate total for the 0.5B checkpoint.
|
| 151 |
+
- ‡ GPT-2 reports 40GB of Internet text; ≈10B tokens is a rough conversion using OpenAI's stated heuristic of about four characters per token.
|
| 152 |
+
|
| 153 |
+
## Checkpoint evaluation trajectory
|
| 154 |
+
|
| 155 |
+
All evaluations use zero-shot `lm-eval`, BF16 inference, and batch size 64. Task and macro scores are length-normalized accuracy (`acc_norm`). Intermediate checkpoints contain raw training weights; the final row reports the EMA model.
|
| 156 |
+
|
| 157 |
+
| Checkpoint | Tokens | Val. loss | BPB | ARC-Easy | ARC-Challenge | HellaSwag | PIQA | Norm. macro |
|
| 158 |
+
|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
| 159 |
+
| 1k | 1.049B | 3.0255 | 0.9389 | 38.68% | 22.35% | 29.33% | 59.25% | 37.40% |
|
| 160 |
+
| 5k | 5.243B | 2.6211 | 0.8134 | 46.76% | 25.85% | 34.71% | 62.89% | 42.56% |
|
| 161 |
+
| 10k | 10.486B | 2.5320 | 0.7857 | 47.47% | 26.71% | 37.39% | 64.85% | 44.11% |
|
| 162 |
+
| 20k | 20.972B | 2.4640 | 0.7646 | 51.98% | 27.39% | 38.83% | 65.07% | 45.82% |
|
| 163 |
+
| 30k | 31.457B | 2.4313 | 0.7545 | 52.36% | 30.03% | 40.11% | 66.92% | 47.36% |
|
| 164 |
+
| 40k | 41.943B | 2.4118 | 0.7484 | 50.51% | 29.44% | 40.76% | 67.03% | 46.93% |
|
| 165 |
+
| **Final EMA** | **50.000B** | — | — | **53.37%** | **29.69%** | **41.98%** | **67.36%** | **48.10%** |
|
| 166 |
+
|
| 167 |
+
Normalized macro accuracy increased from 37.40% at 1k steps to 48.10% for the final model.
|
| 168 |
+
|
| 169 |
+
## Repository contents
|
| 170 |
+
|
| 171 |
+
- `model.safetensors`: EMA model weights
|
| 172 |
+
- `config.json`, `config.py`, `model.py`: custom Transformers model definition
|
| 173 |
+
- `superword.model`, `tokenizer_config.json`, `tokenization_superword.py`:
|
| 174 |
+
tokenizer model and Transformers integration
|
bg.png
ADDED
|
Git LFS Details
|
config.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"GPTForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"auto_map": {
|
| 6 |
+
"AutoConfig": "config.GPTConfig",
|
| 7 |
+
"AutoModelForCausalLM": "model.GPTForCausalLM"
|
| 8 |
+
},
|
| 9 |
+
"block_size": 1024,
|
| 10 |
+
"dtype": "float32",
|
| 11 |
+
"head_dim": 128,
|
| 12 |
+
"hidden_size": 768,
|
| 13 |
+
"intermediate_size": 1920,
|
| 14 |
+
"labels_are_shifted": true,
|
| 15 |
+
"max_position_embeddings": 1024,
|
| 16 |
+
"model_type": "gpt",
|
| 17 |
+
"num_attention_heads": 6,
|
| 18 |
+
"num_hidden_layers": 35,
|
| 19 |
+
"num_key_value_heads": 2,
|
| 20 |
+
"rms_norm_eps": 1e-06,
|
| 21 |
+
"rope_theta": 100000.0,
|
| 22 |
+
"tie_word_embeddings": true,
|
| 23 |
+
"transformers_version": "5.14.1",
|
| 24 |
+
"vocab_size": 16384,
|
| 25 |
+
"xsa_projection": true
|
| 26 |
+
}
|
config.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Standalone Transformers configuration for Limen0.2B."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from transformers import PretrainedConfig
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
DEFAULT_VOCAB_SIZE = 16_384
|
| 9 |
+
DEFAULT_HIDDEN_SIZE = 768
|
| 10 |
+
DEFAULT_NUM_HIDDEN_LAYERS = 35
|
| 11 |
+
DEFAULT_NUM_ATTENTION_HEADS = 6
|
| 12 |
+
DEFAULT_NUM_KEY_VALUE_HEADS = 2
|
| 13 |
+
DEFAULT_HEAD_DIM = DEFAULT_HIDDEN_SIZE // DEFAULT_NUM_ATTENTION_HEADS
|
| 14 |
+
DEFAULT_INTERMEDIATE_SIZE = DEFAULT_HIDDEN_SIZE * 5 // 2
|
| 15 |
+
DEFAULT_BLOCK_SIZE = 1024
|
| 16 |
+
DEFAULT_ROPE_THETA = 100_000.0
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class GPTConfig(PretrainedConfig):
|
| 20 |
+
"""Configuration for the Limen0.2B decoder-only language model."""
|
| 21 |
+
|
| 22 |
+
model_type = "gpt"
|
| 23 |
+
|
| 24 |
+
def __init__(
|
| 25 |
+
self,
|
| 26 |
+
vocab_size: int = DEFAULT_VOCAB_SIZE,
|
| 27 |
+
hidden_size: int = DEFAULT_HIDDEN_SIZE,
|
| 28 |
+
num_hidden_layers: int = DEFAULT_NUM_HIDDEN_LAYERS,
|
| 29 |
+
num_attention_heads: int = DEFAULT_NUM_ATTENTION_HEADS,
|
| 30 |
+
num_key_value_heads: int | None = DEFAULT_NUM_KEY_VALUE_HEADS,
|
| 31 |
+
intermediate_size: int | None = DEFAULT_INTERMEDIATE_SIZE,
|
| 32 |
+
head_dim: int | None = None,
|
| 33 |
+
block_size: int = DEFAULT_BLOCK_SIZE,
|
| 34 |
+
rope_theta: float = DEFAULT_ROPE_THETA,
|
| 35 |
+
rms_norm_eps: float = 1e-6,
|
| 36 |
+
xsa_projection: bool = True,
|
| 37 |
+
tie_word_embeddings: bool = True,
|
| 38 |
+
labels_are_shifted: bool = False,
|
| 39 |
+
**kwargs,
|
| 40 |
+
):
|
| 41 |
+
if num_key_value_heads is None:
|
| 42 |
+
num_key_value_heads = num_attention_heads
|
| 43 |
+
if head_dim is None:
|
| 44 |
+
if hidden_size % num_attention_heads != 0:
|
| 45 |
+
raise ValueError("hidden_size must be divisible by num_attention_heads")
|
| 46 |
+
head_dim = hidden_size // num_attention_heads
|
| 47 |
+
if intermediate_size is None:
|
| 48 |
+
intermediate_size = hidden_size * 4
|
| 49 |
+
if num_attention_heads % num_key_value_heads != 0:
|
| 50 |
+
raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
|
| 51 |
+
if head_dim % 2 != 0:
|
| 52 |
+
raise ValueError("head_dim must be even for RoPE")
|
| 53 |
+
|
| 54 |
+
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
| 55 |
+
self.vocab_size = int(vocab_size)
|
| 56 |
+
self.hidden_size = int(hidden_size)
|
| 57 |
+
self.num_hidden_layers = int(num_hidden_layers)
|
| 58 |
+
self.num_attention_heads = int(num_attention_heads)
|
| 59 |
+
self.num_key_value_heads = int(num_key_value_heads)
|
| 60 |
+
self.intermediate_size = int(intermediate_size)
|
| 61 |
+
self.head_dim = int(head_dim)
|
| 62 |
+
self.block_size = int(block_size)
|
| 63 |
+
self.max_position_embeddings = int(block_size)
|
| 64 |
+
self.rope_theta = float(rope_theta)
|
| 65 |
+
self.rms_norm_eps = float(rms_norm_eps)
|
| 66 |
+
self.xsa_projection = bool(xsa_projection)
|
| 67 |
+
self.labels_are_shifted = bool(labels_are_shifted)
|
configuration_gpt.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Backward-compatible exports for the Limen0.2B configuration."""
|
| 2 |
+
|
| 3 |
+
from .config import (
|
| 4 |
+
DEFAULT_BLOCK_SIZE,
|
| 5 |
+
DEFAULT_HEAD_DIM,
|
| 6 |
+
DEFAULT_HIDDEN_SIZE,
|
| 7 |
+
DEFAULT_INTERMEDIATE_SIZE,
|
| 8 |
+
DEFAULT_NUM_ATTENTION_HEADS,
|
| 9 |
+
DEFAULT_NUM_HIDDEN_LAYERS,
|
| 10 |
+
DEFAULT_NUM_KEY_VALUE_HEADS,
|
| 11 |
+
DEFAULT_ROPE_THETA,
|
| 12 |
+
DEFAULT_VOCAB_SIZE,
|
| 13 |
+
GPTConfig,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
__all__ = [
|
| 17 |
+
"DEFAULT_BLOCK_SIZE",
|
| 18 |
+
"DEFAULT_HEAD_DIM",
|
| 19 |
+
"DEFAULT_HIDDEN_SIZE",
|
| 20 |
+
"DEFAULT_INTERMEDIATE_SIZE",
|
| 21 |
+
"DEFAULT_NUM_ATTENTION_HEADS",
|
| 22 |
+
"DEFAULT_NUM_HIDDEN_LAYERS",
|
| 23 |
+
"DEFAULT_NUM_KEY_VALUE_HEADS",
|
| 24 |
+
"DEFAULT_ROPE_THETA",
|
| 25 |
+
"DEFAULT_VOCAB_SIZE",
|
| 26 |
+
"GPTConfig",
|
| 27 |
+
]
|
generation_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"eos_token_id": 16383,
|
| 4 |
+
"output_attentions": false,
|
| 5 |
+
"output_hidden_states": false,
|
| 6 |
+
"pad_token_id": 16379,
|
| 7 |
+
"transformers_version": "5.14.1"
|
| 8 |
+
}
|
model.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
from torch import Tensor
|
| 7 |
+
from torch.nn import functional as F
|
| 8 |
+
from transformers import PreTrainedModel
|
| 9 |
+
from transformers.cache_utils import DynamicCache
|
| 10 |
+
from transformers.generation.utils import GenerationMixin
|
| 11 |
+
from transformers.modeling_outputs import CausalLMOutputWithPast
|
| 12 |
+
|
| 13 |
+
from .config import GPTConfig
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
CONTROL_TENSOR_NAME_PATTERNS = (
|
| 17 |
+
"scale",
|
| 18 |
+
"gate",
|
| 19 |
+
"gain",
|
| 20 |
+
"norm",
|
| 21 |
+
"ln_",
|
| 22 |
+
"rms",
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _parameter_count(module: nn.Module) -> int:
|
| 27 |
+
return sum(parameter.numel() for parameter in module.parameters())
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _format_parameter_count(count: int) -> str:
|
| 31 |
+
return f"{count:,} ({count / 1_000_000:.3f}M)"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def parameter_breakdown(model: nn.Module) -> str:
|
| 35 |
+
"""Return a tree-style parameter summary for the GPT architecture."""
|
| 36 |
+
transformer = model.transformer
|
| 37 |
+
blocks = list(transformer["h"])
|
| 38 |
+
embedding = transformer["wte"]
|
| 39 |
+
final_norm = transformer["ln_f"]
|
| 40 |
+
first = blocks[0] if blocks else None
|
| 41 |
+
|
| 42 |
+
embedding_params = _parameter_count(embedding)
|
| 43 |
+
final_norm_params = _parameter_count(final_norm)
|
| 44 |
+
block_params = _parameter_count(first) if first is not None else 0
|
| 45 |
+
attention_params = _parameter_count(first.attn) if first is not None else 0
|
| 46 |
+
mlp_params = _parameter_count(first.mlp) if first is not None else 0
|
| 47 |
+
ln1_params = _parameter_count(first.ln_1) if first is not None else 0
|
| 48 |
+
ln2_params = _parameter_count(first.ln_2) if first is not None else 0
|
| 49 |
+
lm_head_params = _parameter_count(model.lm_head)
|
| 50 |
+
tied_lm_head = bool(model.lm_head.weight is embedding.weight)
|
| 51 |
+
unique_total = _parameter_count(model)
|
| 52 |
+
|
| 53 |
+
def component_lines(prefix: str = "│ │ ") -> list[str]:
|
| 54 |
+
if first is None:
|
| 55 |
+
return []
|
| 56 |
+
attn = first.attn
|
| 57 |
+
mlp = first.mlp
|
| 58 |
+
return [
|
| 59 |
+
f"{prefix}├─ norm 1 ................ {_format_parameter_count(ln1_params)}",
|
| 60 |
+
f"{prefix}├─ attention .............. {_format_parameter_count(attention_params)}",
|
| 61 |
+
f"{prefix}│ ├─ q_proj .............. {_format_parameter_count(_parameter_count(attn.q_proj))}",
|
| 62 |
+
f"{prefix}│ ├─ k_proj .............. {_format_parameter_count(_parameter_count(attn.k_proj))}",
|
| 63 |
+
f"{prefix}│ ├─ v_proj .............. {_format_parameter_count(_parameter_count(attn.v_proj))}",
|
| 64 |
+
f"{prefix}│ └─ o_proj .............. {_format_parameter_count(_parameter_count(attn.o_proj))}",
|
| 65 |
+
f"{prefix}├─ norm 2 ................ {_format_parameter_count(ln2_params)}",
|
| 66 |
+
f"{prefix}└─ mlp ................... {_format_parameter_count(mlp_params)}",
|
| 67 |
+
f"{prefix} ├─ w_gate ............ {_format_parameter_count(_parameter_count(mlp.w_gate))}",
|
| 68 |
+
f"{prefix} ├─ w_up .............. {_format_parameter_count(_parameter_count(mlp.w_up))}",
|
| 69 |
+
f"{prefix} └─ w_down ............ {_format_parameter_count(_parameter_count(mlp.w_down))}",
|
| 70 |
+
]
|
| 71 |
+
|
| 72 |
+
lines = [
|
| 73 |
+
"parameter breakdown:",
|
| 74 |
+
f"├─ embedding (wte) ........ {_format_parameter_count(embedding_params)}",
|
| 75 |
+
f"├─ transformer blocks × {len(blocks)}",
|
| 76 |
+
f"│ ├─ one block ........... {_format_parameter_count(block_params)}",
|
| 77 |
+
"│ ├─ per-block detail",
|
| 78 |
+
*component_lines(),
|
| 79 |
+
f"│ └─ all blocks .......... {_format_parameter_count(block_params * len(blocks))}",
|
| 80 |
+
f"├─ final norm (ln_f) ....... {_format_parameter_count(final_norm_params)}",
|
| 81 |
+
]
|
| 82 |
+
if tied_lm_head:
|
| 83 |
+
lines.append(
|
| 84 |
+
f"├─ lm_head ................ tied to embedding (+0 unique; raw {_format_parameter_count(lm_head_params)})"
|
| 85 |
+
)
|
| 86 |
+
else:
|
| 87 |
+
lines.append(f"├─ lm_head ................ {_format_parameter_count(lm_head_params)}")
|
| 88 |
+
lines.append(f"└─ total unique ............ {_format_parameter_count(unique_total)}")
|
| 89 |
+
return "\n".join(lines)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class CastedLinear(nn.Linear):
|
| 93 |
+
"""Store linear params in FP32, cast to activation dtype for matmul."""
|
| 94 |
+
|
| 95 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 96 |
+
weight = self.weight.to(dtype=x.dtype)
|
| 97 |
+
bias = self.bias.to(dtype=x.dtype) if self.bias is not None else None
|
| 98 |
+
return F.linear(x, weight, bias)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def restore_fp32_params(model: nn.Module) -> None:
|
| 102 |
+
"""Keep linear weights and control params in FP32 after dtype conversion."""
|
| 103 |
+
for module in model.modules():
|
| 104 |
+
if isinstance(module, CastedLinear):
|
| 105 |
+
module.float()
|
| 106 |
+
for name, param in model.named_parameters():
|
| 107 |
+
if (
|
| 108 |
+
param.ndim < 2
|
| 109 |
+
or any(pattern in name for pattern in CONTROL_TENSOR_NAME_PATTERNS)
|
| 110 |
+
) and param.dtype != torch.float32:
|
| 111 |
+
param.data = param.data.float()
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class RMSNorm(nn.Module):
|
| 115 |
+
def __init__(self, dim, eps=1e-6):
|
| 116 |
+
super().__init__()
|
| 117 |
+
self.eps = eps
|
| 118 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 119 |
+
|
| 120 |
+
def forward(self, x):
|
| 121 |
+
rms = torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps)
|
| 122 |
+
return (x.float() * rms).to(dtype=x.dtype) * self.weight.to(dtype=x.dtype)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def build_rope_inv_freq(head_dim, theta=2500.0):
|
| 126 |
+
return 1.0 / (theta ** (torch.arange(0, head_dim, 2, dtype=torch.float32) / head_dim))
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def precompute_rope_cos_sin(head_dim, seq_len, theta=2500.0):
|
| 130 |
+
freqs = build_rope_inv_freq(head_dim, theta)
|
| 131 |
+
t = torch.arange(seq_len, dtype=torch.float32)
|
| 132 |
+
freqs = torch.outer(t, freqs)
|
| 133 |
+
return freqs.cos(), freqs.sin()
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _apply_rope(x, cos, sin):
|
| 137 |
+
x_float = x.float()
|
| 138 |
+
x_pair = x_float.reshape(*x_float.shape[:-1], -1, 2)
|
| 139 |
+
even = x_pair[..., 0]
|
| 140 |
+
odd = x_pair[..., 1]
|
| 141 |
+
cos = cos.unsqueeze(0).unsqueeze(0)
|
| 142 |
+
sin = sin.unsqueeze(0).unsqueeze(0)
|
| 143 |
+
x_rot = torch.stack((even * cos - odd * sin, even * sin + odd * cos), dim=-1)
|
| 144 |
+
return x_rot.flatten(-2).type_as(x)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def apply_rotary_emb(q, k, freqs_cis):
|
| 148 |
+
cos, sin = freqs_cis
|
| 149 |
+
return _apply_rope(q, cos, sin), _apply_rope(k, cos, sin)
|
| 150 |
+
|
| 151 |
+
class GPTAttention(nn.Module):
|
| 152 |
+
def __init__(self, config, layer_idx):
|
| 153 |
+
super().__init__()
|
| 154 |
+
self.layer_idx = layer_idx
|
| 155 |
+
self.n_head = config.num_attention_heads
|
| 156 |
+
self.n_kv_heads = config.num_key_value_heads
|
| 157 |
+
self.head_dim = config.head_dim
|
| 158 |
+
self.n_rep = self.n_head // self.n_kv_heads
|
| 159 |
+
self.xsa_projection = config.xsa_projection
|
| 160 |
+
|
| 161 |
+
self.q_proj = CastedLinear(config.hidden_size, self.n_head * self.head_dim, bias=False)
|
| 162 |
+
self.k_proj = CastedLinear(config.hidden_size, self.n_kv_heads * self.head_dim, bias=False)
|
| 163 |
+
self.v_proj = CastedLinear(config.hidden_size, self.n_kv_heads * self.head_dim, bias=False)
|
| 164 |
+
self.o_proj = CastedLinear(self.n_head * self.head_dim, config.hidden_size, bias=False)
|
| 165 |
+
|
| 166 |
+
def _xsa_efficient(self, y: Tensor, v_current: Tensor) -> Tensor:
|
| 167 |
+
# y: [B, H, T, D]
|
| 168 |
+
# v_current: [B, Hkv, T, D]
|
| 169 |
+
B, H, T, D = y.shape
|
| 170 |
+
Hkv = v_current.size(1)
|
| 171 |
+
group = H // Hkv
|
| 172 |
+
|
| 173 |
+
y_g = y.reshape(B, Hkv, group, T, D)
|
| 174 |
+
v_n = F.normalize(v_current, dim=-1).unsqueeze(2)
|
| 175 |
+
|
| 176 |
+
proj = (y_g * v_n).sum(dim=-1, keepdim=True) * v_n
|
| 177 |
+
return (y_g - proj).reshape(B, H, T, D)
|
| 178 |
+
|
| 179 |
+
def forward(self, x, freqs_cis, past_key_value=None, use_cache=False, attention_mask=None):
|
| 180 |
+
B, T, _ = x.size()
|
| 181 |
+
|
| 182 |
+
q = self.q_proj(x).view(B, T, self.n_head, self.head_dim).transpose(1, 2)
|
| 183 |
+
k_current = self.k_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
|
| 184 |
+
v_current = self.v_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
|
| 185 |
+
|
| 186 |
+
q, k_current = apply_rotary_emb(q, k_current, freqs_cis)
|
| 187 |
+
|
| 188 |
+
if past_key_value is not None:
|
| 189 |
+
k, v = past_key_value.update(k_current, v_current, self.layer_idx)
|
| 190 |
+
else:
|
| 191 |
+
k, v = k_current, v_current
|
| 192 |
+
|
| 193 |
+
S = k.size(2)
|
| 194 |
+
|
| 195 |
+
is_causal = past_key_value is None or past_key_value.get_seq_length(self.layer_idx) == T
|
| 196 |
+
|
| 197 |
+
attn_mask = None
|
| 198 |
+
if attention_mask is not None:
|
| 199 |
+
key_pad = attention_mask.to(torch.bool)[:, None, None, :]
|
| 200 |
+
|
| 201 |
+
if is_causal and T > 1:
|
| 202 |
+
causal = torch.ones(T, S, dtype=torch.bool, device=x.device).tril(diagonal=S - T)
|
| 203 |
+
attn_mask = key_pad & causal[None, None, :, :]
|
| 204 |
+
else:
|
| 205 |
+
attn_mask = key_pad.expand(B, 1, T, S)
|
| 206 |
+
|
| 207 |
+
is_causal = False
|
| 208 |
+
|
| 209 |
+
y = F.scaled_dot_product_attention(
|
| 210 |
+
q,
|
| 211 |
+
k,
|
| 212 |
+
v,
|
| 213 |
+
attn_mask=attn_mask,
|
| 214 |
+
is_causal=is_causal,
|
| 215 |
+
enable_gqa=(self.n_kv_heads != self.n_head),
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
if self.xsa_projection:
|
| 219 |
+
y = self._xsa_efficient(y, v_current)
|
| 220 |
+
|
| 221 |
+
y = y.transpose(1, 2).contiguous().view(B, T, self.n_head * self.head_dim)
|
| 222 |
+
return self.o_proj(y)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
class GPTMLP(nn.Module):
|
| 226 |
+
def __init__(self, config):
|
| 227 |
+
super().__init__()
|
| 228 |
+
self.w_gate = CastedLinear(config.hidden_size, config.intermediate_size, bias=False)
|
| 229 |
+
self.w_up = CastedLinear(config.hidden_size, config.intermediate_size, bias=False)
|
| 230 |
+
self.w_down = CastedLinear(config.intermediate_size, config.hidden_size, bias=False)
|
| 231 |
+
|
| 232 |
+
def forward(self, x):
|
| 233 |
+
return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x))
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
class GPTBlock(nn.Module):
|
| 237 |
+
def __init__(self, config, layer_idx):
|
| 238 |
+
super().__init__()
|
| 239 |
+
self.ln_1 = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 240 |
+
self.attn = GPTAttention(config, layer_idx)
|
| 241 |
+
self.ln_2 = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 242 |
+
self.mlp = GPTMLP(config)
|
| 243 |
+
|
| 244 |
+
def forward(self, x, freqs_cis, past_key_value=None, use_cache=False, attention_mask=None):
|
| 245 |
+
x = x + self.attn(self.ln_1(x), freqs_cis, past_key_value, use_cache, attention_mask=attention_mask)
|
| 246 |
+
x = x + self.mlp(self.ln_2(x))
|
| 247 |
+
return x
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
class GPTPreTrainedModel(PreTrainedModel):
|
| 251 |
+
config_class = GPTConfig
|
| 252 |
+
base_model_prefix = "transformer"
|
| 253 |
+
supports_gradient_checkpointing = False
|
| 254 |
+
|
| 255 |
+
def _init_weights(self, module):
|
| 256 |
+
std = self.config.hidden_size ** -0.5
|
| 257 |
+
if isinstance(module, nn.Linear):
|
| 258 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=std)
|
| 259 |
+
elif isinstance(module, nn.Embedding):
|
| 260 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=std)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
class GPTForCausalLM(GPTPreTrainedModel, GenerationMixin):
|
| 264 |
+
_tied_weights_keys = {"lm_head.weight": "transformer.wte.weight"}
|
| 265 |
+
|
| 266 |
+
def __init__(self, config):
|
| 267 |
+
super().__init__(config)
|
| 268 |
+
self.config = config
|
| 269 |
+
self.transformer = nn.ModuleDict(dict(
|
| 270 |
+
wte=nn.Embedding(config.vocab_size, config.hidden_size),
|
| 271 |
+
h=nn.ModuleList([GPTBlock(config, i) for i in range(config.num_hidden_layers)]),
|
| 272 |
+
ln_f=RMSNorm(config.hidden_size, eps=config.rms_norm_eps),
|
| 273 |
+
))
|
| 274 |
+
self.lm_head = CastedLinear(config.hidden_size, config.vocab_size, bias=False)
|
| 275 |
+
if config.tie_word_embeddings:
|
| 276 |
+
self.lm_head.weight = self.transformer["wte"].weight
|
| 277 |
+
self._freqs_cis_cache = None
|
| 278 |
+
self.post_init()
|
| 279 |
+
restore_fp32_params(self)
|
| 280 |
+
|
| 281 |
+
def _apply(self, fn):
|
| 282 |
+
module = super()._apply(fn)
|
| 283 |
+
restore_fp32_params(self)
|
| 284 |
+
return module
|
| 285 |
+
|
| 286 |
+
def get_input_embeddings(self):
|
| 287 |
+
return self.transformer["wte"]
|
| 288 |
+
|
| 289 |
+
def set_input_embeddings(self, value):
|
| 290 |
+
self.transformer["wte"] = value
|
| 291 |
+
|
| 292 |
+
def get_output_embeddings(self):
|
| 293 |
+
return self.lm_head
|
| 294 |
+
|
| 295 |
+
def set_output_embeddings(self, new_embeddings):
|
| 296 |
+
self.lm_head = new_embeddings
|
| 297 |
+
|
| 298 |
+
def prepare_inputs_for_generation(self, input_ids, past_key_values=None, attention_mask=None, **kwargs):
|
| 299 |
+
if past_key_values is not None and past_key_values.get_seq_length() > 0:
|
| 300 |
+
input_ids = input_ids[:, -1:]
|
| 301 |
+
return {
|
| 302 |
+
"input_ids": input_ids,
|
| 303 |
+
"attention_mask": attention_mask,
|
| 304 |
+
"past_key_values": past_key_values,
|
| 305 |
+
"use_cache": True,
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
def _get_freqs_cis(self, seq_len, device):
|
| 309 |
+
cache = self._freqs_cis_cache
|
| 310 |
+
if cache is None or cache[0].device != device or cache[0].size(0) < seq_len:
|
| 311 |
+
cache = tuple(
|
| 312 |
+
tensor.to(device)
|
| 313 |
+
for tensor in precompute_rope_cos_sin(self.config.head_dim, seq_len, self.config.rope_theta)
|
| 314 |
+
)
|
| 315 |
+
if torch.is_inference_mode_enabled():
|
| 316 |
+
return cache[0][:seq_len], cache[1][:seq_len]
|
| 317 |
+
self._freqs_cis_cache = cache
|
| 318 |
+
return cache[0][:seq_len], cache[1][:seq_len]
|
| 319 |
+
|
| 320 |
+
def forward(
|
| 321 |
+
self,
|
| 322 |
+
input_ids,
|
| 323 |
+
attention_mask=None,
|
| 324 |
+
labels=None,
|
| 325 |
+
past_key_values: Optional[DynamicCache] = None,
|
| 326 |
+
use_cache=False,
|
| 327 |
+
**kwargs,
|
| 328 |
+
):
|
| 329 |
+
B, T = input_ids.size()
|
| 330 |
+
if use_cache and past_key_values is None:
|
| 331 |
+
past_key_values = DynamicCache()
|
| 332 |
+
|
| 333 |
+
past_len = past_key_values.get_seq_length() if past_key_values is not None else 0
|
| 334 |
+
x = self.transformer["wte"](input_ids)
|
| 335 |
+
cos, sin = self._get_freqs_cis(past_len + T, input_ids.device)
|
| 336 |
+
freqs_cis = cos[past_len:], sin[past_len:]
|
| 337 |
+
|
| 338 |
+
for block in self.transformer["h"]:
|
| 339 |
+
x = block(x, freqs_cis, past_key_values if use_cache else None, use_cache, attention_mask=attention_mask)
|
| 340 |
+
|
| 341 |
+
x = self.transformer["ln_f"](x)
|
| 342 |
+
logits = self.lm_head(x)
|
| 343 |
+
|
| 344 |
+
loss = None
|
| 345 |
+
if labels is not None:
|
| 346 |
+
if getattr(self.config, "labels_are_shifted", False):
|
| 347 |
+
loss = F.cross_entropy(logits.float().reshape(-1, logits.size(-1)), labels.reshape(-1))
|
| 348 |
+
else:
|
| 349 |
+
shift_logits = logits[..., :-1, :].contiguous()
|
| 350 |
+
shift_labels = labels[..., 1:].contiguous()
|
| 351 |
+
loss = F.cross_entropy(shift_logits.float().view(-1, shift_logits.size(-1)), shift_labels.reshape(-1))
|
| 352 |
+
|
| 353 |
+
return CausalLMOutputWithPast(
|
| 354 |
+
loss=loss,
|
| 355 |
+
logits=logits,
|
| 356 |
+
past_key_values=past_key_values if use_cache else None,
|
| 357 |
+
)
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:924c6b0064e0835bed70613bc03e28a040964e9057a890d1c9fd286a606b2288
|
| 3 |
+
size 890099464
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
transformers>=4.57
|
| 3 |
+
regex
|
| 4 |
+
heapdict
|
| 5 |
+
boundlessbpe @ git+https://github.com/UniversalComputingResearch/fastboundlessbpe.git@53a32afc808226f81e4cb203ae3a3d7f11f7b60f
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token": "<|bos|>",
|
| 3 |
+
"eos_token": "<|endoftext|>",
|
| 4 |
+
"pad_token": "<|pad|>",
|
| 5 |
+
"unk_token": "<|unk|>"
|
| 6 |
+
}
|
superword.model
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8135d93e13806e4f78289e9771389b27ae2b83472dc9d9b2a7397a383dfe9cab
|
| 3 |
+
size 705007
|
tokenization_superword.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hugging Face adapter for Limen0.2B's SuperBPE tokenizer.
|
| 2 |
+
|
| 3 |
+
Install the Rust-backed tokenizer package before loading this tokenizer:
|
| 4 |
+
|
| 5 |
+
pip install "git+https://github.com/UniversalComputingResearch/fastboundlessbpe.git@perf/tokenid-training"
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
from transformers import PreTrainedTokenizer
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
from boundlessbpe import FastTokenizer, RUST_AVAILABLE
|
| 16 |
+
from boundlessbpe.vocabulary import Vocabulary
|
| 17 |
+
except ImportError as exc: # pragma: no cover - depends on the consumer environment
|
| 18 |
+
raise ImportError(
|
| 19 |
+
"Limen0.2B requires the Rust-backed `boundlessbpe` package. Install it with: "
|
| 20 |
+
'pip install "git+https://github.com/UniversalComputingResearch/fastboundlessbpe.git@perf/tokenid-training"'
|
| 21 |
+
) from exc
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class SuperwordTokenizer(PreTrainedTokenizer):
|
| 25 |
+
"""Exact inference adapter for the SuperBPE model used in pretraining."""
|
| 26 |
+
|
| 27 |
+
model_input_names = ["input_ids", "attention_mask"]
|
| 28 |
+
vocab_files_names = {"superword_model_file": "superword.model"}
|
| 29 |
+
|
| 30 |
+
def __init__(self, superword_model_file: str = "superword.model", **kwargs):
|
| 31 |
+
if not RUST_AVAILABLE or FastTokenizer is None:
|
| 32 |
+
raise RuntimeError(
|
| 33 |
+
"`boundlessbpe` is installed without its Rust extension. Reinstall the "
|
| 34 |
+
"package from https://github.com/UniversalComputingResearch/fastboundlessbpe/tree/perf/tokenid-training."
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
model_file = Path(superword_model_file)
|
| 38 |
+
if not model_file.is_absolute():
|
| 39 |
+
model_file = Path(kwargs.pop("name_or_path", ".")) / model_file
|
| 40 |
+
self.superword_model_file = str(model_file)
|
| 41 |
+
|
| 42 |
+
self._fast = FastTokenizer()
|
| 43 |
+
self._fast.load(str(model_file))
|
| 44 |
+
with model_file.open("r", encoding="utf-8") as model_handle:
|
| 45 |
+
header = model_handle.readline().strip()
|
| 46 |
+
if not header.startswith("BoundlessBPE v2 "):
|
| 47 |
+
raise ValueError(f"Unsupported SuperBPE model header: {header!r}")
|
| 48 |
+
self._vocabulary = Vocabulary.load(model_handle)
|
| 49 |
+
|
| 50 |
+
self._special_tokens = dict(self._vocabulary.special_tokens)
|
| 51 |
+
self._inverse_special_tokens = dict(self._vocabulary.inverse_special_tokens)
|
| 52 |
+
self._vocab = {
|
| 53 |
+
token.decode("utf-8", errors="replace"): int(token_id)
|
| 54 |
+
for token, token_id in self._vocabulary.token_to_id.items()
|
| 55 |
+
}
|
| 56 |
+
self._vocab.update(self._special_tokens)
|
| 57 |
+
|
| 58 |
+
model_max_length = int(kwargs.pop("model_max_length", 1024))
|
| 59 |
+
for key in (
|
| 60 |
+
"pad_token",
|
| 61 |
+
"bos_token",
|
| 62 |
+
"eos_token",
|
| 63 |
+
"unk_token",
|
| 64 |
+
):
|
| 65 |
+
kwargs.pop(key, None)
|
| 66 |
+
super().__init__(
|
| 67 |
+
pad_token="<|pad|>",
|
| 68 |
+
bos_token="<|bos|>",
|
| 69 |
+
eos_token="<|endoftext|>",
|
| 70 |
+
unk_token="<|unk|>",
|
| 71 |
+
model_max_length=model_max_length,
|
| 72 |
+
**kwargs,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
def get_vocab(self):
|
| 76 |
+
return dict(self._vocab)
|
| 77 |
+
|
| 78 |
+
@property
|
| 79 |
+
def vocab_size(self):
|
| 80 |
+
return int(self._fast.get_vocab_size(with_added_tokens=False))
|
| 81 |
+
|
| 82 |
+
def _id_to_token(self, token_id: int) -> str:
|
| 83 |
+
token = self._vocabulary.id_to_token.get(int(token_id))
|
| 84 |
+
if token is not None:
|
| 85 |
+
return token.decode("utf-8", errors="replace")
|
| 86 |
+
return self._inverse_special_tokens.get(int(token_id), "<|unk|>")
|
| 87 |
+
|
| 88 |
+
def _tokenize(self, text, **kwargs):
|
| 89 |
+
return [self._id_to_token(token_id) for token_id in self._fast.encode_ordinary(text)]
|
| 90 |
+
|
| 91 |
+
def _convert_token_to_id(self, token):
|
| 92 |
+
return self._vocab.get(token, self._special_tokens["<|unk|>"])
|
| 93 |
+
|
| 94 |
+
def _convert_id_to_token(self, index):
|
| 95 |
+
return self._id_to_token(int(index))
|
| 96 |
+
|
| 97 |
+
def encode(self, text, text_pair=None, add_special_tokens=False, **kwargs):
|
| 98 |
+
if text_pair is not None:
|
| 99 |
+
text = text + text_pair
|
| 100 |
+
if add_special_tokens:
|
| 101 |
+
return list(self._fast.encode(text, allowed_special="all"))
|
| 102 |
+
return list(self._fast.encode_ordinary(text))
|
| 103 |
+
|
| 104 |
+
def decode(self, token_ids, skip_special_tokens=True, **kwargs):
|
| 105 |
+
if isinstance(token_ids, int):
|
| 106 |
+
token_ids = [token_ids]
|
| 107 |
+
if skip_special_tokens:
|
| 108 |
+
token_ids = [
|
| 109 |
+
token_id
|
| 110 |
+
for token_id in token_ids
|
| 111 |
+
if int(token_id) not in self._inverse_special_tokens
|
| 112 |
+
]
|
| 113 |
+
return self._fast.decode(list(token_ids))
|
| 114 |
+
|
| 115 |
+
def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
|
| 116 |
+
if token_ids_1 is None:
|
| 117 |
+
return list(token_ids_0)
|
| 118 |
+
return list(token_ids_0) + list(token_ids_1)
|
| 119 |
+
|
| 120 |
+
def save_vocabulary(self, save_directory, filename_prefix=None):
|
| 121 |
+
target = Path(save_directory) / (filename_prefix or "")
|
| 122 |
+
target = target.with_name(target.name + "superword.model")
|
| 123 |
+
target.write_bytes(Path(self.superword_model_file).read_bytes())
|
| 124 |
+
return (str(target),)
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": [
|
| 3 |
+
"tokenization_superword.SuperwordTokenizer",
|
| 4 |
+
null
|
| 5 |
+
],
|
| 6 |
+
"bos_token": "<|bos|>",
|
| 7 |
+
"eos_token": "<|endoftext|>",
|
| 8 |
+
"model_max_length": 1024,
|
| 9 |
+
"pad_token": "<|pad|>",
|
| 10 |
+
"superword_model_file": "superword.model",
|
| 11 |
+
"unk_token": "<|unk|>"
|
| 12 |
+
}
|