RobZombAI commited on
Commit
d3ff80a
·
verified ·
1 Parent(s): de634da

Upload 4 files

Browse files
hy3-llama-patch/0001-add-hyv3-support.patch ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ From: AngelSlim <hy3-ml@github>
2
+ Subject: [PATCH] Add Hy3 (hy_v3) architecture support to llama.cpp
3
+
4
+ ---
5
+ Adds the Hunyuan V3 (hy_v3) MoE architecture:
6
+ - 295B MoE, 81 layers, 192 experts (8 active)
7
+ - MTP (Multi-Token Prediction) support
8
+ - Sigmoid expert gating with correction bias
9
+ - Shared expert + routed MoE FFN
10
+ ---
11
+
12
+ diff --git a/common/chat.cpp b/common/chat.cpp
13
+ index 24e58ab06..56569383d 100644
14
+ --- a/common/chat.cpp
15
+ +++ b/common/chat.cpp
16
+ @@ -22,6 +22,7 @@
17
+ #include <functional>
18
+
19
+ #include <optional>
20
+ +#include <regex>
21
+ #include <sstream>
22
+ #include <stdexcept>
23
+ #include <string>
24
+ @@ -1608,6 +1609,178 @@ static common_chat_params common_chat_params_init_kimi_k2(const common_chat_temp
25
+ return data;
26
+ }
27
+
28
+ +// Hunyuan V3 (hy_v3) parser.
29
+ +// Reasoning: <think{sfx}>{reasoning}</think{sfx}>
30
+ +// Tool calls (constructed / tagged, key-value split):
31
+ +// <tool_calls{sfx}>
32
+ +// <tool_call{sfx}>{func-name}<tool_sep{sfx}>
33
+ +// <arg_key{sfx}>{key}</arg_key{sfx}>
34
+ +// <arg_value{sfx}>{value}</arg_value{sfx}>
35
+ +// ...
36
+ +// </tool_call{sfx}>
37
+ +// </tool_calls{sfx}>
38
+ +// The special tokens carry a per-tokenizer suffix (e.g. ":opensource" or empty),
39
+ +// so we extract the concrete literals from the template source rather than
40
+ +// hard-coding them.
41
+ +static common_chat_params common_chat_params_init_hunyuan_v3(const common_chat_template & tmpl,
42
+ + const autoparser::generation_params & inputs) {
43
+ + common_chat_params data;
44
+ +
45
+ + const std::string & src = tmpl.source();
46
+ +
47
+ + // Pull the concrete token literal assigned to a jinja variable, e.g.
48
+ + // {%- set toolsep_token = '<tool_sep:opensource>' %}
49
+ + auto extract_tok = [&src](const std::string & var, const std::string & fallback) -> std::string {
50
+ + std::smatch m;
51
+ + std::regex re(var + R"(\s*=\s*'([^']*)')");
52
+ + if (std::regex_search(src, m, re)) {
53
+ + return m[1].str();
54
+ + }
55
+ + return fallback;
56
+ + };
57
+ +
58
+ + const std::string think_begin = extract_tok("think_begin_token", "<think>");
59
+ + const std::string think_end = extract_tok("think_end_token", "</think>");
60
+ + const std::string tcalls_begin = extract_tok("toolcalls_begin_token", "<tool_calls>");
61
+ + const std::string tcalls_end = extract_tok("toolcalls_end_token", "</tool_calls>");
62
+ + const std::string tcall_begin = extract_tok("toolcall_begin_token", "<tool_call>");
63
+ + const std::string tcall_end = extract_tok("toolcall_end_token", "</tool_call>");
64
+ + const std::string tsep = extract_tok("toolsep_token", "<tool_sep>");
65
+ + const std::string argkey_begin = extract_tok("argkey_begin_token", "<arg_key>");
66
+ + const std::string argkey_end = extract_tok("argkey_end_token", "</arg_key>");
67
+ + const std::string argval_begin = extract_tok("argvalue_begin_token", "<arg_value>");
68
+ + const std::string argval_end = extract_tok("argvalue_end_token", "</arg_value>");
69
+ + const std::string assistant_tok = extract_tok("assistant_token", "");
70
+ +
71
+ + auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
72
+ + auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
73
+ + auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
74
+ +
75
+ + data.supports_thinking = true;
76
+ + data.thinking_start_tag = think_begin;
77
+ + data.thinking_end_tag = think_end;
78
+ + data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
79
+ + data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
80
+ + data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
81
+ + data.preserved_tokens = {
82
+ + think_begin, think_end,
83
+ + tcalls_begin, tcalls_end,
84
+ + tcall_begin, tcall_end, tsep,
85
+ + argkey_begin, argkey_end,
86
+ + argval_begin, argval_end,
87
+ + };
88
+ +
89
+ + if (inputs.has_continuation()) {
90
+ + const auto & msg = inputs.continue_msg;
91
+ +
92
+ + data.generation_prompt = think_begin + msg.reasoning_content;
93
+ + if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
94
+ + data.generation_prompt += think_end + msg.render_content();
95
+ + }
96
+ +
97
+ + data.prompt += data.generation_prompt;
98
+ + }
99
+ +
100
+ + auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
101
+ + // The generation prompt prefix that precedes the model's real output may
102
+ + // still be present in the decoded text, in one of these shapes:
103
+ + // {assistant} (default)
104
+ + // {assistant}{think_begin} (reasoning_effort low/high)
105
+ + // {assistant}{think_begin}{think_end}(reasoning_effort no_think)
106
+ + // Consume an optional leading assistant token so it never leaks into content.
107
+ + auto gen_prefix = assistant_tok.empty() ? p.eps() : p.optional(p.literal(assistant_tok));
108
+ +
109
+ + // Reasoning block is optional: <think{sfx}>...</think{sfx}>
110
+ + auto reasoning = extract_reasoning
111
+ + ? p.optional(p.literal(think_begin) +
112
+ + p.reasoning(p.until(think_end)) +
113
+ + p.literal(think_end))
114
+ + : p.eps();
115
+ +
116
+ + // Content only (no tools)
117
+ + if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
118
+ + include_grammar = false;
119
+ + return gen_prefix + (reasoning << p.content(p.rest()));
120
+ + }
121
+ +
122
+ + // One tool call: <tool_call{sfx}>NAME<tool_sep{sfx}> {args} </tool_call{sfx}>
123
+ + // where {args} is a sequence of
124
+ + // <arg_key{sfx}>KEY</arg_key{sfx}> <arg_value{sfx}>VALUE</arg_value{sfx}>
125
+ + auto tool_choice = p.choice();
126
+ + foreach_function(inputs.tools, [&](const json & tool) {
127
+ + const auto & function = tool.at("function");
128
+ + std::string name = function.at("name");
129
+ + const json & params = function.contains("parameters") ? function.at("parameters") : json::object();
130
+ +
131
+ + // Build per-argument parsers restricted to schema-declared property names.
132
+ + auto args = p.eps();
133
+ + if (params.contains("properties") && !params.at("properties").empty()) {
134
+ + auto arg_choice = p.choice();
135
+ + for (const auto & el : params.at("properties").items()) {
136
+ + const std::string & prop = el.key();
137
+ + // Values for string-typed params are literal text (must be quoted in
138
+ + // the emitted JSON); other types are parsed as JSON/python scalars.
139
+ + std::string ptype;
140
+ + if (el.value().is_object() && el.value().contains("type") &&
141
+ + el.value().at("type").is_string()) {
142
+ + ptype = el.value().at("type").get<std::string>();
143
+ + }
144
+ + auto value_node = (ptype == "string")
145
+ + ? p.tool_arg_string_value(p.until(argval_end))
146
+ + : p.tool_arg_value(p.until(argval_end));
147
+ + arg_choice |= p.tool_arg(
148
+ + p.tool_arg_open(p.literal(argkey_begin)) +
149
+ + p.tool_arg_name(p.literal(prop)) +
150
+ + p.literal(argkey_end) + p.space() +
151
+ + p.literal(argval_begin) +
152
+ + value_node +
153
+ + p.tool_arg_close(p.literal(argval_end)));
154
+ + }
155
+ + args = p.zero_or_more(arg_choice + p.space());
156
+ + }
157
+ +
158
+ + auto tool_parser = p.tool(
159
+ + p.tool_open(p.literal(tcall_begin) + p.tool_name(p.literal(name)) + p.literal(tsep)) +
160
+ + p.space() + p.tool_args(args) + p.space() +
161
+ + p.tool_close(p.literal(tcall_end)));
162
+ +
163
+ + tool_choice |= p.rule("tool-" + name, tool_parser);
164
+ + });
165
+ +
166
+ + auto max_calls = inputs.parallel_tool_calls ? -1 : 1;
167
+ + auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
168
+ + // Section: <tool_calls{sfx}> (one or more tool calls) </tool_calls{sfx}>
169
+ + auto tool_calls = p.trigger_rule("tool-call",
170
+ + p.literal(tcalls_begin) + p.space() +
171
+ + p.repeat(tool_choice + p.space(), min_calls, max_calls) +
172
+ + p.optional(p.literal(tcalls_end)));
173
+ +
174
+ + auto content_before_tools = p.content(p.until(tcalls_begin));
175
+ +
176
+ + return gen_prefix + (reasoning << content_before_tools << tool_calls);
177
+ + });
178
+ +
179
+ + data.parser = parser.save();
180
+ +
181
+ + if (include_grammar) {
182
+ + data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
183
+ + data.grammar = build_grammar([&](const common_grammar_builder & builder) {
184
+ + foreach_function(inputs.tools, [&](const json & tool) {
185
+ + const auto & function = tool.at("function");
186
+ + auto schema = function.at("parameters");
187
+ + builder.resolve_refs(schema);
188
+ + });
189
+ + parser.build_grammar(builder, data.grammar_lazy);
190
+ + });
191
+ +
192
+ + data.grammar_triggers = {
193
+ + { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, tcalls_begin }
194
+ + };
195
+ + }
196
+ +
197
+ + return data;
198
+ +}
199
+ +
200
+ // LFM2/LFM2.5 parser. Tool calls are almost Python-style and parallel-capable
201
+ // (except dotted names and JSON literals true/false/null).
202
+ // Always wrapped in <|tool_call_start|>[name(args)]<|tool_call_end|> with optional <think> reasoning.
203
+ @@ -2197,6 +2370,16 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
204
+ return common_chat_params_init_ministral_3(tmpl, params);
205
+ }
206
+
207
+ + // Hunyuan V3 (hy_v3) - key/value split tagged tool calls with per-tokenizer
208
+ + // suffixed special tokens. Detection: template defines the hy_v3-specific
209
+ + // arg_key/arg_value and tool_sep jinja variables (suffix-independent).
210
+ + if (src.find("argkey_begin_token") != std::string::npos &&
211
+ + src.find("argvalue_begin_token") != std::string::npos &&
212
+ + src.find("toolsep_token") != std::string::npos) {
213
+ + LOG_DBG("Using specialized template: Hunyuan V3\n");
214
+ + return common_chat_params_init_hunyuan_v3(tmpl, params);
215
+ + }
216
+ +
217
+ // GPT-OSS - has unique channel-based structure that needs dedicated handler
218
+ if (src.find("<|channel|>") != std::string::npos) {
219
+ LOG_DBG("Using specialized template: GPT-OSS\n");
220
+ diff --git a/conversion/__init__.py b/conversion/__init__.py
221
+ index 18162976f..fbc701974 100644
222
+ --- a/conversion/__init__.py
223
+ +++ b/conversion/__init__.py
224
+ @@ -101,6 +101,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
225
+ "HunYuanDenseV1ForCausalLM": "hunyuan",
226
+ "HunYuanMoEV1ForCausalLM": "hunyuan",
227
+ "HunYuanVLForConditionalGeneration": "hunyuan",
228
+ + "HYV3ForCausalLM": "hyv3",
229
+ "IQuestCoderForCausalLM": "llama",
230
+ "InternLM2ForCausalLM": "internlm",
231
+ "InternLM3ForCausalLM": "internlm",
232
+ diff --git a/gguf-py/gguf/__init__.py b/gguf-py/gguf/__init__.py
233
+ index 243defc4c..a39401789 100644
234
+ --- a/gguf-py/gguf/__init__.py
235
+ +++ b/gguf-py/gguf/__init__.py
236
+ @@ -7,3 +7,5 @@ from .tensor_mapping import *
237
+ from .vocab import *
238
+ from .utility import *
239
+ from .metadata import *
240
+ +
241
+ +__version__ = "0.19.0"
242
+ diff --git a/gguf-py/gguf/constants.py b/gguf-py/gguf/constants.py
243
+ index bd6246137..099e6bb0c 100644
244
+ --- a/gguf-py/gguf/constants.py
245
+ +++ b/gguf-py/gguf/constants.py
246
+ @@ -492,6 +492,7 @@ class MODEL_ARCH(IntEnum):
247
+ ERNIE4_5 = auto()
248
+ ERNIE4_5_MOE = auto()
249
+ HUNYUAN_MOE = auto()
250
+ + HYV3 = auto()
251
+ HUNYUAN_DENSE = auto()
252
+ HUNYUAN_VL = auto()
253
+ SMOLLM3 = auto()
254
+ @@ -1042,6 +1043,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
255
+ MODEL_ARCH.ERNIE4_5_MOE: "ernie4_5-moe",
256
+ MODEL_ARCH.FALCON_H1: "falcon-h1",
257
+ MODEL_ARCH.HUNYUAN_MOE: "hunyuan-moe",
258
+ + MODEL_ARCH.HYV3: "hy_v3",
259
+ MODEL_ARCH.HUNYUAN_DENSE: "hunyuan-dense",
260
+ MODEL_ARCH.HUNYUAN_VL: "hunyuan_vl",
261
+ MODEL_ARCH.SMOLLM3: "smollm3",
262
+ @@ -3755,6 +3757,37 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
263
+ MODEL_TENSOR.FFN_DOWN_SHEXP,
264
+ MODEL_TENSOR.FFN_UP_SHEXP,
265
+ ],
266
+ + MODEL_ARCH.HYV3: [
267
+ + MODEL_TENSOR.TOKEN_EMBD,
268
+ + MODEL_TENSOR.OUTPUT_NORM,
269
+ + MODEL_TENSOR.OUTPUT,
270
+ + MODEL_TENSOR.ATTN_NORM,
271
+ + MODEL_TENSOR.ATTN_Q,
272
+ + MODEL_TENSOR.ATTN_Q_NORM,
273
+ + MODEL_TENSOR.ATTN_K,
274
+ + MODEL_TENSOR.ATTN_K_NORM,
275
+ + MODEL_TENSOR.ATTN_V,
276
+ + MODEL_TENSOR.ATTN_OUT,
277
+ + MODEL_TENSOR.FFN_NORM,
278
+ + MODEL_TENSOR.FFN_GATE,
279
+ + MODEL_TENSOR.FFN_DOWN,
280
+ + MODEL_TENSOR.FFN_UP,
281
+ + MODEL_TENSOR.FFN_GATE_INP,
282
+ + MODEL_TENSOR.FFN_EXP_PROBS_B,
283
+ + MODEL_TENSOR.FFN_GATE_EXP,
284
+ + MODEL_TENSOR.FFN_DOWN_EXP,
285
+ + MODEL_TENSOR.FFN_UP_EXP,
286
+ + MODEL_TENSOR.FFN_GATE_UP_EXP,
287
+ + MODEL_TENSOR.FFN_GATE_SHEXP,
288
+ + MODEL_TENSOR.FFN_DOWN_SHEXP,
289
+ + MODEL_TENSOR.FFN_UP_SHEXP,
290
+ + MODEL_TENSOR.NEXTN_EH_PROJ,
291
+ + MODEL_TENSOR.NEXTN_EMBED_TOKENS,
292
+ + MODEL_TENSOR.NEXTN_ENORM,
293
+ + MODEL_TENSOR.NEXTN_HNORM,
294
+ + MODEL_TENSOR.NEXTN_SHARED_HEAD_HEAD,
295
+ + MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
296
+ + ],
297
+ MODEL_ARCH.HUNYUAN_DENSE: [
298
+ MODEL_TENSOR.TOKEN_EMBD,
299
+ MODEL_TENSOR.OUTPUT_NORM,
300
+ diff --git a/gguf-py/gguf/tensor_mapping.py b/gguf-py/gguf/tensor_mapping.py
301
+ index a9537983d..99cf5158a 100644
302
+ --- a/gguf-py/gguf/tensor_mapping.py
303
+ +++ b/gguf-py/gguf/tensor_mapping.py
304
+ @@ -2397,6 +2397,7 @@ class TensorNameMap:
305
+
306
+ MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM: (
307
+ "model.layers.{bid}.shared_head.norm",
308
+ + "model.layers.{bid}.final_layernorm", # hy_v3 MTP
309
+ ),
310
+ }
311
+
312
+ diff --git a/src/llama-arch.cpp b/src/llama-arch.cpp
313
+ index 6a5d5f8d2..888cafcb2 100644
314
+ --- a/src/llama-arch.cpp
315
+ +++ b/src/llama-arch.cpp
316
+ @@ -110,6 +110,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
317
+ { LLM_ARCH_ERNIE4_5, "ernie4_5" },
318
+ { LLM_ARCH_ERNIE4_5_MOE, "ernie4_5-moe" },
319
+ { LLM_ARCH_HUNYUAN_MOE, "hunyuan-moe" },
320
+ + { LLM_ARCH_HYV3, "hy_v3" },
321
+ { LLM_ARCH_HUNYUAN_DENSE, "hunyuan-dense" },
322
+ { LLM_ARCH_HUNYUAN_VL, "hunyuan_vl" },
323
+ { LLM_ARCH_SMOLLM3, "smollm3" },
324
+ diff --git a/src/llama-arch.h b/src/llama-arch.h
325
+ index 03b1a265d..88c9236b5 100644
326
+ --- a/src/llama-arch.h
327
+ +++ b/src/llama-arch.h
328
+ @@ -114,6 +114,7 @@ enum llm_arch {
329
+ LLM_ARCH_ERNIE4_5,
330
+ LLM_ARCH_ERNIE4_5_MOE,
331
+ LLM_ARCH_HUNYUAN_MOE,
332
+ + LLM_ARCH_HYV3,
333
+ LLM_ARCH_HUNYUAN_DENSE,
334
+ LLM_ARCH_HUNYUAN_VL,
335
+ LLM_ARCH_SMOLLM3,
336
+ diff --git a/src/llama-model.cpp b/src/llama-model.cpp
337
+ index 4f12e0949..a03b39ecc 100644
338
+ --- a/src/llama-model.cpp
339
+ +++ b/src/llama-model.cpp
340
+ @@ -253,6 +253,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params
341
+ return new llama_model_paddleocr(params);
342
+ case LLM_ARCH_HUNYUAN_MOE:
343
+ return new llama_model_hunyuan_moe(params);
344
+ + case LLM_ARCH_HYV3:
345
+ + return new llama_model_hyv3(params);
346
+ case LLM_ARCH_HUNYUAN_VL:
347
+ return new llama_model_hunyuan_vl(params);
348
+ case LLM_ARCH_HUNYUAN_DENSE:
349
+ @@ -2479,6 +2481,7 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {
350
+ case LLM_ARCH_STEP35:
351
+ case LLM_ARCH_TALKIE:
352
+ case LLM_ARCH_MELLUM:
353
+ + case LLM_ARCH_HYV3:
354
+ return LLAMA_ROPE_TYPE_NEOX;
355
+
356
+ case LLM_ARCH_QWEN2VL:
357
+ diff --git a/src/models/models.h b/src/models/models.h
358
+ index c137e32e8..641fdf9e9 100644
359
+ --- a/src/models/models.h
360
+ +++ b/src/models/models.h
361
+ @@ -1566,6 +1566,22 @@ struct llama_model_hunyuan_moe : public llama_model_base {
362
+ std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
363
+ };
364
+
365
+ +struct llama_model_hyv3 : public llama_model_base {
366
+ + llama_model_hyv3(const struct llama_model_params & params) : llama_model_base(params) {}
367
+ + void load_arch_hparams(llama_model_loader & ml) override;
368
+ + void load_arch_tensors(llama_model_loader & ml) override;
369
+ +
370
+ + struct graph : public llm_graph_context {
371
+ + graph(const llama_model & model, const llm_graph_params & params);
372
+ + };
373
+ +
374
+ + struct graph_mtp : public llm_graph_context {
375
+ + graph_mtp(const llama_model & model, const llm_graph_params & params);
376
+ + };
377
+ +
378
+ + std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
379
+ +};
380
+ +
381
+
382
+ struct llama_model_hunyuan_vl : public llama_model_base {
383
+ llama_model_hunyuan_vl(const struct llama_model_params & params) : llama_model_base(params) {}
hy3-llama-patch/HF_README.md ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ - zh
6
+ pipeline_tag: text-generation
7
+ tags:
8
+ - moe
9
+ - hy3
10
+ - hy-v3
11
+ - huggingface
12
+ - llama.cpp
13
+ - apple-silicon
14
+ - metal
15
+ - local-ai
16
+ - 295b
17
+ base_model: tencent/Hy3
18
+ datasets:
19
+ - AngelSlim/Hy3-GGUF
20
+ ---
21
+
22
+ # Hy3 (hy_v3) — macOS Metal Build
23
+
24
+ **Run Tencent Hy3 295B MoE on a MacBook via llama.cpp + Metal.**
25
+
26
+ Not in the cloud. Not on a cluster. On a laptop.
27
+
28
+ ## 🔥 The Numbers
29
+
30
+ | What | Value |
31
+ |------|-------|
32
+ | Architecture | Hy3 (hy_v3) — Hunyuan V3 |
33
+ | Parameters | **295B** |
34
+ | Layers | 81 (80 routed + 1 MTP) |
35
+ | Experts | 192 (8 active per token) |
36
+ | Quantization | IQ1_M (mixed recipe) |
37
+ | File size | ~85 GB |
38
+ | RAM needed | **96-128 GB** (Apple Silicon) |
39
+ | License | Apache 2.0 |
40
+
41
+ ## 🏆 Why This Matters
42
+
43
+ A **295B MoE model running on a single MacBook** is a milestone for open local AI:
44
+
45
+ - ✅ No cloud, no API keys, no subscriptions
46
+ - ✅ Total privacy — data never leaves your machine
47
+ - ✅ No dedicated GPU — Apple Silicon unified memory is enough
48
+ - ✅ Portable — runs on a laptop, not a server rack
49
+
50
+ ## 📥 Download the GGUF
51
+
52
+ The quantized model comes from [AngelSlim/Hy3-GGUF](https://huggingface.co/AngelSlim/Hy3-GGUF):
53
+
54
+ ```bash
55
+ # IQ1_M with MTP (85 GB) — recommended for 128 GB
56
+ wget https://huggingface.co/AngelSlim/Hy3-GGUF/resolve/main/Hy3-IQ1_M-mtp.gguf
57
+
58
+ # IQ1_M without MTP (84 GB) — for 96 GB
59
+ wget https://huggingface.co/AngelSlim/Hy3-GGUF/resolve/main/Hy3-IQ1_M.gguf
60
+ ```
61
+
62
+ ## 🛠️ Build & Run
63
+
64
+ ### 1. Clone the patched llama.cpp
65
+
66
+ ```bash
67
+ git clone https://github.com/RobZombAI/llama.cpp-metal_hyv3
68
+ cd llama.cpp-metal_hyv3
69
+ ```
70
+
71
+ ### 2. Build with Metal
72
+
73
+ ```bash
74
+ mkdir build && cd build
75
+ cmake .. -DLLAMA_METAL=ON
76
+ make -j$(sysctl -n hw.logicalcpu)
77
+ ```
78
+
79
+ ### 3. Run inference
80
+
81
+ ```bash
82
+ ./build/bin/llama-cli \
83
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
84
+ -c 65536 \
85
+ -ngl 99 \
86
+ -fa on \
87
+ -ctk q8_0 -ctv q8_0 \
88
+ -p "Hello" \
89
+ -n 100 \
90
+ --temp 0.6
91
+ ```
92
+
93
+ ### With reasoning enabled
94
+
95
+ ```bash
96
+ ./build/bin/llama-cli \
97
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
98
+ -c 65536 -ngl 99 -fa on \
99
+ -ctk q8_0 -ctv q8_0 \
100
+ -p "Explain quantum computing" \
101
+ -n 300 --temp 0.6 \
102
+ --reasoning on --reasoning-budget -1
103
+ ```
104
+
105
+ ### With MTP self-speculative decoding (higher throughput)
106
+
107
+ ```bash
108
+ ./build/bin/llama-cli \
109
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
110
+ -c 65536 -ngl 99 -fa on \
111
+ --spec-type draft-mtp --spec-draft-n-max 3 --spec-draft-n-min 1 \
112
+ -ctk q8_0 -ctv q8_0 \
113
+ -ctkd q8_0 -ctvd q8_0 \
114
+ -p "Hello" -n 200 --temp 0.6
115
+ ```
116
+
117
+ ### OpenAI-compatible server
118
+
119
+ ```bash
120
+ ./build/bin/llama-server \
121
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
122
+ -c 65536 -ngl 99 -fa on \
123
+ -ctk q8_0 -ctv q8_0 \
124
+ --temp 0.6 --port 8080
125
+ ```
126
+
127
+ ### For 96 GB Macs (no MTP, compressed KV)
128
+
129
+ ```bash
130
+ ./build/bin/llama-cli \
131
+ -m ~/Downloads/Hy3-IQ1_M.gguf \
132
+ -c 65536 -ngl 99 -fa on \
133
+ -ctk q8_0 -ctv q8_0 \
134
+ -p "Hello" -n 100 --temp 0.6
135
+ ```
136
+
137
+ ## 📊 Hardware Requirements
138
+
139
+ | Mac | RAM | MTP | Context | Notes |
140
+ |-----|-----|:---:|:--------:|-------|
141
+ | M5/M4/M3 Max | 128 GB | ✅ | 64K | Everything on |
142
+ | MacBook Pro | 128 GB | ✅ | 64K | Runs on a laptop |
143
+ | Mac Studio | 96 GB | ❌ | 64K | KV q8_0, no MTP |
144
+ | MacBook Pro | 96 GB | ❌ | 64K | Same |
145
+
146
+ ## 🙏 Credits
147
+
148
+ - **Tencent** — original [Hy3 model](https://huggingface.co/tencent/Hy3) (Apache 2.0)
149
+ - **[AngelSlim](https://huggingface.co/AngelSlim)** — GGUF quantization, mixed recipes, importance matrix, base llama.cpp patches, benchmarks. **The real MVP.**
150
+ - **RobZombAI** — macOS Metal build, patches integration, testing
151
+
152
+ ## 📚 Resources
153
+
154
+ - GitHub repo: [github.com/RobZombAI/llama.cpp-metal_hyv3](https://github.com/RobZombAI/llama.cpp-metal_hyv3)
155
+ - AngelSlim's GGUF: [huggingface.co/AngelSlim/Hy3-GGUF](https://huggingface.co/AngelSlim/Hy3-GGUF)
156
+ - Original model: [huggingface.co/tencent/Hy3](https://huggingface.co/tencent/Hy3)
157
+ - llama.cpp: [github.com/ggml-org/llama.cpp](https://github.com/ggml-org/llama.cpp)
158
+
159
+ ## 📜 License
160
+
161
+ Apache 2.0 — same as the original Tencent Hy3 model and AngelSlim's patches.
162
+
163
+ ---
164
+
165
+ **Long live open local AI. 🎉**
hy3-llama-patch/README.md ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hy3 (hy_v3) for llama.cpp — macOS Metal support
2
+
3
+ Patches and binaries to run **Tencent Hy3 (hy_v3) 295B MoE** on **llama.cpp** with **Apple Silicon GPU (Metal)**.
4
+
5
+ ---
6
+
7
+ ## Why this project exists
8
+
9
+ llama.cpp supports dozens of architectures, but **Hy3 (hy_v3) wasn't one of them**. Tencent released Hy3 as open-source (Apache 2.0), AngelSlim quantized it to GGUF, but to run it on a Mac someone had to write the missing piece: hy_v3 architecture support in llama.cpp.
10
+
11
+ This repo contains **the patches that add hy_v3 to llama.cpp** — architecture detection, weight loading, MoE + shared expert forward pass, and MTP self-speculative decoding. All compiled with **Metal** for Apple Silicon GPU.
12
+
13
+ The goal is simple: **run a 295B model on a MacBook**. Not on the cloud, not on a cluster. On a laptop.
14
+
15
+ ---
16
+
17
+ ## Credits
18
+
19
+ This starts and ends with **AngelSlim** and their work on HuggingFace:
20
+
21
+ [**AngelSlim/Hy3-GGUF**](https://huggingface.co/AngelSlim/Hy3-GGUF) — GGUF-quantized model (IQ1_M and Q4_K_M), mixed-precision recipes, importance matrix, setup script, benchmarks, chat template. Without this work, this project wouldn't exist.
22
+
23
+ AngelSlim provided:
24
+ - The **base patches** for hy_v3 architecture in llama.cpp
25
+ - The **IQ1_M quantization** with mixed recipe (critical weights in Q8_0/Q6_K, experts in IQ1_M/IQ2_XXS)
26
+ - The **importance matrix** to allocate bits where they matter
27
+ - The **chat template** for tool calling and reasoning
28
+
29
+ This repo takes those patches, applies them to llama.cpp, and **builds them with Metal for macOS**.
30
+
31
+ **Thank you AngelSlim.** 🙌
32
+
33
+ ---
34
+
35
+ ## The model
36
+
37
+ | Detail | Value |
38
+ |--------|-------|
39
+ | **Architecture** | Hy3 (hy_v3) — Hunyuan V3 |
40
+ | **Developed by** | Tencent |
41
+ | **Parameters** | 295B |
42
+ | **Layers** | 81 (80 routed + 1 MTP) |
43
+ | **Experts** | 192 (8 active per token) |
44
+ | **Gating** | Sigmoid + correction bias + top-8 selection |
45
+ | **Quantization** | IQ1_M (AngelSlim mixed recipe) |
46
+ | **File size** | ~85 GB (with MTP) |
47
+
48
+ > Original HF model: [Tencent/Hy3](https://huggingface.co/tencent/Hy3)
49
+ > AngelSlim GGUF quant: [**AngelSlim/Hy3-GGUF**](https://huggingface.co/AngelSlim/Hy3-GGUF)
50
+ > Download: [Hy3-IQ1_M-mtp.gguf](https://huggingface.co/AngelSlim/Hy3-GGUF/resolve/main/Hy3-IQ1_M-mtp.gguf) (85 GB, IQ1_M with MTP)
51
+
52
+ **IQ1_M vs BF16 quality loss**: ~+0.3% PPL — **imperceptible**. Full benchmarks on [AngelSlim's HF page](https://huggingface.co/AngelSlim/Hy3-GGUF), file `assets/benchmark.png`.
53
+
54
+ ---
55
+
56
+ ## Why a MacBook?
57
+
58
+ | Mac | RAM | IQ1_M (85 GB) | MTP | Context | Notes |
59
+ |-----|-----|:---:|:---:|:--------:|-------|
60
+ | **M5 Max** | 128 GB | ✅ | ✅ | 64K | Everything on, comfortable |
61
+ | **M4 Max** | 128 GB | ✅ | ✅ | 64K | Everything on |
62
+ | **M3 Max** | 128 GB | ✅ | ✅ | 64K | Everything on |
63
+ | **MacBook Pro** | 128 GB | ✅ | ✅ | 64K | Runs on a laptop |
64
+ | **Mac Studio** | 96 GB | ✅ | ❌ | 64K | KV q8_0 only, no MTP |
65
+ | **MacBook Pro** | 96 GB | ✅ | ❌ | 64K | Same as above |
66
+
67
+ A **295B MoE running on a MacBook with 128 GB** is a concrete milestone for local AI:
68
+
69
+ - **No cloud**, no API keys, no subscriptions
70
+ - **Total privacy** — data never leaves your machine
71
+ - **No dedicated GPU needed** — Apple Silicon unified memory is enough
72
+ - **Portable** — no server rack, no cluster
73
+
74
+ With 96 GB it still works: the model is ~85 GB, leaving ~11 GB for the system. Just compress the KV cache (`-ctk q8_0 -ctv q8_0`) and skip MTP (which adds ~2 GB of weights plus a draft KV cache). With 128 GB everything runs — MTP included, with headroom.
75
+
76
+ ---
77
+
78
+ ## Download
79
+
80
+ ### 1. The GGUF model
81
+
82
+ ```bash
83
+ # IQ1_M with MTP (85 GB) — recommended for 128 GB
84
+ wget https://huggingface.co/AngelSlim/Hy3-GGUF/resolve/main/Hy3-IQ1_M-mtp.gguf
85
+
86
+ # IQ1_M without MTP (84 GB) — for 96 GB
87
+ wget https://huggingface.co/AngelSlim/Hy3-GGUF/resolve/main/Hy3-IQ1_M.gguf
88
+ ```
89
+
90
+ ### 2. The code
91
+
92
+ ```bash
93
+ git clone https://github.com/ggml-org/llama.cpp
94
+ cd llama.cpp
95
+ git checkout 19bba67c1
96
+ git apply /path/to/0001-add-hyv3-support.patch
97
+ cp /path/to/hyv3.cpp src/models/
98
+ ```
99
+
100
+ Or clone the ready-made fork:
101
+
102
+ ```bash
103
+ git clone https://github.com/RobZombAI/llama.cpp-metal_hyv3
104
+ cd llama.cpp-metal_hyv3
105
+ ```
106
+
107
+ ### 3. Build
108
+
109
+ ```bash
110
+ mkdir build && cd build
111
+ cmake .. -DLLAMA_METAL=ON
112
+ make -j$(sysctl -n hw.logicalcpu)
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Commands
118
+
119
+ ### CLI (base inference)
120
+
121
+ ```bash
122
+ ./build/bin/llama-cli \
123
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
124
+ -c 65536 \
125
+ -ngl 99 \
126
+ -fa on \
127
+ -ctk q8_0 -ctv q8_0 \
128
+ -p "Hello" \
129
+ -n 100 \
130
+ --temp 0.6
131
+ ```
132
+
133
+ ### CLI (with reasoning/thinking)
134
+
135
+ ```bash
136
+ ./build/bin/llama-cli \
137
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
138
+ -c 65536 \
139
+ -ngl 99 -fa on \
140
+ -ctk q8_0 -ctv q8_0 \
141
+ -p "Hello" \
142
+ -n 200 \
143
+ --temp 0.6 \
144
+ --reasoning on \
145
+ --reasoning-budget -1
146
+ ```
147
+
148
+ ### CLI (with MTP self-speculative — higher throughput)
149
+
150
+ ```bash
151
+ ./build/bin/llama-cli \
152
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
153
+ -c 65536 \
154
+ -ngl 99 -fa on \
155
+ --spec-type draft-mtp \
156
+ --spec-draft-n-max 3 \
157
+ --spec-draft-n-min 1 \
158
+ -ctk q8_0 -ctv q8_0 \
159
+ -ctkd q8_0 -ctvd q8_0 \
160
+ -p "Hello" \
161
+ -n 200 \
162
+ --temp 0.6
163
+ ```
164
+
165
+ ### Server (OpenAI-compatible API)
166
+
167
+ ```bash
168
+ ./build/bin/llama-server \
169
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
170
+ -c 65536 \
171
+ -ngl 99 -fa on \
172
+ -ctk q8_0 -ctv q8_0 \
173
+ --temp 0.6 \
174
+ --port 8080
175
+ ```
176
+
177
+ Test API call:
178
+
179
+ ```bash
180
+ curl http://localhost:8080/v1/chat/completions \
181
+ -H "Content-Type: application/json" \
182
+ -d '{
183
+ "model": "Hy3-IQ1_M-mtp",
184
+ "messages": [{"role": "user", "content": "Hello"}],
185
+ "temperature": 0.6,
186
+ "max_tokens": 200
187
+ }'
188
+ ```
189
+
190
+ ### Server (with reasoning + MTP)
191
+
192
+ ```bash
193
+ ./build/bin/llama-server \
194
+ -m ~/Downloads/Hy3-IQ1_M-mtp.gguf \
195
+ -c 65536 \
196
+ -ngl 99 -fa on \
197
+ --spec-type draft-mtp \
198
+ --spec-draft-n-max 3 \
199
+ --spec-draft-n-min 1 \
200
+ -ctk q8_0 -ctv q8_0 \
201
+ -ctkd q8_0 -ctvd q8_0 \
202
+ --temp 0.6 \
203
+ --reasoning on \
204
+ --reasoning-budget -1 \
205
+ --port 8080
206
+ ```
207
+
208
+ ### For Mac with 96 GB RAM
209
+
210
+ ```bash
211
+ # No MTP, compressed KV cache
212
+ ./build/bin/llama-cli \
213
+ -m ~/Downloads/Hy3-IQ1_M.gguf \
214
+ -c 65536 \
215
+ -ngl 99 -fa on \
216
+ -ctk q8_0 -ctv q8_0 \
217
+ -p "Hello" -n 100 \
218
+ --temp 0.6
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Flag reference
224
+
225
+ | Flag | What it does |
226
+ |------|--------------|
227
+ | `-m PATH` | Path to the GGUF model file |
228
+ | `-c N` | Context size in tokens. `65536` = 64K. Higher = more memory |
229
+ | `-ngl N` | Layers to offload to GPU. `99` = all layers on Metal |
230
+ | `-fa on` | Flash attention — reduces memory and speeds up attention |
231
+ | `-ctk q8_0 -ctv q8_0` | KV cache in q8_0. **Essential for 96 GB** (saves ~20 GB) |
232
+ | `--temp N` | Sampling temperature. `0.0` = deterministic/greedy |
233
+ | `--reasoning on` | Enable thinking/reasoning (tag) |
234
+ | `--reasoning-budget N` | Max tokens for thinking. `-1` = unlimited |
235
+ | `--spec-type draft-mtp` | MTP self-speculative decoding (*-mtp.gguf only) |
236
+ | `--spec-draft-n-max N` | Max draft tokens per MTP step |
237
+
238
+ ---
239
+
240
+ ## Project structure
241
+
242
+ ```
243
+ ├── 0001-add-hyv3-support.patch # Patch for 9 llama.cpp files (383 lines)
244
+ ├── src/models/hyv3.cpp # hy_v3 model implementation + MTP (388 lines)
245
+ └── README.md # This file
246
+ ```
247
+
248
+ ## Modified files in llama.cpp
249
+
250
+ | File | Change |
251
+ |------|--------|
252
+ | `src/llama-arch.h` | New enum `LLM_ARCH_HYV3` |
253
+ | `src/llama-arch.cpp` | Architecture name `hy_v3` |
254
+ | `src/llama-model.cpp` | Model mapping + Neox rope type |
255
+ | `src/models/models.h` | `llama_model_hyv3` class declaration |
256
+ | `src/models/hyv3.cpp` | **New** — load, forward, MTP draft head |
257
+ | `gguf-py/gguf/constants.py` | Arch enum + tensor list (28 hy_v3 tensors) |
258
+ | `gguf-py/gguf/tensor_mapping.py` | MTP tensor name mapping |
259
+ | `conversion/__init__.py` | HF → GGUF model name mapping |
260
+ | `common/chat.cpp` | Chat template parser (tool calls + reasoning) |
261
+
262
+ ## License
263
+
264
+ **Apache 2.0.** Same as the original [Tencent/Hy3](https://huggingface.co/tencent/Hy3) model and [AngelSlim](https://huggingface.co/AngelSlim)'s patches.
265
+
266
+ ---
267
+
268
+ **Long live open local AI. A 295B model running on a MacBook. 🎉**
hy3-llama-patch/src/models/hyv3.cpp ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "models.h"
2
+
3
+ void llama_model_hyv3::load_arch_hparams(llama_model_loader & ml) {
4
+ ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
5
+ ml.get_key(LLM_KV_EXPERT_FEED_FORWARD_LENGTH, hparams.n_ff_exp);
6
+ ml.get_key(LLM_KV_EXPERT_SHARED_FEED_FORWARD_LENGTH, hparams.n_ff_shexp, false);
7
+ ml.get_key(LLM_KV_EXPERT_GATING_FUNC, hparams.expert_gating_func, false);
8
+ ml.get_key(LLM_KV_EXPERT_WEIGHTS_SCALE, hparams.expert_weights_scale, false);
9
+ ml.get_key(LLM_KV_EXPERT_WEIGHTS_NORM, hparams.expert_weights_norm, false);
10
+
11
+ // MTP / NextN head (optional). Absent in pre-MTP gguf -> n_layer_nextn stays 0
12
+ // and everything below (extra block load, MTP graph, MTP context) is skipped.
13
+ ml.get_key(LLM_KV_NEXTN_PREDICT_LAYERS, hparams.n_layer_nextn, false);
14
+
15
+ if (hparams.expert_gating_func == LLAMA_EXPERT_GATING_FUNC_TYPE_NONE) {
16
+ hparams.expert_gating_func = LLAMA_EXPERT_GATING_FUNC_TYPE_SIGMOID;
17
+ }
18
+
19
+ switch (hparams.n_layer()) {
20
+ case 48: type = LLM_TYPE_30B_A3B; break;
21
+ default: type = LLM_TYPE_UNKNOWN;
22
+ }
23
+ }
24
+
25
+ void llama_model_hyv3::load_arch_tensors(llama_model_loader &) {
26
+ LLAMA_LOAD_LOCALS;
27
+
28
+ tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
29
+
30
+ output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0);
31
+ output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED);
32
+ if (output == NULL) {
33
+ output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, TENSOR_DUPLICATED);
34
+ }
35
+
36
+ for (int i = 0; i < n_layer; ++i) {
37
+ auto & layer = layers[i];
38
+ const int64_t n_ff_exp = hparams.n_ff_exp ? hparams.n_ff_exp : n_ff / n_expert_used;
39
+ const int64_t n_ff_shexp = hparams.n_ff_shexp ? hparams.n_ff_shexp : n_ff_exp;
40
+
41
+ layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0);
42
+
43
+ create_tensor_qkv(layer, i, n_embd, n_embd_head_k * n_head, n_embd_k_gqa, n_embd_v_gqa, 0);
44
+ layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd}, 0);
45
+
46
+ layer.attn_q_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_NORM, "weight", i), {n_embd_head_k}, 0);
47
+ layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), {n_embd_head_k}, 0);
48
+
49
+ layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, 0);
50
+
51
+ layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}, TENSOR_NOT_REQUIRED);
52
+ layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), {n_ff, n_embd}, TENSOR_NOT_REQUIRED);
53
+ layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, TENSOR_NOT_REQUIRED);
54
+
55
+ layer.ffn_gate_inp = create_tensor(tn(LLM_TENSOR_FFN_GATE_INP, "weight", i), {n_embd, n_expert}, TENSOR_NOT_REQUIRED);
56
+ layer.ffn_exp_probs_b = create_tensor(tn(LLM_TENSOR_FFN_EXP_PROBS_B, "bias", i), {n_expert}, TENSOR_NOT_REQUIRED);
57
+ layer.ffn_down_exps = create_tensor(tn(LLM_TENSOR_FFN_DOWN_EXPS, "weight", i), {n_ff_exp, n_embd, n_expert}, TENSOR_NOT_REQUIRED);
58
+ create_tensor_gate_up_exps(layer, i, n_embd, n_ff_exp, n_expert, TENSOR_NOT_REQUIRED);
59
+
60
+ layer.ffn_gate_shexp = create_tensor(tn(LLM_TENSOR_FFN_GATE_SHEXP, "weight", i), {n_embd, n_ff_shexp}, TENSOR_NOT_REQUIRED);
61
+ layer.ffn_up_shexp = create_tensor(tn(LLM_TENSOR_FFN_UP_SHEXP, "weight", i), {n_embd, n_ff_shexp}, TENSOR_NOT_REQUIRED);
62
+ layer.ffn_down_shexp = create_tensor(tn(LLM_TENSOR_FFN_DOWN_SHEXP, "weight", i), {n_ff_shexp, n_embd}, TENSOR_NOT_REQUIRED);
63
+ }
64
+
65
+ // MTP / NextN block(s): loaded as extra decoder blocks beyond the main stack
66
+ // (index range [n_layer, n_layer_all)). Skipped entirely for pre-MTP gguf
67
+ // where n_layer_all == n_layer, so those models load unchanged.
68
+ for (int i = n_layer; i < (int) hparams.n_layer_all; ++i) {
69
+ auto & layer = layers[i];
70
+ const int64_t n_ff_exp = hparams.n_ff_exp ? hparams.n_ff_exp : n_ff / n_expert_used;
71
+ const int64_t n_ff_shexp = hparams.n_ff_shexp ? hparams.n_ff_shexp : n_ff_exp;
72
+
73
+ // Standard hy_v3 MoE decoder block (same layout as a trunk sparse layer).
74
+ layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0);
75
+
76
+ create_tensor_qkv(layer, i, n_embd, n_embd_head_k * n_head, n_embd_k_gqa, n_embd_v_gqa, 0);
77
+ layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd}, 0);
78
+
79
+ layer.attn_q_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_NORM, "weight", i), {n_embd_head_k}, 0);
80
+ layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), {n_embd_head_k}, 0);
81
+
82
+ layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, 0);
83
+
84
+ layer.ffn_gate_inp = create_tensor(tn(LLM_TENSOR_FFN_GATE_INP, "weight", i), {n_embd, n_expert}, 0);
85
+ layer.ffn_exp_probs_b = create_tensor(tn(LLM_TENSOR_FFN_EXP_PROBS_B, "bias", i), {n_expert}, TENSOR_NOT_REQUIRED);
86
+ layer.ffn_down_exps = create_tensor(tn(LLM_TENSOR_FFN_DOWN_EXPS, "weight", i), {n_ff_exp, n_embd, n_expert}, 0);
87
+ create_tensor_gate_up_exps(layer, i, n_embd, n_ff_exp, n_expert, 0);
88
+
89
+ layer.ffn_gate_shexp = create_tensor(tn(LLM_TENSOR_FFN_GATE_SHEXP, "weight", i), {n_embd, n_ff_shexp}, TENSOR_NOT_REQUIRED);
90
+ layer.ffn_up_shexp = create_tensor(tn(LLM_TENSOR_FFN_UP_SHEXP, "weight", i), {n_embd, n_ff_shexp}, TENSOR_NOT_REQUIRED);
91
+ layer.ffn_down_shexp = create_tensor(tn(LLM_TENSOR_FFN_DOWN_SHEXP, "weight", i), {n_ff_shexp, n_embd}, TENSOR_NOT_REQUIRED);
92
+
93
+ // NextN-specific tensors. eh_proj fuses [enorm(embed), hnorm(hidden)] (2*n_embd -> n_embd).
94
+ // shared_head_head / embed_tokens are tied to the main lm_head / tok_embd in Hy3,
95
+ // so they are optional and fall back in the graph.
96
+ layer.nextn.eh_proj = create_tensor(tn(LLM_TENSOR_NEXTN_EH_PROJ, "weight", i), {2 * n_embd, n_embd}, 0);
97
+ layer.nextn.enorm = create_tensor(tn(LLM_TENSOR_NEXTN_ENORM, "weight", i), {n_embd}, 0);
98
+ layer.nextn.hnorm = create_tensor(tn(LLM_TENSOR_NEXTN_HNORM, "weight", i), {n_embd}, 0);
99
+ layer.nextn.shared_head_norm = create_tensor(tn(LLM_TENSOR_NEXTN_SHARED_HEAD_NORM, "weight", i), {n_embd}, TENSOR_NOT_REQUIRED);
100
+ layer.nextn.shared_head_head = create_tensor(tn(LLM_TENSOR_NEXTN_SHARED_HEAD_HEAD, "weight", i), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED);
101
+ layer.nextn.embed_tokens = create_tensor(tn(LLM_TENSOR_NEXTN_EMBED_TOKENS, "weight", i), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED);
102
+ }
103
+ }
104
+
105
+ std::unique_ptr<llm_graph_context> llama_model_hyv3::build_arch_graph(const llm_graph_params & params) const {
106
+ if (params.gtype == LLM_GRAPH_TYPE_DECODER_MTP) {
107
+ return std::make_unique<graph_mtp>(*this, params);
108
+ }
109
+ return std::make_unique<graph>(*this, params);
110
+ }
111
+
112
+ llama_model_hyv3::graph::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) {
113
+ const int64_t n_embd_head = hparams.n_embd_head_v();
114
+
115
+ GGML_ASSERT(n_embd_head == hparams.n_embd_head_k());
116
+ GGML_ASSERT(n_embd_head == n_rot);
117
+
118
+ ggml_tensor * cur;
119
+ ggml_tensor * inpL;
120
+
121
+ inpL = build_inp_embd(model.tok_embd);
122
+ ggml_tensor * inp_pos = build_inp_pos();
123
+ auto * inp_attn = build_attn_inp_kv();
124
+ ggml_tensor * inp_out_ids = build_inp_out_ids();
125
+
126
+ const float kq_scale = 1.0f / sqrtf(float(n_embd_head));
127
+
128
+ for (int il = 0; il < n_layer; ++il) {
129
+ ggml_tensor * inpSA = inpL;
130
+
131
+ cur = build_norm(inpL, model.layers[il].attn_norm, nullptr, LLM_NORM_RMS, il);
132
+ cb(cur, "attn_norm", il);
133
+
134
+ {
135
+ ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
136
+
137
+ auto [Qcur, Kcur, Vcur] = build_qkv(model.layers[il], cur, n_embd_head, n_head, n_head_kv, il);
138
+
139
+ Qcur = build_norm(Qcur, model.layers[il].attn_q_norm, nullptr, LLM_NORM_RMS, il);
140
+ Kcur = build_norm(Kcur, model.layers[il].attn_k_norm, nullptr, LLM_NORM_RMS, il);
141
+
142
+ Qcur = ggml_rope_ext(ctx0, Qcur, inp_pos, rope_factors,
143
+ n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
144
+ ext_factor, attn_factor, beta_fast, beta_slow);
145
+ Kcur = ggml_rope_ext(ctx0, Kcur, inp_pos, rope_factors,
146
+ n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
147
+ ext_factor, attn_factor, beta_fast, beta_slow);
148
+
149
+ cur = build_attn(inp_attn,
150
+ model.layers[il].wo, model.layers[il].wo_b, model.layers[il].wo_s,
151
+ Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il);
152
+ cb(cur, "attn_out", il);
153
+ }
154
+
155
+ if (il == n_layer - 1 && inp_out_ids && cparams.embeddings_nextn_masked) {
156
+ cur = ggml_get_rows(ctx0, cur, inp_out_ids);
157
+ inpSA = ggml_get_rows(ctx0, inpSA, inp_out_ids);
158
+ }
159
+
160
+ ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);
161
+ cb(ffn_inp, "ffn_inp", il);
162
+
163
+ cur = build_norm(ffn_inp, model.layers[il].ffn_norm, nullptr, LLM_NORM_RMS, il);
164
+ cb(cur, "ffn_norm", il);
165
+
166
+ if (model.layers[il].ffn_gate_inp == nullptr) {
167
+ cur = build_ffn(cur,
168
+ model.layers[il].ffn_up, model.layers[il].ffn_up_b, model.layers[il].ffn_up_s,
169
+ model.layers[il].ffn_gate, model.layers[il].ffn_gate_b, model.layers[il].ffn_gate_s,
170
+ model.layers[il].ffn_down, model.layers[il].ffn_down_b, model.layers[il].ffn_down_s,
171
+ nullptr,
172
+ LLM_FFN_SILU, LLM_FFN_PAR, il);
173
+ cb(cur, "ffn_dense_out", il);
174
+ } else {
175
+ ggml_tensor * moe_out = build_moe_ffn(cur,
176
+ model.layers[il].ffn_gate_inp,
177
+ model.layers[il].ffn_up_exps,
178
+ model.layers[il].ffn_gate_exps,
179
+ model.layers[il].ffn_down_exps,
180
+ model.layers[il].ffn_exp_probs_b,
181
+ n_expert, n_expert_used,
182
+ LLM_FFN_SILU,
183
+ hparams.expert_weights_norm,
184
+ hparams.expert_weights_scale,
185
+ (llama_expert_gating_func_type) hparams.expert_gating_func,
186
+ il,
187
+ nullptr, model.layers[il].ffn_gate_up_exps,
188
+ model.layers[il].ffn_up_exps_s,
189
+ model.layers[il].ffn_gate_exps_s,
190
+ model.layers[il].ffn_down_exps_s);
191
+ cb(moe_out, "ffn_moe_out", il);
192
+
193
+ ggml_tensor * sh_out = build_ffn(cur,
194
+ model.layers[il].ffn_up_shexp, nullptr, model.layers[il].ffn_up_shexp_s,
195
+ model.layers[il].ffn_gate_shexp, nullptr, model.layers[il].ffn_gate_shexp_s,
196
+ model.layers[il].ffn_down_shexp, nullptr, model.layers[il].ffn_down_shexp_s,
197
+ nullptr,
198
+ LLM_FFN_SILU, LLM_FFN_PAR, il);
199
+ cb(sh_out, "ffn_shared_out", il);
200
+
201
+ cur = ggml_add(ctx0, moe_out, sh_out);
202
+ cb(cur, "ffn_out", il);
203
+ }
204
+
205
+ cur = ggml_add(ctx0, cur, ffn_inp);
206
+ cur = build_cvec(cur, il);
207
+ cb(cur, "l_out", il);
208
+
209
+ inpL = cur;
210
+ }
211
+
212
+ cur = build_norm(inpL, model.output_norm, nullptr, LLM_NORM_RMS, -1);
213
+
214
+ // post-norm hidden state feeds both the LM head and the MTP seed (t_h_nextn).
215
+ // When masking is off, the MTP path needs the full-width hidden, so defer the
216
+ // output-id gather until after capturing t_h_nextn (matches qwen35moe).
217
+ cb(cur, "h_nextn", -1);
218
+ res->t_h_nextn = cur;
219
+
220
+ if (!cparams.embeddings_nextn_masked && inp_out_ids) {
221
+ cur = ggml_get_rows(ctx0, cur, inp_out_ids);
222
+ }
223
+
224
+ cb(cur, "result_norm", -1);
225
+ res->t_embd = cur;
226
+
227
+ cur = build_lora_mm(model.output, cur, model.output_s);
228
+ cb(cur, "result_output", -1);
229
+ res->t_logits = cur;
230
+
231
+ ggml_build_forward_expand(gf, cur);
232
+ }
233
+
234
+ // LLM_GRAPH_TYPE_DECODER_MTP draft head for Hy3 MoE.
235
+ // Mirrors vLLM HYV3MultiTokenPredictorLayer.forward:
236
+ // e = enorm(inputs_embeds); h = hnorm(previous_hidden);
237
+ // x = eh_proj(cat([e, h])); x = mtp_block(x); x += residual; x = final_ln(x)
238
+ // logits = lm_head(x) (lm_head/embed tied to the main model)
239
+ // Differences vs qwen35moe MTP: default RoPE (not mrope), no attention gate,
240
+ // no shared-expert gate, sigmoid MoE gating with correction bias.
241
+ llama_model_hyv3::graph_mtp::graph_mtp(const llama_model & model, const llm_graph_params & params)
242
+ : llm_graph_context(params) {
243
+ const int64_t n_embd_head = hparams.n_embd_head_v();
244
+ GGML_ASSERT(n_embd_head == hparams.n_embd_head_k());
245
+ GGML_ASSERT(n_embd_head == n_rot);
246
+
247
+ GGML_ASSERT(hparams.n_layer_nextn > 0 && "HYV3 MTP requires n_layer_nextn > 0");
248
+ GGML_ASSERT(hparams.n_layer_nextn == 1 && "HYV3 MTP currently only supports a single MTP block");
249
+
250
+ const int il = hparams.n_layer();
251
+ const auto & layer = model.layers[il];
252
+
253
+ GGML_ASSERT(layer.nextn.eh_proj && "HYV3 MTP block missing nextn.eh_proj");
254
+ GGML_ASSERT(layer.nextn.enorm && "HYV3 MTP block missing nextn.enorm");
255
+ GGML_ASSERT(layer.nextn.hnorm && "HYV3 MTP block missing nextn.hnorm");
256
+ GGML_ASSERT(layer.ffn_gate_inp && "HYV3 MTP block missing ffn_gate_inp");
257
+
258
+ const float kq_scale = 1.0f / sqrtf(float(n_embd_head));
259
+
260
+ // Inputs: token ids (-> embedding), and the previous-step hidden state h.
261
+ auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
262
+
263
+ inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
264
+ ggml_set_input(inp->tokens);
265
+
266
+ inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
267
+ ggml_set_input(inp->embd);
268
+
269
+ ggml_tensor * tok_embd;
270
+ if (ubatch.token) {
271
+ ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
272
+ tok_embd = ggml_get_rows(ctx0, tok_embd_w, inp->tokens);
273
+ } else {
274
+ tok_embd = inp->embd;
275
+ }
276
+ cb(tok_embd, "mtp_tok_embd", il);
277
+
278
+ inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
279
+ ggml_set_input(inp->h);
280
+ ggml_set_name(inp->h, "mtp_h_input");
281
+
282
+ ggml_tensor * h_embd = inp->h;
283
+
284
+ res->add_input(std::move(inp));
285
+
286
+ ggml_tensor * inp_pos = build_inp_pos();
287
+ ggml_tensor * inp_out_ids = build_inp_out_ids();
288
+
289
+ auto * inp_attn = build_attn_inp_kv();
290
+
291
+ // e = enorm(embed), h = hnorm(hidden); fuse via eh_proj: cat([e, h]) -> n_embd
292
+ ggml_tensor * e_norm = build_norm(tok_embd, layer.nextn.enorm, nullptr, LLM_NORM_RMS, il);
293
+ cb(e_norm, "mtp_enorm", il);
294
+
295
+ ggml_tensor * h_norm = build_norm(h_embd, layer.nextn.hnorm, nullptr, LLM_NORM_RMS, il);
296
+ cb(h_norm, "mtp_hnorm", il);
297
+
298
+ ggml_tensor * concat = ggml_concat(ctx0, e_norm, h_norm, /*dim=*/ 0);
299
+ cb(concat, "mtp_concat", il);
300
+
301
+ ggml_tensor * cur = build_lora_mm(layer.nextn.eh_proj, concat, layer.nextn.eh_proj_s);
302
+ cb(cur, "mtp_eh_proj", il);
303
+
304
+ // --- one standard hy_v3 decoder block on the fused hidden ---
305
+ ggml_tensor * inpSA = cur;
306
+
307
+ cur = build_norm(cur, layer.attn_norm, nullptr, LLM_NORM_RMS, il);
308
+ cb(cur, "mtp_attn_norm", il);
309
+
310
+ {
311
+ ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
312
+
313
+ auto [Qcur, Kcur, Vcur] = build_qkv(layer, cur, n_embd_head, n_head, n_head_kv, il);
314
+
315
+ Qcur = build_norm(Qcur, layer.attn_q_norm, nullptr, LLM_NORM_RMS, il);
316
+ Kcur = build_norm(Kcur, layer.attn_k_norm, nullptr, LLM_NORM_RMS, il);
317
+
318
+ Qcur = ggml_rope_ext(ctx0, Qcur, inp_pos, rope_factors,
319
+ n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
320
+ ext_factor, attn_factor, beta_fast, beta_slow);
321
+ Kcur = ggml_rope_ext(ctx0, Kcur, inp_pos, rope_factors,
322
+ n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
323
+ ext_factor, attn_factor, beta_fast, beta_slow);
324
+
325
+ cur = build_attn(inp_attn,
326
+ layer.wo, layer.wo_b, layer.wo_s,
327
+ Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il);
328
+ cb(cur, "mtp_attn_out", il);
329
+ }
330
+
331
+ ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);
332
+ cb(ffn_inp, "mtp_ffn_inp", il);
333
+
334
+ cur = build_norm(ffn_inp, layer.ffn_norm, nullptr, LLM_NORM_RMS, il);
335
+ cb(cur, "mtp_ffn_norm", il);
336
+
337
+ ggml_tensor * moe_out = build_moe_ffn(cur,
338
+ layer.ffn_gate_inp,
339
+ layer.ffn_up_exps,
340
+ layer.ffn_gate_exps,
341
+ layer.ffn_down_exps,
342
+ layer.ffn_exp_probs_b,
343
+ n_expert, n_expert_used,
344
+ LLM_FFN_SILU,
345
+ hparams.expert_weights_norm,
346
+ hparams.expert_weights_scale,
347
+ (llama_expert_gating_func_type) hparams.expert_gating_func,
348
+ il,
349
+ nullptr, layer.ffn_gate_up_exps,
350
+ layer.ffn_up_exps_s,
351
+ layer.ffn_gate_exps_s,
352
+ layer.ffn_down_exps_s);
353
+ cb(moe_out, "mtp_ffn_moe_out", il);
354
+
355
+ ggml_tensor * sh_out = build_ffn(cur,
356
+ layer.ffn_up_shexp, nullptr, layer.ffn_up_shexp_s,
357
+ layer.ffn_gate_shexp, nullptr, layer.ffn_gate_shexp_s,
358
+ layer.ffn_down_shexp, nullptr, layer.ffn_down_shexp_s,
359
+ nullptr,
360
+ LLM_FFN_SILU, LLM_FFN_PAR, il);
361
+ cb(sh_out, "mtp_ffn_shared_out", il);
362
+
363
+ cur = ggml_add(ctx0, moe_out, sh_out);
364
+ cb(cur, "mtp_ffn_out", il);
365
+
366
+ cur = ggml_add(ctx0, cur, ffn_inp);
367
+ cb(cur, "mtp_post_ffn", il);
368
+
369
+ // final_layernorm then LM head (both fall back to the main model when tied)
370
+ ggml_tensor * head_norm_w = layer.nextn.shared_head_norm ? layer.nextn.shared_head_norm : model.output_norm;
371
+ GGML_ASSERT(head_norm_w && "HYV3 MTP: missing both nextn.shared_head_norm and output_norm");
372
+ cur = build_norm(cur, head_norm_w, nullptr, LLM_NORM_RMS, -1);
373
+
374
+ cb(cur, "h_nextn", -1);
375
+ res->t_h_nextn = cur;
376
+
377
+ cur = ggml_get_rows(ctx0, cur, inp_out_ids);
378
+ cb(cur, "mtp_final_norm", -1);
379
+
380
+ ggml_tensor * head_w = layer.nextn.shared_head_head ? layer.nextn.shared_head_head : model.output;
381
+ ggml_tensor * head_s = layer.nextn.shared_head_head ? layer.nextn.shared_head_head_s : model.output_s;
382
+ GGML_ASSERT(head_w && "HYV3 MTP: missing LM head (nextn.shared_head_head or model.output)");
383
+ cur = build_lora_mm(head_w, cur, head_s);
384
+ cb(cur, "result_output", -1);
385
+
386
+ res->t_logits = cur;
387
+ ggml_build_forward_expand(gf, cur);
388
+ }