vmlinux commited on
Commit
1219c03
·
verified ·
1 Parent(s): dd401fd

Add model card, checksums, and Muse ROCmFPX runtime patch

Browse files
Files changed (3) hide show
  1. README.md +182 -0
  2. ROCmFPX-Muse-Glimmer.patch +1339 -0
  3. SHA256SUMS +4 -0
README.md ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ base_model: meta-models/Muse-Glimmer-30B
3
+ base_model_relation: quantized
4
+ library_name: llama.cpp
5
+ pipeline_tag: image-text-to-text
6
+ license: apache-2.0
7
+ tags:
8
+ - gguf
9
+ - llama.cpp
10
+ - rocm
11
+ - rocmfpx
12
+ - amd
13
+ - strix-halo
14
+ - gfx1151
15
+ - imatrix
16
+ - multimodal
17
+ - muse-glimmer
18
+ - conversational
19
+ ---
20
+
21
+ # Muse-Glimmer-30B ROCmFPX GGUF
22
+
23
+ ROCmFP4 and ROCmFP8 builds of
24
+ [meta-models/Muse-Glimmer-30B](https://huggingface.co/meta-models/Muse-Glimmer-30B),
25
+ targeted and tested on AMD Strix Halo (`gfx1151`). Rawr. 🦖
26
+
27
+ At publication time, a Hub search returned no other Muse Glimmer ROCmFPX
28
+ repositories. These are custom ROCmFPX formats, not ordinary llama.cpp Q4/Q8
29
+ files; read the compatibility section before downloading.
30
+
31
+ > **Experimental runtime required:** these GGUFs need a patched build of
32
+ > [charlie12345/ROCmFPX](https://github.com/charlie12345/ROCmFPX). Stock
33
+ > llama.cpp does not implement the ROCmFP4/ROCmFP8 tensor layouts, while the
34
+ > pinned ROCmFPX base predates Muse Glimmer support. Apply the included
35
+ > `ROCmFPX-Muse-Glimmer.patch` to ROCmFPX commit `00d54526e…`, then build that
36
+ > checkout. The patch adds the upstream Muse text and vision implementation,
37
+ > ports it to ROCmFPX's older APIs, and supplies the FP16 sparse-attention mask
38
+ > required by its multimodal flash-attention path.
39
+
40
+ ## Files
41
+
42
+ | File | ROCmFPX preset | Size | BPW | iMatrix | Measured decode |
43
+ | --- | --- | ---: | ---: | --- | ---: |
44
+ | `Muse-Glimmer-30B-ROCmFP4.gguf` | `Q4_0_ROCMFP4_STRIX` | 14.17 GiB | 4.36 | Yes | 14.9 tok/s |
45
+ | `Muse-Glimmer-30B-ROCmFP4-Q6-QUALITY.gguf` | `Q4_0_ROCMFP4_COHERENT` | 14.94 GiB | 4.60 | Yes | 14.0 tok/s |
46
+ | `Muse-Glimmer-30B-ROCmFP8.gguf` | `Q8_0_ROCMFPX` | 26.77 GiB | 8.25 | No | 7.8 tok/s |
47
+ | `mmproj-Muse-Glimmer-30B-BF16.gguf` | BF16 vision projector | 3.59 GiB | — | — | — |
48
+
49
+ Suggested choices:
50
+
51
+ - **ROCmFP4:** default Strix Halo speed/quality build. Fast FP4 body, dual-scale
52
+ FP4 attention K/V, and Q6_K token embeddings.
53
+ - **ROCmFP4-Q6-QUALITY:** coherence-biased build. Dual-scale FP4 throughout the
54
+ body with Q6_K token embeddings.
55
+ - **ROCmFP8:** high-fidelity 8.25-bpw reference build.
56
+
57
+ The BF16 projector works with all three text models.
58
+
59
+ ## iMatrix
60
+
61
+ Both FP4 models use the same GGUF importance matrix:
62
+
63
+ - 500 chunks × 512 tokens (approximately 256k calibration tokens)
64
+ - 416 tensor importance entries consumed by each quantizer
65
+ - varied narrative/general-language calibration corpus
66
+ - checkpoints saved every 100 chunks
67
+
68
+ `Q8_0_ROCMFPX` does not consume importance weights, so the FP8 reference was
69
+ intentionally built without an iMatrix.
70
+
71
+ ## Compatibility
72
+
73
+ These files use experimental ROCmFPX tensor types and **will not load in stock
74
+ llama.cpp**.
75
+
76
+ The validated runtime was built from:
77
+
78
+ - [ROCmFPX](https://github.com/charlie12345/ROCmFPX) base commit
79
+ `00d54526e24e3aba4c76474e3147cbf9c7cc034c`
80
+ - upstream llama.cpp Muse support commit
81
+ [`62bf73d25c53b8161f8a22894d4f90c4aebbd7d0`](https://github.com/ggml-org/llama.cpp/commit/62bf73d25c53b8161f8a22894d4f90c4aebbd7d0)
82
+ - small compatibility adaptations for the older ROCmFPX chat, model, and
83
+ multimodal APIs
84
+
85
+ `ROCmFPX-Muse-Glimmer.patch` contains the complete patch against the pinned
86
+ ROCmFPX base. The runtime was built with ROCm and Vulkan backends; the reported
87
+ generation tests used `ROCm0` on `gfx1151`.
88
+
89
+ Minimal runtime setup:
90
+
91
+ ```bash
92
+ git clone https://github.com/charlie12345/ROCmFPX.git
93
+ cd ROCmFPX
94
+ git checkout 00d54526e24e3aba4c76474e3147cbf9c7cc034c
95
+
96
+ hf download vmlinux/Muse-Glimmer-30B-ROCmFPX-GGUF \
97
+ ROCmFPX-Muse-Glimmer.patch \
98
+ --local-dir /tmp/muse-glimmer-rocmfpx
99
+ git apply /tmp/muse-glimmer-rocmfpx/ROCmFPX-Muse-Glimmer.patch
100
+
101
+ BUILD_DIR=build-muse-rocmfpx \
102
+ JOBS=16 \
103
+ CMAKE_HIP_COMPILER=/opt/rocm-7.2.0/lib/llvm/bin/clang++ \
104
+ GGML_HIP_ROCWMMA_FATTN=OFF \
105
+ ./scripts/build-strix-rocmfp4-mtp.sh
106
+ ```
107
+
108
+ Adjust `CMAKE_HIP_COMPILER` for the installed ROCm version. The resulting
109
+ runtime binaries are under `build-muse-rocmfpx/bin/`. The patch must be applied
110
+ to the exact pinned commit; `git apply --check` was verified before publishing.
111
+
112
+ ## Download and run
113
+
114
+ ```bash
115
+ hf download vmlinux/Muse-Glimmer-30B-ROCmFPX-GGUF \
116
+ Muse-Glimmer-30B-ROCmFP4.gguf \
117
+ mmproj-Muse-Glimmer-30B-BF16.gguf \
118
+ --local-dir ./Muse-Glimmer-30B-ROCmFPX
119
+ ```
120
+
121
+ Text:
122
+
123
+ ```bash
124
+ ./llama-cli \
125
+ -m ./Muse-Glimmer-30B-ROCmFPX/Muse-Glimmer-30B-ROCmFP4.gguf \
126
+ -dev ROCm0 -ngl all -c 8192 -cnv
127
+ ```
128
+
129
+ Vision:
130
+
131
+ ```bash
132
+ ./llama-cli \
133
+ -m ./Muse-Glimmer-30B-ROCmFPX/Muse-Glimmer-30B-ROCmFP4.gguf \
134
+ -mm ./Muse-Glimmer-30B-ROCmFPX/mmproj-Muse-Glimmer-30B-BF16.gguf \
135
+ --image ./image.png \
136
+ -p "Describe this image." \
137
+ -dev ROCm0 -ngl all -c 8192 -cnv -st
138
+ ```
139
+
140
+ ## Validation
141
+
142
+ All three files completed clean, single-turn ROCm generation with every layer
143
+ offloaded. The projector completed an end-to-end image test and correctly
144
+ identified the test image as a folder icon.
145
+
146
+ | Model | Prompt processing | Token generation |
147
+ | --- | ---: | ---: |
148
+ | ROCmFP4 | 113.7 tok/s | 14.9 tok/s |
149
+ | ROCmFP4-Q6-QUALITY | 39.0 tok/s | 14.0 tok/s |
150
+ | ROCmFP8 | 96.7 tok/s | 7.8 tok/s |
151
+ | ROCmFP4 + BF16 projector | 81.7 tok/s | 14.9 tok/s |
152
+
153
+ These are short smoke-test measurements, not a formal benchmark. Host: AMD
154
+ Strix Halo `gfx1151`, 128 GiB unified memory, ROCm backend, 1,024-token text
155
+ context (2,048 for vision).
156
+
157
+ Additional verification:
158
+
159
+ - 731 tensors and `muse-glimmer` architecture in every text GGUF
160
+ - 809 tensors and 50 vision blocks in the projector
161
+ - ROCmFP4 kernel copy/conversion tests: 34/34 passed on Vulkan
162
+ - `test-quantize-fns` and architecture tests passed
163
+ - SHA-256 hashes supplied in `SHA256SUMS`
164
+
165
+ ## MTP / speculative decoding
166
+
167
+ No MTP or DFlash drafter is included. The downloaded base repository contained
168
+ the main Muse Glimmer weights but no separate `MuseGlimmerAssistantModel`
169
+ checkpoint. The patched runtime includes Muse DFlash loader support for use if
170
+ a compatible drafter is published separately.
171
+
172
+ ## Provenance
173
+
174
+ - Source revision: `f84ecc3a0ea984a4c04542a84269e3d065350a6e`
175
+ - Conversion: upstream llama.cpp `d2f83055dca6dd009d8a52bdff792fbb286f4444`
176
+ - Every published GGUF is covered by `SHA256SUMS`; source, intermediate, and
177
+ calibration hashes are retained in `PROVENANCE_SHA256SUMS`
178
+ - Detailed local build report: `BUILD_RESULTS.md`
179
+
180
+ The original model license and usage policy apply. See the
181
+ [source model card](https://huggingface.co/meta-models/Muse-Glimmer-30B) before
182
+ use.
ROCmFPX-Muse-Glimmer.patch ADDED
@@ -0,0 +1,1339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ diff --git a/common/chat.cpp b/common/chat.cpp
2
+ index 58a193f77..01e4166ee 100644
3
+ --- a/common/chat.cpp
4
+ +++ b/common/chat.cpp
5
+ @@ -2081,6 +2081,133 @@ static void func_args_not_string(json & messages) {
6
+
7
+ }
8
+
9
+ +// An assistant turn is rendered as one or more messages, each
10
+ +// "<|start|>assistant to=<recipient><|message|>{content}{END}" where END is
11
+ +// <|eom|> (more messages follow) or <|eot|> (end of turn):
12
+ +// - chain-of-thought: to=self, terminated by <|eom|>
13
+ +// - final answer: to=user, terminated by <|eot|>
14
+ +// The generation prompt is just "<|start|>assistant"; the model emits its own
15
+ +// " to=...<|message|>".
16
+ +static common_chat_params common_chat_params_init_muse_glimmer(const common_chat_template & tmpl,
17
+ + const autoparser::generation_params & inputs) {
18
+ + common_chat_params data;
19
+ +
20
+ + data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
21
+ + data.generation_prompt = "<|start|>assistant";
22
+ + data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
23
+ + data.supports_thinking = true;
24
+ +
25
+ + data.preserved_tokens = {
26
+ + "<|start|>", "<|message|>", "<|eom|>", "<|eot|>",
27
+ + // ATEM tool-call markup emitted on " to=<tool>" turns.
28
+ + "<atem:function_calls>", "<atem:invoke", "<atem:parameter", "</atem:parameter>",
29
+ + "</atem:invoke>", "</atem:function_calls>",
30
+ + };
31
+ +
32
+ + auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
33
+ +
34
+ + auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
35
+ + // Constrained grammar whenever tools are offered.
36
+ + auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
37
+ +
38
+ + auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
39
+ + auto start = p.rule("start", p.literal("<|start|>assistant"));
40
+ +
41
+ + if (!extract_reasoning && !include_grammar) {
42
+ + return start + p.content(p.rest());
43
+ + }
44
+ +
45
+ + if (extract_reasoning) {
46
+ + p.rule("analysis", p.literal(" to=self<|message|>") + p.reasoning(p.until("<|eom|>")) + p.literal("<|eom|>"));
47
+ + } else {
48
+ + p.rule("analysis", p.literal(" to=self<|message|>") + p.content(p.until("<|eom|>")) + p.literal("<|eom|>"));
49
+ + }
50
+ + auto analysis = p.ref("analysis");
51
+ +
52
+ + auto recipient = p.optional(p.literal(" to=user"));
53
+ + auto final_msg = p.rule("final", recipient + p.literal("<|message|>") + p.content(p.until("<|eot|>")));
54
+ +
55
+ + if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
56
+ + auto string_value =
57
+ + p.tool_arg_string_value(p.until("</atem:parameter>")) +
58
+ + p.tool_arg_close(p.literal("</atem:parameter>"));
59
+ +
60
+ + auto tool_choice = p.choice();
61
+ + foreach_function(inputs.tools, [&](const json & tool) {
62
+ + const auto & function = tool.at("function");
63
+ + const std::string name = function.at("name");
64
+ + auto params = function.contains("parameters") ? function.at("parameters") : json::object();
65
+ +
66
+ + auto args = p.eps();
67
+ + if (params.contains("properties") && params.at("properties").is_object() && !params.at("properties").empty()) {
68
+ + auto schema_info = common_schema_info();
69
+ + schema_info.resolve_refs(params);
70
+ +
71
+ + auto arg_choice = p.choice();
72
+ + for (const auto & [prop_name, prop_schema] : params.at("properties").items()) {
73
+ + auto value_parser = p.eps();
74
+ + if (schema_info.resolves_to_string(prop_schema)) {
75
+ + value_parser = string_value;
76
+ + } else {
77
+ + value_parser = p.tool_arg_json_value(
78
+ + p.schema(p.json(), "tool-" + name + "-arg-" + prop_name + "-schema", prop_schema, false))
79
+ + + p.tool_arg_close(p.literal("</atem:parameter>"));
80
+ + }
81
+ +
82
+ + auto arg_rule = p.tool_arg(
83
+ + p.tool_arg_open(p.literal("<atem:parameter name=\"") + p.tool_arg_name(p.literal(prop_name)) + p.literal("\">")) +
84
+ + value_parser);
85
+ +
86
+ + arg_choice |= arg_rule;
87
+ + }
88
+ + args = p.zero_or_more(arg_choice + p.space());
89
+ + }
90
+ +
91
+ + auto tool_parser = p.tool(
92
+ + p.tool_open(p.literal(" to=") + p.until("<|message|>") +
93
+ + p.literal("<|message|><atem:function_calls>") + p.space() +
94
+ + p.literal("<atem:invoke name=\"") + p.tool_name(p.literal(name)) + p.literal("\">") + p.space())
95
+ + << p.tool_args(args)
96
+ + << p.tool_close(p.literal("</atem:invoke>") + p.space() + p.literal("</atem:function_calls>")));
97
+ +
98
+ + tool_choice |= p.rule("tool-" + name, tool_parser);
99
+ + });
100
+ +
101
+ + auto tool_calls = inputs.parallel_tool_calls
102
+ + ? p.trigger_rule("tool-call", tool_choice + p.zero_or_more(p.literal("<|eom|>") + start + tool_choice))
103
+ + : p.trigger_rule("tool-call", tool_choice);
104
+ +
105
+ +
106
+ + if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
107
+ + return p.zero_or_more(start + analysis) + start + tool_calls;
108
+ + }
109
+ + return p.zero_or_more(start + analysis) + start + (tool_calls | final_msg);
110
+ + }
111
+ +
112
+ + return p.zero_or_more(start + analysis) + start + final_msg;
113
+ + });
114
+ +
115
+ + data.parser = parser.save();
116
+ +
117
+ + if (include_grammar) {
118
+ + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
119
+ + data.grammar = build_grammar([&](const common_grammar_builder & builder) {
120
+ + foreach_function(inputs.tools, [&](const json & tool) {
121
+ + const auto & function = tool.at("function");
122
+ + auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
123
+ + builder.resolve_refs(schema);
124
+ + });
125
+ + parser.build_grammar(builder, data.grammar_lazy);
126
+ + });
127
+ + data.grammar_triggers = {
128
+ + { COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
129
+ + "<\\|start\\|>assistant( to=(?!self<\\|message\\|>)(?!user<\\|message\\|>)[^<]*?<\\|message\\|>)" },
130
+ + };
131
+ + }
132
+ +
133
+ + return data;
134
+ +}
135
+ +
136
+ static json common_chat_extra_context() {
137
+ json ctx = json::object();
138
+ std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
139
+ @@ -2109,6 +2236,12 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
140
+ return common_chat_params_init_gpt_oss(tmpl, params);
141
+ }
142
+
143
+ + // Muse Glimmer format using " to=<recipient>" recipients and <|eom|>/<|eot|> message terminators.
144
+ + if (src.find("<atem:function_calls>") != std::string::npos && src.find("<|eom|>") != std::string::npos) {
145
+ + LOG_DBG("Using specialized template: Muse Glimmer\n");
146
+ + return common_chat_params_init_muse_glimmer(tmpl, params);
147
+ + }
148
+ +
149
+ // Functionary v3.2 - uses recipient-based format with >>>recipient\n{content}
150
+ // Detection: template has ">>>all" for content and ">>>" prefix for tool calls
151
+ if (src.find(">>>all") != std::string::npos && src.find(">>>${recipient}") != std::string::npos) {
152
+ diff --git a/common/speculative.cpp b/common/speculative.cpp
153
+ index bbfd349ac..abd2e7fdd 100644
154
+ --- a/common/speculative.cpp
155
+ +++ b/common/speculative.cpp
156
+ @@ -1105,7 +1105,14 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
157
+ return true;
158
+ }
159
+
160
+ - if (batch_in.token == nullptr || batch_in.embd != nullptr) {
161
+ + // Target prefill may contain token IDs or multimodal embeddings. Both
162
+ + // produce the target-layer features used to seed the draft KV cache, so
163
+ + // skipping the embedding batches leaves a hole in the draft's cache and
164
+ + // the next injection fails to initialize.
165
+ + // TODO: revisit after https://github.com/ggml-org/llama.cpp/pull/24669 is merged
166
+ + const bool has_tokens = batch_in.token != nullptr;
167
+ + const bool has_embeddings = batch_in.embd != nullptr;
168
+ + if (has_tokens == has_embeddings) {
169
+ return true;
170
+ }
171
+
172
+ diff --git a/conversion/__init__.py b/conversion/__init__.py
173
+ index 46618905a..7aebf9dc7 100644
174
+ --- a/conversion/__init__.py
175
+ +++ b/conversion/__init__.py
176
+ @@ -170,6 +170,8 @@ TEXT_MODEL_MAP: dict[str, str] = {
177
+ "Olmo3ForCausalLM": "olmo",
178
+ "OlmoForCausalLM": "olmo",
179
+ "OlmoeForCausalLM": "olmo",
180
+ + "MuseGlimmerAssistantModel": "muse_glimmer",
181
+ + "MuseGlimmerForConditionalGeneration": "muse_glimmer",
182
+ "OpenELMForCausalLM": "openelm",
183
+ "OrionForCausalLM": "orion",
184
+ "PLMForCausalLM": "plm",
185
+ @@ -280,6 +282,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
186
+ "MiniCPMV4_6ForConditionalGeneration": "minicpm",
187
+ "Mistral3ForConditionalGeneration": "llava",
188
+ "NemotronH_Nano_VL_V2": "nemotron",
189
+ + "MuseGlimmerForConditionalGeneration": "muse_glimmer",
190
+ "PaddleOCRVisionModel": "ernie",
191
+ "Phi4ForCausalLMV": "phi",
192
+ "Qwen2AudioForConditionalGeneration": "ultravox",
193
+ diff --git a/conversion/muse_glimmer.py b/conversion/muse_glimmer.py
194
+ new file mode 100644
195
+ index 000000000..cc588e832
196
+ --- /dev/null
197
+ +++ b/conversion/muse_glimmer.py
198
+ @@ -0,0 +1,179 @@
199
+ +from __future__ import annotations
200
+ +
201
+ +import json
202
+ +from typing import Any, Iterable, TYPE_CHECKING
203
+ +
204
+ +import torch
205
+ +
206
+ +if TYPE_CHECKING:
207
+ + from torch import Tensor
208
+ +
209
+ +from .base import MmprojModel, ModelBase, TextModel, gguf
210
+ +
211
+ +
212
+ +def _unpermute_for_rope(tensor: "Tensor", n_heads: int) -> "Tensor":
213
+ + """Invert transformers' `_permute_for_rope`: HF stores Q/K in rotate_half layout,
214
+ + llama.cpp consumes the interleaved (NORM) layout."""
215
+ + if tensor.ndim == 2:
216
+ + dim1, dim2 = tensor.shape
217
+ + return tensor.view(n_heads, 2, dim1 // n_heads // 2, dim2).transpose(1, 2).reshape(dim1, dim2)
218
+ + if tensor.ndim == 1:
219
+ + (dim1,) = tensor.shape
220
+ + return tensor.view(n_heads, 2, dim1 // n_heads // 2).transpose(1, 2).reshape(dim1)
221
+ + raise ValueError(f"_unpermute_for_rope: unexpected shape {tuple(tensor.shape)}")
222
+ +
223
+ +
224
+ +@ModelBase.register("MuseGlimmerForConditionalGeneration")
225
+ +class MuseGlimmerModel(TextModel):
226
+ + model_arch = gguf.MODEL_ARCH.MUSE_GLIMMER
227
+ +
228
+ + def norm_shift(self, name: str) -> float:
229
+ + # All four layer norms use 1, the final norm uses 0.
230
+ + return 1.0 if name.endswith("layernorm.weight") else 0.0
231
+ +
232
+ + def set_vocab(self):
233
+ + self._set_vocab_gpt2()
234
+ +
235
+ + from transformers import AutoTokenizer
236
+ + tok = AutoTokenizer.from_pretrained(self.dir_model)
237
+ + eot_id = tok.convert_tokens_to_ids("<|eot|>")
238
+ + if isinstance(eot_id, int) and eot_id >= 0:
239
+ + self.gguf_writer.add_eot_token_id(eot_id)
240
+ +
241
+ + def set_gguf_parameters(self):
242
+ + super().set_gguf_parameters()
243
+ + hparams = self.hparams
244
+ +
245
+ + self.gguf_writer.add_final_logit_softcapping(hparams["final_logit_softcapping"])
246
+ + self.gguf_writer.add_logit_scale(hparams["output_multiplier"])
247
+ + self.gguf_writer.add_sliding_window(hparams["sliding_window"])
248
+ + self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in hparams["layer_types"]])
249
+ +
250
+ + def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
251
+ + shift = self.norm_shift(name)
252
+ + if shift != 0.0:
253
+ + data_torch = data_torch + shift
254
+ +
255
+ + # Invert transformers' `_permute_for_rope` on Q/K, we keep ggml's NORM (interleaved) rope
256
+ + if ".self_attn.q_proj." in name:
257
+ + data_torch = _unpermute_for_rope(data_torch, int(self.hparams["num_attention_heads"]))
258
+ + elif ".self_attn.k_proj." in name:
259
+ + data_torch = _unpermute_for_rope(data_torch, int(self.hparams["num_key_value_heads"]))
260
+ +
261
+ + # Synthesize QK-norm weights to absorb qk_scale_factor.
262
+ + # MuseGlimmer implementation: scaleless RMSNorm followed by qk_scale_factor..
263
+ + if bid is not None and name.endswith(f"model.layers.{bid}.self_attn.q_proj.weight"):
264
+ + head_dim = self.hparams["head_dim"]
265
+ + q_scale = float(self.hparams["qk_scale_factor"])
266
+ + yield (
267
+ + self.map_tensor_name(f"model.layers.{bid}.self_attn.q_norm.weight"),
268
+ + torch.full((head_dim,), q_scale, dtype=torch.float32),
269
+ + )
270
+ + yield (
271
+ + self.map_tensor_name(f"model.layers.{bid}.self_attn.k_norm.weight"),
272
+ + torch.ones((head_dim,), dtype=torch.float32),
273
+ + )
274
+ +
275
+ + yield from super().modify_tensors(data_torch, name, bid)
276
+ +
277
+ +
278
+ +@ModelBase.register("MuseGlimmerForConditionalGeneration")
279
+ +class MuseGlimmerVisionModel(MmprojModel):
280
+ + def get_vision_config(self) -> dict[str, Any] | None:
281
+ + c = self.global_config.get("vision_config")
282
+ + if not c:
283
+ + return None
284
+ + # MuseGlimmer actually uses dynamic size, initialize with nominal size
285
+ + image_size = c["pos_emb_height"] * c["patch_size"] * c["merge_size"]
286
+ + return {**c, "image_size": image_size}
287
+ +
288
+ + def set_gguf_parameters(self):
289
+ + super().set_gguf_parameters()
290
+ + assert self.hparams_vision is not None
291
+ + c = self.hparams_vision # enriched vision_config from get_vision_config()
292
+ +
293
+ + self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.MUSE_GLIMMER)
294
+ + self.gguf_writer.add_vision_attention_layernorm_eps(float(c["layer_norm_eps"]))
295
+ + self.gguf_writer.add_vision_spatial_merge_size(int(c["merge_size"]))
296
+ +
297
+ + @classmethod
298
+ + def filter_tensors(cls, item):
299
+ + name, gen = item
300
+ + keep = ("model.vision_tower.", "model.vision_adapter.", "model.vision_projection.")
301
+ + if not any(name.startswith(k) for k in keep):
302
+ + return None
303
+ + return super().filter_tensors((name, gen))
304
+ +
305
+ + # 3-layer projector MLP
306
+ + _MM_MLP_MAP = {
307
+ + "model.vision_adapter.fc1": (gguf.MODEL_TENSOR.V_MMPROJ, 0),
308
+ + "model.vision_adapter.fc2": (gguf.MODEL_TENSOR.V_MMPROJ, 1),
309
+ + "model.vision_projection": (gguf.MODEL_TENSOR.V_MMPROJ, 2),
310
+ + }
311
+ +
312
+ + def modify_tensors(self, data_torch, name, bid):
313
+ + assert self.hparams_vision is not None
314
+ + if ".attn.q_proj." in name or ".attn.k_proj." in name:
315
+ + n_heads = int(self.hparams_vision["num_attention_heads"])
316
+ + data_torch = _unpermute_for_rope(data_torch, n_heads)
317
+ + # Lay out the pt=2 temporal slabs of the patch embedding as a conv2d for build_inp()
318
+ + if name.endswith("patch_embedder.patch_embedding.weight"):
319
+ + n_embd = data_torch.shape[0]
320
+ + pt = int(self.hparams_vision["patch_temporal"])
321
+ + ps = int(self.hparams_vision["patch_size"])
322
+ + data_torch = data_torch.view(n_embd, pt, 3, ps, ps).sum(dim=1) # (n_embd, 3, ps, ps)
323
+ + stem, _, suffix = name.rpartition(".")
324
+ + if stem in self._MM_MLP_MAP:
325
+ + tensor_key, idx = self._MM_MLP_MAP[stem]
326
+ + yield (self.format_tensor_name(tensor_key, bid=idx, suffix="." + suffix), data_torch)
327
+ + return
328
+ + yield (self.map_tensor_name(name), data_torch)
329
+ +
330
+ +
331
+ +@ModelBase.register("MuseGlimmerAssistantModel")
332
+ +class MuseGlimmerAssistantModel(TextModel):
333
+ + model_arch = gguf.MODEL_ARCH.DFLASH
334
+ +
335
+ + def set_vocab(self):
336
+ + if self.target_model_dir is None:
337
+ + raise ValueError(
338
+ + "MuseGlimmerAssistant (DFlash drafter) requires --target-model-dir pointing to the "
339
+ + "target MuseGlimmer HF directory"
340
+ + )
341
+ +
342
+ + original_dir = self.dir_model
343
+ + self.dir_model = self.target_model_dir
344
+ +
345
+ + from . import get_model_class
346
+ + with open(self.target_model_dir / "config.json", "r", encoding="utf-8") as f:
347
+ + target_arch = json.load(f)["architectures"][0]
348
+ + target_cls = get_model_class(target_arch)
349
+ + if target_cls is not type(self):
350
+ + target_cls.set_vocab(self) # ty: ignore[unresolved-attribute]
351
+ + else:
352
+ + super().set_vocab()
353
+ +
354
+ + self.dir_model = original_dir
355
+ +
356
+ + mask_token_id = self.hparams.get("mask_token_id")
357
+ + if mask_token_id is not None:
358
+ + self.gguf_writer.add_mask_token_id(int(mask_token_id))
359
+ +
360
+ + def set_gguf_parameters(self):
361
+ + super().set_gguf_parameters()
362
+ + h = self.hparams
363
+ +
364
+ + self.gguf_writer.add_block_size(int(h["block_size"]))
365
+ +
366
+ + # dflash.target_layers[k] refers to the inputs going into the ith layer, which come from the (i-1)th layer's output.
367
+ + # The transformers configuration refers to the outputs being recorded.
368
+ + self.gguf_writer.add_target_layers([int(x) + 1 for x in h["target_layer_ids"]])
369
+ +
370
+ + if h.get("sliding_window") and h.get("layer_types"):
371
+ + self.gguf_writer.add_sliding_window(int(h["sliding_window"]))
372
+ + self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in h["layer_types"]])
373
+ +
374
+ + def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
375
+ + # DFlash defaults to NEOX (rotate_half) rope, matching transformers HF layout for Q/K, QK-norms
376
+ + # no permutation needed.
377
+ + yield (self.map_tensor_name(name), data_torch)
378
+ diff --git a/gguf-py/gguf/constants.py b/gguf-py/gguf/constants.py
379
+ index 85f2ca488..8ec605c1f 100644
380
+ --- a/gguf-py/gguf/constants.py
381
+ +++ b/gguf-py/gguf/constants.py
382
+ @@ -471,6 +471,7 @@ class MODEL_ARCH(IntEnum):
383
+ OLMO = auto()
384
+ OLMO2 = auto()
385
+ OLMOE = auto()
386
+ + MUSE_GLIMMER = auto()
387
+ OPENELM = auto()
388
+ ARCTIC = auto()
389
+ DEEPSEEK = auto()
390
+ @@ -1037,6 +1038,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
391
+ MODEL_ARCH.OLMO: "olmo",
392
+ MODEL_ARCH.OLMO2: "olmo2",
393
+ MODEL_ARCH.OLMOE: "olmoe",
394
+ + MODEL_ARCH.MUSE_GLIMMER: "muse-glimmer",
395
+ MODEL_ARCH.OPENELM: "openelm",
396
+ MODEL_ARCH.ARCTIC: "arctic",
397
+ MODEL_ARCH.DEEPSEEK: "deepseek",
398
+ @@ -2983,6 +2985,25 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
399
+ MODEL_TENSOR.FFN_UP_EXP,
400
+ MODEL_TENSOR.FFN_DOWN_EXP,
401
+ ],
402
+ + MODEL_ARCH.MUSE_GLIMMER: [
403
+ + MODEL_TENSOR.TOKEN_EMBD,
404
+ + MODEL_TENSOR.OUTPUT,
405
+ + MODEL_TENSOR.OUTPUT_NORM,
406
+ + MODEL_TENSOR.ATTN_Q,
407
+ + MODEL_TENSOR.ATTN_Q_NORM,
408
+ + MODEL_TENSOR.ATTN_K,
409
+ + MODEL_TENSOR.ATTN_K_NORM,
410
+ + MODEL_TENSOR.ATTN_V,
411
+ + MODEL_TENSOR.ATTN_OUT,
412
+ + MODEL_TENSOR.ATTN_GATE,
413
+ + MODEL_TENSOR.FFN_GATE,
414
+ + MODEL_TENSOR.FFN_DOWN,
415
+ + MODEL_TENSOR.FFN_UP,
416
+ + MODEL_TENSOR.ATTN_NORM,
417
+ + MODEL_TENSOR.ATTN_POST_NORM,
418
+ + MODEL_TENSOR.FFN_PRE_NORM,
419
+ + MODEL_TENSOR.FFN_POST_NORM,
420
+ + ],
421
+ MODEL_ARCH.OPENELM: [
422
+ MODEL_TENSOR.TOKEN_EMBD,
423
+ MODEL_TENSOR.OUTPUT_NORM,
424
+ @@ -4680,6 +4701,7 @@ class VisionProjectorType:
425
+ MINICPMV4_6 = "minicpmv4_6"
426
+ GRANITE_SPEECH = "granite_speech" # audio
427
+ MIMOVL = "mimovl"
428
+ + MUSE_GLIMMER = "muse-glimmer"
429
+
430
+
431
+ # Items here are (block size, type size)
432
+ diff --git a/gguf-py/gguf/tensor_mapping.py b/gguf-py/gguf/tensor_mapping.py
433
+ index 3200a993b..5b2e0b7ab 100644
434
+ --- a/gguf-py/gguf/tensor_mapping.py
435
+ +++ b/gguf-py/gguf/tensor_mapping.py
436
+ @@ -377,7 +377,7 @@ class TensorNameMap:
437
+ ),
438
+
439
+ MODEL_TENSOR.ATTN_GATE: (
440
+ - "model.layers.{bid}.self_attn.gate_proj", # afmoe
441
+ + "model.layers.{bid}.self_attn.gate_proj", # afmoe muse-glimmer
442
+ "model.layers.{bid}.linear_attn.in_proj_z", # qwen3.5
443
+ "model.layers.{bid}.self_attn.g_proj", # step3.5 head-wise attention gate
444
+ ),
445
+ @@ -1275,10 +1275,12 @@ class TensorNameMap:
446
+ "encoder.final_layer_norm", # t5
447
+ "layer_norm", # neobert
448
+ "model.hidden_norm", # dflash
449
+ + "encoder.output_norm_enc", # dflash (transformers MuseGlimmerAssistant)
450
+ ),
451
+
452
+ MODEL_TENSOR.FC: (
453
+ - "model.fc", # dflash
454
+ + "model.fc", # dflash
455
+ + "encoder.fc", # dflash (transformers MuseGlimmerAssistant)
456
+ ),
457
+
458
+ MODEL_TENSOR.DSPARK_MARKOV_W1: (
459
+ @@ -1443,6 +1445,7 @@ class TensorNameMap:
460
+ "vision_tower.patch_embed.patchifier.proj", # dots.ocr
461
+ "vision_model.conv1", # Step3-VL
462
+ "model.vision_embedder.patch_dense", # gemma4 unified
463
+ + "model.vision_tower.patch_embedder.patch_embedding", # muse-glimmer
464
+ ),
465
+
466
+ MODEL_TENSOR.V_ENC_EMBD_NORM: (
467
+ @@ -1507,6 +1510,7 @@ class TensorNameMap:
468
+ "siglip2.vision_model.encoder.layers.{bid}.self_attn.q_proj", # youtuvl
469
+ "model.vision_model.transformer.layers.{bid}.self_attn.q_proj", # Deepseek-OCR CLIP, generated
470
+ "vision_model.model.layers.{bid}.self_attn.q_proj.linear", # gemma4
471
+ + "model.vision_tower.layers.{bid}.attn.q_proj", # muse-glimmer
472
+ ),
473
+
474
+ MODEL_TENSOR.V_ENC_ATTN_Q_NORM: (
475
+ @@ -1531,6 +1535,7 @@ class TensorNameMap:
476
+ "model.vision_model.transformer.layers.{bid}.self_attn.k_proj", # Deepseek-OCR CLIP, generated
477
+ "siglip2.vision_model.encoder.layers.{bid}.self_attn.k_proj",
478
+ "vision_model.model.layers.{bid}.self_attn.k_proj.linear", # gemma4
479
+ + "model.vision_tower.layers.{bid}.attn.k_proj", # muse-glimmer
480
+ ),
481
+
482
+ MODEL_TENSOR.V_ENC_ATTN_K_NORM: (
483
+ @@ -1555,6 +1560,7 @@ class TensorNameMap:
484
+ "siglip2.vision_model.encoder.layers.{bid}.self_attn.v_proj",
485
+ "model.vision_model.transformer.layers.{bid}.self_attn.v_proj", # Deepseek-OCR CLIP, generated
486
+ "vision_model.model.layers.{bid}.self_attn.v_proj.linear", # gemma4
487
+ + "model.vision_tower.layers.{bid}.attn.v_proj", # muse-glimmer
488
+ ),
489
+
490
+ MODEL_TENSOR.V_ENC_INPUT_NORM: (
491
+ @@ -1576,6 +1582,7 @@ class TensorNameMap:
492
+ "vision_model.radio_model.model.blocks.{bid}.norm1", # Nemotron Nano v2 VL
493
+ "vision_tower.blocks.{bid}.norm1", # dots.ocr
494
+ "vision_model.transformer.resblocks.{bid}.ln_1", # Step3-VL
495
+ + "model.vision_tower.layers.{bid}.norm1", # muse-glimmer
496
+ ),
497
+
498
+ MODEL_TENSOR.V_ENC_ATTN_O: (
499
+ @@ -1599,6 +1606,7 @@ class TensorNameMap:
500
+ "vision_model.model.layers.{bid}.self_attn.o_proj.linear", # gemma4
501
+ "vision_tower.blocks.{bid}.attn.proj", # dots.ocr
502
+ "vision_model.transformer.resblocks.{bid}.attn.out_proj", # Step3-VL
503
+ + "model.vision_tower.layers.{bid}.attn.proj", # muse-glimmer
504
+ ),
505
+
506
+ MODEL_TENSOR.V_ENC_ATTN_SINKS: (
507
+ @@ -1625,6 +1633,7 @@ class TensorNameMap:
508
+ "vision_model.model.layers.{bid}.pre_feedforward_layernorm", # gemma4
509
+ "vision_tower.blocks.{bid}.norm2", # dots.ocr
510
+ "vision_model.transformer.resblocks.{bid}.ln_2", # Step3-VL
511
+ + "model.vision_tower.layers.{bid}.norm2", # muse-glimmer
512
+ ),
513
+
514
+ MODEL_TENSOR.V_ENC_FFN_UP: (
515
+ @@ -1647,6 +1656,7 @@ class TensorNameMap:
516
+ "vision_model.radio_model.model.blocks.{bid}.mlp.fc1", # Nemotron Nano v2 VL
517
+ "vision_model.model.layers.{bid}.mlp.up_proj", # gemma4
518
+ "vision_model.transformer.resblocks.{bid}.mlp.c_fc", # Step3-VL
519
+ + "model.vision_tower.layers.{bid}.mlp.fc1", # muse-glimmer
520
+ ),
521
+
522
+ MODEL_TENSOR.V_ENC_FFN_GATE: (
523
+ @@ -1676,6 +1686,7 @@ class TensorNameMap:
524
+ "vision_model.radio_model.model.blocks.{bid}.mlp.fc2", # Nemotron Nano v2 VL
525
+ "vision_model.model.layers.{bid}.mlp.down_proj", # gemma4
526
+ "vision_model.transformer.resblocks.{bid}.mlp.c_proj", # Step3-VL
527
+ + "model.vision_tower.layers.{bid}.mlp.fc2", # muse-glimmer
528
+ ),
529
+
530
+ MODEL_TENSOR.V_ENC_ATTN_POST_NORM: (
531
+ @@ -1710,6 +1721,7 @@ class TensorNameMap:
532
+ "model.vision_model.pre_layrnorm", # Deepseek-OCR CLIP
533
+ "vision_tower.patch_embed.patchifier.norm", # dots.ocr
534
+ "vision_model.ln_pre", # Step3-VL
535
+ + "model.vision_tower.ln_pre", # muse-glimmer
536
+ ),
537
+
538
+ MODEL_TENSOR.V_POST_NORM: (
539
+ @@ -1721,6 +1733,7 @@ class TensorNameMap:
540
+ "vision_tower.encoder.final_layernorm", # kimi-vl
541
+ "visual.post_layernorm", # glm4v
542
+ "siglip2.vision_model.post_layernorm",
543
+ + "model.vision_tower.ln_post", # muse-glimmer
544
+ ),
545
+
546
+ MODEL_TENSOR.V_MM_POST_NORM: (
547
+ diff --git a/src/llama-arch.cpp b/src/llama-arch.cpp
548
+ index 17908449b..1d9552074 100644
549
+ --- a/src/llama-arch.cpp
550
+ +++ b/src/llama-arch.cpp
551
+ @@ -71,6 +71,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
552
+ { LLM_ARCH_OLMO, "olmo" },
553
+ { LLM_ARCH_OLMO2, "olmo2" },
554
+ { LLM_ARCH_OLMOE, "olmoe" },
555
+ + { LLM_ARCH_MUSE_GLIMMER, "muse-glimmer" },
556
+ { LLM_ARCH_OPENELM, "openelm" },
557
+ { LLM_ARCH_ARCTIC, "arctic" },
558
+ { LLM_ARCH_DEEPSEEK, "deepseek" },
559
+ diff --git a/src/llama-arch.h b/src/llama-arch.h
560
+ index d2f948e84..9daeb9931 100644
561
+ --- a/src/llama-arch.h
562
+ +++ b/src/llama-arch.h
563
+ @@ -76,6 +76,7 @@ enum llm_arch {
564
+ LLM_ARCH_OLMO,
565
+ LLM_ARCH_OLMO2,
566
+ LLM_ARCH_OLMOE,
567
+ + LLM_ARCH_MUSE_GLIMMER,
568
+ LLM_ARCH_OPENELM,
569
+ LLM_ARCH_ARCTIC,
570
+ LLM_ARCH_DEEPSEEK,
571
+ diff --git a/src/llama-model-saver.cpp b/src/llama-model-saver.cpp
572
+ index 19ab54378..d93ebfd6d 100644
573
+ --- a/src/llama-model-saver.cpp
574
+ +++ b/src/llama-model-saver.cpp
575
+ @@ -29,6 +29,7 @@ bool llama_model_saver_supports_arch(llm_arch arch) {
576
+ case LLM_ARCH_APERTUS:
577
+ case LLM_ARCH_MIMO2:
578
+ case LLM_ARCH_STEP35:
579
+ + case LLM_ARCH_MUSE_GLIMMER:
580
+ case LLM_ARCH_LAGUNA:
581
+ return false;
582
+ default:
583
+ diff --git a/src/llama-model.cpp b/src/llama-model.cpp
584
+ index c33f8c247..594affda4 100644
585
+ --- a/src/llama-model.cpp
586
+ +++ b/src/llama-model.cpp
587
+ @@ -163,6 +163,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params
588
+ return new llama_model_olmo2(params);
589
+ case LLM_ARCH_OLMOE:
590
+ return new llama_model_olmoe(params);
591
+ + case LLM_ARCH_MUSE_GLIMMER:
592
+ + return new llama_model_muse_glimmer(params);
593
+ case LLM_ARCH_OPENELM:
594
+ return new llama_model_openelm(params);
595
+ case LLM_ARCH_GPTNEOX:
596
+ @@ -2372,6 +2374,7 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {
597
+ case LLM_ARCH_DEEPSEEK2OCR:
598
+ case LLM_ARCH_DEEPSEEK32:
599
+ case LLM_ARCH_DEEPSEEK4:
600
+ + case LLM_ARCH_MUSE_GLIMMER:
601
+ case LLM_ARCH_PLM:
602
+ case LLM_ARCH_CHATGLM:
603
+ case LLM_ARCH_GRANITE:
604
+ diff --git a/src/models/models.h b/src/models/models.h
605
+ index 5cc93e6b9..cb3b4d4ad 100644
606
+ --- a/src/models/models.h
607
+ +++ b/src/models/models.h
608
+ @@ -1017,6 +1017,19 @@ struct llama_model_olmoe : public llama_model_base {
609
+ };
610
+
611
+
612
+ +struct llama_model_muse_glimmer : public llama_model_base {
613
+ + llama_model_muse_glimmer(const struct llama_model_params & params) : llama_model_base(params) {}
614
+ + void load_arch_hparams(llama_model_loader & ml) override;
615
+ + void load_arch_tensors(llama_model_loader & ml) override;
616
+ +
617
+ + struct graph : public llm_graph_context {
618
+ + graph(const llama_model & model, const llm_graph_params & params);
619
+ + };
620
+ +
621
+ + std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
622
+ +};
623
+ +
624
+ +
625
+ struct llama_model_openelm : public llama_model_base {
626
+ llama_model_openelm(const struct llama_model_params & params) : llama_model_base(params) {}
627
+ void load_arch_hparams(llama_model_loader & ml) override;
628
+ diff --git a/src/models/muse-glimmer.cpp b/src/models/muse-glimmer.cpp
629
+ new file mode 100644
630
+ index 000000000..6e860b55d
631
+ --- /dev/null
632
+ +++ b/src/models/muse-glimmer.cpp
633
+ @@ -0,0 +1,208 @@
634
+ +#include "models.h"
635
+ +
636
+ +void llama_model_muse_glimmer::load_arch_hparams(llama_model_loader & ml) {
637
+ + ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
638
+ + ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa);
639
+ + ml.get_key(LLM_KV_FINAL_LOGIT_SOFTCAPPING, hparams.f_final_logit_softcapping, false);
640
+ + ml.get_key(LLM_KV_LOGIT_SCALE, hparams.f_logit_scale);
641
+ +
642
+ + hparams.rope_freq_base_train_swa = hparams.rope_freq_base_train;
643
+ + ml.get_key(LLM_KV_ROPE_FREQ_BASE_SWA, hparams.rope_freq_base_train_swa, false);
644
+ +
645
+ + hparams.swa_type = LLAMA_SWA_TYPE_STANDARD;
646
+ + uint32_t swa_period = 4;
647
+ + if (ml.get_key_or_arr(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, swa_period, false)) {
648
+ + hparams.set_swa_pattern(swa_period);
649
+ + } else {
650
+ + ml.get_key_or_arr(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, hparams.swa_layers, hparams.n_layer);
651
+ + }
652
+ +
653
+ + switch (hparams.n_layer) {
654
+ + case 52: type = LLM_TYPE_30B; break;
655
+ + default: type = LLM_TYPE_UNKNOWN;
656
+ + }
657
+ +}
658
+ +
659
+ +void llama_model_muse_glimmer::load_arch_tensors(llama_model_loader &) {
660
+ + LLAMA_LOAD_LOCALS;
661
+ +
662
+ + tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
663
+ + output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0);
664
+ + output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab}, 0);
665
+ +
666
+ + for (int i = 0; i < n_layer; ++i) {
667
+ + auto & layer = layers[i];
668
+ +
669
+ + // Pre/post-attention norms (Muse Glimmer's `weight + 1` applied at conversion time).
670
+ + layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0);
671
+ + layer.attn_post_norm = create_tensor(tn(LLM_TENSOR_ATTN_POST_NORM, "weight", i), {n_embd}, 0);
672
+ +
673
+ + // Q/K/V/O projections.
674
+ + create_tensor_qkv(layer, i, n_embd, n_embd_head_k * n_head, n_embd_k_gqa, n_embd_v_gqa, 0);
675
+ + layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd}, 0);
676
+ +
677
+ + // QK-norm. Weights are synthesized at conversion time to absorb `qk_scale_factor`.
678
+ + layer.attn_q_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_NORM, "weight", i), {n_embd_head_k}, 0);
679
+ + layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), {n_embd_head_k}, 0);
680
+ +
681
+ + // Attention output gate: sigmoid(gate) * attn_out before o_proj (same as afmoe).
682
+ + layer.wqkv_gate = create_tensor(tn(LLM_TENSOR_ATTN_GATE, "weight", i), {n_embd, n_embd_head_k * n_head}, 0);
683
+ +
684
+ + // Pre/post-FFN norms (FFN_PRE_NORM is aliased to LLM_TENSOR_FFN_NORM).
685
+ + layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, 0);
686
+ + layer.ffn_post_norm = create_tensor(tn(LLM_TENSOR_FFN_POST_NORM, "weight", i), {n_embd}, 0);
687
+ +
688
+ + // Dense FFN (unlike afmoe, no MoE branches).
689
+ + layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}, 0);
690
+ + layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), {n_ff, n_embd}, 0);
691
+ + layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, 0);
692
+ + }
693
+ +}
694
+ +
695
+ +llama_model_muse_glimmer::graph::graph(const llama_model & model, const llm_graph_params & params)
696
+ + : llm_graph_context(params) {
697
+ + const int64_t n_embd_head = hparams.n_embd_head_v();
698
+ + GGML_ASSERT(n_embd_head == hparams.n_embd_head_k());
699
+ +
700
+ + // Different to f_norm_rms_eps for post-attn / post-FFN norms
701
+ + const float post_norm_eps = 1e-8f;
702
+ +
703
+ + ggml_tensor * cur;
704
+ + ggml_tensor * inpL;
705
+ +
706
+ + inpL = build_inp_embd(model.tok_embd);
707
+ + inpL = build_norm(inpL, nullptr, nullptr, LLM_NORM_RMS, -1);
708
+ + cb(inpL, "embd_norm", -1);
709
+ +
710
+ + ggml_tensor * inp_pos = build_inp_pos();
711
+ + auto * inp_attn = build_attn_inp_kv_iswa();
712
+ + ggml_tensor * inp_out_ids = build_inp_out_ids();
713
+ +
714
+ + const float kq_scale = 1.0f / sqrtf(float(n_embd_head));
715
+ +
716
+ + for (int il = 0; il < n_layer; ++il) {
717
+ + // expose per-layer residual for speculative drafts (see LLM_KV_TARGET_LAYERS).
718
+ + res->t_layer_inp[il] = inpL;
719
+ +
720
+ + const float freq_base_l = model.get_rope_freq_base (cparams, il);
721
+ + const float freq_scale_l = model.get_rope_freq_scale(cparams, il);
722
+ +
723
+ + ggml_tensor * inpSA = inpL;
724
+ +
725
+ + // RoPE runs on the SWA layers, NoPE on full ones.
726
+ + const bool use_rope = hparams.is_swa(il);
727
+ +
728
+ + // pre-attention norm (weight+1 folded at conversion time)
729
+ + cur = build_norm(inpL, model.layers[il].attn_norm, NULL, LLM_NORM_RMS, il);
730
+ + cb(cur, "attn_norm", il);
731
+ +
732
+ + // self-attention: attention output gate around SDPA (afmoe.cpp:147-191)
733
+ + {
734
+ + ggml_tensor * attn_inp = cur; // save input for gate computation
735
+ +
736
+ + auto [Qcur, Kcur, Vcur] = build_qkv(model.layers[il], cur,
737
+ + n_embd_head, n_head, n_head_kv, il);
738
+ +
739
+ + // gate = wqkv_gate @ attn_inp (from pre-attn hidden state)
740
+ + ggml_tensor * gate = build_lora_mm(model.layers[il].wqkv_gate, attn_inp);
741
+ + cb(gate, "attn_gate_proj", il);
742
+ +
743
+ + // QK-norm. attn_q_norm weight was synthesized at conversion to broadcast
744
+ + // qk_scale_factor across head_dim; attn_k_norm is identity (ones).
745
+ + Qcur = build_norm(Qcur, model.layers[il].attn_q_norm, NULL, LLM_NORM_RMS, il);
746
+ + Kcur = build_norm(Kcur, model.layers[il].attn_k_norm, NULL, LLM_NORM_RMS, il);
747
+ + cb(Qcur, "Qcur_normed", il);
748
+ + cb(Kcur, "Kcur_normed", il);
749
+ +
750
+ + if (use_rope) {
751
+ + Qcur = ggml_rope_ext(
752
+ + ctx0, Qcur, inp_pos, nullptr,
753
+ + n_rot, rope_type, n_ctx_orig, freq_base_l, freq_scale_l,
754
+ + ext_factor, attn_factor, beta_fast, beta_slow);
755
+ + cb(Qcur, "Qcur_rope", il);
756
+ +
757
+ + Kcur = ggml_rope_ext(
758
+ + ctx0, Kcur, inp_pos, nullptr,
759
+ + n_rot, rope_type, n_ctx_orig, freq_base_l, freq_scale_l,
760
+ + ext_factor, attn_factor, beta_fast, beta_slow);
761
+ + cb(Kcur, "Kcur_rope", il);
762
+ + }
763
+ +
764
+ + // SDPA. wo is deferred; the gate goes between attn_out and o_proj.
765
+ + cur = build_attn(inp_attn,
766
+ + NULL, NULL, NULL,
767
+ + Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il);
768
+ + cb(cur, "attn_out", il);
769
+ +
770
+ + gate = ggml_sigmoid(ctx0, gate);
771
+ + cb(gate, "attn_gate_sig", il);
772
+ + cur = ggml_mul(ctx0, cur, gate);
773
+ + cb(cur, "attn_gated", il);
774
+ +
775
+ + cur = build_lora_mm(model.layers[il].wo, cur, model.layers[il].wo_s);
776
+ + cb(cur, "attn_o_proj", il);
777
+ + }
778
+ +
779
+ + cur = ggml_rms_norm(ctx0, cur, post_norm_eps);
780
+ + cur = ggml_mul(ctx0, cur, model.layers[il].attn_post_norm);
781
+ + cb(cur, "attn_post_norm", il);
782
+ +
783
+ + if (il == n_layer - 1 && inp_out_ids) {
784
+ + cur = ggml_get_rows(ctx0, cur, inp_out_ids);
785
+ + inpSA = ggml_get_rows(ctx0, inpSA, inp_out_ids);
786
+ + }
787
+ +
788
+ + ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);
789
+ + cb(ffn_inp, "ffn_inp", il);
790
+ +
791
+ + // pre-FFN norm
792
+ + cur = build_norm(ffn_inp, model.layers[il].ffn_norm, NULL, LLM_NORM_RMS, il);
793
+ + cb(cur, "ffn_norm", il);
794
+ +
795
+ + // SwiGLU dense FFN
796
+ + cur = build_ffn(cur,
797
+ + model.layers[il].ffn_up, NULL, NULL,
798
+ + model.layers[il].ffn_gate, NULL, NULL,
799
+ + model.layers[il].ffn_down, NULL, NULL,
800
+ + NULL,
801
+ + LLM_FFN_SILU, LLM_FFN_PAR, il);
802
+ + cb(cur, "ffn_out", il);
803
+ +
804
+ + cur = ggml_rms_norm(ctx0, cur, post_norm_eps);
805
+ + cur = ggml_mul(ctx0, cur, model.layers[il].ffn_post_norm);
806
+ + cb(cur, "ffn_post_norm", il);
807
+ +
808
+ + cur = ggml_add(ctx0, cur, ffn_inp);
809
+ + cur = build_cvec(cur, il);
810
+ + cb(cur, "l_out", il);
811
+ +
812
+ + inpL = cur;
813
+ + }
814
+ +
815
+ + cur = inpL;
816
+ +
817
+ + // final norm
818
+ + cur = build_norm(cur, model.output_norm, NULL, LLM_NORM_RMS, -1);
819
+ + cb(cur, "result_norm", -1);
820
+ + res->t_embd = cur;
821
+ +
822
+ + // lm_head, followed by output multiplier
823
+ + cur = build_lora_mm(model.output, cur);
824
+ + cur = ggml_scale(ctx0, cur, hparams.f_logit_scale);
825
+ +
826
+ + // Final logit tanh softcap (from gemma3.cpp).
827
+ + if (hparams.f_final_logit_softcapping) {
828
+ + cur = ggml_scale(ctx0, cur, 1.0f / hparams.f_final_logit_softcapping);
829
+ + cur = ggml_tanh(ctx0, cur);
830
+ + cur = ggml_scale(ctx0, cur, hparams.f_final_logit_softcapping);
831
+ + }
832
+ +
833
+ + cb(cur, "result_output", -1);
834
+ + res->t_logits = cur;
835
+ +
836
+ + ggml_build_forward_expand(gf, cur);
837
+ +}
838
+ +
839
+ +std::unique_ptr<llm_graph_context> llama_model_muse_glimmer::build_arch_graph(const llm_graph_params & params) const {
840
+ + return std::make_unique<graph>(*this, params);
841
+ +}
842
+ diff --git a/tests/test-llama-archs.cpp b/tests/test-llama-archs.cpp
843
+ index 29eae5590..617a12b38 100644
844
+ --- a/tests/test-llama-archs.cpp
845
+ +++ b/tests/test-llama-archs.cpp
846
+ @@ -187,7 +187,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
847
+ ms.add_kv(LLM_KV_ROPE_FREQ_BASE_SWA, 10000.0f);
848
+ // SWA pattern: every 5th layer is full attention (matches E2B layer_types)
849
+ ms.add_kv(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, uint32_t(5));
850
+ - } else if (arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_STEP35) {
851
+ + } else if (arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_STEP35 || arch == LLM_ARCH_MUSE_GLIMMER) {
852
+ std::vector<uint32_t> pattern;
853
+ pattern.reserve(n_layer);
854
+ for (uint32_t il = 0; il < n_layer; il++) {
855
+ diff --git a/tools/mtmd/CMakeLists.txt b/tools/mtmd/CMakeLists.txt
856
+ index 3ff313ee8..d312312a6 100644
857
+ --- a/tools/mtmd/CMakeLists.txt
858
+ +++ b/tools/mtmd/CMakeLists.txt
859
+ @@ -31,6 +31,7 @@ add_library(mtmd
860
+ models/kimivl.cpp
861
+ models/kimik25.cpp
862
+ models/nemotron-v2-vl.cpp
863
+ + models/muse-glimmer.cpp
864
+ models/llama4.cpp
865
+ models/llava.cpp
866
+ models/minicpmv.cpp
867
+ diff --git a/tools/mtmd/clip-graph.h b/tools/mtmd/clip-graph.h
868
+ index 951480be9..38ae6d4b0 100644
869
+ --- a/tools/mtmd/clip-graph.h
870
+ +++ b/tools/mtmd/clip-graph.h
871
+ @@ -11,6 +11,11 @@
872
+
873
+ #define DEFAULT_INTERPOLATION_MODE (GGML_SCALE_MODE_BILINEAR | GGML_SCALE_FLAG_ANTIALIAS)
874
+
875
+ +struct build_vit_opts {
876
+ + ggml_tensor * attn_mask = nullptr;
877
+ + std::vector<ggml_tensor *> attn_mask_layers;
878
+ +};
879
+ +
880
+ struct clip_graph {
881
+ const clip_model & model;
882
+ const clip_hparams & hparams;
883
+ @@ -67,7 +72,8 @@ struct clip_graph {
884
+ norm_type norm_t,
885
+ ffn_op_type ffn_t,
886
+ ggml_tensor * learned_pos_embd,
887
+ - std::function<ggml_tensor *(ggml_tensor *, const clip_layer &)> add_pos);
888
+ + std::function<ggml_tensor *(ggml_tensor *, const clip_layer &)> add_pos,
889
+ + const build_vit_opts & opts = {});
890
+
891
+ // build the input after conv2d (inp_raw --> patches)
892
+ // returns tensor with shape [n_embd, n_patches]
893
+ diff --git a/tools/mtmd/clip-impl.h b/tools/mtmd/clip-impl.h
894
+ index bc0165e6a..9fb761041 100644
895
+ --- a/tools/mtmd/clip-impl.h
896
+ +++ b/tools/mtmd/clip-impl.h
897
+ @@ -351,6 +351,7 @@ enum projector_type {
898
+ PROJECTOR_TYPE_MINICPMV4_6,
899
+ PROJECTOR_TYPE_GRANITE_SPEECH,
900
+ PROJECTOR_TYPE_MIMOVL,
901
+ + PROJECTOR_TYPE_MUSE_GLIMMER,
902
+ PROJECTOR_TYPE_UNKNOWN,
903
+ };
904
+
905
+ @@ -403,6 +404,7 @@ static std::map<projector_type, std::string> PROJECTOR_TYPE_NAMES = {
906
+ { PROJECTOR_TYPE_MINICPMV4_6, "minicpmv4_6"},
907
+ { PROJECTOR_TYPE_GRANITE_SPEECH, "granite_speech"},
908
+ { PROJECTOR_TYPE_MIMOVL, "mimovl"},
909
+ + { PROJECTOR_TYPE_MUSE_GLIMMER, "muse-glimmer"},
910
+ };
911
+
912
+ static projector_type clip_projector_type_from_string(const std::string & str) {
913
+ diff --git a/tools/mtmd/clip-model.h b/tools/mtmd/clip-model.h
914
+ index c06d9f77b..d68ac631f 100644
915
+ --- a/tools/mtmd/clip-model.h
916
+ +++ b/tools/mtmd/clip-model.h
917
+ @@ -91,6 +91,10 @@ struct clip_hparams {
918
+ int32_t sam_n_head = 0;
919
+ int32_t sam_n_embd = 0;
920
+
921
+ + // Muse Glimmer vision (per-block sparse-window pattern, learned pos-emb, patch-temporal)
922
+ + // NOTE: these perhaps shouldn't have the architecture prefix
923
+ + int32_t muse_glimmer_patch_temporal = 0;
924
+ + int32_t muse_glimmer_sparse_factor = 0;
925
+ // audio
926
+ int32_t n_mel_bins = 0; // whisper preprocessor
927
+ int32_t proj_stack_factor = 0; // ultravox
928
+ diff --git a/tools/mtmd/clip.cpp b/tools/mtmd/clip.cpp
929
+ index fa5d4f90c..5222f5630 100644
930
+ --- a/tools/mtmd/clip.cpp
931
+ +++ b/tools/mtmd/clip.cpp
932
+ @@ -301,7 +301,8 @@ ggml_tensor * clip_graph::build_vit(
933
+ norm_type norm_t,
934
+ ffn_op_type ffn_t,
935
+ ggml_tensor * learned_pos_embd,
936
+ - std::function<ggml_tensor *(ggml_tensor *, const clip_layer &)> add_pos
937
+ + std::function<ggml_tensor *(ggml_tensor *, const clip_layer &)> add_pos,
938
+ + const build_vit_opts & opts
939
+ ) {
940
+ // batch dim: inp is [n_embd, n_pos] (B==1) or [n_embd, n_pos, B] (multi-tile encode)
941
+ const int64_t B = inp->ne[2];
942
+ @@ -327,6 +328,11 @@ ggml_tensor * clip_graph::build_vit(
943
+ auto & layer = model.layers[il];
944
+ ggml_tensor * cur = inpL; // inpL = residual, cur = hidden_states
945
+
946
+ + ggml_tensor * attn_mask = opts.attn_mask;
947
+ + if (opts.attn_mask_layers.size() > (size_t) il) {
948
+ + attn_mask = opts.attn_mask_layers[il];
949
+ + }
950
+ +
951
+ // layernorm1
952
+ cur = build_norm(cur, layer.ln_1_w, layer.ln_1_b, norm_t, eps, il);
953
+ cb(cur, "layer_inp_normed", il);
954
+ @@ -439,7 +445,7 @@ ggml_tensor * clip_graph::build_vit(
955
+
956
+ // build_attn returns a flat 2D [n_embd, n_pos*B]
957
+ cur = build_attn(layer.o_w, layer.o_b,
958
+ - Qcur, Kcur, Vcur, nullptr, kq_scale, il);
959
+ + Qcur, Kcur, Vcur, attn_mask, kq_scale, il);
960
+ cb(cur, "attn_out", il);
961
+ }
962
+
963
+ @@ -897,6 +903,10 @@ static ggml_cgraph * clip_image_build_graph(clip_ctx * ctx, const clip_image_f32
964
+ {
965
+ builder = std::make_unique<clip_graph_mimovl>(ctx, img);
966
+ } break;
967
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
968
+ + {
969
+ + builder = std::make_unique<clip_graph_muse_glimmer>(ctx, img);
970
+ + } break;
971
+ case PROJECTOR_TYPE_STEP3VL:
972
+ {
973
+ builder = std::make_unique<clip_graph_step3vl>(ctx, img);
974
+ @@ -1435,6 +1445,19 @@ struct clip_model_loader {
975
+ LOG_WRN("%s: more info: https://github.com/ggml-org/llama.cpp/issues/16842\n\n", __func__);
976
+ }
977
+ } break;
978
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
979
+ + {
980
+ + hparams.n_merge = 2; // pixel-shuffle downsample after the ViT
981
+ + // This ROCmFPX base predates the Lanczos resize enum; Pillow-style
982
+ + // bicubic is the closest available high-quality dynamic resize.
983
+ + hparams.image_resize_algo = RESIZE_ALGO_BICUBIC_PILLOW;
984
+ + hparams.rope_theta = 10000.0f;
985
+ + hparams.muse_glimmer_patch_temporal = 2;
986
+ + hparams.muse_glimmer_sparse_factor = 4; // 3 sparse layers + 1 global, repeating
987
+ + get_u32(KEY_SPATIAL_MERGE_SIZE, hparams.n_merge, false);
988
+ + hparams.set_limit_image_tokens(1, 4096);
989
+ + hparams.set_warmup_n_tokens(32*32);
990
+ + } break;
991
+ case PROJECTOR_TYPE_MIMOVL:
992
+ {
993
+ hparams.n_merge = 2; // spatial_merge_size
994
+ @@ -1994,6 +2017,13 @@ struct clip_model_loader {
995
+ model.mm_1_w = get_tensor(string_format(TN_LLAVA_PROJ, 2, "weight"));
996
+ model.mm_1_b = get_tensor(string_format(TN_LLAVA_PROJ, 2, "bias"), false);
997
+ } break;
998
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
999
+ + {
1000
+ + // 3-linear MLP: fc -> erf-GELU -> proj -> erf-GELU -> vision_proj (into LLM residual dim)
1001
+ + model.mm_0_w = get_tensor(string_format(TN_LLAVA_PROJ, 0, "weight"));
1002
+ + model.mm_1_w = get_tensor(string_format(TN_LLAVA_PROJ, 1, "weight"));
1003
+ + model.mm_2_w = get_tensor(string_format(TN_LLAVA_PROJ, 2, "weight"));
1004
+ + } break;
1005
+ case PROJECTOR_TYPE_STEP3VL:
1006
+ {
1007
+ model.mm_0_w = get_tensor(string_format(TN_LLAVA_PROJ, 0, "weight"));
1008
+ @@ -3120,6 +3150,7 @@ int clip_n_output_tokens_x(const struct clip_ctx * ctx, struct clip_image_f32 *
1009
+ case PROJECTOR_TYPE_HUNYUANOCR:
1010
+ case PROJECTOR_TYPE_HUNYUANVL:
1011
+ case PROJECTOR_TYPE_YOUTUVL:
1012
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
1013
+ return (img->nx / params.patch_size) / 2;
1014
+ case PROJECTOR_TYPE_STEP3VL:
1015
+ return img->nx / (params.patch_size * params.n_merge);
1016
+ @@ -3141,6 +3172,7 @@ int clip_n_output_tokens_y(const struct clip_ctx * ctx, struct clip_image_f32 *
1017
+ case PROJECTOR_TYPE_PADDLEOCR:
1018
+ case PROJECTOR_TYPE_HUNYUANVL:
1019
+ case PROJECTOR_TYPE_YOUTUVL:
1020
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
1021
+ return (img->ny / params.patch_size) / 2;
1022
+ case PROJECTOR_TYPE_STEP3VL:
1023
+ return img->ny / (params.patch_size * params.n_merge);
1024
+ @@ -3218,6 +3250,7 @@ int clip_n_output_tokens(const struct clip_ctx * ctx, struct clip_image_f32 * im
1025
+ case PROJECTOR_TYPE_MIMOVL:
1026
+ case PROJECTOR_TYPE_GLM4V:
1027
+ case PROJECTOR_TYPE_YOUTUVL:
1028
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
1029
+ {
1030
+ // dynamic size (2 conv, so double patch size)
1031
+ int x_patch = img->nx / (params.patch_size * 2);
1032
+ @@ -3441,6 +3474,15 @@ bool clip_image_batch_encode(clip_ctx * ctx, const int n_threads, const clip_ima
1033
+ ggml_backend_tensor_set(cur, values.data(), 0, ggml_nbytes(cur));
1034
+ };
1035
+
1036
+ + auto set_input_f16 = [&get_inp_tensor](const char * name, const std::vector<float> & values) {
1037
+ + ggml_tensor * cur = get_inp_tensor(name);
1038
+ + GGML_ASSERT(cur->type == GGML_TYPE_F16);
1039
+ + GGML_ASSERT(ggml_nelements(cur) == (int64_t) values.size());
1040
+ + std::vector<ggml_fp16_t> values_f16(values.size());
1041
+ + ggml_fp32_to_fp16_row(values.data(), values_f16.data(), values.size());
1042
+ + ggml_backend_tensor_set(cur, values_f16.data(), 0, ggml_nbytes(cur));
1043
+ + };
1044
+ +
1045
+ auto set_input_i32 = [&get_inp_tensor](const char * name, std::vector<int32_t> & values) {
1046
+ ggml_tensor * cur = get_inp_tensor(name);
1047
+ GGML_ASSERT(cur->type == GGML_TYPE_I32);
1048
+ @@ -3500,6 +3542,70 @@ bool clip_image_batch_encode(clip_ctx * ctx, const int n_threads, const clip_ima
1049
+
1050
+ // set input per projector
1051
+ switch (ctx->model.proj_type) {
1052
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
1053
+ + {
1054
+ + const int grid_w = pos_w; // image_size_width / patch_size
1055
+ + const int grid_h = pos_h; // image_size_height / patch_size
1056
+ + const int n_tok = grid_w * grid_h;
1057
+ + const int pgrid = (int) std::sqrt((double) ctx->model.position_embeddings->ne[1]); // 32
1058
+ + const int f = hparams.n_merge; // downsample 2
1059
+ +
1060
+ + // pixel patchify runs inside the graph via build_inp() (ggml_conv_2d);
1061
+ + // pos-emb bilinear interp via resize_position_embeddings().
1062
+ +
1063
+ + // --- sparse window grouping (pgrid x pgrid windows) ---
1064
+ + const int win = pgrid;
1065
+ + const int nwin_h = (grid_h + win - 1) / win;
1066
+ + const int nwin_w = (grid_w + win - 1) / win;
1067
+ + std::vector<int32_t> sp_perm; sp_perm.reserve(n_tok);
1068
+ + std::vector<int> sp_slens;
1069
+ + for (int wy = 0; wy < nwin_h; wy++) {
1070
+ + for (int wx = 0; wx < nwin_w; wx++) {
1071
+ + int cnt = 0;
1072
+ + for (int hh = 0; hh < win; hh++) {
1073
+ + for (int ww = 0; ww < win; ww++) {
1074
+ + const int gy = wy * win + hh;
1075
+ + const int gx = wx * win + ww;
1076
+ + if (gy < grid_h && gx < grid_w) { sp_perm.push_back(gy * grid_w + gx); cnt++; }
1077
+ + }
1078
+ + }
1079
+ + if (cnt > 0) sp_slens.push_back(cnt);
1080
+ + }
1081
+ + }
1082
+ + std::vector<int32_t> rpos_w(n_tok), rpos_h(n_tok), inv_perm(n_tok);
1083
+ + for (int i = 0; i < n_tok; i++) {
1084
+ + const int orig = sp_perm[i];
1085
+ + rpos_w[i] = (orig % grid_w) + 1; // 1-indexed
1086
+ + rpos_h[i] = (orig / grid_w) + 1;
1087
+ + inv_perm[orig] = i;
1088
+ + }
1089
+ + set_input_i32("muse_glimmer_sp_perm", sp_perm);
1090
+ + set_input_i32("muse_glimmer_inv_perm", inv_perm);
1091
+ + set_input_i32("muse_glimmer_pos_w", rpos_w);
1092
+ + set_input_i32("muse_glimmer_pos_h", rpos_h);
1093
+ +
1094
+ + // block-diagonal window mask (permuted order)
1095
+ + std::vector<float> sp_mask((size_t) n_tok * n_tok, -INFINITY);
1096
+ + {
1097
+ + int off = 0;
1098
+ + for (int s : sp_slens) {
1099
+ + for (int a = 0; a < s; a++)
1100
+ + for (int b = 0; b < s; b++)
1101
+ + sp_mask[(size_t) (off + a) * n_tok + (off + b)] = 0.0f;
1102
+ + off += s;
1103
+ + }
1104
+ + }
1105
+ + set_input_f16("muse_glimmer_sp_mask", sp_mask);
1106
+ +
1107
+ + // pixel-shuffle gather (original order): f*f spatial neighbours grouped
1108
+ + std::vector<int32_t> dsp; dsp.reserve(n_tok);
1109
+ + for (int oy = 0; oy < grid_h / f; oy++)
1110
+ + for (int ox = 0; ox < grid_w / f; ox++)
1111
+ + for (int ry = 0; ry < f; ry++)
1112
+ + for (int rx = 0; rx < f; rx++)
1113
+ + dsp.push_back((oy * f + ry) * grid_w + (ox * f + rx));
1114
+ + set_input_i32("muse_glimmer_ds_perm", dsp);
1115
+ + } break;
1116
+ case PROJECTOR_TYPE_MINICPMV:
1117
+ {
1118
+ // inspired from siglip:
1119
+ @@ -4273,6 +4379,8 @@ int clip_n_mmproj_embd(const struct clip_ctx * ctx) {
1120
+ return ctx->model.mm_ffn_down_w->ne[1];
1121
+ case PROJECTOR_TYPE_GLM_EDGE:
1122
+ return ctx->model.mm_model_mlp_3_w->ne[1];
1123
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
1124
+ + return ctx->model.mm_2_w->ne[1];
1125
+ case PROJECTOR_TYPE_QWEN2VL:
1126
+ case PROJECTOR_TYPE_QWEN25VL:
1127
+ case PROJECTOR_TYPE_JANUS_PRO:
1128
+ diff --git a/tools/mtmd/models/models.h b/tools/mtmd/models/models.h
1129
+ index 33b485a4a..b91979db3 100644
1130
+ --- a/tools/mtmd/models/models.h
1131
+ +++ b/tools/mtmd/models/models.h
1132
+ @@ -201,3 +201,7 @@ struct clip_graph_kimik25 : clip_graph {
1133
+
1134
+ ggml_tensor * resize_position_embeddings_3d(uint32_t interpolation_mode);
1135
+ };
1136
+ +struct clip_graph_muse_glimmer : clip_graph {
1137
+ + clip_graph_muse_glimmer(clip_ctx * ctx, const clip_image_f32 & img) : clip_graph(ctx, img) {}
1138
+ + ggml_cgraph * build() override;
1139
+ +};
1140
+ diff --git a/tools/mtmd/models/muse-glimmer.cpp b/tools/mtmd/models/muse-glimmer.cpp
1141
+ new file mode 100644
1142
+ index 000000000..d2f0a7e02
1143
+ --- /dev/null
1144
+ +++ b/tools/mtmd/models/muse-glimmer.cpp
1145
+ @@ -0,0 +1,90 @@
1146
+ +#include "models.h"
1147
+ +
1148
+ +// MuseGlimmer vision encoder: 50-layer ViT with 2D RoPE, sparse block-diagonal
1149
+ +// window attention (every 4th + last layer global), pixel-shuffle downsample, then
1150
+ +// adapter MLP + LLM's vision_projection.
1151
+ +//
1152
+ +// Several quantities are precomputed on host and fed as named graph inputs (filled in
1153
+ +// clip.cpp set_input, PROJECTOR_TYPE_MUSE_GLIMMER branch):
1154
+ +// muse_glimmer_pos_w/_h [n_tok] i32 : 1-indexed RoPE positions (sparse-permuted order)
1155
+ +// muse_glimmer_sp_perm [n_tok] i32 : window grouping permutation (applied after ln_pre)
1156
+ +// muse_glimmer_inv_perm [n_tok] i32 : inverse of sp_perm (applied after blocks)
1157
+ +// muse_glimmer_ds_perm [n_tok] i32 : pixel-shuffle gather (original order)
1158
+ +// muse_glimmer_sp_mask [n_tok, n_tok] f16 : block-diagonal window mask (sparse layers)
1159
+ +ggml_cgraph * clip_graph_muse_glimmer::build() {
1160
+ + const int ds = hparams.n_merge; // downsample factor (2)
1161
+ + const int sf = hparams.muse_glimmer_sparse_factor; // 4
1162
+ + const int n_tok = n_patches;
1163
+ + const int n_out = (n_patches_x / ds) * (n_patches_y / ds);
1164
+ + const float rope_base = hparams.rope_theta; // 10000
1165
+ +
1166
+ + auto inp_i32 = [&](const char * name, int64_t n) {
1167
+ + ggml_tensor * t = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n);
1168
+ + ggml_set_name(t, name);
1169
+ + ggml_set_input(t);
1170
+ + return t;
1171
+ + };
1172
+ +
1173
+ + ggml_tensor * pos_w = inp_i32("muse_glimmer_pos_w", n_tok);
1174
+ + ggml_tensor * pos_h = inp_i32("muse_glimmer_pos_h", n_tok);
1175
+ + ggml_tensor * sp_perm = inp_i32("muse_glimmer_sp_perm", n_tok);
1176
+ + ggml_tensor * inv_perm = inp_i32("muse_glimmer_inv_perm", n_tok);
1177
+ + ggml_tensor * ds_perm = inp_i32("muse_glimmer_ds_perm", n_tok);
1178
+ +
1179
+ + // This ROCmFPX base predates the F32 flash-attention mask support used by
1180
+ + // current upstream, so keep the same values in the required F16 format.
1181
+ + ggml_tensor * sp_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F16, n_tok, n_tok);
1182
+ + ggml_set_name(sp_mask, "muse_glimmer_sp_mask");
1183
+ + ggml_set_input(sp_mask);
1184
+ +
1185
+ + // patchify via build_inp (conv2d over raw pixels) + bilinear-resized learned pos-emb
1186
+ + ggml_tensor * x = build_inp(); // [n_embd, n_tok, 1]
1187
+ + x = ggml_add(ctx0, x, resize_position_embeddings(GGML_SCALE_MODE_BILINEAR));
1188
+ + cb(x, "after_posemb", -1);
1189
+ +
1190
+ + // group patches into pgrid x pgrid windows (sparse attention order)
1191
+ + x = ggml_get_rows(ctx0, x, sp_perm);
1192
+ + cb(x, "after_sp_perm", -1);
1193
+ +
1194
+ + // per-layer mask: sparse layers get sp_mask, global layers (every sf-th and last) get none
1195
+ + std::vector<ggml_tensor *> attn_mask_layers(n_layer);
1196
+ + for (int il = 0; il < n_layer; ++il) {
1197
+ + const bool is_global = (il == n_layer - 1) || ((il + 1) % sf == 0);
1198
+ + attn_mask_layers[il] = is_global ? nullptr : sp_mask;
1199
+ + }
1200
+ +
1201
+ + // 2D RoPE: first half of head_dim uses width pos, second half uses height pos
1202
+ + auto add_pos = [&](ggml_tensor * cur, const clip_layer &) {
1203
+ + return build_rope_2d(ctx0, cur, pos_w, pos_h, rope_base, false);
1204
+ + };
1205
+ +
1206
+ + build_vit_opts opts;
1207
+ + opts.attn_mask_layers = std::move(attn_mask_layers);
1208
+ +
1209
+ + // pre_ln, per-layer transformer, post_ln (all inside build_vit); reference uses exact (erf) GELU
1210
+ + x = build_vit(x, n_tok, NORM_TYPE_NORMAL, FFN_GELU_ERF, nullptr, add_pos, opts);
1211
+ +
1212
+ + // un-permute back to original grid order
1213
+ + x = ggml_get_rows(ctx0, x, inv_perm);
1214
+ + cb(x, "after_inv_perm", -1);
1215
+ +
1216
+ + // pixel-shuffle downsample: gather f*f spatial neighbors then concat channel-outer.
1217
+ + // out[c*(ds*ds)+s, o] = x[ds_perm gathered][o*(ds*ds)+s, c]
1218
+ + x = ggml_get_rows(ctx0, x, ds_perm); // [n_embd, n_tok], grouped
1219
+ + x = ggml_reshape_3d(ctx0, x, n_embd, ds * ds, n_out);// [c, s, o]
1220
+ + x = ggml_permute(ctx0, x, 1, 0, 2, 3); // [s, c, o]
1221
+ + x = ggml_cont(ctx0, x);
1222
+ + x = ggml_reshape_2d(ctx0, x, n_embd * ds * ds, n_out); // [6144, n_out]
1223
+ + cb(x, "encoder_out", -1);
1224
+ +
1225
+ + // adapter (6144->4096->4096, exact GELU each) + LLM vision_projection (4096->6656)
1226
+ + x = build_mm(model.mm_0_w, x);
1227
+ + x = ggml_gelu_erf(ctx0, x);
1228
+ + x = build_mm(model.mm_1_w, x);
1229
+ + x = ggml_gelu_erf(ctx0, x);
1230
+ + x = build_mm(model.mm_2_w, x); // [6656, n_out]
1231
+ + cb(x, "projected", -1);
1232
+ +
1233
+ + ggml_build_forward_expand(gf, x);
1234
+ + return gf;
1235
+ +}
1236
+ diff --git a/tools/mtmd/mtmd-image.cpp b/tools/mtmd/mtmd-image.cpp
1237
+ index 1b058e026..dff9c1bd0 100644
1238
+ --- a/tools/mtmd/mtmd-image.cpp
1239
+ +++ b/tools/mtmd/mtmd-image.cpp
1240
+ @@ -1427,3 +1427,68 @@ bool mtmd_image_preprocessor_youtuvl::preprocess(const clip_image_u8 & img, clip
1241
+ output.entries.push_back(std::move(img_f32));
1242
+ return true;
1243
+ }
1244
+ +
1245
+ +//
1246
+ +// mtmd_image_preprocessor_muse_glimmer
1247
+ +//
1248
+ +
1249
+ +// Replicates transformers' get_aspect_ratio_preserving_size
1250
+ +static clip_image_size muse_glimmer_grid_size(int img_w, int img_h, int patch_hw, int max_tokens) {
1251
+ + double i_nph = (double) img_h / patch_hw;
1252
+ + double i_npw = (double) img_w / patch_hw;
1253
+ + const double ratio = i_nph > 0.0 ? i_npw / i_nph : 1.0;
1254
+ + if (i_nph * i_npw > (double) max_tokens) {
1255
+ + i_nph = std::sqrt((double) max_tokens / ratio);
1256
+ + i_npw = i_nph * ratio;
1257
+ + }
1258
+ + const int hs[2] = { (int) std::floor(i_nph), (int) std::ceil(i_nph) };
1259
+ + const int ws[2] = { (int) std::floor(i_npw), (int) std::ceil(i_npw) };
1260
+ + const double target_ar = (double) img_h / (double) img_w;
1261
+ + int best_nph = -1;
1262
+ + int best_npw = -1;
1263
+ + double best_d = 0.0;
1264
+ + for (int a = 0; a < 2; ++a) {
1265
+ + for (int b = 0; b < 2; ++b) {
1266
+ + const int nph = hs[a];
1267
+ + const int npw = ws[b];
1268
+ + if (nph < 1 || npw < 1 || nph * npw > max_tokens) {
1269
+ + continue;
1270
+ + }
1271
+ + const double d = std::fabs((double) nph / (double) npw - target_ar);
1272
+ + const int n_tokens = nph * npw;
1273
+ + const int best_n_tokens = best_nph * best_npw;
1274
+ + if (best_nph < 0 || d < best_d || (d == best_d && n_tokens > best_n_tokens)) {
1275
+ + best_nph = nph;
1276
+ + best_npw = npw;
1277
+ + best_d = d;
1278
+ + }
1279
+ + }
1280
+ + }
1281
+ + if (best_nph < 0) { // no candidate fit under the cap: round and clamp
1282
+ + best_nph = std::max(1, (int) std::lround(i_nph));
1283
+ + best_npw = std::max(1, (int) std::lround(i_npw));
1284
+ + }
1285
+ + return clip_image_size{ best_npw * patch_hw, best_nph * patch_hw };
1286
+ +}
1287
+ +
1288
+ +bool mtmd_image_preprocessor_muse_glimmer::preprocess(
1289
+ + const clip_image_u8 & img,
1290
+ + clip_image_f32_batch & output) {
1291
+ + const int patch_hw = hparams.patch_size * hparams.n_merge;
1292
+ + const int patch_area = hparams.patch_size * hparams.patch_size * hparams.n_merge * hparams.n_merge;
1293
+ + GGML_ASSERT(patch_area > 0 && hparams.image_max_pixels > 0);
1294
+ + const int max_tokens = hparams.image_max_pixels / patch_area;
1295
+ +
1296
+ + const clip_image_size original_size = { img.nx, img.ny };
1297
+ + const clip_image_size target_size = muse_glimmer_grid_size(
1298
+ + original_size.width, original_size.height, patch_hw, max_tokens);
1299
+ +
1300
+ + // PIL resizes directly to (target_w, target_h) -- a stretch, no padding.
1301
+ + clip_image_u8 resized_image;
1302
+ + img_tool::resize(img, resized_image, target_size, hparams.image_resize_algo, false);
1303
+ +
1304
+ + clip_image_f32_ptr img_f32(clip_image_f32_init());
1305
+ + img_u8_to_f32(resized_image, *img_f32, hparams.image_mean, hparams.image_std);
1306
+ + output.entries.push_back(std::move(img_f32));
1307
+ + return true;
1308
+ +}
1309
+ diff --git a/tools/mtmd/mtmd-image.h b/tools/mtmd/mtmd-image.h
1310
+ index 08129a08e..fdfcaaf2c 100644
1311
+ --- a/tools/mtmd/mtmd-image.h
1312
+ +++ b/tools/mtmd/mtmd-image.h
1313
+ @@ -177,3 +177,9 @@ struct mtmd_image_preprocessor_youtuvl : mtmd_image_preprocessor {
1314
+ mtmd_image_preprocessor_youtuvl(const clip_ctx * ctx) : mtmd_image_preprocessor(ctx) {}
1315
+ bool preprocess(const clip_image_u8 & img, clip_image_f32_batch & output) override;
1316
+ };
1317
+ +
1318
+ +// pick the patch grid closest to the input aspect ratio under the per-image token cap, stretch-resize.
1319
+ +struct mtmd_image_preprocessor_muse_glimmer : mtmd_image_preprocessor {
1320
+ + mtmd_image_preprocessor_muse_glimmer(const clip_ctx * ctx) : mtmd_image_preprocessor(ctx) {}
1321
+ + bool preprocess(const clip_image_u8 & img, clip_image_f32_batch & output) override;
1322
+ +};
1323
+ diff --git a/tools/mtmd/mtmd.cpp b/tools/mtmd/mtmd.cpp
1324
+ index 8e3e5e013..9f7964828 100644
1325
+ --- a/tools/mtmd/mtmd.cpp
1326
+ +++ b/tools/mtmd/mtmd.cpp
1327
+ @@ -336,6 +336,12 @@ struct mtmd_context {
1328
+ img_end = "<|vision_end|>";
1329
+ image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);
1330
+ } break;
1331
+ + case PROJECTOR_TYPE_MUSE_GLIMMER:
1332
+ + {
1333
+ + img_beg = "<|image_start|>";
1334
+ + img_end = "<|image_end|>";
1335
+ + image_preproc = std::make_unique<mtmd_image_preprocessor_muse_glimmer>(ctx_v);
1336
+ + } break;
1337
+ case PROJECTOR_TYPE_YOUTUVL:
1338
+ {
1339
+ // <|vision_start|> ... (image embeddings) ... <|vision_end|>
SHA256SUMS ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ 733b6eee371dfe2c49bd338dbf597bcdae1e5297de53cfa98f61a4d7e6e196ae Muse-Glimmer-30B-ROCmFP4.gguf
2
+ 6b80db6b044d9a3999b539eec140dc4d29279d53c18f42f80a557d3e6736123a Muse-Glimmer-30B-ROCmFP4-Q6-QUALITY.gguf
3
+ 74a5f469f17420d01a837400a2ef19d8191dcb1b479fd7c0be0c862901879915 Muse-Glimmer-30B-ROCmFP8.gguf
4
+ 3727a47650ae65be12e91aae49f9b9f4bef09dc6c401a54ca1bd3d1a6160f35a mmproj-Muse-Glimmer-30B-BF16.gguf