philipjohnbasile commited on
Commit
f63cb8e
·
verified ·
1 Parent(s): 404ff98

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: tencent/Hy3
4
+ library_name: mlx
5
+ pipeline_tag: text-generation
6
+ language: en
7
+ tags:
8
+ - mlx
9
+ - apple-silicon
10
+ - hy3
11
+ - mixture-of-experts
12
+ - mtp
13
+ - speculative-decoding
14
+ - mtplx
15
+ ---
16
+
17
+ # Hy3-Demolition-MLX lite-v1-mtp
18
+
19
+ The **MTP-equipped** variant of [lite-v1](https://huggingface.co/philipjohnbasile/hy3-demolition-mlx-lite-v1): its fused trunk with the Hy3 **NextN (Multi-Token-Prediction) sidecar** grafted back on (`num_nextn_predict_layers=1`, num_experts=192), for self-speculative decoding on MTPLX.
20
+
21
+ ## Status — honest
22
+
23
+ This artifact is **MTP-equipped but not yet end-to-end runnable**. `mtplx inspect` (release v2.0.1) recognizes it — *"HY V3 MTP markers recognized, but MTPLX does not yet have a native MLX runtime backend for this family"* — i.e. it is **`recognized-backend-pending`**. The native `hy_v3` runtime backend is in flight as [MTPLX PR #142](https://github.com/youssofal/MTPLX/pull/142), gated on `hy_v3` reaching MTPLX's mlx-lm pin. Until that lands, use the AR daily-driver ([lite-v1](https://huggingface.co/philipjohnbasile/hy3-demolition-mlx-lite-v1)) instead.
24
+
25
+ ## How it was built
26
+
27
+ The MTP head consumes the trunk's final hidden state (hidden_size 4096, unchanged by pruning) and runs its own MoE on the global `num_experts`. So the base checkpoint's `mtp.*` sidecar grafts directly onto the fused AR trunk — no re-heal, no re-prune of the trunk.
28
+
29
+
30
+ Graft script + `mtplx inspect` receipts: https://github.com/PhilipJohnBasile/hy3-demolition-mlx (`scripts/38_mtp_sidecar_graft.py`, `eval/receipts/mtplx_inspect_*.json`).
31
+
32
+ ## Limitations
33
+
34
+ - **Does not run on stock mlx_lm as MTP.** The fast MTP path needs the MTPLX backend; mlx-lm's own per-token self-speculative loop is ~4.7× *slower* than AR (measured), which is why the MTPLX batched-verify backend is the target.
35
+ - Everything from the base lite-v1 card applies (quantized MoE, English/agent focus, no tool execution).
36
+ - End-to-end MTP behavior is **unverified** until the backend loads it; recognition is structural (`mtplx inspect`), not a live run.
37
+
38
+ Base recipe + receipts: https://github.com/PhilipJohnBasile/hy3-demolition-mlx
chat_template.jinja ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {#- ----------‑‑‑ special token variables ‑‑‑---------- -#}
2
+ {%- set HYTK = ':opensource' %}
3
+ {%- set eos_token = '<|hy_eos{}|>'.format(HYTK) %}
4
+ {%- set bos_token = '<|hy_begin_of_sentence{}|>'.format(HYTK) %}
5
+ {%- set pad_token = '<|hy_pad{}|>'.format(HYTK) %}
6
+ {%- set user_token = '<|hy_User{}|>'.format(HYTK) %}
7
+ {%- set assistant_token = '<|hy_Assistant{}|>'.format(HYTK) %}
8
+ {%- set think_begin_token = '<think{}>'.format(HYTK) %}
9
+ {%- set think_end_token = '</think{}>'.format(HYTK) %}
10
+ {%- set toolcalls_begin_token = '<tool_calls{}>'.format(HYTK) %}
11
+ {%- set toolcalls_end_token = '</tool_calls{}>'.format(HYTK) %}
12
+ {%- set toolcall_begin_token = '<tool_call{}>'.format(HYTK) %}
13
+ {%- set toolcall_end_token = '</tool_call{}>'.format(HYTK) %}
14
+ {%- set toolsep_token = '<tool_sep{}>'.format(HYTK) %}
15
+ {%- set argkey_begin_token = '<arg_key{}>'.format(HYTK) %}
16
+ {%- set argkey_end_token = '</arg_key{}>'.format(HYTK) %}
17
+ {%- set argvalue_begin_token = '<arg_value{}>'.format(HYTK) %}
18
+ {%- set argvalue_end_token = '</arg_value{}>'.format(HYTK) %}
19
+ {%- set toolresponses_begin_token = '<tool_responses{}>'.format(HYTK) %}
20
+ {%- set toolresponses_end_token = '</tool_responses{}>'.format(HYTK) %}
21
+ {%- set toolresponse_begin_token = '<tool_response{}>'.format(HYTK) %}
22
+ {%- set toolresponse_end_token = '</tool_response{}>'.format(HYTK) %}
23
+ {%- set reasoning_mode_token = '<|reasoning_mode{}|>'.format(HYTK) %}
24
+
25
+ {#- ----------‑‑‑ hyperparameters variables ‑‑‑---------- -#}
26
+ {%- if not add_generation_prompt is defined %}
27
+ {%- set add_generation_prompt = false %}
28
+ {%- endif %}
29
+ {%- if not preserved_thinking is defined %}
30
+ {%- if not tools %}
31
+ {%- set preserved_thinking = false %}
32
+ {%- else %}
33
+ {%- set preserved_thinking = true %}
34
+ {%- endif %}
35
+ {%- endif %}
36
+ {%- if not is_training is defined %}
37
+ {%- set is_training = false %}
38
+ {%- endif %}
39
+
40
+ {%- if not reasoning_effort is defined %}
41
+ {%- set reasoning_effort = 'high' %}
42
+ {%- elif reasoning_effort not in ['high', 'low', 'no_think'] %}
43
+ {%- if reasoning_effort is none %}
44
+ {{- raise_exception('reasoning_effort error : None, should be no_think/low/high') }}
45
+ {%- else %}
46
+ {{- raise_exception('reasoning_effort error : ' + reasoning_effort + ', should be no_think/low/high') }}
47
+ {%- endif %}
48
+ {%- endif %}
49
+
50
+ {%- if fallback_strategy is defined and fallback_strategy == 'reasoning_toolcall_retry' %}
51
+ {%- set reasoning_effort = 'high' %}
52
+ {%- set add_generation_prompt = false %}
53
+ {%- endif %}
54
+ {%- if not raw_last_assistant is defined %}
55
+ {%- set raw_last_assistant = false %}
56
+ {%- endif %}
57
+
58
+ {%- macro visible_text(content) -%}
59
+ {%- if content is string -%}
60
+ {{- content }}
61
+ {%- elif content is iterable and content is not mapping -%}
62
+ {%- for item in content -%}
63
+ {%- if item is mapping and item.type == 'text' -%}
64
+ {{- item.text }}
65
+ {%- elif item is string -%}
66
+ {{- item }}
67
+ {%- endif -%}
68
+ {%- endfor -%}
69
+ {%- elif content is none -%}
70
+ {{- '' }}
71
+ {%- else -%}
72
+ {{- content }}
73
+ {%- endif -%}
74
+ {%- endmacro -%}
75
+
76
+ {%- set ns = namespace(last_user_index=-1) %}
77
+ {%- set sp_ns = namespace(system_prompt='', is_first_sp=true) %}
78
+ {%- for message in messages %}
79
+ {%- if message['role'] == 'system' %}
80
+ {%- set sp_ns.system_prompt = sp_ns.system_prompt + visible_text(message['content']) %}
81
+ {%- endif %}
82
+ {%- if message['role'] == 'user' %}
83
+ {%- set ns.last_user_index = loop.index0 %}
84
+ {%- endif %}
85
+ {%- endfor %}
86
+ {%- if reasoning_effort is defined and reasoning_effort is string and reasoning_effort != '' and not tools %}
87
+ {%- set sp_ns.system_prompt = sp_ns.system_prompt + reasoning_mode_token + 'reasoning_effort:' + reasoning_effort %}
88
+ {%- endif %}
89
+ {{- bos_token }}
90
+ {{- sp_ns.system_prompt }}
91
+ {%- if tools %}
92
+ {%- if sp_ns.system_prompt != '' %}
93
+ {{- '\n\n# Tools\n\nYou may call one or more functions to assist with the user query.' }}
94
+ {%- else %}
95
+ {{- '# Tools\n\nYou may call one or more functions to assist with the user query.' }}
96
+ {%- endif %}
97
+ {{- '\n\nYou are provided with function signatures within <tools></tools> XML tags:' }}
98
+ {{- '\n<tools>\n' }}
99
+ {%- for tool in tools %}
100
+ {%- if loop.index0 > 0 %}
101
+ {{- '\n' }}
102
+ {%- endif %}
103
+ {{- tool | tojson }}
104
+ {%- endfor %}
105
+ {{- '\n</tools>\n\n' }}
106
+ {{- 'For function call returns, you should first print ' + toolcalls_begin_token + '\n' }}
107
+ {{- 'For each function call, you should return object like:\n' }}
108
+ {{- toolcall_begin_token + '{function-name}' + toolsep_token + '\n' }}
109
+ {{- argkey_begin_token + '{arg-key-1}' + argkey_end_token + '\n' }}
110
+ {{- argvalue_begin_token + '{arg-value-1}' + argvalue_end_token + '\n' }}
111
+ {{- argkey_begin_token + '{arg-key-2}' + argkey_end_token + '\n' }}
112
+ {{- argvalue_begin_token + '{arg-value-2}' + argvalue_end_token + '\n' }}
113
+ {{- '...\n' }}
114
+ {{- toolcall_end_token + '\n' }}
115
+ {%- if reasoning_effort is defined and reasoning_effort is string and reasoning_effort != '' %}
116
+ {{- 'At the end of function call returns, you should print ' + toolcalls_end_token + reasoning_mode_token + 'reasoning_effort:' + reasoning_effort }}
117
+ {%- else %}
118
+ {{- 'At the end of function call returns, you should print ' + toolcalls_end_token }}
119
+ {%- endif %}
120
+ {%- endif %}
121
+
122
+ {%- set prev_ns = namespace(is_tool=false, is_tool_first=true) %}
123
+ {%- set last_ns = namespace(last_is_assistant=false) %}
124
+ {%- for message in messages %}
125
+ {%- if message['role'] == 'user' %}
126
+ {%- if prev_ns.is_tool %}
127
+ {{- toolresponses_end_token }}
128
+ {%- endif %}
129
+ {{- user_token + visible_text(message['content']) }}
130
+ {%- set prev_ns.is_tool = false %}
131
+ {%- endif %}
132
+ {%- if message['role'] == 'assistant' %}
133
+ {%- if is_training %}
134
+ {%- if 'reasoning_content' in message and message['reasoning_content'] is string %}
135
+ {%- set rc = message['reasoning_content'] %}
136
+ {%- elif 'reasoning' in message and message['reasoning'] is string %}
137
+ {%- set rc = message['reasoning'] %}
138
+ {%- else %}
139
+ {%- set rc = none %}
140
+ {%- endif %}
141
+ {%- if rc is not none %}
142
+ {%- set content = think_begin_token + rc + think_end_token + visible_text(message['content']) %}
143
+ {%- else %}
144
+ {%- set content = think_begin_token + think_end_token + visible_text(message['content']) %}
145
+ {%- endif %}
146
+ {%- else %}
147
+ {%- if ((preserved_thinking is defined and preserved_thinking) or loop.index0 > ns.last_user_index) %}
148
+ {%- if 'reasoning_content' in message and message['reasoning_content'] is string %}
149
+ {%- set rc = message['reasoning_content'] %}
150
+ {%- elif 'reasoning' in message and message['reasoning'] is string %}
151
+ {%- set rc = message['reasoning'] %}
152
+ {%- else %}
153
+ {%- set rc = none %}
154
+ {%- endif %}
155
+ {%- if rc is not none %}
156
+ {%- set content = think_begin_token + rc + think_end_token + visible_text(message['content']) %}
157
+ {%- else %}
158
+ {%- set content = think_begin_token + think_end_token + visible_text(message['content']) %}
159
+ {%- endif %}
160
+ {%- else %}
161
+ {%- set content = think_begin_token + think_end_token + visible_text(message['content']) %}
162
+ {%- endif %}
163
+ {%- endif %}
164
+ {%- if prev_ns.is_tool %}
165
+ {{- toolresponses_end_token }}
166
+ {%- endif %}
167
+ {{- assistant_token }}
168
+ {%- if message['tool_calls'] is defined and message['tool_calls'] %}
169
+ {%- set prev_ns.is_tool_first = true %}
170
+ {{- content }}
171
+ {{- toolcalls_begin_token + '\n' }}
172
+ {%- for tool in message['tool_calls'] %}
173
+ {%- set arguments = tool['function']['arguments'] %}
174
+ {{- toolcall_begin_token + tool['function']['name'] + toolsep_token + '\n' }}
175
+ {%- for key, value in arguments.items() %}
176
+ {{- argkey_begin_token + key + argkey_end_token + '\n' }}
177
+ {%- if value is not string %}
178
+ {%- set value = value | tojson(ensure_ascii=False) %}
179
+ {%- endif %}
180
+ {{- argvalue_begin_token + value + argvalue_end_token + '\n' }}
181
+ {%- endfor %}
182
+ {{- toolcall_end_token + '\n' }}
183
+ {%- endfor %}
184
+ {{- toolcalls_end_token + eos_token }}
185
+ {%- else %}
186
+ {%- if loop.last and raw_last_assistant %}
187
+ {{- visible_text(message['content']) }}
188
+ {%- elif not loop.last or is_training %}
189
+ {{- content + eos_token }}
190
+ {%- else %}
191
+ {{- content }}
192
+ {%- endif %}
193
+ {%- endif %}
194
+ {%- set prev_ns.is_tool = false %}
195
+ {%- endif %}
196
+ {%- if message['role'] == 'tool' %}
197
+ {%- set prev_ns.is_tool = true %}
198
+ {%- if prev_ns.is_tool_first %}
199
+ {{- toolresponses_begin_token + '\n' }}
200
+ {%- set prev_ns.is_tool_first = false %}
201
+ {%- endif %}
202
+ {{- toolresponse_begin_token + '\n' + visible_text(message['content']) + '\n' + toolresponse_end_token + '\n' }}
203
+ {%- endif %}
204
+ {%- if loop.last and message['role'] == 'assistant' %}
205
+ {%- set last_ns.last_is_assistant = true %}
206
+ {%- endif %}
207
+
208
+ {%- endfor %}
209
+ {%- if prev_ns.is_tool %}
210
+ {{- toolresponses_end_token }}
211
+ {%- endif %}
212
+ {%- if add_generation_prompt %}
213
+ {%- if not last_ns.last_is_assistant %}
214
+ {%- if reasoning_effort is defined and reasoning_effort in ['low', 'high'] %}
215
+ {{- assistant_token + think_begin_token }}
216
+ {%- elif reasoning_effort is defined and reasoning_effort == 'no_think' %}
217
+ {{- assistant_token + think_begin_token + think_end_token }}
218
+ {%- else %}
219
+ {{- assistant_token }}
220
+ {%- endif %}
221
+ {%- endif %}
222
+ {%- endif %}
config.json ADDED
The diff for this file is too large to render. See raw diff
 
hy_v3.py ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright © 2026 Apple Inc.
2
+ #
3
+ # Tencent Hunyuan 3 (hy_v3). Base model support follows the community work in
4
+ # ml-explore/mlx-lm#1211 (kernelpool); this file additionally *keeps and uses*
5
+ # the Multi-Token-Prediction (MTP) layer for self-speculative decoding instead
6
+ # of stripping it.
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Any, Dict, Optional
10
+
11
+ import mlx.core as mx
12
+ import mlx.nn as nn
13
+ from mlx.nn.layers.distributed import shard_inplace, shard_linear, sum_gradients
14
+
15
+ from .activations import swiglu
16
+ from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention
17
+ from .cache import KVCache
18
+ from .pipeline import PipelineMixin
19
+ from .rope_utils import initialize_rope
20
+ from .switch_layers import SwitchGLU
21
+
22
+
23
+ @dataclass
24
+ class ModelArgs(BaseModelArgs):
25
+ model_type: str
26
+ vocab_size: int
27
+ hidden_size: int
28
+ intermediate_size: int
29
+ num_hidden_layers: int
30
+ num_attention_heads: int
31
+ num_key_value_heads: int
32
+ head_dim: int
33
+ num_experts: int
34
+ num_experts_per_tok: int
35
+ num_shared_experts: int
36
+ expert_hidden_dim: int
37
+ first_k_dense_replace: int
38
+ rms_norm_eps: float
39
+ rope_parameters: Dict[str, Any]
40
+ router_scaling_factor: float = 1.0
41
+ qk_norm: bool = True
42
+ route_norm: bool = True
43
+ moe_router_use_sigmoid: bool = True
44
+ moe_router_enable_expert_bias: bool = True
45
+ tie_word_embeddings: bool = False
46
+ num_nextn_predict_layers: int = 0
47
+ max_position_embeddings: int = 262144
48
+ enable_moe_fp32_combine: bool = False
49
+ enable_lm_head_fp32: bool = False
50
+
51
+
52
+ class Attention(nn.Module):
53
+ def __init__(self, args: ModelArgs):
54
+ super().__init__()
55
+
56
+ dim = args.hidden_size
57
+ self.n_heads = args.num_attention_heads
58
+ self.n_kv_heads = args.num_key_value_heads
59
+ self.head_dim = args.head_dim
60
+ self.scale = self.head_dim**-0.5
61
+
62
+ self.q_proj = nn.Linear(dim, self.n_heads * self.head_dim, bias=False)
63
+ self.k_proj = nn.Linear(dim, self.n_kv_heads * self.head_dim, bias=False)
64
+ self.v_proj = nn.Linear(dim, self.n_kv_heads * self.head_dim, bias=False)
65
+ self.o_proj = nn.Linear(self.n_heads * self.head_dim, dim, bias=False)
66
+
67
+ self.use_qk_norm = args.qk_norm
68
+ if self.use_qk_norm:
69
+ self.q_norm = nn.RMSNorm(self.head_dim, eps=args.rms_norm_eps)
70
+ self.k_norm = nn.RMSNorm(self.head_dim, eps=args.rms_norm_eps)
71
+
72
+ self.rope = initialize_rope(
73
+ dims=self.head_dim,
74
+ base=args.rope_parameters["rope_theta"],
75
+ traditional=False,
76
+ scaling_config=args.rope_parameters,
77
+ max_position_embeddings=args.max_position_embeddings,
78
+ )
79
+
80
+ def __call__(
81
+ self,
82
+ x: mx.array,
83
+ mask: Optional[mx.array] = None,
84
+ cache: Optional[Any] = None,
85
+ ) -> mx.array:
86
+ B, L, _ = x.shape
87
+
88
+ queries = self.q_proj(x).reshape(B, L, self.n_heads, self.head_dim)
89
+ keys = self.k_proj(x).reshape(B, L, self.n_kv_heads, self.head_dim)
90
+ values = self.v_proj(x).reshape(B, L, self.n_kv_heads, self.head_dim)
91
+
92
+ if self.use_qk_norm:
93
+ queries = self.q_norm(queries)
94
+ keys = self.k_norm(keys)
95
+
96
+ queries = queries.transpose(0, 2, 1, 3)
97
+ keys = keys.transpose(0, 2, 1, 3)
98
+ values = values.transpose(0, 2, 1, 3)
99
+
100
+ offset = cache.offset if cache is not None else 0
101
+ queries = self.rope(queries, offset=offset)
102
+ keys = self.rope(keys, offset=offset)
103
+ if cache is not None:
104
+ keys, values = cache.update_and_fetch(keys, values)
105
+
106
+ output = scaled_dot_product_attention(
107
+ queries, keys, values, cache=cache, scale=self.scale, mask=mask
108
+ )
109
+ output = output.transpose(0, 2, 1, 3).reshape(B, L, -1)
110
+ return self.o_proj(output)
111
+
112
+
113
+ class MLP(nn.Module):
114
+ def __init__(self, hidden_size: int, intermediate_size: int):
115
+ super().__init__()
116
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
117
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
118
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
119
+
120
+ def __call__(self, x):
121
+ return self.down_proj(swiglu(self.gate_proj(x), self.up_proj(x)))
122
+
123
+
124
+ @mx.compile
125
+ def expert_select(
126
+ gates,
127
+ expert_bias,
128
+ top_k,
129
+ routed_scaling_factor,
130
+ norm_topk_prob,
131
+ ):
132
+ scores = mx.sigmoid(gates.astype(mx.float32))
133
+ orig_scores = scores
134
+ scores = scores + expert_bias
135
+
136
+ inds = mx.argpartition(scores, kth=-top_k, axis=-1)[..., -top_k:]
137
+ scores = mx.take_along_axis(orig_scores, inds, axis=-1)
138
+ if top_k > 1 and norm_topk_prob:
139
+ scores = scores / (scores.sum(axis=-1, keepdims=True) + 1e-20)
140
+ scores = scores * routed_scaling_factor
141
+
142
+ return inds, scores
143
+
144
+
145
+ class MoEGate(nn.Module):
146
+ def __init__(self, args: ModelArgs):
147
+ super().__init__()
148
+ self.top_k = args.num_experts_per_tok
149
+ self.norm_topk_prob = args.route_norm
150
+ self.routed_scaling_factor = args.router_scaling_factor
151
+ self.gate = nn.Linear(args.hidden_size, args.num_experts, bias=False)
152
+ self.expert_bias = mx.zeros((args.num_experts,))
153
+
154
+ def __call__(self, x):
155
+ return expert_select(
156
+ self.gate(x),
157
+ self.expert_bias,
158
+ self.top_k,
159
+ self.routed_scaling_factor,
160
+ self.norm_topk_prob,
161
+ )
162
+
163
+
164
+ class MoE(nn.Module):
165
+ def __init__(self, args: ModelArgs):
166
+ super().__init__()
167
+ self.num_experts_per_tok = args.num_experts_per_tok
168
+ self.switch_mlp = SwitchGLU(
169
+ args.hidden_size,
170
+ args.expert_hidden_dim,
171
+ args.num_experts,
172
+ )
173
+ self.router = MoEGate(args)
174
+ if args.num_shared_experts > 0:
175
+ self.shared_mlp = MLP(
176
+ args.hidden_size,
177
+ args.expert_hidden_dim * args.num_shared_experts,
178
+ )
179
+ else:
180
+ self.shared_mlp = None
181
+
182
+ self.fp32_combine = args.enable_moe_fp32_combine
183
+ self.sharding_group = None
184
+
185
+ def __call__(self, x):
186
+ if self.sharding_group is not None:
187
+ x = sum_gradients(self.sharding_group)(x)
188
+
189
+ inds, scores = self.router(x)
190
+ if not self.fp32_combine:
191
+ scores = scores.astype(x.dtype)
192
+ y = self.switch_mlp(x, inds)
193
+ y = (y * scores[..., None]).sum(axis=-2)
194
+ if self.shared_mlp is not None:
195
+ y = y + self.shared_mlp(x)
196
+
197
+ if self.sharding_group is not None:
198
+ y = mx.distributed.all_sum(y, group=self.sharding_group)
199
+
200
+ return y.astype(x.dtype)
201
+
202
+
203
+ class DecoderLayer(nn.Module):
204
+ def __init__(self, args: ModelArgs, layer_idx: int):
205
+ super().__init__()
206
+ self.self_attn = Attention(args)
207
+ if layer_idx < args.first_k_dense_replace:
208
+ self.mlp = MLP(args.hidden_size, args.intermediate_size)
209
+ else:
210
+ self.mlp = MoE(args)
211
+ self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
212
+ self.post_attention_layernorm = nn.RMSNorm(
213
+ args.hidden_size, eps=args.rms_norm_eps
214
+ )
215
+
216
+ def __call__(
217
+ self,
218
+ x: mx.array,
219
+ mask: Optional[mx.array] = None,
220
+ cache: Optional[Any] = None,
221
+ ) -> mx.array:
222
+ r = self.self_attn(self.input_layernorm(x), mask, cache)
223
+ h = x + r
224
+ r = self.mlp(self.post_attention_layernorm(h))
225
+ return h + r
226
+
227
+
228
+ class MTPBlock(nn.Module):
229
+ """Hy3 Multi-Token-Prediction block (the layer after the main stack).
230
+
231
+ Projects concat[norm(next-token embedding), norm(hidden state)] through
232
+ ``eh_proj`` and one full decoder layer to produce the hidden state for the
233
+ speculatively-drafted next token.
234
+ """
235
+
236
+ def __init__(self, args: ModelArgs):
237
+ super().__init__()
238
+ self.enorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
239
+ self.hnorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
240
+ self.eh_proj = nn.Linear(args.hidden_size * 2, args.hidden_size, bias=False)
241
+ self.layer = DecoderLayer(args, layer_idx=args.num_hidden_layers)
242
+ self.final_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
243
+
244
+ def __call__(
245
+ self,
246
+ h_N: mx.array,
247
+ e_N1: mx.array,
248
+ mask: Optional[mx.array] = None,
249
+ cache: Optional[Any] = None,
250
+ ) -> mx.array:
251
+ # Order matters: [normed embedding, normed hidden state].
252
+ x = mx.concatenate([self.enorm(e_N1), self.hnorm(h_N)], axis=-1)
253
+ y = self.layer(self.eh_proj(x), mask, cache)
254
+ return self.final_layernorm(y)
255
+
256
+
257
+ class HYV3Model(PipelineMixin, nn.Module):
258
+ def __init__(self, args: ModelArgs):
259
+ super().__init__()
260
+ self.vocab_size = args.vocab_size
261
+ self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size)
262
+ self.layers = [DecoderLayer(args, idx) for idx in range(args.num_hidden_layers)]
263
+ self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
264
+
265
+ def __call__(
266
+ self,
267
+ x: mx.array,
268
+ cache: Optional[Any] = None,
269
+ return_hidden_states: bool = False,
270
+ ) -> mx.array:
271
+ h = self.embed_tokens(x)
272
+
273
+ pipeline_rank = self.pipeline_rank
274
+ pipeline_size = self.pipeline_size
275
+
276
+ if cache is None:
277
+ cache = [None] * len(self.pipeline_layers)
278
+ mask = create_attention_mask(h, cache[0])
279
+
280
+ if pipeline_rank < pipeline_size - 1:
281
+ h = mx.distributed.recv_like(h, (pipeline_rank + 1))
282
+
283
+ for layer, c in zip(self.pipeline_layers, cache):
284
+ h = layer(h, mask, cache=c)
285
+
286
+ if pipeline_rank != 0:
287
+ h = mx.distributed.send(h, (pipeline_rank - 1) % pipeline_size)
288
+ if cache[-1] is not None:
289
+ cache[-1].keys = mx.depends(cache[-1].keys, h)
290
+
291
+ if pipeline_size > 1:
292
+ h = mx.distributed.all_gather(h)[: h.shape[0]]
293
+
294
+ out = self.norm(h)
295
+ if return_hidden_states:
296
+ return out, h
297
+ return out
298
+
299
+
300
+ class Model(nn.Module):
301
+ def __init__(self, args: ModelArgs):
302
+ super().__init__()
303
+ self.args = args
304
+ self.model_type = args.model_type
305
+ self.model = HYV3Model(args)
306
+ if not args.tie_word_embeddings:
307
+ self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False)
308
+
309
+ self.num_nextn_predict_layers = getattr(args, "num_nextn_predict_layers", 0)
310
+ if self.num_nextn_predict_layers > 0:
311
+ self.mtp = MTPBlock(args)
312
+
313
+ def _logits(self, out):
314
+ if self.args.enable_lm_head_fp32:
315
+ out = out.astype(mx.float32)
316
+ if self.args.tie_word_embeddings:
317
+ return self.model.embed_tokens.as_linear(out)
318
+ return self.lm_head(out)
319
+
320
+ def __call__(
321
+ self,
322
+ inputs: mx.array,
323
+ cache: Optional[Any] = None,
324
+ return_hidden_states: bool = False,
325
+ ):
326
+ if return_hidden_states:
327
+ out, h = self.model(inputs, cache, return_hidden_states=True)
328
+ return self._logits(out), h
329
+ out = self.model(inputs, cache)
330
+ return self._logits(out)
331
+
332
+ def predict_next_tokens(self, h_N: mx.array, token_ids: mx.array, cache=None):
333
+ """Run the MTP head to draft the next token from a hidden state."""
334
+ if not hasattr(self, "mtp"):
335
+ raise ValueError("MTP is not enabled or its weights are not loaded.")
336
+ e_N1 = self.model.embed_tokens(token_ids)
337
+ mask = create_attention_mask(e_N1, cache)
338
+ h_mtp = self.mtp(h_N, e_N1, mask, cache)
339
+ return self._logits(h_mtp)
340
+
341
+ @property
342
+ def layers(self):
343
+ return self.model.layers
344
+
345
+ def make_cache(self):
346
+ return [KVCache() for _ in self.layers]
347
+
348
+ def sanitize(self, weights):
349
+ n_layers = self.args.num_hidden_layers
350
+ n_mtp = self.args.num_nextn_predict_layers
351
+
352
+ # Keep the MTP layer (the base model drops it). If the checkpoint stores
353
+ # it under model.layers.{n_layers}.*, remap it onto the mtp.* submodule;
354
+ # if it is already stored under mtp.*, leave it as-is.
355
+ if n_mtp > 0:
356
+ mtp_src = f"model.layers.{n_layers}."
357
+ for k in list(weights.keys()):
358
+ if k.startswith(mtp_src):
359
+ rest = k[len(mtp_src):]
360
+ if any(
361
+ t in rest
362
+ for t in ("enorm", "hnorm", "eh_proj", "final_layernorm")
363
+ ):
364
+ weights["mtp." + rest] = weights.pop(k)
365
+ else:
366
+ weights["mtp.layer." + rest] = weights.pop(k)
367
+
368
+ def fix_moe(prefix):
369
+ bias_key = f"{prefix}.mlp.expert_bias"
370
+ if bias_key in weights:
371
+ weights[f"{prefix}.mlp.router.expert_bias"] = weights.pop(bias_key)
372
+ for m in ("gate_proj", "down_proj", "up_proj"):
373
+ for k in ("weight", "scales", "biases"):
374
+ per_expert = f"{prefix}.mlp.experts.0.{m}.{k}"
375
+ stacked = f"{prefix}.mlp.experts.{m}.{k}"
376
+ if per_expert in weights:
377
+ to_join = [
378
+ weights.pop(f"{prefix}.mlp.experts.{e}.{m}.{k}")
379
+ for e in range(self.args.num_experts)
380
+ ]
381
+ weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = mx.stack(to_join)
382
+ elif stacked in weights:
383
+ # Already stacked (MLX-converted checkpoint): just rename.
384
+ weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = weights.pop(
385
+ stacked
386
+ )
387
+
388
+ for l in range(n_layers):
389
+ fix_moe(f"model.layers.{l}")
390
+ if n_mtp > 0:
391
+ fix_moe("mtp.layer")
392
+
393
+ if self.args.tie_word_embeddings:
394
+ weights.pop("lm_head.weight", None)
395
+
396
+ return weights
397
+
398
+ def shard(self, group: Optional[mx.distributed.Group] = None):
399
+ group = group or mx.distributed.init()
400
+ N = group.size()
401
+ for layer in self.model.layers:
402
+ layer.self_attn.q_proj = shard_linear(
403
+ layer.self_attn.q_proj, "all-to-sharded", group=group
404
+ )
405
+ layer.self_attn.k_proj = shard_linear(
406
+ layer.self_attn.k_proj, "all-to-sharded", group=group
407
+ )
408
+ layer.self_attn.v_proj = shard_linear(
409
+ layer.self_attn.v_proj, "all-to-sharded", group=group
410
+ )
411
+ layer.self_attn.o_proj = shard_linear(
412
+ layer.self_attn.o_proj, "sharded-to-all", group=group
413
+ )
414
+ layer.self_attn.n_heads //= N
415
+ layer.self_attn.n_kv_heads = max(1, layer.self_attn.n_kv_heads // N)
416
+
417
+ if isinstance(layer.mlp, MLP):
418
+ layer.mlp.gate_proj = shard_linear(
419
+ layer.mlp.gate_proj, "all-to-sharded", group=group
420
+ )
421
+ layer.mlp.down_proj = shard_linear(
422
+ layer.mlp.down_proj, "sharded-to-all", group=group
423
+ )
424
+ layer.mlp.up_proj = shard_linear(
425
+ layer.mlp.up_proj, "all-to-sharded", group=group
426
+ )
427
+ else:
428
+ layer.mlp.sharding_group = group
429
+ if layer.mlp.shared_mlp is not None:
430
+ shard_inplace(
431
+ layer.mlp.shared_mlp.gate_proj, "all-to-sharded", group=group
432
+ )
433
+ shard_inplace(
434
+ layer.mlp.shared_mlp.down_proj, "sharded-to-all", group=group
435
+ )
436
+ shard_inplace(
437
+ layer.mlp.shared_mlp.up_proj, "all-to-sharded", group=group
438
+ )
439
+ shard_inplace(
440
+ layer.mlp.switch_mlp.gate_proj, "all-to-sharded", group=group
441
+ )
442
+ shard_inplace(
443
+ layer.mlp.switch_mlp.down_proj, "sharded-to-all", group=group
444
+ )
445
+ shard_inplace(
446
+ layer.mlp.switch_mlp.up_proj, "all-to-sharded", group=group
447
+ )
448
+
449
+ @property
450
+ def layers(self):
451
+ return self.model.pipeline_layers
452
+
453
+ @property
454
+ def quant_predicate(self):
455
+ def predicate(path, _):
456
+ if path.endswith("mlp.router.gate"):
457
+ return {"group_size": 64, "bits": 8}
458
+ return True
459
+
460
+ return predicate
461
+
462
+ @property
463
+ def cast_predicate(self):
464
+ def predicate(k):
465
+ return "expert_bias" not in k
466
+
467
+ return predicate
model-00001-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1fcc3c32f95ba1b767477f61e9c304f034650d21a9a45e57edec34cf5dccd75
3
+ size 5338990287
model-00002-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d2e0005d727aa6b2a127b05f8eb275310d12a6e7bec66c6370f61afc4818f5de
3
+ size 5047605611
model-00003-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cec8bb2cad74b180e03600fa264696cae54aabe995982c25a722f8d7ef83cbeb
3
+ size 5160851740
model-00004-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:17c8cda9936757e59e4c22223769650d17eb35eb32d4ee20404cef51e8f1367a
3
+ size 5160851734
model-00005-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:688998634e92aec9e0135c97d31e53f21ee191c44af0966066b98cdcd093bbe1
3
+ size 5361715118
model-00006-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:853bd15a1aabda87eb99d0b466407548dee7a55442241a36c88d61904f0b222e
3
+ size 5085354618
model-00007-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:481b04ee770c725f02ede066e720aec8689b182d57afa0d382428f348667751c
3
+ size 5160851840
model-00008-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ed31ec10d05611505c9efb03195186c9200d6cb3d1c4f1957e17706a572d852
3
+ size 5160851788
model-00009-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2c8b695fcc048cb931b14322ca8b9fcade4693b0fd7123d3c6f6c2faceb3ec5e
3
+ size 5361715110
model-00010-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d17a74c4851f266b3aaa2e9915b32dfebf372b18beed1aa5bc81a2479de39b0a
3
+ size 5085354618
model-00011-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f599930039e05afbb5df06a85267b43a62dfc795a84a97290efab345d475964
3
+ size 5160851830
model-00012-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e4d47b3402bbcb343fa258ae95741ba1f332e845b12b28c38987f752c87c0c60
3
+ size 5160851746
model-00013-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:94c1c4cf734f7654f57ed33c6dc23059ba53ee407dc03585a16d78db0ec93ec1
3
+ size 5361715148
model-00014-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f492bac5b7af192e63f8c3095b29627454fafa54c88db0b5811a6f2e12bc73a6
3
+ size 5085354616
model-00015-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2c47e41a438cec2870943862c43477cb6cd85e40768e5dcc69d9a52da7dc0a2
3
+ size 5160851842
model-00016-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c4b299e151a710dfc97dde5692e5f0d48710bb741d6634abdc0961f6f97b00c5
3
+ size 5160851740
model-00017-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:84389688fbdfa5e6c9df0a82fc6af8eed61ac670a5c96a289a65f1a09888cdff
3
+ size 5361715116
model-00018-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a0471a5f9ff92d6bf079a0fcab2afd3be9b0c16b66dab132b3ca4d356f39666e
3
+ size 5085354594
model-00019-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:61f00869f93afb11ed0ffce422fb3af06f55e3dad575579042eb40f7318bca09
3
+ size 5160851798
model-00020-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:32ce4c3931d3c4effe590f454cc09aae21eb88b7786ab1f89dfa4ec6a92ca5de
3
+ size 5160851754
model-00021-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:539f91b4901370c604756bef8f124d673911bce54f4071d5a81efd8a1dafa85d
3
+ size 5361715112
model-00022-of-00022.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:80aef2284cf42d762b0e2bdd555409dc58905acabe94c113d2995af041d2fa82
3
+ size 2006860547
model-mtp-sidecar.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6af6eb3c84449c201afee367e82b1bbc8497b72ba57d566da72d03e8cb692496
3
+ size 1420261306
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|hy_begin_of_sentence:opensource|>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "eos_token": "<|hy_eos:opensource|>",
6
+ "fix_mistral_regex": true,
7
+ "is_local": true,
8
+ "local_files_only": false,
9
+ "model_max_length": 1000000000000000019884624838656,
10
+ "pad_token": "<|hy_pad:opensource|>",
11
+ "token_suffix": ":opensource",
12
+ "tokenizer_class": "TokenizersBackend",
13
+ "tool_parser_type": "hy_v3_opensource"
14
+ }