[object Object]

← 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

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 →