4.5 KiB
US-047 — Tracker-first model downloads: visibility, sane timeouts, RAM-based sizing
Status: in progress Priority: High (follow-up to US-044/US-046; blocks usable LAN downloads)
Context
Reported 2026-07-06 (Windows CPU node, 79.2 GB RAM, --tracker http://192.168.0.179:8080 --model Qwen3.6-35B-A3B):
- Startup prints
(auto-join unavailable: HTTP Error 503)even though the user explicitly named a model. The auto-join query (/v1/network/assign) never sends the requested model, so a fresh tracker + a caller too small for the recommended preset 503s (expected per US-046) — but the whole auto-join step is pointless when the user already picked a model: the/v1/nodes/assign?model=…call right after it succeeds (assigned layers 0–2 with trackermodel_sources). - The tracker-vs-HuggingFace race then starts, but only HuggingFace shows
progress (hf tqdm bars). The tracker tar download prints nothing and
swallows every failure (
except Exception: return None), so the node appears to download only from slow HF; the user killed it. Tracker-side log showed the tar stream reset mid-archive.add— with no way to tell whether the client timed out or the user aborted. _download_model_sourceinheritspeer_timeout(2.0 s) as its urlopen socket timeout. Any 2 s read stall during a multi-GB tar stream silently kills the tracker source and leaves HF as the only contender.- Every client abort spams the tracker console with a full
BrokenPipeError/ConnectionResetErrortraceback fromsocketserver.
Fix
startup.py: skip the network auto-join query entirely when a model was explicitly requested (modelset and not"stub-model"); path 3b (/v1/nodes/assign?model=…) is the authoritative one there.downloader.py: model-source downloads get their own timeout constant (30 s socket timeout) instead of the 2 s peer-probe timeout. Peer shard downloads keep 2 s — they run sequentially before the race, and a dead peer must not hang startup for 30 s; the race is concurrent so a slow source costs nothing.downloader.py: progress + failure visibility for the race —_download_model_sourceprints received bytes every 512 MB and prints the exception when a source fails, so "downloads only from HF" can never happen silently again.- Tracker
_handle_model_files_download: catchBrokenPipeError/ConnectionResetErroraround the tar stream and log a single line instead of a traceback.
Design revision (2026-07-06, after live retest)
The race is gone. User decision: HuggingFace is used only when the model is
not available from a tracker/peer source, or when --tracker-source-disabled
is passed. Sources are tried sequentially with progress + failure output;
HF (layer-filtered via the source file list, else the remote index) is the
fallback.
Second live finding: the node was assigned only layers 0–2 of 40 on a 79 GB
box. Cause: CPU-mode nodes still report the detected-but-unusable GPU's
vram_mb (RTX 4060 → 8192), and shard sizing used VRAM whenever it was > 0
(8 GB × 0.8 ≈ 6.5 GB ≈ 3 layers). Fixed on both sides: the node now sends
assignment_vram_mb (0 unless CUDA is actually usable) to /v1/nodes/assign,
and the tracker only trusts vram_mb when device=cuda (all three sizing
sites), falling back to ram_mb.
Acceptance criteria
- Node started with an explicit
--modelnever queries/v1/network/assignand never printsauto-join unavailable. - Tracker/peer model source is preferred outright; HF is contacted only
when no source is advertised, every source fails, or
--tracker-source-disabledis passed (flag on both CLI parsers, plumbed through config andrun_startup). - Tracker-source downloads print progress every 512 MB and print the exception + URL on failure; nothing fails silently.
- A ≥2 s read stall no longer aborts a tracker model-source download (30 s socket timeout).
- Client disconnect during
/v1/model-files/downloadlogs one line on the tracker, no traceback. - CPU node with big RAM gets a RAM-sized shard:
/v1/nodes/assignand both/v1/network/assignsizing paths ignore VRAM unlessdevice=cuda. pytest tests/test_node_startup.py tests/test_tracker_routing.pypasses (139/140; the one failure is the pre-existing port-dependenttest_mining_clicase, present on clean master).- Live two-machine retest: Windows node downloads only from tracker at LAN speed and is assigned a RAM-sized shard.