Repository navigation
feat(download): allow direct offline loading of complete model folders without internet - #2469
Open
presentyourlove wants to merge 1 commit into
Open
presentyourlove wants to merge 1 commit into
presentyourlove wants to merge 1 commit into
Conversation
…s without internet
cdvankammen
pushed a commit
to cdvankammen/exo
that referenced
this pull request
Oct 23, 2026
…I (exo#2470) Ports upstream exo-explore#2470 (aa9e120) onto the fork chain head a485ca9 (the exo-explore#2469 offline-loading port). Resolves TODO.md item 14. Applied as a clean cherry-pick: no conflicts, no fork-side rewrites of the touched hunks. Verified first that the base had none of the new symbols (git grep ip_to_interface_type / connectionCategory on a485ca9: both absent), so this is a pure addition rather than a divergent re-implementation. What the port does ------------------ Backend (src/exo/): - InterfaceType Literal widened from 5 to 8 values: adds tb5, rdma, virtual. - system_info.py splits the old macOS-only networksetup parser into _parse_networksetup_ports() (pure, testable) and adds get_heuristic_interface_type(), a cross-platform name-prefix classifier (tb5/tb/thunderbolt, ib/mlx -> rdma, docker/veth/br-/tun/... -> virtual, wlan/wl -> wifi, eth/en* -> ethernet). get_network_interfaces() now falls back to the heuristic whenever networksetup did not classify the iface, so Linux and Windows hosts get real types instead of "unknown". Dashboard (dashboard/src/): - app.svelte.ts carries interface type through the topology transform into a new ip_to_interface_type map, and classifies every edge into a connectionCategory (tb5 > tb > rdma > ethernet > wifi > virtual > unknown) using the per-node interface type, falling back to an IP heuristic (192.168.100.x / 169.254.x -> Thunderbolt bridge, else Ethernet). - TopologyGraph.svelte renders a themed, colour-coded badge pill at each edge midpoint with an SVG title tooltip listing every underlying connection, and styles the link itself (stroke colour/width/dash by category). - +page.svelte appends the interface type to the interface label tooltips. Verification ------------ - npm run build: exit 0. - npm run check: 15 errors on the changed tree AND 15 on the base worktree (a485ca9); the error-location sets are byte-identical (18 normalized file:line entries, comm diff empty both directions). No new diagnostics. - pytest src/exo/utils/info_gatherer: 6 passed on base, 8 passed on the changed tree (the 2 new upstream tests). Wiring control (upstream's tests are tautological on this hunk) --------------------------------------------------------------- Upstream's two tests only call get_heuristic_interface_type() and _parse_networksetup_ports() directly. Neither proves that get_network_interfaces() USES the heuristic -- and on Linux/Windows _get_interface_types_from_networksetup() returns {}, so without the wiring hunk every interface silently stays "unknown" while both tests still pass. Demonstrated with a harness that stubs psutil.net_if_addrs() to expose a single "tb5_0" iface and stubs the macOS port lookup to return {}: - changed tree: type == "tb5" (HEURISTIC_WIRED, exit 0) - wiring hunk reverted: type == "unknown" (HEURISTIC_NOT_WIRED, exit 1) ... and the upstream suite still reported 8 passed in that state. That is the negative control for this port: it names the one hunk upstream's tests cannot see. The file was restored from the index afterwards and the control re-run green before staging. Completeness: git diff against upstream/pr2470 for all six touched files is empty -- the port is byte-identical to upstream's tip.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR resolves item 8 in
TODO.md:If model folder exists, load directly without checking anything (allows using models without internet connection).Background & Motivation
Previously, Exo attempted to fetch the model's file list from HuggingFace even if the model was already fully downloaded locally. If internet was unavailable:
fetch_file_list_with_cachewould fall back to_build_file_list_from_local_directory._scan_model_directorystrictly required a*.safetensors.index.jsonfile..safetensors/.ggufweights, or copied/offline model folders lacking an explicit index JSON,_scan_model_directoryreturnedNone, triggering a fatalFileNotFoundError: No internet connection and no cached file list for <model_id>.This prevented users from running models in air-gapped environments, private subnets, or using pre-downloaded model folders.
Key Changes
Model Folder Readiness Detection (
is_local_model_folder_ready):src/exo/download/download_utils.pyto check that a model directory is non-empty, contains recognized model artifacts (.safetensors,.bin,.pt,.gguf,.onnx,config.json, etc.), and contains no incomplete download fragments (.partialfiles).has_partial_fileshelper to quickly detect in-progress or corrupted downloads.Standalone & Copied Directory Scanning (
_scan_model_directory):_scan_model_directoryinto two complementary branches:*.safetensors.index.jsonwhen present.FileListEntryobjects with actual file sizes via filesystem stats. If any.partialfile is encountered, scanning safely aborts (returningNone) to prevent loading half-downloaded weights.Offline File List Cache Persistence:
{model_id}--{revision}--file_list.jsonin the cache folder. Subsequent launches bypass filesystem re-scanning.Unit Tests (
src/exo/download/tests/test_offline_model_loading.py):test_has_partial_files: In-progress fragment detection.test_is_local_model_folder_ready: Validation of complete vs. empty/non-model directories.test_scan_model_directory_offline_copy_without_index: Correct file list assembly for index-less offline models.test_scan_model_directory_with_partial_file_fails: Safety rejection when partial downloads exist.test_fetch_file_list_with_cache_offline_fallback: End-to-end verification of offline loading without internet and cache generation.Verification
.venv/Scripts/pytest.exe src/exo/download/tests/test_offline_model_loading.py -v # 5 passed in 1.46s.venv/Scripts/pytest.exe src/exo/download/tests/test_model_dirs.py src/exo/download/tests/test_offline_mode.py src/exo/download/tests/test_safetensors_index.py src/exo/download/tests/test_offline_model_loading.py -v # 36 passed in 1.67s.venv/Scripts/ruff.exe check src/exo/download/tests/test_offline_model_loading.py # All checks passed!Related Issues
TODO.md(If model folder exists, load directly without checking anything (allows using models without internet connection))