Skip to content

fix: detect hand-copied models in the cache root (#110) - #116

Open
solderzzc wants to merge 1 commit into
mainfrom
claude/issue-110-model-discovery
Open

fix: detect hand-copied models in the cache root (#110)#116
solderzzc wants to merge 1 commit into
mainfrom
claude/issue-110-model-discovery

Conversation

@solderzzc

Copy link
Copy Markdown
Member

Fixes #110.

Root cause

ModelStorage.scanDownloadedModels() accepted only two layouts under the cache root:

  • models--org--name/snapshots/<hash>/ — the huggingface-cli cache layout, which additionally needs refs/main, a snapshots/main, or exactly one snapshot directory to resolve
  • models/org/name/ — Swift Hub's materialized layout

Copying a model folder in by hand produces neither. The folder was skipped by the scan entirely, or reached isDownloaded() and failed because resolvedSnapshotDirectory() returned nil — either way it silently did not appear, with nothing logged to say why.

Changes

Accept the layouts users actually produce. In addition to the two above:

  • models--org--name/ with the weights directly inside and no snapshots/ level
  • org/name/ or a bare name/ folder copied straight into the cache root

Honour HF_HUB_CACHE. Only HF_HOME was read. Precedence now matches huggingface_hub: HF_HUB_CACHE (the hub directory itself), then HF_HOME/hub, then ~/.cache/huggingface/hub.

Fix snapshotDirectory(). It returned a synthesised …/snapshots/main path that does not exist for a hand-copied model. InferenceEngine.swift:370 hands that path to SSD expert streaming, so a detected-but-unresolvable model would have misconfigured the streaming reader instead of failing loudly. It now resolves through every supported layout and falls back to the hub path only when nothing exists.

Explain rejections. diagnoseUnrecognizedDirectories() reports folders that contain a config.json but did not make it into the scan, with the reason — missing weights, sharded weights with no model.safetensors.index.json, or leftover .incomplete files. ModelDownloadManager.refresh() logs these once per distinct set, so repeated refreshes stay quiet.

Rescan on view appear. refresh() ran only at init, after a download, and after a delete. A folder copied in while the app was open stayed invisible until relaunch.

Deletion safety. delete() walks the directories associated with an id, so a hand-copied path is included only when it actually exists, and an empty or "models" id can no longer resolve to the cache root or the layout wrapper. Covered by tests.

Also corrects two stale comments: the file header named ~/Library/Caches/huggingface/hub (wrong directory), and scanDownloadedModels() claimed to filter by ModelCatalog.all, which it does not do.

Tests

New tests/SwiftBuddyTests/ModelStorageLayoutTests.swift — 17 tests building real directory trees under a temporary cache root (ModelStorage.cacheRootOverride, added as a test hook): every accepted layout including sharded, the rejections that must stay rejected, the diagnostic reasons, snapshotDirectory resolution per layout, and deletion safety.

Also verified against a real 622MB model folder copied into a cache root exactly as the report describes (cp -RL <snapshot> <hub>/Qwen3.5-0.8B-oQ6): detected at the right size, config.json readable, max context length parsed, snapshotDirectory resolving to the copied folder, and no spurious diagnostics.

Full suite: 129 tests across 10 suites, 0 failures. PromptCacheTests and ModelLifecycleTests abort under swift test with Failed to load the default metallib — reproduced at HEAD without these changes, so pre-existing and environmental; suites were run individually.

What is not verified here

The fix is verified at the ModelStorage layer, not through the SwiftBuddy UI — loading a model end-to-end in a test needs the Metal library the test bundle cannot find. The reporter's exact directory layout is also still unknown; this covers the plausible shapes rather than a confirmed one, and the new diagnostics mean the next such report arrives with the reason attached.

🤖 Generated with Claude Code

The scan accepted only two layouts: `models--org--name/snapshots/<hash>/`
(huggingface-cli) and `models/org/name/` (Swift Hub). A user who copies a
model folder into the cache by hand — the case in the report — produces
neither, so the app silently refused to list it with no indication why.

- Accept two further layouts: a `models--org--name` folder whose weights sit
  directly inside with no `snapshots/` level, and a folder copied in without
  the `models--` prefix at all (`org/name/` or bare `name/`).
- Honour `HF_HUB_CACHE`, which huggingface-cli respects and this did not.
  Precedence now matches huggingface_hub: HF_HUB_CACHE, then HF_HOME/hub,
  then ~/.cache/huggingface/hub.
- `snapshotDirectory()` now resolves through every supported layout. It
  previously returned a synthesised `…/snapshots/main` path that does not
  exist for a hand-copied model, which would have pointed SSD expert
  streaming at a missing directory rather than failing loudly.
- Add `diagnoseUnrecognizedDirectories()`, reported once per distinct set
  from `ModelDownloadManager.refresh()`, so a folder that looks like a model
  but fails verification says why (missing weights, missing shard index,
  leftover .incomplete files) instead of vanishing.
- Refresh on ModelsView appear. refresh() ran only at init, after a download
  and after a delete, so a folder copied in while the app was open stayed
  invisible until relaunch.
- `delete()` only walks a hand-copied path that actually exists, and an empty
  or "models" id no longer resolves to the cache root.
- Fix two stale comments: the header named the wrong cache directory, and
  scanDownloadedModels claimed a ModelCatalog filter it does not apply.

Tests build real directory trees under a temporary cache root and cover every
accepted layout, the rejections that must stay rejected, the diagnostics, and
snapshotDirectory resolution. Also verified against a real 622MB model folder
copied into a cache root exactly as described in the report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mac app not recogonising already downladed models(not via swiftlm)

1 participant