Skip to content
Merged
14 changes: 7 additions & 7 deletions docs/api/build_from_gguf.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Support is capability-specific: graph import does not imply runtime packaging.

| Census | Total | Closure |
|---|---:|---|
| Architectures | 147 | graph verdicts: {'deferred': 55, 'rejected': 2, 'supported': 90}; importable: 89; quantized import: {'rejected': 31, 'supported': 116}; runtime: {'deferred': 142, 'rejected': 2, 'supported': 3} |
| Architectures | 148 | graph verdicts: {'deferred': 56, 'rejected': 2, 'supported': 90}; importable: 89; quantized import: {'rejected': 32, 'supported': 116}; runtime: {'deferred': 143, 'rejected': 2, 'supported': 3} |
| Active stored qtypes | 25 | 24 have an import route; 1 are explicitly deferred with no route |
| Serialized projector strings | 60 | {'graph-importable': 5, 'runtime-supported': 0} |
| Tokenizer pre identifiers | 87 | 56 semantic groups; route dispositions: {'deferred-compiled-semantics': 45, 'deferred-pinned-artifact-evidence': 12, 'validated-pinned-source': 30} |
Expand All @@ -33,12 +33,10 @@ payload conversion, emits one aggregate warning for lossy requantization, and
saves the typed result as `quantization_report.json`. Use
`keep_quantized=False` for explicit float import.

Storage and compute are separate: packed MatMulNBits initializers may use a
native custom op or a portable inline fallback with nibble unpack,
`DequantizeLinear`, and float `MatMul`. The fallback does not replace packed
initializers with dense float storage or promise a particular ORT kernel. Pass
`mmproj=` only for an evidenced multimodal sidecar. The CLI equivalent is
`mobius build model.gguf -o output`.
Packed MatMulNBits storage may use a native op or portable nibble unpack,
`DequantizeLinear`, and float `MatMul`; neither implies dense storage or a specific kernel.
Use `mmproj=` only for evidenced sidecars; CLI: `mobius build model.gguf -o output`.
Split shards validate siblings and ownership; Hub references reject partial downloads.

## API

Expand Down Expand Up @@ -87,6 +85,7 @@ remain machine-readable in `_route_census.py`; this table groups only shared nex
| `artifact-unavailable` | `tokenizer-artifact-replacement` | `tokenizer:gpt-4o`, `tokenizer:jina-v1-en` | replacement complete artifact; matching official tokenizer source |
| `artifact-unavailable` | `tokenizer-artifact-replacement` | `tokenizer:llama4` | replacement complete artifact; matching official tokenizer source; PR #652 |
| `dependency-or-runtime-abi-blocked` | `architecture-abi-dependencies` | `architecture:falcon-h1`, `architecture:granitehybrid`, `architecture:jamba`, `architecture:kimi-k3`, `architecture:kimi-linear`, `architecture:minimax-01`, `architecture:nemotron_h_moe`, `architecture:plamo2` | ORT GenAI heterogeneous-state schema (issue #605); stateful runtime package parity |
| `dependency-or-runtime-abi-blocked` | `architecture-abi-dependencies` | `architecture:qwen4exp` | dedicated graph topology; cache/state contract; synthetic parity |
| `dependency-or-runtime-abi-blocked` | `architecture-abi-dependencies` | `architecture:afmoe`, `architecture:arwkv7`, `architecture:bailingmoe3`, `architecture:chameleon`, `architecture:cogvlm`, `architecture:cohere2moe`, `architecture:deepseek2`, `architecture:deepseek2-ocr`, `architecture:deepseek32`, `architecture:deepseek4`, `architecture:gemma3n`, `architecture:gemma4-assistant`, `architecture:gpt-oss`, `architecture:granite_swa`, `architecture:graniteswitch`, `architecture:hunyuan_vl`, `architecture:hy_v3`, `architecture:laguna`, `architecture:llama4`, `architecture:mellum`, `architecture:mimo2`, `architecture:minimax-m3`, `architecture:mistral3`, `architecture:nanbeige`, `architecture:paddleocr`, `architecture:plamo3`, `architecture:pockettts`, `architecture:qwen3tts`, `architecture:qwen3vl`, `architecture:qwen3vlmoe`, `architecture:rwkv6`, `architecture:rwkv6qwen2`, `architecture:rwkv7`, `architecture:step35`, `architecture:wavtokenizer-dec` | exact metadata extraction; tensor closure; dedicated graph and parity |
| `dependency-or-runtime-abi-blocked` | `mtp-specialized-abi` | `mtp:bailingmoe3`, `mtp:cohere2moe`, `mtp:deepseek2`, `mtp:deepseek32`, `mtp:deepseek4`, `mtp:glm-dsa`, `mtp:hy_v3`, `mtp:mimo2`, `mtp:nemotron_h_moe`, `mtp:qwen35moe`, `mtp:qwen3next`, `mtp:step35` | specialized sidecar graph; routed/cache state ABI |
| `dependency-or-runtime-abi-blocked` | `tokenizer-compiled-semantics` | `tokenizer:afmoe`, `tokenizer:bloom`, `tokenizer:chameleon`, `tokenizer:codeshell`, `tokenizer:command-r`, `tokenizer:dbrx`, `tokenizer:deepseek-coder`, `tokenizer:deepseek-llm`, `tokenizer:deepseek-v3`, `tokenizer:default`, `tokenizer:exaone`, `tokenizer:exaone-moe`, `tokenizer:falcon`, `tokenizer:gpt3-finnish`, `tokenizer:granite-docling`, `tokenizer:granite-embed-multi-97m`, `tokenizer:grok-2`, `tokenizer:hunyuan`, `tokenizer:hunyuan-dense`, `tokenizer:jais`, `tokenizer:jais-2`, `tokenizer:joyai-llm`, `tokenizer:kimi-k2`, `tokenizer:laguna`, `tokenizer:megrez`, `tokenizer:mellum2`, `tokenizer:minerva-7b`, `tokenizer:minicpm5`, `tokenizer:minimax-m2`, `tokenizer:mpt`, `tokenizer:olmo`, `tokenizer:poro-chat`, `tokenizer:refact`, `tokenizer:sarvam-moe`, `tokenizer:seed-coder`, `tokenizer:smaug-bpe`, `tokenizer:solar-open`, `tokenizer:stablelm2`, `tokenizer:starcoder`, `tokenizer:superbpe`, `tokenizer:tekken`, `tokenizer:trillion`, `tokenizer:viking`, `tokenizer:whitespace`, `tokenizer:youtu` | compiled pinned llama.cpp oracle; dispatch-equivalence fixture |
Expand Down Expand Up @@ -275,6 +274,7 @@ Reason codes are concise user-facing categories; detailed architecture audits re
| `qwen3tts` | — | none (fails before config extraction) | not claimed | config=deferred; tensor_map=deferred; graph=deferred; runtime=deferred; quantized_import=supported | CONFIG_DEFERRED — The primary GGUF is only a transformed Qwen3-TTS talker backbone, not the existing Mobius Qwen3TTS conditional-generation or codec model. |
| `qwen3vl` | — | none (fails before config extraction) | exact-direct-loader-conditional-union | config=deferred; tensor_map=deferred; graph=deferred; runtime=deferred; quantized_import=supported | CONFIG_DEFERRED — Qwen3-VL text GGUF requires multimodal position IDs and an exact qwen3vl_merger clip companion, including deep-stack vision features and architecture-specific token placement. |
| `qwen3vlmoe` | — | none (fails before config extraction) | exact-direct-loader-conditional-union | config=deferred; tensor_map=deferred; graph=deferred; runtime=deferred; quantized_import=supported | CONFIG_DEFERRED — Qwen3-VL-MoE combines the Qwen3-VL multimodal position/token contract and merger sidecar with routed experts in the text backbone. |
| `qwen4exp` | — | header/config/tensor preflight only; model=`qwen4_exp_text`; tensor=`qwen4exp` | exact pinned 3-shard artifact header closure | config=supported; tensor_map=supported; graph=deferred; runtime=deferred; quantized_import=rejected | GRAPH_DEFERRED — Exact header/config/tensor preflight is implemented, but no executable graph import route is claimed. |
| `refact` | — | model=`refact`; module=`gguf_legacy`; tensor=`legacy_layernorm` | audited-direct-loader-conditional-union | config=supported; tensor_map=supported; graph=supported; runtime=deferred; quantized_import=rejected | RUNTIME_EVIDENCE_PENDING / FLOAT_IMPORT_ONLY — Import is narrowed to split, bias-free dense tensors with one KV head; loaded-but-unexecuted expert, RoPE-factor, and bias families are rejected. |
| `rnd1` | — | model=`llada`; module=`rnd1`; tensor=`llama`+`diffusion_fused_qkv`+`moe_qk_norm_extras`+`moe_extras` | not claimed | config=supported; tensor_map=supported; graph=supported; runtime=deferred; quantized_import=supported | RUNTIME_EVIDENCE_PENDING — Config extraction, suffix-exact tensor closure, masked-diffusion task dispatch, and synthetic full-sequence execution are covered, but no pinned real GGUF has passed independent Hugging Face/llama.cpp masked-step logit parity and deterministic multi-step generation parity. |
| `rwkv6` | — | none (fails before config extraction) | audited-direct-loader-conditional-union | config=deferred; tensor_map=deferred; graph=deferred; runtime=deferred; quantized_import=supported | CONFIG_DEFERRED — RWKV6 carries two F32 states per layer (two token-shift vectors and a per-head WKV matrix) and applies token-dependent exp(-exp(decay)), a time_first read-before-update term, per-head group norm, and cumulative rescale transforms. |
Expand Down
40 changes: 35 additions & 5 deletions docs/design/qwen4-exp-text-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Mobius implements the text decoder identified by Hugging Face
The implementation is pinned to:

- `Qwen/Qwen3.8-Flash-Next@f5d08274bafd880402bd16f5e3e6c514136ec06c`
- `unsloth/Qwen3.8-Flash-Next-FP8@41cc25fe32cc20053a59c89716196897580cddf6`
- `unsloth/Qwen3.8-Flash-Next-GGUF@d3bc75ee6ccef3efc1e228ec00a6cc2cdb1e2249`
- `huggingface/transformers@598d8ba8baaec7fec5a22da0e2844c7bf4ea20e1`

Exported models record that pin as `mobius.semantic_reference_revision` and
Expand Down Expand Up @@ -55,8 +57,36 @@ Configurations with dedicated MTP embeddings fail closed because omitting
those embeddings could change the decoder contract. A future standalone
NextN sidecar requires an authoritative execution equation and cache ABI.

FP8/NVFP4 checkpoint lowering, GGUF import, and the multimodal wrapper are
outside this text-core implementation. The nested `qwen4_exp_text`
configuration and architecture registration are present so a later
multimodal wrapper can reuse the existing Qwen3/Qwen3.5 vision and embedding
components without aliasing this decoder.
FP8/NVFP4 checkpoint lowering and the multimodal wrapper remain outside this
text-core implementation. The nested `qwen4_exp_text` configuration and
architecture registration are present so a later multimodal wrapper can reuse
the existing Qwen3/Qwen3.5 vision and embedding components without aliasing
this decoder.

## GGUF header support and payload guard

The pinned GGUF is a text-only `general.architecture=qwen4exp` split set:

| Shard | Tensors | Bytes | LFS SHA-256 |
|---|---:|---:|---|
| `UD-IQ1_S/Qwen3.8-Flash-Next-UD-IQ1_S-00001-of-00003.gguf` | 0 | 10,946,624 | `88a1420825a9304063e882ada29d438263617f51ac8923d438d927496693bafd` |
| `UD-IQ1_S/Qwen3.8-Flash-Next-UD-IQ1_S-00002-of-00003.gguf` | 595 | 49,990,818,368 | `3a62e35bbf9add4733bd1438ebd3a67649d5edd6cb0e72bb78e33c913992b2b6` |
| `UD-IQ1_S/Qwen3.8-Flash-Next-UD-IQ1_S-00003-of-00003.gguf` | 629 | 22,544,696,352 | `0e25ceaeb89b8a80aa973c6c0c7448943682f7408c2855b2ebd016b7643a861a` |

Shard 0 owns all model/tokenizer metadata and no tensors. The importer
inherits that metadata across the complete set and requires the exact
`0 + 595 + 629 = 1224` closure. Header validation covers every
hyper-connection, PLE, QSA/indexer, DeltaNet, routed/shared expert, and final
output mixer tensor. GGUF's split indexer query/key matrices are concatenated
row-wise into Hugging Face's fused `index_qk_proj`; they are not Q/K-permuted.

Payload conversion deliberately fails before Hub download. The combined PLE
table is an enormous IQ4_NL embedding for which the graph has no compatible
native gather ABI. Routed experts are rank-3 banks with IQ1_S gate/up and
IQ4_NL down tensors, while the released runtime has neither a mixed-format
sparse native-block MoE ABI nor real-weight execution evidence. Treating these
as ordinary affine `MatMulNBits` would be incorrect. Explicit float
dequantization is also rejected because the PLE table alone expands beyond the
bounded single-tensor materialization policy. The exact header/config/mapping
support is therefore a fail-closed foundation for future runtime ABI work, not
a quantized execution claim.
4 changes: 4 additions & 0 deletions src/mobius/integrations/gguf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

# Text-only model
pkg = build_from_gguf("path/to/model.gguf")
# Any local shard discovers and validates its exact sibling set.
pkg = build_from_gguf("path/to/model-00002-of-00003.gguf")
# Hub shard references resolve and pin the complete immutable set.
pkg = build_from_gguf("owner/repo@commit:model-00002-of-00003.gguf")
# Quantized target storage is used by default with a fidelity report;
# pass keep_quantized=False for explicitly reported float storage.

Expand Down
57 changes: 54 additions & 3 deletions src/mobius/integrations/gguf/_arch_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,56 @@
runtime=Support.DEFERRED,
reason=_RECURRENT_RUNTIME_VALIDATION_PENDING,
),
GGUFArchitectureSpec(
gguf_arch="qwen4exp",
model_type="qwen4_exp_text",
tensor_map_recipe=("qwen4exp",),
config_key_map="qwen4exp",
config_postprocessor="qwen4exp",
graph=Support.DEFERRED,
preflight_only=True,
offset_norm=True,
v_head_reorder=True,
required_metadata=(
"attention.head_count_kv",
"attention.key_length",
"attention.layer_norm_rms_epsilon",
"attention.compress_ratios",
"attention.indexer.head_count",
"attention.indexer.key_length",
"attention.indexer.top_k",
"expert_count",
"expert_used_count",
"expert_feed_forward_length",
"expert_shared_feed_forward_length",
"hyper_connection.count",
"hyper_connection.low_rank",
"ple.layers",
"ple.ngram_size",
"ple.heads_per_ngram",
"ple.conv_kernel",
"ple.eos_token_id",
"ple.layer_multipliers",
"ple.head_offsets",
"ple.head_vocab_sizes",
"embedding_length_per_layer_input",
"rope.dimension_sections",
"ssm.conv_kernel",
"ssm.group_count",
"ssm.inner_size",
"ssm.state_size",
"ssm.time_step_rank",
),
quantized_import=Support.REJECTED,
runtime=Support.DEFERRED,
reason=(
"Exact header/config/tensor preflight is implemented, but no executable "
"graph import route is claimed. The published UD-IQ1_S "
"payload is rejected: its IQ4_NL PLE table has no native gather ABI, "
"mixed IQ1_S/IQ4_NL rank-3 experts have no released mixed sparse-MoE "
"ABI/runtime evidence, and float dequantization exceeds bounded memory."
),
),
# ---------------------------------------------------------------- Gemma
GGUFArchitectureSpec(
gguf_arch="gemma",
Expand Down Expand Up @@ -2400,9 +2450,10 @@ def _unknown_architecture_message(architecture: str) -> str:
prefix = f"Unsupported GGUF architecture: {architecture!r}."
if upstream is None:
return (
f"{prefix} It is not among the {len(upstream_architectures())} "
"architectures llama.cpp defines at the pinned commit, so the file is "
"either newer than this build of mobius or not a llama.cpp GGUF. "
f"{prefix} It is not among the {len(upstream_architectures())} pinned "
"GGUF architecture formats (147 from the llama.cpp census plus explicit "
"post-census artifact pins), so the file is either newer than this build "
"of mobius or not a recognized pinned GGUF. "
f"Supported: {', '.join(supported_architectures())}."
)
if not upstream.cpp_loader:
Expand Down
16 changes: 11 additions & 5 deletions src/mobius/integrations/gguf/_arch_registry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ def test_a_mappable_architecture_is_also_configurable_and_buildable(self, spec)
f"{spec.gguf_arch}: tensor mapping is supported but config extraction "
f"is {spec.config.value}"
)
if spec.preflight_only:
assert spec.graph is not Support.SUPPORTED
return
assert spec.graph is Support.SUPPORTED, (
f"{spec.gguf_arch}: tensor mapping is supported but graph construction "
f"is {spec.graph.value}"
Expand Down Expand Up @@ -417,9 +420,9 @@ def test_canonical_names_are_real_upstream_architectures(self, spec) -> None:
went unmapped or were unreachable.
"""
assert spec.gguf_arch in upstream_architectures(), (
f"{spec.gguf_arch!r} is not one of the 147 architectures llama.cpp "
"defines at the pinned commit. If it is a defensive spelling, declare "
"it in `aliases` instead of as the canonical name."
f"{spec.gguf_arch!r} is not one of the 148 pinned GGUF formats. "
"If it is a defensive spelling, declare it in `aliases` instead of "
"as the canonical name."
)

def test_aliases_do_not_collide(self) -> None:
Expand All @@ -434,7 +437,7 @@ def test_aliases_do_not_collide(self) -> None:
def test_registry_exactly_closes_the_pinned_census(self) -> None:
specs = iter_arch_specs()
_validate_census_closure(specs, frozenset(upstream_architectures()))
assert len(specs) == 147
assert len(specs) == 148
assert {spec.gguf_arch for spec in specs} == set(upstream_architectures())

@pytest.mark.parametrize(
Expand Down Expand Up @@ -1609,7 +1612,10 @@ def test_an_unimported_upstream_architecture_names_its_cohort(self) -> None:
get_arch_spec("rwkv6")

def test_an_unknown_architecture_is_distinguished_from_an_upstream_one(self) -> None:
with pytest.raises(UnsupportedGGUFArchitectureError, match="not among the 147"):
with pytest.raises(
UnsupportedGGUFArchitectureError,
match="not among the 148 pinned",
):
get_arch_spec("definitely-not-real")

def test_legacy_exception_types_still_catch_everything(self) -> None:
Expand Down
Loading
Loading