Repository navigation
fix: close loader and LoRA follow-ups from #2438 and #2439 - #2446
Merged
Merged
Conversation
- kv_arch: map a Nemotron-H `layers_block_type` / `layer_types` array through the model's own block-name table (now `pub(crate)`), count `*` layers and take the layer count from the array length, so a transformers 5 config without `num_hidden_layers` gets an estimate and `linear_attention` is not counted as attention. - weights: move `optiq_vision_sidecar_path` into `mlxcel_core::weights` and drop exactly the declared OptiQ vision sidecar from the index shard list before validation, so OptiQ checkpoints resolve through their index instead of printing the stale-index warning. `loading::vlm` reuses the helper. - Muse Glimmer: split the marker check into a helper over token ids, keep the checkpoint's image and video ids on the text wrapper, and refuse marker tokens for the text-only load at both the worker and the VLM preparation sites. - LoRA fusion: cast both factors to f32 before the delta matmul, as the runtime and embedding paths do, so an integer pair no longer aborts the process. - Nemotron-H: build the four optional projections as absent only when their weight is absent and propagate a constructor error, so the runtime LoRA width error from `claim_linear` reaches the user.
…2445) Nemotron-H configs carry `time_step_limit: [0.0, Infinity]`, which serde rejects, so `estimate_kv_arch` reported the KV cache as unavailable for every such checkpoint and the `layer_types` support had no effect on a real one. Apply `sanitize_config_json`, as the model loader does.
Clippy `manual_contains` on the split marker helper.
#2445) Review follow-ups: read `config.json` with the loader's `Infinity` / `NaN` replacement so a Nemotron-H-based OptiQ config still declares its sidecar, and drop the declared sidecar only when it names a subdirectory path, since a top-level name is an ordinary shard the index path loads.
6 tasks
inureyes
added a commit
that referenced
this pull request
Oct 19, 2026
OptiQ conversions index their vision tensors to the sidecar `config.json` declares under `optiq/`. #2446 taught the loader to accept that entry, but three other checks still treated it as a missing or unsafe shard: the downloader classified a store copy as incomplete (an `--offline` run failed and a normal run re-downloaded), the WebUI catalog reported "safetensors index contains an unsafe shard path", and `weight_footprint_bytes` fell back to `model.safetensors` and left out the sidecar. `mlxcel_core::weights::declared_optiq_vision_sidecar` is now the one config read all four sites share. The downloader and the catalog check the declared sidecar at its relative path and report it missing by name when absent; undeclared subdirectory and traversal entries are still rejected. The catalog fingerprint now covers the sidecar. The footprint drops the entry before index validation and adds the sidecar header, since `metadata.total_size` omits it. On lfm2.5-vl-3b-optiq-4bit and muse-glimmer-30b-optiq-4bit, inspect reports 2811046368 and 23847540736 bytes (main: 1958475776 and 20003849216), offline store resolution succeeds, and the catalog marks both complete. The other 191 catalog entries are unchanged. Closes #2451
inureyes
added a commit
that referenced
this pull request
Oct 19, 2026
…oader (#2455) OptiQ conversions index their vision tensors to the sidecar that `config.json` `optiq_vision.sidecar` declares (`optiq/optiq_vision.safetensors`). #2446 made the loader accept that entry; three other checks still treated it as a missing or unsafe shard. On main a store copy of an OptiQ checkpoint classifies `Incomplete` (an `--offline` run fails, a normal run re-downloads), the WebUI catalog marks it incomplete with "safetensors index contains an unsafe shard path", and `mlxcel inspect` leaves the vision tower out of the weight footprint. - `mlxcel_core::weights::declared_optiq_vision_sidecar(dir)` is split out of #2446's private helper: the Infinity/NaN-tolerant config read plus `optiq_vision_sidecar_path` validation, subdirectory paths only. The loader's `collect_shard_paths` and the three sites below all call it. The read is capped at 16 MiB because the catalog calls it on directories it does not control. - Downloader `missing_indexed_shards`: the declared sidecar counts as present when `dir.join(sidecar)` is non-empty and is reported missing under its relative name otherwise. Undeclared subdirectory and traversal entries are still missing. Config is read only when the index names a multi-component entry. - WebUI catalog: `indexed_completeness` and `push_index_shard_facts` skip the unsafe-path refusal for the declared sidecar only, so completeness is decided by the shared rule above and the content fingerprint now covers the sidecar file. - `weight_footprint_bytes` drops the sidecar before index validation and adds its header bytes when the file exists. With a sidecar declared it sums the main shard headers instead of trusting `metadata.total_size`, which OptiQ writes without the sidecar (and a converter that counted it would otherwise be counted twice). Non-OptiQ checkpoints take exactly the old path. ## Verification (GB10, checkpoints under `~/models_archive/mlx`) | Check | main (fixes disabled) | this PR | |---|---|---| | `mlxcel inspect -m lfm2.5-vl-3b-optiq-4bit` | 1958475776 bytes | 2811046368 bytes | | `mlxcel inspect -m muse-glimmer-30b-optiq-4bit` | 20003849216 bytes | 23847540736 bytes | | `mlxcel-server --offline -m mlx-community/<name> --model-store-root <store>` (store entry for each OptiQ checkpoint) | "no cached snapshot" error | resolves from the store (LFM serves `/health`; Muse reaches model loading) | | `GET /ui-api/v1/catalog` with `--models-dir ~/models_archive/mlx --webui` | both OptiQ entries `complete: false`, unsafe shard path | both `complete: true`, no reason | The expected totals equal main header plus sidecar header, computed independently from the safetensors headers (sidecars 852570592 and 3843691520 bytes). Non-OptiQ checks are unchanged: the other 191 catalog entries have identical completeness and fingerprints, inspect footprints for gemma-3-4b-it-4bit, glm-4.5v-4bit (stale index), llama-3.2-1b-instruct-bf16 and qwen3.5-0.8b-optiq-4bit (no sidecar) match main, and gemma-3-4b-it-4bit still resolves offline from the store. Five new tests fail with their fix disabled and pass with it: completeness present/absent, catalog OptiQ layout, catalog fingerprint, footprint (including a `total_size` that already counts the sidecar, and a stale index), and the bounded config read. The two undeclared/traversal-path tests are guards and pass both ways. `downloader::`, `webui::catalog`, `execution::memory_estimate`, mlxcel-core `weights::`, clippy (`--lib --tests -D warnings`, main crate and mlxcel-core), fmt and the contract scripts pass locally. Closes #2451
8 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #2445. Five follow-ups left by #2438 and #2439, in one PR as the issue asks, plus one blocker found while verifying item 1 on a real checkpoint.
Changes
kv_archreads Nemotron-Hlayer_types(src/execution/kv_arch.rs). Formodel_typenemotron_hwithout ahybrid_override_pattern, thelayers_block_type/layer_typesarray is mapped throughNEMOTRON_H_BLOCK_NAMES(nowpub(crate)insrc/models/nemotron_h.rs, not copied),*entries are counted, and the layer count is the array length, the wayNemotronHConfig::post_initderives it.linear_attentionis anMlayer and is no longer counted by thecontains("attention")filter.estimate_kv_archparsedconfig.jsonwith plain serde, and Nemotron-H configs such asnvidia-nemotron-3-nano-30b-a3b-4bitcarrytime_step_limit: [0.0, Infinity], so the KV estimate was "unavailable" for them, pattern or not (main reportsKV cache: 0 bytes (unavailable ...)for that checkpoint). The file now goes throughsanitize_config_jsonfirst, as the model loader does. Without this, item 1 has no effect on a real checkpoint. This also makes the estimate available for any other config that carriesInfinity/NaN, which used to report 0 KV bytes.src/lib/mlxcel-core/src/weights.rs,src/loading/vlm.rs).optiq_vision_sidecar_pathmoves intomlxcel_core::weights(same validation: relative, normal components only,.safetensors), andloading::vlmreuses it.collect_shard_pathsdrops exactly the declared sidecar from the index's shard list beforevalidate_index_shards, and only when it names a subdirectory path (a top-level name is an ordinary shard). The config is read with the sameInfinity/NaNreplacement the model loader applies. An undeclared subdirectory entry, a malformed sidecar path or an unreadable config leaves the list unchanged, so those still fail validation and take the glob fallback as before.src/multimodal/muse_glimmer_runtime.rs,src/models/muse_glimmer.rs,src/loading/vlm_muse_glimmer.rs,src/server/model_worker.rs,src/multimodal/vlm_runtime.rs). The check is split into a helper over token ids.MuseGlimmerTextWrapperkeeps the checkpoint'simage_token_id/video_token_id(twoi32fields, set byload_muse_glimmer_text), andreject_muse_glimmer_text_only_markersruns at both sites forLoadedModel::MuseGlimmer. The error says the checkpoint is loaded text-only.src/lora/loader.rs).compute_lora_deltacasts both factors to f32 before the matmul, asruntime_lora(at claim) andsrc/lora/embedding.rsdo. The delta was already f32 because the scale is an f32 array, so for a floating-point pair only the matmul's input precision changes; for an f32 adapter (whatmlx_lm.lorawrites) nothing changes.src/models/nemotron_h.rs). The shared-expertup_proj/down_projand the two latent projections returnNoneonly when{prefix}.weightis absent; otherwise the constructor error is propagated with?, so theclaim_linearwidth error from fix(lora): pin runtime LoRA term widths on quantized linear bases #2438 is the load error instead of a later "unclaimed" failure.Tests
Each new test fails on the reverted fix (each fix reverted in place, test binary rebuilt, then restored):
execution::kv_arch::tests::nemotron_h_layer_types_match_the_equivalent_patternlayer_typesconfigexecution::kv_arch::tests::a_config_file_with_infinity_still_estimatesInfinity)weights::tests::test_collect_shard_paths_skips_only_the_declared_optiq_sidecar(mlxcel-core)left: ["model.safetensors", "stray.safetensors"], glob fallback takenloading::vlm::muse_glimmer::tests::text_only_muse_glimmer_refuses_image_and_video_markerslora::loader::tests::an_integer_lora_pair_fuses_like_the_same_values_in_f32[matmul] Only inexact types are supported but int32 and int32 were providedmodels::nemotron_h::tests::a_wrong_width_lora_term_on_an_optional_projection_fails_the_loadThe Nemotron-H test is
cfg(not(all(target_os = "macos", feature = "metal"))), since Metal refuses runtime LoRA for this model before any layer is built.Verification
GB10,
--features cuda,test-fastprofile, every GPU run undergpu-lock,MLXCEL_SDPA_DETERMINISTIC=1. "Main" is a binary built from 74f9ae1 in the same worktree; "fixed" is this branch.cargo test --profile test-fast --features cuda --lib -- kv_arch lora:: muse_glimmer nemotron_h loading::vlm multimodal:: model_worker memory_estimate --test-threads=1: 877 passed, 0 failed.cargo test --profile test-fast -p mlxcel-core --features cuda --lib -- weights:: runtime_lora --test-threads=1: 38 passed.cargo clippy --profile test-fast --features cuda --lib --tests -- -D warnings,cargo fmt --all -- --check, and the five contract checks (check_llama_compat_manifest,check_kernel_dtype_keys,check_kernel_port_dispatch,check_crate_versions,insert_apache_header --check): clean.Real checkpoints (
~/models_archive/mlx/...):nvidia-nemotron-3-nano-30b-a3b-4bit,mlxcel inspect -mKV cache: 0 bytes (unavailable ...)48.0 MiB (hybrid: 6 attention layer(s) hold KV)hybrid_override_patternrewritten aslayer_types,num_hidden_layersremoved),mlxcel inspect -m0 bytes (unavailable ...)48.0 MiB (hybrid: 6 attention layer(s) hold KV), identical to the pattern configlfm2.5-vl-3b-optiq-4bit,mlxcel generate, text prompt and--image tests/fixtures/test_image_shapes.pngmuse-glimmer-30b-optiq-4bit,mlxcel generate --image ... --show-reasoningmuse-glimmer-30b-optiq-4bit(top-level shards, index without the sidecar entries, nooptiq_vision; detected asMuseGlimmer), prompt"<|image|> What is in the picture?"Error: this Muse Glimmer checkpoint is loaded text-only (no vision tower), but the prompt contains image or video marker tokens ..., exit 1llama-3.2-1b-instruct-bf16(dense),mlxcel generate --adapter, q_proj adapter of layers 14 and 15 trained withmlx_lm.lora, factors rounded toround(x * 100)and stored as int32, scale divided by 1e4terminate called after throwing an instance of 'std::invalid_argument' ... [matmul] Only inexact types are supported, exit 134nvidia-nemotron-3-nano-30b-a3b-4bit(n_shared_experts: 1, so every MoE layer builds its shared experts through the new path),mlxcel generate --show-reasoning, 24 tokensThe CLI's media preparation goes through
prepare_request_vlm_embeddings, the server worker site, so the item 3 run exercises that call site. The unit test covers theprepare_vlm_embeddings_with_cachesite and the helper the worker calls; no unit test drivesprepare_request_vlm_embeddingsitself, so the worker arm is covered only by the real run.Not in this PR
Review found the same declared-sidecar entry treated as a bad shard in two other places, outside this issue's scope:
src/downloader/completeness.rs(missing_indexed_shardscountsoptiq/optiq_vision.safetensorsas missing, so a store-resolved OptiQ snapshot classifies as incomplete) andsrc/server/webui/catalog_fs.rs(reports it as an unsafe shard path).weight_footprint_bytesalso leaves the sidecar's bytes out of the memory preflight. Both can reusemlxcel_core::weights::optiq_vision_sidecar_path.