joerowell varunrandery commited on
Commit
216d1f1
·
0 Parent(s):

Laguna S 2.1 initial release

Browse files

Co-authored-by: varunrandery <varunrandery@users.noreply.huggingface.co>

.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
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
36
+ model.safetensors.index.json filter=lfs diff=lfs merge=lfs -text
LICENSE.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ OpenMDW License Agreement, version 1.1 (OpenMDW-1.1)
2
+
3
+ By exercising rights granted to you under this agreement, you accept and agree
4
+ to its terms.
5
+
6
+ As used in this agreement, "Model Materials" means the materials provided to
7
+ you under this agreement, consisting of: (1) one or more machine learning
8
+ models (including architecture and parameters); and (2) all related artifacts
9
+ (including associated data, documentation and software) that are provided to
10
+ you hereunder.
11
+
12
+ Subject to your compliance with this agreement, permission is hereby granted,
13
+ free of charge, to deal in the Model Materials without restriction, including
14
+ under all copyright, patent, database, and trade secret rights included or
15
+ embodied therein.
16
+
17
+ If you distribute any portion of the Model Materials, you shall retain in your
18
+ distribution (1) a copy of this agreement, and (2) all copyright notices and
19
+ other notices of origin included in the Model Materials that are applicable to
20
+ your distribution.
21
+
22
+ If you file, maintain, or voluntarily participate in a lawsuit against any
23
+ person or entity asserting that the Model Materials directly or indirectly
24
+ infringe any patent or copyright, then all rights and grants made to you
25
+ hereunder are terminated, unless that lawsuit was in response to a
26
+ corresponding lawsuit first brought against you.
27
+
28
+ This agreement does not impose any restrictions or obligations with respect to
29
+ any use, modification, or sharing of any outputs generated by using the Model
30
+ Materials.
31
+
32
+ THE MODEL MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
33
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE, NONINFRINGEMENT, ACCURACY, OR THE
35
+ ABSENCE OF LATENT OR OTHER DEFECTS OR ERRORS, WHETHER OR NOT DISCOVERABLE, ALL
36
+ TO THE GREATEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW.
37
+
38
+ YOU ARE SOLELY RESPONSIBLE FOR (1) CLEARING RIGHTS OF OTHER PERSONS THAT MAY
39
+ APPLY TO THE MODEL MATERIALS OR ANY USE THEREOF, INCLUDING WITHOUT LIMITATION
40
+ ANY PERSON'S COPYRIGHTS OR OTHER RIGHTS INCLUDED OR EMBODIED IN THE MODEL
41
+ MATERIALS; (2) OBTAINING ANY NECESSARY CONSENTS, PERMISSIONS OR OTHER RIGHTS
42
+ REQUIRED FOR ANY USE OF THE MODEL MATERIALS; OR (3) PERFORMING ANY DUE
43
+ DILIGENCE OR UNDERTAKING ANY OTHER INVESTIGATIONS INTO THE MODEL MATERIALS OR
44
+ ANYTHING INCORPORATED OR EMBODIED THEREIN.
45
+
46
+ IN NO EVENT SHALL THE PROVIDERS OF THE MODEL MATERIALS BE LIABLE FOR ANY CLAIM,
47
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
48
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MODEL MATERIALS, THE
49
+ USE THEREOF OR OTHER DEALINGS THEREIN.
README.md ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: vllm
3
+ inference: false
4
+ extra_gated_description: >-
5
+ To learn more about how we process your personal data, please read our <a
6
+ href="https://poolside.ai/legal/privacy">Privacy Policy</a>.
7
+ tags:
8
+ - laguna-s-2.1
9
+ license: openmdw-1.1
10
+ pipeline_tag: text-generation
11
+ base_model:
12
+ - poolside/Laguna-S-2.1
13
+ ---
14
+
15
+ <p align="center">
16
+ <img alt="poolside-banner" src="https://poolside.ai/assets/laguna/laguna-s-2-1-banner.svg" width="800px">
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://openrouter.ai/poolside/laguna-s-2.1"><strong>Use on OpenRouter</strong></a> ·
21
+ <a href="https://vercel.com/ai-gateway/models/laguna-s-2.1"><strong>Use on Vercel AI Gateway</strong></a> ·
22
+ <a href="https://poolside.ai/blog/introducing-laguna-s-2-1"><strong>Release blog post</strong></a>
23
+ </p>
24
+
25
+ <br>
26
+
27
+ # Laguna S 2.1-NVFP4
28
+ Laguna S 2.1-NVFP4 is a 117.6B total parameter Mixture-of-Experts model with 8.5B activated parameters per token designed for agentic coding and long-horizon work on a local machine. It uses Sliding Window Attention with per-head gating in 36 out of 48 layers for fast inference and low KV cache requirements.
29
+
30
+
31
+ ## Highlights
32
+ - **Mixed SWA and global attention layout**: Laguna S 2.1 uses softplus gating with per-layer rotary scales, enabling mixed SWA (Sliding Window Attention) and global attention layers in a 3:1 ratio (across 48 total layers)
33
+ - **KV cache in FP8**: KV cache quantized to FP8, reducing memory per token
34
+ - **Native reasoning support**: Interleaved thinking between tool calls with support for enabling and disabling thinking per-request
35
+ - **Local-ready**: At 117.6B total parameters and 8.5B activated, the NVFP4 weights are roughly 71 GB. Available on [Ollama](https://ollama.com/laguna-s-2.1) and [llama.cpp](https://github.com/ggml-org/llama.cpp/pull/25165) (BF16 and Q4\_K\_M only)
36
+ - **OpenMDW-1.1 license**: Use and modify the model and associated materials freely for commercial and non-commercial purposes ([learn more about OpenMDW](https://openmdw.ai/))
37
+
38
+ ---
39
+
40
+ ## Model overview
41
+
42
+ - Training: pre-training, post-training and reinforcement learning stages
43
+ - Number of parameters: 117.6B total with 8.5B activated per token
44
+ - Optimizer: Muon
45
+ - Layers: 48 layers (12 layers with global attention, 36 layers with sliding window attention)
46
+ - Experts: 256 experts with 1 shared expert
47
+ - Sliding Window: 512 tokens
48
+ - Modality: text-to-text
49
+ - Context window: 262,144 tokens
50
+ - Reasoning support: interleaved thinking with preserved thinking
51
+
52
+
53
+ ## Benchmark results
54
+
55
+ <p align="center">
56
+ <img alt="benchmarks" src="https://poolside.ai/assets/laguna/laguna-s-2-1-chart.svg" width="800px">
57
+ </p>
58
+
59
+ | Model | Size | Terminal-Bench 2.1 | SWE-bench Multilingual | SWE-Bench Pro (Public Dataset) | DeepSWE | SWE Atlas (Codebase QnA) | Toolathlon Verified |
60
+ |---|---|---|---|---|---|---|---|
61
+ | **Laguna S 2.1** | 118B-A8B | **70.2%** | **78.5%** | **59.4%** | **40.4%** | **46.2%** | **49.7%** |
62
+ | Tencent Hy3 | 295B-A21B | 71.7% | 75.8% | 57.9% | - | - | - |
63
+ | Inkling | 975B-A41B | 63.8% | - | 54.3% | - | - | 45.5%* |
64
+ | Nemotron 3 Ultra | 550B-A55B | 56.4% | 67.7% | - | - | - | 34.3%* |
65
+ | DeepSeek-V4-Pro Max | 1.6T-A49B | 64.0%* | 76.2% | 55.4% | 9.0%* | 27.2%* | 55.9%* |
66
+ | Kimi K3 | 2800B-A50B | 88.3% | - | - | 69% | - | - |
67
+ | Qwen 3.7 Max | - | 74.5%* | 78.3% | 60.6% | - | - | - |
68
+ | Muse Spark 1.1 | - | 80% | - | 61.5% | 53.3% | 42.2%* | 75.6% |
69
+ | Claude Fable 5 | - | 88% | - | 80.3% | 70% | - | - |
70
+
71
+ Benchmarks as of 21 July 2026. Laguna S 2.1 in **bold**; a dash (-) marks a benchmark a model was not evaluated on. Scores marked * are as reported by third parties: Terminal-Bench 2.1 and DeepSWE via Artificial Analysis, SWE Atlas via Scale AI's official leaderboard, and Toolathlon Verified via its official leaderboard. Full evaluation trajectories: [trajectories.poolside.ai](https://trajectories.poolside.ai).
72
+
73
+ ## Usage
74
+
75
+ ### Context length
76
+
77
+ This checkpoint ships configured for a 262,144-token (256K) context window. This is the configuration we recommend for best output quality.
78
+
79
+ The weights are native 1M checkpoints: training included a long-context extension stage up to 1,048,576 tokens, and quantization was calibrated at the 1M configuration. If you need more than 256K of context, restore the 1M configuration by editing `config.json`:
80
+
81
+ ```json
82
+ "rope_parameters": {
83
+ "full_attention": {
84
+ "factor": 128.0,
85
+ "attention_factor": 1.4852030263919618
86
+ }
87
+ },
88
+ "max_position_embeddings": 1048576
89
+ ```
90
+
91
+ You may experience quality degradation with the 1M configuration. If you use it, we recommend sampling with `temperature <= 0.7` and `top_p <= 0.95`.
92
+
93
+ ### Recommended sampling
94
+
95
+ For the best balance of quality and reliability we recommend sampling with `temperature 0.7` and `top_p 0.95`.
96
+
97
+
98
+ ### Local deployment
99
+
100
+ Laguna S 2.1-NVFP4 is supported in vLLM, SGLang and Transformers, and TRT-LLM thanks to the support of the team at NVIDIA. Use Laguna-S 2.1 with Ollama (with MLX support) or Llama.cpp (BF16 and Q4\_K\_M only) for the best results on your local machine.
101
+
102
+ #### vLLM
103
+
104
+ > Serving requires vLLM 0.25.0 or later. See the main [Laguna S 2.1 model card](https://huggingface.co/poolside/Laguna-S-2.1) for the full recipe.
105
+
106
+ The full vLLM recipe is on the main [Laguna S 2.1 model card](https://huggingface.co/poolside/Laguna-S-2.1) and on the [vLLM recipes page](https://recipes.vllm.ai/poolside/Laguna-S-2.1). Quantization is detected automatically from `quantization_config` in this checkpoint, so the same command works with `poolside/Laguna-S-2.1-NVFP4` substituted for the model ID. No extra flags required.
107
+
108
+ > [!NOTE]
109
+ > **Optional: speculative decoding with DFlash.** Pair with the quantization-matched draft model [poolside/Laguna-S-2.1-DFlash-NVFP4](https://huggingface.co/poolside/Laguna-S-2.1-DFlash-NVFP4) by adding `--speculative-config '{"model":"poolside/Laguna-S-2.1-DFlash-NVFP4","num_speculative_tokens":15,"method":"dflash"}'` to the serve command.
110
+
111
+ #### DGX Spark & Mac Studio
112
+
113
+ Ollama is the quickest way to run Laguna S 2.1 on a single 128 GB box, whether
114
+ that is an NVIDIA DGX Spark (GB10) or an Apple Silicon Mac Studio:
115
+
116
+ ```bash
117
+ ollama run laguna-s-2.1
118
+ ```
119
+
120
+ It pulls the Q4\_K\_M GGUF (about 75 GB) and runs on the GB10 GPU on the Spark
121
+ or on Metal on the Mac, with no build step. We measured about 12.6 tokens/s on
122
+ the Spark and 17.6 on Apple Silicon.
123
+
124
+ - You need about 128 GB of unified memory; the Q4\_K\_M weights are around
125
+ 75 GB on their own.
126
+ - The first load reads 75 GB off disk and can run past Ollama's 5-minute
127
+ default timeout. If it does, set `OLLAMA_LOAD_TIMEOUT=20m`.
128
+ - Reasoning is on by default. Pass `"think": false` in the request to turn it off.
129
+ - The higher-fidelity tags need a bigger box: `laguna-s-2.1:q8_0` is about
130
+ 128 GB (so a 192 GB Mac Studio) and `laguna-s-2.1:f16` is about 235 GB (256 GB
131
+ or more). On the Spark's 128 GB, stick to Q4\_K\_M. Apple Silicon can also
132
+ run it through MLX.
133
+ - Ollama is built on llama.cpp. To build the runtime yourself, poolside's
134
+ [`laguna` branch of llama.cpp](https://github.com/poolsideai/llama.cpp/tree/laguna)
135
+ serves the same GGUFs.
136
+
137
+ **Maximum performance on the DGX Spark (native NVFP4 + DFlash)**
138
+
139
+ For the fastest setup on the Spark, serve this checkpoint with vLLM instead. That
140
+ gives you the native NVFP4 kernels and DFlash speculative decoding.
141
+
142
+ *One-time setup:*
143
+
144
+ ```bash
145
+ # Python headers: Triton JIT needs them and DGX OS ships without them.
146
+ # Without this, the first server start dies in triton/runtime/build.py.
147
+ sudo apt install -y python3.12-dev
148
+
149
+ curl -LsSf https://astral.sh/uv/install.sh | sh
150
+ uv venv ~/venvs/vllm025 -p 3.12
151
+
152
+ # vLLM 0.25.1 with CUDA-13 torch (aarch64 wheels are on PyPI)
153
+ uv pip install -p ~/venvs/vllm025 vllm==0.25.1 --torch-backend=cu130
154
+
155
+ # FlashInfer nightly trio: without flashinfer-python the NVFP4 path is not
156
+ # native; the jit-cache wheel avoids most first-start JIT compilation.
157
+ uv pip install -p ~/venvs/vllm025 \
158
+ "flashinfer-python==0.6.15.dev20260712" \
159
+ "flashinfer-cubin==0.6.15.dev20260712" \
160
+ "flashinfer-jit-cache==0.6.15.dev20260712" \
161
+ --extra-index-url https://flashinfer.ai/whl/nightly/ \
162
+ --extra-index-url https://flashinfer.ai/whl/nightly/cu130/ \
163
+ --index-strategy unsafe-best-match
164
+
165
+ hf download poolside/Laguna-S-2.1-NVFP4
166
+ hf download poolside/Laguna-S-2.1-DFlash-NVFP4 # 73 GB total
167
+ ```
168
+
169
+ *Serve:*
170
+
171
+ ```bash
172
+ export CUTE_DSL_ARCH=sm_121a # arch string for FP4 kernel JIT
173
+ export PATH=/usr/local/cuda/bin:$PATH # nvcc for JIT
174
+ export MAX_JOBS=4 # cap JIT fan-out; see warning below
175
+ source ~/venvs/vllm025/bin/activate
176
+
177
+ vllm serve poolside/Laguna-S-2.1-NVFP4 \
178
+ --speculative-config '{"model":"poolside/Laguna-S-2.1-DFlash-NVFP4","num_speculative_tokens":15}' \
179
+ --enable-auto-tool-choice \
180
+ --tool-call-parser poolside_v1 \
181
+ --reasoning-parser poolside_v1 \
182
+ --override-generation-config '{"temperature":0.7,"top_p":0.95}' \
183
+ --max-num-seqs 32 \
184
+ --max-model-len 262144 \
185
+ --gpu-memory-utilization 0.85 \
186
+ --host 0.0.0.0 --port 8000
187
+ ```
188
+
189
+ - You do not need backend flags: auto-selection picks FlashInferCutlass, which
190
+ runs natively on `sm_121`. Do not set `--linear-backend flashinfer_b12x` on
191
+ 0.25.1; the opt-in is broken there and it is slower anyway.
192
+ - Keep the `--override-generation-config`. Many clients send no sampling
193
+ parameters, and the raw defaults degrade output on the NVFP4 quantization. The
194
+ model's `generation_config.json` sets `top_k 20` (eval-certified truncation),
195
+ which handles that. Do not add `min_p`: vLLM rejects `min_p` and `logit_bias`
196
+ under speculative decoding, so putting it in the defaults returns a 400 on
197
+ every sampled request.
198
+ - `--max-num-seqs 32` is required: DFlash crashes vLLM at the default of 256.
199
+ - The first start takes about 15 minutes (weight load from NVMe, JIT, and graph
200
+ capture).
201
+
202
+ > [!WARNING]
203
+ > Never drop `MAX_JOBS=4` on a cold `~/.cache/flashinfer`. An uncapped nvcc
204
+ > fan-out can exhaust the 128 GB unified memory and take the whole machine down.
205
+ > A warm cache makes it a no-op, but the cache is cold again after any config or
206
+ > shape change.
207
+
208
+ Prefill runs 600-800 tok/s, decode is around 15 tok/s on prose and 22-24 on
209
+ code, and DFlash accepts 2.9-3.1 tokens per step. At the 256K setting you get
210
+ 830-870K KV tokens. Two requests at once roughly double total throughput.
211
+ Without speculation, decode sits at 13-14 tok/s on every engine we tried on the
212
+ GB10, which is the memory-bandwidth ceiling for this model. Speculation is how
213
+ you get past it.
214
+
215
+ *pool CLI against the box:*
216
+
217
+ ```bash
218
+ POOLSIDE_STANDALONE_BASE_URL=http://<spark-ip>:8000/v1 \
219
+ POOLSIDE_STANDALONE_MODEL=poolside/Laguna-S-2.1-NVFP4 \
220
+ POOLSIDE_STANDALONE_CONTEXT_LENGTH=262144 \
221
+ POOLSIDE_API_KEY=dummy pool
222
+ ```
223
+
224
+ Use the box's IPv4 address rather than the `.local` mDNS name: Go's resolver can
225
+ pick the link-local IPv6 and fail with "no route to host". On macOS the terminal
226
+ app needs Local Network permission, and the system curl is exempt from that
227
+ check, so if curl works but pool does not, it is the permission and not the
228
+ network.
229
+
230
+ #### SGLang
231
+
232
+ Laguna S 2.1 is supported in SGLang via [sgl-project/sglang#24204](https://github.com/sgl-project/sglang/pull/24204). Quantization is detected automatically from `quantization_config`, so no extra flags are required. See the [SGLang cookbook entry](https://docs.sglang.io/cookbook/autoregressive/Poolside/Laguna-S-2.1) and the main [Laguna S 2.1 model card](https://huggingface.co/poolside/Laguna-S-2.1) for a serving recipe.
233
+
234
+ #### Transformers
235
+
236
+ The full Transformers recipe is on the main [Laguna S 2.1 model card](https://huggingface.co/poolside/Laguna-S-2.1). Substitute `poolside/Laguna-S-2.1-NVFP4` for the model ID; quantization is detected automatically from `quantization_config`.
237
+
238
+ #### TRT-LLM
239
+
240
+ Laguna S 2.1 support ships in TensorRT-LLM `>=1.3.0rc16`; see the install recipe on the main [Laguna S 2.1 model card](https://huggingface.co/poolside/Laguna-S-2.1). Substitute `poolside/Laguna-S-2.1-NVFP4` for the model ID; quantization is detected automatically from `quantization_config`, no extra flags required.
241
+
242
+ ```python
243
+ from tensorrt_llm import LLM
244
+
245
+ llm = LLM(model="poolside/Laguna-S-2.1-NVFP4", trust_remote_code=True)
246
+ ```
247
+
248
+ #### Ollama
249
+
250
+ Available on the [Ollama library](https://ollama.com/laguna-s-2.1).
251
+
252
+
253
+ ## Controlling reasoning
254
+
255
+ Laguna S 2.1-NVFP4 uses the same reasoning controls (interleaved thinking, preserved reasoning, and the `enable_thinking` flag) as the base model. See the [Controlling reasoning](https://huggingface.co/poolside/Laguna-S-2.1#controlling-reasoning) section of the main Laguna S 2.1 model card.
256
+
257
+ ## License
258
+
259
+ This model is licensed under the [OpenMDW-1.1 License](https://huggingface.co/poolside/Laguna-S-2.1-NVFP4/blob/main/LICENSE.md).
260
+
261
+ ## Intended and Responsible Use
262
+
263
+ Laguna S 2.1-NVFP4 is designed for software engineering and agentic coding use cases, and you are responsible for confirming that it is appropriate for your intended application. Laguna S 2.1-NVFP4 is subject to the [OpenMDW-1.1 License](https://huggingface.co/poolside/Laguna-S-2.1-NVFP4/blob/main/LICENSE.md), and should be used consistently with Poolside's [Acceptable Use Policy](https://poolside.ai/legal/acceptable-use-policy). We advise against circumventing Laguna S 2.1-NVFP4 safety guardrails without implementing substantially equivalent mitigations appropriate for your use case.
264
+
265
+ Please report security vulnerabilities or safety concerns to [security@poolside.ai](mailto:security@poolside.ai).
266
+
chat_template.jinja ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {#- Iteration on laguna_glm_thinking_v8/chat_template.jinja -#}
2
+ {#- No formatting instructions -#}
3
+ {{- "〈|EOS|〉" -}}
4
+ {%- set enable_thinking = enable_thinking | default(false) -%}
5
+ {%- set add_generation_prompt = add_generation_prompt | default(false) -%}
6
+
7
+ {#- ───── header (system message) ───── -#}
8
+ {#- A caller-supplied system message with empty content opts out of the default below, producing no <system> block — used to train without a system message. -#}
9
+ {%- set system_message = "You are a helpful, conversationally-fluent assistant made by Poolside. You are here to be helpful to users through natural language conversations." -%}
10
+ {%- if messages and messages[0].role == "system" -%}
11
+ {%- set system_message = messages[0].content -%}
12
+ {%- set messages = messages[1:] -%}
13
+ {%- endif -%}
14
+
15
+ {%- set has_sys = system_message and system_message.strip() -%}
16
+ {%- if has_sys or tools or enable_thinking -%}
17
+ {{- "<system>" -}}
18
+
19
+ {%- if has_sys -%}
20
+ {{- system_message.rstrip() -}}
21
+ {%- if tools -%}{{- "\n\n" -}}{%- endif -%}
22
+ {%- endif -%}
23
+
24
+ {%- if tools -%}
25
+ {{- "### Tools\n\n" -}}
26
+ {{- "You may call functions to assist with the user query.\n" -}}
27
+ {{- "All available function signatures are listed below:\n" -}}
28
+ {{- "<available_tools>\n" -}}
29
+ {%- for tool in tools -%}
30
+ {{- (tool | tojson) ~ "\n" -}}
31
+ {%- endfor -%}
32
+ {{- "</available_tools>" -}}
33
+ {%- endif -%}
34
+
35
+ {{- "</system>\n" -}}
36
+ {%- endif -%}
37
+
38
+ {#- ───── main loop ───── -#}
39
+ {%- for message in messages -%}
40
+ {%- set content = message.content if message.content is string else "" -%}
41
+ {%- if message.role == "user" -%}
42
+ {{- "<user>" + content + "</user>\n" -}}
43
+ {%- elif message.role == "assistant" -%}
44
+ {%- generation -%}
45
+ {{- "<assistant>" -}}
46
+ {#- Extract reasoning content from message.reasoning (vLLM field name) or message.reasoning_content -#}
47
+ {%- set reasoning_content = '' -%}
48
+ {%- if message.reasoning is string -%}
49
+ {%- set reasoning_content = message.reasoning -%}
50
+ {%- elif message.reasoning_content is string -%}
51
+ {%- set reasoning_content = message.reasoning_content -%}
52
+ {%- endif -%}
53
+ {#- Display reasoning content for all messages if enable_thinking -#}
54
+ {%- if enable_thinking -%}
55
+ {{- '<think>' + reasoning_content + '</think>' -}}
56
+ {%- else -%}
57
+ {{- '</think>' -}}
58
+ {%- endif -%}
59
+ {#- Display main content (trailing newline only when no tool_calls follow) -#}
60
+ {%- if content -%}
61
+ {{- content -}}
62
+ {%- endif -%}
63
+ {%- if message.tool_calls -%}
64
+ {%- for tool_call in message.tool_calls -%}
65
+ {%- set function_data = tool_call.function -%}
66
+ {{- '<tool_call>' + function_data.name -}}
67
+ {%- set _args = function_data.arguments -%}
68
+ {%- for k, v in _args.items() -%}
69
+ {{- "<arg_key>" ~ k ~ "</arg_key>" -}}
70
+ {{- "<arg_value>" -}}{{- v | tojson(ensure_ascii=False) if v is not string else v -}}{{- "</arg_value>" -}}
71
+ {%- endfor -%}
72
+ {{- "</tool_call>" -}}
73
+ {%- endfor -%}
74
+ {%- endif -%}
75
+ {{- "</assistant>\n" -}}
76
+ {%- endgeneration -%}
77
+ {%- elif message.role == "tool" -%}
78
+ {{- "<tool_response>" + content + "</tool_response>\n" -}}
79
+ {%- elif message.role == "system" -%}
80
+ {#- Render additional system messages (the first one, if any, is handled separately in the header and was sliced off above) -#}
81
+ {{- "<system>" + content + "</system>\n" -}}
82
+ {%- endif -%}
83
+ {%- endfor -%}
84
+ {#- ───── generation prompt ───── -#}
85
+ {%- if add_generation_prompt -%}
86
+ {{- "<assistant>" -}}
87
+ {#- ───── Include reasoning mode directive ───── -#}
88
+ {%- if enable_thinking -%}
89
+ {{- '<think>' -}}
90
+ {%- else -%}
91
+ {{- '</think>' -}}
92
+ {%- endif -%}
93
+ {%- endif -%}
config.json ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "LagunaForCausalLM"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_laguna.LagunaConfig",
7
+ "AutoModelForCausalLM": "modeling_laguna.LagunaForCausalLM"
8
+ },
9
+ "model_type": "laguna",
10
+ "vocab_size": 100352,
11
+ "hidden_size": 3072,
12
+ "intermediate_size": 12288,
13
+ "num_hidden_layers": 48,
14
+ "num_attention_heads": 48,
15
+ "num_key_value_heads": 8,
16
+ "head_dim": 128,
17
+ "max_position_embeddings": 262144,
18
+ "attention_bias": false,
19
+ "attention_dropout": 0.0,
20
+ "rms_norm_eps": 1e-06,
21
+ "num_experts": 256,
22
+ "num_experts_per_tok": 10,
23
+ "moe_intermediate_size": 1024,
24
+ "shared_expert_intermediate_size": 1024,
25
+ "norm_topk_prob": true,
26
+ "router_aux_loss_coef": 0.0,
27
+ "moe_router_logit_softcapping": 0.0,
28
+ "decoder_sparse_step": 1,
29
+ "mlp_only_layers": [
30
+ 0
31
+ ],
32
+ "bos_token_id": 2,
33
+ "eos_token_id": [
34
+ 2,
35
+ 24
36
+ ],
37
+ "pad_token_id": 9,
38
+ "tie_word_embeddings": false,
39
+ "use_cache": true,
40
+ "torch_dtype": "bfloat16",
41
+ "gating": "per-head",
42
+ "sliding_window": 512,
43
+ "rope_parameters": {
44
+ "full_attention": {
45
+ "rope_theta": 500000.0,
46
+ "rope_type": "yarn",
47
+ "factor": 32.0,
48
+ "original_max_position_embeddings": 8192,
49
+ "beta_slow": 1.0,
50
+ "beta_fast": 32.0,
51
+ "attention_factor": 1.3465735902799727,
52
+ "partial_rotary_factor": 0.5
53
+ },
54
+ "sliding_attention": {
55
+ "rope_type": "default",
56
+ "rope_theta": 10000.0,
57
+ "partial_rotary_factor": 1.0
58
+ }
59
+ },
60
+ "layer_types": [
61
+ "full_attention",
62
+ "sliding_attention",
63
+ "sliding_attention",
64
+ "sliding_attention",
65
+ "full_attention",
66
+ "sliding_attention",
67
+ "sliding_attention",
68
+ "sliding_attention",
69
+ "full_attention",
70
+ "sliding_attention",
71
+ "sliding_attention",
72
+ "sliding_attention",
73
+ "full_attention",
74
+ "sliding_attention",
75
+ "sliding_attention",
76
+ "sliding_attention",
77
+ "full_attention",
78
+ "sliding_attention",
79
+ "sliding_attention",
80
+ "sliding_attention",
81
+ "full_attention",
82
+ "sliding_attention",
83
+ "sliding_attention",
84
+ "sliding_attention",
85
+ "full_attention",
86
+ "sliding_attention",
87
+ "sliding_attention",
88
+ "sliding_attention",
89
+ "full_attention",
90
+ "sliding_attention",
91
+ "sliding_attention",
92
+ "sliding_attention",
93
+ "full_attention",
94
+ "sliding_attention",
95
+ "sliding_attention",
96
+ "sliding_attention",
97
+ "full_attention",
98
+ "sliding_attention",
99
+ "sliding_attention",
100
+ "sliding_attention",
101
+ "full_attention",
102
+ "sliding_attention",
103
+ "sliding_attention",
104
+ "sliding_attention",
105
+ "full_attention",
106
+ "sliding_attention",
107
+ "sliding_attention",
108
+ "sliding_attention"
109
+ ],
110
+ "moe_apply_router_weight_on_input": false,
111
+ "mlp_layer_types": [
112
+ "dense",
113
+ "sparse",
114
+ "sparse",
115
+ "sparse",
116
+ "sparse",
117
+ "sparse",
118
+ "sparse",
119
+ "sparse",
120
+ "sparse",
121
+ "sparse",
122
+ "sparse",
123
+ "sparse",
124
+ "sparse",
125
+ "sparse",
126
+ "sparse",
127
+ "sparse",
128
+ "sparse",
129
+ "sparse",
130
+ "sparse",
131
+ "sparse",
132
+ "sparse",
133
+ "sparse",
134
+ "sparse",
135
+ "sparse",
136
+ "sparse",
137
+ "sparse",
138
+ "sparse",
139
+ "sparse",
140
+ "sparse",
141
+ "sparse",
142
+ "sparse",
143
+ "sparse",
144
+ "sparse",
145
+ "sparse",
146
+ "sparse",
147
+ "sparse",
148
+ "sparse",
149
+ "sparse",
150
+ "sparse",
151
+ "sparse",
152
+ "sparse",
153
+ "sparse",
154
+ "sparse",
155
+ "sparse",
156
+ "sparse",
157
+ "sparse",
158
+ "sparse",
159
+ "sparse"
160
+ ],
161
+ "gating_types": [
162
+ "per_head",
163
+ "per_head",
164
+ "per_head",
165
+ "per_head",
166
+ "per_head",
167
+ "per_head",
168
+ "per_head",
169
+ "per_head",
170
+ "per_head",
171
+ "per_head",
172
+ "per_head",
173
+ "per_head",
174
+ "per_head",
175
+ "per_head",
176
+ "per_head",
177
+ "per_head",
178
+ "per_head",
179
+ "per_head",
180
+ "per_head",
181
+ "per_head",
182
+ "per_head",
183
+ "per_head",
184
+ "per_head",
185
+ "per_head",
186
+ "per_head",
187
+ "per_head",
188
+ "per_head",
189
+ "per_head",
190
+ "per_head",
191
+ "per_head",
192
+ "per_head",
193
+ "per_head",
194
+ "per_head",
195
+ "per_head",
196
+ "per_head",
197
+ "per_head",
198
+ "per_head",
199
+ "per_head",
200
+ "per_head",
201
+ "per_head",
202
+ "per_head",
203
+ "per_head",
204
+ "per_head",
205
+ "per_head",
206
+ "per_head",
207
+ "per_head",
208
+ "per_head",
209
+ "per_head"
210
+ ],
211
+ "moe_routed_scaling_factor": 2.5,
212
+ "num_attention_heads_per_layer": [
213
+ 48,
214
+ 72,
215
+ 72,
216
+ 72,
217
+ 48,
218
+ 72,
219
+ 72,
220
+ 72,
221
+ 48,
222
+ 72,
223
+ 72,
224
+ 72,
225
+ 48,
226
+ 72,
227
+ 72,
228
+ 72,
229
+ 48,
230
+ 72,
231
+ 72,
232
+ 72,
233
+ 48,
234
+ 72,
235
+ 72,
236
+ 72,
237
+ 48,
238
+ 72,
239
+ 72,
240
+ 72,
241
+ 48,
242
+ 72,
243
+ 72,
244
+ 72,
245
+ 48,
246
+ 72,
247
+ 72,
248
+ 72,
249
+ 48,
250
+ 72,
251
+ 72,
252
+ 72,
253
+ 48,
254
+ 72,
255
+ 72,
256
+ 72,
257
+ 48,
258
+ 72,
259
+ 72,
260
+ 72
261
+ ],
262
+ "quantization_config": {
263
+ "config_groups": {
264
+ "group_0": {
265
+ "format": "nvfp4-pack-quantized",
266
+ "input_activations": {
267
+ "actorder": null,
268
+ "block_structure": null,
269
+ "dynamic": "local",
270
+ "group_size": 16,
271
+ "num_bits": 4,
272
+ "observer": "static_minmax",
273
+ "observer_kwargs": {},
274
+ "scale_dtype": "torch.float8_e4m3fn",
275
+ "strategy": "tensor_group",
276
+ "symmetric": true,
277
+ "type": "float",
278
+ "zp_dtype": null
279
+ },
280
+ "output_activations": null,
281
+ "targets": [
282
+ "re:.*experts\\.[0-9]+\\.(gate_proj|up_proj|down_proj)$"
283
+ ],
284
+ "weights": {
285
+ "actorder": null,
286
+ "block_structure": null,
287
+ "dynamic": false,
288
+ "group_size": 16,
289
+ "num_bits": 4,
290
+ "observer": "memoryless_minmax",
291
+ "observer_kwargs": {},
292
+ "scale_dtype": "torch.float8_e4m3fn",
293
+ "strategy": "tensor_group",
294
+ "symmetric": true,
295
+ "type": "float",
296
+ "zp_dtype": null
297
+ }
298
+ }
299
+ },
300
+ "format": "nvfp4-pack-quantized",
301
+ "global_compression_ratio": null,
302
+ "ignore": [
303
+ "lm_head",
304
+ "re:.*\\.self_attn\\.q_proj$",
305
+ "re:.*\\.self_attn\\.k_proj$",
306
+ "re:.*\\.self_attn\\.v_proj$",
307
+ "re:.*\\.self_attn\\.o_proj$",
308
+ "re:.*\\.self_attn\\.g_proj$",
309
+ "re:.*\\.mlp\\.gate$",
310
+ "model.layers.0.mlp.gate_proj",
311
+ "model.layers.0.mlp.up_proj",
312
+ "model.layers.0.mlp.down_proj",
313
+ "re:.*\\.mlp\\.shared_expert\\.gate_proj$",
314
+ "re:.*\\.mlp\\.shared_expert\\.up_proj$",
315
+ "re:.*\\.mlp\\.shared_expert\\.down_proj$"
316
+ ],
317
+ "kv_cache_scheme": {
318
+ "actorder": null,
319
+ "block_structure": null,
320
+ "dynamic": false,
321
+ "group_size": null,
322
+ "num_bits": 8,
323
+ "observer": "minmax",
324
+ "observer_kwargs": {},
325
+ "scale_dtype": null,
326
+ "strategy": "tensor",
327
+ "symmetric": true,
328
+ "type": "float",
329
+ "zp_dtype": null
330
+ },
331
+ "quant_method": "compressed-tensors",
332
+ "quantization_status": "compressed",
333
+ "sparsity_config": {},
334
+ "transform_config": {},
335
+ "version": "0.14.1.dev11+gf2ee47b"
336
+ }
337
+ }
configuration_laguna.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ruff: noqa
2
+ # Copyright 2025 Poolside and the HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from transformers.configuration_utils import PretrainedConfig as PreTrainedConfig
16
+ try:
17
+ from transformers.modeling_rope_utils import RopeParameters
18
+ except ImportError:
19
+ RopeParameters = dict
20
+ try:
21
+ from transformers.utils.import_utils import is_causal_conv1d_available, is_flash_linear_attention_available
22
+ except ImportError:
23
+ def is_causal_conv1d_available(): return False
24
+ def is_flash_linear_attention_available(): return False
25
+
26
+
27
+ class LagunaConfig(PreTrainedConfig):
28
+ r"""
29
+ Configuration class for Laguna model.
30
+
31
+ Laguna is Poolside's MoE architecture with:
32
+ - Attention output gating (softplus gate)
33
+ - Sigmoid routing instead of softmax
34
+ - No QKV bias
35
+ - Explicit head_dim parameter
36
+
37
+ Args:
38
+ head_dim (`int`, *optional*, defaults to 128):
39
+ Dimension of attention heads. Laguna uses explicit head_dim rather than
40
+ computing it from hidden_size // num_attention_heads.
41
+ qkv_bias (`bool`, *optional*, defaults to `False`):
42
+ Whether to add bias to QKV projections. Laguna uses no QKV bias.
43
+ attention_bias (`bool`, *optional*, defaults to `False`):
44
+ Whether to add bias to attention output projection. Laguna uses no attention bias.
45
+ gating (`bool` or `str`, *optional*, defaults to `True`):
46
+ Attention output gating mode. When ``True`` or ``"per-element"`` a g_proj
47
+ linear layer with output size ``num_attention_heads * head_dim`` is added
48
+ and ``attn_output = attn_output * softplus(g_proj(x))``. When ``"per-head"``
49
+ g_proj has output size ``num_attention_heads`` and the gate broadcasts across
50
+ ``head_dim``. When ``False`` no gating is applied.
51
+ partial_rotary_factor (`float`, *optional*):
52
+ Fraction of head_dim to apply rotary embeddings to. When set, this value is
53
+ injected into ``rope_parameters`` (and ``swa_rope_parameters``) if not already
54
+ specified there. When ``None`` the default behaviour of the rope implementation
55
+ is used (typically full rotary).
56
+ num_attention_heads_per_layer (`list[int]`, *optional*):
57
+ Optional per-layer override for ``num_attention_heads``. When provided the list
58
+ length must equal ``num_hidden_layers`` and each entry is the head count used by
59
+ that layer. When ``None`` every layer uses ``num_attention_heads``.
60
+ vocab_size (`int`, *optional*, defaults to 100352):
61
+ Vocabulary size of the Laguna model.
62
+ hidden_size (`int`, *optional*, defaults to 2048):
63
+ Dimension of the hidden representations.
64
+ intermediate_size (`int`, *optional*, defaults to 8192):
65
+ Dimension of the MLP representations for dense layers.
66
+ num_hidden_layers (`int`, *optional*, defaults to 48):
67
+ Number of hidden layers in the Transformer.
68
+ num_attention_heads (`int`, *optional*, defaults to 32):
69
+ Number of attention heads.
70
+ num_key_value_heads (`int`, *optional*, defaults to 8):
71
+ Number of key-value heads for GQA.
72
+ max_position_embeddings (`int`, *optional*, defaults to 4096):
73
+ Maximum sequence length.
74
+ rms_norm_eps (`float`, *optional*, defaults to 1e-6):
75
+ Epsilon for RMSNorm layers.
76
+ sliding_window (`int`, *optional*):
77
+ Sliding window attention size. Used by layers whose type in ``layer_types``
78
+ is ``"sliding_attention"``. When ``None``, all layers use full attention.
79
+ layer_types (`list[str]`, *optional*):
80
+ Per-layer attention type. Each element should be ``"sliding_attention"`` or
81
+ ``"full_attention"``. Length must equal ``num_hidden_layers``. When ``None``,
82
+ all layers default to global attention.
83
+ swa_attention_sink_enabled (`bool`, *optional*, defaults to `False`):
84
+ Whether to enable learnable attention sinks on sliding-window attention layers.
85
+ When enabled, a per-head bias parameter is added that allows the model to attend
86
+ to position 0 even when it falls outside the sliding window.
87
+ swa_rope_parameters (`RopeParameters`, *optional*):
88
+ Separate RoPE configuration for sliding-window attention layers. When ``None``,
89
+ SWA layers use the same RoPE as global attention layers.
90
+ num_experts (`int`, *optional*, defaults to 256):
91
+ Number of routed experts.
92
+ num_experts_per_tok (`int`, *optional*, defaults to 16):
93
+ Number of experts selected per token (top-k).
94
+ moe_intermediate_size (`int`, *optional*, defaults to 1024):
95
+ Intermediate size of routed experts.
96
+ shared_expert_intermediate_size (`int`, *optional*, defaults to 1024):
97
+ Intermediate size of the shared expert.
98
+ norm_topk_prob (`bool`, *optional*, defaults to `True`):
99
+ Whether to normalize top-k routing probabilities.
100
+ decoder_sparse_step (`int`, *optional*, defaults to 1):
101
+ Frequency of MoE layers (1 = every layer is MoE after mlp_only_layers).
102
+ mlp_only_layers (`list[int]`, *optional*, defaults to `[0]`):
103
+ Layer indices that use dense MLP instead of MoE.
104
+ router_aux_loss_coef (`float`, *optional*, defaults to 0.001):
105
+ Auxiliary loss coefficient for load balancing.
106
+ moe_routed_scaling_factor (`float`, *optional*, defaults to 1.0):
107
+ Scalar multiplier applied to the routed-expert output before combining with the
108
+ shared-expert output.
109
+ moe_apply_router_weight_on_input (`bool`, *optional*, defaults to `False`):
110
+ When ``True`` the top-k routing weights are multiplied into each expert's input
111
+ rather than its output. Matches the numerical form used by the trained checkpoint.
112
+ moe_router_logit_softcapping (`float`, *optional*, defaults to 0.0):
113
+ Optional soft-capping value ``c`` applied to router logits as
114
+ ``x = tanh(x / c) * c`` before sigmoid + top-k. Disabled when ``0``.
115
+ rope_parameters (`RopeParameters`, *optional*):
116
+ RoPE configuration. Defaults to rope_theta=500000.0.
117
+ """
118
+
119
+ model_type = "laguna"
120
+ keys_to_ignore_at_inference = ["past_key_values"]
121
+ # PreTrainedConfig in transformers v5 no longer auto-declares these; subclasses
122
+ # opt in by providing class-level annotations with defaults.
123
+ pad_token_id: int | None = None
124
+ bos_token_id: int | None = None
125
+ eos_token_id: int | list[int] | None = None
126
+ base_model_tp_plan = {
127
+ "layers.*.self_attn.q_proj": "colwise",
128
+ "layers.*.self_attn.k_proj": "colwise",
129
+ "layers.*.self_attn.v_proj": "colwise",
130
+ "layers.*.self_attn.g_proj": "colwise", # Laguna-specific gating projection
131
+ "layers.*.self_attn.o_proj": "rowwise",
132
+ "layers.*.mlp.gate_proj": "colwise",
133
+ "layers.*.mlp.up_proj": "colwise",
134
+ "layers.*.mlp.down_proj": "rowwise",
135
+ }
136
+ base_model_pp_plan = {
137
+ "embed_tokens": (["input_ids"], ["inputs_embeds"]),
138
+ "layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
139
+ "norm": (["hidden_states"], ["hidden_states"]),
140
+ }
141
+
142
+ def __init__(
143
+ self,
144
+ vocab_size: int = 100352,
145
+ hidden_size: int = 2048,
146
+ intermediate_size: int = 8192,
147
+ num_hidden_layers: int = 48,
148
+ num_attention_heads: int = 32,
149
+ num_key_value_heads: int = 8,
150
+ head_dim: int = 128,
151
+ qkv_bias: bool = False,
152
+ attention_bias: bool = False,
153
+ gating: bool | str = True,
154
+ hidden_act: str = "silu",
155
+ max_position_embeddings: int = 4096,
156
+ initializer_range: float = 0.02,
157
+ rms_norm_eps: float = 1e-6,
158
+ use_cache: bool = True,
159
+ tie_word_embeddings: bool = False,
160
+ rope_parameters: RopeParameters | dict[str, RopeParameters] | None = None,
161
+ partial_rotary_factor: float | None = None,
162
+ attention_dropout: float = 0.0,
163
+ sliding_window: int | None = None,
164
+ layer_types: list[str] | None = None,
165
+ num_attention_heads_per_layer: list[int] | None = None,
166
+ swa_attention_sink_enabled: bool = False,
167
+ swa_rope_parameters: RopeParameters | None = None,
168
+ num_experts: int = 256,
169
+ num_experts_per_tok: int = 16,
170
+ moe_intermediate_size: int = 1024,
171
+ shared_expert_intermediate_size: int = 1024,
172
+ norm_topk_prob: bool = True,
173
+ decoder_sparse_step: int = 1,
174
+ mlp_only_layers: list[int] | None = None,
175
+ router_aux_loss_coef: float = 0.001,
176
+ moe_routed_scaling_factor: float = 1.0,
177
+ moe_apply_router_weight_on_input: bool = False,
178
+ moe_router_logit_softcapping: float = 0.0,
179
+ output_router_logits: bool = False,
180
+ **kwargs,
181
+ ):
182
+ # Default mlp_only_layers: first layer is dense (moe_first_k_dense_replace=1)
183
+ if mlp_only_layers is None:
184
+ mlp_only_layers = [0]
185
+
186
+ # Default layer_types: all layers use full attention (Laguna-M). Laguna-XS
187
+ # ships an explicit list with a mix of "full_attention" and "sliding_attention".
188
+ # Downstream mask builders (``create_masks_for_generate``) iterate
189
+ # ``layer_types``, so it must be a list — not left as ``None``.
190
+ if layer_types is None:
191
+ layer_types = ["full_attention"] * num_hidden_layers
192
+
193
+ # Default rope_parameters with Laguna's theta
194
+ if rope_parameters is None:
195
+ rope_parameters = {"rope_type": "default", "rope_theta": 500000.0}
196
+
197
+ # config.json stores SWA rope nested in rope_parameters["sliding_attention"]
198
+ # and carries no top-level swa_rope_parameters. Derive it here, else the
199
+ # sliding-window layers silently reuse the full-attention rope.
200
+ if swa_rope_parameters is None and isinstance(rope_parameters, dict):
201
+ swa_rope_parameters = rope_parameters.get("sliding_attention")
202
+
203
+ # If ``partial_rotary_factor`` is set at the top level, inject it into any
204
+ # rope dict that does not already carry one so the rotary embedding picks
205
+ # it up consistently for both full-attention and SWA layers.
206
+ if partial_rotary_factor is not None:
207
+ if isinstance(rope_parameters, dict) and "partial_rotary_factor" not in rope_parameters:
208
+ rope_parameters = {**rope_parameters, "partial_rotary_factor": partial_rotary_factor}
209
+ if isinstance(swa_rope_parameters, dict) and "partial_rotary_factor" not in swa_rope_parameters:
210
+ swa_rope_parameters = {
211
+ **swa_rope_parameters,
212
+ "partial_rotary_factor": partial_rotary_factor,
213
+ }
214
+
215
+ self.vocab_size = vocab_size
216
+ self.hidden_size = hidden_size
217
+ self.intermediate_size = intermediate_size
218
+ self.num_hidden_layers = num_hidden_layers
219
+ self.num_attention_heads = num_attention_heads
220
+ self.num_key_value_heads = num_key_value_heads
221
+ self.head_dim = head_dim
222
+ self.qkv_bias = qkv_bias
223
+ self.attention_bias = attention_bias
224
+ self.gating = gating
225
+ self.hidden_act = hidden_act
226
+ self.max_position_embeddings = max_position_embeddings
227
+ self.initializer_range = initializer_range
228
+ self.rms_norm_eps = rms_norm_eps
229
+ self.use_cache = use_cache
230
+ self.rope_parameters = rope_parameters
231
+ self.partial_rotary_factor = partial_rotary_factor
232
+ self.attention_dropout = attention_dropout
233
+ # Sliding window attention arguments
234
+ self.sliding_window = sliding_window
235
+ self.layer_types = layer_types
236
+ self.num_attention_heads_per_layer = num_attention_heads_per_layer
237
+ self.swa_attention_sink_enabled = swa_attention_sink_enabled
238
+ self.swa_rope_parameters = swa_rope_parameters
239
+ # MoE arguments
240
+ self.num_experts = num_experts
241
+ self.num_experts_per_tok = num_experts_per_tok
242
+ self.moe_intermediate_size = moe_intermediate_size
243
+ self.shared_expert_intermediate_size = shared_expert_intermediate_size
244
+ self.norm_topk_prob = norm_topk_prob
245
+ self.decoder_sparse_step = decoder_sparse_step
246
+ self.mlp_only_layers = mlp_only_layers
247
+ self.router_aux_loss_coef = router_aux_loss_coef
248
+ self.moe_routed_scaling_factor = moe_routed_scaling_factor
249
+ self.moe_apply_router_weight_on_input = moe_apply_router_weight_on_input
250
+ self.moe_router_logit_softcapping = moe_router_logit_softcapping
251
+ self.output_router_logits = output_router_logits
252
+
253
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
254
+
255
+
256
+ __all__ = ["LagunaConfig"]
generation_config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 2,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 2,
6
+ 24
7
+ ],
8
+ "max_new_tokens": 32768,
9
+ "pad_token_id": 9,
10
+ "temperature": 1.0,
11
+ "top_p": 1.0,
12
+ "speculative_config": {
13
+ "method": "dflash",
14
+ "source": "huggingface",
15
+ "model": "poolside/Laguna-S-2.1-DFlash-NVFP4",
16
+ "num_speculative_tokens": 15
17
+ },
18
+ "tool_call_parser": "poolside_v1",
19
+ "reasoning_parser": "poolside_v1",
20
+ "default_chat_template_kwargs": {
21
+ "enable_thinking": true
22
+ },
23
+ "top_k": 20,
24
+ "min_p": 0.0
25
+ }
model-00001-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:80518caa724f3ec5bd0470767196354a7f64abdb35d48272d4e99ad69033f5bc
3
+ size 5086273312
model-00002-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:820c842ffb7ae8e9194fcf4065a42fb7c07a16c778199428466f3a273e2b5021
3
+ size 5932654328
model-00003-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:04f5fffa73885e25ee1b7cdda91a0694375b786f903970c0443c808d8fad9482
3
+ size 5120530760
model-00004-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9bca976e9c83994f05f0387091655f4a3e763aac671c6ff82eafee52d2da2f26
3
+ size 5120532288
model-00005-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:324829efb416227d885fcb1fc3a90b68f7ebe5cfbcaf80c4d9063b2c908489e0
3
+ size 5120541608
model-00006-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:560ebc627b686211da1bc3e286181ec0394f5f0ad62f6e18b12aecd646ed9448
3
+ size 5120541976
model-00007-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fb08bdbcd6b81a8dae481b78e3e9ed63636863e9a492fc29554aeb7b8ea0206d
3
+ size 5120542336
model-00008-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f99f3ef4edb547c4198cd426527f4888d8838ee907b410aba58345689e3a801
3
+ size 5120541824
model-00009-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4704c1278648134026d2af30e26743967e69f3164aa1152fec9d4d2d299529a3
3
+ size 5120541600
model-00010-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2417eeb1905f7ba68e5cd33a0aa0e9ef7bfdbddf0ffbf77738fc4358d8f6f56c
3
+ size 5120541776
model-00011-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b514d82a3c12b965222d63c92bd796b62178c65f9c7677fbf91856f63f217779
3
+ size 5120542320
model-00012-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aded66fad205ae52b6869ee04cdc1ece6b79cbd8a6919061316a8d9438705fa5
3
+ size 5120542032
model-00013-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ce940e388edb9b10e4bc21ee7e6fe3b08f32d6e63967e7483279f2091cd4f0c
3
+ size 5120541608
model-00014-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:69c7b4087149252cf69f26f790ad937272357497577e5c8eee4e0499e10b4c6f
3
+ size 4571848840
model.safetensors.index.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c872bd9b5ac116cebfd78a5f27565787dbceae76985cef041179110d0b03cd2b
3
+ size 14534109
modeling_laguna.py ADDED
@@ -0,0 +1,886 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ruff: noqa
2
+ # Copyright 2025 Poolside and the HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from collections.abc import Callable
17
+
18
+ import torch
19
+ import torch.nn.functional as F
20
+ from torch import nn
21
+
22
+ from transformers.activations import ACT2FN
23
+ from transformers.cache_utils import Cache
24
+ from transformers.integrations import use_experts_implementation, use_kernelized_func
25
+ from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
26
+ from transformers.modeling_layers import GradientCheckpointingLayer
27
+ from transformers.modeling_outputs import MoeModelOutputWithPast
28
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
29
+ from transformers.processing_utils import Unpack
30
+ from transformers.utils import auto_docstring, can_return_tuple, is_grouped_mm_available
31
+ from transformers.utils.generic import TransformersKwargs, merge_with_config_defaults
32
+ from transformers.utils.output_capturing import OutputRecorder, capture_outputs
33
+ from transformers.cache_utils import DynamicCache
34
+ from transformers.generation import GenerationMixin
35
+ from transformers.integrations import use_kernel_forward_from_hub
36
+ from transformers.masking_utils import create_causal_mask
37
+ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
38
+ from transformers.modeling_utils import PreTrainedModel
39
+ from transformers.utils.generic import maybe_autocast
40
+ from .configuration_laguna import LagunaConfig
41
+
42
+ from transformers import initialization as init
43
+ from transformers.masking_utils import create_sliding_window_causal_mask
44
+ from transformers.modeling_outputs import MoeCausalLMOutputWithPast
45
+ from transformers.utils.import_utils import is_causal_conv1d_available, is_flash_linear_attention_available
46
+
47
+
48
+ @use_kernel_forward_from_hub("RMSNorm")
49
+ class LagunaRMSNorm(nn.Module):
50
+ def __init__(self, hidden_size, eps: float = 1e-6) -> None:
51
+ """
52
+ LagunaRMSNorm is equivalent to T5LayerNorm
53
+ """
54
+ super().__init__()
55
+ self.weight = nn.Parameter(torch.ones(hidden_size))
56
+ self.variance_epsilon = eps
57
+
58
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
59
+ input_dtype = hidden_states.dtype
60
+ hidden_states = hidden_states.to(torch.float32)
61
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
62
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
63
+ return self.weight * hidden_states.to(input_dtype)
64
+
65
+ def extra_repr(self):
66
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
67
+
68
+
69
+ class LagunaRotaryEmbedding(nn.Module):
70
+ inv_freq: torch.Tensor # fix linting for `register_buffer`
71
+
72
+ def __init__(self, config: LagunaConfig, device=None):
73
+ super().__init__()
74
+ self.max_seq_len_cached = config.max_position_embeddings
75
+ self.original_max_seq_len = config.max_position_embeddings
76
+
77
+ self.config = config
78
+
79
+ self.rope_type = self.config.rope_parameters["rope_type"]
80
+ rope_init_fn: Callable = self.compute_default_rope_parameters
81
+ if self.rope_type != "default":
82
+ rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
83
+ inv_freq, self.attention_scaling = rope_init_fn(self.config, device)
84
+
85
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
86
+ self.register_buffer("original_inv_freq", inv_freq.clone(), persistent=False)
87
+
88
+ @staticmethod
89
+ def compute_default_rope_parameters(config, device=None, seq_len=None) -> tuple["torch.Tensor", float]:
90
+ """
91
+ Computes the inverse frequencies according to the original RoPE implementation
92
+ Args:
93
+ config ([`~transformers.PreTrainedConfig`]):
94
+ The model configuration.
95
+ device (`torch.device`):
96
+ The device to use for initialization of the inverse frequencies.
97
+ seq_len (`int`, *optional*):
98
+ The current sequence length. Unused for this type of RoPE.
99
+ Returns:
100
+ Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
101
+ post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).
102
+ """
103
+ base = config.rope_parameters["rope_theta"]
104
+ head_dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
105
+ partial = config.rope_parameters.get("partial_rotary_factor", 1.0)
106
+ dim = int(head_dim * partial)
107
+ inv_freq = 1.0 / (
108
+ base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim)
109
+ )
110
+ return inv_freq, 1.0
111
+
112
+ @torch.no_grad()
113
+ @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
114
+ def forward(self, x, position_ids):
115
+ inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
116
+ position_ids_expanded = position_ids[:, None, :].float()
117
+
118
+ device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
119
+ with maybe_autocast(device_type=device_type, enabled=False): # Force float32
120
+ freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
121
+ emb = torch.cat((freqs, freqs), dim=-1)
122
+ cos = emb.cos() * self.attention_scaling
123
+ sin = emb.sin() * self.attention_scaling
124
+
125
+ return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
126
+
127
+
128
+ class LagunaMLP(nn.Module):
129
+ def __init__(self, config, intermediate_size=None):
130
+ super().__init__()
131
+ self.config = config
132
+ self.hidden_size = config.hidden_size
133
+ self.intermediate_size = config.intermediate_size if intermediate_size is None else intermediate_size
134
+ self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
135
+ self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
136
+ self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
137
+ self.act_fn = ACT2FN[config.hidden_act]
138
+
139
+ def forward(self, x):
140
+ down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
141
+ return down_proj
142
+
143
+
144
+ class LagunaTopKRouter(nn.Module):
145
+ """Laguna MoE router using sigmoid scoring (not softmax).
146
+
147
+ Supports optional router-logit soft-capping and auxiliary-loss-free load
148
+ balancing (arXiv:2408.15664): the per-expert bias ``e_score_correction_bias``
149
+ is added to selection scores but the returned routing weights remain unbiased.
150
+ The bias lives on the router so accelerate's per-module hooks can co-locate it
151
+ with the gate — moving it to the experts module would cross a hook boundary
152
+ and leave the bias on meta under ``device_map="auto"`` / CPU-offload.
153
+ """
154
+
155
+ def __init__(self, config):
156
+ super().__init__()
157
+ self.top_k = config.num_experts_per_tok
158
+ self.num_experts = config.num_experts
159
+ self.norm_topk_prob = config.norm_topk_prob
160
+ self.hidden_dim = config.hidden_size
161
+ self.weight = nn.Parameter(torch.zeros(self.num_experts, self.hidden_dim))
162
+ # Zero-initialised so inference on checkpoints that don't ship the bias
163
+ # is a no-op. ``_checkpoint_conversion_mapping`` below remaps the
164
+ # ``mlp.experts.e_score_correction_bias`` key from vLLM-trained
165
+ # checkpoints onto this attribute.
166
+ self.e_score_correction_bias = nn.Parameter(torch.zeros(config.num_experts), requires_grad=False)
167
+ self.router_logit_softcapping = float(getattr(config, "moe_router_logit_softcapping", 0.0) or 0.0)
168
+
169
+ def forward(
170
+ self,
171
+ hidden_states: torch.Tensor,
172
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
173
+ hidden_states = hidden_states.reshape(-1, self.hidden_dim)
174
+ router_logits = F.linear(hidden_states, self.weight).float()
175
+ if self.router_logit_softcapping > 0.0:
176
+ router_logits = torch.tanh(router_logits / self.router_logit_softcapping) * self.router_logit_softcapping
177
+ routing_scores = torch.sigmoid(router_logits)
178
+ scores_for_selection = routing_scores + self.e_score_correction_bias.to(routing_scores.dtype)
179
+ _, selected_experts = torch.topk(scores_for_selection, self.top_k, dim=-1)
180
+ routing_weights = routing_scores.gather(-1, selected_experts)
181
+ if self.norm_topk_prob:
182
+ routing_weights = routing_weights / routing_weights.sum(dim=-1, keepdim=True)
183
+ routing_weights = routing_weights.to(hidden_states.dtype)
184
+ return router_logits, routing_weights, selected_experts
185
+
186
+
187
+ @use_experts_implementation
188
+ class LagunaExperts(nn.Module):
189
+ """Fused expert weights as 3D tensors for batched execution."""
190
+
191
+ def __init__(self, config):
192
+ super().__init__()
193
+ self.num_experts = config.num_experts
194
+ self.hidden_dim = config.hidden_size
195
+ self.intermediate_dim = config.moe_intermediate_size
196
+ self.gate_up_proj = nn.Parameter(torch.empty(self.num_experts, 2 * self.intermediate_dim, self.hidden_dim))
197
+ self.down_proj = nn.Parameter(torch.empty(self.num_experts, self.hidden_dim, self.intermediate_dim))
198
+ self.act_fn = ACT2FN[config.hidden_act]
199
+
200
+ def forward(
201
+ self,
202
+ hidden_states: torch.Tensor,
203
+ top_k_index: torch.Tensor,
204
+ top_k_weights: torch.Tensor,
205
+ ) -> torch.Tensor:
206
+ final_hidden_states = torch.zeros_like(hidden_states)
207
+ with torch.no_grad():
208
+ expert_mask = F.one_hot(top_k_index, num_classes=self.num_experts)
209
+ expert_mask = expert_mask.permute(2, 1, 0)
210
+ expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero()
211
+
212
+ for expert_idx in expert_hit:
213
+ expert_idx = expert_idx[0]
214
+ if expert_idx == self.num_experts:
215
+ continue
216
+ top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
217
+ current_state = hidden_states[token_idx]
218
+ gate, up = F.linear(current_state, self.gate_up_proj[expert_idx]).chunk(2, dim=-1)
219
+ current_hidden_states = self.act_fn(gate) * up
220
+ current_hidden_states = F.linear(current_hidden_states, self.down_proj[expert_idx])
221
+ current_hidden_states = current_hidden_states * top_k_weights[token_idx, top_k_pos, None]
222
+ final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype))
223
+
224
+ return final_hidden_states
225
+
226
+
227
+ class LagunaSparseMoeBlock(nn.Module):
228
+ """Laguna MoE block using sigmoid router, fused expert tensors, and a shared expert."""
229
+
230
+ def __init__(self, config):
231
+ super().__init__()
232
+ self.num_experts = config.num_experts
233
+ self.routed_scaling_factor = float(getattr(config, "moe_routed_scaling_factor", 1.0))
234
+ # ``moe_apply_router_weight_on_input=True`` would require scaling each expert's
235
+ # input (rather than its output) by the routing weight. Supporting it cleanly
236
+ # alongside the fused experts kernels (``grouped_mm`` / ``batched_mm``) is future
237
+ # work; for now we fail loudly so a checkpoint that needs it can't silently
238
+ # diverge from its numerical form.
239
+ if getattr(config, "moe_apply_router_weight_on_input", False):
240
+ raise NotImplementedError(
241
+ "moe_apply_router_weight_on_input=True is not yet supported in the "
242
+ "transformers implementation of Laguna."
243
+ )
244
+ self.gate = LagunaTopKRouter(config)
245
+ self.experts = LagunaExperts(config)
246
+ self.shared_expert = LagunaMLP(config, intermediate_size=config.shared_expert_intermediate_size)
247
+
248
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
249
+ batch_size, sequence_length, hidden_dim = hidden_states.shape
250
+ hidden_states = hidden_states.view(-1, hidden_dim)
251
+
252
+ shared_expert_output = self.shared_expert(hidden_states)
253
+ _, routing_weights, selected_experts = self.gate(hidden_states)
254
+ expert_output = self.experts(hidden_states, selected_experts, routing_weights)
255
+ if self.routed_scaling_factor != 1.0:
256
+ expert_output = expert_output * self.routed_scaling_factor
257
+
258
+ expert_output = expert_output + shared_expert_output
259
+ expert_output = expert_output.reshape(batch_size, sequence_length, hidden_dim)
260
+ return expert_output
261
+
262
+
263
+ def rotate_half(x):
264
+ """Rotates half the hidden dims of the input."""
265
+ x1 = x[..., : x.shape[-1] // 2]
266
+ x2 = x[..., x.shape[-1] // 2 :]
267
+ return torch.cat((-x2, x1), dim=-1)
268
+
269
+
270
+ # Adapted from transformers.models.glm.modular_glm.apply_rotary_pos_emb
271
+ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
272
+ """Applies Rotary Position Embedding to the query and key tensors.
273
+
274
+ Removes the interleaving of cos and sin from GLM
275
+
276
+ Args:
277
+ q (`torch.Tensor`): The query tensor.
278
+ k (`torch.Tensor`): The key tensor.
279
+ cos (`torch.Tensor`): The cosine part of the rotary embedding.
280
+ sin (`torch.Tensor`): The sine part of the rotary embedding.
281
+ unsqueeze_dim (`int`, *optional*, defaults to 1):
282
+ The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
283
+ sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
284
+ that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
285
+ k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
286
+ cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
287
+ the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
288
+ Returns:
289
+ `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
290
+ """
291
+ cos = cos.unsqueeze(unsqueeze_dim)
292
+ sin = sin.unsqueeze(unsqueeze_dim)
293
+
294
+ # Keep half or full tensor for later concatenation
295
+ rotary_dim = cos.shape[-1]
296
+ q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
297
+ k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
298
+
299
+ # Apply rotary embeddings on the first half or full tensor
300
+ q_embed = (q_rot * cos) + (rotate_half(q_rot) * sin)
301
+ k_embed = (k_rot * cos) + (rotate_half(k_rot) * sin)
302
+
303
+ # Concatenate back to full shape
304
+ q_embed = torch.cat([q_embed, q_pass], dim=-1)
305
+ k_embed = torch.cat([k_embed, k_pass], dim=-1)
306
+ return q_embed, k_embed
307
+
308
+
309
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
310
+ """
311
+ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
312
+ num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
313
+ """
314
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
315
+ if n_rep == 1:
316
+ return hidden_states
317
+ hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
318
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
319
+
320
+
321
+ def eager_attention_forward(
322
+ module: nn.Module,
323
+ query: torch.Tensor,
324
+ key: torch.Tensor,
325
+ value: torch.Tensor,
326
+ attention_mask: torch.Tensor | None,
327
+ scaling: float,
328
+ dropout: float = 0.0,
329
+ **kwargs: Unpack[TransformersKwargs],
330
+ ):
331
+ key_states = repeat_kv(key, module.num_key_value_groups)
332
+ value_states = repeat_kv(value, module.num_key_value_groups)
333
+
334
+ attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
335
+ if attention_mask is not None:
336
+ attn_weights = attn_weights + attention_mask
337
+
338
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
339
+ attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
340
+ attn_output = torch.matmul(attn_weights, value_states)
341
+ attn_output = attn_output.transpose(1, 2).contiguous()
342
+
343
+ return attn_output, attn_weights
344
+
345
+
346
+ # Laguna attention is identical to Qwen2MoE attention except:
347
+ # - No QKV bias
348
+ # - Explicit head_dim from config
349
+ # - Output gating: attn_output = attn_output * softplus(g_proj(hidden_states)) (optional)
350
+ # - Per-layer sliding window attention with optional attention sinks
351
+ @use_kernelized_func(apply_rotary_pos_emb)
352
+ class LagunaAttention(nn.Module):
353
+ def __init__(self, config: LagunaConfig, layer_idx: int, num_heads: int | None = None):
354
+ super().__init__()
355
+ self.config = config
356
+ self.layer_idx = layer_idx
357
+ self.head_dim = config.head_dim
358
+ # Allow the caller (decoder layer) to supply a per-layer head count; fall back
359
+ # to config.num_attention_heads when not provided.
360
+ self.num_heads = num_heads if num_heads is not None else config.num_attention_heads
361
+ self.num_key_value_groups = self.num_heads // config.num_key_value_heads
362
+ self.scaling = self.head_dim**-0.5
363
+ self.attention_dropout = config.attention_dropout
364
+ self.is_causal = True
365
+
366
+ # Per-layer sliding window (follows Gemma2/Cohere2 convention)
367
+ layer_types = getattr(config, "layer_types", None)
368
+ if layer_types is not None:
369
+ self.is_sliding = layer_types[layer_idx] == "sliding_attention"
370
+ self.sliding_window = config.sliding_window if self.is_sliding else None
371
+ else:
372
+ self.is_sliding = False
373
+ self.sliding_window = None
374
+
375
+ # Laguna: no QKV bias, explicit head_dim
376
+ self.q_proj = nn.Linear(config.hidden_size, self.num_heads * config.head_dim, bias=False)
377
+ self.k_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * config.head_dim, bias=False)
378
+ self.v_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * config.head_dim, bias=False)
379
+ self.o_proj = nn.Linear(self.num_heads * config.head_dim, config.hidden_size, bias=False)
380
+
381
+ # Laguna-specific: optional gating projection.
382
+ # ``gating`` may be:
383
+ # - True / "per-element": one gate per (head, head_dim) channel
384
+ # - "per-head": one gate per head, broadcast across head_dim
385
+ # - False: no gating
386
+ gating = getattr(config, "gating", True)
387
+ self.gating = bool(gating)
388
+ self.gate_per_head = gating == "per-head"
389
+ if self.gating:
390
+ g_out = self.num_heads if self.gate_per_head else self.num_heads * config.head_dim
391
+ self.g_proj = nn.Linear(config.hidden_size, g_out, bias=False)
392
+
393
+ # Attention sinks (learnable per-head bias for SWA layers)
394
+ if self.is_sliding and getattr(config, "swa_attention_sink_enabled", False):
395
+ self.sink = nn.Parameter(torch.zeros(self.num_heads))
396
+
397
+ # QK normalization (RMSNorm applied per-head after reshape, before RoPE)
398
+ self.q_norm = LagunaRMSNorm(config.head_dim, eps=config.rms_norm_eps)
399
+ self.k_norm = LagunaRMSNorm(config.head_dim, eps=config.rms_norm_eps)
400
+
401
+ def forward(
402
+ self,
403
+ hidden_states: torch.Tensor,
404
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
405
+ attention_mask: torch.Tensor | None,
406
+ past_key_values: Cache | None = None,
407
+ **kwargs: Unpack[FlashAttentionKwargs],
408
+ ) -> tuple[torch.Tensor, torch.Tensor | None]:
409
+ input_shape = hidden_states.shape[:-1]
410
+ hidden_shape = (*input_shape, -1, self.head_dim)
411
+
412
+ query_states = self.q_proj(hidden_states)
413
+ key_states = self.k_proj(hidden_states)
414
+ value_states = self.v_proj(hidden_states)
415
+
416
+ query_states = query_states.view(hidden_shape).transpose(1, 2)
417
+ key_states = key_states.view(hidden_shape).transpose(1, 2)
418
+ value_states = value_states.view(hidden_shape).transpose(1, 2)
419
+
420
+ # QK normalization (applied per-head before RoPE)
421
+ query_states = self.q_norm(query_states)
422
+ key_states = self.k_norm(key_states)
423
+
424
+ cos, sin = position_embeddings
425
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
426
+
427
+ if past_key_values is not None:
428
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx)
429
+
430
+ # ``attention_mask`` here is already the correct mask for this layer type —
431
+ # ``LagunaModel.forward`` builds separate full-attention and sliding-attention
432
+ # masks (using ``create_causal_mask`` / ``create_sliding_window_causal_mask``)
433
+ # and the decoder layer passes the right one in.
434
+ attention_interface: Callable = eager_attention_forward
435
+ if self.config._attn_implementation != "eager":
436
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
437
+
438
+ attn_output, attn_weights = attention_interface(
439
+ self,
440
+ query_states,
441
+ key_states,
442
+ value_states,
443
+ attention_mask,
444
+ dropout=0.0 if not self.training else self.attention_dropout,
445
+ scaling=self.scaling,
446
+ **kwargs,
447
+ )
448
+
449
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
450
+
451
+ # Laguna-specific: apply gating BEFORE o_proj (optional)
452
+ if self.gating:
453
+ gate = F.softplus(self.g_proj(hidden_states).float()).to(attn_output.dtype)
454
+ if self.gate_per_head:
455
+ # gate: [..., num_heads]; broadcast across head_dim
456
+ attn_shape = attn_output.shape
457
+ attn_output = (
458
+ attn_output.view(*attn_shape[:-1], self.num_heads, self.head_dim) * gate.unsqueeze(-1)
459
+ ).view(attn_shape)
460
+ else:
461
+ attn_output = attn_output * gate
462
+
463
+ attn_output = self.o_proj(attn_output)
464
+
465
+ return attn_output, attn_weights
466
+
467
+
468
+ class LagunaDecoderLayer(GradientCheckpointingLayer):
469
+ """Laguna decoder layer with gated attention and sigmoid-routed MoE."""
470
+
471
+ def __init__(self, config: LagunaConfig, layer_idx: int):
472
+ super().__init__()
473
+ per_layer_heads = getattr(config, "num_attention_heads_per_layer", None)
474
+ layer_num_heads = per_layer_heads[layer_idx] if per_layer_heads is not None else config.num_attention_heads
475
+ # Layer type drives mask and position-embedding dispatch in ``LagunaModel.forward``.
476
+ layer_types = getattr(config, "layer_types", None)
477
+ self.attention_type = layer_types[layer_idx] if layer_types is not None else "full_attention"
478
+ self.self_attn = LagunaAttention(config, layer_idx, num_heads=layer_num_heads)
479
+ # Use MoE or dense MLP based on layer configuration
480
+ if (layer_idx not in config.mlp_only_layers) and (
481
+ config.num_experts > 0 and (layer_idx + 1) % config.decoder_sparse_step == 0
482
+ ):
483
+ self.mlp = LagunaSparseMoeBlock(config)
484
+ else:
485
+ self.mlp = LagunaMLP(config, intermediate_size=config.intermediate_size)
486
+ self.input_layernorm = LagunaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
487
+ self.post_attention_layernorm = LagunaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
488
+ self.hidden_size = config.hidden_size
489
+
490
+ def forward(
491
+ self,
492
+ hidden_states: torch.Tensor,
493
+ attention_mask: torch.Tensor | None = None,
494
+ position_ids: torch.LongTensor | None = None,
495
+ past_key_values: Cache | None = None,
496
+ use_cache: bool | None = False,
497
+ position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
498
+ **kwargs: Unpack[TransformersKwargs],
499
+ ) -> torch.Tensor:
500
+ residual = hidden_states
501
+ hidden_states = self.input_layernorm(hidden_states)
502
+ # Self Attention
503
+ hidden_states, _ = self.self_attn(
504
+ hidden_states=hidden_states,
505
+ attention_mask=attention_mask,
506
+ position_ids=position_ids,
507
+ past_key_values=past_key_values,
508
+ use_cache=use_cache,
509
+ position_embeddings=position_embeddings,
510
+ **kwargs,
511
+ )
512
+ hidden_states = residual + hidden_states
513
+
514
+ # Fully Connected
515
+ residual = hidden_states
516
+ hidden_states = self.post_attention_layernorm(hidden_states)
517
+ hidden_states = self.mlp(hidden_states)
518
+ hidden_states = residual + hidden_states
519
+ return hidden_states
520
+
521
+
522
+ @auto_docstring
523
+ class LagunaPreTrainedModel(PreTrainedModel):
524
+ config: LagunaConfig
525
+ base_model_prefix = "model"
526
+ supports_gradient_checkpointing = True
527
+ _no_split_modules = ["LagunaDecoderLayer"]
528
+ _skip_keys_device_placement = ["past_key_values"]
529
+ _supports_flash_attn = True
530
+ _supports_sdpa = True
531
+ _supports_flex_attn = True
532
+ _can_compile_fullgraph = (
533
+ is_grouped_mm_available()
534
+ ) # https://huggingface.co/docs/transformers/experts_interface#torchcompile
535
+ _supports_attention_backend = True
536
+ _can_record_outputs = {
537
+ "router_logits": OutputRecorder(LagunaTopKRouter, index=0),
538
+ "hidden_states": LagunaDecoderLayer,
539
+ "attentions": LagunaAttention,
540
+ }
541
+ # vLLM-trained Laguna checkpoints store the aux-loss-free routing bias on the
542
+ # experts module (``mlp.experts.e_score_correction_bias``). In this impl the
543
+ # bias lives on the router to stay co-located with its consumer across
544
+ # accelerate's per-module hooks, so remap the legacy key on load.
545
+ _checkpoint_conversion_mapping = {
546
+ r"^(.*)\.mlp\.experts\.e_score_correction_bias$": r"\1.mlp.gate.e_score_correction_bias",
547
+ }
548
+
549
+ @torch.no_grad()
550
+ def _init_weights(self, module):
551
+ super()._init_weights(module)
552
+ std = self.config.initializer_range
553
+ if isinstance(module, LagunaExperts):
554
+ init.normal_(module.gate_up_proj, mean=0.0, std=std)
555
+ init.normal_(module.down_proj, mean=0.0, std=std)
556
+ elif isinstance(module, LagunaTopKRouter):
557
+ init.normal_(module.weight, mean=0.0, std=std)
558
+ # Bare ``nn.Parameter``s that are not covered by the parent's generic
559
+ # Linear/Embedding/norm handling need their own rules so that the
560
+ # __init__ and from_pretrained(state_dict={}) paths produce identical
561
+ # weights under a fixed seed.
562
+ if isinstance(module, LagunaTopKRouter):
563
+ torch.nn.init.zeros_(module.e_score_correction_bias)
564
+ if isinstance(module, LagunaAttention) and hasattr(module, "sink"):
565
+ torch.nn.init.zeros_(module.sink)
566
+
567
+
568
+ class LagunaModel(LagunaPreTrainedModel):
569
+ def __init__(self, config: LagunaConfig):
570
+ super().__init__(config)
571
+ self.padding_idx = config.pad_token_id
572
+ self.vocab_size = config.vocab_size
573
+
574
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
575
+ self.layers = nn.ModuleList(
576
+ [LagunaDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
577
+ )
578
+ self.norm = LagunaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
579
+
580
+ # ``LagunaRotaryEmbedding`` inherits ``Qwen2MoeRotaryEmbedding``'s flat-shape
581
+ # contract — it reads ``config.rope_parameters["rope_type"]`` at the outer
582
+ # level. Laguna stores rope nested by layer type (``{"full_attention": {...},
583
+ # ...}``), so pass a config clone with the full-attention sub-dict flattened.
584
+ rp = getattr(config, "rope_parameters", None)
585
+ if isinstance(rp, dict) and isinstance(rp.get("full_attention"), dict):
586
+ import copy
587
+
588
+ full_config = copy.deepcopy(config)
589
+ full_config.rope_parameters = dict(rp["full_attention"])
590
+ self.rotary_emb = LagunaRotaryEmbedding(config=full_config)
591
+ else:
592
+ self.rotary_emb = LagunaRotaryEmbedding(config=config)
593
+
594
+ # Separate RoPE for sliding-window attention layers (when configured).
595
+ # Be careful with ``partial_rotary_factor`` — ``PreTrainedConfig.standardize_rope_params``
596
+ # unconditionally overwrites ``rope_parameters["partial_rotary_factor"]`` with
597
+ # ``self.partial_rotary_factor``, so we must align the top-level field on the
598
+ # cloned config to the SWA value, otherwise the global partial factor silently
599
+ # clobbers the SWA one.
600
+ if getattr(config, "swa_rope_parameters", None) is not None:
601
+ import copy
602
+
603
+ swa_config = copy.deepcopy(config)
604
+ swa_config.rope_parameters = dict(config.swa_rope_parameters)
605
+ swa_partial = swa_config.rope_parameters.get("partial_rotary_factor")
606
+ swa_config.partial_rotary_factor = swa_partial
607
+ self.swa_rotary_emb = LagunaRotaryEmbedding(config=swa_config)
608
+ else:
609
+ self.swa_rotary_emb = None
610
+
611
+ self.gradient_checkpointing = False
612
+
613
+ # Initialize weights and apply final processing
614
+ self.post_init()
615
+
616
+ @merge_with_config_defaults
617
+ @capture_outputs
618
+ @auto_docstring
619
+ def forward(
620
+ self,
621
+ input_ids: torch.LongTensor | None = None,
622
+ attention_mask: torch.Tensor | None = None,
623
+ position_ids: torch.LongTensor | None = None,
624
+ past_key_values: Cache | None = None,
625
+ inputs_embeds: torch.FloatTensor | None = None,
626
+ use_cache: bool | None = None,
627
+ **kwargs: Unpack[TransformersKwargs],
628
+ ) -> MoeModelOutputWithPast:
629
+ from transformers.cache_utils import DynamicCache
630
+ from transformers.masking_utils import create_causal_mask, create_sliding_window_causal_mask
631
+
632
+ if (input_ids is None) ^ (inputs_embeds is not None):
633
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
634
+
635
+ if inputs_embeds is None:
636
+ inputs_embeds = self.embed_tokens(input_ids)
637
+
638
+ if use_cache and past_key_values is None:
639
+ past_key_values = DynamicCache(config=self.config)
640
+
641
+ if position_ids is None:
642
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
643
+ position_ids = (
644
+ torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens
645
+ ).unsqueeze(0)
646
+
647
+ # Build one mask per layer-type so each layer can be dispatched with the right
648
+ # attention pattern (follows the afmoe / cohere2 v5 convention).
649
+ layer_types = getattr(self.config, "layer_types", None)
650
+ has_swa = layer_types is not None and "sliding_attention" in layer_types
651
+ if not isinstance(causal_mask_mapping := attention_mask, dict):
652
+ mask_kwargs = {
653
+ "config": self.config,
654
+ "inputs_embeds": inputs_embeds,
655
+ "attention_mask": attention_mask,
656
+ "past_key_values": past_key_values,
657
+ "position_ids": position_ids,
658
+ }
659
+ causal_mask_mapping = {"full_attention": create_causal_mask(**mask_kwargs)}
660
+ if has_swa:
661
+ causal_mask_mapping["sliding_attention"] = create_sliding_window_causal_mask(**mask_kwargs)
662
+
663
+ hidden_states = inputs_embeds
664
+ global_pe = self.rotary_emb(hidden_states, position_ids)
665
+ # Per-layer-type position embeddings: Laguna optionally uses a different rope for
666
+ # sliding layers (``swa_rope_parameters``). When absent, SWA layers share the
667
+ # global rope.
668
+ if has_swa:
669
+ swa_pe = self.swa_rotary_emb(hidden_states, position_ids) if self.swa_rotary_emb is not None else global_pe
670
+ position_embeddings_mapping = {"full_attention": global_pe, "sliding_attention": swa_pe}
671
+ else:
672
+ position_embeddings_mapping = None
673
+
674
+ for decoder_layer in self.layers[: self.config.num_hidden_layers]:
675
+ layer_attn_mask = causal_mask_mapping[decoder_layer.attention_type]
676
+ layer_pos_emb = (
677
+ position_embeddings_mapping[decoder_layer.attention_type]
678
+ if position_embeddings_mapping is not None
679
+ else global_pe
680
+ )
681
+ hidden_states = decoder_layer(
682
+ hidden_states,
683
+ attention_mask=layer_attn_mask,
684
+ position_ids=position_ids,
685
+ past_key_values=past_key_values,
686
+ use_cache=use_cache,
687
+ position_embeddings=layer_pos_emb,
688
+ **kwargs,
689
+ )
690
+
691
+ hidden_states = self.norm(hidden_states)
692
+
693
+ return MoeModelOutputWithPast(
694
+ last_hidden_state=hidden_states,
695
+ past_key_values=past_key_values,
696
+ )
697
+
698
+
699
+ def load_balancing_loss_func(
700
+ gate_logits: torch.Tensor | tuple[torch.Tensor] | None,
701
+ num_experts: int | None = None,
702
+ top_k=2,
703
+ attention_mask: torch.Tensor | None = None,
704
+ ) -> torch.Tensor | int:
705
+ r"""
706
+ Computes auxiliary load balancing loss as in Switch Transformer - implemented in Pytorch.
707
+
708
+ See Switch Transformer (https://huggingface.co/papers/2101.03961) for more details. This function implements the loss
709
+ function presented in equations (4) - (6) of the paper. It aims at penalizing cases where the routing between
710
+ experts is too unbalanced.
711
+
712
+ Args:
713
+ gate_logits:
714
+ Logits from the `gate`, should be a tuple of model.config.num_hidden_layers tensors of
715
+ shape [batch_size X sequence_length, num_experts].
716
+ num_experts:
717
+ Number of experts
718
+ top_k:
719
+ The number of experts to route per-token, can be also interpreted as the `top-k` routing
720
+ parameter.
721
+ attention_mask (`torch.Tensor`, *optional*):
722
+ The attention_mask used in forward function
723
+ shape [batch_size X sequence_length] if not None.
724
+
725
+ Returns:
726
+ The auxiliary loss.
727
+ """
728
+ if gate_logits is None or not isinstance(gate_logits, tuple):
729
+ return 0
730
+
731
+ if isinstance(gate_logits, tuple):
732
+ compute_device = gate_logits[0].device
733
+ concatenated_gate_logits = torch.cat([layer_gate.to(compute_device) for layer_gate in gate_logits], dim=0)
734
+
735
+ routing_weights = torch.nn.functional.softmax(concatenated_gate_logits, dim=-1)
736
+
737
+ _, selected_experts = torch.topk(routing_weights, top_k, dim=-1)
738
+
739
+ expert_mask = torch.nn.functional.one_hot(selected_experts, num_experts)
740
+
741
+ if attention_mask is None:
742
+ # Compute the percentage of tokens routed to each experts
743
+ tokens_per_expert = torch.mean(expert_mask.float(), dim=0)
744
+
745
+ # Compute the average probability of routing to these experts
746
+ router_prob_per_expert = torch.mean(routing_weights, dim=0)
747
+ else:
748
+ batch_size, sequence_length = attention_mask.shape
749
+ num_hidden_layers = concatenated_gate_logits.shape[0] // (batch_size * sequence_length)
750
+
751
+ # Compute the mask that masks all padding tokens as 0 with the same shape of expert_mask
752
+ expert_attention_mask = (
753
+ attention_mask[None, :, :, None, None]
754
+ .expand((num_hidden_layers, batch_size, sequence_length, top_k, num_experts))
755
+ .reshape(-1, top_k, num_experts)
756
+ .to(compute_device)
757
+ )
758
+
759
+ # Compute the percentage of tokens routed to each experts
760
+ tokens_per_expert = torch.sum(expert_mask.float() * expert_attention_mask, dim=0) / torch.sum(
761
+ expert_attention_mask, dim=0
762
+ )
763
+
764
+ # Compute the mask that masks all padding tokens as 0 with the same shape of tokens_per_expert
765
+ router_per_expert_attention_mask = (
766
+ attention_mask[None, :, :, None]
767
+ .expand((num_hidden_layers, batch_size, sequence_length, num_experts))
768
+ .reshape(-1, num_experts)
769
+ .to(compute_device)
770
+ )
771
+
772
+ # Compute the average probability of routing to these experts
773
+ router_prob_per_expert = torch.sum(routing_weights * router_per_expert_attention_mask, dim=0) / torch.sum(
774
+ router_per_expert_attention_mask, dim=0
775
+ )
776
+
777
+ overall_loss = torch.sum(tokens_per_expert * router_prob_per_expert.unsqueeze(0))
778
+ return overall_loss * num_experts
779
+
780
+
781
+ @auto_docstring
782
+ class LagunaForCausalLM(LagunaPreTrainedModel, GenerationMixin):
783
+ _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
784
+ _tp_plan = {"lm_head": "colwise_gather_output"}
785
+ _pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
786
+
787
+ def __init__(self, config):
788
+ super().__init__(config)
789
+ self.model = LagunaModel(config)
790
+ self.vocab_size = config.vocab_size
791
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
792
+ self.router_aux_loss_coef = config.router_aux_loss_coef
793
+ self.num_experts = config.num_experts
794
+ self.num_experts_per_tok = config.num_experts_per_tok
795
+
796
+ # Initialize weights and apply final processing
797
+ self.post_init()
798
+
799
+ @can_return_tuple
800
+ @auto_docstring
801
+ def forward(
802
+ self,
803
+ input_ids: torch.LongTensor | None = None,
804
+ attention_mask: torch.Tensor | None = None,
805
+ position_ids: torch.LongTensor | None = None,
806
+ past_key_values: Cache | None = None,
807
+ inputs_embeds: torch.FloatTensor | None = None,
808
+ labels: torch.LongTensor | None = None,
809
+ use_cache: bool | None = None,
810
+ output_router_logits: bool | None = None,
811
+ logits_to_keep: int | torch.Tensor = 0,
812
+ **kwargs: Unpack[TransformersKwargs],
813
+ ) -> MoeCausalLMOutputWithPast:
814
+ r"""
815
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
816
+ Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
817
+ config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
818
+ (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
819
+ """
820
+
821
+ output_router_logits = (
822
+ output_router_logits if output_router_logits is not None else self.config.output_router_logits
823
+ )
824
+
825
+ # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn)
826
+ outputs: MoeModelOutputWithPast = self.model(
827
+ input_ids=input_ids,
828
+ attention_mask=attention_mask,
829
+ position_ids=position_ids,
830
+ past_key_values=past_key_values,
831
+ inputs_embeds=inputs_embeds,
832
+ use_cache=use_cache,
833
+ output_router_logits=output_router_logits,
834
+ **kwargs,
835
+ )
836
+
837
+ hidden_states = outputs.last_hidden_state
838
+ # Only compute necessary logits, and do not upcast them to float if we are not computing the loss
839
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
840
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
841
+
842
+ loss = None
843
+ if labels is not None:
844
+ loss = self.loss_function(logits, labels, self.vocab_size, **kwargs)
845
+
846
+ aux_loss = None
847
+ if output_router_logits:
848
+ aux_loss = load_balancing_loss_func(
849
+ outputs.router_logits,
850
+ self.num_experts,
851
+ self.num_experts_per_tok,
852
+ attention_mask,
853
+ )
854
+ if labels is not None:
855
+ loss += self.router_aux_loss_coef * aux_loss.to(loss.device) # make sure to reside in the same device
856
+
857
+ return MoeCausalLMOutputWithPast(
858
+ loss=loss,
859
+ aux_loss=aux_loss,
860
+ logits=logits,
861
+ past_key_values=outputs.past_key_values,
862
+ hidden_states=outputs.hidden_states,
863
+ attentions=outputs.attentions,
864
+ router_logits=outputs.router_logits,
865
+ )
866
+
867
+
868
+ __all__ = ["LagunaForCausalLM", "LagunaModel", "LagunaPreTrainedModel"]
869
+
870
+
871
+ # --- Added: register the native Laguna checkpoint-conversion for trust_remote_code loads.
872
+ # transformers >=5.12 skips checkpoint-conversion mappings for custom (remote) code
873
+ # unless explicitly registered, which broke loading the shipped per-expert MoE weights.
874
+ try:
875
+ from transformers.conversion_mapping import (
876
+ get_checkpoint_conversion_mapping as _lg_get,
877
+ register_checkpoint_conversion_mapping as _lg_reg,
878
+ USER_REGISTERED_MAPPINGS as _lg_user,
879
+ )
880
+
881
+ if "laguna" not in _lg_user:
882
+ _lg_m = _lg_get("laguna")
883
+ if _lg_m is not None:
884
+ _lg_reg("laguna", _lg_m, overwrite=True)
885
+ except Exception:
886
+ pass
special_tokens_map.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "〈|EOS|〉",
3
+ "cls_token": "〈|CLS|〉",
4
+ "eos_token": "〈|EOS|〉",
5
+ "mask_token": "〈|MASK|〉",
6
+ "pad_token": "〈|PAD|〉",
7
+ "sep_token": "〈|SEP|〉",
8
+ "unk_token": "〈|UNK|〉"
9
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "〈|UNK|〉",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "1": {
12
+ "content": "〈|CODE_START|〉",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "2": {
20
+ "content": "〈|EOS|〉",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "3": {
28
+ "content": "〈|CODE_END|〉",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "4": {
36
+ "content": "〈|META_START|〉",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ },
43
+ "5": {
44
+ "content": "〈|META_END|〉",
45
+ "lstrip": false,
46
+ "normalized": false,
47
+ "rstrip": false,
48
+ "single_word": false,
49
+ "special": true
50
+ },
51
+ "6": {
52
+ "content": "〈|FIM_MIDDLE|〉",
53
+ "lstrip": false,
54
+ "normalized": false,
55
+ "rstrip": false,
56
+ "single_word": false,
57
+ "special": true
58
+ },
59
+ "7": {
60
+ "content": "〈|FIM_SUFFIX|〉",
61
+ "lstrip": false,
62
+ "normalized": false,
63
+ "rstrip": false,
64
+ "single_word": false,
65
+ "special": true
66
+ },
67
+ "8": {
68
+ "content": "〈|SEP|〉",
69
+ "lstrip": false,
70
+ "normalized": false,
71
+ "rstrip": false,
72
+ "single_word": false,
73
+ "special": true
74
+ },
75
+ "9": {
76
+ "content": "〈|PAD|〉",
77
+ "lstrip": false,
78
+ "normalized": false,
79
+ "rstrip": false,
80
+ "single_word": false,
81
+ "special": true
82
+ },
83
+ "10": {
84
+ "content": "〈|CLS|〉",
85
+ "lstrip": false,
86
+ "normalized": false,
87
+ "rstrip": false,
88
+ "single_word": false,
89
+ "special": true
90
+ },
91
+ "11": {
92
+ "content": "〈|FIM_START|〉",
93
+ "lstrip": false,
94
+ "normalized": false,
95
+ "rstrip": false,
96
+ "single_word": false,
97
+ "special": true
98
+ },
99
+ "12": {
100
+ "content": "〈|MASK|〉",
101
+ "lstrip": false,
102
+ "normalized": false,
103
+ "rstrip": false,
104
+ "single_word": false,
105
+ "special": true
106
+ },
107
+ "13": {
108
+ "content": "|◊|",
109
+ "lstrip": false,
110
+ "normalized": false,
111
+ "rstrip": false,
112
+ "single_word": false,
113
+ "special": true
114
+ },
115
+ "14": {
116
+ "content": "〈|",
117
+ "lstrip": false,
118
+ "normalized": false,
119
+ "rstrip": false,
120
+ "single_word": false,
121
+ "special": true
122
+ },
123
+ "15": {
124
+ "content": "|〉",
125
+ "lstrip": false,
126
+ "normalized": false,
127
+ "rstrip": false,
128
+ "single_word": false,
129
+ "special": true
130
+ },
131
+ "16": {
132
+ "content": "〈|/",
133
+ "lstrip": false,
134
+ "normalized": false,
135
+ "rstrip": false,
136
+ "single_word": false,
137
+ "special": true
138
+ },
139
+ "17": {
140
+ "content": "/|〉",
141
+ "lstrip": false,
142
+ "normalized": false,
143
+ "rstrip": false,
144
+ "single_word": false,
145
+ "special": true
146
+ },
147
+ "20": {
148
+ "content": "〈|SPECIAL_1|〉",
149
+ "lstrip": false,
150
+ "normalized": false,
151
+ "rstrip": false,
152
+ "single_word": false,
153
+ "special": true
154
+ },
155
+ "21": {
156
+ "content": "〈|SPECIAL_2|〉",
157
+ "lstrip": false,
158
+ "normalized": false,
159
+ "rstrip": false,
160
+ "single_word": false,
161
+ "special": true
162
+ },
163
+ "22": {
164
+ "content": "〈|SPECIAL_3|〉",
165
+ "lstrip": false,
166
+ "normalized": false,
167
+ "rstrip": false,
168
+ "single_word": false,
169
+ "special": true
170
+ },
171
+ "27": {
172
+ "content": "〈|SPECIAL_8|〉",
173
+ "lstrip": false,
174
+ "normalized": false,
175
+ "rstrip": false,
176
+ "single_word": false,
177
+ "special": true
178
+ },
179
+ "28": {
180
+ "content": "〈|SPECIAL_9|〉",
181
+ "lstrip": false,
182
+ "normalized": false,
183
+ "rstrip": false,
184
+ "single_word": false,
185
+ "special": true
186
+ },
187
+ "29": {
188
+ "content": "〈|SPECIAL_10|〉",
189
+ "lstrip": false,
190
+ "normalized": false,
191
+ "rstrip": false,
192
+ "single_word": false,
193
+ "special": true
194
+ },
195
+ "30": {
196
+ "content": "〈|SPECIAL_11|〉",
197
+ "lstrip": false,
198
+ "normalized": false,
199
+ "rstrip": false,
200
+ "single_word": false,
201
+ "special": true
202
+ },
203
+ "31": {
204
+ "content": "〈|SPECIAL_12|〉",
205
+ "lstrip": false,
206
+ "normalized": false,
207
+ "rstrip": false,
208
+ "single_word": false,
209
+ "special": true
210
+ },
211
+ "32": {
212
+ "content": "〈|SPECIAL_13|〉",
213
+ "lstrip": false,
214
+ "normalized": false,
215
+ "rstrip": false,
216
+ "single_word": false,
217
+ "special": true
218
+ },
219
+ "33": {
220
+ "content": "〈|SPECIAL_14|〉",
221
+ "lstrip": false,
222
+ "normalized": false,
223
+ "rstrip": false,
224
+ "single_word": false,
225
+ "special": true
226
+ },
227
+ "34": {
228
+ "content": "〈|SPECIAL_15|〉",
229
+ "lstrip": false,
230
+ "normalized": false,
231
+ "rstrip": false,
232
+ "single_word": false,
233
+ "special": true
234
+ },
235
+ "35": {
236
+ "content": "〈|SPECIAL_16|〉",
237
+ "lstrip": false,
238
+ "normalized": false,
239
+ "rstrip": false,
240
+ "single_word": false,
241
+ "special": true
242
+ },
243
+ "36": {
244
+ "content": "〈|SPECIAL_17|〉",
245
+ "lstrip": false,
246
+ "normalized": false,
247
+ "rstrip": false,
248
+ "single_word": false,
249
+ "special": true
250
+ },
251
+ "37": {
252
+ "content": "〈|SPECIAL_18|〉",
253
+ "lstrip": false,
254
+ "normalized": false,
255
+ "rstrip": false,
256
+ "single_word": false,
257
+ "special": true
258
+ },
259
+ "38": {
260
+ "content": "〈|SPECIAL_19|〉",
261
+ "lstrip": false,
262
+ "normalized": false,
263
+ "rstrip": false,
264
+ "single_word": false,
265
+ "special": true
266
+ },
267
+ "39": {
268
+ "content": "〈|SPECIAL_20|〉",
269
+ "lstrip": false,
270
+ "normalized": false,
271
+ "rstrip": false,
272
+ "single_word": false,
273
+ "special": true
274
+ },
275
+ "40": {
276
+ "content": "〈|SPECIAL_21|〉",
277
+ "lstrip": false,
278
+ "normalized": false,
279
+ "rstrip": false,
280
+ "single_word": false,
281
+ "special": true
282
+ },
283
+ "41": {
284
+ "content": "〈|SPECIAL_22|〉",
285
+ "lstrip": false,
286
+ "normalized": false,
287
+ "rstrip": false,
288
+ "single_word": false,
289
+ "special": true
290
+ },
291
+ "42": {
292
+ "content": "〈|SPECIAL_23|〉",
293
+ "lstrip": false,
294
+ "normalized": false,
295
+ "rstrip": false,
296
+ "single_word": false,
297
+ "special": true
298
+ },
299
+ "43": {
300
+ "content": "〈|SPECIAL_24|〉",
301
+ "lstrip": false,
302
+ "normalized": false,
303
+ "rstrip": false,
304
+ "single_word": false,
305
+ "special": true
306
+ },
307
+ "44": {
308
+ "content": "〈|SPECIAL_25|〉",
309
+ "lstrip": false,
310
+ "normalized": false,
311
+ "rstrip": false,
312
+ "single_word": false,
313
+ "special": true
314
+ },
315
+ "45": {
316
+ "content": "〈|SPECIAL_26|〉",
317
+ "lstrip": false,
318
+ "normalized": false,
319
+ "rstrip": false,
320
+ "single_word": false,
321
+ "special": true
322
+ },
323
+ "46": {
324
+ "content": "〈|SPECIAL_27|〉",
325
+ "lstrip": false,
326
+ "normalized": false,
327
+ "rstrip": false,
328
+ "single_word": false,
329
+ "special": true
330
+ },
331
+ "47": {
332
+ "content": "〈|SPECIAL_28|〉",
333
+ "lstrip": false,
334
+ "normalized": false,
335
+ "rstrip": false,
336
+ "single_word": false,
337
+ "special": true
338
+ },
339
+ "48": {
340
+ "content": "〈|SPECIAL_29|〉",
341
+ "lstrip": false,
342
+ "normalized": false,
343
+ "rstrip": false,
344
+ "single_word": false,
345
+ "special": true
346
+ },
347
+ "49": {
348
+ "content": "〈|SPECIAL_30|〉",
349
+ "lstrip": false,
350
+ "normalized": false,
351
+ "rstrip": false,
352
+ "single_word": false,
353
+ "special": true
354
+ },
355
+ "50": {
356
+ "content": "〈|SPECIAL_31|〉",
357
+ "lstrip": false,
358
+ "normalized": false,
359
+ "rstrip": false,
360
+ "single_word": false,
361
+ "special": true
362
+ },
363
+ "51": {
364
+ "content": "〈|SPECIAL_32|〉",
365
+ "lstrip": false,
366
+ "normalized": false,
367
+ "rstrip": false,
368
+ "single_word": false,
369
+ "special": true
370
+ },
371
+ "52": {
372
+ "content": "〈|SPECIAL_33|〉",
373
+ "lstrip": false,
374
+ "normalized": false,
375
+ "rstrip": false,
376
+ "single_word": false,
377
+ "special": true
378
+ },
379
+ "53": {
380
+ "content": "〈|SPECIAL_34|〉",
381
+ "lstrip": false,
382
+ "normalized": false,
383
+ "rstrip": false,
384
+ "single_word": false,
385
+ "special": true
386
+ },
387
+ "54": {
388
+ "content": "〈|SPECIAL_35|〉",
389
+ "lstrip": false,
390
+ "normalized": false,
391
+ "rstrip": false,
392
+ "single_word": false,
393
+ "special": true
394
+ },
395
+ "55": {
396
+ "content": "〈|SPECIAL_36|〉",
397
+ "lstrip": false,
398
+ "normalized": false,
399
+ "rstrip": false,
400
+ "single_word": false,
401
+ "special": true
402
+ },
403
+ "56": {
404
+ "content": "〈|SPECIAL_37|〉",
405
+ "lstrip": false,
406
+ "normalized": false,
407
+ "rstrip": false,
408
+ "single_word": false,
409
+ "special": true
410
+ },
411
+ "57": {
412
+ "content": "〈|SPECIAL_38|〉",
413
+ "lstrip": false,
414
+ "normalized": false,
415
+ "rstrip": false,
416
+ "single_word": false,
417
+ "special": true
418
+ },
419
+ "58": {
420
+ "content": "〈|SPECIAL_39|〉",
421
+ "lstrip": false,
422
+ "normalized": false,
423
+ "rstrip": false,
424
+ "single_word": false,
425
+ "special": true
426
+ },
427
+ "59": {
428
+ "content": "〈|SPECIAL_40|〉",
429
+ "lstrip": false,
430
+ "normalized": false,
431
+ "rstrip": false,
432
+ "single_word": false,
433
+ "special": true
434
+ },
435
+ "60": {
436
+ "content": "〈|SPECIAL_41|〉",
437
+ "lstrip": false,
438
+ "normalized": false,
439
+ "rstrip": false,
440
+ "single_word": false,
441
+ "special": true
442
+ },
443
+ "61": {
444
+ "content": "〈|SPECIAL_42|〉",
445
+ "lstrip": false,
446
+ "normalized": false,
447
+ "rstrip": false,
448
+ "single_word": false,
449
+ "special": true
450
+ },
451
+ "62": {
452
+ "content": "〈|SPECIAL_43|〉",
453
+ "lstrip": false,
454
+ "normalized": false,
455
+ "rstrip": false,
456
+ "single_word": false,
457
+ "special": true
458
+ },
459
+ "63": {
460
+ "content": "〈|SPECIAL_44|〉",
461
+ "lstrip": false,
462
+ "normalized": false,
463
+ "rstrip": false,
464
+ "single_word": false,
465
+ "special": true
466
+ },
467
+ "64": {
468
+ "content": "〈|SPECIAL_45|〉",
469
+ "lstrip": false,
470
+ "normalized": false,
471
+ "rstrip": false,
472
+ "single_word": false,
473
+ "special": true
474
+ },
475
+ "65": {
476
+ "content": "〈|SPECIAL_46|〉",
477
+ "lstrip": false,
478
+ "normalized": false,
479
+ "rstrip": false,
480
+ "single_word": false,
481
+ "special": true
482
+ },
483
+ "66": {
484
+ "content": "〈|SPECIAL_47|〉",
485
+ "lstrip": false,
486
+ "normalized": false,
487
+ "rstrip": false,
488
+ "single_word": false,
489
+ "special": true
490
+ },
491
+ "67": {
492
+ "content": "〈|SPECIAL_48|〉",
493
+ "lstrip": false,
494
+ "normalized": false,
495
+ "rstrip": false,
496
+ "single_word": false,
497
+ "special": true
498
+ },
499
+ "68": {
500
+ "content": "〈|SPECIAL_49|〉",
501
+ "lstrip": false,
502
+ "normalized": false,
503
+ "rstrip": false,
504
+ "single_word": false,
505
+ "special": true
506
+ },
507
+ "69": {
508
+ "content": "〈|SPECIAL_50|〉",
509
+ "lstrip": false,
510
+ "normalized": false,
511
+ "rstrip": false,
512
+ "single_word": false,
513
+ "special": true
514
+ },
515
+ "18": {
516
+ "content": "<think>",
517
+ "single_word": false,
518
+ "lstrip": false,
519
+ "rstrip": false,
520
+ "normalized": false,
521
+ "special": false
522
+ },
523
+ "19": {
524
+ "content": "</think>",
525
+ "single_word": false,
526
+ "lstrip": false,
527
+ "rstrip": false,
528
+ "normalized": false,
529
+ "special": false
530
+ },
531
+ "23": {
532
+ "content": "<assistant>",
533
+ "single_word": false,
534
+ "lstrip": false,
535
+ "rstrip": false,
536
+ "normalized": false,
537
+ "special": false
538
+ },
539
+ "24": {
540
+ "content": "</assistant>",
541
+ "single_word": false,
542
+ "lstrip": false,
543
+ "rstrip": false,
544
+ "normalized": false,
545
+ "special": false
546
+ },
547
+ "25": {
548
+ "content": "<tool_call>",
549
+ "single_word": false,
550
+ "lstrip": false,
551
+ "rstrip": false,
552
+ "normalized": false,
553
+ "special": false
554
+ },
555
+ "26": {
556
+ "content": "</tool_call>",
557
+ "single_word": false,
558
+ "lstrip": false,
559
+ "rstrip": false,
560
+ "normalized": false,
561
+ "special": false
562
+ }
563
+ },
564
+ "bos_token": "〈|EOS|〉",
565
+ "clean_up_tokenization_spaces": false,
566
+ "cls_token": "〈|CLS|〉",
567
+ "eos_token": "〈|EOS|〉",
568
+ "extra_special_tokens": {},
569
+ "mask_token": "〈|MASK|〉",
570
+ "model_max_length": 1000000000000000019884624838656,
571
+ "pad_token": "〈|PAD|〉",
572
+ "sep_token": "〈|SEP|〉",
573
+ "tokenizer_class": "PreTrainedTokenizerFast",
574
+ "unk_token": "〈|UNK|〉",
575
+ "chat_template": "{% include 'chat_template.jinja' %}"
576
+ }