Skip to content

Commit 62bf73d

Browse files
pcuencaYoung Hanalbertodepaolashimmyshimmerruanslv
authored
model: Muse Glimmer Support (ggml-org#26841)
* Get started with Onyx * Add architecture * Skip keys handled in super() * Loading tensors * Shorten * Graph * Apply suggestion from @pcuenca * Remove norm now embedding in transformers weights * Add eot * Explicit output_multiplier * Handle post_norm_eps * No super call; unhardcode eot. The pattern `self._set_vocab_gpt2()` seems preferred throughout the codebase, and it allows `set_vocab()` to be called from a different part of the Python class hierarchy: the drafter model converter that we may need eventually. * Register for drafting * DFlash: inherit rope type from the linked target. Another option would be to store it in the gguf file itself. * mmproj conversion Note: some fields to be renamed after the implementation works. We are keeping compatibility with the reference Meta gguf for testing purposes. * "clip" header declarations * Load mmproj * Pre-processing * Graph * Go back to using delimiters. Otherwise our generations are worse. Transformers does not use them. We need to trace inputs to verify whether they are equivalent. * downsample_factor -> merge_size * Add vision graph lol, forgot from a previous commit * Additional renames, align with llama.cpp / transformers * Prefer _size instead of independent _h and _w * Fix token layout Co-authored-by: Young Han <younghan@fb.com> * onyx: bring the chat parser onto the onyx branch common/chat.cpp on this branch has no Onyx handling, so a converted model serves malformed chat: the assistant preamble leaks into content ("to=self<|message|>...") and tool calls fail with HTTP 500 "The model produced output that does not match the expected peg-native format" common_chat_params_init_onyx exists on onyx-fair-patch, added there by 8bb73dd3d. It was never on this branch, so this is not a regression -- the two lines developed independently. The code here is taken verbatim from that commit. It is the clean side of `git merge origin/onyx-fair-patch`: chat.cpp is one of the files that merges without conflict. The full merge is not viable -- it produces 13 conflicts, including add/add on conversion/onyx.py and src/models/onyx.cpp where the q_norm-folding and metadata-scale approaches contradict each other, and #4/#7 are stacked on this branch's side of that. Verified on this branch: builds with 0 errors, converts an Onyx checkpoint, and serving it gives "4" for "What is 2+2?" plus a correct get_weather {"city":"Paris"} tool call, where the unported branch gives the two failures above. No converter or runtime changes are included, so this should not interact with the q_norm work. Co-authored-by: Beto de Paola <betodepaola@meta.com> * Less params, bilinear pos-emb interpolation as a graph op instead of CPU * Map to symbolic V_MMPROJ instead of strings * Make a couple params explicit * Patchify via build_inp() * No param for rope_theta * Small cleanup * Restore blank line * Unpermute, to adapt to the latest transformers checkpoint * Apply norm after token embeddings This follows the latest transformers approach. * Remove duplicated function * build_vit * onyx: use the model rope theta on sliding-window layers * DFlash: conversion from transformers drafter * Revert rope_type derivation from target NOTE: this breaks compatibility with Meta's distributed DFlash GGUFs, as the Q/K are stored in "NEOX" (rotated half) format, like in transformers. * Apply suggestion from @pcuenca * Set model type * Remove comment that will become obsolete * Hardcode post_norm_rms_eps instead of new param * Derive SWA+RoPE pattern from gguf array or scalar * Fix model type <-> number of layers * Reorder * Rename * Fix typo * DFlash: seed the draft KV cache from multimodal embedding batches `common_speculative_impl_draft_dflash::process()` returned early on any batch carrying embeddings, so an image prefill never had its target-layer features fused through the DFlash encoder and injected into the draft's KV cache. That left a hole spanning the image's positions, and the next injection at a post-image position failed to initialize its batch: ``` decoding image batch 1/1, n_tokens_batch = 256 decode: failed to initialize batch llama_decode: failed to decode, ret = -1 process: llama_decode(ctx_dft) failed rc=-1 (n_tokens=17, offset=0) srv decode: failed to process speculative batch ``` Every image request with `--spec-type draft-dflash` failed with HTTP 500. Text-only was unaffected, since those batches carry token ids and were let through. Restore the earlier condition, which admits a batch that is either tokens or embeddings and skips only the degenerate neither/both cases. The rest of `process()` is already layout-agnostic -- it gathers features via `llama_get_embeddings_layer_inp()` and indexes `batch_in.pos[]` / `batch_in.seq_id[]`, none of which assume token ids -- so this is the whole fix. Validated against `muse-glimmer-30B-bf16.gguf` + `mmproj-muse-glimmer-30B-bf16.gguf` + a DFlash draft head, on an image describe-the-shapes request: - before: HTTP 500, `failed to process speculative batch` - after: HTTP 200, draft acceptance 0.34012 (167 accepted / 491 generated), mean len 3.04 Output equivalence holds, which is the property that matters: at temperature 0 the drafted response is byte-identical to the same request served with no draft attached (1213/1213 chars), so the draft is drafting correctly through the image context rather than merely not crashing. * Conversion: prefer rewrite to mapping * Revert "Conversion: prefer rewrite to mapping" This reverts commit a92d0ac. * fix lint * sliding_window metadata is not optional * disable state save/load * Apply suggestion from @pcuenca --------- Co-authored-by: Young Han <younghan@fb.com> Co-authored-by: Beto de Paola <betodepaola@meta.com> Co-authored-by: Daniel Han <michaelhan2050@gmail.com> Co-authored-by: ruanrms <ruanslv@gmail.com> Co-authored-by: Xuan Son Nguyen <son@huggingface.co> Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
1 parent a52077c commit 62bf73d

22 files changed

Lines changed: 877 additions & 9 deletions

common/chat.cpp

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3086,6 +3086,151 @@ static common_chat_params common_chat_params_init_minicpm5(const common_chat_tem
30863086
return data;
30873087
}
30883088

3089+
// An assistant turn is rendered as one or more messages, each
3090+
// "<|start|>assistant to=<recipient><|message|>{content}{END}" where END is
3091+
// <|eom|> (more messages follow) or <|eot|> (end of turn):
3092+
// - chain-of-thought: to=self, terminated by <|eom|>
3093+
// - final answer: to=user, terminated by <|eot|>
3094+
// The generation prompt is just "<|start|>assistant"; the model emits its own
3095+
// " to=...<|message|>".
3096+
static common_chat_params common_chat_params_init_muse_glimmer(const common_chat_template & tmpl,
3097+
const autoparser::generation_params & inputs) {
3098+
common_chat_params data;
3099+
3100+
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
3101+
data.generation_prompt = "<|start|>assistant";
3102+
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
3103+
data.supports_thinking = true;
3104+
3105+
data.preserved_tokens = {
3106+
"<|start|>", "<|message|>", "<|eom|>", "<|eot|>",
3107+
// ATEM tool-call markup emitted on " to=<tool>" turns.
3108+
"<atem:function_calls>", "<atem:invoke", "<atem:parameter", "</atem:parameter>",
3109+
"</atem:invoke>", "</atem:function_calls>",
3110+
};
3111+
3112+
data.message_delimiters = {
3113+
{ COMMON_CHAT_ROLE_ASSISTANT, "<|start|>assistant" },
3114+
{ COMMON_CHAT_ROLE_USER, "<|start|>user" },
3115+
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>system" },
3116+
{ COMMON_CHAT_ROLE_TOOL, "<|start|>tool" },
3117+
};
3118+
3119+
if (inputs.has_continuation()) {
3120+
const auto & msg = inputs.continue_msg;
3121+
3122+
data.generation_prompt = "<|start|>assistant to=self<|message|>" + msg.reasoning_content;
3123+
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
3124+
data.generation_prompt += "<|eom|><|start|>assistant to=user<|message|>" + msg.render_content();
3125+
}
3126+
3127+
data.prompt += data.generation_prompt;
3128+
}
3129+
3130+
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
3131+
3132+
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
3133+
// Constrained grammar whenever tools are offered.
3134+
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
3135+
3136+
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
3137+
auto start = p.rule("start", p.literal("<|start|>assistant"));
3138+
3139+
if (!extract_reasoning && !include_grammar) {
3140+
return start + p.content(p.rest());
3141+
}
3142+
3143+
if (extract_reasoning) {
3144+
p.rule("analysis", p.literal(" to=self<|message|>") + p.reasoning(p.until("<|eom|>")) + p.literal("<|eom|>"));
3145+
} else {
3146+
p.rule("analysis", p.literal(" to=self<|message|>") + p.content(p.until("<|eom|>")) + p.literal("<|eom|>"));
3147+
}
3148+
auto analysis = p.ref("analysis");
3149+
3150+
auto recipient = p.optional(p.literal(" to=user"));
3151+
auto final_msg = p.rule("final", recipient + p.literal("<|message|>") + p.content(p.until("<|eot|>")));
3152+
3153+
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
3154+
auto string_value = p.ac(
3155+
p.tool_arg_string_value(p.until("</atem:parameter>")) + p.tool_arg_close(p.literal("</atem:parameter>")),
3156+
"</atem:parameter>");
3157+
3158+
auto tool_choice = p.choice();
3159+
foreach_function(inputs.tools, [&](const json & tool) {
3160+
const auto & function = tool.at("function");
3161+
const std::string name = function.at("name");
3162+
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
3163+
3164+
auto args = p.eps();
3165+
if (params.contains("properties") && params.at("properties").is_object() && !params.at("properties").empty()) {
3166+
auto schema_info = common_schema_info();
3167+
schema_info.resolve_refs(params);
3168+
3169+
auto arg_choice = p.choice();
3170+
for (const auto & [prop_name, prop_schema] : params.at("properties").items()) {
3171+
auto value_parser = p.eps();
3172+
if (schema_info.resolves_to_string(prop_schema)) {
3173+
value_parser = string_value;
3174+
} else {
3175+
value_parser = p.tool_arg_json_value(
3176+
p.schema(p.json(), "tool-" + name + "-arg-" + prop_name + "-schema", prop_schema, false))
3177+
+ p.tool_arg_close(p.literal("</atem:parameter>"));
3178+
}
3179+
3180+
auto arg_rule = p.tool_arg(
3181+
p.tool_arg_open(p.literal("<atem:parameter name=\"") + p.tool_arg_name(p.literal(prop_name)) + p.literal("\">")) +
3182+
value_parser);
3183+
3184+
arg_choice |= arg_rule;
3185+
}
3186+
args = p.zero_or_more(arg_choice + p.space());
3187+
}
3188+
3189+
auto tool_parser = p.tool(
3190+
p.tool_open(p.literal(" to=") + p.until("<|message|>") +
3191+
p.literal("<|message|><atem:function_calls>") + p.space() +
3192+
p.literal("<atem:invoke name=\"") + p.tool_name(p.literal(name)) + p.literal("\">") + p.space())
3193+
<< p.tool_args(args)
3194+
<< p.tool_close(p.literal("</atem:invoke>") + p.space() + p.literal("</atem:function_calls>")));
3195+
3196+
tool_choice |= p.rule("tool-" + name, tool_parser);
3197+
});
3198+
3199+
auto tool_calls = inputs.parallel_tool_calls
3200+
? p.trigger_rule("tool-call", tool_choice + p.zero_or_more(p.literal("<|eom|>") + start + tool_choice))
3201+
: p.trigger_rule("tool-call", tool_choice);
3202+
3203+
3204+
if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
3205+
return p.zero_or_more(start + analysis) + start + tool_calls;
3206+
}
3207+
return p.zero_or_more(start + analysis) + start + (tool_calls | final_msg);
3208+
}
3209+
3210+
return p.zero_or_more(start + analysis) + start + final_msg;
3211+
});
3212+
3213+
data.parser = parser.save();
3214+
3215+
if (include_grammar) {
3216+
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
3217+
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
3218+
foreach_function(inputs.tools, [&](const json & tool) {
3219+
const auto & function = tool.at("function");
3220+
auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
3221+
builder.resolve_refs(schema);
3222+
});
3223+
parser.build_grammar(builder, data.grammar_lazy);
3224+
});
3225+
data.grammar_triggers = {
3226+
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
3227+
"<\\|start\\|>assistant( to=(?!self<\\|message\\|>)(?!user<\\|message\\|>)[^<]*?<\\|message\\|>)" },
3228+
};
3229+
}
3230+
3231+
return data;
3232+
}
3233+
30893234
static json common_chat_extra_context() {
30903235
json ctx = json::object();
30913236
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
@@ -3114,6 +3259,12 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
31143259
return common_chat_params_init_gpt_oss(tmpl, params);
31153260
}
31163261

