avlp12 commited on
Commit
e32328c
·
verified ·
1 Parent(s): 665d80c

Upload folder using huggingface_hub

Browse files
Files changed (45) hide show
  1. .gitattributes +3 -0
  2. LICENSE.upstage-solar +96 -0
  3. chat_template.jinja +234 -0
  4. config.json +452 -0
  5. generation_config.json +9 -0
  6. images/architecture_map.png +3 -0
  7. images/build_overview.png +0 -0
  8. images/clip_gate_ppl.png +0 -0
  9. images/dwq_loss_curve.png +3 -0
  10. images/quant_efficiency.png +0 -0
  11. model-00001-of-00031.safetensors +3 -0
  12. model-00002-of-00031.safetensors +3 -0
  13. model-00003-of-00031.safetensors +3 -0
  14. model-00004-of-00031.safetensors +3 -0
  15. model-00005-of-00031.safetensors +3 -0
  16. model-00006-of-00031.safetensors +3 -0
  17. model-00007-of-00031.safetensors +3 -0
  18. model-00008-of-00031.safetensors +3 -0
  19. model-00009-of-00031.safetensors +3 -0
  20. model-00010-of-00031.safetensors +3 -0
  21. model-00011-of-00031.safetensors +3 -0
  22. model-00012-of-00031.safetensors +3 -0
  23. model-00013-of-00031.safetensors +3 -0
  24. model-00014-of-00031.safetensors +3 -0
  25. model-00015-of-00031.safetensors +3 -0
  26. model-00016-of-00031.safetensors +3 -0
  27. model-00017-of-00031.safetensors +3 -0
  28. model-00018-of-00031.safetensors +3 -0
  29. model-00019-of-00031.safetensors +3 -0
  30. model-00020-of-00031.safetensors +3 -0
  31. model-00021-of-00031.safetensors +3 -0
  32. model-00022-of-00031.safetensors +3 -0
  33. model-00023-of-00031.safetensors +3 -0
  34. model-00024-of-00031.safetensors +3 -0
  35. model-00025-of-00031.safetensors +3 -0
  36. model-00026-of-00031.safetensors +3 -0
  37. model-00027-of-00031.safetensors +3 -0
  38. model-00028-of-00031.safetensors +3 -0
  39. model-00029-of-00031.safetensors +3 -0
  40. model-00030-of-00031.safetensors +3 -0
  41. model-00031-of-00031.safetensors +3 -0
  42. model.safetensors.index.json +0 -0
  43. solar_open2.py +858 -0
  44. tokenizer.json +3 -0
  45. tokenizer_config.json +14 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* 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
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ images/architecture_map.png filter=lfs diff=lfs merge=lfs -text
37
+ images/dwq_loss_curve.png filter=lfs diff=lfs merge=lfs -text
38
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
LICENSE.upstage-solar ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Upstage Solar License
2
+
3
+ # Preamble
4
+
5
+ The 'Upstage Solar License' (hereinafter referred to as "this License") was established by Upstage Co., Ltd., incorporated under the laws of the Republic of Korea, to encourage the development of open-source software using Solar AI models, and is not affiliated with the Apache Software Foundation.
6
+
7
+ This License basically adopts all provisions of the Apache License, Version 2.0 (hereinafter referred to as "Apache License 2.0"), including the principle of allowing commercial use, but prescribes minimum strategic conditions for the global expansion of AI technology and the sustainable development of the AI ecosystem.
8
+
9
+ The key additional condition, as specified in Section 4(e), is that if you distribute a "Derivative AI Model" based on the "Work", you must specify the 'Solar' brand. This applies as an exception to Section 6 (Trademarks) of the Apache License 2.0.
10
+
11
+ # TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
12
+
13
+ 1\. Definitions.
14
+
15
+ "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
16
+
17
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
18
+
19
+ "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
20
+
21
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
22
+
23
+ "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
24
+
25
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
26
+
27
+ "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
28
+
29
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
30
+
31
+ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
32
+
33
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
34
+
35
+ 2\. Grant of Copyright License.
36
+ Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
37
+
38
+ 3\. Grant of Patent License.
39
+ Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
40
+
41
+ 4\. Redistribution.
42
+ You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
43
+
44
+ (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
45
+ (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
46
+ (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
47
+ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
48
+ (e) If You distribute or make available a Derivative Work that is an artificial intelligence model created, trained, fine-tuned, or otherwise improved using the Work (the "Derivative AI Model"), You must adhere to the following conditions:
49
+ (i) The name of such Derivative AI Model must begin with "Solar" (e.g., "Solar-MyModel-v1"); and
50
+ (ii) You must prominently display the phrase "Built with Solar" in any related websites, user interfaces, or documentation associated with the Derivative AI Model; and
51
+ (iii) You must provide a copy of this License, including the original copyright notice and NOTICE file as included in the distribution of the Works, alongside the Derivative AI Model.
52
+
53
+ You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
54
+
55
+ 5\. Submission of Contributions.
56
+ Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
57
+
58
+ 6\. Trademarks.
59
+ This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work, reproducing the content of the NOTICE file, and as explicitly required for attribution in Section 4(e) of this License.
60
+ Any use of the “Solar” name under this License must not imply any sponsorship, endorsement, certification, or official relationship with the Licensor, nor mislead users into believing that a Derivative AI Model is an official product of the Licensor.
61
+
62
+ 7\. Disclaimer of Warranty.
63
+ Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
64
+
65
+ 8\. Limitation of Liability.
66
+ In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
67
+
68
+ 9\. Accepting Warranty or Additional Liability.
69
+ While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
70
+
71
+ END OF TERMS AND CONDITIONS
72
+
73
+ # APPENDIX: How to apply the Upstage Solar License to your work.
74
+
75
+ To apply the Upstage Solar License to your work, attach the following boilerplate notice, with the fields enclosed by brackets replaced with your own identifying information. (Don't include the brackets\!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
76
+
77
+ Copyright \[yyyy\] \[Upstage AI (or other copyright owner)\]
78
+
79
+ Licensed under the Upstage Solar License (the "License");
80
+ you may not use this file except in compliance with the License.
81
+ You may obtain a copy of the License at
82
+
83
+ https://huggingface.co/Upstage/Solar-Open-100B/blob/main/LICENSE
84
+
85
+ Unless required by applicable law or agreed to in writing, software
86
+ distributed under the License is distributed on an "AS IS" BASIS,
87
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
88
+ See the License for the specific language governing permissions and
89
+ limitations under the License.
90
+
91
+ This work is based on or derived from materials licensed under the
92
+ Upstage Solar License.
93
+ If you distribute or make available a Derivative AI Model (as defined
94
+ in Section 4(e) of the License), your model name must begin with "Solar"
95
+ and you must prominently display "Built with Solar" in associated
96
+ documentation or interfaces.
chat_template.jinja ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {#- ======== Template Parameters ======== #}
2
+ {%- set add_generation_prompt = add_generation_prompt if add_generation_prompt is defined else true %}
3
+ {%- set provider_system_prompt = provider_system_prompt if provider_system_prompt else false %}
4
+ {%- set reasoning_effort = reasoning_effort if reasoning_effort is defined else "high" %}
5
+ {%- set think_render_option = think_render_option if think_render_option is defined else "interleaved" %}
6
+
7
+ {#- ======== System Block State ======== #}
8
+ {%- set sys_ns = namespace(is_first_block=true) -%}
9
+
10
+ {#- ======== Find last user message index ======== #}
11
+ {%- set last_user_idx = namespace(value=-1) -%}
12
+ {%- for message in messages -%}
13
+ {%- if message.role == 'user' -%}
14
+ {%- set last_user_idx.value = loop.index0 -%}
15
+ {%- endif -%}
16
+ {%- endfor -%}
17
+
18
+ {#- ======== System messages renderers ======== #}
19
+ {%- macro render_system_message(user_system_messages) %}
20
+ {%- if provider_system_prompt %}
21
+ {%- if not sys_ns.is_first_block %}{{- "\n\n" }}{%- endif %}
22
+ {%- set sys_ns.is_first_block = false %}
23
+ {{- "## Provider System Prompt\n\nYou are Solar Open2 250B, a large language model trained by Upstage AI, a Korean startup. Your knowledge cutoff is 2026-02. The current date is " + strftime_now("%Y-%m-%d") + "." }}
24
+ {%- endif -%}
25
+ {%- if user_system_messages %}
26
+ {%- if not sys_ns.is_first_block %}{{- "\n\n" }}{%- endif %}
27
+ {%- set sys_ns.is_first_block = false %}
28
+ {{- "## System Prompt" }}
29
+ {%- for system_message in user_system_messages %}
30
+ {{- "\n\n" }}
31
+ {{- system_message }}
32
+ {%- endfor %}
33
+ {%- endif -%}
34
+ {%- endmacro %}
35
+
36
+ {%- macro render_tool_instruction(tools, is_last_block) %}
37
+ {%- if not sys_ns.is_first_block %}{{- "\n\n" }}{%- endif %}
38
+ {%- set sys_ns.is_first_block = false %}
39
+ {{- "## Tools\n- You may invoke one or more tools to assist with the user's query." }}
40
+ {{- "\n\n### Available Tools\n" }}
41
+ {%- for tool in tools %}
42
+ {{- "<|tool:start|>" }}
43
+ {{- tool.function | tojson }}
44
+ {{- "<|tool:end|>\n" }}
45
+ {%- endfor %}
46
+ {{- "\n### Tool Call Instruction\n" }}
47
+ {{- "- If using a tool, any reasoning must strictly precede the call. Do not append any text after the tool call.\n" }}
48
+ {{- "- If no tool is required, answer directly from your knowledge without ever mentioning the availability or absence of tools.\n" }}
49
+ {{- "- Each tool call MUST use this following format: <|tool_call:start|>{example-tool-name}\n<|tool_arg:start|>{example-key-name-1}<|tool_arg:value|>{example-value-1}<|tool_arg:end|>\n<|tool_arg:start|>{example-key-name-2}<|tool_arg:value|>{example-value-2}<|tool_arg:end|>\n<|tool_call:end|>\n" }}
50
+ {%- endmacro %}
51
+
52
+ {#-
53
+ Input convention for `response_format`:
54
+ - Per the OpenAI Chat Completions API, `response_format` is an object of one of:
55
+ {"type": "json_schema", "json_schema": {"name": ..., "schema": {...}, "strict": ...}}
56
+ {"type": "json_object"}
57
+ - Only `response_format.json_schema.schema` (the inner JSON Schema) is injected
58
+ into the prompt; the OpenAI wrapper fields (`name`, `strict`, ...) are dropped.
59
+ - For `json_object`, no schema exists, so we emit a generic JSON-only instruction.
60
+ - vLLM does NOT pass `response_format` to chat templates by default; it routes it
61
+ to guided decoding instead. This template/whale opts into prompt injection,
62
+ so callers serving the model directly via vLLM must opt in via
63
+ `chat_template_kwargs` (or rely on whale's normalization).
64
+ -#}
65
+ {%- macro render_json_response_format_instruction(response_format) %}
66
+ {%- if not sys_ns.is_first_block %}{{- "\n\n" }}{%- endif %}
67
+ {%- set sys_ns.is_first_block = false %}
68
+ {{- "## Output Format Constraint" }}
69
+ {%- if response_format.type == "json_schema" %}
70
+ {{- "\n\n- Your final response should follow the JSON schema:\n```json\n" }}
71
+ {{- response_format.json_schema.schema | tojson }}
72
+ {{- "\n```\n- Please ensure your answers adhere to this format and do not contain any unnecessary text." }}
73
+ {%- elif response_format.type == "json_object" %}
74
+ {{- "\n\n- Your final response must be a valid JSON object." }}
75
+ {{- "\n- Do not include any text outside the JSON object." }}
76
+ {%- endif %}
77
+ {%- endmacro %}
78
+
79
+ {#-
80
+ Input convention for `tool_arguments`:
81
+ - Must be a mapping (dict), NOT a JSON-encoded string.
82
+ - The OpenAI API spec defines `tool_calls[].function.arguments` as a JSON string,
83
+ but vLLM, HuggingFace transformers tool-use templates (Qwen, Hermes, Llama, ...),
84
+ and this template all expect the value to be parsed into a dict before rendering.
85
+ - vLLM normalizes this in `_postprocess_messages()` (vllm/entrypoints/chat_utils.py).
86
+ - Callers (whale, training pipelines that call `tokenizer.apply_chat_template`
87
+ directly, ...) are responsible for parsing the JSON string into a dict before
88
+ invoking the template.
89
+ -#}
90
+ {%- macro render_tool_arguments(tool_arguments) %}
91
+ {%- for args_name, args_value in tool_arguments|items %}
92
+ {{- "<|tool_arg:start|>"+ args_name + "<|tool_arg:value|>"}}
93
+ {%- set args_value = args_value if args_value is string else (args_value | tojson | safe) %}
94
+ {{- args_value }}
95
+ {{- "<|tool_arg:end|>\n" }}
96
+ {%- endfor %}
97
+ {%- endmacro %}
98
+
99
+ {#- ======== Render system message ======== #}
100
+ {%- set ns = namespace(system_messages=[]) -%}
101
+ {%- for message in messages -%}
102
+ {%- if message.role == 'system' -%}
103
+ {%- set ns.system_messages = ns.system_messages + [message.content] -%}
104
+ {%- endif -%}
105
+ {%- endfor -%}
106
+
107
+ {%- if ns.system_messages or provider_system_prompt or tools or response_format -%}
108
+ {{- "<|im:start|>system<|im:content|>" }}
109
+ {{- render_system_message(ns.system_messages) }}
110
+ {%- if tools -%}
111
+ {{- render_tool_instruction(tools, not response_format) }}
112
+ {%- endif %}
113
+ {%- if response_format -%}
114
+ {{- render_json_response_format_instruction(response_format) }}
115
+ {%- endif %}
116
+ {{- "<|im:end|>\n" }}
117
+ {%- endif -%}
118
+
119
+ {#- ======== Render main messages ======== #}
120
+ {%- for message in messages -%}
121
+ {%- if message.role == 'user' -%}
122
+ {{- "<|im:start|>user<|im:content|>" + message.content + "<|im:end|>\n" }}
123
+ {%- elif message.role == 'tool' -%}
124
+ {%- if not message.tool_call_id -%}
125
+ {{- raise_exception("tool message is missing required 'tool_call_id' field") -}}
126
+ {%- endif -%}
127
+ {%- set prev_is_tool = loop.index0 > 0 and messages[loop.index0 - 1].role == 'tool' -%}
128
+ {#- Only render at the start of a contiguous tool block; subsequent tool messages -#}
129
+ {#- are already emitted (reordered) within the first one. -#}
130
+ {%- if not prev_is_tool -%}
131
+ {%- set start_idx = loop.index0 -%}
132
+ {#- Collect contiguous tool messages starting at start_idx -#}
133
+ {%- set tool_ns = namespace(items=[], scanning=true) -%}
134
+ {%- for j in range(start_idx, messages | length) -%}
135
+ {%- if tool_ns.scanning -%}
136
+ {%- if messages[j].role == 'tool' -%}
137
+ {%- set tool_ns.items = tool_ns.items + [messages[j]] -%}
138
+ {%- else -%}
139
+ {%- set tool_ns.scanning = false -%}
140
+ {%- endif -%}
141
+ {%- endif -%}
142
+ {%- endfor -%}
143
+
144
+ {{- "<|im:start|>tool<|im:content|>" }}
145
+
146
+ {%- set prev_msg = messages[start_idx - 1] if start_idx > 0 else none -%}
147
+ {%- if prev_msg and prev_msg.role == 'assistant' and prev_msg.tool_calls -%}
148
+ {#- Render tool responses in the order of the preceding assistant's tool_calls ids. -#}
149
+ {%- set sep_ns = namespace(first=true, rendered_ids=[]) -%}
150
+ {%- for tool_call in prev_msg.tool_calls -%}
151
+ {%- for tool_msg in tool_ns.items -%}
152
+ {%- if tool_msg.tool_call_id == tool_call.id -%}
153
+ {%- if not sep_ns.first -%}{{- "\n" }}{%- endif -%}
154
+ {%- set sep_ns.first = false -%}
155
+ {%- set sep_ns.rendered_ids = sep_ns.rendered_ids + [tool_msg.tool_call_id] -%}
156
+ {{- "<|tool_response:start|>" + tool_msg.content + "<|tool_response:end|>" }}
157
+ {%- endif -%}
158
+ {%- endfor -%}
159
+ {%- endfor -%}
160
+ {#- Append any orphan tool messages (no matching id) in their original order. -#}
161
+ {%- for tool_msg in tool_ns.items -%}
162
+ {%- if tool_msg.tool_call_id not in sep_ns.rendered_ids -%}
163
+ {%- if not sep_ns.first -%}{{- "\n" }}{%- endif -%}
164
+ {%- set sep_ns.first = false -%}
165
+ {{- "<|tool_response:start|>" + tool_msg.content + "<|tool_response:end|>" }}
166
+ {%- endif -%}
167
+ {%- endfor -%}
168
+ {%- else -%}
169
+ {%- for tool_msg in tool_ns.items -%}
170
+ {%- if not loop.first -%}{{- "\n" }}{%- endif -%}
171
+ {{- "<|tool_response:start|>" + tool_msg.content + "<|tool_response:end|>" }}
172
+ {%- endfor -%}
173
+ {%- endif -%}
174
+
175
+ {{- "\n<|im:end|>\n" }}
176
+ {%- endif -%}
177
+ {%- elif message.role == 'assistant' -%}
178
+ {{- "<|im:start|>assistant<|im:content|>" }}
179
+
180
+ {#- ======== Resolve reasoning field (reasoning or reasoning_content) ======== #}
181
+ {%- if message.reasoning is defined -%}
182
+ {%- set reasoning = message.reasoning -%}
183
+ {%- elif message.reasoning_content is defined -%}
184
+ {%- set reasoning = message.reasoning_content -%}
185
+ {%- else -%}
186
+ {%- set reasoning = none -%}
187
+ {%- endif -%}
188
+
189
+ {#- ======== Assistant Thinking ======== #}
190
+ {%- if think_render_option == "preserved" -%}
191
+ {%- if reasoning -%}
192
+ {{- "<|think:start|>" + reasoning + "<|think:end|>" }}
193
+ {%- else -%}
194
+ {{- "<|think:start|><|think:end|>" }}
195
+ {%- endif -%}
196
+ {%- elif think_render_option == "interleaved" -%}
197
+ {%- if reasoning and loop.index0 > last_user_idx.value -%}
198
+ {{- "<|think:start|>" + reasoning + "<|think:end|>" }}
199
+ {%- else -%}
200
+ {{- "<|think:start|><|think:end|>" }}
201
+ {%- endif -%}
202
+ {%- endif -%}
203
+
204
+ {#- ======== Assistant Messages ======== #}
205
+ {%- if message.content -%}
206
+ {{- message.content }}
207
+ {%- endif -%}
208
+
209
+ {#- ======== Assistant Tool calls ======== #}
210
+ {%- if message.tool_calls -%}
211
+ {%- for tool_call in message.tool_calls -%}
212
+ {%- if not tool_call.id -%}
213
+ {{- raise_exception("assistant tool_call is missing required 'id' field") -}}
214
+ {%- endif -%}
215
+ {%- if not loop.first -%}{{- "\n" }}{%- endif -%}
216
+ {{- "<|tool_call:start|>" + tool_call.function.name + "\n" }}
217
+ {%- if tool_call.function.arguments is defined %}
218
+ {{- render_tool_arguments(tool_call.function.arguments) }}
219
+ {%- endif %}
220
+ {{- "<|tool_call:end|>" }}
221
+ {%- endfor -%}
222
+ {{- "\n" }}
223
+ {%- endif -%}
224
+ {{- "<|im:end|>\n" }}
225
+ {%- endif -%}
226
+ {%- endfor -%}
227
+
228
+ {%- if add_generation_prompt -%}
229
+ {%- if reasoning_effort in ["medium", "high", "xhigh"] -%}
230
+ {{- "<|im:start|>assistant<|im:content|><|think:start|>" }}
231
+ {%- else -%}
232
+ {{- "<|im:start|>assistant<|im:content|><|think:start|><|think:end|>" }}
233
+ {%- endif -%}
234
+ {%- endif -%}
config.json ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "SolarOpen2ForCausalLM"
4
+ ],
5
+ "bos_token_id": 1,
6
+ "eos_token_id": [
7
+ 2,
8
+ 129
9
+ ],
10
+ "first_k_dense_replace": 0,
11
+ "gqa_interval": 3,
12
+ "gqa_layers": [
13
+ 0,
14
+ 4,
15
+ 8,
16
+ 12,
17
+ 16,
18
+ 20,
19
+ 24,
20
+ 28,
21
+ 32,
22
+ 36,
23
+ 40,
24
+ 44
25
+ ],
26
+ "head_dim": 128,
27
+ "hidden_size": 4096,
28
+ "intermediate_size": 10240,
29
+ "kda_allow_neg_eigval": true,
30
+ "kda_use_full_proj": false,
31
+ "linear_attn_config": {
32
+ "short_conv_kernel_size": 4,
33
+ "head_dim": 128,
34
+ "num_heads": 64,
35
+ "num_kv_heads": null
36
+ },
37
+ "max_position_embeddings": 1048576,
38
+ "model_type": "solar_open2",
39
+ "moe_intermediate_size": 1280,
40
+ "n_routed_experts": 320,
41
+ "n_shared_experts": 1,
42
+ "norm_topk_prob": true,
43
+ "num_attention_heads": 64,
44
+ "num_experts_per_tok": 8,
45
+ "num_hidden_layers": 48,
46
+ "num_key_value_heads": 8,
47
+ "pad_token_id": 2,
48
+ "partial_rotary_factor": 1.0,
49
+ "quantization": {
50
+ "group_size": 32,
51
+ "bits": 4,
52
+ "mode": "affine",
53
+ "model.layers.0.mlp.gate": {
54
+ "group_size": 64,
55
+ "bits": 8
56
+ },
57
+ "model.layers.1.mlp.gate": {
58
+ "group_size": 64,
59
+ "bits": 8
60
+ },
61
+ "model.layers.2.mlp.gate": {
62
+ "group_size": 64,
63
+ "bits": 8
64
+ },
65
+ "model.layers.3.mlp.gate": {
66
+ "group_size": 64,
67
+ "bits": 8
68
+ },
69
+ "model.layers.4.mlp.gate": {
70
+ "group_size": 64,
71
+ "bits": 8
72
+ },
73
+ "model.layers.5.mlp.gate": {
74
+ "group_size": 64,
75
+ "bits": 8
76
+ },
77
+ "model.layers.6.mlp.gate": {
78
+ "group_size": 64,
79
+ "bits": 8
80
+ },
81
+ "model.layers.7.mlp.gate": {
82
+ "group_size": 64,
83
+ "bits": 8
84
+ },
85
+ "model.layers.8.mlp.gate": {
86
+ "group_size": 64,
87
+ "bits": 8
88
+ },
89
+ "model.layers.9.mlp.gate": {
90
+ "group_size": 64,
91
+ "bits": 8
92
+ },
93
+ "model.layers.10.mlp.gate": {
94
+ "group_size": 64,
95
+ "bits": 8
96
+ },
97
+ "model.layers.11.mlp.gate": {
98
+ "group_size": 64,
99
+ "bits": 8
100
+ },
101
+ "model.layers.12.mlp.gate": {
102
+ "group_size": 64,
103
+ "bits": 8
104
+ },
105
+ "model.layers.13.mlp.gate": {
106
+ "group_size": 64,
107
+ "bits": 8
108
+ },
109
+ "model.layers.14.mlp.gate": {
110
+ "group_size": 64,
111
+ "bits": 8
112
+ },
113
+ "model.layers.15.mlp.gate": {
114
+ "group_size": 64,
115
+ "bits": 8
116
+ },
117
+ "model.layers.16.mlp.gate": {
118
+ "group_size": 64,
119
+ "bits": 8
120
+ },
121
+ "model.layers.17.mlp.gate": {
122
+ "group_size": 64,
123
+ "bits": 8
124
+ },
125
+ "model.layers.18.mlp.gate": {
126
+ "group_size": 64,
127
+ "bits": 8
128
+ },
129
+ "model.layers.19.mlp.gate": {
130
+ "group_size": 64,
131
+ "bits": 8
132
+ },
133
+ "model.layers.20.mlp.gate": {
134
+ "group_size": 64,
135
+ "bits": 8
136
+ },
137
+ "model.layers.21.mlp.gate": {
138
+ "group_size": 64,
139
+ "bits": 8
140
+ },
141
+ "model.layers.22.mlp.gate": {
142
+ "group_size": 64,
143
+ "bits": 8
144
+ },
145
+ "model.layers.23.mlp.gate": {
146
+ "group_size": 64,
147
+ "bits": 8
148
+ },
149
+ "model.layers.24.mlp.gate": {
150
+ "group_size": 64,
151
+ "bits": 8
152
+ },
153
+ "model.layers.25.mlp.gate": {
154
+ "group_size": 64,
155
+ "bits": 8
156
+ },
157
+ "model.layers.26.mlp.gate": {
158
+ "group_size": 64,
159
+ "bits": 8
160
+ },
161
+ "model.layers.27.mlp.gate": {
162
+ "group_size": 64,
163
+ "bits": 8
164
+ },
165
+ "model.layers.28.mlp.gate": {
166
+ "group_size": 64,
167
+ "bits": 8
168
+ },
169
+ "model.layers.29.mlp.gate": {
170
+ "group_size": 64,
171
+ "bits": 8
172
+ },
173
+ "model.layers.30.mlp.gate": {
174
+ "group_size": 64,
175
+ "bits": 8
176
+ },
177
+ "model.layers.31.mlp.gate": {
178
+ "group_size": 64,
179
+ "bits": 8
180
+ },
181
+ "model.layers.32.mlp.gate": {
182
+ "group_size": 64,
183
+ "bits": 8
184
+ },
185
+ "model.layers.33.mlp.gate": {
186
+ "group_size": 64,
187
+ "bits": 8
188
+ },
189
+ "model.layers.34.mlp.gate": {
190
+ "group_size": 64,
191
+ "bits": 8
192
+ },
193
+ "model.layers.35.mlp.gate": {
194
+ "group_size": 64,
195
+ "bits": 8
196
+ },
197
+ "model.layers.36.mlp.gate": {
198
+ "group_size": 64,
199
+ "bits": 8
200
+ },
201
+ "model.layers.37.mlp.gate": {
202
+ "group_size": 64,
203
+ "bits": 8
204
+ },
205
+ "model.layers.38.mlp.gate": {
206
+ "group_size": 64,
207
+ "bits": 8
208
+ },
209
+ "model.layers.39.mlp.gate": {
210
+ "group_size": 64,
211
+ "bits": 8
212
+ },
213
+ "model.layers.40.mlp.gate": {
214
+ "group_size": 64,
215
+ "bits": 8
216
+ },
217
+ "model.layers.41.mlp.gate": {
218
+ "group_size": 64,
219
+ "bits": 8
220
+ },
221
+ "model.layers.42.mlp.gate": {
222
+ "group_size": 64,
223
+ "bits": 8
224
+ },
225
+ "model.layers.43.mlp.gate": {
226
+ "group_size": 64,
227
+ "bits": 8
228
+ },
229
+ "model.layers.44.mlp.gate": {
230
+ "group_size": 64,
231
+ "bits": 8
232
+ },
233
+ "model.layers.45.mlp.gate": {
234
+ "group_size": 64,
235
+ "bits": 8
236
+ },
237
+ "model.layers.46.mlp.gate": {
238
+ "group_size": 64,
239
+ "bits": 8
240
+ },
241
+ "model.layers.47.mlp.gate": {
242
+ "group_size": 64,
243
+ "bits": 8
244
+ }
245
+ },
246
+ "quantization_config": {
247
+ "group_size": 32,
248
+ "bits": 4,
249
+ "mode": "affine",
250
+ "model.layers.0.mlp.gate": {
251
+ "group_size": 64,
252
+ "bits": 8
253
+ },
254
+ "model.layers.1.mlp.gate": {
255
+ "group_size": 64,
256
+ "bits": 8
257
+ },
258
+ "model.layers.2.mlp.gate": {
259
+ "group_size": 64,
260
+ "bits": 8
261
+ },
262
+ "model.layers.3.mlp.gate": {
263
+ "group_size": 64,
264
+ "bits": 8
265
+ },
266
+ "model.layers.4.mlp.gate": {
267
+ "group_size": 64,
268
+ "bits": 8
269
+ },
270
+ "model.layers.5.mlp.gate": {
271
+ "group_size": 64,
272
+ "bits": 8
273
+ },
274
+ "model.layers.6.mlp.gate": {
275
+ "group_size": 64,
276
+ "bits": 8
277
+ },
278
+ "model.layers.7.mlp.gate": {
279
+ "group_size": 64,
280
+ "bits": 8
281
+ },
282
+ "model.layers.8.mlp.gate": {
283
+ "group_size": 64,
284
+ "bits": 8
285
+ },
286
+ "model.layers.9.mlp.gate": {
287
+ "group_size": 64,
288
+ "bits": 8
289
+ },
290
+ "model.layers.10.mlp.gate": {
291
+ "group_size": 64,
292
+ "bits": 8
293
+ },
294
+ "model.layers.11.mlp.gate": {
295
+ "group_size": 64,
296
+ "bits": 8
297
+ },
298
+ "model.layers.12.mlp.gate": {
299
+ "group_size": 64,
300
+ "bits": 8
301
+ },
302
+ "model.layers.13.mlp.gate": {
303
+ "group_size": 64,
304
+ "bits": 8
305
+ },
306
+ "model.layers.14.mlp.gate": {
307
+ "group_size": 64,
308
+ "bits": 8
309
+ },
310
+ "model.layers.15.mlp.gate": {
311
+ "group_size": 64,
312
+ "bits": 8
313
+ },
314
+ "model.layers.16.mlp.gate": {
315
+ "group_size": 64,
316
+ "bits": 8
317
+ },
318
+ "model.layers.17.mlp.gate": {
319
+ "group_size": 64,
320
+ "bits": 8
321
+ },
322
+ "model.layers.18.mlp.gate": {
323
+ "group_size": 64,
324
+ "bits": 8
325
+ },
326
+ "model.layers.19.mlp.gate": {
327
+ "group_size": 64,
328
+ "bits": 8
329
+ },
330
+ "model.layers.20.mlp.gate": {
331
+ "group_size": 64,
332
+ "bits": 8
333
+ },
334
+ "model.layers.21.mlp.gate": {
335
+ "group_size": 64,
336
+ "bits": 8
337
+ },
338
+ "model.layers.22.mlp.gate": {
339
+ "group_size": 64,
340
+ "bits": 8
341
+ },
342
+ "model.layers.23.mlp.gate": {
343
+ "group_size": 64,
344
+ "bits": 8
345
+ },
346
+ "model.layers.24.mlp.gate": {
347
+ "group_size": 64,
348
+ "bits": 8
349
+ },
350
+ "model.layers.25.mlp.gate": {
351
+ "group_size": 64,
352
+ "bits": 8
353
+ },
354
+ "model.layers.26.mlp.gate": {
355
+ "group_size": 64,
356
+ "bits": 8
357
+ },
358
+ "model.layers.27.mlp.gate": {
359
+ "group_size": 64,
360
+ "bits": 8
361
+ },
362
+ "model.layers.28.mlp.gate": {
363
+ "group_size": 64,
364
+ "bits": 8
365
+ },
366
+ "model.layers.29.mlp.gate": {
367
+ "group_size": 64,
368
+ "bits": 8
369
+ },
370
+ "model.layers.30.mlp.gate": {
371
+ "group_size": 64,
372
+ "bits": 8
373
+ },
374
+ "model.layers.31.mlp.gate": {
375
+ "group_size": 64,
376
+ "bits": 8
377
+ },
378
+ "model.layers.32.mlp.gate": {
379
+ "group_size": 64,
380
+ "bits": 8
381
+ },
382
+ "model.layers.33.mlp.gate": {
383
+ "group_size": 64,
384
+ "bits": 8
385
+ },
386
+ "model.layers.34.mlp.gate": {
387
+ "group_size": 64,
388
+ "bits": 8
389
+ },
390
+ "model.layers.35.mlp.gate": {
391
+ "group_size": 64,
392
+ "bits": 8
393
+ },
394
+ "model.layers.36.mlp.gate": {
395
+ "group_size": 64,
396
+ "bits": 8
397
+ },
398
+ "model.layers.37.mlp.gate": {
399
+ "group_size": 64,
400
+ "bits": 8
401
+ },
402
+ "model.layers.38.mlp.gate": {
403
+ "group_size": 64,
404
+ "bits": 8
405
+ },
406
+ "model.layers.39.mlp.gate": {
407
+ "group_size": 64,
408
+ "bits": 8
409
+ },
410
+ "model.layers.40.mlp.gate": {
411
+ "group_size": 64,
412
+ "bits": 8
413
+ },
414
+ "model.layers.41.mlp.gate": {
415
+ "group_size": 64,
416
+ "bits": 8
417
+ },
418
+ "model.layers.42.mlp.gate": {
419
+ "group_size": 64,
420
+ "bits": 8
421
+ },
422
+ "model.layers.43.mlp.gate": {
423
+ "group_size": 64,
424
+ "bits": 8
425
+ },
426
+ "model.layers.44.mlp.gate": {
427
+ "group_size": 64,
428
+ "bits": 8
429
+ },
430
+ "model.layers.45.mlp.gate": {
431
+ "group_size": 64,
432
+ "bits": 8
433
+ },
434
+ "model.layers.46.mlp.gate": {
435
+ "group_size": 64,
436
+ "bits": 8
437
+ },
438
+ "model.layers.47.mlp.gate": {
439
+ "group_size": 64,
440
+ "bits": 8
441
+ }
442
+ },
443
+ "rms_norm_eps": 1e-05,
444
+ "rope_theta": 10000,
445
+ "routed_scaling_factor": 1.0,
446
+ "tie_word_embeddings": false,
447
+ "torch_dtype": "bfloat16",
448
+ "transformers_version": "5.5.4",
449
+ "use_gqa_gate": true,
450
+ "use_rope": false,
451
+ "vocab_size": 196608
452
+ }
generation_config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 1,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 2,
6
+ 129
7
+ ],
8
+ "transformers_version": "4.57.6"
9
+ }
images/architecture_map.png ADDED

Git LFS Details

  • SHA256: 19963cc20c1f88d81f2e1cbfa3e8a2f60df336cb7791cce196ac243890eebe7f
  • Pointer size: 131 Bytes
  • Size of remote file: 205 kB
images/build_overview.png ADDED
images/clip_gate_ppl.png ADDED
images/dwq_loss_curve.png ADDED

Git LFS Details

  • SHA256: e06436921e9a0eb81b4f499b36fdecf6e6a601b876eb740a81b6f040c2f9d12b
  • Pointer size: 131 Bytes
  • Size of remote file: 141 kB
images/quant_efficiency.png ADDED
model-00001-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c7d3b6d8d4890ae4c57f014f62411c354114ec34395bcaa082d59e37c119e60
3
+ size 4864650180
model-00002-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e49a4b6e2839324ddc7d1d7b08790014cd0820693cd6c8766aaa448629ff147e
3
+ size 5366984481
model-00003-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:62c7893a6629ebfbe067b7f8a2ff5cbff52429f73fc93535b02bdfcd7fc74a12
3
+ size 5288274668
model-00004-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:360f985a1523fd360ec5db8b63e02d156cf0159354fd133dc5b54bd51f2ffa51
3
+ size 5332975549
model-00005-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:374fe3afe4a14b28488efc910423d32ed17408ccb29c6674ec2520a46a8a1bf0
3
+ size 5340357628
model-00006-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:031d169aea90cfaee49b8994d0faed2d74cbcd4bda9dc8fc8df2a2d5a2a85bf1
3
+ size 4580898326
model-00007-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d30098073a3fcf0cc0ee8f53d550790aa966d4233d5a7f2e8b987c00a5c7cc0b
3
+ size 5366984533
model-00008-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cad491dce0e020f135cab73e6a7d71e7414f7f0cbac70c456ade122e4ce5bbef
3
+ size 5288274785
model-00009-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b985413808e4492abbf8cda2d695f36342d6bf003f03faedec50604a8f388146
3
+ size 5332975633
model-00010-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ab59fabbf4c353691793185a3afbec873a6970acc04135da25dc0961687c1ea6
3
+ size 5340357691
model-00011-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9f4e17717e7f5cbfc2e9eed302b9186119a941912305b299969e9456f9b57d2e
3
+ size 4580898416
model-00012-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ef729fd382ff695d4b4dd1e8466fa63b3501a7b82240da1405153f00da05a2fb
3
+ size 5366984527
model-00013-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a8a27a88c786152b756a58f409d25235dcfb1919623caed6293e00e0cc1de653
3
+ size 5288274785
model-00014-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:34a4957d0b51eacc46972f1e63b3deecc7154216589dad68eca8528b810f9e8e
3
+ size 5332975655
model-00015-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bb16aa41087ae3f8c1e1a058d1362d63cfdf51d70313231f4bb3945621efb3cc
3
+ size 5340357691
model-00016-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4093addda4246e9507b6f02a204124137c7259c2c311f45fb3fd2df117942427
3
+ size 4580898424
model-00017-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:445bab41b10ef1aadbfb543e1c2d78f120b83d9c69b9afa07a7285d7029ed742
3
+ size 5366984507
model-00018-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0ab214c830884865fbc5e7b64caf62f05276e22f5bcd82d4e24e14e0be6b2de1
3
+ size 5288274785
model-00019-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ad12203177007925f8bba9646649df7958454d28912dff4a1808c81bc7405546
3
+ size 5332975643
model-00020-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a440266586705db3c958746bf1c35e2f4c9ef1b3e34232ccf4c9fb30f07f4b32
3
+ size 5340357691
model-00021-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb0a51aa37dd674de27b8c70309aefd0a1611da37bd37a03352a0fd80ece9227
3
+ size 4580898428
model-00022-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:46d337226cf330add4812b95964def57f22f7df06547b7b7f5ffecd83ef68ea8
3
+ size 5366984559
model-00023-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7dc34d8f5225db909865ded57b73f010f53b19fc6edb6a880bd456eed107b1c7
3
+ size 5288274769
model-00024-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6a92836b080c39842fc47f05457226872e4d982e6630519208bc74e44fbcdb51
3
+ size 5332975653
model-00025-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ca3e0b4287d9ca738a1fa72598d3cd0d999cec8a95514b390d3a66084b9a6fcf
3
+ size 5340357631
model-00026-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4eb091aa1967d4697ed264282182bb68cd518b4fca003ad6339cefa603a3cb5c
3
+ size 4580898418
model-00027-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bff671e45348d37edf7fba595bc273ebbd72e232cc83660f1c73c8660fa4b70a
3
+ size 5366984555
model-00028-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c62dfc5e9f1e0ed9ad2099e5860f5f73a203de7b8d579f5b5314104c8a8d8d72
3
+ size 5288274717
model-00029-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6295e6dc53c797b76d6338e05f2c5a007fd6c803d3c52440dd9d362219c9be04
3
+ size 5332975639
model-00030-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aab0889991ab565063dd48a330feb0dd36677fa8ff27c5b0d1dff0563f6a5e96
3
+ size 5340357691
model-00031-of-00031.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0e88801c33483ade614528507912b328a486a6f9eb236c2772dbf8d3f64e8fc9
3
+ size 722890053
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
solar_open2.py ADDED
@@ -0,0 +1,858 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright © 2025 Apple Inc.
2
+ #
3
+ # mlx-lm port of Upstage Solar Open 2 (250B-A15B hybrid MoE).
4
+ #
5
+ # Architecture (ground truth: upstageAI/transformers @ v5.14.1-solar-open2,
6
+ # src/transformers/models/solar_open2/modeling_solar_open2.py):
7
+ # * 48 decoder layers; explicit ``gqa_layers`` (0,4,8,...,44) use NoPE
8
+ # grouped-query full attention, the other 36 use Kimi-Delta-Attention
9
+ # (KDA, gated delta rule with a per-(head, dim) vector decay).
10
+ # * Every layer past ``first_k_dense_replace`` is a MoE layer:
11
+ # 320 routed experts (top-8, sigmoid router with grouped top-k and a
12
+ # non-trainable e_score_correction_bias) + 1 shared expert.
13
+ #
14
+ # Reuse strategy: structure follows mlx_lm/models/kimi_linear.py (same KDA
15
+ # family) with the Solar-specific gate math (lower-bounded log-decay gate,
16
+ # optional beta x2 for negative eigenvalues) and a plain NoPE GQA layer
17
+ # instead of MLA. The recurrent metal kernel / ops fallbacks are imported
18
+ # from mlx_lm/models/gated_delta.py; the MoE feed-forward is
19
+ # mlx_lm/models/switch_layers.py:SwitchGLU so QuantizedSwitchGLU conversion
20
+ # and alis-dwq's SwitchGLU hooks keep working unchanged.
21
+
22
+ from dataclasses import dataclass
23
+ from functools import partial
24
+ from typing import Any, Dict, List, Optional, Tuple
25
+
26
+ import mlx.core as mx
27
+ import mlx.nn as nn
28
+
29
+ from .activations import swiglu
30
+ from .base import (
31
+ BaseModelArgs,
32
+ create_attention_mask,
33
+ create_ssm_mask,
34
+ scaled_dot_product_attention,
35
+ )
36
+ from .cache import ArraysCache, KVCache
37
+ from .gated_delta import gated_delta_kernel, gated_delta_ops
38
+ from .rope_utils import initialize_rope
39
+ from .switch_layers import SwitchGLU
40
+
41
+
42
+ @dataclass
43
+ class ModelArgs(BaseModelArgs):
44
+ model_type: str
45
+ vocab_size: int = 196608
46
+ hidden_size: int = 4096
47
+ num_hidden_layers: int = 48
48
+ num_attention_heads: int = 64
49
+ num_key_value_heads: int = 8
50
+ head_dim: int = 128
51
+ intermediate_size: int = 10240
52
+ moe_intermediate_size: int = 1280
53
+ rms_norm_eps: float = 1e-5
54
+ # Kept for checkpoint-config compatibility; unused unless use_rope=True.
55
+ rope_theta: float = 10000.0
56
+ rope_scaling: Optional[Dict[str, Any]] = None
57
+ partial_rotary_factor: float = 1.0 # was silently dropped by from_dict
58
+ max_position_embeddings: int = 1048576
59
+ tie_word_embeddings: bool = False
60
+ attention_bias: bool = False
61
+ # Full-attention (GQA) layer options.
62
+ use_rope: bool = False # NoPE by default
63
+ use_qk_norm: bool = False
64
+ use_gqa_gate: bool = True
65
+ use_gqa_gate_bias: bool = False
66
+ gqa_interval: int = 4
67
+ gqa_layers: Optional[List[int]] = None # takes priority over gqa_interval
68
+ # KDA (linear attention) options.
69
+ linear_attn_config: Optional[Dict[str, Any]] = None
70
+ kda_use_full_proj: bool = False
71
+ kda_gate_lower_bound: Optional[float] = -5.0
72
+ # HF SolarOpen2Config's *code* default is True (configuration:145; its
73
+ # docstring wrongly says False); the shipped checkpoint config sets it
74
+ # explicitly to true, so real-model behavior is unchanged either way.
75
+ # Match the HF code default.
76
+ kda_allow_neg_eigval: bool = True
77
+ # MoE options.
78
+ n_routed_experts: int = 320
79
+ n_shared_experts: int = 1
80
+ num_experts_per_tok: int = 8
81
+ norm_topk_prob: bool = True
82
+ routed_scaling_factor: float = 1.0
83
+ n_group: int = 1
84
+ topk_group: int = 1
85
+ first_k_dense_replace: int = 0
86
+ hidden_act: str = "silu" # SwitchGLU hard-codes SwiGLU; silu-only port
87
+
88
+ def __post_init__(self):
89
+ if self.hidden_act != "silu":
90
+ raise ValueError(
91
+ f"hidden_act={self.hidden_act!r} is not supported: SwitchGLU "
92
+ "and SolarMLP hard-code SwiGLU (silu-only port)."
93
+ )
94
+ if self.linear_attn_config is None:
95
+ self.linear_attn_config = {
96
+ "short_conv_kernel_size": 4,
97
+ "head_dim": self.head_dim,
98
+ "num_heads": self.num_attention_heads,
99
+ "num_kv_heads": None,
100
+ }
101
+ # Per-layer attention pattern, mirroring SolarOpen2Config.__post_init__.
102
+ if self.gqa_layers is not None:
103
+ full = set(self.gqa_layers)
104
+ self.layer_types = [
105
+ "full_attention" if i in full else "linear_attention"
106
+ for i in range(self.num_hidden_layers)
107
+ ]
108
+ else:
109
+ self.layer_types = [
110
+ (
111
+ "full_attention"
112
+ if (i + 1) % self.gqa_interval == 0
113
+ else "linear_attention"
114
+ )
115
+ for i in range(self.num_hidden_layers)
116
+ ]
117
+
118
+
119
+ # --------------------------------------------------------------------------
120
+ # KDA helpers
121
+ # --------------------------------------------------------------------------
122
+
123
+
124
+ @partial(mx.compile, shapeless=True)
125
+ def _kda_decay_gate(
126
+ g_raw: mx.array,
127
+ A_log: mx.array,
128
+ dt_bias: mx.array,
129
+ lower_bound: Optional[float],
130
+ ) -> mx.array:
131
+ """Solar Open 2 KDA log-decay gate (reference: ``torch_kda_gate`` +
132
+ ``fla.ops.kda.gate.fused_kda_gate``).
133
+
134
+ ``g = -exp(A_log) * softplus(g_raw + dt_bias)`` computed in float32,
135
+ clamped from below at ``lower_bound`` (-5.0 by default), then
136
+ exponentiated once so the result is the multiplicative per-(head, dim)
137
+ decay consumed by ``gated_delta_ops`` / ``gated_delta_kernel``.
138
+
139
+ Differs from kimi_linear's ``compute_g``: that variant has no lower-bound
140
+ clamp because Kimi Linear leaves the gate unclamped.
141
+ """
142
+ g = g_raw.astype(mx.float32) + dt_bias.astype(mx.float32)
143
+ g = -mx.exp(A_log.astype(mx.float32)) * nn.softplus(g)
144
+ if lower_bound is not None:
145
+ g = mx.maximum(g, mx.array(lower_bound, dtype=mx.float32))
146
+ return mx.exp(g)
147
+
148
+
149
+ @partial(mx.compile, shapeless=True)
150
+ def _l2norm(x: mx.array, eps: float) -> mx.array:
151
+ """x * rsqrt(sum(x^2) + eps) computed in float32 (HF l2norm reference)."""
152
+ xf = x.astype(mx.float32)
153
+ return xf * mx.rsqrt((xf * xf).sum(axis=-1, keepdims=True) + eps)
154
+
155
+
156
+ class SolarRMSNormGated(nn.Module):
157
+ """Gated RMSNorm at the KDA output: ``RMSNorm(x) * sigmoid(gate)``,
158
+ computed in float32 like ``SolarOpen2RMSNormGated`` /
159
+ ``fla.modules.FusedRMSNormGated(activation="sigmoid")``."""
160
+
161
+ def __init__(self, hidden_size: int, eps: float = 1e-5):
162
+ super().__init__()
163
+ self.weight = mx.ones((hidden_size,))
164
+ self.eps = eps
165
+
166
+ def __call__(self, x: mx.array, gate: mx.array) -> mx.array:
167
+ return _gated_rms_norm(x, self.weight, gate, self.eps)
168
+
169
+
170
+ @partial(mx.compile, shapeless=True)
171
+ def _gated_rms_norm(x, weight, gate, eps):
172
+ xf = x.astype(mx.float32)
173
+ variance = (xf * xf).mean(axis=-1, keepdims=True)
174
+ xf = xf * mx.rsqrt(variance + eps)
175
+ xf = xf * weight.astype(mx.float32) * mx.sigmoid(gate.astype(mx.float32))
176
+ return xf.astype(x.dtype)
177
+
178
+
179
+ class ShortConv1d(nn.Module):
180
+ """Depthwise causal short conv (silu, no bias) with a (kernel - 1)-token
181
+ recurrent state. Identical to kimi_linear.ShortConv1d; kept local so the
182
+ port is self-contained."""
183
+
184
+ def __init__(self, channels: int, kernel_size: int):
185
+ super().__init__()
186
+ self.kernel_size = kernel_size
187
+ self.conv = nn.Conv1d(
188
+ in_channels=channels,
189
+ out_channels=channels,
190
+ kernel_size=kernel_size,
191
+ bias=False,
192
+ groups=channels,
193
+ padding=0,
194
+ )
195
+
196
+ def __call__(
197
+ self,
198
+ x: mx.array,
199
+ state: Optional[mx.array],
200
+ mask: Optional[mx.array],
201
+ lengths: Optional[mx.array],
202
+ ) -> Tuple[mx.array, mx.array]:
203
+ if mask is not None:
204
+ x = mx.where(mask[..., None], x, 0)
205
+
206
+ if state is None:
207
+ state = mx.zeros(
208
+ (x.shape[0], self.kernel_size - 1, x.shape[-1]), dtype=x.dtype
209
+ )
210
+ conv_input = mx.concatenate([state, x], axis=1)
211
+ out = nn.silu(self.conv(conv_input))
212
+ n_keep = self.kernel_size - 1
213
+ if lengths is not None:
214
+ ends = mx.clip(lengths, 0, x.shape[1])
215
+ positions = (ends[:, None] + mx.arange(n_keep))[..., None]
216
+ new_state = mx.take_along_axis(conv_input, positions, axis=1)
217
+ else:
218
+ new_state = mx.contiguous(conv_input[:, -n_keep:, :])
219
+
220
+ return out, new_state
221
+
222
+
223
+ class SolarDeltaAttention(nn.Module):
224
+ """Kimi-Delta-Attention layer as configured for Solar Open 2.
225
+
226
+ Differences vs kimi_linear.KimiDeltaAttention:
227
+ * optional GQA-style kv heads (``linear_attn_config.num_kv_heads``;
228
+ Solar Open 2 sets it to null -> equal to num_heads),
229
+ * ``kda_use_full_proj`` switches between full-rank ``f_proj``/``g_proj``
230
+ and the factored low-rank ``f_a_proj``/``f_b_proj`` /
231
+ ``g_a_proj``/``g_b_proj`` (Solar Open 2 uses the factored form),
232
+ * the decay gate is lower-bounded (``kda_gate_lower_bound=-5.0``),
233
+ * ``kda_allow_neg_eigval`` scales beta by 2 (beta in [0, 2]).
234
+ """
235
+
236
+ def __init__(self, args: ModelArgs, layer_idx: int):
237
+ super().__init__()
238
+ cfg = args.linear_attn_config
239
+
240
+ self.layer_idx = layer_idx
241
+ self.num_heads = cfg["num_heads"]
242
+ self.head_dim = cfg["head_dim"]
243
+ self.num_kv_heads = cfg.get("num_kv_heads") or self.num_heads
244
+ self.n_rep = self.num_heads // self.num_kv_heads
245
+ self.conv_kernel = cfg.get("short_conv_kernel_size", 4)
246
+ self.use_full_proj = args.kda_use_full_proj
247
+ self.gate_lower_bound = args.kda_gate_lower_bound
248
+ self.allow_neg_eigval = args.kda_allow_neg_eigval
249
+
250
+ self.projection_dim = self.num_heads * self.head_dim
251
+ self.kv_projection_dim = self.num_kv_heads * self.head_dim
252
+ hidden = args.hidden_size
253
+
254
+ self.scale = float(self.head_dim) ** -0.5
255
+
256
+ self.q_proj = nn.Linear(hidden, self.projection_dim, bias=False)
257
+ self.k_proj = nn.Linear(hidden, self.kv_projection_dim, bias=False)
258
+ self.v_proj = nn.Linear(hidden, self.kv_projection_dim, bias=False)
259
+ self.b_proj = nn.Linear(hidden, self.num_heads, bias=False)
260
+
261
+ self.q_conv = ShortConv1d(self.projection_dim, self.conv_kernel)
262
+ self.k_conv = ShortConv1d(self.kv_projection_dim, self.conv_kernel)
263
+ self.v_conv = ShortConv1d(self.kv_projection_dim, self.conv_kernel)
264
+
265
+ if self.use_full_proj:
266
+ self.f_proj = nn.Linear(hidden, self.projection_dim, bias=False)
267
+ self.g_proj = nn.Linear(hidden, self.projection_dim, bias=False)
268
+ else:
269
+ self.f_a_proj = nn.Linear(hidden, self.head_dim, bias=False)
270
+ self.f_b_proj = nn.Linear(self.head_dim, self.projection_dim, bias=False)
271
+ self.g_a_proj = nn.Linear(hidden, self.head_dim, bias=False)
272
+ self.g_b_proj = nn.Linear(self.head_dim, self.projection_dim, bias=False)
273
+
274
+ # Shapes match the HF checkpoint: A_log (1, 1, H, 1), dt_bias (H*Dh,).
275
+ self.A_log = mx.expand_dims(
276
+ mx.log(mx.random.uniform(low=1.0, high=16.0, shape=(self.num_heads,))),
277
+ (0, 1, 3),
278
+ )
279
+ self.dt_bias = mx.ones((self.projection_dim,))
280
+
281
+ self.o_norm = SolarRMSNormGated(self.head_dim, eps=args.rms_norm_eps)
282
+ self.o_proj = nn.Linear(self.projection_dim, hidden, bias=False)
283
+
284
+ def __call__(
285
+ self,
286
+ x: mx.array,
287
+ mask: Optional[mx.array] = None,
288
+ cache: Optional[Any] = None,
289
+ ) -> mx.array:
290
+ B, T, _ = x.shape
291
+ dtype = x.dtype
292
+
293
+ if cache is not None:
294
+ q_state, k_state, v_state, ssm_state = cache
295
+ lengths = cache.lengths
296
+ else:
297
+ q_state = k_state = v_state = ssm_state = None
298
+ lengths = None
299
+
300
+ q_conv, q_state = self.q_conv(self.q_proj(x), q_state, mask, lengths)
301
+ k_conv, k_state = self.k_conv(self.k_proj(x), k_state, mask, lengths)
302
+ v_conv, v_state = self.v_conv(self.v_proj(x), v_state, mask, lengths)
303
+
304
+ if cache is not None:
305
+ cache[0] = q_state
306
+ cache[1] = k_state
307
+ cache[2] = v_state
308
+
309
+ q = q_conv.reshape(B, T, self.num_heads, self.head_dim)
310
+ k = k_conv.reshape(B, T, self.num_kv_heads, self.head_dim)
311
+ v = v_conv.reshape(B, T, self.num_kv_heads, self.head_dim)
312
+
313
+ # l2-normalize q/k exactly like the reference
314
+ # (use_qk_l2norm_in_kernel=True): x * rsqrt(sum(x^2) + eps) in f32.
315
+ # Folding this through mx.fast.rms_norm is NOT equivalent: rms_norm
316
+ # puts eps under the mean (i.e. D*eps on the sum), which measurably
317
+ # shifts small-norm head vectors (validated on real weights, see
318
+ # VALIDATION_RESULTS.md). q/k/v go into the recurrence in f32,
319
+ # mirroring the f32 casts in torch_recurrent_kda.
320
+ q = _l2norm(q, 1e-6) * self.scale
321
+ k = _l2norm(k, 1e-6)
322
+ v = v.astype(mx.float32)
323
+
324
+ if self.use_full_proj:
325
+ g_raw = self.f_proj(x)
326
+ else:
327
+ g_raw = self.f_b_proj(self.f_a_proj(x))
328
+ g_raw = g_raw.reshape(B, T, self.num_heads, self.head_dim)
329
+
330
+ beta = mx.sigmoid(self.b_proj(x))
331
+ if self.allow_neg_eigval:
332
+ beta = beta * 2.0
333
+
334
+ g = _kda_decay_gate(
335
+ g_raw,
336
+ self.A_log.reshape(self.num_heads, 1),
337
+ self.dt_bias.reshape(self.num_heads, self.head_dim),
338
+ self.gate_lower_bound,
339
+ )
340
+
341
+ if ssm_state is None:
342
+ ssm_state = mx.zeros(
343
+ (B, self.num_kv_heads, self.head_dim, self.head_dim),
344
+ dtype=mx.float32,
345
+ )
346
+
347
+ if (
348
+ self.training
349
+ or mx.default_device() != mx.gpu
350
+ or not mx.metal.is_available()
351
+ ):
352
+ out, ssm_state = gated_delta_ops(q, k, v, g, beta, ssm_state, mask)
353
+ else:
354
+ out, ssm_state = gated_delta_kernel(q, k, v, g, beta, ssm_state, mask)
355
+
356
+ if cache is not None:
357
+ cache[3] = ssm_state
358
+ cache.advance(T)
359
+
360
+ if self.use_full_proj:
361
+ g_out = self.g_proj(x)
362
+ else:
363
+ g_out = self.g_b_proj(self.g_a_proj(x))
364
+ g_out = g_out.reshape(B, T, self.num_heads, self.head_dim)
365
+
366
+ out = self.o_norm(out.reshape(B, T, self.num_heads, self.head_dim), g_out)
367
+ out = out.reshape(B, T, -1).astype(dtype)
368
+ return self.o_proj(out)
369
+
370
+
371
+ class SolarFullAttention(nn.Module):
372
+ """Grouped-query full attention: NoPE by default (use_rope=False),
373
+ optional per-head qk RMSNorm (off in Solar Open 2), and a sigmoid output
374
+ gate (``use_gqa_gate=True``: ``attn_out * sigmoid(g_proj(x))``)."""
375
+
376
+ def __init__(self, args: ModelArgs, layer_idx: int):
377
+ super().__init__()
378
+ self.layer_idx = layer_idx
379
+ self.num_heads = args.num_attention_heads
380
+ self.num_key_value_heads = args.num_key_value_heads
381
+ self.head_dim = args.head_dim
382
+ self.scale = float(self.head_dim) ** -0.5
383
+ self.use_gqa_gate = args.use_gqa_gate
384
+ self.use_qk_norm = args.use_qk_norm
385
+ self.use_rope = args.use_rope
386
+
387
+ hidden = args.hidden_size
388
+ self.q_proj = nn.Linear(
389
+ hidden, self.num_heads * self.head_dim, bias=args.attention_bias
390
+ )
391
+ self.k_proj = nn.Linear(
392
+ hidden, self.num_key_value_heads * self.head_dim, bias=args.attention_bias
393
+ )
394
+ self.v_proj = nn.Linear(
395
+ hidden, self.num_key_value_heads * self.head_dim, bias=args.attention_bias
396
+ )
397
+ self.o_proj = nn.Linear(self.num_heads * self.head_dim, hidden, bias=False)
398
+
399
+ if self.use_qk_norm:
400
+ self.q_norm = nn.RMSNorm(self.head_dim, eps=args.rms_norm_eps)
401
+ self.k_norm = nn.RMSNorm(self.head_dim, eps=args.rms_norm_eps)
402
+
403
+ if self.use_rope:
404
+ self.rope = initialize_rope(
405
+ int(self.head_dim * args.partial_rotary_factor),
406
+ base=args.rope_theta,
407
+ traditional=False,
408
+ scaling_config=args.rope_scaling,
409
+ max_position_embeddings=args.max_position_embeddings,
410
+ )
411
+ else:
412
+ self.rope = None
413
+
414
+ if self.use_gqa_gate:
415
+ self.g_proj = nn.Linear(
416
+ hidden,
417
+ self.num_heads * self.head_dim,
418
+ bias=args.use_gqa_gate_bias,
419
+ )
420
+
421
+ def __call__(
422
+ self,
423
+ x: mx.array,
424
+ mask: Optional[mx.array] = None,
425
+ cache: Optional[KVCache] = None,
426
+ ) -> mx.array:
427
+ B, L, _ = x.shape
428
+
429
+ q = self.q_proj(x).reshape(B, L, self.num_heads, self.head_dim)
430
+ k = self.k_proj(x).reshape(B, L, self.num_key_value_heads, self.head_dim)
431
+ v = self.v_proj(x).reshape(B, L, self.num_key_value_heads, self.head_dim)
432
+
433
+ if self.use_qk_norm:
434
+ q = self.q_norm(q)
435
+ k = self.k_norm(k)
436
+
437
+ q = q.transpose(0, 2, 1, 3)
438
+ k = k.transpose(0, 2, 1, 3)
439
+ v = v.transpose(0, 2, 1, 3)
440
+
441
+ if self.rope is not None:
442
+ offset = cache.offset if cache is not None else 0
443
+ q = self.rope(q, offset=offset)
444
+ k = self.rope(k, offset=offset)
445
+
446
+ if cache is not None:
447
+ k, v = cache.update_and_fetch(k, v)
448
+
449
+ out = scaled_dot_product_attention(q, k, v, cache=cache, scale=self.scale, mask=mask)
450
+ out = out.transpose(0, 2, 1, 3).reshape(B, L, -1)
451
+
452
+ if self.use_gqa_gate:
453
+ out = out * mx.sigmoid(self.g_proj(x))
454
+
455
+ return self.o_proj(out)
456
+
457
+
458
+ # --------------------------------------------------------------------------
459
+ # MoE
460
+ # --------------------------------------------------------------------------
461
+
462
+
463
+ class SolarMLP(nn.Module):
464
+ def __init__(
465
+ self,
466
+ args: ModelArgs,
467
+ hidden_size: Optional[int] = None,
468
+ intermediate_size: Optional[int] = None,
469
+ ):
470
+ super().__init__()
471
+ dim = hidden_size or args.hidden_size
472
+ hidden = intermediate_size or args.intermediate_size
473
+ self.gate_proj = nn.Linear(dim, hidden, bias=False)
474
+ self.up_proj = nn.Linear(dim, hidden, bias=False)
475
+ self.down_proj = nn.Linear(hidden, dim, bias=False)
476
+
477
+ def __call__(self, x: mx.array) -> mx.array:
478
+ return self.down_proj(swiglu(self.gate_proj(x), self.up_proj(x)))
479
+
480
+
481
+ @mx.compile
482
+ def _expert_select(
483
+ gates: mx.array,
484
+ bias: Optional[mx.array],
485
+ top_k: int,
486
+ n_group: int,
487
+ topk_group: int,
488
+ routed_scaling_factor: float,
489
+ renormalize: bool,
490
+ ) -> Tuple[mx.array, mx.array]:
491
+ """Sigmoid router with optional grouped top-k (SolarOpen2TopkRouter).
492
+
493
+ Selection uses bias-adjusted scores; the returned weights are the raw
494
+ sigmoid scores at the selected experts (bias only affects *which* experts
495
+ are picked), optionally renormalized (``norm_topk_prob``) and scaled by
496
+ ``routed_scaling_factor``.
497
+ """
498
+ scores = mx.sigmoid(gates)
499
+ orig_scores = scores
500
+ if bias is not None:
501
+ scores = scores + bias.astype(scores.dtype)
502
+
503
+ if n_group > 1:
504
+ scores = mx.unflatten(scores, axis=-1, shape=(n_group, -1))
505
+ group_scores = mx.topk(scores, 2, axis=-1).sum(axis=-1, keepdims=True)
506
+ k = n_group - topk_group
507
+ group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-2)[..., :k, :]
508
+ # HF masks dropped groups with -inf (masked_fill), not 0.0: with a
509
+ # negative e_score_correction_bias an unmasked adjusted score can be
510
+ # < 0, and a 0.0 mask would then pick a MASKED expert HF never picks
511
+ # (attacks/attack6 A6a).
512
+ scores = mx.put_along_axis(
513
+ scores,
514
+ mx.stop_gradient(group_idx),
515
+ mx.array(-float("inf"), dtype=scores.dtype),
516
+ axis=-2,
517
+ )
518
+ scores = mx.flatten(scores, -2, -1)
519
+
520
+ inds = mx.argpartition(-scores, kth=top_k - 1, axis=-1)[..., :top_k]
521
+ weights = mx.take_along_axis(orig_scores, inds, axis=-1)
522
+
523
+ if top_k > 1 and renormalize:
524
+ weights = weights / (weights.sum(axis=-1, keepdims=True) + 1e-20)
525
+
526
+ return inds, weights * routed_scaling_factor
527
+
528
+
529
+ class SolarSparseMoE(nn.Module):
530
+ """Routed MoE + shared expert.
531
+
532
+ Naming contract (kept identical to kimi_linear so alis-dwq's hooks match):
533
+ * ``gate`` -- router Linear; module path ``...mlp.gate`` matches
534
+ alis-dwq's ``(?:^|\.)(?:gate|router)$`` router regex
535
+ * ``switch_mlp`` -- SwitchGLU instance (expert_traffic hooks the class)
536
+ * ``shared_experts``
537
+ * ``e_score_correction_bias`` lives on this module (not on ``gate``) so
538
+ an 8-bit quantization of ``gate`` cannot drop it; ``sanitize`` moves
539
+ the HF key ``...mlp.gate.e_score_correction_bias`` here.
540
+ """
541
+
542
+ def __init__(self, args: ModelArgs):
543
+ super().__init__()
544
+ self.args = args
545
+ hidden = args.hidden_size
546
+ experts = args.n_routed_experts
547
+
548
+ self.gate = nn.Linear(hidden, experts, bias=False)
549
+ self.switch_mlp = SwitchGLU(hidden, args.moe_intermediate_size, experts)
550
+ self.e_score_correction_bias = mx.zeros((experts,), dtype=mx.float32)
551
+
552
+ if args.n_shared_experts:
553
+ shared_hidden = args.moe_intermediate_size * args.n_shared_experts
554
+ self.shared_experts = SolarMLP(args, intermediate_size=shared_hidden)
555
+ else:
556
+ self.shared_experts = None
557
+
558
+ def __call__(self, x: mx.array) -> mx.array:
559
+ # HF computes the router logits in float32:
560
+ # F.linear(hidden_states.float(), gate.weight.float())
561
+ # Mirror that exactly — a bf16 matmul's reduction-order noise flips
562
+ # razor-tie routing decisions (attacks/attack1b: 7/64 tokens flipped
563
+ # vs HF f32 logits; both-sides-f32 gives 0/64). A quantized router
564
+ # (packed uint32 weight) cannot upcast, so that path keeps the
565
+ # activation-dtype matmul with an f32 cast of the logits.
566
+ if hasattr(self.gate, "bits"):
567
+ gates = self.gate(x).astype(mx.float32)
568
+ else:
569
+ gates = x.astype(mx.float32) @ self.gate.weight.astype(mx.float32).T
570
+ inds, weights = _expert_select(
571
+ gates,
572
+ self.e_score_correction_bias,
573
+ self.args.num_experts_per_tok,
574
+ self.args.n_group,
575
+ self.args.topk_group,
576
+ self.args.routed_scaling_factor,
577
+ self.args.norm_topk_prob,
578
+ )
579
+ out = self.switch_mlp(x, inds)
580
+ # HF casts the weighted expert sum back to the activation dtype.
581
+ out = (out * weights[..., None]).sum(axis=-2).astype(x.dtype)
582
+ if self.shared_experts is not None:
583
+ out = out + self.shared_experts(x)
584
+ return out
585
+
586
+
587
+ class SolarDecoderLayer(nn.Module):
588
+ def __init__(self, args: ModelArgs, layer_idx: int):
589
+ super().__init__()
590
+ self.is_linear = args.layer_types[layer_idx] == "linear_attention"
591
+
592
+ if self.is_linear:
593
+ self.self_attn = SolarDeltaAttention(args, layer_idx)
594
+ else:
595
+ self.self_attn = SolarFullAttention(args, layer_idx)
596
+
597
+ if layer_idx >= args.first_k_dense_replace:
598
+ self.mlp = SolarSparseMoE(args)
599
+ else:
600
+ self.mlp = SolarMLP(args)
601
+
602
+ self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
603
+ self.post_attention_layernorm = nn.RMSNorm(
604
+ args.hidden_size, eps=args.rms_norm_eps
605
+ )
606
+
607
+ def __call__(
608
+ self,
609
+ x: mx.array,
610
+ mask: Optional[mx.array] = None,
611
+ cache: Optional[Any] = None,
612
+ ) -> mx.array:
613
+ y = self.self_attn(self.input_layernorm(x), mask, cache)
614
+ h = x + y
615
+ z = self.mlp(self.post_attention_layernorm(h))
616
+ return h + z
617
+
618
+
619
+ class SolarOpen2Model(nn.Module):
620
+ def __init__(self, args: ModelArgs):
621
+ super().__init__()
622
+ self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size)
623
+ self.layers = [SolarDecoderLayer(args, i) for i in range(args.num_hidden_layers)]
624
+ self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
625
+ self.ssm_idx = None
626
+ self.attn_idx = None
627
+ for i, layer in enumerate(self.layers):
628
+ if layer.is_linear:
629
+ self.ssm_idx = i
630
+ break
631
+ for i, layer in enumerate(self.layers):
632
+ if not layer.is_linear:
633
+ self.attn_idx = i
634
+ break
635
+
636
+ def __call__(
637
+ self,
638
+ inputs: mx.array,
639
+ cache: Optional[List[Any]] = None,
640
+ ) -> mx.array:
641
+ h = self.embed_tokens(inputs)
642
+ if cache is None:
643
+ cache = [None] * len(self.layers)
644
+
645
+ ssm_mask = (
646
+ create_ssm_mask(h, cache[self.ssm_idx])
647
+ if self.ssm_idx is not None
648
+ else None
649
+ )
650
+ attn_mask = (
651
+ create_attention_mask(h, cache[self.attn_idx])
652
+ if self.attn_idx is not None
653
+ else None
654
+ )
655
+
656
+ for layer, layer_cache in zip(self.layers, cache):
657
+ mask = ssm_mask if layer.is_linear else attn_mask
658
+ h = layer(h, mask=mask, cache=layer_cache)
659
+
660
+ return self.norm(h)
661
+
662
+
663
+ def sanitize_layer_weights(
664
+ weights: Dict[str, mx.array],
665
+ prefix: str,
666
+ layer: "SolarDecoderLayer",
667
+ num_experts: int,
668
+ ) -> Dict[str, mx.array]:
669
+ """In-place HF -> MLX key conversion for one decoder layer.
670
+
671
+ Handles both on-disk expert layouts:
672
+ * per-expert tensors (the actual upstage/Solar-Open2-250B checkpoint):
673
+ ``{prefix}.mlp.experts.{i}.{gate,up,down}_proj.weight`` -> stacked
674
+ ``switch_mlp.{gate,up,down}_proj.weight``;
675
+ * fused 3D tensors (module layout of the HF modeling code):
676
+ ``experts.gate_up_proj`` (E, 2I, H) split into gate/up, and
677
+ ``experts.down_proj`` renamed.
678
+ """
679
+ if isinstance(layer.mlp, SolarSparseMoE):
680
+ mlp_prefix = f"{prefix}.mlp"
681
+
682
+ if f"{mlp_prefix}.experts.0.gate_proj.weight" in weights:
683
+ # Per-expert layout: refuse silently-misconverted pre-quantized
684
+ # tensors (scales/biases companions would be left behind and the
685
+ # stacked uint32 "weights" would be nonsense).
686
+ if any(
687
+ k.startswith(f"{mlp_prefix}.experts.")
688
+ and (k.endswith(".scales") or k.endswith(".biases"))
689
+ for k in weights
690
+ ):
691
+ raise NotImplementedError(
692
+ "Pre-quantized HF expert tensors are not supported by "
693
+ "this port; load the BF16 checkpoint and quantize "
694
+ "with mlx-lm instead."
695
+ )
696
+ for name in ("gate_proj", "up_proj", "down_proj"):
697
+ stacked = [
698
+ weights.pop(f"{mlp_prefix}.experts.{i}.{name}.weight")
699
+ for i in range(num_experts)
700
+ ]
701
+ weights[f"{mlp_prefix}.switch_mlp.{name}.weight"] = mx.stack(stacked)
702
+ else:
703
+ if f"{mlp_prefix}.experts.gate_up_proj.scales" in weights:
704
+ raise NotImplementedError(
705
+ "Pre-quantized HF expert tensors are not supported by "
706
+ "this port; load the BF16 checkpoint and quantize "
707
+ "with mlx-lm instead."
708
+ )
709
+ gu_key = f"{mlp_prefix}.experts.gate_up_proj"
710
+ if gu_key in weights:
711
+ gu = weights.pop(gu_key)
712
+ # mlx#3836: mx.split silently corrupts >2**31-element tensors;
713
+ # a fused HF-layout gate_up_proj (E, 2I, H) is 1.56x that for
714
+ # this model. Use strided slices instead.
715
+ half = gu.shape[1] // 2
716
+ gate_w, up_w = gu[:, :half], gu[:, half:]
717
+ weights[f"{mlp_prefix}.switch_mlp.gate_proj.weight"] = mx.contiguous(
718
+ gate_w
719
+ )
720
+ weights[f"{mlp_prefix}.switch_mlp.up_proj.weight"] = mx.contiguous(
721
+ up_w
722
+ )
723
+ down_key = f"{mlp_prefix}.experts.down_proj"
724
+ if down_key in weights:
725
+ weights[f"{mlp_prefix}.switch_mlp.down_proj.weight"] = weights.pop(
726
+ down_key
727
+ )
728
+
729
+ # Router bias lives on the MoE block in MLX (see class note).
730
+ bias_key = f"{mlp_prefix}.gate.e_score_correction_bias"
731
+ if bias_key in weights:
732
+ weights[f"{mlp_prefix}.e_score_correction_bias"] = weights.pop(bias_key)
733
+
734
+ attn = layer.self_attn
735
+ if isinstance(attn, SolarDeltaAttention):
736
+ attn_prefix = f"{prefix}.self_attn"
737
+ for src_name, dst_name in (
738
+ ("q_conv1d", "q_conv"),
739
+ ("k_conv1d", "k_conv"),
740
+ ("v_conv1d", "v_conv"),
741
+ ):
742
+ src_key = f"{attn_prefix}.{src_name}.weight"
743
+ if src_key in weights:
744
+ w = weights.pop(src_key)
745
+ if w.ndim == 3:
746
+ # HF depthwise conv weight (C, 1, K) ->
747
+ # MLX Conv1d weight (C, K, 1)
748
+ w = w.moveaxis(2, 1)
749
+ weights[f"{attn_prefix}.{dst_name}.conv.weight"] = w
750
+ dt_key = f"{attn_prefix}.dt_bias"
751
+ if dt_key in weights and weights[dt_key].ndim > 1:
752
+ weights[dt_key] = mx.reshape(weights[dt_key], (-1,))
753
+
754
+ return weights
755
+
756
+
757
+ class Model(nn.Module):
758
+ def __init__(self, args: ModelArgs):
759
+ super().__init__()
760
+ self.args = args
761
+ self.model_type = args.model_type
762
+ self.model = SolarOpen2Model(args)
763
+ if args.tie_word_embeddings:
764
+ self.lm_head = None
765
+ else:
766
+ self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False)
767
+
768
+ def __call__(
769
+ self,
770
+ inputs: mx.array,
771
+ cache: Optional[List[Any]] = None,
772
+ ) -> mx.array:
773
+ out = self.model(inputs, cache)
774
+ if self.lm_head is None:
775
+ return self.model.embed_tokens.as_linear(out)
776
+ return self.lm_head(out)
777
+
778
+ @property
779
+ def layers(self):
780
+ return self.model.layers
781
+
782
+ def make_cache(self):
783
+ caches: List[Any] = []
784
+ for layer in self.layers:
785
+ if layer.is_linear:
786
+ # [q_conv_state, k_conv_state, v_conv_state, recurrent_state]
787
+ caches.append(ArraysCache(size=4))
788
+ else:
789
+ caches.append(KVCache())
790
+ return caches
791
+
792
+ def sanitize(self, weights: Dict[str, mx.array]) -> Dict[str, mx.array]:
793
+ # Drop non-persistent HF rope buffers if a checkpoint carries them.
794
+ weights = {k: v for k, v in weights.items() if "rotary_emb.inv_freq" not in k}
795
+
796
+ if self.args.tie_word_embeddings:
797
+ weights.pop("lm_head.weight", None)
798
+
799
+ for layer_idx, layer in enumerate(self.layers):
800
+ sanitize_layer_weights(
801
+ weights,
802
+ f"model.layers.{layer_idx}",
803
+ layer,
804
+ self.args.n_routed_experts,
805
+ )
806
+
807
+ return weights
808
+
809
+ @property
810
+ def cast_predicate(self):
811
+ def predicate(path: str):
812
+ if "e_score_correction_bias" in path:
813
+ return False
814
+ if path.endswith("A_log") or path.endswith("dt_bias"):
815
+ return False
816
+ return True
817
+
818
+ return predicate
819
+
820
+ def quant_predicate_builder(self, group_size: int, bits: int):
821
+ """Per-tensor bit assignment for Solar-Open2.
822
+
823
+ The CLI global ``bits``/``group_size`` apply to the routed experts
824
+ only (they dominate the parameter count, so they alone decide the
825
+ model size). Everything else follows the runbook intent — sensitive
826
+ paths run one tier up:
827
+
828
+ - router (``mlp.gate``) → 8b/g64: routing is selection-sensitive
829
+ and the tensor is tiny (320 x 4096 per layer).
830
+ - KDA low-rank chokepoints (``f_a/f_b/g_a/g_b/b_proj``) → 8b/g64:
831
+ these feed the recurrent state, so quantization error compounds
832
+ across time steps.
833
+ - ``embed_tokens`` / ``lm_head`` → 6b/g64 (8b when the global is 8).
834
+ - attention q/k/v/o, GQA elementwise gate (``g_proj``), and the
835
+ shared expert → global bits + 2 (capped at 8).
836
+
837
+ At bits=8/g64 this reproduces the uniform 8-bit map, so the recipe
838
+ is a no-op for the reference build.
839
+ """
840
+ attn_bits = min(bits + 2, 8)
841
+ embed_bits = max(6, min(bits, 8))
842
+ chokepoint = ("f_a_proj", "f_b_proj", "g_a_proj", "g_b_proj")
843
+ upper = ("q_proj", "k_proj", "v_proj", "o_proj", "g_proj", "shared_experts")
844
+
845
+ def predicate(path, _):
846
+ if path.endswith("mlp.gate"):
847
+ return {"group_size": 64, "bits": 8}
848
+ if "switch_mlp" in path:
849
+ return True # routed experts take the global CLI values
850
+ if any(k in path for k in chokepoint) or path.endswith("b_proj"):
851
+ return {"group_size": 64, "bits": 8}
852
+ if "embed_tokens" in path or "lm_head" in path:
853
+ return {"group_size": 64, "bits": embed_bits}
854
+ if any(k in path for k in upper):
855
+ return {"group_size": 64, "bits": attn_bits}
856
+ return True
857
+
858
+ return predicate
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c7b7cb72447bbe1f8d856adaf34933acfdedfa89c7fe1d95d0fdabedf9d19a94
3
+ size 16473585
tokenizer_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|startoftext|>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "eos_token": "<|endoftext|>",
6
+ "is_local": true,
7
+ "local_files_only": false,
8
+ "model_max_length": 1048576,
9
+ "pad_token": "<|endoftext|>",
10
+ "padding_side": "left",
11
+ "split_special_tokens": false,
12
+ "tokenizer_class": "TokenizersBackend",
13
+ "unk_token": "<unk>"
14
+ }