Skip to content

model : add support for HrmTextForCausalLM (DFM Mimir 1B) - #27625

Merged
CISC merged 22 commits into
ggml-org:masterfrom
noctrex:hrm-text-support
Sep 16, 2026
Merged

CISC merged 22 commits into
ggml-org:masterfrom
noctrex:hrm-text-support

Conversation

@noctrex

@noctrex noctrex commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Overview

HRM-Text runs two transformer stacks (low, high) in an alternating cycle over the same token stream. The low-cycle state z_l starts from a learned [n_embd] tensor and is broadcast over positions.

  • conversion: new writer for the fused gqkv projection (order gate,q,k,v) remapped to llama.cpp q/k/v plus a separate sigmoid gate tensor
  • loader: block_count = lps * h_cycles * (l_cycles + 1) cache slots aliasing 2*lps physical blocks via struct copies
  • graph: looped build with sigmoid-gated attention, SwiGLU FFN and parameterless RMS norms; learned embedding_scale applied in build_inp_embd
  • saver: pointer-deduplicated layer loop (looped archs alias tensors)
  • tests: hrm_text fixture in test-llama-archs

Additional information

Limitations:
causal attention only - the upstream prefix-LM mode is not implemented (the prefix_lm GGUF key round-trips unused).
The KV cache holds one entry per pass: 128 layers for Mimir 1B, i.e. 4x a same-width 32-layer model - about 3072 MiB at ctx 4096 in F16 (halves with q8_0 KV + FA).
Every token runs all 128 block passes, so decode cost is roughly 4x a dense model of equal width (2.65 t/s BF16, 8-thread desktop CPU).

Verified against the HF reference: identical argmax at 334/334 positions across 20 prompts (BF16 GGUF vs FP32 golden).
q8_0 requant: 95.8% top-1, all remaining misses inside the HF top-5 (accumulated error over 128 sequential blocks).

I have uploaded the GGUF's here: https://huggingface.co/noctrex/DFM-Mimir

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES - Used GLM-5.3 for the majority of code AI-generated under my direction, all gates verified locally. All in all I could say that I have written less than 20% of the code and most of the heavy lifting has been done by the model. As such, this should be considered experimental.

@github-actions github-actions Bot added model Model specific testing Everything test related conversion labels Aug 23, 2026
HRM-Text runs two transformer stacks (low, high) in an alternating cycle over the same token stream. The low-cycle state z_l starts from a learned [n_embd] tensor and is broadcast over positions.

- conversion: new writer for the fused gqkv projection (order gate,q,k,v) remapped to llama.cpp q/k/v plus a separate sigmoid gate tensor
- loader: block_count = lps * h_cycles * (l_cycles + 1) cache slots aliasing 2*lps physical blocks via struct copies
- graph: looped build with sigmoid-gated attention, SwiGLU FFN and parameterless RMS norms; learned embedding_scale applied in build_inp_embd
- saver: pointer-deduplicated layer loop (looped archs alias tensors)
- tests: hrm_text fixture (lps 1, h 2, l 3) in test-llama-archs

Limitations:
causal attention only - the upstream prefix-LM mode is not implemented (the prefix_lm GGUF key round-trips unused).
The KV cache holds one entry per pass: 128 layers for Mimir 1B, i.e. 4x a same-width 32-layer model - about 3072 MiB at ctx 4096 in F16 (halves with q8_0 KV + FA).
Every token runs all 128 block passes, so decode cost is roughly 4x a dense model of equal width (2.65 t/s BF16, 8-thread desktop CPU).

Verified against the HF reference: identical argmax at 334/334 positions across 20 prompts (BF16 GGUF vs FP32 golden).
q8_0 requant: 95.8% top-1, all remaining misses inside the HF top-5 (accumulated error over 128 sequential blocks).