3262+
// Muse Glimmer format using " to=<recipient>" recipients and <|eom|>/<|eot|> message terminators.
3263+
if (src.find("<atem:function_calls>") != std::string::npos && src.find("<|eom|>") != std::string::npos) {
3264+
LOG_DBG("Using specialized template: Muse Glimmer\n");
3265+
return common_chat_params_init_muse_glimmer(tmpl, params);
3266+
}
3267+
31173268
// Functionary v3.2 - uses recipient-based format with >>>recipient\n{content}
31183269
// Detection: template has ">>>all" for content and ">>>" prefix for tool calls
31193270
if (src.find(">>>all") != std::string::npos && src.find(">>>${recipient}") != std::string::npos) {

common/speculative.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,14 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
10321032
return true;
10331033
}
10341034

1035-
if (batch_in.token == nullptr || batch_in.embd != nullptr) {
1035+
// Target prefill may contain token IDs or multimodal embeddings. Both
1036+
// produce the target-layer features used to seed the draft KV cache, so
1037+
// skipping the embedding batches leaves a hole in the draft's cache and
1038+
// the next injection fails to initialize.
1039+
// TODO: revisit after https://github.com/ggml-org/llama.cpp/pull/24669 is merged
1040+
const bool has_tokens = batch_in.token != nullptr;
1041+
const bool has_embeddings = batch_in.embd != nullptr;
1042+
if (has_tokens == has_embeddings) {
10361043
return true;
10371044
}
10381045

conversion/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@
183183
"Olmo3ForCausalLM": "olmo",
184184
"OlmoForCausalLM": "olmo",
185185
"OlmoeForCausalLM": "olmo",
186+
"MuseGlimmerAssistantModel": "muse_glimmer",
187+
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
186188
"OpenELMForCausalLM": "openelm",
187189
"OrionForCausalLM": "orion",
188190
"PLMForCausalLM": "plm",
@@ -298,6 +300,7 @@
298300
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
299301
"Mistral3ForConditionalGeneration": "llava",
300302
"NemotronH_Nano_VL_V2": "nemotron",
303+
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
301304
"PaddleOCRVisionModel": "ernie",
302305
"Phi4ForCausalLMV": "phi",
303306
"Qwen2AudioForConditionalGeneration": "ultravox",

conversion/muse_glimmer.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from typing import Any, Iterable, TYPE_CHECKING
5+
6+
import torch
7+
8+
if TYPE_CHECKING:
9+
from torch import Tensor
10+
11+
from .base import MmprojModel, ModelBase, TextModel, gguf
12+
13+
14+
def _unpermute_for_rope(tensor: "Tensor", n_heads: int) -> "Tensor":
15+
"""Invert transformers' `_permute_for_rope`: HF stores Q/K in rotate_half layout,
16+
llama.cpp consumes the interleaved (NORM) layout."""
17+
if tensor.ndim == 2:
18+
dim1, dim2 = tensor.shape
19+
return tensor.view(n_heads, 2, dim1 // n_heads // 2, dim2).transpose(1, 2).reshape(dim1, dim2)
20+
if tensor.ndim == 1:
21+
(dim1,) = tensor.shape
22+
return tensor.view(n_heads, 2, dim1 // n_heads // 2).transpose(1, 2).reshape(dim1)
23+
raise ValueError(f"_unpermute_for_rope: unexpected shape {tuple(tensor.shape)}")
24+
25+
26+
@ModelBase.register("MuseGlimmerForConditionalGeneration")
27+
class MuseGlimmerModel(TextModel):
28+
model_arch = gguf.MODEL_ARCH.MUSE_GLIMMER
29+
30+
def norm_shift(self, name: str) -> float:
31+
# All four layer norms use 1, the final norm uses 0.
32+
return 1.0 if name.endswith("layernorm.weight") else 0.0
33+
34+
def set_vocab(self):
35+
self._set_vocab_gpt2()
36+
37+
from transformers import AutoTokenizer
38+
tok = AutoTokenizer.from_pretrained(self.dir_model)
39+
eot_id = tok.convert_tokens_to_ids("<|eot|>")
40+
if isinstance(eot_id, int) and eot_id >= 0:
41+
self.gguf_writer.add_eot_token_id(eot_id)
42+
43+
def set_gguf_parameters(self):
44+
super().set_gguf_parameters()
45+
hparams = self.hparams
46+
47+
self.gguf_writer.add_final_logit_softcapping(hparams["final_logit_softcapping"])
48+
self.gguf_writer.add_logit_scale(hparams["output_multiplier"])
49+
self.gguf_writer.add_sliding_window(hparams["sliding_window"])
50+
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in hparams["layer_types"]])
51+
52+
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
53+
shift = self.norm_shift(name)
54+
if shift != 0.0:
55+
data_torch = data_torch + shift
56+
57+
# Invert transformers' `_permute_for_rope` on Q/K, we keep ggml's NORM (interleaved) rope
58+
if ".self_attn.q_proj." in name:
59+
data_torch = _unpermute_for_rope(data_torch, int(self.hparams["num_attention_heads"]))
60+
elif ".self_attn.k_proj." in name:
61+
data_torch = _unpermute_for_rope(data_torch, int(self.hparams["num_key_value_heads"]))
62+
63+
# Synthesize QK-norm weights to absorb qk_scale_factor.
64+
# MuseGlimmer implementation: scaleless RMSNorm followed by qk_scale_factor..
65+
if bid is not None and name.endswith(f"model.layers.{bid}.self_attn.q_proj.weight"):
66+
head_dim = self.hparams["head_dim"]
67+
q_scale = float(self.hparams["qk_scale_factor"])
68+
yield (
69+
self.map_tensor_name(f"model.layers.{bid}.self_attn.q_norm.weight"),
70+
torch.full((head_dim,), q_scale, dtype=torch.float32),
71+
)
72+
yield (
73+
self.map_tensor_name(f"model.layers.{bid}.self_attn.k_norm.weight"),
74+
torch.ones((head_dim,), dtype=torch.float32),
75+
)
76+
77+
yield from super().modify_tensors(data_torch, name, bid)
78+
79+
80+
@ModelBase.register("MuseGlimmerForConditionalGeneration")
81+
class MuseGlimmerVisionModel(MmprojModel):
82+
def get_vision_config(self) -> dict[str, Any] | None:
83+
c = self.global_config.get("vision_config")
84+
if not c:
85+
return None
86+
# MuseGlimmer actually uses dynamic size, initialize with nominal size
87+
image_size = c["pos_emb_height"] * c["patch_size"] * c["merge_size"]
88+
return {**c, "image_size": image_size}
89+
90+
def set_gguf_parameters(self):
91+
super().set_gguf_parameters()
92+
assert self.hparams_vision is not None
93+
c = self.hparams_vision # enriched vision_config from get_vision_config()
94+
95+
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.MUSE_GLIMMER)
96+
self.gguf_writer.add_vision_attention_layernorm_eps(float(c["layer_norm_eps"]))
97+
self.gguf_writer.add_vision_spatial_merge_size(int(c["merge_size"]))
98+
99+
@classmethod
100+
def filter_tensors(cls, item):
101+
name, gen = item
102+
keep = ("model.vision_tower.", "model.vision_adapter.", "model.vision_projection.")
103+
if not any(name.startswith(k) for k in keep):
104+
return None
105+
return super().filter_tensors((name, gen))
106+
107+
# 3-layer projector MLP
108+
_MM_MLP_MAP = {
109+
"model.vision_adapter.fc1": (gguf.MODEL_TENSOR.V_MMPROJ, 0),
110+
"model.vision_adapter.fc2": (gguf.MODEL_TENSOR.V_MMPROJ, 1),
111+
"model.vision_projection": (gguf.MODEL_TENSOR.V_MMPROJ, 2),
112+
}
113+
114+
def modify_tensors(self, data_torch, name, bid):
115+
assert self.hparams_vision is not None
116+
if ".attn.q_proj." in name or ".attn.k_proj." in name:
117+
n_heads = int(self.hparams_vision["num_attention_heads"])
118+
data_torch = _unpermute_for_rope(data_torch, n_heads)
119+
# Lay out the pt=2 temporal slabs of the patch embedding as a conv2d for build_inp()
120+
if name.endswith("patch_embedder.patch_embedding.weight"):
121+
n_embd = data_torch.shape[0]
122+
pt = int(self.hparams_vision["patch_temporal"])
123+
ps = int(self.hparams_vision["patch_size"])
124+
data_torch = data_torch.view(n_embd, pt, 3, ps, ps).sum(dim=1) # (n_embd, 3, ps, ps)
125+
stem, _, suffix = name.rpartition(".")
126+
if stem in self._MM_MLP_MAP:
127+
tensor_key, idx = self._MM_MLP_MAP[stem]
128+
yield (self.format_tensor_name(tensor_key, bid=idx, suffix="." + suffix), data_torch)
129+
return
130+
yield (self.map_tensor_name(name), data_torch)
131+
132+
133+
@ModelBase.register("MuseGlimmerAssistantModel")
134+
class MuseGlimmerAssistantModel(TextModel):
135+
model_arch = gguf.MODEL_ARCH.DFLASH
136+
137+
def set_vocab(self):
138+
if self.target_model_dir is None:
139+
raise ValueError(
140+
"MuseGlimmerAssistant (DFlash drafter) requires --target-model-dir pointing to the "
141+
"target MuseGlimmer HF directory"
142+
)
143+
144+
original_dir = self.dir_model
145+
self.dir_model = self.target_model_dir
146+
147+
from . import get_model_class
148+
with open(self.target_model_dir / "config.json", "r", encoding="utf-8") as f:
149+
target_arch = json.load(f)["architectures"][0]
150+
target_cls = get_model_class(target_arch)
151+
if target_cls is not type(self):
152+
target_cls.set_vocab(self) # ty: ignore[unresolved-attribute]
153+
else:
154+
super().set_vocab()
155+
156+
self.dir_model = original_dir
157+
158+
mask_token_id = self.hparams.get("mask_token_id")
159+
if mask_token_id is not None:
160+
self.gguf_writer.add_mask_token_id(int(mask_token_id))
161+
162+
def set_gguf_parameters(self):
163+
super().set_gguf_parameters()
164+
h = self.hparams
165+
166+
self.gguf_writer.add_block_size(int(h["block_size"]))
167+
168+
# dflash.target_layers[k] refers to the inputs going into the ith layer, which come from the (i-1)th layer's output.
169+
# The transformers configuration refers to the outputs being recorded.
170+
self.gguf_writer.add_target_layers([int(x) + 1 for x in h["target_layer_ids"]])
171+
172+
if h.get("sliding_window") and h.get("layer_types"):
173+
self.gguf_writer.add_sliding_window(int(h["sliding_window"]))
174+
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in h["layer_types"]])
175+
176+
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
177+
# DFlash defaults to NEOX (rotate_half) rope, matching transformers HF layout for Q/K, QK-norms
178+
# no permutation needed.
179+
yield (self.map_tensor_name(name), data_torch)

0 commit comments

Comments
 (0)