← back to Exo
Fix config.json download errors for image models (#1245)
8f6726d6be7b5e62ca27192bb71d3df020473c25 · 2026-01-21 21:30:48 +0000 · Alex Cheema
## Motivation
When `get_shard_download_status()` runs, it iterates over all models in
`MODEL_CARDS` and calls `build_full_shard()` → `build_base_shard()` →
`ModelCard.from_hf()`. This unconditionally tried to download
`config.json` from HuggingFace, but image models (FLUX, Qwen-Image)
don't have a root-level config.json file, causing errors:
```
Error downloading shard: File not found: https://huggingface.co/black-forest-labs/FLUX.1-dev/resolve/main/config.json
Error downloading shard: File not found: https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/config.json
Error downloading shard: File not found: https://huggingface.co/Qwen/Qwen-Image/resolve/main/config.json
Error downloading shard: File not found: https://huggingface.co/Qwen/Qwen-Image-Edit-2509/resolve/main/config.json
```
## Changes
### ModelCard.load() fix
- `build_base_shard()` now uses `ModelCard.load()` instead of
`ModelCard.from_hf()`
- `ModelCard.load()` iterates through `MODEL_CARDS.values()` to find a
match by `model_id`
### exo-bench fixes
- Use `name` field instead of `id` for model resolution
- Pass `full_model_id` to `/instance/previews` endpoint
- Make model name matching case-insensitive
- Update README example model name
## Why It Works
`MODEL_CARDS` uses short names as keys (e.g., `"flux1-schnell"`) but the
`model_id` values are HuggingFace paths (e.g.,
`"black-forest-labs/FLUX.1-schnell"`). When `ModelCard.load()` was
called with the HF path, it didn't match any key and fell back to
`from_hf()` which tried to download config.json.
The fix iterates through `MODEL_CARDS.values()` to find a match by
`model_id`, ensuring predefined models (including image models) use
their registry entries directly without network calls. A key lookup is
unnecessary since `load()` is always called with HF paths which don't
match the short-name keys.
## Test Plan
### Manual Testing
- Run exo and verify no more "Error downloading shard: File not found:
.../config.json" errors for image models
- Run exo-bench and verify model resolution works correctly
### Automated Testing
- `uv run basedpyright` - passes with 0 errors
- `uv run pytest` - all tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Files touched
M README.mdM bench/exo_bench.pyM src/exo/shared/models/model_cards.pyM src/exo/worker/download/impl_shard_downloader.py
Diff
commit 8f6726d6be7b5e62ca27192bb71d3df020473c25
Author: Alex Cheema <41707476+AlexCheema@users.noreply.github.com>
Date: Wed Jan 21 21:30:48 2026 +0000
Fix config.json download errors for image models (#1245)
## Motivation
When `get_shard_download_status()` runs, it iterates over all models in
`MODEL_CARDS` and calls `build_full_shard()` → `build_base_shard()` →
`ModelCard.from_hf()`. This unconditionally tried to download
`config.json` from HuggingFace, but image models (FLUX, Qwen-Image)
don't have a root-level config.json file, causing errors:
```
Error downloading shard: File not found: https://huggingface.co/black-forest-labs/FLUX.1-dev/resolve/main/config.json
Error downloading shard: File not found: https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/config.json
Error downloading shard: File not found: https://huggingface.co/Qwen/Qwen-Image/resolve/main/config.json
Error downloading shard: File not found: https://huggingface.co/Qwen/Qwen-Image-Edit-2509/resolve/main/config.json
```
## Changes
### ModelCard.load() fix
- `build_base_shard()` now uses `ModelCard.load()` instead of
`ModelCard.from_hf()`
- `ModelCard.load()` iterates through `MODEL_CARDS.values()` to find a
match by `model_id`
### exo-bench fixes
- Use `name` field instead of `id` for model resolution
- Pass `full_model_id` to `/instance/previews` endpoint
- Make model name matching case-insensitive
- Update README example model name
## Why It Works
`MODEL_CARDS` uses short names as keys (e.g., `"flux1-schnell"`) but the
`model_id` values are HuggingFace paths (e.g.,
`"black-forest-labs/FLUX.1-schnell"`). When `ModelCard.load()` was
called with the HF path, it didn't match any key and fell back to
`from_hf()` which tried to download config.json.
The fix iterates through `MODEL_CARDS.values()` to find a match by
`model_id`, ensuring predefined models (including image models) use
their registry entries directly without network calls. A key lookup is
unnecessary since `load()` is always called with HF paths which don't
match the short-name keys.
## Test Plan
### Manual Testing
- Run exo and verify no more "Error downloading shard: File not found:
.../config.json" errors for image models
- Run exo-bench and verify model resolution works correctly
### Automated Testing
- `uv run basedpyright` - passes with 0 errors
- `uv run pytest` - all tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
---
README.md | 4 ++--
bench/exo_bench.py | 10 +++++-----
src/exo/shared/models/model_cards.py | 5 +++--
src/exo/worker/download/impl_shard_downloader.py | 2 +-
4 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 8a4644cb..b2ab43b2 100644
--- a/README.md
+++ b/README.md
@@ -364,7 +364,7 @@ The `exo-bench` tool measures model prefill and token generation speed across di
```bash
uv run bench/exo_bench.py \
- --model llama-3.2-1b \
+ --model Llama-3.2-1B-Instruct-4bit \
--pp 128,256,512 \
--tg 128,256
```
@@ -385,7 +385,7 @@ uv run bench/exo_bench.py \
```bash
uv run bench/exo_bench.py \
- --model llama-3.2-1b \
+ --model Llama-3.2-1B-Instruct-4bit \
--pp 128,512 \
--tg 128 \
--max-nodes 2 \
diff --git a/bench/exo_bench.py b/bench/exo_bench.py
index 478ebf9e..c8bbba5c 100644
--- a/bench/exo_bench.py
+++ b/bench/exo_bench.py
@@ -195,14 +195,14 @@ def resolve_model_short_id(client: ExoClient, model_arg: str) -> tuple[str, str]
data = models.get("data") or []
for m in data:
- if m.get("id") == model_arg:
- short_id = str(m["id"])
- full_id = str(m.get("hugging_face_id") or m["id"])
+ if m.get("name").lower() == model_arg.lower():
+ short_id = str(m["name"])
+ full_id = str(m.get("hugging_face_id") or m["name"])
return short_id, full_id
for m in data:
if m.get("hugging_face_id") == model_arg:
- short_id = str(m["id"])
+ short_id = str(m["name"])
full_id = str(m["hugging_face_id"])
return short_id, full_id
@@ -373,7 +373,7 @@ def main() -> int:
short_id, full_model_id = resolve_model_short_id(client, args.model)
previews_resp = client.request_json(
- "GET", "/instance/previews", params={"model_id": short_id}
+ "GET", "/instance/previews", params={"model_id": full_model_id}
)
previews = previews_resp.get("previews") or []
diff --git a/src/exo/shared/models/model_cards.py b/src/exo/shared/models/model_cards.py
index c58f321a..7f8b5dde 100644
--- a/src/exo/shared/models/model_cards.py
+++ b/src/exo/shared/models/model_cards.py
@@ -59,8 +59,9 @@ class ModelCard(CamelCaseModel):
@staticmethod
async def load(model_id: ModelId) -> "ModelCard":
- if model_id in MODEL_CARDS:
- return MODEL_CARDS[model_id]
+ for card in MODEL_CARDS.values():
+ if card.model_id == model_id:
+ return card
return await ModelCard.from_hf(model_id)
@staticmethod
diff --git a/src/exo/worker/download/impl_shard_downloader.py b/src/exo/worker/download/impl_shard_downloader.py
index bf7eb6cf..bba76e38 100644
--- a/src/exo/worker/download/impl_shard_downloader.py
+++ b/src/exo/worker/download/impl_shard_downloader.py
@@ -19,7 +19,7 @@ def exo_shard_downloader(max_parallel_downloads: int = 8) -> ShardDownloader:
async def build_base_shard(model_id: ModelId) -> ShardMetadata:
- model_card = await ModelCard.from_hf(model_id)
+ model_card = await ModelCard.load(model_id)
return PipelineShardMetadata(
model_card=model_card,
device_rank=0,
← ede77921 Reduce log spam (#1241)
·
back to Exo
·
dashboard: show model total size on downloads page for pendi c9818c30 →