AI usage disclosure: YES
Used GLM-5.3 for the majority of code AI-generated under my direction, all gates verified locally.
All in all I could say that I have written less than 20% of the code and most of the heavy lifting has been done by the model. As such, this should be considered experimental.
# Conflicts:
#	src/llama-arch.cpp
#	src/llama-arch.h
#	src/llama-model-saver.cpp
#	src/llama-model.h
@noctrex

noctrex commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

@CISC Would ask you kindly if you would have a look at this model support, please. I know that you're very busy and I'm sorry if I interrupt you

@bolgacg

bolgacg commented Sep 5, 2026

Copy link
Copy Markdown

Mimir scores a third lower than it should when llama.cpp runs it, and the cause is how
the prompt is read.

DFM-Mimir is a Danish 1B model. It was trained as a prefix language model. That means:
while it reads the prompt, every token can see every other token in the prompt, in both
directions. Only the answer is generated one token at a time, left to right. The model's
config file says this (prefix_lm: true).

This PR reads the prompt left to right only, the way an ordinary causal model does. So
the model reads its input in a way it was never trained for.

I measured the effect on DAISY, a public Danish quiz of 592 questions made by the same
group that made Mimir. Same weights, same questions, their prompt, their exact-match
scorer, greedy decoding. Only the way the prompt is read changes:

official transformers code, prompt read in both directions 8.4 %
official transformers code, prompt read left to right only 5.4 %
this PR, Q8_0 GGUF, left to right only 5.6 %
the number the Mimir paper reports 9.6 %

The first two rows use the same code and differ only in attention mode: 8.4 against 5.4.
So the attention mode explains the gap to the paper. Quantisation does not: the Q8_0
GGUF scores the same as the unquantised model in the same mode.

Script and logged outputs: github.com/bolgacg/daisy-tools
(scripts/mimir_official.py --prefix, results/pred_mimir-official-*).

What would fix it is an attention mode for this architecture where the prompt is read in
both directions and only the generated tokens are causal, which is how the model was
trained. Until then, a short note on the model card that scores from this port are lower
than the paper's would help anyone comparing numbers. I am happy to rerun the benchmark
on any change.

@noctrex

noctrex commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

@bolgacg thank you for your report.
Yes this is a limitation of this implementation because foremost I just want to add model support for it, and as I already have said:

Limitations:
causal attention only - the upstream prefix-LM mode is not implemented

because first I wanted to do the minimal changes for the model to be able to load before creating any additional additions needed for it working correctly.
The prefix-LM mode is a much larger change that should be added at later point in a separate PR.

Comment thread conversion/hrm_text.py Outdated
Comment thread conversion/hrm_text.py Outdated
Comment thread conversion/hrm_text.py Outdated
Comment thread src/models/hrm-text.cpp Outdated
Comment thread src/llama-arch.cpp Outdated
Comment thread conversion/hrm_text.py Outdated
@bolgacg

bolgacg commented Sep 6, 2026

Copy link
Copy Markdown

@noctrex I benefit from this PR getting sorted, since my Mimir evaluation work runs through it, so I went ahead and built the prefix-LM mode on top of your branch; now that the review is moving I wanted to share it: noctrex#1. 35 lines: the KQ mask drops the causal check for models with prefix_lm set, the server stops reusing cached prompt prefixes, and decode warns when a prompt is split across ubatches. Checked against the official transformers implementation on the full DAISY set: 8.3 against 8.4 exact match, word-identical answers on 93% of the 592 questions, where the causal build gives 29%. You can merge it into this branch if it helps, or it can go against master once this lands, whichever fits your plan.

noctrex and others added 8 commits September 6, 2026 23:35
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
replace raw add_uint32/add_bool calls with dedicated GGUFWriter methods, following the add_embedding_scale pattern

Assisted-by: GLM-5.3
delegate unfused checkpoints to the base tensor mapping; training-style attn. names are renamed to self_attn. so the patterns match

Assisted-by: GLM-5.3
one argument group per line, matching sibling model files

Assisted-by: GLM-5.3
place the name and tensor-info entries with the other global input tensors

