Skip to content

Add HRM-Text (hrm_text) hierarchical recurrent model with PrefixLM masking - #592

Open
justinchuby wants to merge 2 commits into
mainfrom
justinchuby-add-hrm-text-model
Open

Add HRM-Text (hrm_text) hierarchical recurrent model with PrefixLM masking#592
justinchuby wants to merge 2 commits into
mainfrom
justinchuby-add-hrm-text-model

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Adds production ONNX export for the HuggingFace hrm_text architecture, using sapientinc/HRM-Text-1B pinned to revision 9f082d68b8cd0ebc56e33f1c88c45609174c272c. The revision is threaded through config, weights, tokenizer, golden generation, the integration test, and the CLI.

Architecture

HRM-Text is not a flat decoder stack. It owns two independently-weighted transformer stacks — a fast "low" stack (L_module) and a slow "high" stack (H_module) — and runs them in a fixed recurrence:

z_H = embed(input_ids) * embedding_scale
z_L = z_L_init                                  # broadcast over (B, S, H)
for h in range(H_cycles):
    for l in range(L_cycles):
        z_L = L_module(z_L + z_H)
    z_H = H_module(z_H + z_L)
logits = lm_head(z_H)

Every stack invocation runs all num_layers_per_stack blocks and therefore performs its own attention with its own KV-cache slots. Upstream reflects that by inflating config.num_hidden_layers to num_layers_per_stack * H_cycles * (L_cycles + 1) so DynamicCache allocates one slot per unique attention invocation. The exported graph uses the same inflated count and emits slots in the same order, so past_key_values.{i} / present.{i} line up 1:1 with HuggingFace. For HRM-Text-1B that is 128 cache slots driven by 32 blocks of weights (16 per stack), in a 2198-node graph.

Faithfully replicated against transformers.models.hrm_text and transformers.conversion_mapping:

Feature Detail
Parameterless RMSNorm HrmTextRMSNorm has no learnable scale and normalises in fp32 → ScaleFreeRMSNorm
Gated attention output separate gate_proj; sigmoid(gate) * attn_out applied before o_proj, from the same normalized states that produced Q/K/V
Embedding scaling * config.embedding_scale, defaulting to 1 / initializer_range
MHA, not GQA k_proj/v_proj sized by num_attention_heads (upstream hardcodes num_key_value_groups = 1)
No trailing model norm each stack ends with its own final_norm, so the last H_module output feeds lm_head directly
PrefixLM masking block_sequence_ids = where(token_type_ids == 1, 0, -1), bidirectional within the prefix block
Fused checkpoint weights attn.gqkv_proj splits gate, q, k, v on dim 0; mlp.gate_up_proj splits gate/up

PrefixLM is implemented, not approximated

The model card is explicit that omitting token_type_ids "does not match the pre-training distribution and will give noticeably worse logits". Measured on the pinned revision (HF fp32, CPU), this is a real semantic difference rather than rounding:

prompt prefill logit Δ (max / mean) causal greedy PrefixLM greedy
"Here is my poem:" 14.26 / 1.10 24 identical space tokens 24 identical : tokens
"The capital of France is" 16.48 / 1.28 8 distinct tokens, looping 4 distinct tokens
model-card prompt 19.61 / 1.35 19 distinct 23 distinct, coherent

So CausalLMTask gained an opt-in token_type_ids graph input, probed via a requires_token_type_ids module hook — the same pattern as the existing kv_cache_layer_count / static_kv_cache_specs hooks. task_type stays text-generation, the registry is untouched, and no other model gains an input or a forward keyword.

HrmTextModel then feeds mobius's existing create_attention_bias(block_sequence_ids=...) primitive, which implements HF's blockwise_overlay(q_group == kv_group) & (q_group >= 0), OR-ed onto causal before the padding AND — identically.

Upstream gates the overlay on is_first_iteration. The graph reproduces that by data rather than by branching: a generated token is fed with token_type_ids == 0 → block -1, and the q_group >= 0 guard makes the overlay a pure no-op, so decode stays causal. Consequently all-zero token_type_ids reproduces HF's token_type_ids=None path through the very same graph, and one export serves both contracts.

