joerowell varunrandery Jiminator commited on
Commit
c0a69d7
·
0 Parent(s):

Laguna-XS-2.1

Browse files

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

.eval_results/swe-bench_pro.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ - dataset:
2
+ id: ScaleAI/SWE-bench_Pro
3
+ task_id: SWE_Bench_Pro
4
+ value: 47.6
5
+ source:
6
+ url: https://huggingface.co/poolside/Laguna-XS-2.1
7
+ name: Model Card
.eval_results/swe-bench_verified.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ - dataset:
2
+ id: SWE-bench/SWE-bench_Verified
3
+ task_id: swe_bench_%_resolved
4
+ value: 70.9
5
+ source:
6
+ url: https://huggingface.co/poolside/Laguna-XS-2.1
7
+ name: Model Card
.eval_results/terminal-bench-2.0.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ - dataset:
2
+ id: harborframework/terminal-bench-2.0
3
+ task_id: terminalbench_2
4
+ value: 37.5
5
+ source:
6
+ url: https://huggingface.co/poolside/Laguna-XS-2.1
7
+ name: Model Card
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
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-xs-2.1
9
+ - vllm
10
+ license: openmdw-1.1
11
+ pipeline_tag: text-generation
12
+ ---
13
+
14
+ <p align="center">
15
+ <img alt="poolside-banner" src="https://poolside.ai/assets/laguna/laguna-xs2-1-banner.svg" width="800px">
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://openrouter.ai/poolside/laguna-xs-2.1"><strong>Use on OpenRouter</strong></a> ·
20
+ <a href="https://poolside.ai/blog/introducing-laguna-xs-2-1"><strong>Release blog post</strong></a>
21
+ </p>
22
+
23
+ <br>
24
+
25
+ # Laguna XS 2.1
26
+ Laguna XS 2.1 is a 33B total parameter Mixture-of-Experts model with 3B activated parameters per token designed for agentic coding and long-horizon work on a local machine. This model is an upgraded version of our [Laguna XS.2](https://huggingface.co/poolside/Laguna-XS.2) model with a +5.4% jump on SWE-bench Multilingual as well as stronger performance on terminal-style tasks.
27
+
28
+ > [!NOTE]
29
+ > For more details on how we train, including on data automixing and async off-policy agent RL, check out our recent [technical report](https://poolside.ai/assets/laguna/laguna-m1-xs2-technical-report.pdf).
30
+
31
+ ## Highlights
32
+ - **Mixed SWA and global attention layout**: Laguna XS 2.1 uses sigmoid gating with per-layer rotary scales, enabling mixed SWA (Sliding Window Attention) and global attention layers in a 3:1 ratio (across 40 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 33B total parameters and 3B activated, Laguna XS 2.1 is compact enough to run on a Mac with 36 GB of RAM. Available on [Ollama](https://ollama.com/library/laguna-xs-2.1) and [llama.cpp](https://github.com/ggml-org/llama.cpp/pull/25165). High-quality FP8, NVFP4 and INT4 quantized variants available ([see the collection](https://huggingface.co/collections/poolside/laguna-xs-21))
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: 33B total with 3B activated per token
44
+ - Optimizer: Muon
45
+ - Layers: 40 layers (10 layers with global attention, 30 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
+ ## Benchmark results
53
+
54
+ <p align="center">
55
+ <img alt="benchmarks" src="https://poolside.ai/assets/laguna/laguna-xs2-1-chart.svg" width="800px">
56
+ </p>
57
+
58
+ | Model | Size (total params.) | SWE-bench Verified | SWE-bench Multilingual | SWE-Bench Pro (Public Dataset) | Terminal-Bench 2.0 |
59
+ |---------------------------|----------------------|--------------------|------------------------|--------------------------------|--------------------|
60
+ | **Laguna XS 2.1** | 33B | 70.9% | 63.1% | 47.6% | 37.5% |
61
+ | **Laguna XS.2** | 33B | 69.9% | 57.7% | 46.3% | 35.7% |
62
+ | Qwen3.6-35B-A3B | 35B | 73.4% | 67.2% | 49.5% | 51.5% |
63
+ | North Mini Code | 30B | 67.6% | - | 40.2% | 36.0% |
64
+ | MAI-Code-1-Flash | 137B | 71.6% | 65.5% | 51.2% | 54.8% |
65
+ | gpt-oss-120B | 120B | - | - | 16.2% | 18.7% |
66
+ | Claude Haiku 4.5 | - | 73.3% | - | 39.5% | 29.8% |
67
+ | GPT-5.4 Nano | - | - | - | 52.4% | 46.3% |
68
+
69
+ *We used the highest publicly-referenced scores for all comparison models across each benchmark. In all cases these were official scores published in release blog posts or equivalent, with the exception of gpt-oss-120b and Claude Haiku 4.5 where the highest published (verified) scores for SWE-Bench Pro and Terminal-Bench 2.0 are from their respective official leaderboards.*
70
+
71
+ <details>
72
+ <summary>Expand for benchmarking methodology</summary>
73
+
74
+ All benchmarking for Laguna XS 2.1 was completed using Laude Institute’s Harbor Framework with our [agent harness](https://github.com/poolsideai/pool), with a maximum of 500 steps and sandboxed execution. The same sampling parameters were used for all Laguna XS 2.1 benchmarking: temperature=1.0, top_k=20 and top_p=1, with thinking mode enabled and a context length of 256K tokens. All tasks were run in their own sandbox using 8 GB RAM/2 CPUs, with the exception of Terminal-Bench 2.0, which used 48 GB RAM/32 CPUs.
75
+
76
+ Some base task images and verifiers were patched to fix infrastructure reliability issues inherent in task setup, such as rate limits on third-party dependencies in external registries used by the verifier. All four agentic benchmarks were run with patched images. We also ran a reward-hack judge post-hoc on Laguna XS 2.1 evaluation runs and did not find significant reward hacking after joint judge review and manual review.
77
+
78
+ - SWE-bench Verified: mean pass@1 averaged over 4 attempts per task
79
+ - SWE-bench Multilingual: mean pass@1 averaged over 4 attempts per task
80
+ - SWE-Bench Pro: mean pass@1 averaged over 2 attempts per task
81
+ - Terminal-Bench 2.0: mean pass@1 averaged over 5 attempts per task; 48 GB RAM/32 CPUs
82
+
83
+ </details>
84
+
85
+ ## Usage
86
+
87
+ Laguna XS 2.1 has launch-day support in vLLM, SGLang, Transformers and Llama.cpp, and TRT-LLM thanks to the support of the team at NVIDIA.
88
+
89
+ The fastest way to get started is using OpenRouter.
90
+
91
+ > [!NOTE]
92
+ > We are providing free inference for a limited time for Laguna XS 2.1, as well as our larger 225B model, Laguna M.1. Visit our [provider page on OpenRouter](https://openrouter.ai/poolside/) to get started.
93
+
94
+ ### pool
95
+
96
+ **pool** is a lightweight terminal-based coding agent and a dual [Agent Client Protocol](https://agentclientprotocol.com/get-started) client-server.
97
+
98
+ Download and install for macOS and Linux:
99
+
100
+ ```shell
101
+ curl -fsSL https://downloads.poolside.ai/pool/install.sh | bash
102
+ ```
103
+
104
+ Launch and `> Log in with Poolside` to get a free, limited-use API key.
105
+
106
+ Alternatively, use Poolside models alongside the wide catalog of models available on OpenRouter by selecting `> Log in with OpenRouter`.
107
+
108
+ ```shell
109
+ pool login
110
+ ```
111
+
112
+ Use in any [ACP client](https://agentclientprotocol.com/get-started/clients). Configure Zed and JetBrains automatically:
113
+
114
+ ```shell
115
+ pool acp setup --editor zed|jetbrains
116
+ ```
117
+
118
+ Use pool with Ollama with one-command setup:
119
+
120
+ ```shell
121
+ ollama pull laguna-xs-2.1
122
+ ollama launch pool --model laguna-xs-2.1
123
+ ```
124
+
125
+ #### Feedback and issues
126
+
127
+ Submit feedback with `/feedback` and read the [full documentation on GitHub](https://github.com/poolsideai/pool).
128
+
129
+ ### Local deployment
130
+
131
+ Laguna XS 2.1 is supported in vLLM, SGLang, Transformers and Llama.cpp, and TRT-LLM thanks to the support of the team at NVIDIA. Use Laguna-XS 2.1 with Ollama (with MLX support) and the mlx-lm framework for the best experience on your local machine.
132
+
133
+ #### vLLM
134
+
135
+ Serve Laguna XS 2.1 locally with vLLM and query it from any OpenAI-compatible client (see [Controlling reasoning](#controlling-reasoning) for tool calls, streaming, and reasoning extraction):
136
+
137
+ > [!NOTE]
138
+ > Laguna XS 2.1 support is available in vLLM 0.21.0 and later ([vllm-project/vllm#41129](https://github.com/vllm-project/vllm/pull/41129)).
139
+
140
+ ```shell
141
+ pip install 'vllm>=0.21.0'
142
+
143
+ vllm serve \
144
+ --model poolside/Laguna-XS-2.1 \
145
+ --tool-call-parser poolside_v1 \
146
+ --reasoning-parser poolside_v1 \
147
+ --enable-auto-tool-choice \
148
+ --served-model-name laguna \
149
+ --default-chat-template-kwargs '{"enable_thinking": true}'
150
+ ```
151
+
152
+ See the [vLLM recipes page](https://recipes.vllm.ai/poolside/Laguna-XS-2.1) for additional deployment guidance.
153
+
154
+ > [!NOTE]
155
+ > **Optional: speculative decoding with DFlash.** For lower latency, pair Laguna XS 2.1 with the [DFlash speculator](https://huggingface.co/poolside/Laguna-XS-2.1-DFlash), a 5-layer Llama-style draft model that proposes up to 7 tokens per step at ~70% per-position acceptance on coding tasks. vLLM support is in progress in [vllm-project/vllm#46853](https://github.com/vllm-project/vllm/pull/46853); once it lands, add `--speculative-config '{"model":"poolside/Laguna-XS-2.1-DFlash","num_speculative_tokens":7,"method":"dflash"}'` to the serve command above.
156
+
157
+ #### SGLang
158
+
159
+ Laguna XS 2.1 is supported in SGLang via [sgl-project/sglang#24204](https://github.com/sgl-project/sglang/pull/24204). See the [SGLang cookbook entry](https://docs.sglang.io/cookbook/autoregressive/Poolside/Laguna-XS-2.1) for a serving recipe.
160
+
161
+ ```shell
162
+ git clone https://github.com/sgl-project/sglang.git
163
+ cd sglang
164
+ pip install -e "python[all]"
165
+
166
+ python -m sglang.launch_server \
167
+ --model-path poolside/Laguna-XS-2.1 \
168
+ --tp-size 8 \
169
+ --mem-fraction-static 0.7 \
170
+ --reasoning-parser poolside_v1 \
171
+ --trust-remote-code
172
+ ```
173
+
174
+ > [!NOTE]
175
+ > **Optional: speculative decoding with DFlash.** The [DFlash speculator](https://huggingface.co/poolside/Laguna-XS-2.1-DFlash) can be paired with Laguna XS 2.1 for lower latency. SGLang support was added in [sgl-project/sglang#29446](https://github.com/sgl-project/sglang/pull/29446). Add `--speculative-algorithm DFLASH \ --speculative-draft-model-path poolside/Laguna-XS-2.1-DFlash-FP8` to the serve command above.
176
+
177
+ #### Transformers
178
+
179
+ Laguna XS 2.1 is supported in Transformers `v5.7.0` and later ([huggingface/transformers#45673](https://github.com/huggingface/transformers/pull/45673)).
180
+
181
+ ```python
182
+ import torch
183
+ from transformers import AutoModelForCausalLM, AutoTokenizer
184
+
185
+ model_id = "poolside/Laguna-XS-2.1"
186
+
187
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
188
+ model = AutoModelForCausalLM.from_pretrained(
189
+ model_id,
190
+ dtype=torch.bfloat16,
191
+ device_map="auto",
192
+ )
193
+
194
+ messages = [
195
+ {"role": "user", "content": "Write a Python retry wrapper with exponential backoff."},
196
+ ]
197
+
198
+ # Reasoning is on by default; pass enable_thinking=False to skip the <think> block.
199
+ inputs = tokenizer.apply_chat_template(
200
+ messages,
201
+ add_generation_prompt=True,
202
+ return_tensors="pt",
203
+ enable_thinking=True,
204
+ ).to(model.device)
205
+
206
+ outputs = model.generate(
207
+ inputs,
208
+ max_new_tokens=1024,
209
+ do_sample=True,
210
+ temperature=1.0,
211
+ top_k=20,
212
+ )
213
+
214
+ response = tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True)
215
+ print(response)
216
+ ```
217
+
218
+ #### TRT-LLM
219
+
220
+ Laguna XS 2.1 support is merged into TensorRT-LLM ([NVIDIA/TensorRT-LLM#13559](https://github.com/NVIDIA/TensorRT-LLM/pull/13559)) and ships in the `v1.3.0rc16` pre-release wheels and later.
221
+
222
+ > [!NOTE]
223
+ > A stable `v1.3.0` has not been released yet, so install a pre-release wheel from NVIDIA's package index (the latest stable, `v1.2.x`, does not include Laguna XS 2.1 support). To build from source instead, see the TensorRT-LLM build documentation. Laguna XS 2.1 support is on `main`.
224
+
225
+ Install the **CUDA-13 `torch` build first**, then TensorRT-LLM. The default PyPI `torch` is a CUDA-12 build whose `cuda-bindings` pin conflicts with TRT-LLM's `cuda-python 13.x`, so a bare `pip install tensorrt-llm` fails to resolve; installing the cu130 `torch` up front avoids it.
226
+
227
+ ```shell
228
+ # 1. CUDA-13 torch build (pins cuda-bindings 13.x, matching TRT-LLM's cuda-python)
229
+ pip install 'torch==2.10.0' torchvision --index-url https://download.pytorch.org/whl/cu130
230
+
231
+ # 2. TRT-LLM from NVIDIA's index (torch already satisfied, so it is not replaced)
232
+ pip install --pre 'tensorrt-llm>=1.3.0rc16' \
233
+ --extra-index-url https://pypi.nvidia.com \
234
+ --extra-index-url https://download.pytorch.org/whl/cu130
235
+ ```
236
+
237
+ This resolves to `tensorrt-llm 1.3.0rc20` with `torch 2.10.0+cu130`, `cuda-python 13.0.3`, and `transformers 5.5.4`.
238
+
239
+ Load the checkpoint directly with `trust_remote_code=True`. No `transformers` compatibility overlay is required: `v1.3.0rc16+` pins `transformers 5.5.4`, which provides the symbols Laguna XS 2.1's config needs (earlier TRT-LLM releases pinned `transformers 4.57`, which did not).
240
+
241
+ ```python
242
+ from tensorrt_llm import LLM, SamplingParams
243
+
244
+ llm = LLM(
245
+ model="poolside/Laguna-XS-2.1",
246
+ trust_remote_code=True,
247
+ tensor_parallel_size=1,
248
+ )
249
+
250
+ sampling = SamplingParams(max_tokens=1024, temperature=1.0, top_k=20)
251
+ out = llm.generate(["Write a Python retry wrapper with exponential backoff."], sampling)
252
+ print(out[0].outputs[0].text)
253
+ ```
254
+
255
+ Or serve with an OpenAI-compatible endpoint:
256
+
257
+ ```shell
258
+ trtllm-serve poolside/Laguna-XS-2.1 --port 8000 --trust-remote-code --tool_parser poolside_v1 --reasoning_parser laguna
259
+ ```
260
+
261
+ The Laguna XS 2.1 tool-call and reasoning parsers are built into TRT-LLM `>=1.3.0rc16` (shipped with [#13559](https://github.com/NVIDIA/TensorRT-LLM/pull/13559)), so no extra install is needed. Note that the flag names differ from vLLM's (`--tool_parser`, and the reasoning parser is `laguna`, not `poolside_v1`).
262
+
263
+ The same recipe works for the [FP8](https://huggingface.co/poolside/Laguna-XS-2.1-FP8) and [NVFP4](https://huggingface.co/poolside/Laguna-XS-2.1-NVFP4) variants: quantization is detected automatically from `quantization_config`, no extra flags required.
264
+
265
+ > [!NOTE]
266
+ > **Optional: speculative decoding with DFlash.** The [DFlash speculator](https://huggingface.co/poolside/Laguna-XS-2.1-DFlash) can be paired with Laguna XS 2.1 for lower latency. TRT-LLM support is in progress in [NVIDIA/TensorRT-LLM#15666](https://github.com/NVIDIA/TensorRT-LLM/pull/15666).
267
+
268
+ #### llama.cpp
269
+
270
+ > [!NOTE]
271
+ > Requires building llama.cpp from the upstream PR that adds Laguna XS 2.1 support until it lands ([ggml-org/llama.cpp#25165](https://github.com/ggml-org/llama.cpp/pull/25165)).
272
+
273
+ Official GGUF conversions (BF16 and Q4\_K\_M) are available at [poolside/Laguna-XS-2.1-GGUF](https://huggingface.co/poolside/Laguna-XS-2.1-GGUF).
274
+
275
+ ```shell
276
+ # Build llama.cpp from the PR branch
277
+ git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
278
+ git fetch origin pull/25165/head:laguna && git checkout laguna
279
+ cmake -B build && cmake --build build -j
280
+
281
+ # Download a GGUF and serve an OpenAI-compatible endpoint
282
+ huggingface-cli download poolside/Laguna-XS-2.1-GGUF Laguna-XS-2.1-Q4_K_M.gguf --local-dir ~/models/Laguna-XS-2.1-GGUF
283
+ ./build/bin/llama-server -m ~/models/Laguna-XS-2.1-GGUF/Laguna-XS-2.1-Q4_K_M.gguf --jinja --port 8000
284
+ ```
285
+
286
+ #### Ollama
287
+
288
+ Available on the [Ollama library](https://ollama.com/library/laguna-xs-2.1).
289
+
290
+ ollama run laguna-xs-2.1 # default — Q4_K_M (imatrix)
291
+ ollama run laguna-xs-2.1:q8_0 # higher precision
292
+ ollama run laguna-xs-2.1:bf16 # full precision
293
+
294
+ Reasoning and tool-calling work out of the box via the built-in `laguna` template.
295
+
296
+ > [!NOTE]
297
+ > **macOS (Metal) users:** Chat (`ollama run` / `/api/chat`) works as expected on Linux/CUDA. On macOS/Metal it may currently return empty output; the root cause is not yet fully understood and we're investigating it with the Ollama team. On a Mac, use a Linux/CUDA host, or the `/api/generate` endpoint with `"raw": true`.
298
+
299
+ ## Controlling reasoning
300
+
301
+ Laguna XS 2.1 has native reasoning support and is designed to work best with *preserved thinking*, where `reasoning` content from prior assistant messages is preserved in the message history. This model will generally reason before calling tools and between tool calls.
302
+
303
+ Reasoning may not be generated in follow-up steps if prior thinking blocks are dropped (i.e., thinking is not preserved) when messages are reconstructed over multiple steps.
304
+
305
+ <details>
306
+ <summary>Expand for example</summary>
307
+
308
+ ```python
309
+ import json
310
+ from openai import OpenAI
311
+
312
+ client = OpenAI(
313
+ base_url="https://openrouter.ai/api/v1",
314
+ api_key="...",
315
+ )
316
+
317
+ model = "poolside/laguna-xs-2.1"
318
+
319
+ tools = [{"type": "function", "function": {
320
+ "name": "shell",
321
+ "description": "Execute a bash command and return the output.",
322
+ "parameters": {"type": "object", "properties": {"cmd": {"type": "string"}}, "required": ["cmd"]},
323
+ }}]
324
+
325
+ messages = [
326
+ {"role": "system", "content": "You are a coding agent with access to a shell tool."},
327
+ {"role": "user", "content": "Run uname -a"},
328
+ ]
329
+
330
+ # Thinking is enabled by default when the server sets --default-chat-template-kwargs {"enable_thinking": True}
331
+ # When using OpenRouter's Chat API (https://openrouter.ai/api/v1), this flag is set by default
332
+ response = client.chat.completions.create(
333
+ model=model,
334
+ messages=messages,
335
+ tools=tools,
336
+ stream=True,
337
+ )
338
+
339
+ reasoning, content, tool_calls = "", "", []
340
+ for chunk in response:
341
+ delta = chunk.choices[0].delta
342
+ if hasattr(delta, "reasoning_content") and delta.reasoning_content:
343
+ reasoning += delta.reasoning_content
344
+ if hasattr(delta, "content") and delta.content:
345
+ content += delta.content
346
+ if hasattr(delta, "tool_calls") and delta.tool_calls:
347
+ for tc in delta.tool_calls:
348
+ if tc.index >= len(tool_calls):
349
+ tool_calls.append({"id": tc.id, "function": {"name": "", "arguments": ""}})
350
+ if tc.function.name:
351
+ tool_calls[tc.index]["function"]["name"] = tc.function.name
352
+ if tc.function.arguments:
353
+ tool_calls[tc.index]["function"]["arguments"] += tc.function.arguments
354
+
355
+ print(f"Reasoning: {reasoning}\nContent: {content}\nTool calls: {tool_calls}\n")
356
+
357
+ # Return reasoning in the next request for best performance
358
+ messages.append({
359
+ "role": "assistant",
360
+ "content": content,
361
+ "reasoning_content": reasoning,
362
+ "tool_calls": [{"id": tc["id"], "type": "function", "function": tc["function"]} for tc in tool_calls]
363
+ })
364
+
365
+ messages.append({
366
+ "role": "tool",
367
+ "tool_call_id": tool_calls[0]["id"],
368
+ "content": json.dumps({"stdout": "Darwin arm64", "exit_code": "0"})
369
+ })
370
+
371
+ response = client.chat.completions.create(
372
+ model=model,
373
+ messages=messages,
374
+ tools=tools,
375
+ stream=True,
376
+ )
377
+
378
+ reasoning, content = "", ""
379
+ for chunk in response:
380
+ delta = chunk.choices[0].delta
381
+ if hasattr(delta, "reasoning_content") and delta.reasoning_content:
382
+ reasoning += delta.reasoning_content
383
+ if hasattr(delta, "content") and delta.content:
384
+ content += delta.content
385
+
386
+ print(f"Reasoning: {reasoning}\nContent: {content}")
387
+ ```
388
+
389
+ </details>
390
+
391
+ ### Disabling reasoning
392
+
393
+ You can disable thinking by setting `enable_thinking` to `False` in a request or by not providing `--default-chat-template-kwargs {"enable_thinking": True}` or equivalent when starting the server.
394
+
395
+ <details>
396
+ <summary>Expand for example</summary>
397
+
398
+ ```python
399
+ from openai import OpenAI
400
+ client = OpenAI()
401
+
402
+ completion = client.chat.completions.create(
403
+ model="poolside/laguna-xs-2.1",
404
+ messages=[
405
+ {"role": "user", "content": "Write a retry wrapper with exponential backoff."}
406
+ ],
407
+ extra_body={
408
+ "chat_template_kwargs": { "enable_thinking": False },
409
+ },
410
+ stream=True
411
+ )
412
+
413
+ for chunk in completion:
414
+ print(chunk.choices[0].delta)
415
+ ```
416
+
417
+ </details>
418
+
419
+ For agentic coding use cases, we recommend enabling thinking and preserving reasoning in message history as outlined in the [Controlling reasoning](#controlling-reasoning) section.
420
+
421
+ ## License
422
+
423
+ This model is licensed under the [OpenMDW-1.1 License](https://huggingface.co/poolside/Laguna-XS-2.1/blob/main/LICENSE.md).
424
+
425
+ ## Intended and Responsible Use
426
+
427
+ Laguna XS 2.1 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 XS 2.1 is subject to the [OpenMDW-1.1 License](https://huggingface.co/poolside/Laguna-XS-2.1/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 XS 2.1 safety guardrails without implementing substantially equivalent mitigations appropriate for your use case.
428
+
429
+ Please report security vulnerabilities or safety concerns to [security@poolside.ai](mailto:security@poolside.ai).
chat_template.jinja ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {#- Copied from laguna_glm_thinking_v4/chat_template.jinja -#}
2
+ {#- Removes prefix that references <think> token, and replaces message.reasoning_content reference with message.reasoning -#}
3
+ {{- "〈|EOS|〉" -}}
4
+ {%- set enable_thinking = enable_thinking | default(false) -%}
5
+ {%- set render_assistant_messages_raw = render_assistant_messages_raw | default(false) -%}
6
+ {%- set add_generation_prompt = add_generation_prompt | default(false) -%}
7
+
8
+ {#- ───── header (system message) ───── -#}
9
+ {%- set system_message = "" -%}
10
+ {%- if messages and messages[0].role == "system" -%}
11
+ {%- set system_message = messages[0].content -%}
12
+ {%- endif -%}
13
+
14
+ {%- if (system_message and system_message.strip()) or tools -%}
15
+ {{- "<system>\n" -}}
16
+
17
+ {%- if system_message and system_message.strip() -%}
18
+ {{- "\n" -}}
19
+ {{- system_message.rstrip() -}}
20
+ {%- endif -%}
21
+
22
+ {%- if tools -%}
23
+ {{- "\n\n### Tools\n\n" -}}
24
+ {%- set ns = namespace(tool_string="You may call functions to assist with the user query.\n"
25
+ ~ "All available function signatures are listed below:\n"
26
+ ~ "<available_tools>\n") -%}
27
+ {%- for tool in tools -%}
28
+ {%- set ns.tool_string = ns.tool_string ~ (tool | tojson) ~ "\n" -%}
29
+ {%- endfor -%}
30
+ {%- if enable_thinking -%}
31
+ {%- set tool_string = ns.tool_string + "</available_tools>\n\n" ~
32
+ "Wrap your thinking in '<think>', '</think>' tags, followed by a function call. For each function call, return an unescaped XML-like object with function name and arguments within '<tool_call>' and '</tool_call>' tags, like here:\n" ~
33
+ "<think> your thoughts here </think>\n" ~
34
+ "<tool_call>function-name\n<arg_key>argument-key</arg_key>\n<arg_value>value-of-argument-key</arg_value>\n" ~
35
+ "</tool_call>" -%}
36
+ {%- else -%}
37
+ {%- set tool_string = ns.tool_string + "</available_tools>\n\n" ~
38
+ "For each function call, return an unescaped XML-like object " ~
39
+ "with function name and arguments within '<tool_call>' and '</tool_call>' tags, like here:\n" ~
40
+ "<tool_call>function-name\n<arg_key>argument-key</arg_key>\n<arg_value>value-of-argument-key</arg_value>\n" ~
41
+ "</tool_call>" -%}
42
+ {%- endif -%}
43
+ {{- tool_string -}}
44
+ {%- endif -%}
45
+
46
+ {{- "\n</system>\n" -}}
47
+ {%- endif -%}
48
+
49
+ {#- ───── main loop ───── -#}
50
+ {%- for message in messages -%}
51
+ {%- set content = message.content if message.content is string else "" -%}
52
+ {%- if message.role == "user" -%}
53
+ {{- "<user>\n" + content + "\n</user>\n" -}}
54
+ {%- elif message.role == "assistant" -%}
55
+ {%- generation -%}
56
+ {{- "<assistant>\n" -}}
57
+ {%- if render_assistant_messages_raw -%}
58
+ {#- Raw mode: prepend the generation prompt token, then dump content verbatim. -#}
59
+ {#- The generation prompt is <think> when enable_thinking, </think> otherwise. -#}
60
+ {#- Only prepend if content doesn't already start with it. -#}
61
+ {%- if enable_thinking -%}
62
+ {%- if not content.startswith('<think>') -%}
63
+ {{- '<think>' -}}
64
+ {%- endif -%}
65
+ {%- else -%}
66
+ {%- if not content.startswith('</think>') -%}
67
+ {{- '</think>' -}}
68
+ {%- endif -%}
69
+ {%- endif -%}
70
+ {{- content -}}
71
+ {#- Append closing tag if content doesn't already end with it. -#}
72
+ {%- if not content.endswith('</assistant>\n') and not content.endswith('</assistant>') -%}
73
+ {{- '\n</assistant>' -}}
74
+ {%- endif -%}
75
+ {{- "\n" -}}
76
+ {%- else -%}
77
+ {#- Extract reasoning content from message.reasoning (vLLM field name) or message.reasoning_content, or from <think> tags -#}
78
+ {%- set reasoning_content = '' %}
79
+ {%- if message.reasoning is string %}
80
+ {%- set reasoning_content = message.reasoning %}
81
+ {%- elif message.reasoning_content is string %}
82
+ {%- set reasoning_content = message.reasoning_content %}
83
+ {%- endif %}
84
+ {#- Always strip <think> tags from content if present to avoid duplication -#}
85
+ {%- if '</think>' in content %}
86
+ {%- if not reasoning_content %}
87
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
88
+ {%- endif %}
89
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
90
+ {%- endif %}
91
+ {#- Display reasoning content for all messages -#}
92
+ {%- if reasoning_content -%}
93
+ {{- '<think>\n' + reasoning_content.strip() + '\n</think>\n' -}}
94
+ {%- else -%}
95
+ {{- '</think>\n' -}}
96
+ {%- endif -%}
97
+ {#- Display main content -#}
98
+ {%- if content.strip() -%}
99
+ {{- content.strip() ~ "\n" -}}
100
+ {%- endif -%}
101
+ {%- if message.tool_calls -%}
102
+ {%- for tool_call in message.tool_calls -%}
103
+ {%- set function_data = tool_call.function -%}
104
+ {{- '<tool_call>' + function_data.name }}
105
+ {% set _args = function_data.arguments %}
106
+ {%- for k, v in _args.items() -%}
107
+ {{- "<arg_key>" ~ k ~ "</arg_key>\n" -}}
108
+ {{- "<arg_value>"}}{{ v | tojson(ensure_ascii=False) if v is not string else v }}{{ "</arg_value>\n" -}}
109
+ {%- endfor -%}
110
+ {{- "</tool_call>\n" -}}
111
+ {%- endfor -%}
112
+ {%- endif -%}
113
+ {{- "</assistant>\n" -}}
114
+ {%- endif -%}
115
+ {%- endgeneration -%}
116
+ {%- elif message.role == "tool" -%}
117
+ {{- "<tool_response>\n" + content + "\n</tool_response>\n" -}}
118
+ {%- elif message.role == "system" and loop.index0 != 0 -%}
119
+ {#- Render additional system messages (skip the first one which is handled separately in the header) -#}
120
+ {{- "<system>\n" + content + "\n</system>\n" -}}
121
+ {%- endif -%}
122
+ {%- endfor -%}
123
+ {#- ───── generation prompt ───── -#}
124
+ {%- if add_generation_prompt -%}
125
+ {{- "<assistant>\n" -}}
126
+ {#- ───── Include reasoning mode directive ───── -#}
127
+ {%- if not enable_thinking %}
128
+ {{- '</think>' -}}
129
+ {%- else %}
130
+ {{- '<think>' -}}
131
+ {%- endif %}
132
+ {%- endif -%}
config.json ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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": 2048,
12
+ "intermediate_size": 8192,
13
+ "num_hidden_layers": 40,
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": 8,
23
+ "moe_intermediate_size": 512,
24
+ "shared_expert_intermediate_size": 512,
25
+ "norm_topk_prob": true,
26
+ "router_aux_loss_coef": 0.0,
27
+ "decoder_sparse_step": 1,
28
+ "mlp_only_layers": [
29
+ 0
30
+ ],
31
+ "bos_token_id": 2,
32
+ "eos_token_id": [
33
+ 2,
34
+ 24
35
+ ],
36
+ "pad_token_id": 9,
37
+ "tie_word_embeddings": false,
38
+ "use_cache": true,
39
+ "torch_dtype": "bfloat16",
40
+ "gating": "per-head",
41
+ "sliding_window": 512,
42
+ "rope_parameters": {
43
+ "full_attention": {
44
+ "rope_theta": 500000.0,
45
+ "rope_type": "yarn",
46
+ "factor": 32.0,
47
+ "original_max_position_embeddings": 8192,
48
+ "beta_slow": 1.0,
49
+ "beta_fast": 64.0,
50
+ "attention_factor": 1.0,
51
+ "partial_rotary_factor": 0.5
52
+ },
53
+ "sliding_attention": {
54
+ "rope_type": "default",
55
+ "rope_theta": 10000.0,
56
+ "partial_rotary_factor": 1.0
57
+ }
58
+ },
59
+ "layer_types": [
60
+ "full_attention",
61
+ "sliding_attention",
62
+ "sliding_attention",
63
+ "sliding_attention",
64
+ "full_attention",
65
+ "sliding_attention",
66
+ "sliding_attention",
67
+ "sliding_attention",
68
+ "full_attention",
69
+ "sliding_attention",
70
+ "sliding_attention",
71
+ "sliding_attention",
72
+ "full_attention",
73
+ "sliding_attention",
74
+ "sliding_attention",
75
+ "sliding_attention",
76
+ "full_attention",
77
+ "sliding_attention",
78
+ "sliding_attention",
79
+ "sliding_attention",
80
+ "full_attention",
81
+ "sliding_attention",
82
+ "sliding_attention",
83
+ "sliding_attention",
84
+ "full_attention",
85
+ "sliding_attention",
86
+ "sliding_attention",
87
+ "sliding_attention",
88
+ "full_attention",
89
+ "sliding_attention",
90
+ "sliding_attention",
91
+ "sliding_attention",
92
+ "full_attention",
93
+ "sliding_attention",
94
+ "sliding_attention",
95
+ "sliding_attention",
96
+ "full_attention",
97
+ "sliding_attention",
98
+ "sliding_attention",
99
+ "sliding_attention"
100
+ ],
101
+ "moe_apply_router_weight_on_input": false,
102
+ "mlp_layer_types": [
103
+ "dense",
104
+ "sparse",
105
+ "sparse",
106
+ "sparse",
107
+ "sparse",
108
+ "sparse",
109
+ "sparse",
110
+ "sparse",
111
+ "sparse",
112
+ "sparse",
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
+ ],
144
+ "gating_types": [
145
+ "per_head",
146
+ "per_head",
147
+ "per_head",
148
+ "per_head",
149
+ "per_head",
150
+ "per_head",
151
+ "per_head",
152
+ "per_head",
153
+ "per_head",
154
+ "per_head",
155
+ "per_head",
156
+ "per_head",
157
+ "per_head",
158
+ "per_head",
159
+ "per_head",
160
+ "per_head",
161
+ "per_head",
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
+ ],
186
+ "moe_routed_scaling_factor": 2.5,
187
+ "num_attention_heads_per_layer": [
188
+ 48,
189
+ 64,
190
+ 64,
191
+ 64,
192
+ 48,
193
+ 64,
194
+ 64,
195
+ 64,
196
+ 48,
197
+ 64,
198
+ 64,
199
+ 64,
200
+ 48,
201
+ 64,
202
+ 64,
203
+ 64,
204
+ 48,
205
+ 64,
206
+ 64,
207
+ 64,
208
+ 48,
209
+ 64,
210
+ 64,
211
+ 64,
212
+ 48,
213
+ 64,
214
+ 64,
215
+ 64,
216
+ 48,
217
+ 64,
218
+ 64,
219
+ 64,
220
+ 48,
221
+ 64,
222
+ 64,
223
+ 64,
224
+ 48,
225
+ 64,
226
+ 64,
227
+ 64
228
+ ]
229
+ }
configuration_laguna.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Poolside and the HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from transformers.configuration_utils import PreTrainedConfig
15
+ from transformers.modeling_rope_utils import RopeParameters
16
+ from transformers.utils.import_utils import is_causal_conv1d_available, is_flash_linear_attention_available
17
+
18
+
19
+ class LagunaConfig(PreTrainedConfig):
20
+ r"""
21
+ Configuration class for Laguna model.
22
+
23
+ Laguna is Poolside's MoE architecture with:
24
+ - Attention output gating (softplus gate)
25
+ - Sigmoid routing instead of softmax
26
+ - No QKV bias
27
+ - Explicit head_dim parameter
28
+
29
+ Args:
30
+ head_dim (`int`, *optional*, defaults to 128):
31
+ Dimension of attention heads. Laguna uses explicit head_dim rather than
32
+ computing it from hidden_size // num_attention_heads.
33
+ qkv_bias (`bool`, *optional*, defaults to `False`):
34
+ Whether to add bias to QKV projections. Laguna uses no QKV bias.
35
+ attention_bias (`bool`, *optional*, defaults to `False`):
36
+ Whether to add bias to attention output projection. Laguna uses no attention bias.
37
+ gating (`bool` or `str`, *optional*, defaults to `True`):
38
+ Attention output gating mode. When ``True`` or ``"per-element"`` a g_proj
39
+ linear layer with output size ``num_attention_heads * head_dim`` is added
40
+ and ``attn_output = attn_output * softplus(g_proj(x))``. When ``"per-head"``
41
+ g_proj has output size ``num_attention_heads`` and the gate broadcasts across
42
+ ``head_dim``. When ``False`` no gating is applied.
43
+ partial_rotary_factor (`float`, *optional*):
44
+ Fraction of head_dim to apply rotary embeddings to. When set, this value is
45
+ injected into ``rope_parameters`` (and ``swa_rope_parameters``) if not already
46
+ specified there. When ``None`` the default behaviour of the rope implementation
47
+ is used (typically full rotary).
48
+ num_attention_heads_per_layer (`list[int]`, *optional*):
49
+ Optional per-layer override for ``num_attention_heads``. When provided the list
50
+ length must equal ``num_hidden_layers`` and each entry is the head count used by
51
+ that layer. When ``None`` every layer uses ``num_attention_heads``.
52
+ vocab_size (`int`, *optional*, defaults to 100352):
53
+ Vocabulary size of the Laguna model.
54
+ hidden_size (`int`, *optional*, defaults to 2048):
55
+ Dimension of the hidden representations.
56
+ intermediate_size (`int`, *optional*, defaults to 8192):
57
+ Dimension of the MLP representations for dense layers.
58
+ num_hidden_layers (`int`, *optional*, defaults to 48):
59
+ Number of hidden layers in the Transformer.
60
+ num_attention_heads (`int`, *optional*, defaults to 32):
61
+ Number of attention heads.
62
+ num_key_value_heads (`int`, *optional*, defaults to 8):
63
+ Number of key-value heads for GQA.
64
+ max_position_embeddings (`int`, *optional*, defaults to 4096):
65
+ Maximum sequence length.
66
+ rms_norm_eps (`float`, *optional*, defaults to 1e-6):
67
+ Epsilon for RMSNorm layers.
68
+ sliding_window (`int`, *optional*):
69
+ Sliding window attention size. Used by layers whose type in ``layer_types``
70
+ is ``"sliding_attention"``. When ``None``, all layers use full attention.
71
+ layer_types (`list[str]`, *optional*):
72
+ Per-layer attention type. Each element should be ``"sliding_attention"`` or
73
+ ``"full_attention"``. Length must equal ``num_hidden_layers``. When ``None``,
74
+ all layers default to global attention.
75
+ swa_attention_sink_enabled (`bool`, *optional*, defaults to `False`):
76
+ Whether to enable learnable attention sinks on sliding-window attention layers.
77
+ When enabled, a per-head bias parameter is added that allows the model to attend
78
+ to position 0 even when it falls outside the sliding window.
79
+ swa_rope_parameters (`RopeParameters`, *optional*):
80
+ Separate RoPE configuration for sliding-window attention layers. When ``None``,
81
+ SWA layers use the same RoPE as global attention layers.
82
+ num_experts (`int`, *optional*, defaults to 256):
83
+ Number of routed experts.
84
+ num_experts_per_tok (`int`, *optional*, defaults to 16):
85
+ Number of experts selected per token (top-k).
86
+ moe_intermediate_size (`int`, *optional*, defaults to 1024):
87
+ Intermediate size of routed experts.
88
+ shared_expert_intermediate_size (`int`, *optional*, defaults to 1024):
89
+ Intermediate size of the shared expert.
90
+ norm_topk_prob (`bool`, *optional*, defaults to `True`):
91
+ Whether to normalize top-k routing probabilities.
92
+ decoder_sparse_step (`int`, *optional*, defaults to 1):
93
+ Frequency of MoE layers (1 = every layer is MoE after mlp_only_layers).
94
+ mlp_only_layers (`list[int]`, *optional*, defaults to `[0]`):
95
+ Layer indices that use dense MLP instead of MoE.
96
+ router_aux_loss_coef (`float`, *optional*, defaults to 0.001):
97
+ Auxiliary loss coefficient for load balancing.
98
+ moe_routed_scaling_factor (`float`, *optional*, defaults to 1.0):
99
+ Scalar multiplier applied to the routed-expert output before combining with the
100
+ shared-expert output.
101
+ moe_apply_router_weight_on_input (`bool`, *optional*, defaults to `False`):
102
+ When ``True`` the top-k routing weights are multiplied into each expert's input
103
+ rather than its output. Matches the numerical form used by the trained checkpoint.
104
+ moe_router_logit_softcapping (`float`, *optional*, defaults to 0.0):
105
+ Optional soft-capping value ``c`` applied to router logits as
106
+ ``x = tanh(x / c) * c`` before sigmoid + top-k. Disabled when ``0``.
107
+ rope_parameters (`RopeParameters`, *optional*):
108
+ RoPE configuration. Defaults to rope_theta=500000.0.
109
+ """
110
+
111
+ model_type = "laguna"
112
+ keys_to_ignore_at_inference = ["past_key_values"]
113
+ # PreTrainedConfig in transformers v5 no longer auto-declares these; subclasses
114
+ # opt in by providing class-level annotations with defaults.
115
+ pad_token_id: int | None = None
116
+ bos_token_id: int | None = None
117
+ eos_token_id: int | list[int] | None = None
118
+ base_model_tp_plan = {
119
+ "layers.*.self_attn.q_proj": "colwise",
120
+ "layers.*.self_attn.k_proj": "colwise",
121
+ "layers.*.self_attn.v_proj": "colwise",
122
+ "layers.*.self_attn.g_proj": "colwise", # Laguna-specific gating projection
123
+ "layers.*.self_attn.o_proj": "rowwise",
124
+ "layers.*.mlp.gate_proj": "colwise",
125
+ "layers.*.mlp.up_proj": "colwise",
126
+ "layers.*.mlp.down_proj": "rowwise",
127
+ }
128
+ base_model_pp_plan = {
129
+ "embed_tokens": (["input_ids"], ["inputs_embeds"]),
130
+ "layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
131
+ "norm": (["hidden_states"], ["hidden_states"]),
132
+ }
133
+
134
+ def __init__(
135
+ self,
136
+ vocab_size: int = 100352,
137
+ hidden_size: int = 2048,
138
+ intermediate_size: int = 8192,
139
+ num_hidden_layers: int = 48,
140
+ num_attention_heads: int = 32,
141
+ num_key_value_heads: int = 8,
142
+ head_dim: int = 128,
143
+ qkv_bias: bool = False,
144
+ attention_bias: bool = False,
145
+ gating: bool | str = True,
146
+ hidden_act: str = "silu",
147
+ max_position_embeddings: int = 4096,
148
+ initializer_range: float = 0.02,
149
+ rms_norm_eps: float = 1e-6,
150
+ use_cache: bool = True,
151
+ tie_word_embeddings: bool = False,
152
+ rope_parameters: RopeParameters | dict[str, RopeParameters] | None = None,
153
+ partial_rotary_factor: float | None = None,
154
+ attention_dropout: float = 0.0,
155
+ sliding_window: int | None = None,
156
+ layer_types: list[str] | None = None,
157
+ num_attention_heads_per_layer: list[int] | None = None,
158
+ swa_attention_sink_enabled: bool = False,
159
+ swa_rope_parameters: RopeParameters | None = None,
160
+ num_experts: int = 256,
161
+ num_experts_per_tok: int = 16,
162
+ moe_intermediate_size: int = 1024,
163
+ shared_expert_intermediate_size: int = 1024,
164
+ norm_topk_prob: bool = True,
165
+ decoder_sparse_step: int = 1,
166
+ mlp_only_layers: list[int] | None = None,
167
+ router_aux_loss_coef: float = 0.001,
168
+ moe_routed_scaling_factor: float = 1.0,
169
+ moe_apply_router_weight_on_input: bool = False,
170
+ moe_router_logit_softcapping: float = 0.0,
171
+ output_router_logits: bool = False,
172
+ **kwargs,
173
+ ):
174
+ # Default mlp_only_layers: first layer is dense (moe_first_k_dense_replace=1)
175
+ if mlp_only_layers is None:
176
+ mlp_only_layers = [0]
177
+
178
+ # Default layer_types: all layers use full attention (Laguna-M). Laguna-XS
179
+ # ships an explicit list with a mix of "full_attention" and "sliding_attention".
180
+ # Downstream mask builders (``create_masks_for_generate``) iterate
181
+ # ``layer_types``, so it must be a list — not left as ``None``.
182
+ if layer_types is None:
183
+ layer_types = ["full_attention"] * num_hidden_layers
184
+
185
+ # Default rope_parameters with Laguna's theta
186
+ if rope_parameters is None:
187
+ rope_parameters = {"rope_type": "default", "rope_theta": 500000.0}
188
+
189
+ # If ``partial_rotary_factor`` is set at the top level, inject it into any
190
+ # rope dict that does not already carry one so the rotary embedding picks
191
+ # it up consistently for both full-attention and SWA layers.
192
+ if partial_rotary_factor is not None:
193
+ if isinstance(rope_parameters, dict) and "partial_rotary_factor" not in rope_parameters:
194
+ rope_parameters = {**rope_parameters, "partial_rotary_factor": partial_rotary_factor}
195
+ if (
196
+ isinstance(swa_rope_parameters, dict)
197
+ and "partial_rotary_factor" not in swa_rope_parameters
198
+ ):
199
+ swa_rope_parameters = {
200
+ **swa_rope_parameters,
201
+ "partial_rotary_factor": partial_rotary_factor,
202
+ }
203
+
204
+ self.vocab_size = vocab_size
205
+ self.hidden_size = hidden_size
206
+ self.intermediate_size = intermediate_size
207
+ self.num_hidden_layers = num_hidden_layers
208
+ self.num_attention_heads = num_attention_heads
209
+ self.num_key_value_heads = num_key_value_heads
210
+ self.head_dim = head_dim
211
+ self.qkv_bias = qkv_bias
212
+ self.attention_bias = attention_bias
213
+ self.gating = gating
214
+ self.hidden_act = hidden_act
215
+ self.max_position_embeddings = max_position_embeddings
216
+ self.initializer_range = initializer_range
217
+ self.rms_norm_eps = rms_norm_eps
218
+ self.use_cache = use_cache
219
+ self.rope_parameters = rope_parameters
220
+ self.partial_rotary_factor = partial_rotary_factor
221
+ self.attention_dropout = attention_dropout
222
+ # Sliding window attention arguments
223
+ self.sliding_window = sliding_window
224
+ self.layer_types = layer_types
225
+ self.num_attention_heads_per_layer = num_attention_heads_per_layer
226
+ self.swa_attention_sink_enabled = swa_attention_sink_enabled
227
+ self.swa_rope_parameters = swa_rope_parameters
228
+ # MoE arguments
229
+ self.num_experts = num_experts
230
+ self.num_experts_per_tok = num_experts_per_tok
231
+ self.moe_intermediate_size = moe_intermediate_size
232
+ self.shared_expert_intermediate_size = shared_expert_intermediate_size
233
+ self.norm_topk_prob = norm_topk_prob
234
+ self.decoder_sparse_step = decoder_sparse_step
235
+ self.mlp_only_layers = mlp_only_layers
236
+ self.router_aux_loss_coef = router_aux_loss_coef
237
+ self.moe_routed_scaling_factor = moe_routed_scaling_factor
238
+ self.moe_apply_router_weight_on_input = moe_apply_router_weight_on_input
239
+ self.moe_router_logit_softcapping = moe_router_logit_softcapping
240
+ self.output_router_logits = output_router_logits
241
+
242
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
243
+
244
+
245
+ __all__ = ["LagunaConfig"]
generation_config.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "min_p": 0.0,
13
+ "speculative_config": {
14
+ "method": "dflash",
15
+ "source": "huggingface",
16
+ "model": "poolside/Laguna-XS-2.1-DFlash",
17
+ "num_speculative_tokens": 15
18
+ },
19
+ "tool_call_parser": "poolside_v1",
20
+ "reasoning_parser": "poolside_v1",
21
+ "default_chat_template_kwargs": {
22
+ "enable_thinking": true
23
+ }
24
+ }
model-00001-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:86288833ce515f50a6250326f2ccb4b33a5046c17a979116fcddf9ac9cfaa8a7
3
+ size 5120041576
model-00002-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2d7cacbaa76207154ca3996aacbd396a2b0bd2cfa8a3a275f15f892210c18dba
3
+ size 5119449520
model-00003-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4ee9c12d460da29064ad5183844dcc2683d6ee994c93a1ad62ce7f713feedd2e
3
+ size 5119449504
model-00004-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c0d52fff49688679c923e0969d7647d59f502e7bcda757922b43bc48136374f5
3
+ size 5119450272
model-00005-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a12f9cbc3180b9129f435f93388f50b0d6e45414329b084402e8b5ddc9a03c5d
3
+ size 5119451824
model-00006-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f75306fb1a345440d177dfed5bb859e50e21108a5a544e891efa61ad33d094a1
3
+ size 5119451944
model-00007-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6aaeed1042b88e4dadd162b544c2bc0931b006f64f0e0bd5faf1412a57a021c4
3
+ size 5119451960
model-00008-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:09854eebb5b314166dfc21851ce83a28c204233692577127d1fe9eee0ac48f45
3
+ size 5119451960
model-00009-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3ffe57522141e492885b6528c6672b3ed9494a4f39e2b701e9c68167b9fe50d
3
+ size 5119451872
model-00010-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:17ca972076637f41147ed55401c2c348c5ed9ecb0370c94dabd604525cd14cf5
3
+ size 5119451824
model-00011-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9093a9821cc0bf03001af1150ec4e9edf47b5157e490c9247cd4faaa6018ff8e
3
+ size 5119451856
model-00012-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0f262af6eb5807868265a15598e64a372fcda471b6bb90355b1da4fffd7e6b57
3
+ size 5119451960
model-00013-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3aff2437362a1830c667eddb43e0e66dfa1765b4b1cc96b7e4aeb137b6faea11
3
+ size 5119451960
model-00014-of-00014.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:732c167b2fda5985a6046556eb454b91de7bdafe49a4c38700a92a2d929bd658
3
+ size 335563984
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
modeling_laguna.py ADDED
@@ -0,0 +1,879 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Poolside and the HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from collections.abc import Callable
16
+
17
+ import torch
18
+ import torch.nn.functional as F
19
+ from torch import nn
20
+
21
+ from transformers.activations import ACT2FN
22
+ from transformers.cache_utils import Cache
23
+ from transformers.integrations import use_experts_implementation, use_kernelized_func
24
+ from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
25
+ from transformers.modeling_layers import GradientCheckpointingLayer
26
+ from transformers.modeling_outputs import MoeModelOutputWithPast
27
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
28
+ from transformers.processing_utils import Unpack
29
+ from transformers.utils import auto_docstring, can_return_tuple, is_grouped_mm_available
30
+ from transformers.utils.generic import TransformersKwargs, merge_with_config_defaults
31
+ from transformers.utils.output_capturing import OutputRecorder, capture_outputs
32
+ from transformers.cache_utils import DynamicCache
33
+ from transformers.generation import GenerationMixin
34
+ from transformers.integrations import use_kernel_forward_from_hub
35
+ from transformers.masking_utils import create_causal_mask
36
+ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
37
+ from transformers.modeling_utils import PreTrainedModel
38
+ from transformers.utils.generic import maybe_autocast
39
+ from .configuration_laguna import LagunaConfig
40
+
41
+ from transformers import initialization as init
42
+ from transformers.masking_utils import create_sliding_window_causal_mask
43
+ from transformers.modeling_outputs import MoeCausalLMOutputWithPast
44
+ from transformers.utils.import_utils import is_causal_conv1d_available, is_flash_linear_attention_available
45
+
46
+
47
+ @use_kernel_forward_from_hub("RMSNorm")
48
+ class LagunaRMSNorm(nn.Module):
49
+ def __init__(self, hidden_size, eps: float = 1e-6) -> None:
50
+ """
51
+ LagunaRMSNorm is equivalent to T5LayerNorm
52
+ """
53
+ super().__init__()
54
+ self.weight = nn.Parameter(torch.ones(hidden_size))
55
+ self.variance_epsilon = eps
56
+
57
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
58
+ input_dtype = hidden_states.dtype
59
+ hidden_states = hidden_states.to(torch.float32)
60
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
61
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
62
+ return self.weight * hidden_states.to(input_dtype)
63
+
64
+ def extra_repr(self):
65
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
66
+
67
+
68
+ class LagunaRotaryEmbedding(nn.Module):
69
+ inv_freq: torch.Tensor # fix linting for `register_buffer`
70
+
71
+ def __init__(self, config: LagunaConfig, device=None):
72
+ super().__init__()
73
+ self.max_seq_len_cached = config.max_position_embeddings
74
+ self.original_max_seq_len = config.max_position_embeddings
75
+
76
+ self.config = config
77
+
78
+ self.rope_type = self.config.rope_parameters["rope_type"]
79
+ rope_init_fn: Callable = self.compute_default_rope_parameters
80
+ if self.rope_type != "default":
81
+ rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
82
+ inv_freq, self.attention_scaling = rope_init_fn(self.config, device)
83
+
84
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
85
+ self.register_buffer("original_inv_freq", inv_freq.clone(), persistent=False)
86
+
87
+ @staticmethod
88
+ def compute_default_rope_parameters(
89
+ 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 = (
105
+ getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
106
+ )
107
+ partial = config.rope_parameters.get("partial_rotary_factor", 1.0)
108
+ dim = int(head_dim * partial)
109
+ inv_freq = 1.0 / (
110
+ base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim)
111
+ )
112
+ return inv_freq, 1.0
113
+
114
+ @torch.no_grad()
115
+ @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
116
+ def forward(self, x, position_ids):
117
+ inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
118
+ position_ids_expanded = position_ids[:, None, :].float()
119
+
120
+ device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
121
+ with maybe_autocast(device_type=device_type, enabled=False): # Force float32
122
+ freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
123
+ emb = torch.cat((freqs, freqs), dim=-1)
124
+ cos = emb.cos() * self.attention_scaling
125
+ sin = emb.sin() * self.attention_scaling
126
+
127
+ return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
128
+
129
+
130
+ class LagunaMLP(nn.Module):
131
+ def __init__(self, config, intermediate_size=None):
132
+ super().__init__()
133
+ self.config = config
134
+ self.hidden_size = config.hidden_size
135
+ self.intermediate_size = config.intermediate_size if intermediate_size is None else intermediate_size
136
+ self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
137
+ self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
138
+ self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
139
+ self.act_fn = ACT2FN[config.hidden_act]
140
+
141
+ def forward(self, x):
142
+ down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
143
+ return down_proj
144
+
145
+
146
+ class LagunaTopKRouter(nn.Module):
147
+ """Laguna MoE router using sigmoid scoring (not softmax).
148
+
149
+ Supports optional router-logit soft-capping and auxiliary-loss-free load
150
+ balancing (arXiv:2408.15664): the per-expert bias ``e_score_correction_bias``
151
+ is added to selection scores but the returned routing weights remain unbiased.
152
+ The bias lives on the router so accelerate's per-module hooks can co-locate it
153
+ with the gate — moving it to the experts module would cross a hook boundary
154
+ and leave the bias on meta under ``device_map="auto"`` / CPU-offload.
155
+ """
156
+ def __init__(self, config):
157
+ super().__init__()
158
+ self.top_k = config.num_experts_per_tok
159
+ self.num_experts = config.num_experts
160
+ self.norm_topk_prob = config.norm_topk_prob
161
+ self.hidden_dim = config.hidden_size
162
+ self.weight = nn.Parameter(torch.zeros(self.num_experts, self.hidden_dim))
163
+ # Zero-initialised so inference on checkpoints that don't ship the bias
164
+ # is a no-op. ``_checkpoint_conversion_mapping`` below remaps the
165
+ # ``mlp.experts.e_score_correction_bias`` key from vLLM-trained
166
+ # checkpoints onto this attribute.
167
+ self.e_score_correction_bias = nn.Parameter(
168
+ torch.zeros(config.num_experts), requires_grad=False
169
+ )
170
+ self.router_logit_softcapping = float(
171
+ getattr(config, "moe_router_logit_softcapping", 0.0) or 0.0
172
+ )
173
+
174
+ def forward(self,
175
+ hidden_states: torch.Tensor,
176
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
177
+ hidden_states = hidden_states.reshape(-1, self.hidden_dim)
178
+ router_logits = F.linear(hidden_states, self.weight).float()
179
+ if self.router_logit_softcapping > 0.0:
180
+ router_logits = (
181
+ torch.tanh(router_logits / self.router_logit_softcapping) * self.router_logit_softcapping
182
+ )
183
+ routing_scores = torch.sigmoid(router_logits)
184
+ scores_for_selection = routing_scores + self.e_score_correction_bias.to(routing_scores.dtype)
185
+ _, selected_experts = torch.topk(scores_for_selection, self.top_k, dim=-1)
186
+ routing_weights = routing_scores.gather(-1, selected_experts)
187
+ if self.norm_topk_prob:
188
+ routing_weights = routing_weights / routing_weights.sum(dim=-1, keepdim=True)
189
+ routing_weights = routing_weights.to(hidden_states.dtype)
190
+ return router_logits, routing_weights, selected_experts
191
+
192
+
193
+ @use_experts_implementation
194
+ class LagunaExperts(nn.Module):
195
+ """Fused expert weights as 3D tensors for batched execution."""
196
+
197
+ def __init__(self, config):
198
+ super().__init__()
199
+ self.num_experts = config.num_experts
200
+ self.hidden_dim = config.hidden_size
201
+ self.intermediate_dim = config.moe_intermediate_size
202
+ self.gate_up_proj = nn.Parameter(torch.empty(self.num_experts, 2 * self.intermediate_dim, self.hidden_dim))
203
+ self.down_proj = nn.Parameter(torch.empty(self.num_experts, self.hidden_dim, self.intermediate_dim))
204
+ self.act_fn = ACT2FN[config.hidden_act]
205
+
206
+ def forward(
207
+ self,
208
+ hidden_states: torch.Tensor,
209
+ top_k_index: torch.Tensor,
210
+ top_k_weights: torch.Tensor,
211
+ ) -> torch.Tensor:
212
+ final_hidden_states = torch.zeros_like(hidden_states)
213
+ with torch.no_grad():
214
+ expert_mask = F.one_hot(top_k_index, num_classes=self.num_experts)
215
+ expert_mask = expert_mask.permute(2, 1, 0)
216
+ expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero()
217
+
218
+ for expert_idx in expert_hit:
219
+ expert_idx = expert_idx[0]
220
+ if expert_idx == self.num_experts:
221
+ continue
222
+ top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
223
+ current_state = hidden_states[token_idx]
224
+ gate, up = F.linear(current_state, self.gate_up_proj[expert_idx]).chunk(2, dim=-1)
225
+ current_hidden_states = self.act_fn(gate) * up
226
+ current_hidden_states = F.linear(current_hidden_states, self.down_proj[expert_idx])
227
+ current_hidden_states = current_hidden_states * top_k_weights[token_idx, top_k_pos, None]
228
+ final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype))
229
+
230
+ return final_hidden_states
231
+
232
+
233
+ class LagunaSparseMoeBlock(nn.Module):
234
+ """Laguna MoE block using sigmoid router, fused expert tensors, and a shared expert."""
235
+
236
+ def __init__(self, config):
237
+ super().__init__()
238
+ self.num_experts = config.num_experts
239
+ self.routed_scaling_factor = float(getattr(config, "moe_routed_scaling_factor", 1.0))
240
+ # ``moe_apply_router_weight_on_input=True`` would require scaling each expert's
241
+ # input (rather than its output) by the routing weight. Supporting it cleanly
242
+ # alongside the fused experts kernels (``grouped_mm`` / ``batched_mm``) is future
243
+ # work; for now we fail loudly so a checkpoint that needs it can't silently
244
+ # diverge from its numerical form.
245
+ if getattr(config, "moe_apply_router_weight_on_input", False):
246
+ raise NotImplementedError(
247
+ "moe_apply_router_weight_on_input=True is not yet supported in the "
248
+ "transformers implementation of Laguna."
249
+ )
250
+ self.gate = LagunaTopKRouter(config)
251
+ self.experts = LagunaExperts(config)
252
+ self.shared_expert = LagunaMLP(config, intermediate_size=config.shared_expert_intermediate_size)
253
+
254
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
255
+ batch_size, sequence_length, hidden_dim = hidden_states.shape
256
+ hidden_states = hidden_states.view(-1, hidden_dim)
257
+
258
+ shared_expert_output = self.shared_expert(hidden_states)
259
+ _, routing_weights, selected_experts = self.gate(hidden_states)
260
+ expert_output = self.experts(hidden_states, selected_experts, routing_weights)
261
+ if self.routed_scaling_factor != 1.0:
262
+ expert_output = expert_output * self.routed_scaling_factor
263
+
264
+ expert_output = expert_output + shared_expert_output
265
+ expert_output = expert_output.reshape(batch_size, sequence_length, hidden_dim)
266
+ return expert_output
267
+
268
+
269
+ def rotate_half(x):
270
+ """Rotates half the hidden dims of the input."""
271
+ x1 = x[..., : x.shape[-1] // 2]
272
+ x2 = x[..., x.shape[-1] // 2 :]
273
+ return torch.cat((-x2, x1), dim=-1)
274
+
275
+
276
+ # Adapted from transformers.models.glm.modular_glm.apply_rotary_pos_emb
277
+ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
278
+ """Applies Rotary Position Embedding to the query and key tensors.
279
+
280
+ Removes the interleaving of cos and sin from GLM
281
+
282
+ Args:
283
+ q (`torch.Tensor`): The query tensor.
284
+ k (`torch.Tensor`): The key tensor.
285
+ cos (`torch.Tensor`): The cosine part of the rotary embedding.
286
+ sin (`torch.Tensor`): The sine part of the rotary embedding.
287
+ unsqueeze_dim (`int`, *optional*, defaults to 1):
288
+ The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
289
+ sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
290
+ that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
291
+ k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
292
+ cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
293
+ the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
294
+ Returns:
295
+ `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
296
+ """
297
+ cos = cos.unsqueeze(unsqueeze_dim)
298
+ sin = sin.unsqueeze(unsqueeze_dim)
299
+
300
+ # Keep half or full tensor for later concatenation
301
+ rotary_dim = cos.shape[-1]
302
+ q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
303
+ k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
304
+
305
+ # Apply rotary embeddings on the first half or full tensor
306
+ q_embed = (q_rot * cos) + (rotate_half(q_rot) * sin)
307
+ k_embed = (k_rot * cos) + (rotate_half(k_rot) * sin)
308
+
309
+ # Concatenate back to full shape
310
+ q_embed = torch.cat([q_embed, q_pass], dim=-1)
311
+ k_embed = torch.cat([k_embed, k_pass], dim=-1)
312
+ return q_embed, k_embed
313
+
314
+
315
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
316
+ """
317
+ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
318
+ num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
319
+ """
320
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
321
+ if n_rep == 1:
322
+ return hidden_states
323
+ hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
324
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
325
+
326
+
327
+ def eager_attention_forward(
328
+ module: nn.Module,
329
+ query: torch.Tensor,
330
+ key: torch.Tensor,
331
+ value: torch.Tensor,
332
+ attention_mask: torch.Tensor | None,
333
+ scaling: float,
334
+ dropout: float = 0.0,
335
+ **kwargs: Unpack[TransformersKwargs],
336
+ ):
337
+ key_states = repeat_kv(key, module.num_key_value_groups)
338
+ value_states = repeat_kv(value, module.num_key_value_groups)
339
+
340
+ attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
341
+ if attention_mask is not None:
342
+ attn_weights = attn_weights + attention_mask
343
+
344
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
345
+ attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
346
+ attn_output = torch.matmul(attn_weights, value_states)
347
+ attn_output = attn_output.transpose(1, 2).contiguous()
348
+
349
+ return attn_output, attn_weights
350
+
351
+
352
+ # Laguna attention is identical to Qwen2MoE attention except:
353
+ # - No QKV bias
354
+ # - Explicit head_dim from config
355
+ # - Output gating: attn_output = attn_output * softplus(g_proj(hidden_states)) (optional)
356
+ # - Per-layer sliding window attention with optional attention sinks
357
+ @use_kernelized_func(apply_rotary_pos_emb)
358
+ class LagunaAttention(nn.Module):
359
+ def __init__(self, config: LagunaConfig, layer_idx: int, num_heads: int | None = None):
360
+ super().__init__()
361
+ self.config = config
362
+ self.layer_idx = layer_idx
363
+ self.head_dim = config.head_dim
364
+ # Allow the caller (decoder layer) to supply a per-layer head count; fall back
365
+ # to config.num_attention_heads when not provided.
366
+ self.num_heads = num_heads if num_heads is not None else config.num_attention_heads
367
+ self.num_key_value_groups = self.num_heads // config.num_key_value_heads
368
+ self.scaling = self.head_dim**-0.5
369
+ self.attention_dropout = config.attention_dropout
370
+ self.is_causal = True
371
+
372
+ # Per-layer sliding window (follows Gemma2/Cohere2 convention)
373
+ layer_types = getattr(config, "layer_types", None)
374
+ if layer_types is not None:
375
+ self.is_sliding = layer_types[layer_idx] == "sliding_attention"
376
+ self.sliding_window = config.sliding_window if self.is_sliding else None
377
+ else:
378
+ self.is_sliding = False
379
+ self.sliding_window = None
380
+
381
+ # Laguna: no QKV bias, explicit head_dim
382
+ self.q_proj = nn.Linear(config.hidden_size, self.num_heads * config.head_dim, bias=False)
383
+ self.k_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * config.head_dim, bias=False)
384
+ self.v_proj = nn.Linear(config.hidden_size, config.num_key_value_heads * config.head_dim, bias=False)
385
+ self.o_proj = nn.Linear(self.num_heads * config.head_dim, config.hidden_size, bias=False)
386
+
387
+ # Laguna-specific: optional gating projection.
388
+ # ``gating`` may be:
389
+ # - True / "per-element": one gate per (head, head_dim) channel
390
+ # - "per-head": one gate per head, broadcast across head_dim
391
+ # - False: no gating
392
+ gating = getattr(config, "gating", True)
393
+ self.gating = bool(gating)
394
+ self.gate_per_head = gating == "per-head"
395
+ if self.gating:
396
+ g_out = self.num_heads if self.gate_per_head else self.num_heads * config.head_dim
397
+ self.g_proj = nn.Linear(config.hidden_size, g_out, bias=False)
398
+
399
+ # Attention sinks (learnable per-head bias for SWA layers)
400
+ if self.is_sliding and getattr(config, "swa_attention_sink_enabled", False):
401
+ self.sink = nn.Parameter(torch.zeros(self.num_heads))
402
+
403
+ # QK normalization (RMSNorm applied per-head after reshape, before RoPE)
404
+ self.q_norm = LagunaRMSNorm(config.head_dim, eps=config.rms_norm_eps)
405
+ self.k_norm = LagunaRMSNorm(config.head_dim, eps=config.rms_norm_eps)
406
+
407
+ def forward(
408
+ self,
409
+ hidden_states: torch.Tensor,
410
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
411
+ attention_mask: torch.Tensor | None,
412
+ past_key_values: Cache | None = None,
413
+ **kwargs: Unpack[FlashAttentionKwargs],
414
+ ) -> tuple[torch.Tensor, torch.Tensor | None]:
415
+ input_shape = hidden_states.shape[:-1]
416
+ hidden_shape = (*input_shape, -1, self.head_dim)
417
+
418
+ query_states = self.q_proj(hidden_states)
419
+ key_states = self.k_proj(hidden_states)
420
+ value_states = self.v_proj(hidden_states)
421
+
422
+ query_states = query_states.view(hidden_shape).transpose(1, 2)
423
+ key_states = key_states.view(hidden_shape).transpose(1, 2)
424
+ value_states = value_states.view(hidden_shape).transpose(1, 2)
425
+
426
+ # QK normalization (applied per-head before RoPE)
427
+ query_states = self.q_norm(query_states)
428
+ key_states = self.k_norm(key_states)
429
+
430
+ cos, sin = position_embeddings
431
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
432
+
433
+ if past_key_values is not None:
434
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx)
435
+
436
+ # ``attention_mask`` here is already the correct mask for this layer type —
437
+ # ``LagunaModel.forward`` builds separate full-attention and sliding-attention
438
+ # masks (using ``create_causal_mask`` / ``create_sliding_window_causal_mask``)
439
+ # and the decoder layer passes the right one in.
440
+ attention_interface: Callable = eager_attention_forward
441
+ if self.config._attn_implementation != "eager":
442
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
443
+
444
+ attn_output, attn_weights = attention_interface(
445
+ self,
446
+ query_states,
447
+ key_states,
448
+ value_states,
449
+ attention_mask,
450
+ dropout=0.0 if not self.training else self.attention_dropout,
451
+ scaling=self.scaling,
452
+ **kwargs,
453
+ )
454
+
455
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
456
+
457
+ # Laguna-specific: apply gating BEFORE o_proj (optional)
458
+ if self.gating:
459
+ gate = F.softplus(self.g_proj(hidden_states).float()).to(attn_output.dtype)
460
+ if self.gate_per_head:
461
+ # gate: [..., num_heads]; broadcast across head_dim
462
+ attn_shape = attn_output.shape
463
+ attn_output = (
464
+ attn_output.view(*attn_shape[:-1], self.num_heads, self.head_dim)
465
+ * gate.unsqueeze(-1)
466
+ ).view(attn_shape)
467
+ else:
468
+ attn_output = attn_output * gate
469
+
470
+ attn_output = self.o_proj(attn_output)
471
+
472
+ return attn_output, attn_weights
473
+
474
+ class LagunaDecoderLayer(GradientCheckpointingLayer):
475
+ """Laguna decoder layer with gated attention and sigmoid-routed MoE."""
476
+
477
+ def __init__(self, config: LagunaConfig, layer_idx: int):
478
+ super().__init__()
479
+ per_layer_heads = getattr(config, "num_attention_heads_per_layer", None)
480
+ layer_num_heads = (
481
+ per_layer_heads[layer_idx] if per_layer_heads is not None else config.num_attention_heads
482
+ )
483
+ # Layer type drives mask and position-embedding dispatch in ``LagunaModel.forward``.
484
+ layer_types = getattr(config, "layer_types", None)
485
+ self.attention_type = layer_types[layer_idx] if layer_types is not None else "full_attention"
486
+ self.self_attn = LagunaAttention(config, layer_idx, num_heads=layer_num_heads)
487
+ # Use MoE or dense MLP based on layer configuration
488
+ if (layer_idx not in config.mlp_only_layers) and (
489
+ config.num_experts > 0 and (layer_idx + 1) % config.decoder_sparse_step == 0
490
+ ):
491
+ self.mlp = LagunaSparseMoeBlock(config)
492
+ else:
493
+ self.mlp = LagunaMLP(config, intermediate_size=config.intermediate_size)
494
+ self.input_layernorm = LagunaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
495
+ self.post_attention_layernorm = LagunaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
496
+ self.hidden_size = config.hidden_size
497
+
498
+ def forward(
499
+ self,
500
+ hidden_states: torch.Tensor,
501
+ attention_mask: torch.Tensor | None = None,
502
+ position_ids: torch.LongTensor | None = None,
503
+ past_key_values: Cache | None = None,
504
+ use_cache: bool | None = False,
505
+ position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
506
+ **kwargs: Unpack[TransformersKwargs],
507
+ ) -> torch.Tensor:
508
+ residual = hidden_states
509
+ hidden_states = self.input_layernorm(hidden_states)
510
+ # Self Attention
511
+ hidden_states, _ = self.self_attn(
512
+ hidden_states=hidden_states,
513
+ attention_mask=attention_mask,
514
+ position_ids=position_ids,
515
+ past_key_values=past_key_values,
516
+ use_cache=use_cache,
517
+ position_embeddings=position_embeddings,
518
+ **kwargs,
519
+ )
520
+ hidden_states = residual + hidden_states
521
+
522
+ # Fully Connected
523
+ residual = hidden_states
524
+ hidden_states = self.post_attention_layernorm(hidden_states)
525
+ hidden_states = self.mlp(hidden_states)
526
+ hidden_states = residual + hidden_states
527
+ return hidden_states
528
+
529
+
530
+ @auto_docstring
531
+ class LagunaPreTrainedModel(PreTrainedModel):
532
+ config: LagunaConfig
533
+ base_model_prefix = "model"
534
+ supports_gradient_checkpointing = True
535
+ _no_split_modules = ["LagunaDecoderLayer"]
536
+ _skip_keys_device_placement = ["past_key_values"]
537
+ _supports_flash_attn = True
538
+ _supports_sdpa = True
539
+ _supports_flex_attn = True
540
+ _can_compile_fullgraph = (
541
+ is_grouped_mm_available()
542
+ ) # https://huggingface.co/docs/transformers/experts_interface#torchcompile
543
+ _supports_attention_backend = True
544
+ _can_record_outputs = {
545
+ "router_logits": OutputRecorder(LagunaTopKRouter, index=0),
546
+ "hidden_states": LagunaDecoderLayer,
547
+ "attentions": LagunaAttention,
548
+ }
549
+ # vLLM-trained Laguna checkpoints store the aux-loss-free routing bias on the
550
+ # experts module (``mlp.experts.e_score_correction_bias``). In this impl the
551
+ # bias lives on the router to stay co-located with its consumer across
552
+ # accelerate's per-module hooks, so remap the legacy key on load.
553
+ _checkpoint_conversion_mapping = {
554
+ r"^(.*)\.mlp\.experts\.e_score_correction_bias$": r"\1.mlp.gate.e_score_correction_bias",
555
+ }
556
+
557
+ @torch.no_grad()
558
+ def _init_weights(self, module):
559
+ super()._init_weights(module)
560
+ std = self.config.initializer_range
561
+ if isinstance(module, LagunaExperts):
562
+ init.normal_(module.gate_up_proj, mean=0.0, std=std)
563
+ init.normal_(module.down_proj, mean=0.0, std=std)
564
+ elif isinstance(module, LagunaTopKRouter):
565
+ init.normal_(module.weight, mean=0.0, std=std)
566
+ # Bare ``nn.Parameter``s that are not covered by the parent's generic
567
+ # Linear/Embedding/norm handling need their own rules so that the
568
+ # __init__ and from_pretrained(state_dict={}) paths produce identical
569
+ # weights under a fixed seed.
570
+ if isinstance(module, LagunaTopKRouter):
571
+ torch.nn.init.zeros_(module.e_score_correction_bias)
572
+ if isinstance(module, LagunaAttention) and hasattr(module, "sink"):
573
+ torch.nn.init.zeros_(module.sink)
574
+
575
+
576
+ class LagunaModel(LagunaPreTrainedModel):
577
+ def __init__(self, config: LagunaConfig):
578
+ super().__init__(config)
579
+ self.padding_idx = config.pad_token_id
580
+ self.vocab_size = config.vocab_size
581
+
582
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
583
+ self.layers = nn.ModuleList(
584
+ [LagunaDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
585
+ )
586
+ self.norm = LagunaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
587
+
588
+ # ``LagunaRotaryEmbedding`` inherits ``Qwen2MoeRotaryEmbedding``'s flat-shape
589
+ # contract — it reads ``config.rope_parameters["rope_type"]`` at the outer
590
+ # level. Laguna stores rope nested by layer type (``{"full_attention": {...},
591
+ # ...}``), so pass a config clone with the full-attention sub-dict flattened.
592
+ rp = getattr(config, "rope_parameters", None)
593
+ if isinstance(rp, dict) and isinstance(rp.get("full_attention"), dict):
594
+ import copy
595
+ full_config = copy.deepcopy(config)
596
+ full_config.rope_parameters = dict(rp["full_attention"])
597
+ self.rotary_emb = LagunaRotaryEmbedding(config=full_config)
598
+ else:
599
+ self.rotary_emb = LagunaRotaryEmbedding(config=config)
600
+
601
+ # Separate RoPE for sliding-window attention layers (when configured).
602
+ # Be careful with ``partial_rotary_factor`` — ``PreTrainedConfig.standardize_rope_params``
603
+ # unconditionally overwrites ``rope_parameters["partial_rotary_factor"]`` with
604
+ # ``self.partial_rotary_factor``, so we must align the top-level field on the
605
+ # cloned config to the SWA value, otherwise the global partial factor silently
606
+ # clobbers the SWA one.
607
+ if getattr(config, "swa_rope_parameters", None) is not None:
608
+ import copy
609
+
610
+ swa_config = copy.deepcopy(config)
611
+ swa_config.rope_parameters = dict(config.swa_rope_parameters)
612
+ swa_partial = swa_config.rope_parameters.get("partial_rotary_factor")
613
+ swa_config.partial_rotary_factor = swa_partial
614
+ self.swa_rotary_emb = LagunaRotaryEmbedding(config=swa_config)
615
+ else:
616
+ self.swa_rotary_emb = None
617
+
618
+ self.gradient_checkpointing = False
619
+
620
+ # Initialize weights and apply final processing
621
+ self.post_init()
622
+
623
+ @merge_with_config_defaults
624
+ @capture_outputs
625
+ @auto_docstring
626
+ def forward(
627
+ self,
628
+ input_ids: torch.LongTensor | None = None,
629
+ attention_mask: torch.Tensor | None = None,
630
+ position_ids: torch.LongTensor | None = None,
631
+ past_key_values: Cache | None = None,
632
+ inputs_embeds: torch.FloatTensor | None = None,
633
+ use_cache: bool | None = None,
634
+ **kwargs: Unpack[TransformersKwargs],
635
+ ) -> MoeModelOutputWithPast:
636
+ from ...cache_utils import DynamicCache
637
+ from ...masking_utils import create_causal_mask, create_sliding_window_causal_mask
638
+
639
+ if (input_ids is None) ^ (inputs_embeds is not None):
640
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
641
+
642
+ if inputs_embeds is None:
643
+ inputs_embeds = self.embed_tokens(input_ids)
644
+
645
+ if use_cache and past_key_values is None:
646
+ past_key_values = DynamicCache(config=self.config)
647
+
648
+ if position_ids is None:
649
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
650
+ position_ids = (
651
+ torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens
652
+ ).unsqueeze(0)
653
+
654
+ # Build one mask per layer-type so each layer can be dispatched with the right
655
+ # attention pattern (follows the afmoe / cohere2 v5 convention).
656
+ layer_types = getattr(self.config, "layer_types", None)
657
+ has_swa = layer_types is not None and "sliding_attention" in layer_types
658
+ if not isinstance(causal_mask_mapping := attention_mask, dict):
659
+ mask_kwargs = {
660
+ "config": self.config,
661
+ "inputs_embeds": inputs_embeds,
662
+ "attention_mask": attention_mask,
663
+ "past_key_values": past_key_values,
664
+ "position_ids": position_ids,
665
+ }
666
+ causal_mask_mapping = {"full_attention": create_causal_mask(**mask_kwargs)}
667
+ if has_swa:
668
+ causal_mask_mapping["sliding_attention"] = create_sliding_window_causal_mask(**mask_kwargs)
669
+
670
+ hidden_states = inputs_embeds
671
+ global_pe = self.rotary_emb(hidden_states, position_ids)
672
+ # Per-layer-type position embeddings: Laguna optionally uses a different rope for
673
+ # sliding layers (``swa_rope_parameters``). When absent, SWA layers share the
674
+ # global rope.
675
+ if has_swa:
676
+ swa_pe = (
677
+ self.swa_rotary_emb(hidden_states, position_ids)
678
+ if self.swa_rotary_emb is not None
679
+ else global_pe
680
+ )
681
+ position_embeddings_mapping = {"full_attention": global_pe, "sliding_attention": swa_pe}
682
+ else:
683
+ position_embeddings_mapping = None
684
+
685
+ for decoder_layer in self.layers[: self.config.num_hidden_layers]:
686
+ layer_attn_mask = causal_mask_mapping[decoder_layer.attention_type]
687
+ layer_pos_emb = (
688
+ position_embeddings_mapping[decoder_layer.attention_type]
689
+ if position_embeddings_mapping is not None
690
+ else global_pe
691
+ )
692
+ hidden_states = decoder_layer(
693
+ hidden_states,
694
+ attention_mask=layer_attn_mask,
695
+ position_ids=position_ids,
696
+ past_key_values=past_key_values,
697
+ use_cache=use_cache,
698
+ position_embeddings=layer_pos_emb,
699
+ **kwargs,
700
+ )
701
+
702
+ hidden_states = self.norm(hidden_states)
703
+
704
+ return MoeModelOutputWithPast(
705
+ last_hidden_state=hidden_states,
706
+ past_key_values=past_key_values,
707
+ )
708
+
709
+
710
+ def load_balancing_loss_func(
711
+ gate_logits: torch.Tensor | tuple[torch.Tensor] | None,
712
+ num_experts: int | None = None,
713
+ top_k=2,
714
+ attention_mask: torch.Tensor | None = None,
715
+ ) -> torch.Tensor | int:
716
+ r"""
717
+ Computes auxiliary load balancing loss as in Switch Transformer - implemented in Pytorch.
718
+
719
+ See Switch Transformer (https://huggingface.co/papers/2101.03961) for more details. This function implements the loss
720
+ function presented in equations (4) - (6) of the paper. It aims at penalizing cases where the routing between
721
+ experts is too unbalanced.
722
+
723
+ Args:
724
+ gate_logits:
725
+ Logits from the `gate`, should be a tuple of model.config.num_hidden_layers tensors of
726
+ shape [batch_size X sequence_length, num_experts].
727
+ num_experts:
728
+ Number of experts
729
+ top_k:
730
+ The number of experts to route per-token, can be also interpreted as the `top-k` routing
731
+ parameter.
732
+ attention_mask (`torch.Tensor`, *optional*):
733
+ The attention_mask used in forward function
734
+ shape [batch_size X sequence_length] if not None.
735
+
736
+ Returns:
737
+ The auxiliary loss.
738
+ """
739
+ if gate_logits is None or not isinstance(gate_logits, tuple):
740
+ return 0
741
+
742
+ if isinstance(gate_logits, tuple):
743
+ compute_device = gate_logits[0].device
744
+ concatenated_gate_logits = torch.cat([layer_gate.to(compute_device) for layer_gate in gate_logits], dim=0)
745
+
746
+ routing_weights = torch.nn.functional.softmax(concatenated_gate_logits, dim=-1)
747
+
748
+ _, selected_experts = torch.topk(routing_weights, top_k, dim=-1)
749
+
750
+ expert_mask = torch.nn.functional.one_hot(selected_experts, num_experts)
751
+
752
+ if attention_mask is None:
753
+ # Compute the percentage of tokens routed to each experts
754
+ tokens_per_expert = torch.mean(expert_mask.float(), dim=0)
755
+
756
+ # Compute the average probability of routing to these experts
757
+ router_prob_per_expert = torch.mean(routing_weights, dim=0)
758
+ else:
759
+ batch_size, sequence_length = attention_mask.shape
760
+ num_hidden_layers = concatenated_gate_logits.shape[0] // (batch_size * sequence_length)
761
+
762
+ # Compute the mask that masks all padding tokens as 0 with the same shape of expert_mask
763
+ expert_attention_mask = (
764
+ attention_mask[None, :, :, None, None]
765
+ .expand((num_hidden_layers, batch_size, sequence_length, top_k, num_experts))
766
+ .reshape(-1, top_k, num_experts)
767
+ .to(compute_device)
768
+ )
769
+
770
+ # Compute the percentage of tokens routed to each experts
771
+ tokens_per_expert = torch.sum(expert_mask.float() * expert_attention_mask, dim=0) / torch.sum(
772
+ expert_attention_mask, dim=0
773
+ )
774
+
775
+ # Compute the mask that masks all padding tokens as 0 with the same shape of tokens_per_expert
776
+ router_per_expert_attention_mask = (
777
+ attention_mask[None, :, :, None]
778
+ .expand((num_hidden_layers, batch_size, sequence_length, num_experts))
779
+ .reshape(-1, num_experts)
780
+ .to(compute_device)
781
+ )
782
+
783
+ # Compute the average probability of routing to these experts
784
+ router_prob_per_expert = torch.sum(routing_weights * router_per_expert_attention_mask, dim=0) / torch.sum(
785
+ router_per_expert_attention_mask, dim=0
786
+ )
787
+
788
+ overall_loss = torch.sum(tokens_per_expert * router_prob_per_expert.unsqueeze(0))
789
+ return overall_loss * num_experts
790
+
791
+
792
+ @auto_docstring
793
+ class LagunaForCausalLM(LagunaPreTrainedModel, GenerationMixin):
794
+ _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
795
+ _tp_plan = {"lm_head": "colwise_gather_output"}
796
+ _pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
797
+
798
+ def __init__(self, config):
799
+ super().__init__(config)
800
+ self.model = LagunaModel(config)
801
+ self.vocab_size = config.vocab_size
802
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
803
+ self.router_aux_loss_coef = config.router_aux_loss_coef
804
+ self.num_experts = config.num_experts
805
+ self.num_experts_per_tok = config.num_experts_per_tok
806
+
807
+ # Initialize weights and apply final processing
808
+ self.post_init()
809
+
810
+ @can_return_tuple
811
+ @auto_docstring
812
+ def forward(
813
+ self,
814
+ input_ids: torch.LongTensor | None = None,
815
+ attention_mask: torch.Tensor | None = None,
816
+ position_ids: torch.LongTensor | None = None,
817
+ past_key_values: Cache | None = None,
818
+ inputs_embeds: torch.FloatTensor | None = None,
819
+ labels: torch.LongTensor | None = None,
820
+ use_cache: bool | None = None,
821
+ output_router_logits: bool | None = None,
822
+ logits_to_keep: int | torch.Tensor = 0,
823
+ **kwargs: Unpack[TransformersKwargs],
824
+ ) -> MoeCausalLMOutputWithPast:
825
+ r"""
826
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
827
+ Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
828
+ config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
829
+ (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
830
+ """
831
+
832
+ output_router_logits = (
833
+ output_router_logits if output_router_logits is not None else self.config.output_router_logits
834
+ )
835
+
836
+ # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn)
837
+ outputs: MoeModelOutputWithPast = self.model(
838
+ input_ids=input_ids,
839
+ attention_mask=attention_mask,
840
+ position_ids=position_ids,
841
+ past_key_values=past_key_values,
842
+ inputs_embeds=inputs_embeds,
843
+ use_cache=use_cache,
844
+ output_router_logits=output_router_logits,
845
+ **kwargs,
846
+ )
847
+
848
+ hidden_states = outputs.last_hidden_state
849
+ # Only compute necessary logits, and do not upcast them to float if we are not computing the loss
850
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
851
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
852
+
853
+ loss = None
854
+ if labels is not None:
855
+ loss = self.loss_function(logits, labels, self.vocab_size, **kwargs)
856
+
857
+ aux_loss = None
858
+ if output_router_logits:
859
+ aux_loss = load_balancing_loss_func(
860
+ outputs.router_logits,
861
+ self.num_experts,
862
+ self.num_experts_per_tok,
863
+ attention_mask,
864
+ )
865
+ if labels is not None:
866
+ loss += self.router_aux_loss_coef * aux_loss.to(loss.device) # make sure to reside in the same device
867
+
868
+ return MoeCausalLMOutputWithPast(
869
+ loss=loss,
870
+ aux_loss=aux_loss,
871
+ logits=logits,
872
+ past_key_values=outputs.past_key_values,
873
+ hidden_states=outputs.hidden_states,
874
+ attentions=outputs.attentions,
875
+ router_logits=outputs.router_logits,
876
+ )
877
+
878
+
879
+ __all__ = ["LagunaForCausalLM", "LagunaModel", "LagunaPreTrainedModel"]
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,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }