Repository navigation
fix(models): discover safetensors shards from the index - #819
Conversation
jason-fxz
left a comment
There was a problem hiding this comment.
Thanks for the fix. I checked it against the nvidia/Qwen3.8-Flash-Next-FP4-Local index (all 21 shards under tensors/). The unit tests pass, and the subdirectory test fails without the change.
Two requests:
-
Please read the index directly in
iter_weight_filesinstead of callingsafetensors_weight_map, and use it whenever it exists:def iter_weight_files(model_path: str) -> list[str]: model_folder = download_hf_weight(model_path) index = os.path.join(model_folder, "model.safetensors.index.json") if os.path.exists(index): with open(index, encoding="utf-8") as f: shards = sorted(set(json.load(f)["weight_map"].values())) return [os.path.join(model_folder, shard) for shard in shards] files = glob.glob(f"{model_folder}/*.safetensors") return [f for f in files if not f.endswith("consolidated.safetensors")] or files
safetensors_weight_mapcallsiter_weight_fileswhen there is no index, so with the current fallback the two functions call each other. It only stops because both check the same file. Taking the index first keeps the dependency one way. It also matchesdownload_hf_weight, which already downloads only the shards the index names, and it covers checkpoints that split shards between the root and a subdirectory.test_iter_weight_files_prefers_root_shards_over_the_indexthen needs to flip: a root file that the index does not list is not loaded. -
_host_ram_fits_parallelinpython/freetoken/moe/expert_banks.pyalso globs only the root. For this layout it finds no files and returnsTrue, so the low-RAM fallback to the serial expert reader never fires. Please useiter_weight_files(model_path)there too.
The index is read directly so safetensors_weight_map stays the only header-based reader, and _host_ram_fits_parallel sizes the shards the reader will actually open, so the low-RAM serial fallback fires again for subdirectory layouts.
|
Both addressed in 4005264:
|
Share one index parser between iter_weight_files and safetensors_weight_map instead of a third copy; warn and glob the root when the index is broken, absent or empty; reject index keys that escape the model folder; let the hub download fetch the index itself; tell the truth in the diagnose docstring.
|
Follow-up in ad95ce2, from an independent review pass of my own diff:
Server: 15 passed in tests/models/test_models_loader.py, RAM test 1 passed; the three new tests fail on 4005264 and pass on ad95ce2. |
…points that keep shards in a subdirectory
…g#819 subdirectory test Assisted-by: Claude Opus 5.5
jason-fxz
left a comment
There was a problem hiding this comment.
Thanks, 4005264 is what I was after: the index decides when it exists, and the RAM probe sizes the same files.
Could you revert ad95ce2 and keep only test_iter_weight_files_falls_back_to_root_shards_without_an_index? That test is worth having: once the root-precedence test flipped, nothing else covered the no-index glob and the consolidated.safetensors rule.
The rest is more defensive than this loader needs. A broken index is a broken checkpoint, so a plain error is clearer than a warning and a guess from the root glob, and index paths come from a checkpoint we already trust. The fallback also misses a few shapes (a list at the top level, or a list as weight_map), and the escape check covers iter_weight_files only, not the safetensors_weight_map readers. Rather than complete both, I'd rather keep the PR to the one rule: use the index if there is one, otherwise glob the root.
After that this is ready to merge.
…dex test Per review: a broken index is a broken checkpoint, so the loader keeps the plain one rule -- use the index if there is one, otherwise glob the root. The no-index fallback test stays as the only coverage of the root glob and the consolidated.safetensors rule.
|
Reverted ad95ce2 in 00b855c, keeping only Server re-verified on 00b855c: tests/models/test_models_loader.py 13 passed (the kept test included), RAM probe test 1 passed. |
Problem
iter_weight_files()only globs*.safetensorsat the top level of the model folder. A checkpoint that ships a rootmodel.safetensors.index.jsonwhoseweight_mappoints the shards into a subdirectory (e.g.tensors/model-00000-of-00021.safetensors) resolves to zero weight files: the dense scan silently loads nothing and the failure surfaces much later as an unrelated error.Fix
When the top-level glob comes back empty, resolve the shard list from the root index's
weight_map(sorted for a deterministic load order, deduplicated, joined against the folder). The fast path (flat directory) is unchanged.Fixes #818
Tested
tests/models/test_models_loader.py(12 cases, includes the subdirectory layout).nvidia/Qwen3.8-Flash-Next-FP4-Local,ft serve --model ~/models/NVIDIA-Qwen3.8-Flash-Next-FP4-Local/ --moe-strategy offload --moe-cache-auto.