Text Generation
Transformers
English
Chinese
nano_dense
architecture-only
from-scratch
deepseek-v4
dense
custom_code
Instructions to use bowang0911/Nano-1B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use bowang0911/Nano-1B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="bowang0911/Nano-1B", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("bowang0911/Nano-1B", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use bowang0911/Nano-1B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "bowang0911/Nano-1B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "bowang0911/Nano-1B", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/bowang0911/Nano-1B
- SGLang
How to use bowang0911/Nano-1B 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 "bowang0911/Nano-1B" \ --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": "bowang0911/Nano-1B", "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 "bowang0911/Nano-1B" \ --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": "bowang0911/Nano-1B", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use bowang0911/Nano-1B with Docker Model Runner:
docker model run hf.co/bowang0911/Nano-1B
Add a 4/16/4 recurrent 1B architecture and 100-step trial results
#1
by bowang0911 - opened
- README.md +129 -82
- config.json +5 -1
- configuration_nano_dense.py +18 -1
- count_parameters.py +10 -1
- modeling_nano_dense.py +44 -9
README.md
CHANGED
|
@@ -2,34 +2,62 @@
|
|
| 2 |
license: apache-2.0
|
| 3 |
library_name: transformers
|
| 4 |
tags:
|
| 5 |
-
-
|
| 6 |
- architecture-only
|
| 7 |
- from-scratch
|
| 8 |
- deepseek-v4
|
| 9 |
- dense
|
|
|
|
| 10 |
language:
|
| 11 |
- en
|
| 12 |
- zh
|
| 13 |
---
|
| 14 |
|
| 15 |
-
# Nano-1B
|
| 16 |
|
| 17 |
-
A **1,076,700,672-parameter dense decoder architecture for training from scratch**
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
|
| 22 |
-
The dimensions follow [MiniCPM5-1B](https://huggingface.co/openbmb/MiniCPM5-1B).
|
| 23 |
-
Normalization, dense SwiGLU and interleaved RoPE
|
| 24 |
-
components. `NanoDenseForCausalLM` supplies
|
| 25 |
-
|
| 26 |
-
[DeepSeek-V4-Flash-Base, revision 8855555](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Base/tree/8855555deef230a27a21a8d6f294b7b7497759b6).
|
| 27 |
|
| 28 |
## Architecture
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
| Setting | Value |
|
| 31 |
| --- | ---: |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
| 33 |
| Residual hidden size | 1,536 |
|
| 34 |
| Dense SwiGLU intermediate size | 4,608 |
|
| 35 |
| Query / key-value heads | 16 / 2 |
|
|
@@ -37,7 +65,7 @@ global grouped-query attention. The tokenizer is pinned to
|
|
| 37 |
| Attention projection width | 2,048 |
|
| 38 |
| Attention pattern | Global causal GQA in every layer |
|
| 39 |
| Normalization | DeepseekV4RMSNorm, epsilon 1e-6 |
|
| 40 |
-
| Position encoding | V4 interleaved
|
| 41 |
| RoPE theta | 5,000,000 |
|
| 42 |
| Configured context target | 131,072 tokens |
|
| 43 |
| Vocabulary | 129,280 |
|
|
@@ -46,36 +74,28 @@ global grouped-query attention. The tokenizer is pinned to
|
|
| 46 |
| Total parameters | **1,076,700,672** |
|
| 47 |
| Excluding embedding and output head | **679,552,512** |
|
| 48 |
|
| 49 |
-
Each layer computes
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
y = h + SwiGLU(RMSNorm(h))
|
| 54 |
-
```
|
| 55 |
-
|
| 56 |
-
The attention width is independent of the residual width: Q projects
|
| 57 |
-
1536 -> 2048, K/V each project 1536 -> 256, and O projects 2048 -> 1536.
|
| 58 |
-
All 24 layers have independent parameters. Linear and embedding weights use
|
| 59 |
-
normal initialization with standard deviation 0.02; RMSNorm weights start at one.
|
| 60 |
No depth-dependent initialization scaling is applied.
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
|
| 67 |
## Initialize for training
|
| 68 |
|
| 69 |
-
``
|
| 70 |
-
pip install -r requirements.txt
|
| 71 |
-
```
|
| 72 |
|
| 73 |
```python
|
| 74 |
import torch
|
| 75 |
from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
|
| 76 |
|
| 77 |
-
repo = "
|
| 78 |
config = AutoConfig.from_pretrained(repo, trust_remote_code=True)
|
|
|
|
| 79 |
tokenizer = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
|
| 80 |
model = AutoModelForCausalLM.from_config(
|
| 81 |
config,
|
|
@@ -83,70 +103,97 @@ model = AutoModelForCausalLM.from_config(
|
|
| 83 |
attn_implementation="sdpa",
|
| 84 |
dtype=torch.bfloat16,
|
| 85 |
)
|
| 86 |
-
# Randomly initialized model; pass it to your training loop / Trainer.
|
| 87 |
-
```
|
| 88 |
-
|
| 89 |
-
To inspect the full architecture without allocating weight storage:
|
| 90 |
-
|
| 91 |
-
```python
|
| 92 |
-
with torch.device("meta"):
|
| 93 |
-
model = AutoModelForCausalLM.from_config(config, trust_remote_code=True)
|
| 94 |
-
print(sum(p.numel() for p in model.parameters())) # 1076700672
|
| 95 |
```
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
| 101 |
|
| 102 |
-
`
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
Existing data encoded with this pinned tokenizer can be reused directly.
|
| 106 |
-
The pretraining convention used here is raw text with `add_special_tokens=False`
|
| 107 |
-
and one EOS token (ID 1) appended per document. No chat template is provided.
|
| 108 |
|
| 109 |
-
|
| 110 |
-
padding labels to `-100`. Default positions are derived from that mask, including
|
| 111 |
-
when continuing a KV cache. The tokenizer uses EOS as its padding token; the
|
| 112 |
-
model embedding has no dedicated zeroed padding row, preserving the EOS embedding.
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
## Validation
|
| 127 |
|
| 128 |
-
|
|
|
|
| 129 |
|
| 130 |
-
- Full-size
|
| 131 |
-
-
|
| 132 |
-
-
|
| 133 |
-
|
| 134 |
-
-
|
| 135 |
-
-
|
| 136 |
-
|
| 137 |
-
- FP32/BF16 save/reload and invalid configuration override rejection.
|
| 138 |
-
- Tokenizer parity with the pinned source.
|
| 139 |
|
| 140 |
-
The
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
components and attention/cache interfaces.
|
| 144 |
|
| 145 |
## Sources and licenses
|
| 146 |
|
| 147 |
-
- [MiniCPM5-1B configuration](https://huggingface.co/openbmb/MiniCPM5-1B/blob/87179e5c1f455ef22e6223592d2d61351b525bfc/config.json):
|
| 148 |
- [Transformers 5.9.0 DeepSeek V4](https://github.com/huggingface/transformers/blob/v5.9.0/src/transformers/models/deepseek_v4/modeling_deepseek_v4.py): RMSNorm, dense MLP and interleaved RoPE.
|
| 149 |
- Model implementation: Apache-2.0, see `LICENSE`.
|
| 150 |
- DeepSeek tokenizer assets: MIT, see `LICENSE-TOKENIZER`.
|
| 151 |
|
| 152 |
-
No pretrained MiniCPM or DeepSeek
|
|
|
|
| 2 |
license: apache-2.0
|
| 3 |
library_name: transformers
|
| 4 |
tags:
|
| 5 |
+
- nano_dense_loop
|
| 6 |
- architecture-only
|
| 7 |
- from-scratch
|
| 8 |
- deepseek-v4
|
| 9 |
- dense
|
| 10 |
+
- recurrent
|
| 11 |
language:
|
| 12 |
- en
|
| 13 |
- zh
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# Recurrent Nano-1B
|
| 17 |
|
| 18 |
+
A **1,076,700,672-parameter dense decoder architecture for training from scratch**,
|
| 19 |
+
with a shared core that can run multiple times. This revision contains configuration,
|
| 20 |
+
model code and the same pinned DeepSeek V4 tokenizer. **No model weights or trained
|
| 21 |
+
checkpoints are included.** Initialize with `AutoModelForCausalLM.from_config`.
|
| 22 |
|
| 23 |
+
The base dimensions follow [MiniCPM5-1B](https://huggingface.co/openbmb/MiniCPM5-1B).
|
| 24 |
+
Normalization, dense SwiGLU and interleaved RoPE use Transformers' DeepSeek V4
|
| 25 |
+
components. `NanoDenseForCausalLM` supplies global grouped-query attention and
|
| 26 |
+
the recurrent execution schedule. The model type is `nano_dense_loop`.
|
|
|
|
| 27 |
|
| 28 |
## Architecture
|
| 29 |
|
| 30 |
+
```text
|
| 31 |
+
DSV4 tokens → embedding → prelude (4 layers)
|
| 32 |
+
→ core (16 layers) → same core (16 layers)
|
| 33 |
+
→ coda (4 layers) → final RMSNorm → LM head
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
| Component | Physical layers | Executions at K=2 |
|
| 37 |
+
| --- | ---: | ---: |
|
| 38 |
+
| Prelude | 4 | 4 |
|
| 39 |
+
| Shared core | 16 | 32 |
|
| 40 |
+
| Coda | 4 | 4 |
|
| 41 |
+
| Total | **24** | **40** |
|
| 42 |
+
|
| 43 |
+
The prelude runs once, the core runs `num_core_iterations` times, and the coda
|
| 44 |
+
runs once. Each core pass receives the previous pass's output and reuses the
|
| 45 |
+
same 16 sets of parameters. Token positions stay the same between passes.
|
| 46 |
+
Backpropagation traverses every pass. No extra boundary normalization, anchor
|
| 47 |
+
injection, adaptive halting or truncated backpropagation is added.
|
| 48 |
+
|
| 49 |
+
The default is **K=2**. K=1 is exactly the original 24-layer execution for the
|
| 50 |
+
same weights. Physical layer names and weight shapes are unchanged; changing
|
| 51 |
+
K does not add parameters. The split preserves the 1/6, 2/3, 1/6 proportions of
|
| 52 |
+
the earlier 36-layer Nano loop candidate; it is an initial candidate, not a
|
| 53 |
+
measured optimum or a complete reproduction of a paper's training recipe.
|
| 54 |
+
|
| 55 |
| Setting | Value |
|
| 56 |
| --- | ---: |
|
| 57 |
+
| Physical decoder layers | 24 |
|
| 58 |
+
| Prelude / core / coda | 4 / 16 / 4 |
|
| 59 |
+
| Default core iterations | 2 |
|
| 60 |
+
| Default effective depth | 40 |
|
| 61 |
| Residual hidden size | 1,536 |
|
| 62 |
| Dense SwiGLU intermediate size | 4,608 |
|
| 63 |
| Query / key-value heads | 16 / 2 |
|
|
|
|
| 65 |
| Attention projection width | 2,048 |
|
| 66 |
| Attention pattern | Global causal GQA in every layer |
|
| 67 |
| Normalization | DeepseekV4RMSNorm, epsilon 1e-6 |
|
| 68 |
+
| Position encoding | V4 interleaved full-head RoPE |
|
| 69 |
| RoPE theta | 5,000,000 |
|
| 70 |
| Configured context target | 131,072 tokens |
|
| 71 |
| Vocabulary | 129,280 |
|
|
|
|
| 74 |
| Total parameters | **1,076,700,672** |
|
| 75 |
| Excluding embedding and output head | **679,552,512** |
|
| 76 |
|
| 77 |
+
Each layer computes `h = x + GQA(RMSNorm(x))`, then
|
| 78 |
+
`y = h + SwiGLU(RMSNorm(h))`. Q projects 1536 → 2048, K/V each project
|
| 79 |
+
1536 → 256, and O projects 2048 → 1536. Linear and embedding weights use normal
|
| 80 |
+
initialization with standard deviation 0.02; RMSNorm weights start at one.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
No depth-dependent initialization scaling is applied.
|
| 82 |
|
| 83 |
+
Each execution has a separate KV cache slot, including the two executions of
|
| 84 |
+
each shared core layer. At batch size one, a complete 128K BF16 KV cache occupies
|
| 85 |
+
**5 GiB at K=2**, versus 3 GiB at K=1, excluding weights and temporary tensors.
|
| 86 |
+
The 128K context is a configuration target, not a validated capability.
|
| 87 |
|
| 88 |
## Initialize for training
|
| 89 |
|
| 90 |
+
Install `requirements.txt`, then run this from a local checkout of this revision:
|
|
|
|
|
|
|
| 91 |
|
| 92 |
```python
|
| 93 |
import torch
|
| 94 |
from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
|
| 95 |
|
| 96 |
+
repo = "."
|
| 97 |
config = AutoConfig.from_pretrained(repo, trust_remote_code=True)
|
| 98 |
+
config.num_core_iterations = 2
|
| 99 |
tokenizer = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
|
| 100 |
model = AutoModelForCausalLM.from_config(
|
| 101 |
config,
|
|
|
|
| 103 |
attn_implementation="sdpa",
|
| 104 |
dtype=torch.bfloat16,
|
| 105 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
```
|
| 107 |
|
| 108 |
+
When loading directly from the Hub before merge, pass the PR revision to
|
| 109 |
+
`AutoConfig.from_pretrained` and `AutoTokenizer.from_pretrained`; `main` still
|
| 110 |
+
contains the vanilla architecture. `num_core_iterations` can also be passed to
|
| 111 |
+
`forward` or `generate` for a specific call. Use the same K across distributed
|
| 112 |
+
training ranks. This implementation does not sample K randomly.
|
| 113 |
|
| 114 |
+
`python count_parameters.py --meta` verifies the full physical parameter count
|
| 115 |
+
without allocating weight storage and reports the configured execution depth
|
| 116 |
+
and KV cache size. `--core-iterations 1` checks the single-pass configuration.
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
+
## Data, packing and cache
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
+
The tokenizer remains pinned to
|
| 121 |
+
[DeepSeek-V4-Flash-Base, revision 8855555](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Base/tree/8855555deef230a27a21a8d6f294b7b7497759b6).
|
| 122 |
+
`tokenizer.json` is byte-identical to the original artifact. The existing
|
| 123 |
+
`model_max_length=131072` tokenizer-config setting and token IDs are unchanged.
|
| 124 |
+
`tokenizer_provenance.json` records the hashes. Pretraining uses raw text with
|
| 125 |
+
`add_special_tokens=False` and one appended EOS (ID 1), without a chat template.
|
| 126 |
+
|
| 127 |
+
For padded batches, pass a two-dimensional `attention_mask` and set padding
|
| 128 |
+
labels to `-100`. Default positions are derived from the mask. For padding-free
|
| 129 |
+
packing, use one flattened row and reset `position_ids` at document boundaries.
|
| 130 |
+
Omit the mask or pass an all-ones mask. The model isolates documents and excludes
|
| 131 |
+
cross-document next-token targets. Packed batches with padding or multiple rows
|
| 132 |
+
are rejected. Training and packed evaluation disable KV caching.
|
| 133 |
+
|
| 134 |
+
Unpacked evaluation/generation supports dynamic cache only. The returned cache
|
| 135 |
+
records its depth and layer schedule; these cannot change during continuation.
|
| 136 |
+
An empty vanilla `DynamicCache` is accepted and replaced with the loop cache.
|
| 137 |
+
Populated untagged caches and other cache implementations are rejected.
|
| 138 |
+
|
| 139 |
+
## 100-step trial
|
| 140 |
+
|
| 141 |
+
The full-size trial ran on the same CoreWeave 4×GB200 node as the dense baseline.
|
| 142 |
+
Both used seed 20260925, the same DSV4-tokenized English web stream and token
|
| 143 |
+
order, 2048-token sequences, global batch 32, and **6,553,600 training tokens**.
|
| 144 |
+
AdamW used LR 3e-4 with 10 warmup steps, betas 0.9/0.95, weight decay 0.1, and
|
| 145 |
+
gradient clipping 1.0; parameters and optimizer states were FP32 with BF16 autocast.
|
| 146 |
+
The loop trained at fixed K=2. Every 20 steps, both evaluation depths used the
|
| 147 |
+
same fixed samples from six existing validation panels. Each panel contains
|
| 148 |
+
32 windows of 2048 tokens; the overall panel overlaps the domain panels.
|
| 149 |
+
|
| 150 |
+
| Metric at step 100 | Base 1B (Vanilla) | Recurrent 1B (K=2) |
|
| 151 |
+
| --- | ---: | ---: |
|
| 152 |
+
| Physical / effective layers | 24 / 24 | 24 / 40 |
|
| 153 |
+
| Prelude / core / coda | — | 4 / 16 / 4 |
|
| 154 |
+
| Final training CE | 6.9774 | 7.1601 |
|
| 155 |
+
| Overall validation CE | **7.2836** | 7.4369 |
|
| 156 |
+
| Overall validation BPB | **2.2104** | 2.2568 |
|
| 157 |
+
| Mean seconds per step, steps 11–100 | 0.741 | 0.920 |
|
| 158 |
+
| Peak allocated GiB/GPU | 29.97 | 33.13 |
|
| 159 |
+
|
| 160 |
+
On the recurrent checkpoint, evaluating with K=1 gives CE **7.5839** and BPB
|
| 161 |
+
**2.3012**. A second core pass improves that checkpoint, but it does not outperform
|
| 162 |
+
the separately trained dense baseline in this trial. These runs match training
|
| 163 |
+
tokens, not compute. One seed and 100 steps do not establish long-run quality.
|
| 164 |
+
CE includes EOS; BPB uses text-token NLL and exact UTF-8 byte counts, excluding
|
| 165 |
+
special tokens from both numerator and denominator.
|
| 166 |
+
|
| 167 |
+
- [Dense baseline](https://wandb.ai/pplx-ai/nano-1b-ablation/runs/opurhf9m)
|
| 168 |
+
- [Recurrent trial](https://wandb.ai/pplx-ai/nano-1b-ablation/runs/h73o4lvs)
|
| 169 |
+
|
| 170 |
+
The trial completed with finite losses, gradients and weight-update probes in
|
| 171 |
+
all three sections. Model/optimizer weights remain on the training cluster.
|
| 172 |
+
W&B server readback verified 100 training points and all 72 per-panel evaluation
|
| 173 |
+
records (six panels × six checkpoints × two depths).
|
| 174 |
|
| 175 |
## Validation
|
| 176 |
|
| 177 |
+
Eleven local behavioral tests passed with Python 3.12, PyTorch 2.12.0 and
|
| 178 |
+
Transformers 5.9.0. Tests remain outside this model repository. They cover:
|
| 179 |
|
| 180 |
+
- Full-size parameter count on meta device and the 40-layer execution schedule.
|
| 181 |
+
- Exact K=1 initialization, logits and gradients versus the dense reference.
|
| 182 |
+
- K=2 outputs versus an independently unrolled network and summed shared gradients.
|
| 183 |
+
- Gradient checkpointing, model save/reload and optimizer continuation.
|
| 184 |
+
- Separate cache slots, chunked decoding and cached/uncached greedy generation.
|
| 185 |
+
- Depth-change rejection, invalid settings, packed-document isolation and padding.
|
| 186 |
+
- FlashAttention varlen dispatch at every execution, with external kernels stubbed.
|
|
|
|
|
|
|
| 187 |
|
| 188 |
+
The full-size GPU trial used SDPA, PyTorch
|
| 189 |
+
`2.12.0a0+0291f960b6.nv26.04.48445190`, CUDA 13.2 and Transformers 5.9.0.
|
| 190 |
+
Actual FlashAttention kernels and 128K training were not evaluated.
|
|
|
|
| 191 |
|
| 192 |
## Sources and licenses
|
| 193 |
|
| 194 |
+
- [MiniCPM5-1B configuration](https://huggingface.co/openbmb/MiniCPM5-1B/blob/87179e5c1f455ef22e6223592d2d61351b525bfc/config.json): base dimensions.
|
| 195 |
- [Transformers 5.9.0 DeepSeek V4](https://github.com/huggingface/transformers/blob/v5.9.0/src/transformers/models/deepseek_v4/modeling_deepseek_v4.py): RMSNorm, dense MLP and interleaved RoPE.
|
| 196 |
- Model implementation: Apache-2.0, see `LICENSE`.
|
| 197 |
- DeepSeek tokenizer assets: MIT, see `LICENSE-TOKENIZER`.
|
| 198 |
|
| 199 |
+
No pretrained MiniCPM or DeepSeek weights are included or converted.
|
config.json
CHANGED
|
@@ -19,7 +19,11 @@
|
|
| 19 |
"intermediate_size": 4608,
|
| 20 |
"max_position_embeddings": 131072,
|
| 21 |
"mlp_bias": false,
|
| 22 |
-
"model_type": "
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
"num_attention_heads": 16,
|
| 24 |
"num_hidden_layers": 24,
|
| 25 |
"num_key_value_heads": 2,
|
|
|
|
| 19 |
"intermediate_size": 4608,
|
| 20 |
"max_position_embeddings": 131072,
|
| 21 |
"mlp_bias": false,
|
| 22 |
+
"model_type": "nano_dense_loop",
|
| 23 |
+
"prelude_num_layers": 4,
|
| 24 |
+
"core_num_layers": 16,
|
| 25 |
+
"coda_num_layers": 4,
|
| 26 |
+
"num_core_iterations": 2,
|
| 27 |
"num_attention_heads": 16,
|
| 28 |
"num_hidden_layers": 24,
|
| 29 |
"num_key_value_heads": 2,
|
configuration_nano_dense.py
CHANGED
|
@@ -19,13 +19,17 @@ def _reject_unused(values):
|
|
| 19 |
|
| 20 |
@strict
|
| 21 |
class NanoDenseConfig(PreTrainedConfig):
|
| 22 |
-
model_type = "
|
| 23 |
keys_to_ignore_at_inference = ["past_key_values"]
|
| 24 |
|
| 25 |
vocab_size: int = 129280
|
| 26 |
hidden_size: int = 1536
|
| 27 |
intermediate_size: int = 4608
|
| 28 |
num_hidden_layers: int = 24
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
num_attention_heads: int = 16
|
| 30 |
num_key_value_heads: int = 2
|
| 31 |
head_dim: int = 128
|
|
@@ -80,6 +84,11 @@ class NanoDenseConfig(PreTrainedConfig):
|
|
| 80 |
|
| 81 |
def validate_architecture(self):
|
| 82 |
_reject_unused(vars(self))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
for name in ("vocab_size", "hidden_size", "intermediate_size", "num_hidden_layers",
|
| 84 |
"num_attention_heads", "num_key_value_heads", "head_dim", "max_position_embeddings"):
|
| 85 |
value = getattr(self, name)
|
|
@@ -99,5 +108,13 @@ class NanoDenseConfig(PreTrainedConfig):
|
|
| 99 |
if not math.isfinite(self.rms_norm_eps) or self.rms_norm_eps <= 0:
|
| 100 |
raise ValueError("rms_norm_eps must be finite and positive")
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
NanoDenseConfig.register_for_auto_class()
|
|
|
|
| 19 |
|
| 20 |
@strict
|
| 21 |
class NanoDenseConfig(PreTrainedConfig):
|
| 22 |
+
model_type = "nano_dense_loop"
|
| 23 |
keys_to_ignore_at_inference = ["past_key_values"]
|
| 24 |
|
| 25 |
vocab_size: int = 129280
|
| 26 |
hidden_size: int = 1536
|
| 27 |
intermediate_size: int = 4608
|
| 28 |
num_hidden_layers: int = 24
|
| 29 |
+
prelude_num_layers: int = 4
|
| 30 |
+
core_num_layers: int = 16
|
| 31 |
+
coda_num_layers: int = 4
|
| 32 |
+
num_core_iterations: int = 2
|
| 33 |
num_attention_heads: int = 16
|
| 34 |
num_key_value_heads: int = 2
|
| 35 |
head_dim: int = 128
|
|
|
|
| 84 |
|
| 85 |
def validate_architecture(self):
|
| 86 |
_reject_unused(vars(self))
|
| 87 |
+
for name in ('prelude_num_layers', 'core_num_layers', 'coda_num_layers', 'num_core_iterations'):
|
| 88 |
+
if type(getattr(self, name)) is not int or getattr(self, name) <= 0:
|
| 89 |
+
raise ValueError(f'{name} must be a positive integer')
|
| 90 |
+
if self.prelude_num_layers + self.core_num_layers + self.coda_num_layers != self.num_hidden_layers:
|
| 91 |
+
raise ValueError('prelude + core + coda must equal num_hidden_layers')
|
| 92 |
for name in ("vocab_size", "hidden_size", "intermediate_size", "num_hidden_layers",
|
| 93 |
"num_attention_heads", "num_key_value_heads", "head_dim", "max_position_embeddings"):
|
| 94 |
value = getattr(self, name)
|
|
|
|
| 108 |
if not math.isfinite(self.rms_norm_eps) or self.rms_norm_eps <= 0:
|
| 109 |
raise ValueError("rms_norm_eps must be finite and positive")
|
| 110 |
|
| 111 |
+
def get_layer_schedule(self, num_core_iterations=None):
|
| 112 |
+
iterations = self.num_core_iterations if num_core_iterations is None else num_core_iterations
|
| 113 |
+
if type(iterations) is not int or iterations <= 0:
|
| 114 |
+
raise ValueError('num_core_iterations must be a positive integer')
|
| 115 |
+
start = self.prelude_num_layers
|
| 116 |
+
end = start + self.core_num_layers
|
| 117 |
+
return tuple(range(start)) + tuple(range(start, end)) * iterations + tuple(range(end, self.num_hidden_layers))
|
| 118 |
+
|
| 119 |
|
| 120 |
NanoDenseConfig.register_for_auto_class()
|
count_parameters.py
CHANGED
|
@@ -20,18 +20,27 @@ def parameter_breakdown(config):
|
|
| 20 |
if __name__ == "__main__":
|
| 21 |
parser = argparse.ArgumentParser(description=__doc__)
|
| 22 |
parser.add_argument("--meta", action="store_true")
|
|
|
|
| 23 |
args = parser.parse_args()
|
| 24 |
root = Path(__file__).resolve().parent
|
| 25 |
config = json.loads((root / "config.json").read_text())
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
result = parameter_breakdown(config)
|
|
|
|
|
|
|
|
|
|
| 27 |
result["bf16_kv_bytes_at_context_limit_batch1"] = (
|
| 28 |
-
|
| 29 |
* config["head_dim"] * 2 * config["max_position_embeddings"]
|
| 30 |
)
|
| 31 |
if args.meta:
|
| 32 |
import torch
|
| 33 |
from transformers import AutoConfig, AutoModelForCausalLM
|
| 34 |
cfg = AutoConfig.from_pretrained(root, trust_remote_code=True)
|
|
|
|
| 35 |
with torch.device("meta"):
|
| 36 |
model = AutoModelForCausalLM.from_config(cfg, trust_remote_code=True)
|
| 37 |
actual = sum(p.numel() for p in model.parameters())
|
|
|
|
| 20 |
if __name__ == "__main__":
|
| 21 |
parser = argparse.ArgumentParser(description=__doc__)
|
| 22 |
parser.add_argument("--meta", action="store_true")
|
| 23 |
+
parser.add_argument("--core-iterations", type=int, default=None)
|
| 24 |
args = parser.parse_args()
|
| 25 |
root = Path(__file__).resolve().parent
|
| 26 |
config = json.loads((root / "config.json").read_text())
|
| 27 |
+
iterations = config["num_core_iterations"] if args.core_iterations is None else args.core_iterations
|
| 28 |
+
if iterations <= 0:
|
| 29 |
+
parser.error("--core-iterations must be positive")
|
| 30 |
+
effective_layers = config["prelude_num_layers"] + config["core_num_layers"] * iterations + config["coda_num_layers"]
|
| 31 |
result = parameter_breakdown(config)
|
| 32 |
+
result["physical_layers"] = config["num_hidden_layers"]
|
| 33 |
+
result["core_iterations"] = iterations
|
| 34 |
+
result["effective_layers"] = effective_layers
|
| 35 |
result["bf16_kv_bytes_at_context_limit_batch1"] = (
|
| 36 |
+
effective_layers * 2 * config["num_key_value_heads"]
|
| 37 |
* config["head_dim"] * 2 * config["max_position_embeddings"]
|
| 38 |
)
|
| 39 |
if args.meta:
|
| 40 |
import torch
|
| 41 |
from transformers import AutoConfig, AutoModelForCausalLM
|
| 42 |
cfg = AutoConfig.from_pretrained(root, trust_remote_code=True)
|
| 43 |
+
cfg.num_core_iterations = iterations
|
| 44 |
with torch.device("meta"):
|
| 45 |
model = AutoModelForCausalLM.from_config(cfg, trust_remote_code=True)
|
| 46 |
actual = sum(p.numel() for p in model.parameters())
|
modeling_nano_dense.py
CHANGED
|
@@ -3,6 +3,8 @@
|
|
| 3 |
# Licensed under the Apache License, Version 2.0.
|
| 4 |
"""Nano dense decoder using DeepSeek V4 normalization, MLP and interleaved RoPE."""
|
| 5 |
|
|
|
|
|
|
|
| 6 |
import torch
|
| 7 |
from torch import nn
|
| 8 |
from transformers import initialization as init
|
|
@@ -22,6 +24,15 @@ from transformers.utils.output_capturing import capture_outputs
|
|
| 22 |
from .configuration_nano_dense import NanoDenseConfig
|
| 23 |
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def _document_boundaries(position_ids, attention_mask):
|
| 26 |
boundaries = position_ids[:, 1:] != position_ids[:, :-1] + 1
|
| 27 |
if attention_mask is not None:
|
|
@@ -57,7 +68,7 @@ class NanoDenseAttention(nn.Module):
|
|
| 57 |
self.o_proj = nn.Linear(config.num_attention_heads * self.head_dim, config.hidden_size, bias=False)
|
| 58 |
|
| 59 |
def forward(self, hidden_states, position_embeddings, attention_mask=None,
|
| 60 |
-
past_key_values=None, **kwargs):
|
| 61 |
batch, length, _ = hidden_states.shape
|
| 62 |
shape = (batch, length, -1, self.head_dim)
|
| 63 |
query = self.q_proj(hidden_states).view(shape).transpose(1, 2)
|
|
@@ -67,7 +78,7 @@ class NanoDenseAttention(nn.Module):
|
|
| 67 |
query = apply_rotary_pos_emb(query, cos, sin)
|
| 68 |
key = apply_rotary_pos_emb(key, cos, sin)
|
| 69 |
if past_key_values is not None:
|
| 70 |
-
key, value = past_key_values.update(key, value,
|
| 71 |
backend = ALL_ATTENTION_FUNCTIONS.get_interface(
|
| 72 |
self.config._attn_implementation, eager_attention_forward
|
| 73 |
)
|
|
@@ -132,12 +143,30 @@ class NanoDenseModel(NanoDensePreTrainedModel):
|
|
| 132 |
self.gradient_checkpointing = False
|
| 133 |
self.post_init()
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
@merge_with_config_defaults
|
| 136 |
@capture_outputs
|
| 137 |
def forward(self, input_ids=None, attention_mask=None, position_ids=None,
|
| 138 |
-
past_key_values=None, inputs_embeds=None, use_cache=None,
|
|
|
|
| 139 |
if (input_ids is None) == (inputs_embeds is None):
|
| 140 |
raise ValueError("Specify exactly one of input_ids and inputs_embeds")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
if inputs_embeds is None:
|
| 142 |
inputs_embeds = self.embed_tokens(input_ids)
|
| 143 |
if self.training:
|
|
@@ -173,19 +202,20 @@ class NanoDenseModel(NanoDensePreTrainedModel):
|
|
| 173 |
raise ValueError("Packed documents with padding are unsupported; use padding-free packing")
|
| 174 |
attention_mask = None
|
| 175 |
use_cache = False
|
| 176 |
-
if use_cache
|
| 177 |
-
past_key_values =
|
| 178 |
mask = create_causal_mask(
|
| 179 |
config=self.config, inputs_embeds=inputs_embeds, attention_mask=attention_mask,
|
| 180 |
past_key_values=past_key_values, position_ids=position_ids,
|
| 181 |
)
|
| 182 |
position_embeddings = self.rotary_emb(inputs_embeds, position_ids, layer_type="main")
|
| 183 |
hidden_states = inputs_embeds
|
| 184 |
-
for
|
|
|
|
| 185 |
hidden_states = layer(
|
| 186 |
hidden_states, attention_mask=mask, position_ids=position_ids,
|
| 187 |
position_embeddings=position_embeddings, past_key_values=past_key_values,
|
| 188 |
-
use_cache=use_cache, **kwargs,
|
| 189 |
)
|
| 190 |
return BaseModelOutputWithPast(
|
| 191 |
last_hidden_state=self.norm(hidden_states), past_key_values=past_key_values,
|
|
@@ -205,13 +235,13 @@ class NanoDenseForCausalLM(NanoDensePreTrainedModel, GenerationMixin):
|
|
| 205 |
@can_return_tuple
|
| 206 |
def forward(self, input_ids=None, attention_mask=None, position_ids=None,
|
| 207 |
past_key_values=None, inputs_embeds=None, labels=None, use_cache=None,
|
| 208 |
-
logits_to_keep=0, **kwargs):
|
| 209 |
if labels is not None and not (isinstance(logits_to_keep, int) and logits_to_keep == 0):
|
| 210 |
raise ValueError("Training with labels requires all logits (logits_to_keep=0)")
|
| 211 |
outputs = self.model(
|
| 212 |
input_ids=input_ids, attention_mask=attention_mask, position_ids=position_ids,
|
| 213 |
past_key_values=past_key_values, inputs_embeds=inputs_embeds,
|
| 214 |
-
use_cache=use_cache, **kwargs,
|
| 215 |
)
|
| 216 |
indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
|
| 217 |
logits = self.lm_head(outputs.last_hidden_state[:, indices, :])
|
|
@@ -228,6 +258,11 @@ class NanoDenseForCausalLM(NanoDensePreTrainedModel, GenerationMixin):
|
|
| 228 |
hidden_states=outputs.hidden_states, attentions=outputs.attentions,
|
| 229 |
)
|
| 230 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
NanoDenseModel.register_for_auto_class("AutoModel")
|
| 233 |
NanoDenseForCausalLM.register_for_auto_class("AutoModelForCausalLM")
|
|
|
|
| 3 |
# Licensed under the Apache License, Version 2.0.
|
| 4 |
"""Nano dense decoder using DeepSeek V4 normalization, MLP and interleaved RoPE."""
|
| 5 |
|
| 6 |
+
from copy import copy
|
| 7 |
+
|
| 8 |
import torch
|
| 9 |
from torch import nn
|
| 10 |
from transformers import initialization as init
|
|
|
|
| 24 |
from .configuration_nano_dense import NanoDenseConfig
|
| 25 |
|
| 26 |
|
| 27 |
+
class NanoDenseLoopCache(DynamicCache):
|
| 28 |
+
def __init__(self, config, num_core_iterations):
|
| 29 |
+
self.num_core_iterations = num_core_iterations
|
| 30 |
+
self.physical_layer_indices = config.get_layer_schedule(num_core_iterations)
|
| 31 |
+
cache_config = copy(config)
|
| 32 |
+
cache_config.num_hidden_layers = len(self.physical_layer_indices)
|
| 33 |
+
super().__init__(config=cache_config)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
def _document_boundaries(position_ids, attention_mask):
|
| 37 |
boundaries = position_ids[:, 1:] != position_ids[:, :-1] + 1
|
| 38 |
if attention_mask is not None:
|
|
|
|
| 68 |
self.o_proj = nn.Linear(config.num_attention_heads * self.head_dim, config.hidden_size, bias=False)
|
| 69 |
|
| 70 |
def forward(self, hidden_states, position_embeddings, attention_mask=None,
|
| 71 |
+
past_key_values=None, cache_layer_idx=None, **kwargs):
|
| 72 |
batch, length, _ = hidden_states.shape
|
| 73 |
shape = (batch, length, -1, self.head_dim)
|
| 74 |
query = self.q_proj(hidden_states).view(shape).transpose(1, 2)
|
|
|
|
| 78 |
query = apply_rotary_pos_emb(query, cos, sin)
|
| 79 |
key = apply_rotary_pos_emb(key, cos, sin)
|
| 80 |
if past_key_values is not None:
|
| 81 |
+
key, value = past_key_values.update(key, value, cache_layer_idx)
|
| 82 |
backend = ALL_ATTENTION_FUNCTIONS.get_interface(
|
| 83 |
self.config._attn_implementation, eager_attention_forward
|
| 84 |
)
|
|
|
|
| 143 |
self.gradient_checkpointing = False
|
| 144 |
self.post_init()
|
| 145 |
|
| 146 |
+
def _prepare_loop_cache(self, cache, iterations, schedule):
|
| 147 |
+
if isinstance(cache, NanoDenseLoopCache):
|
| 148 |
+
if cache.num_core_iterations != iterations or cache.physical_layer_indices != schedule:
|
| 149 |
+
raise ValueError('Cannot change iterations or layer schedule of an existing cache')
|
| 150 |
+
return cache
|
| 151 |
+
if cache is not None:
|
| 152 |
+
if type(cache) is not DynamicCache or cache.offloading or any(
|
| 153 |
+
layer.get_seq_length() != 0 for layer in cache.layers
|
| 154 |
+
):
|
| 155 |
+
raise ValueError('Use an empty DynamicCache or the returned loop cache')
|
| 156 |
+
return NanoDenseLoopCache(self.config, iterations)
|
| 157 |
+
|
| 158 |
@merge_with_config_defaults
|
| 159 |
@capture_outputs
|
| 160 |
def forward(self, input_ids=None, attention_mask=None, position_ids=None,
|
| 161 |
+
past_key_values=None, inputs_embeds=None, use_cache=None,
|
| 162 |
+
num_core_iterations=None, **kwargs):
|
| 163 |
if (input_ids is None) == (inputs_embeds is None):
|
| 164 |
raise ValueError("Specify exactly one of input_ids and inputs_embeds")
|
| 165 |
+
if num_core_iterations is None:
|
| 166 |
+
num_core_iterations = (past_key_values.num_core_iterations
|
| 167 |
+
if isinstance(past_key_values, NanoDenseLoopCache)
|
| 168 |
+
else self.config.num_core_iterations)
|
| 169 |
+
schedule = self.config.get_layer_schedule(num_core_iterations)
|
| 170 |
if inputs_embeds is None:
|
| 171 |
inputs_embeds = self.embed_tokens(input_ids)
|
| 172 |
if self.training:
|
|
|
|
| 202 |
raise ValueError("Packed documents with padding are unsupported; use padding-free packing")
|
| 203 |
attention_mask = None
|
| 204 |
use_cache = False
|
| 205 |
+
if use_cache:
|
| 206 |
+
past_key_values = self._prepare_loop_cache(past_key_values, num_core_iterations, schedule)
|
| 207 |
mask = create_causal_mask(
|
| 208 |
config=self.config, inputs_embeds=inputs_embeds, attention_mask=attention_mask,
|
| 209 |
past_key_values=past_key_values, position_ids=position_ids,
|
| 210 |
)
|
| 211 |
position_embeddings = self.rotary_emb(inputs_embeds, position_ids, layer_type="main")
|
| 212 |
hidden_states = inputs_embeds
|
| 213 |
+
for execution_index, physical_index in enumerate(schedule):
|
| 214 |
+
layer = self.layers[physical_index]
|
| 215 |
hidden_states = layer(
|
| 216 |
hidden_states, attention_mask=mask, position_ids=position_ids,
|
| 217 |
position_embeddings=position_embeddings, past_key_values=past_key_values,
|
| 218 |
+
cache_layer_idx=execution_index, use_cache=use_cache, **kwargs,
|
| 219 |
)
|
| 220 |
return BaseModelOutputWithPast(
|
| 221 |
last_hidden_state=self.norm(hidden_states), past_key_values=past_key_values,
|
|
|
|
| 235 |
@can_return_tuple
|
| 236 |
def forward(self, input_ids=None, attention_mask=None, position_ids=None,
|
| 237 |
past_key_values=None, inputs_embeds=None, labels=None, use_cache=None,
|
| 238 |
+
logits_to_keep=0, num_core_iterations=None, **kwargs):
|
| 239 |
if labels is not None and not (isinstance(logits_to_keep, int) and logits_to_keep == 0):
|
| 240 |
raise ValueError("Training with labels requires all logits (logits_to_keep=0)")
|
| 241 |
outputs = self.model(
|
| 242 |
input_ids=input_ids, attention_mask=attention_mask, position_ids=position_ids,
|
| 243 |
past_key_values=past_key_values, inputs_embeds=inputs_embeds,
|
| 244 |
+
use_cache=use_cache, num_core_iterations=num_core_iterations, **kwargs,
|
| 245 |
)
|
| 246 |
indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
|
| 247 |
logits = self.lm_head(outputs.last_hidden_state[:, indices, :])
|
|
|
|
| 258 |
hidden_states=outputs.hidden_states, attentions=outputs.attentions,
|
| 259 |
)
|
| 260 |
|
| 261 |
+
def _prepare_cache_for_generation(self, generation_config, *args, **kwargs):
|
| 262 |
+
if generation_config.cache_implementation not in (None, 'dynamic'):
|
| 263 |
+
raise ValueError("Loop generation supports only cache_implementation='dynamic'")
|
| 264 |
+
return super()._prepare_cache_for_generation(generation_config, *args, **kwargs)
|
| 265 |
+
|
| 266 |
|
| 267 |
NanoDenseModel.register_for_auto_class("AutoModel")
|
| 268 |
NanoDenseForCausalLM.register_for_auto_class("AutoModelForCausalLM")
|