Problem
hal0 assumes a single-file GGUF at every stage, so split GGUFs (model-00001-of-00003.gguf, produced by gguf-split and increasingly common for large HF uploads) can't be pulled, scanned, or registered through any supported path:
- Pull —
run_pull (src/hal0/registry/pull.py:935) builds a per-file manifest of exactly one main GGUF plus an optional mmproj sidecar (WS-11). There is no shard enumeration; pulling a sharded repo via the Add-by-HF modal downloads a single shard, which is unloadable alone.
- Scan —
_SHARD_RE = ^.+-\d{5}-of-\d{5}$ in src/hal0/registry/discover.py:84 makes _is_skippable (:158) drop every shard, so hal0 model scan never surfaces split GGUFs. The same rule filters HF-cache candidates in src/hal0/api/routes/models.py:503. The comment says lone shards aren't loadable — true for a lone shard, but llama.cpp loads a complete shard set from the first shard automatically.
- Metadata —
gguf_header.py / detect.py have no awareness of the GGUF split.count/split.no KVs, so even a hand-registered first shard reports the size/params of shard 1 only.
- Run — the slot manager passes a single
-m <path> to llama-server. This part already works for split models: llama.cpp auto-discovers -0000N-of- siblings in the same directory.
Current workarounds
gguf-split --merge into one file, then pull/scan/register normally (the layout the repo assumes).
- Download all shards into one directory and
hal0 model register <id> --path .../<name>-00001-of-0000N.gguf — POST /api/models does no shard check, and llama backends load it fine. Registry size/params metadata are wrong (first shard only), and FLM/NPU providers can't use it.
Proposed implementation
The pull manifest already supports multi-file jobs via PullFile(kind="model"|"mmproj"), so the plumbing mostly exists:
- Pull: when the selected HF file matches the shard pattern (or the repo file listing shows a
-00001-of- set for the chosen quant), enqueue all shards as PullFile(kind="shard") entries in one job — aggregate bytes_total keeps the SSE wire shape unchanged. Register the row pointing at shard 1; store aggregate size.
- Scan/discover: exempt
.gguf files matching -00001-of- from _is_skippable when their sibling set is complete (all N shards present in the directory); keep skipping shards 2..N and incomplete sets. Surface one candidate for the set.
- Metadata: read
split.count from the GGUF header; when present, sum sibling file sizes for the registry size_bytes and mark the row (e.g. shards: N) so the dashboard can display it.
- Guardrails:
model rm should remove the whole shard set (or warn); FLM/NPU capability routing should reject split models with a clear error instead of failing at load.
Ref: single-file assumption discussion while auditing the registry on 2026-07-11; llama.cpp shard auto-load verified as the existing behavior the run path already relies on.
🤖 Generated with Claude Code
Problem
hal0 assumes a single-file GGUF at every stage, so split GGUFs (
model-00001-of-00003.gguf, produced bygguf-splitand increasingly common for large HF uploads) can't be pulled, scanned, or registered through any supported path:run_pull(src/hal0/registry/pull.py:935) builds a per-file manifest of exactly one main GGUF plus an optionalmmprojsidecar (WS-11). There is no shard enumeration; pulling a sharded repo via the Add-by-HF modal downloads a single shard, which is unloadable alone._SHARD_RE = ^.+-\d{5}-of-\d{5}$insrc/hal0/registry/discover.py:84makes_is_skippable(:158) drop every shard, sohal0 model scannever surfaces split GGUFs. The same rule filters HF-cache candidates insrc/hal0/api/routes/models.py:503. The comment says lone shards aren't loadable — true for a lone shard, but llama.cpp loads a complete shard set from the first shard automatically.gguf_header.py/detect.pyhave no awareness of the GGUFsplit.count/split.noKVs, so even a hand-registered first shard reports the size/params of shard 1 only.-m <path>to llama-server. This part already works for split models: llama.cpp auto-discovers-0000N-of-siblings in the same directory.Current workarounds
gguf-split --mergeinto one file, then pull/scan/register normally (the layout the repo assumes).hal0 model register <id> --path .../<name>-00001-of-0000N.gguf— POST /api/models does no shard check, and llama backends load it fine. Registry size/params metadata are wrong (first shard only), and FLM/NPU providers can't use it.Proposed implementation
The pull manifest already supports multi-file jobs via
PullFile(kind="model"|"mmproj"), so the plumbing mostly exists:-00001-of-set for the chosen quant), enqueue all shards asPullFile(kind="shard")entries in one job — aggregatebytes_totalkeeps the SSE wire shape unchanged. Register the row pointing at shard 1; store aggregate size..gguffiles matching-00001-of-from_is_skippablewhen their sibling set is complete (allNshards present in the directory); keep skipping shards 2..N and incomplete sets. Surface one candidate for the set.split.countfrom the GGUF header; when present, sum sibling file sizes for the registrysize_bytesand mark the row (e.g.shards: N) so the dashboard can display it.model rmshould remove the whole shard set (or warn); FLM/NPU capability routing should reject split models with a clear error instead of failing at load.Ref: single-file assumption discussion while auditing the registry on 2026-07-11; llama.cpp shard auto-load verified as the existing behavior the run path already relies on.
🤖 Generated with Claude Code