Supporting changes to shared code

  • Attention._post_attention — a no-op extension point invoked before o_proj on both the plain and the GroupQueryAttention path, so gated variants reuse the entire Q/K/V + RoPE + cache pipeline instead of duplicating it.
  • Attention._is_causal — an instance attribute (default 1) so a subclass feeding a float additive bias that already encodes causality is not double-masked. It is a static per-graph property, so it needs no forward-signature change. Static-cache mode still derives causality from attn_mask presence, unchanged.
  • TextModel._build_attention_context — the existing EP-aware GQA / padding-mask / static-cache-bias selection moved out of forward verbatim so a model with a non-sequential layer schedule can reuse it.
  • mobius._testing.prefix_lm — single source of truth for the PrefixLM contract, so the HF reference and the ONNX graph cannot drift apart. Wired into generate_golden.py, e2e_golden_test.py, OnnxGenerator, and the integration feeds.
  • Revision pinningload_torch_model accepts revision, and generate_golden.py forwards the test case revision, so golden references are reproducibly pinned.

Bug found and fixed while testing

A raw config.json carries only the default rope_theta = 10000.0, which mobius's generic extractor deliberately ignores as a RoPE signal (NoPE models inherit it as dead config data). That left rope_type = None and silently exported a position-free graph even though HrmTextRotaryEmbedding is unconditional upstream. HrmTextConfig now pins default RoPE explicitly, with a regression test. Verified this cannot clobber a genuine non-default RoPE (rope_theta=500000 and rope_scaling={"rope_type":"yarn"} both survive, because a non-default theta is itself a RoPE signal).

Test evidence

All runs used PYTHONPATH=<this worktree>/src and PYTHONIOENCODING=utf-8, verified in-process via print(mobius.__file__).

Level Result
L1 graph build + weight alignment pass (tiny config in _test_configs.py, is_representative=True)
L2 arch validation from pinned full HF config 3 passed
L3 synthetic parity vs HF pass at atol=1e-3
L3 real-weight prefill + cached decode + causal fallback pass (integration_test.py, pinned revision)
L4 golden pass (126.5 s)
L5 generation golden pass (152.5 s), exact_match: true
hrm_text unit tests 20 passed
Full non-integration suite 5225 passed

Real-weight numerical parity (sapientinc/HRM-Text-1B, pinned)

Full-logit comparison against an independently loaded HF fp32 CPU reference:

build providers prefill PrefixLM (max / cosine) cached decode (max / cosine) 24-token generation
f32 / cpu CPU 1.29e-5 1.05e-5
f16 / cuda CUDA ✅ 0.04067 / 0.99999636 0.01242 / 0.99999940 24/24 exact, text exact
bf16 / cuda CUDA ✅ 0.84057 / 0.99986333 0.09451 / 0.99999225 24/24 exact, text exact
f16 / cpu CPU 0.05298 / 0.99999696 0.00640 / 0.99999946 24/24 exact, text exact

session.get_providers() was asserted in-script, not assumed; the run hard-fails if CUDA EP is absent.

CPU-vs-CUDA: the identical fp16 graph produced byte-identical 24-token output on both EPs, and both equal the committed L5 golden exactly.

The overlay is provably live, not inert. Prefix-vs-causal divergence measured through the same exported graph: HF fp32 19.6093 · ONNX f16/cuda 19.6328 · bf16/cuda 19.6875 · f16/cpu 19.6211. Tests assert the ONNX gap matches HF's to 1% and that it exceeds 1.0 on real weights, so a graph that silently dropped PrefixLM cannot pass.

L5 golden (non-degenerate)

prompt: <|im_start|><|quad_end|><|object_ref_end|>Explain why the sky is blue.<|im_end|>
output: "The sky appears blue due to the scattering of sunlight by air molecules.
         Shorter wavelengths of light (blue"

24 tokens, 23 distinct. exact_match: true asserts exact sequence length, exact token IDs, and exact decoded text.

Graph shape

build op histogram
PrefixLM (default) MatMul 1025, SkipSimplifiedLayerNorm 263, Mul 257, RotaryEmbedding 256, Attention 128, Sigmoid 128, Swish 128
prefix_lm=False … GroupQueryAttention 128, Sigmoid 128, Swish 128

128 attention nodes = exactly one per unique attention invocation, and 128 Sigmoid = the gated output, confirming the _post_attention hook fires on both the plain and the fused GQA path. The PrefixLM graph forgoes GQA fusion because the float additive bias requires is_causal=0 and GQA's mask model is causal/local-window only — the same correctness-for-speed trade-off Gemma4 documents for its vision-block overlay.

CLI + runtime metadata

mobius build --model sapientinc/HRM-Text-1B --revision 9f082d68… --runtime ort-genai succeeds, producing model.onnx + model.onnx.data (4515.6 MB) + genai_config.json + tokenizer files. The metadata is graph-faithful, including num_hidden_layers: 128 (the inflated slot count, matching the real cache contract) and the token_type_ids input.

INT4 quantization

Quantizing the exported package with ORT's MatMulNBitsQuantizer (4-bit, block 32, asymmetric) works end to end:

  • 4522 MB → 1055 MB (4.3× smaller)
  • all 1025 MatMulMatMulNBits, including the fused gqkv_proj-derived projections and lm_head; the recurrence structure (Attention 128, Sigmoid 128, Swish 128, RotaryEmbedding 256) is preserved
  • the quantized package loads and runs, and greedy generation is non-degenerate and 24/24 identical to the fp32 golden: "The sky appears blue due to the scattering of sunlight by air molecules. Shorter wavelengths of light (blue"

tests/quantization_integration_test.py also passes (6/6), confirming the shared CausalLMTask / Attention changes introduce no quantization regressions.

Waivers

1. onnxruntime-genai cannot drive this model (runtime-side, two independent reasons).

Probed with onnxruntime-genai 0.15.2 against the generated package:

RuntimeError: Error encountered while parsing genai_config.json
JSON Error: model:decoder:inputs: Unknown value "token_type_ids"

I then re-probed a variant with token_type_ids removed from the config, to prove the failure is not caused by mobius emitting that input:

RuntimeError: Unsupported model_type in config.json: hrm_text

So ORT GenAI rejects hrm_text outright regardless of PrefixLM — it has no registry entry for this architecture — and separately has no token_type_ids value in its decoder input schema. Both are runtime limitations, not export defects. Per the quality checklist, downstream runtime capability does not gate Mobius export, and the generated metadata is kept faithful to the graph (a config omitting the required input would load and then fail later with a missing-input error, which is strictly worse).

2. PrefixLM + static KV cache is not supported. The static-cache path passes attention_mask=None and relies on is_causal=1 + nonpad_kv_seqlen, which cannot express a bidirectional prefix block. This raises an explicit NotImplementedError naming the two supported alternatives, rather than silently exporting a degraded causal graph.

3. Chunked-prefill edge case. Upstream gates the overlay on is_first_iteration; the graph gates it per-query on block_sequence_ids >= 0. These agree exactly for the documented single-shot prefill and for all decoding. They would differ only if a caller split one prefix block across multiple cached prefill chunks, which neither the model card nor generate() does.

Review

A specialist code review was run twice. Round 1's single High finding was the missing PrefixLM implementation — independently reaching the same conclusion as the analysis above — which is fixed here. Round 2 reported no significant issues, having independently verified overlay semantics against masking_utils.blockwise_overlay, GQA suppression across cuda/f16, cpu/f32, dml/f16 and default/f32, that _is_causal is inert for all 7 Attention subclasses across 121 model modules, that the RoPE pin cannot clobber genuine non-default RoPE, and that the base.py extraction is behavior-preserving.

The only failing tests in the suite (gguf q4_0, deepseek_v4 QMoE, decompose_attention softcap, qwen_image cuda low-precision, modernbert-decoder synthetic parity) reproduce byte-identically on 059bf5f1 and are unrelated to this change.

justinchuby and others added 2 commits August 24, 2026 05:52
Adds production ONNX export for the HuggingFace `hrm_text` architecture
(`sapientinc/HRM-Text-1B`).

HRM-Text is not a flat decoder stack: it owns two independently-weighted
transformer stacks (`L_module`, `H_module`) and runs them in a fixed
recurrence, so a single forward performs `H_cycles * (L_cycles + 1)` stack
invocations. Upstream reflects that by inflating `num_hidden_layers` to
`num_layers_per_stack * H_cycles * (L_cycles + 1)` so its cache allocates one
slot per unique attention invocation; the exported graph uses the same
inflated count and emits slots in the same order, so `past_key_values.{i}` /
`present.{i}` line up 1:1 with HuggingFace.

Architecture specifics replicated:
- parameterless (scale-free) RMSNorm with fp32 variance accumulation
- sigmoid-gated attention output driven by a separate `gate_proj`
- token-embedding scaling by `1 / initializer_range`
- MHA (k/v sized by `num_attention_heads`), full causal attention
- no trailing model norm (each stack ends with its own `final_norm`)

`preprocess_weights` un-fuses the checkpoint's `attn.gqkv_proj`
(gate/q/k/v on dim 0) and `mlp.gate_up_proj`, matching upstream's
`conversion_mapping` entry, and stays an identity for already-converted
HuggingFace state dicts.

Supporting changes:
- `Attention._post_attention`: a no-op extension point invoked before
  `o_proj` on both the plain and GroupQueryAttention paths, so gated
  variants reuse the whole Q/K/V + RoPE + cache pipeline.
- `TextModel._build_attention_context`: the existing EP-aware GQA /
  padding-mask / static-cache bias selection moved out of `forward` so a
  model with a non-sequential layer schedule can reuse it verbatim.
- `load_torch_model` accepts `revision`, and `generate_golden.py` forwards
  the test case revision so golden references are reproducibly pinned.

Tests: L1 tiny graph + weight alignment, L2 arch validation, L3 synthetic
parity, real-weight prefill and cached-decode parity against HuggingFace,
and dedicated unit tests for config inflation, cache-slot layout, and the
fused-weight split.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
HRM-Text was pre-trained with a PrefixLM mask: prompt tokens attend
bidirectionally within one prefix block, generated tokens attend causally.
The model card for `sapientinc/HRM-Text-1B` is explicit that omitting it
"does not match the pre-training distribution and will give noticeably
worse logits". The previous commit parsed `config.prefix_lm` but never
implemented it, so the export was unconditionally causal.

Measured on the pinned revision with HF fp32 on CPU, causal vs PrefixLM is
a real semantic difference rather than rounding:

    prompt                          prefill logit delta (max / mean)
    "Here is my poem:"                        14.26 / 1.10
    "The capital of France is"                16.48 / 1.28
    model-card prompt                         19.61 / 1.35

and only the model card's documented prompt under PrefixLM produces a
coherent, non-degenerate continuation.

Implementation:
- `CausalLMTask` gains an opt-in `token_type_ids` graph input, probed via a
  `requires_token_type_ids` module hook exactly like the existing
  `kv_cache_layer_count` / `static_kv_cache_specs` hooks. `task_type` stays
  `text-generation` and no other model gains an input or forward keyword.
- `HrmTextModel` maps `block_sequence_ids = where(token_type_ids == 1, 0, -1)`
  and feeds the existing `create_attention_bias(block_sequence_ids=...)`
  primitive, which implements HF's `blockwise_overlay`
  (`(q_group == kv_group) & (q_group >= 0)`, OR-ed onto causal) exactly.
- `Attention` gains an `_is_causal` instance attribute so a float additive
  bias that already encodes causality is not double-masked. It is a static
  per-graph property, so it needs no forward-signature change.

Upstream gates the overlay on `is_first_iteration`; the graph reproduces
that by data instead of by branching. A generated token is fed with
`token_type_ids == 0` -> block `-1`, and the `q_group >= 0` guard makes the
overlay a no-op, so decode stays causal. All-zero `token_type_ids` therefore
reproduces HF's `token_type_ids=None` path through the very same graph.

Also fixes a silent faithfulness bug found while testing: a raw
`config.json` only carries the default `rope_theta` of 10000.0, which the
generic extractor deliberately ignores as a RoPE signal, so the raw-config
path exported a position-free graph. `HrmTextRotaryEmbedding` is
unconditional upstream, so `HrmTextConfig` now pins default RoPE explicitly.

Golden/test plumbing sends the identical contract to both sides, derived
once in `mobius._testing.prefix_lm` rather than restated per call site:
`generate_golden.py`, `e2e_golden_test.py`, `OnnxGenerator`, and the
integration feeds. `torch_forward` accepts `token_type_ids` and forwards it
only when the model's signature has it.

The L4/L5 goldens are regenerated with the documented prompt and are now
non-degenerate (24 tokens, 23 distinct):
"The sky appears blue due to the scattering of sunlight by air molecules.
Shorter wavelengths of light (blue". The case sets `exact_match: true`, so
L5 asserts exact sequence length, token IDs, and decoded text.

Tests: PrefixLM prefill parity, causal-fallback parity through the same
graph, an assertion that the two modes diverge by the same amount on both
sides (so a graph ignoring the overlay cannot pass), PrefixLM cached decode,
graph-input declaration, and the `prefix_lm=False` input set.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby
justinchuby requested review from a team and a lite review from Copilot August 24, 2026 13:49
@github-actions

Copy link
Copy Markdown

Performance Comparison

Comparing 059bf5f993050d

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 68 68 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 66 66 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 105 105 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 60 60 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 56 56 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 94 94 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 58 58 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 54 54 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 60 60 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 56 56 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 264 264 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 126 126 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 428 428 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 176 176 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

Comment thread src/mobius/models/base.py
Comment on lines +291 to +300
def forward(
self,
op: OpBuilder,
input_ids: ir.Value,
attention_mask: ir.Value | None,
position_ids: ir.Value,
past_key_values: list | None = None,
inputs_embeds: ir.Value | None = None,
deepstack_embeds: list | None = None,
):
@github-actions

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 059bf5f993050d

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision_encoder 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class Mobius ONNX export support for the HuggingFace hrm_text architecture (HRM-Text-1B), including its hierarchical recurrent H/L stack schedule and PrefixLM masking semantics, and wires revision pinning through golden/reference generation and integration testing to keep results reproducible.

Changes:

  • Add HrmTextConfig extraction logic (including explicit default RoPE pinning) and register/export the new HrmTextCausalLMModel.
  • Implement HRM-Text model graph construction (H/L recurrence, gated attention output, embedding scaling) and opt-in token_type_ids handling for PrefixLM via CausalLMTask.
  • Add/update test + golden infrastructure for PrefixLM (token_type_ids) and for pinned-revision golden/reference generation.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/synthetic_parity_test.py Adds hrm_text tiny-config overrides and feeds zero token_type_ids when the exported graph declares it.
tests/integration_test.py Adds real-weight HRM-Text-1B PrefixLM prefill + cached decode parity test and threads token_type_ids into feeds.
tests/e2e_golden_test.py Ensures e2e golden harness feeds correct token_type_ids contract for PrefixLM graphs.
tests/_test_configs.py Adds a tiny HRM-Text config entry for graph-build and representative-arch coverage.
testdata/golden/causal-lm/hrm-text-1b.json Adds L4 logits golden for HRM-Text-1B.
testdata/golden/causal-lm/hrm-text-1b_generation.json Adds L5 greedy generation golden for HRM-Text-1B.
testdata/cases/causal-lm/hrm-text-1b.yaml Adds an HRM-Text-1B test case including pinned revision and generation parameters.
src/mobius/tasks/_causal_lm.py Adds optional token_type_ids graph input gated by module.requires_token_type_ids.
src/mobius/models/hrm_text.py New HRM-Text backbone + recurrence implementation, PrefixLM bias path, and checkpoint weight unfusing.
src/mobius/models/hrm_text_test.py Adds unit tests for config inflation, graph/cache shape, recurrence parity, and weight preprocessing.
src/mobius/models/base.py Factors out shared attention-context construction into _build_attention_context for reuse by non-sequential schedulers.
src/mobius/models/init.py Exports HrmTextCausalLMModel.
src/mobius/components/_attention.py Adds _post_attention hook and per-module _is_causal control to support gated output and float-bias masking.
src/mobius/_testing/torch_reference.py Adds revision pinning to load_torch_model and optional token_type_ids forwarding in torch_forward.
src/mobius/_testing/prefix_lm.py Introduces a shared PrefixLM token_type_ids contract helper for tests/golden generation.
src/mobius/_testing/generation.py Updates ONNX greedy generator to feed PrefixLM-style token_type_ids when required by the graph.
src/mobius/_registry.py Registers hrm_text and adds a default test model id entry for the architecture.
src/mobius/_configs/_base.py Adds HrmTextConfig dataclass and extraction logic (including explicit default RoPE pinning).
src/mobius/_configs/init.py Exports HrmTextConfig.
scripts/generate_golden.py Threads pinned revision into HF reference loading and applies PrefixLM token_type_ids contract for goldens.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +248 to +265
if token_type_ids is None:
raise ValueError(
"HRM-Text was built with config.prefix_lm=True, which requires a "
"token_type_ids input. The task supplies it via the module's "
"requires_token_type_ids hook; a caller invoking forward() "
"directly must pass token_type_ids explicitly."
)
if attention_mask is None:
# Static cache passes attention_mask=None and relies on
# is_causal=1 + nonpad_kv_seqlen, which cannot express the
# bidirectional prefix block.
raise NotImplementedError(
"HRM-Text PrefixLM masking is not supported with the static KV "
"cache: the bidirectional prefix overlay needs the dynamic "
"attention_mask to build its float additive bias. Build with "
"the default dynamic cache, or set config.prefix_lm=False for a "
"fully causal export."
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants