Skip to content

Add GLM-ASR Nano speech-language support - #509

Merged
justinchuby merged 3 commits into
mainfrom
justinchuby-add-glm-asr-nano
Aug 21, 2026
Merged

Add GLM-ASR Nano speech-language support#509
justinchuby merged 3 commits into
mainfrom
justinchuby-add-glm-asr-nano

Conversation

@justinchuby

@justinchuby justinchuby commented Aug 17, 2026

Copy link
Copy Markdown
Member

Summary

  • Add complete glmasr support for zai-org/GLM-ASR-Nano-2512 as standardized audio_encoderembedding → cached decoder ONNX models.
  • Implement the convolutional 32-layer bidirectional partial-RoPE audio tower, config-driven audio/projector activations, four-frame projector, audio-token embedding replacement, and 28-layer Llama-like decoder with dynamic KV cache.
  • Add exact checkpoint weight alignment, registry/config/task exports, ONNX GenAI metadata, immutable CLI --revision propagation, and documentation metadata.
  • Add a pinned real nonzero speech fixture plus exact L4 prefill and L5 transcript/token goldens.

Immutable inputs

  • Model: zai-org/GLM-ASR-Nano-2512@61ba4e0b3309b6656edea3e93e419f7bd5c61957
  • Audio: eustlb/audio-samples@1045238c3afdc0254472ecdde5dae95370d621e4
  • All config, weight, tokenizer, processor, metadata, golden-generation, and test Hub calls carry the immutable revision.

Architecture checklist

  • Nested GLM-ASR text/audio config extraction
  • Registry and default speech-language task wiring
  • Standardized audio_encoder, embedding, and decoder package keys
  • Whisper feature-extractor contract: float32 [B,128,3000], 16 kHz, FFT 400, hop 160
  • 20-head bidirectional audio Attention with 32/64 partial RoPE, asymmetric Q/V/O biases, and one output (no unused present KV)
  • Config-driven audio MLP and projector activations
  • Four-frame 5120 → 4096 → 2048 projector
  • Exact audio-placeholder replacement and valid projected-feature stripping inside ONNX
  • Llama decoder with 16 query heads, 4 KV heads, full RoPE, and cached decode
  • All 745 checkpoint parameter names aligned after preprocessing
  • ONNX GenAI speech-language metadata includes the pinned audio processor

Validation evidence

L1–L3 and regression gates

  • GLM-ASR graph/config/parity and rebase regression suite: 46 passed
  • ORT GenAI compatibility guard tests: 2 passed
  • Complete CLI suite: 37 passed
  • Synthetic FP32 HF/ONNX parity executes the complete audio → embedding → decoder pipeline and compares every stage, including short-audio zero-token behavior.
  • Standard non-integration gate: 4051 passed, 58 skipped, 10 deselected. Five unrelated environment-sensitive failures remain in GGUF Q4 dequantization, Qwen Image CUDA tolerance, and softcap Attention-decomposition parity; none overlap this change set or the required CI failures previously reported.

L4 CUDA prefill (rebased head)

$env:MOBIUS_TEST_DEVICE='cuda'
python -m pytest tests/e2e_golden_test.py -m golden --models glmasr -v --timeout=300 --tb=short
  • 1 passed, 240 skipped, 161 deselected in 166.73s
  • Real pinned speech, 138 projected audio tokens, 153-token prompt.
  • FP16 full-logit reference comparison: shape [1,153,59264], correlation 0.9991001, mean absolute difference 0.02795, maximum absolute difference 7.456; greedy token matches.

L5 CUDA cached generation (rebased head)

$env:MOBIUS_TEST_DEVICE='cuda'
python -m pytest tests/e2e_golden_test.py -m generation --models glmasr -v --timeout=300 --tb=short
  • 1 passed, 156 skipped, 245 deselected in 238.19s
  • Exact 23-token sequence and exact decoded transcript asserted:
    • Yesterday it was thirty five degrees in Barcelona, but today the temperature will go down to minus twenty degrees.

CLI export and CUDA execution

python -m mobius build --model zai-org/GLM-ASR-Nano-2512 <output> \
  --revision 61ba4e0b3309b6656edea3e93e419f7bd5c61957 \
  --ep cuda --dtype f16
  • Direct three-model ONNX export and saved-file CUDA execution pass:
    • audio (138, 2048)
    • embeddings (1, 153, 2048)
    • logits (1, 153, 59264)
    • prefill top-1 token 89
  • The staged test harness now destroys each ORT session before loading the next component. This fixes the old CI BFCArena initialization OOM (242745344-byte decoder allocation) and returns CUDA memory to baseline between stages.

Formatting and review

  • lintrunner -a: passed
  • Final specialist re-review: no high-confidence findings
  • All inline review threads are addressed and resolved.

Waivers and runtime constraints

  • FP32: complete synthetic stage-by-stage semantic parity passes at rtol=2e-4, atol=2e-4 for decoder logits and tighter tolerances for earlier stages. A full real FP32 CUDA checkpoint run is not practical on the available 8 GB RTX A1000.
  • FP16: full real-checkpoint CUDA L4/L5 and saved CLI artifact execution pass as documented above.
  • BF16: the weighted package builds and loads on CUDA. ORT 1.26 Python's NumPy API cannot materialize BF16 outputs (No corresponding Numpy type for Tensor Type. bfloat16), so executable L4/L5 uses FP16; BF16 output validation requires I/O binding or a runtime with BF16 host materialization.
  • GQA fusion: CUDA FP16 GroupQueryAttention changed greedy termination from the exact 23-token HF sequence to an earlier valid EOS at 21 tokens. GLM-ASR intentionally preserves standard opset-24 Attention for exact transcript parity; graph coverage locks this dispatch decision.
  • ORT GenAI: onnxruntime-genai 0.15.2 registers multimodal model types only for gemma4 and phi4mm; loading type: glmasr fails as unsupported. Mobius now rejects --runtime ort-genai before saving rather than emitting unloadable metadata. Direct ONNX and ONNX GenAI metadata remain supported; native ORT GenAI packaging is deferred until the runtime registers a compatible GLM-ASR pipeline.
  • Memcpy: ORT reports small control-tensor Memcpy nodes from dynamic shape/index operations (NonZero, integer CumSum, and placeholder indexing). Eliminating them would require changing the feature-stripping/embedding contract.

@justinchuby
justinchuby requested review from a team and a lite review from Copilot August 17, 2026 18:52
Comment thread src/mobius/models/glm_asr.py Fixed
Comment thread src/mobius/models/glm_asr_test.py Fixed
Comment thread src/mobius/models/glm_asr_test.py Fixed
Comment thread src/mobius/models/glm_asr_test.py Fixed
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing b2b821349ca0a4

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 60 60 +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 166 166 +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.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing b2b821349ca0a4

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

Adds first-class support for the GLM-ASR-Nano-2512 speech-language architecture in Mobius, exporting a standardized 3-model ONNX package (audio_encoderembedding → cached decoder) and extending runtime metadata/export flows to support pinned Hugging Face revisions and audio processor artifacts.

Changes:

  • Introduces the glmasr model implementation (partial-RoPE audio tower + 4-frame projector, audio-token embedding replacement, and cached Llama-like decoder) plus task wiring and registry/config extraction.
  • Extends ORT GenAI + ONNX GenAI export/metadata to include an explicit embedding stage and to propagate pinned HF revisions to config/tokenizer/processor/runtime asset downloads.
  • Adds graph/unit tests plus end-to-end golden cases (L4 prefill + L5 cached generation) and schema support for strict exact_match generation assertions.

Reviewed changes

Copilot reviewed 32 out of 33 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/e2e_golden_test.py Updates speech-language golden runner for GLM-ASR preprocessing, mask shapes, audio feature handling, and optional exact-match transcript assertions.
tests/cli_test.py Adds coverage ensuring --revision is forwarded to detection and build.
tests/build_graph_test.py Adds graph-contract + pipeline smoke tests for GLM-ASR three-stage split and CUDA attention op preservation.
tests/_test_configs.py Adds tiny override config for glmasr test cases.
testdata/golden/speech/glm-asr-nano.json Adds pinned golden logits + prompt input IDs for L4 prefill.
testdata/golden/speech/glm-asr-nano_generation.json Adds pinned golden generated tokens + transcript for L5 generation.
testdata/cases/speech/glm-asr-nano.yaml Adds L4+L5 speech-language case definition for GLM-ASR-Nano with exact_match.
testdata/cases/schema.json Extends generation schema with exact_match boolean flag.
src/mobius/tasks/_glmasr_speech_language.py Implements GLM-ASR three-model export task and audio encoder graph boundary casting/IO contract.
src/mobius/tasks/init.py Exports GlmAsrSpeechLanguageTask and registers the task name.
src/mobius/models/glm_asr.py Adds GLM-ASR model implementation (audio tower + projector, embedding mixer, cached decoder) and weight routing.
src/mobius/models/glm_asr_test.py Adds synthetic stage-by-stage parity tests vs HF for audio → embedding → decoder.
src/mobius/models/init.py Exports GlmAsrForConditionalGeneration from the models package.
src/mobius/integrations/ort_genai/genai_config.py Adds an explicit with_embedding() generator path for multimodal embedding stages.
src/mobius/integrations/ort_genai/auto_export.py Adds GLM-ASR audio processor emission, embedding stage metadata, and pinned-revision propagation for hub assets.
src/mobius/integrations/ort_genai/auto_export_test.py Adds tests for GLM-ASR processor/genai config emission and pinned-revision forwarding.
src/mobius/integrations/onnx_genai/inference_metadata.py Adds optional revision propagation for processor/runtime asset loading and diffusers scheduler config fetch.
src/mobius/integrations/onnx_genai/auto_export.py Propagates revision to tokenizer/audio processor emission and diffusers scheduler loading.
src/mobius/integrations/onnx_genai/auto_export_test.py Adds regression coverage for pinned-revision propagation to audio processor emission.
src/mobius/_registry.py Registers glmasr model type, default task mapping, and default example model ID.
src/mobius/_diffusers_builder.py Propagates pinned revision for optional diffusers metadata downloads.
src/mobius/_diffusers_builder_test.py Adds unit test ensuring optional diffusers metadata downloads use pinned revision.
src/mobius/_configs/per_model/_glmasr_audio.py Adds GLM-ASR audio_config extraction hook for nested composite configs.
src/mobius/_configs/per_model/init.py Registers the GLM-ASR audio config hook.
src/mobius/_configs/_sub_configs.py Extends AudioConfig with additional encoder-specific fields needed by GLM-ASR.
src/mobius/_configs/_base.py Introduces GlmAsrConfig with nested HF config unwrapping and dtype/audio_token_id handling.
src/mobius/_configs/init.py Exports GlmAsrConfig.
src/mobius/_configs_test.py Adds config extraction unit test for GLM-ASR nested audio/text config behavior.
src/mobius/main.py Adds CLI --revision and propagates it into diffusers detection/build and runtime metadata generation.
scripts/generate_golden.py Ensures hub calls use pinned revisions and stores reference logits as float32 for bf16 compatibility; adds GLM-ASR loading/input prep path.
README.md Lists GLM-ASR under supported Speech-to-Text architectures.
docs/cli_reference.md Documents the new --revision CLI option.
Suppressed comments (1)

src/mobius/models/glm_asr.py:251

  • GlmAsrMultiModalProjector hard-codes GELU and never consults GlmAsrConfig.projector_hidden_act. Since the config extracts and stores this field from HF (projector_hidden_act), ignoring it can produce an incorrect projector for non-default configs. Consider using get_activation(projector_hidden_act) to keep the ONNX graph faithful to the checkpoint config.
        merged = op.Reshape(
            audio_hidden_states,
            op.Constant(value_ints=[0, -1, self._projector_input_size]),
        )
        return self.linear_2(op, op.Gelu(self.linear_1(op, merged)))

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

Comment thread src/mobius/models/glm_asr.py Outdated
Comment thread src/mobius/models/glm_asr.py Outdated
justinchuby and others added 2 commits August 19, 2026 16:23
Implement the GLM-ASR audio encoder, projector, embedding mixer, and cached Llama decoder with pinned real-audio L4/L5 coverage. Add revision-pinned CLI/runtime metadata export and exact CUDA transcript validation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Honor configured audio and projector activations and emit single-output encoder Attention nodes. Release staged ORT sessions so large CUDA components do not retain device weights, and reject unsupported ORT GenAI metadata rather than producing an unloadable package. Clean up rebase regressions in Diffusers and CLI tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby
justinchuby force-pushed the justinchuby-add-glm-asr-nano branch from 9b538cf to 64c6e2d Compare August 20, 2026 00:33
@justinchuby

Copy link
Copy Markdown
Member Author

@copilot please update from main

Resolve conflicts where main independently added pinned HuggingFace
revision plumbing to the CLI, ORT-GenAI artifact export, and the staged
ORT session teardown. Keep main's revision kwarg handling, drop the
duplicated --revision argument and duplicated build() kwarg introduced by
the merge, and retain main's upstream asset patching.

Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: justinchuby <11205048+justinchuby@users.noreply.github.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ justinchuby
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

@copilot please update from main

Merged main (b2b8213) into this branch in 49ca0a4.

Conflicts were where main independently added pinned HF revision plumbing:

  • src/mobius/__main__.py — kept main's --revision argument and kwarg ordering; removed the duplicated --revision argparse option and duplicated build(..., revision=...) kwarg the merge produced.
  • src/mobius/integrations/ort_genai/auto_export.py — kept main's revision-kwargs style for config/tokenizer/processor downloads and retained main's apply_asset_patches() call alongside the GLM-ASR audio processor artifact.
  • src/mobius/_testing/ort_inference.py — merged both comments on the eager session teardown.

Post-merge validation: pytest tests/build_graph_test.py tests/cli_test.py src/ -k "not phi4mm and not apply_weights_unknown" → 4209 passed, 61 skipped; ruff format/check clean on the resolved files.

@justinchuby
justinchuby requested a lite review from Copilot August 21, 2026 04:35
@justinchuby
justinchuby merged commit 79b48bc into main Aug 21, 2026
16 of 23 checks passed
@justinchuby
justinchuby deleted the justinchuby-add-glm-asr-nano branch August 21, 2026 04:40

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

Copilot reviewed 32 out of 33 changed files in this pull request and generated no new comments.

Suppressed comments (2)

tests/build_graph_test.py:3170

  • The assertion intended to ensure the audio encoder’s opset-24 Attention nodes don’t request any optional inputs (mask / past KV) is incomplete: len(node.inputs) == 3 or node.inputs[3] is None doesn’t check the remaining optional inputs (e.g., past key/value), so a regression could slip through. Consider asserting that all inputs after the first 3 are None (e.g., all(inp is None for inp in node.inputs[3:])).
    src/mobius/integrations/ort_genai/auto_export_test.py:858
  • Test name is misleading: test_audio_glmasr_writes_whisper_feature_extraction_json asserts that _write_audio_processor_config() writes audio_processor.json (not feature_extraction.json). Renaming the test (and/or its docstring) would better reflect the file it validates and avoid confusion with the Gemma4 audio_feature_extraction.json path.
    def test_audio_glmasr_writes_whisper_feature_extraction_json(self, tmp_path):
        config = mock.MagicMock()
        config.audio = mock.MagicMock()
        config.model_type = "glmasr"

        path = _write_audio_processor_config(config, str(tmp_path))

        assert path is not None
        assert path.endswith("audio_processor.json")

justinchuby added a commit that referenced this pull request Aug 21, 2026
Rebasing 141 commits onto `79b48bc0` conflicted in the five files where this
branch's workflow migration overlaps the two new model integrations. The
conflicts were resolved in favour of this branch, which is right for the
metadata architecture and wrong for everything else main had just added, so
the endpoint was then checked against main hunk by hunk rather than trusted.

Every `def`/`class` main added was confirmed present, which surfaced what the
resolution had silently dropped. `--revision` was threaded through the asset
writers by #509 and did not survive: `_write_clip_tokenizer`,
`_write_hf_tokenizer` and `_copy_runtime_assets` lost the keyword from their
signatures while their bodies still referenced it, and
`write_onnx_genai_config` lost the parameter entirely. The last one is the
dangerous shape — it has `**kwargs`, so a pinned revision would have been
absorbed and ignored rather than raising, and the package would still build
and still validate while its tokenizer came from whatever the branch tip
happened to be. That is precisely the failure a pin exists to prevent.
Restored at all nine sites, plus `_write_text_runtime_assets`, which is this
branch's own helper and needed the same parameter to pass it on.

Also restored: audio-processor emission for speech-language packages, which
#509 added to the multimodal dispatch and this branch's rewrite of that branch
did not carry. Expressed as `_has_audio_encoder` rather than main's inline
`"audio_encoder" in pkg` so a package object without `keys()` is a False
rather than a TypeError.

Two of main's tests could not be taken verbatim.
`test_revision_is_forwarded_to_detection_and_build` was overwritten wholesale
by a test of this branch's and is restored byte-identically alongside it.
`test_dispatch_audio_only_multimodal_pipeline` asserts on `pipeline.models`,
the legacy composite ABI this branch replaces, so its still-relevant half —
that the revision reaches the feature extractor — is covered by a new test
against the workflow instead.

`test_runtime_onnx_genai_routes_vlm_through_workflow_emitter` pins the writer
call exactly and now expects the threaded `revision`.

Verified: 4580 passed (up from 4541; the increase is main's new tests now
running), 11/11 `validate_metadata` and 11/11 runtime conformance against
ONNX GenAI `6e2ddc78`, lintrunner clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: justinchuby <justinchuby@users.noreply.github.com>
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.

5 participants