Assisted-by: GLM-5.3
keep both pre-tokenizer hash entries (gemma4/DFM-Mimir, spark2_5)

Assisted-by: GLM-5.3
@peter-sk

peter-sk commented Sep 8, 2026

Copy link
Copy Markdown

Is there a way Danish Foundation Models could support this implementation? We would be very happy to ensure stable HRMText support with full prefix LM.

Comment thread conversion/hrm_text.py Outdated
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/llama-arch.cpp Outdated
The tensor map holds concrete per-block names, so format the template
with the computed layer index before handing it to super().
The four keys are arch-independent, unlike the arch-substituted
Keys.LLM entries, so group them under Keys.HRM (like Keys.Split) and
rename the llm_kv entries to LLM_KV_HRM_*. Only our own GGUFs carry
the old hrm_text.* keys; they are regenerated.
@noctrex

noctrex commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@peter-sk hello and thanks for the model!
For now let me iron out the kinks in order to successfully merge the initial PR, so that the model will be supported and then, there is already a PR open from @bolgacg to add the prefix LM.
We could need your expertise then.
Could we call you up then if needed?

Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/llama-model-saver.cpp Outdated
noctrex and others added 2 commits September 8, 2026 22:47
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Per review: the GGUF keys stay "{arch}.h_cycles" style, so the Python
members drop the LLM_KV_HRM_ prefix and keep arch templates; C++ keeps
the LLM_KV_HRM_* enums. GGUF output is unchanged - existing files and
HF uploads stay valid.
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/llama-arch.cpp Outdated
noctrex and others added 2 commits September 9, 2026 03:42
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Comment thread gguf-py/gguf/gguf_writer.py Outdated
Comment thread src/llama-arch.cpp Outdated
noctrex and others added 2 commits September 9, 2026 13:24
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Generic names like add_h_cycles/add_prefix_lm are too broad on the
shared GGUFWriter; prefix them with hrm_ like the metadata keys.
@CISC

CISC commented Sep 9, 2026

Copy link
Copy Markdown
Member

Cache tensors of archs that alias physical blocks across looped slots
(hrm_text, nanbeige with num_loops > 1) can reference block indices
without weight tensor names. Take the output projection from the layer
array instead of asserting; all other lookups are unchanged.
@noctrex

noctrex commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

https://github.com/ggml-org/llama.cpp/actions/runs/34296160375/job/102368307545#step:3:3153

ok, cache tensors whose block index has no weight-tensor names (aliased slots) now take the split config from their layer's output projection.
Also covers nanbeige with num_loops > 1, which had the same latent issue.
Unfortunately, I don't have a mac so I can't confirm it on my system so let's see how the actions proceed
noctrex/llama.cpp@d83e3b0c6
Assisted by glm-5.3-flash

@CISC

CISC commented Sep 9, 2026

Copy link
Copy Markdown
Member

@ggerganov Mind reviewing in case you have differing views on this one? :)

@CISC

CISC commented Sep 9, 2026

Copy link
Copy Markdown
Member

The aliased cache slots rotate split states differently from their
physical weights, so the meta-split execution invariants (set_rows
requires the cache state to match the token indices) cannot hold for
any device count. Replicate all hrm_text tensors on every meta device
instead; single-device and non-meta paths are unchanged.

Assisted-by: Claude Sonnet
@noctrex

noctrex commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Slightly different failure: https://github.com/ggml-org/llama.cpp/actions/runs/34347683722/job/102461471303?pr=27625#step:3:6024

Metal and CUDA failed for the same reason, just at a later step.
The first fix got the model to load, but actually running split across devices still breaks.
hrm_text now doesn't split across meta devices at all
Every device just gets a full copy of the model.
That always works.
Tradeoff: no multi-GPU speedup, but its a small 1B model so it should not pose a problem
516bac0

@CISC
CISC merged commit 7d6f5d0 into ggml-org:master Sep 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conversion model Model specific testing Everything test related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants