mirror of
https://github.com/exo-explore/exo.git
synced 2026-09-08 19:41:32 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21a54c5ea0 | ||
|
|
b5375f8cee | ||
|
|
cdf1add867 |
No files matched your search
@@ -29,6 +29,7 @@ To run EXO from source:
|
||||
git clone https://github.com/exo-explore/exo.git
|
||||
cd exo/dashboard
|
||||
npm install && npm run build && cd ..
|
||||
uv sync --extra mlx
|
||||
uv run exo
|
||||
```
|
||||
|
||||
|
||||
Generated
+29
-54
@@ -1007,7 +1007,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"zenoh",
|
||||
"zenoh-ext",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1843,12 +1842,6 @@ dependencies = [
|
||||
"spin 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
@@ -5114,7 +5107,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"arc-swap",
|
||||
@@ -5164,7 +5157,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-buffers"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"zenoh-collections",
|
||||
]
|
||||
@@ -5172,7 +5165,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-codec"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"uhlc",
|
||||
@@ -5183,7 +5176,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-collections"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
@@ -5191,7 +5184,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-config"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"json5",
|
||||
"nonempty-collections",
|
||||
@@ -5216,7 +5209,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-core"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"tokio",
|
||||
@@ -5227,7 +5220,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-crypto"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"hmac",
|
||||
@@ -5237,29 +5230,10 @@ dependencies = [
|
||||
"zenoh-result",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zenoh-ext"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"flume",
|
||||
"futures",
|
||||
"leb128",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uhlc",
|
||||
"zenoh",
|
||||
"zenoh-macros",
|
||||
"zenoh-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zenoh-keyexpr"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"hashbrown 0.16.1",
|
||||
@@ -5274,7 +5248,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"zenoh-config",
|
||||
"zenoh-link-commons",
|
||||
@@ -5292,7 +5266,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-commons"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
@@ -5328,7 +5302,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-quic"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"rustls-webpki",
|
||||
@@ -5344,7 +5318,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-quic_datagram"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"rustls-webpki",
|
||||
@@ -5360,7 +5334,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-tcp"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"socket2 0.5.10",
|
||||
@@ -5377,7 +5351,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-tls"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
@@ -5406,7 +5380,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-udp"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"libc",
|
||||
@@ -5428,7 +5402,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-unixsock_stream"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"nix 0.29.0",
|
||||
@@ -5446,7 +5420,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-link-ws"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-util",
|
||||
@@ -5466,7 +5440,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-macros"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5477,7 +5451,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-plugin-storage-manager"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d9fd55c490af4dc4abb6d51b620ecf6f824a9285a1f9446b59eeec6d66ccf7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bincode",
|
||||
@@ -5504,7 +5479,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-plugin-trait"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"git-version",
|
||||
"libloading",
|
||||
@@ -5521,7 +5496,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-protocol"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"rand 0.8.6",
|
||||
@@ -5536,7 +5511,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-result"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
]
|
||||
@@ -5544,7 +5519,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-runtime"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"ron",
|
||||
@@ -5558,7 +5533,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-sync"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"event-listener",
|
||||
@@ -5572,7 +5547,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-task"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"tokio",
|
||||
@@ -5585,7 +5560,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-transport"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"crossbeam-utils",
|
||||
@@ -5619,7 +5594,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh-util"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"const_format",
|
||||
@@ -5645,7 +5620,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zenoh_backend_traits"
|
||||
version = "1.9.0"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#2fed58ef264a8415b29452cc37054b2e04c10396"
|
||||
source = "git+https://github.com/evanev7/zenoh.git?branch=exo#aa219b84e55dfbbf55ebd827b492626a470b8ac8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"const_format",
|
||||
|
||||
@@ -49,7 +49,6 @@ env_logger = "0.11.10"
|
||||
|
||||
# networking
|
||||
zenoh = "=1.9.0"
|
||||
zenoh-ext = "=1.9.0"
|
||||
zenoh-plugin-storage-manager = { version = "=1.9.0", default-features = false }
|
||||
zenoh-plugin-trait = "=1.9.0"
|
||||
netwatcher = "0.6.0"
|
||||
@@ -62,7 +61,6 @@ pidfile-rs = { git = "https://github.com/AndreiCravtov/pidfile-rs" }
|
||||
|
||||
[patch.crates-io]
|
||||
zenoh = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-ext = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-buffers = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-codec = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-collections = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
@@ -88,7 +86,6 @@ zenoh-sync = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-task = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-transport = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-util = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh-plugin-storage-manager = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
zenoh_backend_traits = { git = "https://github.com/evanev7/zenoh.git", branch = "exo" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
@@ -118,7 +118,7 @@ Then restart the Nix daemon: `sudo launchctl kickstart -k system/org.nixos.nix-d
|
||||
--force
|
||||
```
|
||||
|
||||
Clone the repo, build the dashboard, and run exo:
|
||||
Clone the repo, build the dashboard, install the dependencies, and run exo:
|
||||
|
||||
```bash
|
||||
# Clone exo
|
||||
@@ -127,6 +127,9 @@ git clone https://github.com/exo-explore/exo
|
||||
# Build dashboard
|
||||
cd exo/dashboard && npm install && npm run build && cd ..
|
||||
|
||||
# Install Python dependencies, including the MLX backend
|
||||
uv sync --extra mlx
|
||||
|
||||
# Run exo
|
||||
uv run exo
|
||||
```
|
||||
@@ -176,7 +179,7 @@ rustup toolchain install nightly
|
||||
|
||||
**Note:** The `macmon` package is macOS-only and not required for Linux.
|
||||
|
||||
Clone the repo, build the dashboard, and run exo:
|
||||
Clone the repo, build the dashboard, install the dependencies, and run exo:
|
||||
|
||||
```bash
|
||||
# Clone exo
|
||||
@@ -185,6 +188,10 @@ git clone https://github.com/exo-explore/exo
|
||||
# Build dashboard
|
||||
cd exo/dashboard && npm install && npm run build && cd ..
|
||||
|
||||
# Install Python dependencies with the MLX backend for your hardware
|
||||
# (NVIDIA: --extra mlx-cuda13 or --extra mlx-cuda12)
|
||||
uv sync --extra mlx-cpu
|
||||
|
||||
# Run exo
|
||||
uv run exo
|
||||
```
|
||||
|
||||
Generated
+4
-3
@@ -8,6 +8,7 @@
|
||||
"name": "exo-dashboard",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"devalue": "^5.6.2",
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.27",
|
||||
"marked": "^17.0.1",
|
||||
@@ -2331,9 +2332,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz",
|
||||
"integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
|
||||
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.48.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
@@ -20,6 +18,8 @@
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/node": "^22",
|
||||
"d3": "^7.9.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
@@ -28,6 +28,7 @@
|
||||
"vite": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"devalue": "^5.6.2",
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.27",
|
||||
"marked": "^17.0.1",
|
||||
|
||||
@@ -16,10 +16,10 @@ check:
|
||||
uv run basedpyright --project pyproject.toml
|
||||
|
||||
sync:
|
||||
uv sync --all-packages
|
||||
uv sync --all-packages --extra mlx
|
||||
|
||||
sync-clean:
|
||||
uv sync --all-packages --force-reinstall --no-cache
|
||||
uv sync --all-packages --extra mlx --force-reinstall --no-cache
|
||||
|
||||
rust-rebuild:
|
||||
PYO3_PYTHON="$(uv run python -c 'import sys; print(sys.executable)')" cargo run --bin stub_gen
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
model_id = "moonshotai/Kimi-K2.7-Code"
|
||||
n_layers = 61
|
||||
hidden_size = 7168
|
||||
num_key_value_heads = 64
|
||||
supports_tensor = true
|
||||
tasks = ["TextGeneration"]
|
||||
family = "kimi"
|
||||
quantization = ""
|
||||
base_model = "Kimi K2.7 Code"
|
||||
capabilities = ["text", "thinking", "thinking_toggle", "vision"]
|
||||
|
||||
context_length = 262144
|
||||
backends = ["MlxMetal", "MlxCuda", "MlxCpu"]
|
||||
[storage_size]
|
||||
in_bytes = 595204986173
|
||||
|
||||
# Vision tower + mm_projector extracted unmodified (bf16) from the official
|
||||
# repo, in the same format as exolabs/Kimi-K2.6-vision; extraction script
|
||||
# included in the weights repo. Vision config is identical to Kimi-K2.6's.
|
||||
[vision]
|
||||
image_token_id = 163605
|
||||
model_type = "kimi_vl"
|
||||
weights_repo = "aidiffuser/Kimi-K2.7-Code-vision"
|
||||
processor_repo = "moonshotai/Kimi-K2.7-Code"
|
||||
|
||||
# Source: https://huggingface.co/moonshotai/Kimi-K2.7-Code
|
||||
# (recommends temperature 1.0 / top_p 0.95 for thinking mode, same as K2.6)
|
||||
[sampling_defaults]
|
||||
temperature = 1.0
|
||||
top_p = 0.95
|
||||
min_p = 0.01
|
||||
|
||||
[sampling_defaults.non_thinking]
|
||||
temperature = 0.6
|
||||
top_p = 0.95
|
||||
min_p = 0.01
|
||||
@@ -48,7 +48,6 @@ env_logger.workspace = true
|
||||
|
||||
# Networking
|
||||
zenoh.workspace = true
|
||||
zenoh-ext = { workspace = true, features = ["unstable"] }
|
||||
rand.workspace = true
|
||||
serde_json.workspace = true
|
||||
parking_lot.workspace = true
|
||||
@@ -2,21 +2,14 @@
|
||||
# ruff: noqa: E501, F401, F403, F405
|
||||
|
||||
import builtins
|
||||
import collections.abc
|
||||
import os
|
||||
import pathlib
|
||||
import typing
|
||||
__all__ = [
|
||||
"FromSwarm",
|
||||
"LVAggregator",
|
||||
"LVPublisher",
|
||||
"LVSubscriber",
|
||||
"NetworkingHandle",
|
||||
"Pidfile",
|
||||
"PidfileError",
|
||||
"SessionHandle",
|
||||
"Storage",
|
||||
"StorageGetter",
|
||||
]
|
||||
|
||||
class FromSwarm:
|
||||
@@ -38,18 +31,6 @@ class FromSwarm:
|
||||
|
||||
...
|
||||
|
||||
@typing.final
|
||||
class LVAggregator:
|
||||
def dump(self) -> builtins.dict[builtins.str, builtins.str]: ...
|
||||
|
||||
@typing.final
|
||||
class LVPublisher:
|
||||
def put(self, data: builtins.str) -> collections.abc.Awaitable[None]: ...
|
||||
|
||||
@typing.final
|
||||
class LVSubscriber:
|
||||
def recv(self) -> collections.abc.Awaitable[tuple[str, str] | None]: ...
|
||||
|
||||
@typing.final
|
||||
class NetworkingHandle:
|
||||
@staticmethod
|
||||
@@ -130,24 +111,3 @@ class PidfileError(builtins.Exception):
|
||||
def __repr__(self) -> builtins.str: ...
|
||||
def __str__(self) -> builtins.str: ...
|
||||
|
||||
@typing.final
|
||||
class SessionHandle:
|
||||
@staticmethod
|
||||
def new(identity: builtins.str, namespace: builtins.str, listen_port: builtins.int, discovery_service_port: builtins.int) -> tuple[SessionHandle, NetworkingHandle]: ...
|
||||
def last_value_aggregator(self, prefix: builtins.str) -> LVAggregator: ...
|
||||
def last_value_subscriber(self, kexpr: builtins.str) -> LVSubscriber: ...
|
||||
def last_value_publisher(self, kexpr: builtins.str) -> LVPublisher: ...
|
||||
def storage_interface(self) -> Storage: ...
|
||||
|
||||
@typing.final
|
||||
class Storage:
|
||||
def get(self, key: builtins.str) -> collections.abc.Awaitable[str | None]: ...
|
||||
def get_many(self, key: builtins.str) -> StorageGetter: ...
|
||||
def put(self, key: builtins.str, data: builtins.str) -> collections.abc.Awaitable[None]: ...
|
||||
def delete(self, key: builtins.str) -> collections.abc.Awaitable[None]: ...
|
||||
def dump(self, prefix: builtins.str) -> collections.abc.Awaitable[dict[str, str]]: ...
|
||||
|
||||
@typing.final
|
||||
class StorageGetter:
|
||||
def recv(self) -> collections.abc.Awaitable[tuple[str, str] | None]: ...
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
use networking::{Session, liveliness_aggregator::LivelinessAggregator};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
use zenoh::{Result as ZResult, Wait};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use pyo3::{
|
||||
exceptions::{PyConnectionError, PyRuntimeError},
|
||||
prelude::*,
|
||||
};
|
||||
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
|
||||
use zenoh::{
|
||||
handlers::FifoChannelHandler,
|
||||
sample::{Sample, SampleKind},
|
||||
};
|
||||
use zenoh_ext::{
|
||||
AdvancedPublisher, AdvancedSubscriber, AdvancedSubscriberBuilderExt, HistoryConfig,
|
||||
};
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass]
|
||||
pub struct LVAggregator {
|
||||
pub prefix: Arc<str>,
|
||||
pub store: Arc<Mutex<HashMap<String, String>>>,
|
||||
pub current_live: LivelinessAggregator,
|
||||
}
|
||||
|
||||
pub fn spawn_lv_aggregator_onto(session: &Session, prefix: Arc<str>) -> ZResult<LVAggregator> {
|
||||
// nota bene: config must be kept in line with SessionHandle::last_value_receiver
|
||||
let store = Arc::new(Mutex::new(HashMap::default()));
|
||||
session
|
||||
.z
|
||||
//assuming all LV aggregators are prefix/node_id/atomic_json
|
||||
.declare_subscriber(format!("{prefix}/*/*"))
|
||||
.advanced()
|
||||
.history(
|
||||
HistoryConfig::default()
|
||||
.max_samples(1)
|
||||
.detect_late_publishers(),
|
||||
)
|
||||
.callback({
|
||||
let store = Arc::clone(&store);
|
||||
let prefix = Arc::clone(&prefix);
|
||||
move |sample| {
|
||||
if let Some(s) = sample
|
||||
.key_expr()
|
||||
.to_string()
|
||||
.strip_prefix(&*prefix)
|
||||
.and_then(|it| it.strip_prefix('/'))
|
||||
{
|
||||
let s = s.to_string();
|
||||
match sample.kind() {
|
||||
SampleKind::Put => {
|
||||
store.lock().insert(
|
||||
s,
|
||||
sample
|
||||
.payload()
|
||||
.try_to_string()
|
||||
.expect("we only use utf8 encoded strings. someone messed up")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
SampleKind::Delete => {
|
||||
store.lock().remove(&s);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
.background()
|
||||
.wait()?;
|
||||
Ok(LVAggregator {
|
||||
prefix,
|
||||
store,
|
||||
current_live: session.liveliness_aggregator.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl LVAggregator {
|
||||
pub fn dump(&self) -> HashMap<String, String> {
|
||||
let mut store = self.store.lock();
|
||||
let currently_alive: HashSet<String> = self.current_live.dump();
|
||||
// remove any keys that are no longer live
|
||||
store.retain(|key, _| {
|
||||
currently_alive.iter().any(|node_id| {
|
||||
key.strip_prefix(node_id)
|
||||
.is_some_and(|rest| rest.starts_with("/"))
|
||||
})
|
||||
});
|
||||
store.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass]
|
||||
pub struct LVSubscriber {
|
||||
pub subscriber: AdvancedSubscriber<FifoChannelHandler<Sample>>,
|
||||
}
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl LVSubscriber {
|
||||
#[gen_stub(override_return_type(
|
||||
type_repr="collections.abc.Awaitable[tuple[str, str] | None]",
|
||||
imports=("collections.abc")
|
||||
))]
|
||||
pub fn recv<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, {
|
||||
if self.subscriber.receiver_count() != 1 {
|
||||
return Err(PyRuntimeError::new_err(
|
||||
"tried to receive twice on the same receiver",
|
||||
));
|
||||
}
|
||||
let subscriber = self.subscriber.clone();
|
||||
async move {
|
||||
loop {
|
||||
match subscriber.recv_async().await {
|
||||
Ok(sample) if sample.kind() == SampleKind::Delete => continue,
|
||||
Err(_) => {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(sample) => {
|
||||
return Ok(Some((
|
||||
sample.key_expr().to_string(),
|
||||
sample
|
||||
.payload()
|
||||
.try_to_string()
|
||||
.expect("we only use utf8 encoded strings. someone messed up")
|
||||
.to_string(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass]
|
||||
pub struct LVPublisher {
|
||||
pub state: Arc<AdvancedPublisher<'static>>,
|
||||
}
|
||||
impl LVPublisher {
|
||||
pub fn new(publisher: AdvancedPublisher<'static>) -> Self {
|
||||
Self {
|
||||
state: Arc::new(publisher),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl LVPublisher {
|
||||
#[gen_stub(override_return_type(
|
||||
type_repr="collections.abc.Awaitable[None]",
|
||||
imports=("collections.abc")
|
||||
))]
|
||||
pub fn put<'py>(&'py self, py: Python<'py>, data: String) -> PyResult<Bound<'py, PyAny>> {
|
||||
let state = Arc::clone(&self.state);
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, {
|
||||
// clone the data so py can have it back
|
||||
async move {
|
||||
state
|
||||
.put(data)
|
||||
.await
|
||||
.map_err(|e| PyConnectionError::new_err(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lv_submodule(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<LVPublisher>()?;
|
||||
m.add_class::<LVSubscriber>()?;
|
||||
m.add_class::<LVAggregator>()?;
|
||||
Ok(())
|
||||
}
|
||||
+11
-13
@@ -4,18 +4,13 @@
|
||||
//!
|
||||
//!
|
||||
|
||||
pub mod allow_threading;
|
||||
pub mod last_value;
|
||||
pub mod networking;
|
||||
pub mod pidfile;
|
||||
pub mod session;
|
||||
mod storage;
|
||||
mod allow_threading;
|
||||
// mod ident;
|
||||
mod networking;
|
||||
mod pidfile;
|
||||
|
||||
use crate::last_value::lv_submodule;
|
||||
use crate::networking::networking_submodule;
|
||||
use crate::pidfile::pidfile_submodule;
|
||||
use crate::session::session_submodule;
|
||||
use crate::storage::storage_submodule;
|
||||
use pyo3::prelude::PyModule;
|
||||
use pyo3::{Bound, PyResult, pymodule};
|
||||
use pyo3_stub_gen::define_stub_info_gatherer;
|
||||
@@ -159,12 +154,15 @@ fn main_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
builder.enable_all();
|
||||
pyo3_async_runtimes::tokio::init(builder);
|
||||
|
||||
// TODO: for now this is all NOT a submodule. KISS
|
||||
// TODO: for now this is all NOT a submodule, but figure out how to make the submodule system
|
||||
// work with maturin, where the types generate correctly, in the right folder, without
|
||||
// too many importing issues...
|
||||
pidfile_submodule(m)?;
|
||||
// m.add_class::<PyKeypair>()?;
|
||||
networking_submodule(m)?;
|
||||
lv_submodule(m)?;
|
||||
session_submodule(m)?;
|
||||
storage_submodule(m)?;
|
||||
|
||||
// top-level constructs
|
||||
// TODO: ...
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
use networking::Session;
|
||||
use pyo3::{
|
||||
exceptions::{PyConnectionError, PyRuntimeError, PyValueError},
|
||||
prelude::*,
|
||||
};
|
||||
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
|
||||
use zenoh::Wait;
|
||||
use zenoh_ext::{
|
||||
AdvancedPublisherBuilderExt, AdvancedSubscriberBuilderExt, CacheConfig, HistoryConfig,
|
||||
MissDetectionConfig,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
last_value::{LVAggregator, LVPublisher, LVSubscriber, spawn_lv_aggregator_onto},
|
||||
networking::PyNetworkingHandle,
|
||||
storage::Storage,
|
||||
};
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass]
|
||||
pub struct SessionHandle {
|
||||
pub session: Session,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl SessionHandle {
|
||||
#[staticmethod]
|
||||
pub fn new<'py>(
|
||||
identity: &str,
|
||||
namespace: &str,
|
||||
listen_port: u16,
|
||||
discovery_service_port: u16,
|
||||
) -> PyResult<(SessionHandle, PyNetworkingHandle)> {
|
||||
// get identity
|
||||
if !identity
|
||||
.chars()
|
||||
.all(|c| ('0'..='9').contains(&c) || ('a'..='f').contains(&c))
|
||||
|| identity.len() > 32
|
||||
{
|
||||
return Err(PyValueError::new_err(format!(
|
||||
"{identity} is not a valid zenoh identity"
|
||||
)));
|
||||
}
|
||||
|
||||
let cfg = networking::cfg(identity, listen_port).map_err(|e| {
|
||||
PyValueError::new_err(format!("failed to write config: {}", e.to_string()))
|
||||
})?;
|
||||
let session = pyo3_async_runtimes::tokio::get_runtime()
|
||||
.block_on(networking::open(
|
||||
cfg,
|
||||
namespace,
|
||||
listen_port,
|
||||
discovery_service_port,
|
||||
))
|
||||
.map_err(|e| {
|
||||
PyRuntimeError::new_err(format!(
|
||||
"failed to spawn networking on tokio runtime: {}",
|
||||
e.to_string()
|
||||
))
|
||||
})?;
|
||||
let legacy = PyNetworkingHandle::from_session(session.clone());
|
||||
Ok((Self { session }, legacy))
|
||||
}
|
||||
|
||||
pub fn last_value_aggregator(&self, prefix: String) -> PyResult<LVAggregator> {
|
||||
spawn_lv_aggregator_onto(&self.session, prefix.into()).map_err(|e| {
|
||||
PyConnectionError::new_err(format!("failed to spawn liveliness aggregator: {e}"))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn last_value_subscriber(&self, kexpr: &str) -> PyResult<LVSubscriber> {
|
||||
// nota bene: config must be kept in track with the LVAggregator
|
||||
self.session
|
||||
.z
|
||||
.declare_subscriber(kexpr)
|
||||
.advanced()
|
||||
.history(
|
||||
HistoryConfig::default()
|
||||
.max_samples(1)
|
||||
.detect_late_publishers(),
|
||||
)
|
||||
.wait()
|
||||
.map_err(|e| PyConnectionError::new_err(format!("failed to declare subscriber: {e}")))
|
||||
.map(|subscriber| LVSubscriber { subscriber })
|
||||
}
|
||||
|
||||
pub fn last_value_publisher(&self, kexpr: String) -> PyResult<LVPublisher> {
|
||||
self.session
|
||||
.z
|
||||
.declare_publisher(kexpr)
|
||||
.advanced()
|
||||
.publisher_detection()
|
||||
.sample_miss_detection(MissDetectionConfig::default())
|
||||
.cache(CacheConfig::default().max_samples(1))
|
||||
.wait()
|
||||
.map_err(|e| PyConnectionError::new_err(format!("failed to declare publisher: {e}")))
|
||||
.map(LVPublisher::new)
|
||||
}
|
||||
|
||||
pub fn storage_interface(&self) -> Storage {
|
||||
Storage {
|
||||
session: self.session.z.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_submodule(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<SessionHandle>()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use networking::STORAGE_PREFIX;
|
||||
use pyo3::{
|
||||
exceptions::{PyConnectionError, PyRuntimeError, PyValueError},
|
||||
prelude::*,
|
||||
};
|
||||
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
|
||||
use zenoh::{
|
||||
Session as ZSession, Wait, handlers::FifoChannelHandler, query::Reply, sample::SampleKind,
|
||||
};
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass]
|
||||
pub struct Storage {
|
||||
pub session: ZSession,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl Storage {
|
||||
#[gen_stub(override_return_type(
|
||||
type_repr="collections.abc.Awaitable[str | None]",
|
||||
imports=("collections.abc")
|
||||
))]
|
||||
pub fn get<'py>(&'py self, py: Python<'py>, key: String) -> PyResult<Bound<'py, PyAny>> {
|
||||
if key.contains('*') {
|
||||
return Err(PyValueError::new_err(format!(
|
||||
"{key} is invalid -- Storage.get only supports fixed keys"
|
||||
)));
|
||||
}
|
||||
let session = self.session.clone();
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
||||
let subscriber = session
|
||||
.get(format!("{STORAGE_PREFIX}/{key}"))
|
||||
//.allowed_destination(Locality::SessionLocal)
|
||||
.await
|
||||
.map_err(|e| PyConnectionError::new_err(format!("failed to query storage: {e}")))?;
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(Duration::from_secs(1)) => {
|
||||
Ok(None)
|
||||
}
|
||||
reply = subscriber.recv_async() => {
|
||||
Ok(reply.ok()
|
||||
.and_then(|reply| reply.into_result().ok())
|
||||
.and_then(|sample| {
|
||||
if sample.kind() == SampleKind::Put {
|
||||
Some(sample
|
||||
.payload()
|
||||
.try_to_string()
|
||||
.expect("we only use utf8 encoded strings. someone messed up").to_string()
|
||||
)
|
||||
} else { None }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
pub fn get_many(&self, key: String) -> PyResult<StorageGetter> {
|
||||
self.session
|
||||
.get(key)
|
||||
.wait()
|
||||
.map_err(|e| PyConnectionError::new_err(format!("failed to query storage: {e}")))
|
||||
.map(StorageGetter)
|
||||
}
|
||||
|
||||
#[gen_stub(override_return_type(
|
||||
type_repr="collections.abc.Awaitable[None]",
|
||||
imports=("collections.abc")
|
||||
))]
|
||||
pub fn put<'py>(
|
||||
&'py self,
|
||||
py: Python<'py>,
|
||||
key: String,
|
||||
data: String,
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
if key.contains('*') {
|
||||
return Err(PyValueError::new_err(format!(
|
||||
"{key} is invalid -- Storage.put only supports fixed keys"
|
||||
)));
|
||||
}
|
||||
let session = self.session.clone();
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
||||
session
|
||||
.put(format!("{STORAGE_PREFIX}/{key}"), data)
|
||||
.await
|
||||
.map_err(|e| PyConnectionError::new_err(format!("failed to query storage: {e}")))
|
||||
})
|
||||
}
|
||||
|
||||
#[gen_stub(override_return_type(
|
||||
type_repr="collections.abc.Awaitable[None]",
|
||||
imports=("collections.abc")
|
||||
))]
|
||||
pub fn delete<'py>(&'py self, py: Python<'py>, key: String) -> PyResult<Bound<'py, PyAny>> {
|
||||
if key.contains('*') {
|
||||
return Err(PyValueError::new_err(format!(
|
||||
"{key} is invalid -- Storage.delete only supports fixed keys"
|
||||
)));
|
||||
}
|
||||
let session = self.session.clone();
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
||||
session
|
||||
.delete(format!("{STORAGE_PREFIX}/{key}"))
|
||||
.await
|
||||
.map_err(|e| PyConnectionError::new_err(format!("failed to query storage: {e}")))
|
||||
})
|
||||
}
|
||||
|
||||
#[gen_stub(override_return_type(
|
||||
type_repr="collections.abc.Awaitable[dict[str, str]]",
|
||||
imports=("collections.abc")
|
||||
))]
|
||||
pub fn dump<'py>(&'py self, py: Python<'py>, prefix: String) -> PyResult<Bound<'py, PyAny>> {
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
||||
Ok(networking::read_raw_memory_storage()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|(key, value)| {
|
||||
Some((
|
||||
key?.as_str().strip_prefix(prefix.as_str())?.to_string(),
|
||||
value
|
||||
.payload
|
||||
.try_to_string()
|
||||
.expect("we only use utf8 encoded strings. someone messed up")
|
||||
.to_string(),
|
||||
))
|
||||
})
|
||||
.collect::<HashMap<String, String>>())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass]
|
||||
pub struct StorageGetter(FifoChannelHandler<Reply>);
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl StorageGetter {
|
||||
#[gen_stub(override_return_type(
|
||||
type_repr="collections.abc.Awaitable[tuple[str, str] | None]",
|
||||
imports=("collections.abc")
|
||||
))]
|
||||
fn recv<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
if self.0.receiver_count() != 1 {
|
||||
return Err(PyRuntimeError::new_err(
|
||||
"Tried to call StorageGetter.recv twice concurrently",
|
||||
));
|
||||
}
|
||||
let dupe = self.0.clone();
|
||||
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
||||
let sample = loop {
|
||||
match dupe.recv_async().await {
|
||||
Err(_) => return Ok(None),
|
||||
Ok(reply) => match reply.into_result() {
|
||||
Err(e) => {
|
||||
log::warn!("Ignoring reply error: {e}");
|
||||
continue;
|
||||
}
|
||||
Ok(sample) => match sample.kind() {
|
||||
SampleKind::Put => break sample,
|
||||
SampleKind::Delete => {
|
||||
log::warn!(
|
||||
"Received unexpected DELETE from queryable: {}",
|
||||
sample.key_expr()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
let key = sample
|
||||
.key_expr()
|
||||
.to_string()
|
||||
.strip_prefix(format!("{STORAGE_PREFIX}/").as_str())
|
||||
.expect("invalid storage format encountered")
|
||||
.to_string();
|
||||
|
||||
Ok(Some((
|
||||
key,
|
||||
sample
|
||||
.payload()
|
||||
.try_to_string()
|
||||
.expect("we only use utf8 encoded strings. someone messed up")
|
||||
.to_string(),
|
||||
)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storage_submodule(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<Storage>()?;
|
||||
m.add_class::<StorageGetter>()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use zenoh_ext::{
|
||||
AdvancedPublisherBuilderExt, AdvancedSubscriber, AdvancedSubscriberBuilderExt, CacheConfig,
|
||||
HistoryConfig, MissDetectionConfig,
|
||||
};
|
||||
|
||||
use zenoh::{handlers::FifoChannelHandler, sample::Sample};
|
||||
|
||||
// Adjust these imports to your crate/module paths.
|
||||
use exo_rs::{
|
||||
last_value::{LVPublisher, LVSubscriber},
|
||||
session::SessionHandle,
|
||||
};
|
||||
async fn expect_two_values(
|
||||
sub: &AdvancedSubscriber<FifoChannelHandler<Sample>>,
|
||||
key_a: &str,
|
||||
val_a: &str,
|
||||
key_b: &str,
|
||||
val_b: &str,
|
||||
) {
|
||||
use std::collections::HashMap;
|
||||
use tokio::time::{Duration, Instant, timeout};
|
||||
use zenoh::sample::SampleKind;
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
let mut seen: HashMap<String, String> = HashMap::new();
|
||||
|
||||
loop {
|
||||
if seen.get(key_a).map(String::as_str) == Some(val_a)
|
||||
&& seen.get(key_b).map(String::as_str) == Some(val_b)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let remaining = deadline.saturating_duration_since(Instant::now());
|
||||
assert!(
|
||||
!remaining.is_zero(),
|
||||
"timed out waiting for both historical samples; expected {key_a}={val_a}, {key_b}={val_b}; seen = {seen:?}"
|
||||
);
|
||||
|
||||
match timeout(remaining.min(Duration::from_millis(750)), sub.recv_async()).await {
|
||||
Ok(Ok(sample)) => {
|
||||
if sample.kind() == SampleKind::Delete {
|
||||
continue;
|
||||
}
|
||||
|
||||
let key = sample.key_expr().to_string();
|
||||
let value = sample
|
||||
.payload()
|
||||
.try_to_string()
|
||||
.expect("payload should be UTF-8")
|
||||
.to_string();
|
||||
|
||||
if key == key_a || key == key_b {
|
||||
eprintln!("received relevant {key} = {value}");
|
||||
seen.insert(key, value);
|
||||
} else {
|
||||
eprintln!("received unrelated {key} = {value}");
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => panic!("subscriber receive failed: {e}"),
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn lv_subscriber_receives_last_value_from_multiple_publishers() {
|
||||
let cfg =
|
||||
networking::cfg(&format!("{:x}", rand::random::<u128>()), 52414).expect("create config");
|
||||
let n_session = networking::open(cfg, "exo", 52414, 52413)
|
||||
.await
|
||||
.expect("open session");
|
||||
|
||||
let session = SessionHandle { session: n_session };
|
||||
|
||||
let run_id = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
|
||||
let base = format!("zenoh_advanced_history_test/{run_id}");
|
||||
|
||||
let key_a = format!("{base}/a");
|
||||
let key_b = format!("{base}/b");
|
||||
let sub_key = format!("{base}/*");
|
||||
|
||||
let pub1: LVPublisher = session
|
||||
.last_value_publisher(key_a.clone())
|
||||
.expect("declare LV publisher a");
|
||||
|
||||
pub1.state.put("aa").await.expect("publish aa");
|
||||
|
||||
let pub2: LVPublisher = session
|
||||
.last_value_publisher(key_b.clone())
|
||||
.expect("declare LV publisher b");
|
||||
|
||||
pub2.state.put("bb").await.expect("publish bb");
|
||||
|
||||
// Let publisher detection / cache metadata settle before the late subscriber joins.
|
||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||
|
||||
let sub: LVSubscriber = session
|
||||
.last_value_subscriber(&*sub_key)
|
||||
.expect("declare LV subscriber");
|
||||
|
||||
expect_two_values(&sub.subscriber, &*key_a, "aa", &*key_b, "bb").await
|
||||
}
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn wildcard_advanced_subscriber_receives_history_from_both_publishers() {
|
||||
let cfg =
|
||||
networking::cfg(&format!("{:x}", rand::random::<u128>()), 52412).expect("create config");
|
||||
let n_session = networking::open(cfg, "exo", 52412, 52411)
|
||||
.await
|
||||
.expect("open session");
|
||||
let session = n_session.z.clone();
|
||||
|
||||
// Unique prefix so the wildcard subscriber cannot accidentally see unrelated traffic.
|
||||
let run_id = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
|
||||
let base = format!("zenoh_advanced_history_test/{run_id}");
|
||||
let key_a = format!("{base}/a");
|
||||
let key_b = format!("{base}/b");
|
||||
let sub_key = format!("{base}/*");
|
||||
|
||||
let pub1 = session
|
||||
.declare_publisher(key_a.clone())
|
||||
.advanced()
|
||||
.publisher_detection()
|
||||
.sample_miss_detection(MissDetectionConfig::default())
|
||||
.cache(CacheConfig::default().max_samples(1))
|
||||
.await
|
||||
.expect("declare advanced publisher a");
|
||||
|
||||
pub1.put("aa").await.expect("publish aa");
|
||||
|
||||
let pub2 = session
|
||||
.declare_publisher(key_b.clone())
|
||||
.advanced()
|
||||
.sample_miss_detection(MissDetectionConfig::default())
|
||||
.publisher_detection()
|
||||
.cache(CacheConfig::default().max_samples(1))
|
||||
.await
|
||||
.expect("declare advanced publisher b");
|
||||
|
||||
pub2.put("bb").await.expect("publish bb");
|
||||
|
||||
// Give liveliness/cache declarations a brief chance to settle before declaring
|
||||
// the late-joining advanced subscriber.
|
||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||
|
||||
let sub = session
|
||||
.declare_subscriber(sub_key)
|
||||
.advanced()
|
||||
.history(
|
||||
HistoryConfig::default()
|
||||
.max_samples(1)
|
||||
.detect_late_publishers(),
|
||||
)
|
||||
.await
|
||||
.expect("declare advanced subscriber");
|
||||
|
||||
expect_two_values(&sub, &*key_a, "aa", &*key_b, "bb").await
|
||||
}
|
||||
+38
-35
@@ -1,51 +1,54 @@
|
||||
use core::mem::drop;
|
||||
use core::option::Option::Some;
|
||||
use core::time::Duration;
|
||||
use tokio;
|
||||
use tokio::sync::mpsc;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::mem::drop;
|
||||
use core::option::Option::Some;
|
||||
use core::time::Duration;
|
||||
use tokio;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_drop_channel() {
|
||||
struct Ping;
|
||||
#[tokio::test]
|
||||
async fn test_drop_channel() {
|
||||
struct Ping;
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<Ping>(10);
|
||||
let (tx, mut rx) = mpsc::channel::<Ping>(10);
|
||||
|
||||
let _ = tokio::spawn(async move {
|
||||
println!("TASK: entered");
|
||||
let _ = tokio::spawn(async move {
|
||||
println!("TASK: entered");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = rx.recv() => {
|
||||
match result {
|
||||
Some(_) => {
|
||||
println!("TASK: pinged");
|
||||
}
|
||||
None => {
|
||||
println!("TASK: closing channel");
|
||||
break;
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = rx.recv() => {
|
||||
match result {
|
||||
Some(_) => {
|
||||
println!("TASK: pinged");
|
||||
}
|
||||
None => {
|
||||
println!("TASK: closing channel");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep(Duration::from_secs_f32(0.1)) => {
|
||||
println!("TASK: heartbeat");
|
||||
_ = tokio::time::sleep(Duration::from_secs_f32(0.1)) => {
|
||||
println!("TASK: heartbeat");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("TASK: exited");
|
||||
});
|
||||
println!("TASK: exited");
|
||||
});
|
||||
|
||||
let tx2 = tx.clone();
|
||||
let tx2 = tx.clone();
|
||||
|
||||
tokio::time::sleep(Duration::from_secs_f32(0.11)).await;
|
||||
tokio::time::sleep(Duration::from_secs_f32(0.11)).await;
|
||||
|
||||
tx.send(Ping).await.expect("Should not fail");
|
||||
drop(tx);
|
||||
tx.send(Ping).await.expect("Should not fail");
|
||||
drop(tx);
|
||||
|
||||
tokio::time::sleep(Duration::from_secs_f32(0.11)).await;
|
||||
tokio::time::sleep(Duration::from_secs_f32(0.11)).await;
|
||||
|
||||
tx2.send(Ping).await.expect("Should not fail");
|
||||
drop(tx2);
|
||||
tx2.send(Ping).await.expect("Should not fail");
|
||||
drop(tx2);
|
||||
|
||||
tokio::time::sleep(Duration::from_secs_f32(0.11)).await;
|
||||
tokio::time::sleep(Duration::from_secs_f32(0.11)).await;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
from exo_rs import SessionHandle
|
||||
|
||||
|
||||
ZENOH_PORT = 52414
|
||||
DISCOVERY_PORT = 52413
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def storage():
|
||||
node_id = os.urandom(16).hex().rstrip("0")
|
||||
|
||||
session_handle, _nh = SessionHandle.new(
|
||||
node_id,
|
||||
ZENOH_PORT,
|
||||
DISCOVERY_PORT,
|
||||
)
|
||||
|
||||
return session_handle.storage_interface()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_storage_get_missing_key_returns_none(storage):
|
||||
key = f"tests/storage/{uuid.uuid4().hex}/missing"
|
||||
|
||||
value = await storage.get(key)
|
||||
assert value is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_storage_put_then_get_returns_value(storage):
|
||||
key = f"tests/storage/{uuid.uuid4().hex}/value"
|
||||
expected = "hello storage"
|
||||
|
||||
await storage.put(key, expected)
|
||||
assert await storage.get(key) == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_storage_put_overwrites_value(storage):
|
||||
key = f"tests/storage/{uuid.uuid4().hex}/overwrite"
|
||||
|
||||
await storage.put(key, "old")
|
||||
await storage.put(key, "new")
|
||||
assert await storage.get(key) == "new"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_storage_put_overwrites_value(storage):
|
||||
key = f"tests/storage/{uuid.uuid4().hex}/overwrite"
|
||||
|
||||
await storage.put(key, "old")
|
||||
await storage.delete(key)
|
||||
assert await storage.get(key) == None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_storage_get_rejects_wildcard_key(storage):
|
||||
with pytest.raises(ValueError, match="only supports fixed keys"):
|
||||
await storage.get("tests/storage/*")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_storage_put_rejects_wildcard_key(storage):
|
||||
with pytest.raises(ValueError, match="only supports fixed keys"):
|
||||
await storage.put("tests/storage/*", "value")
|
||||
+14
-29
@@ -7,16 +7,9 @@ use zenoh_plugin_trait::PluginsManager;
|
||||
|
||||
pub use zenoh::{Config, config::ZenohId};
|
||||
|
||||
use crate::{
|
||||
discovery::Discovery,
|
||||
liveliness_aggregator::{LivelinessAggregator, spawn_liveliness_aggregator},
|
||||
};
|
||||
|
||||
pub use zenoh_plugin_storage_manager::read_raw_memory_storage;
|
||||
pub const STORAGE_PREFIX: &str = "storage/mem1";
|
||||
use crate::discovery::Discovery;
|
||||
|
||||
pub mod discovery;
|
||||
pub mod liveliness_aggregator;
|
||||
pub mod swarm;
|
||||
|
||||
pub fn is_valid_zid(identity: &str) -> bool {
|
||||
@@ -40,22 +33,20 @@ pub fn cfg(identity: &str, listen_port: u16) -> Result<zenoh::Config> {
|
||||
cfg.insert_json5("scouting/multicast/autoconnect", "[]")?;
|
||||
cfg.insert_json5("scouting/gossip/multihop", "true")?;
|
||||
cfg.insert_json5("adminspace/enabled", "true")?;
|
||||
cfg.insert_json5("transport/link/tx/batch_size", "9216")?;
|
||||
//cfg.insert_json5("transport/link/tx/batch_size", "9216")?;
|
||||
cfg.insert_json5("transport/link/rx/buffer_size", "16777216")?;
|
||||
cfg.insert_json5("timestamping/enabled", "true")?;
|
||||
//cfg.insert_json5("timestamping/enabled", "true")?;
|
||||
cfg.insert_json5("plugins/storage_manager/__required__", "true")?;
|
||||
cfg.insert_json5(
|
||||
"plugins/storage_manager/storages/mem1/key_expr",
|
||||
&format!("\"{STORAGE_PREFIX}/**\""),
|
||||
)?;
|
||||
cfg.insert_json5(
|
||||
"plugins/storage_manager/storages/mem1/strip_prefix",
|
||||
&format!("\"{STORAGE_PREFIX}\""),
|
||||
)?;
|
||||
cfg.insert_json5("plugins/storage_manager/storages/mem1/volume", "\"memory\"")?;
|
||||
cfg.insert_json5(
|
||||
"plugins/storage_manager/storages/mem1/replication/interval",
|
||||
"2",
|
||||
"plugins/storage_manager/storages/mem1",
|
||||
r#"{
|
||||
key_expr: "storage/mem1/**",
|
||||
strip_prefix: "storage/mem1",
|
||||
volume: "memory",
|
||||
replication: {
|
||||
interval: 2,
|
||||
}
|
||||
}"#,
|
||||
)?;
|
||||
Ok(cfg)
|
||||
}
|
||||
@@ -108,15 +99,10 @@ pub async fn open(
|
||||
.await;
|
||||
}
|
||||
})));
|
||||
let liveliness_aggregator = spawn_liveliness_aggregator(&z)?;
|
||||
Ok(Session {
|
||||
z,
|
||||
liveliness_aggregator,
|
||||
_jh,
|
||||
})
|
||||
Ok(Session { z, _jh })
|
||||
}
|
||||
|
||||
pub struct AbortOnDrop(pub JoinHandle<()>);
|
||||
struct AbortOnDrop(JoinHandle<()>);
|
||||
impl Drop for AbortOnDrop {
|
||||
fn drop(&mut self) {
|
||||
self.0.abort();
|
||||
@@ -126,6 +112,5 @@ impl Drop for AbortOnDrop {
|
||||
#[derive(Clone)]
|
||||
pub struct Session {
|
||||
pub z: ZSession,
|
||||
pub liveliness_aggregator: LivelinessAggregator,
|
||||
_jh: Arc<AbortOnDrop>,
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use zenoh::{Result, Session, Wait, sample::SampleKind};
|
||||
|
||||
pub fn spawn_liveliness_aggregator(session: &Session) -> Result<LivelinessAggregator> {
|
||||
let store = Arc::new(Mutex::new(HashSet::default()));
|
||||
session
|
||||
.liveliness()
|
||||
.declare_subscriber("live/*")
|
||||
.history(true)
|
||||
.callback({
|
||||
let store = Arc::clone(&store);
|
||||
move |sample| {
|
||||
let Some(nid) = sample
|
||||
.key_expr()
|
||||
.to_string()
|
||||
.strip_prefix("live/")
|
||||
.map(str::to_owned)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let mut mg = store.lock();
|
||||
match sample.kind() {
|
||||
SampleKind::Put => mg.insert(nid),
|
||||
SampleKind::Delete => mg.remove(&nid),
|
||||
};
|
||||
}
|
||||
})
|
||||
.background()
|
||||
.wait()?;
|
||||
Ok(LivelinessAggregator { store })
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LivelinessAggregator {
|
||||
// need two arcs as the sub owns an arc to the store.
|
||||
store: Arc<Mutex<HashSet<String>>>,
|
||||
}
|
||||
impl LivelinessAggregator {
|
||||
pub fn dump(&self) -> HashSet<String> {
|
||||
self.store.lock().clone()
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use zenoh::Wait;
|
||||
|
||||
fn unique_key(name: &str) -> String {
|
||||
format!("test/zenoh-runtime-polling/{}/{}", std::process::id(), name)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn get_and_recv_work_on_tokio_baseline() {
|
||||
let session = zenoh::open(zenoh::Config::default())
|
||||
.await
|
||||
.expect("open session");
|
||||
|
||||
let key = unique_key("tokio-baseline");
|
||||
let reply_key = key.clone();
|
||||
|
||||
let _queryable = session
|
||||
.declare_queryable(key.clone())
|
||||
.callback(move |query| {
|
||||
query
|
||||
.reply(reply_key.clone(), "hello-from-queryable")
|
||||
.wait()
|
||||
.expect("reply from queryable");
|
||||
})
|
||||
.await
|
||||
.expect("declare queryable");
|
||||
|
||||
let replies = session.get(key).await.expect("get");
|
||||
|
||||
let reply = tokio::time::timeout(Duration::from_secs(5), replies.recv_async())
|
||||
.await
|
||||
.expect("timed out waiting for reply")
|
||||
.expect("reply channel closed");
|
||||
|
||||
let sample = reply.result().expect("reply result was error");
|
||||
let payload = sample
|
||||
.payload()
|
||||
.try_to_string()
|
||||
.expect("payload should be utf8");
|
||||
|
||||
assert_eq!(payload.as_ref(), "hello-from-queryable");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn get_and_recv_work_when_polled_by_smol_without_tokio_context() {
|
||||
let session = zenoh::open(zenoh::Config::default())
|
||||
.await
|
||||
.expect("open session under tokio");
|
||||
|
||||
let key = unique_key("smol-no-tokio-context");
|
||||
let reply_key = key.clone();
|
||||
|
||||
let _queryable = session
|
||||
.declare_queryable(key.clone())
|
||||
.callback(move |query| {
|
||||
query
|
||||
.reply(reply_key.clone(), "hello-from-queryable")
|
||||
.wait()
|
||||
.expect("reply from queryable");
|
||||
})
|
||||
.await
|
||||
.expect("declare queryable under tokio");
|
||||
|
||||
let session_for_smol = session.clone();
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// This thread was not entered by Tokio.
|
||||
// If Zenoh's get/recv path requires an ambient Tokio Handle in the polling thread,
|
||||
// this is where it should panic, hang, or error.
|
||||
let result = {
|
||||
smol::block_on(async move {
|
||||
let replies = session_for_smol.get(key).await.expect("get under smol");
|
||||
|
||||
let reply = replies.recv_async().await.expect("reply channel closed");
|
||||
|
||||
let sample = reply.result().expect("reply result was error");
|
||||
let payload = sample
|
||||
.payload()
|
||||
.try_to_string()
|
||||
.expect("payload should be utf8");
|
||||
|
||||
payload.to_string()
|
||||
})
|
||||
};
|
||||
|
||||
tx.send(result).expect("send test result");
|
||||
});
|
||||
|
||||
let result = rx
|
||||
.recv_timeout(Duration::from_secs(5))
|
||||
.expect("smol thread timed out; likely hung waiting for get/reply");
|
||||
|
||||
let payload = result;
|
||||
|
||||
assert_eq!(payload, "hello-from-queryable");
|
||||
}
|
||||
+53
-60
@@ -13,7 +13,6 @@ from uuid import uuid4
|
||||
|
||||
import anyio
|
||||
from anyio import BrokenResourceError, ClosedResourceError
|
||||
from exo_rs import SessionHandle
|
||||
from fastapi import FastAPI, File, Form, HTTPException, Query, Request, UploadFile
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
||||
@@ -23,7 +22,6 @@ from hypercorn.config import Config
|
||||
from hypercorn.typing import ASGIFramework
|
||||
from hypercorn.utils import LifespanTimeoutError, ShutdownError
|
||||
from loguru import logger
|
||||
from pydantic import ValidationError
|
||||
|
||||
from exo.api.adapters.chat_completions import (
|
||||
chat_request_to_text_generation,
|
||||
@@ -155,11 +153,14 @@ from exo.shared.types.chunks import (
|
||||
ToolCallChunk,
|
||||
)
|
||||
from exo.shared.types.commands import (
|
||||
AddCustomModelCard,
|
||||
CancelDownload,
|
||||
Command,
|
||||
CreateInstance,
|
||||
DeleteCustomModelCard,
|
||||
DeleteDownload,
|
||||
DeleteInstance,
|
||||
DeleteInstanceLink,
|
||||
DownloadCommand,
|
||||
ForwarderCommand,
|
||||
ForwarderDownloadCommand,
|
||||
@@ -167,6 +168,7 @@ from exo.shared.types.commands import (
|
||||
ImageGeneration,
|
||||
PlaceInstance,
|
||||
SendInputChunk,
|
||||
SetInstanceLink,
|
||||
StartDownload,
|
||||
TaskCancelled,
|
||||
TaskFinished,
|
||||
@@ -244,7 +246,6 @@ class API:
|
||||
download_command_sender: Sender[ForwarderDownloadCommand],
|
||||
# This lets us pause the API if an election is running
|
||||
election_receiver: Receiver[ElectionMessage],
|
||||
session_handle: SessionHandle,
|
||||
) -> None:
|
||||
self.state = State()
|
||||
self._event_log = DiskEventLog(_API_EVENT_LOG_DIR)
|
||||
@@ -257,8 +258,6 @@ class API:
|
||||
self.last_completed_election: int = 0
|
||||
self.port = port
|
||||
self._sent_image_hashes: set[str] = set()
|
||||
self.aggregator = session_handle.last_value_aggregator("metrics")
|
||||
self.storage = session_handle.storage_interface()
|
||||
|
||||
self.paused: bool = False
|
||||
self.paused_ev: anyio.Event = anyio.Event()
|
||||
@@ -410,12 +409,10 @@ class API:
|
||||
self.app.post("/onboarding")(self.complete_onboarding)
|
||||
|
||||
def get_state(self, path: str = ""):
|
||||
state = self.state.with_aggregator(self.aggregator)
|
||||
|
||||
if path == "":
|
||||
return state
|
||||
return self.state
|
||||
try:
|
||||
x: Any = state.model_dump(by_alias=True)
|
||||
x = self.state.model_dump(by_alias=True)
|
||||
for attr in path.split("/"):
|
||||
if attr != "":
|
||||
if isinstance(x, dict):
|
||||
@@ -479,7 +476,6 @@ class API:
|
||||
model_card = await ModelCard.load(model_id)
|
||||
|
||||
try:
|
||||
state = self.state.with_aggregator(self.aggregator)
|
||||
placements = get_instance_placements(
|
||||
PlaceInstance(
|
||||
model_card=model_card,
|
||||
@@ -487,13 +483,13 @@ class API:
|
||||
instance_meta=instance_meta,
|
||||
min_nodes=min_nodes,
|
||||
),
|
||||
node_memory=state.node_memory,
|
||||
node_network=state.node_network,
|
||||
node_backends=state.node_backends,
|
||||
topology=state.topology,
|
||||
current_instances=state.instances,
|
||||
download_status=state.downloads,
|
||||
node_rdma_ctl=state.node_rdma_ctl,
|
||||
node_memory=self.state.node_memory,
|
||||
node_network=self.state.node_network,
|
||||
node_backends=self.state.node_backends,
|
||||
topology=self.state.topology,
|
||||
current_instances=self.state.instances,
|
||||
download_status=self.state.downloads,
|
||||
node_rdma_ctl=self.state.node_rdma_ctl,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
@@ -518,9 +514,8 @@ class API:
|
||||
seen: set[tuple[ModelId, Sharding, InstanceMeta, int]] = set()
|
||||
previews: list[PlacementPreview] = []
|
||||
required_nodes = set(node_ids) if node_ids else None
|
||||
state = self.state.with_aggregator(self.aggregator)
|
||||
|
||||
if len(list(state.topology.list_nodes())) == 0:
|
||||
if len(list(self.state.topology.list_nodes())) == 0:
|
||||
return PlacementPreviewResponse(previews=[])
|
||||
|
||||
try:
|
||||
@@ -535,7 +530,9 @@ class API:
|
||||
instance_combinations.extend(
|
||||
[
|
||||
(sharding, instance_meta, i)
|
||||
for i in range(1, len(list(state.topology.list_nodes())) + 1)
|
||||
for i in range(
|
||||
1, len(list(self.state.topology.list_nodes())) + 1
|
||||
)
|
||||
]
|
||||
)
|
||||
# TODO: PDD
|
||||
@@ -550,14 +547,14 @@ class API:
|
||||
instance_meta=instance_meta,
|
||||
min_nodes=min_nodes,
|
||||
),
|
||||
node_memory=state.node_memory,
|
||||
node_network=state.node_network,
|
||||
node_backends=state.node_backends,
|
||||
topology=state.topology,
|
||||
current_instances=state.instances,
|
||||
node_memory=self.state.node_memory,
|
||||
node_network=self.state.node_network,
|
||||
node_backends=self.state.node_backends,
|
||||
topology=self.state.topology,
|
||||
current_instances=self.state.instances,
|
||||
required_nodes=required_nodes,
|
||||
download_status=state.downloads,
|
||||
node_rdma_ctl=state.node_rdma_ctl,
|
||||
download_status=self.state.downloads,
|
||||
node_rdma_ctl=self.state.node_rdma_ctl,
|
||||
)
|
||||
except ValueError as exc:
|
||||
if (model_card.model_id, sharding, instance_meta, 0) not in seen:
|
||||
@@ -699,16 +696,9 @@ class API:
|
||||
return {"disaggregation": ENABLE_DISAGGREGATION}
|
||||
|
||||
async def list_instance_links(self) -> list[InstanceLink]:
|
||||
links: list[InstanceLink] = []
|
||||
if not ENABLE_DISAGGREGATION:
|
||||
return links
|
||||
for _, value in (await self.storage.dump("custom_model_cards/")).items():
|
||||
try:
|
||||
link = InstanceLink.model_validate_json(value)
|
||||
except ValidationError:
|
||||
continue
|
||||
links.append(link)
|
||||
return links
|
||||
return []
|
||||
return list(self.state.instance_links.values())
|
||||
|
||||
async def create_instance_link(
|
||||
self, body: InstanceLinkBody
|
||||
@@ -725,22 +715,25 @@ class API:
|
||||
async def _set_instance_link(
|
||||
self, link_id: InstanceLinkId, body: InstanceLinkBody
|
||||
) -> InstanceLinkResponse:
|
||||
await self.storage.put(
|
||||
f"instance_links/{link_id}",
|
||||
InstanceLink(
|
||||
link_id=link_id,
|
||||
prefill_instances=body.prefill_instances,
|
||||
decode_instances=body.decode_instances,
|
||||
).model_dump_json(),
|
||||
command = SetInstanceLink(
|
||||
link_id=link_id,
|
||||
prefill_instances=list(body.prefill_instances),
|
||||
decode_instances=list(body.decode_instances),
|
||||
)
|
||||
await self._send(command)
|
||||
return InstanceLinkResponse(
|
||||
message="Command received.", command_id=command.command_id
|
||||
)
|
||||
return InstanceLinkResponse(message="Command received.")
|
||||
|
||||
async def delete_instance_link(
|
||||
self, link_id: InstanceLinkId
|
||||
) -> InstanceLinkResponse:
|
||||
_require_disaggregation_enabled()
|
||||
await self.storage.delete(f"instance_links/{link_id}")
|
||||
return InstanceLinkResponse(message="Command received.")
|
||||
command = DeleteInstanceLink(link_id=link_id)
|
||||
await self._send(command)
|
||||
return InstanceLinkResponse(
|
||||
message="Command received.", command_id=command.command_id
|
||||
)
|
||||
|
||||
async def cancel_command(self, command_id: CommandId) -> CancelCommandResponse:
|
||||
"""Cancel an active command by closing its stream and notifying workers."""
|
||||
@@ -798,11 +791,7 @@ class API:
|
||||
async def _collect_text_generation_with_stats(
|
||||
self, command_id: CommandId
|
||||
) -> BenchChatCompletionResponse:
|
||||
sampler = PowerSampler(
|
||||
get_node_system=lambda: self.state.with_aggregator(
|
||||
self.aggregator
|
||||
).node_system
|
||||
)
|
||||
sampler = PowerSampler(get_node_system=lambda: self.state.node_system)
|
||||
text_parts: list[str] = []
|
||||
tool_calls: list[ToolCall] = []
|
||||
model: ModelId | None = None
|
||||
@@ -1325,11 +1314,7 @@ class API:
|
||||
num_images: int,
|
||||
response_format: str,
|
||||
) -> BenchImageGenerationResponse:
|
||||
sampler = PowerSampler(
|
||||
get_node_system=lambda: self.state.with_aggregator(
|
||||
self.aggregator
|
||||
).node_system
|
||||
)
|
||||
sampler = PowerSampler(get_node_system=lambda: self.state.node_system)
|
||||
images: list[ImageData] = []
|
||||
stats: ImageGenerationStats | None = None
|
||||
async with anyio.create_task_group() as tg:
|
||||
@@ -1793,7 +1778,7 @@ class API:
|
||||
"""Calculate total available memory across all nodes in bytes."""
|
||||
total_available = Memory()
|
||||
|
||||
for memory in self.state.with_aggregator(self.aggregator).node_memory.values():
|
||||
for memory in self.state.node_memory.values():
|
||||
total_available += memory.ram_available
|
||||
|
||||
return total_available
|
||||
@@ -1842,8 +1827,11 @@ class API:
|
||||
status_code=400, detail=f"Failed to fetch model: {exc}"
|
||||
) from exc
|
||||
|
||||
await self.storage.put(
|
||||
f"custom_model_cards/{card.model_id.normalize()}", card.model_dump_json()
|
||||
await self.command_sender.send(
|
||||
ForwarderCommand(
|
||||
origin=self._system_id,
|
||||
command=AddCustomModelCard(model_card=card),
|
||||
)
|
||||
)
|
||||
|
||||
# Immediately update the local cache so the subsequent GET /models
|
||||
@@ -1868,7 +1856,12 @@ class API:
|
||||
if card is None or not card.is_custom:
|
||||
raise HTTPException(status_code=404, detail="Custom model card not found")
|
||||
|
||||
await self.storage.delete(f"custom_model_cards/{card.model_id.normalize()}")
|
||||
await self.command_sender.send(
|
||||
ForwarderCommand(
|
||||
origin=self._system_id,
|
||||
command=DeleteCustomModelCard(model_id=model_id),
|
||||
)
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
{"message": "Model card deleted", "model_id": str(model_id)}
|
||||
|
||||
@@ -329,6 +329,7 @@ class InstanceLinkBody(BaseModel):
|
||||
|
||||
class InstanceLinkResponse(BaseModel):
|
||||
message: str
|
||||
command_id: CommandId
|
||||
|
||||
|
||||
ImageSize = Literal[
|
||||
|
||||
+11
-21
@@ -10,7 +10,7 @@ from typing import Self
|
||||
import anyio
|
||||
from anyio.lowlevel import checkpoint as anyio_checkpoint
|
||||
from daemon import DaemonContext # pyright: ignore[reportMissingTypeStubs]
|
||||
from exo_rs import Pidfile, PidfileError, SessionHandle
|
||||
from exo_rs import Pidfile, PidfileError
|
||||
from loguru import logger
|
||||
from pydantic import PositiveInt
|
||||
|
||||
@@ -47,21 +47,18 @@ class Node:
|
||||
node_id: NodeId
|
||||
offline: bool
|
||||
_api_port: int
|
||||
_sh: SessionHandle
|
||||
_tg: TaskGroup = field(init=False, default_factory=TaskGroup)
|
||||
|
||||
@classmethod
|
||||
async def create(cls, args: "Args") -> Self:
|
||||
node_id = get_node_zid()
|
||||
session_id = SessionId(master_node_id=node_id, election_clock=0)
|
||||
session_handle, _nh = SessionHandle.new(
|
||||
router = Router.create(
|
||||
node_id,
|
||||
namespace=args.namespace,
|
||||
listen_port=args.zenoh_port,
|
||||
discovery_service_port=args.discovery_port,
|
||||
)
|
||||
router = Router(_nh)
|
||||
|
||||
await router.register_topic(topics.GLOBAL_EVENTS)
|
||||
await router.register_topic(topics.LOCAL_EVENTS)
|
||||
await router.register_topic(topics.COMMANDS)
|
||||
@@ -100,7 +97,6 @@ class Node:
|
||||
command_sender=router.sender(topics.COMMANDS),
|
||||
download_command_sender=router.sender(topics.DOWNLOAD_COMMANDS),
|
||||
election_receiver=router.receiver(topics.ELECTION_MESSAGES),
|
||||
session_handle=session_handle,
|
||||
)
|
||||
else:
|
||||
api = None
|
||||
@@ -112,7 +108,6 @@ class Node:
|
||||
event_sender=event_router.sender(),
|
||||
command_sender=router.sender(topics.COMMANDS),
|
||||
download_command_sender=router.sender(topics.DOWNLOAD_COMMANDS),
|
||||
session_handle=session_handle,
|
||||
api_port=args.api_port,
|
||||
)
|
||||
else:
|
||||
@@ -127,8 +122,6 @@ class Node:
|
||||
local_event_receiver=router.receiver(topics.LOCAL_EVENTS),
|
||||
command_receiver=router.receiver(topics.COMMANDS),
|
||||
download_command_sender=router.sender(topics.DOWNLOAD_COMMANDS),
|
||||
aggregator=session_handle.last_value_aggregator("metrics"),
|
||||
storage=session_handle.storage_interface(),
|
||||
)
|
||||
|
||||
er_send, er_recv = channel[ElectionResult]()
|
||||
@@ -157,7 +150,6 @@ class Node:
|
||||
node_id,
|
||||
args.offline,
|
||||
args.api_port,
|
||||
session_handle,
|
||||
)
|
||||
|
||||
async def run(self):
|
||||
@@ -233,8 +225,6 @@ class Node:
|
||||
download_command_sender=self.router.sender(
|
||||
topics.DOWNLOAD_COMMANDS
|
||||
),
|
||||
aggregator=self._sh.last_value_aggregator("metrics"),
|
||||
storage=self._sh.storage_interface(),
|
||||
)
|
||||
self._tg.start_soon(self.master.run)
|
||||
elif (
|
||||
@@ -274,7 +264,6 @@ class Node:
|
||||
download_command_sender=self.router.sender(
|
||||
topics.DOWNLOAD_COMMANDS
|
||||
),
|
||||
session_handle=self._sh,
|
||||
api_port=self._api_port,
|
||||
)
|
||||
self._tg.start_soon(self.worker.run)
|
||||
@@ -389,16 +378,17 @@ def main_inner(args: "Args"):
|
||||
|
||||
|
||||
class Args(FrozenModel):
|
||||
verbosity: int
|
||||
force_master: bool
|
||||
spawn_api: bool
|
||||
api_port: PositiveInt
|
||||
verbosity: int = 0
|
||||
force_master: bool = False
|
||||
spawn_api: bool = False
|
||||
api_port: PositiveInt = 52415
|
||||
tb_only: bool = False
|
||||
no_worker: bool = False
|
||||
no_downloads: bool = False
|
||||
offline: bool
|
||||
no_batch: bool
|
||||
offline: bool = os.getenv("EXO_OFFLINE", "false").lower() == "true"
|
||||
no_batch: bool = False
|
||||
fast_synch: bool | None = None # None = auto, True = force on, False = force off
|
||||
legacy_daemon: bool
|
||||
legacy_daemon: bool = False
|
||||
bootstrap_peers: list[str] = []
|
||||
namespace: str
|
||||
zenoh_port: int
|
||||
@@ -486,7 +476,7 @@ class Args(FrozenModel):
|
||||
type=int,
|
||||
default=52414,
|
||||
dest="zenoh_port",
|
||||
help="Fixed port for zenoh to listen on.",
|
||||
help="Fixed TCP port for zenoh to listen.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--discovery-port",
|
||||
|
||||
+45
-40
@@ -1,9 +1,7 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import anyio
|
||||
from exo_rs import LVAggregator, Storage
|
||||
from loguru import logger
|
||||
from pydantic import ValidationError
|
||||
|
||||
from exo.master.placement import (
|
||||
add_instance_to_placements,
|
||||
@@ -20,8 +18,11 @@ from exo.routing.event_router import (
|
||||
from exo.shared.apply import apply
|
||||
from exo.shared.constants import EXO_EVENT_LOG_DIR, EXO_TRACING_ENABLED
|
||||
from exo.shared.types.commands import (
|
||||
AddCustomModelCard,
|
||||
CreateInstance,
|
||||
DeleteCustomModelCard,
|
||||
DeleteInstance,
|
||||
DeleteInstanceLink,
|
||||
ForwarderCommand,
|
||||
ForwarderDownloadCommand,
|
||||
ImageEdits,
|
||||
@@ -29,6 +30,7 @@ from exo.shared.types.commands import (
|
||||
PlaceInstance,
|
||||
RequestEventLog,
|
||||
SendInputChunk,
|
||||
SetInstanceLink,
|
||||
TaskCancelled,
|
||||
TaskFinished,
|
||||
TestCommand,
|
||||
@@ -36,11 +38,15 @@ from exo.shared.types.commands import (
|
||||
)
|
||||
from exo.shared.types.common import CommandId, NodeId, SessionId, SystemId
|
||||
from exo.shared.types.events import (
|
||||
CustomModelCardAdded,
|
||||
CustomModelCardDeleted,
|
||||
Event,
|
||||
GlobalForwarderEvent,
|
||||
IndexedEvent,
|
||||
InputChunkReceived,
|
||||
InstanceDeleted,
|
||||
InstanceLinkCreated,
|
||||
InstanceLinkDeleted,
|
||||
LocalForwarderEvent,
|
||||
NodeGatheredInfo,
|
||||
NodeTimedOut,
|
||||
@@ -73,9 +79,7 @@ from exo.utils.event_buffer import MultiSourceBuffer
|
||||
from exo.utils.task_group import TaskGroup
|
||||
|
||||
|
||||
def _prefill_endpoint_for(
|
||||
state: State, instance_links: list[InstanceLink], decode_instance_id: InstanceId
|
||||
) -> str | None:
|
||||
def _prefill_endpoint_for(state: State, decode_instance_id: InstanceId) -> str | None:
|
||||
decode = state.instances.get(decode_instance_id)
|
||||
if decode is None:
|
||||
return None
|
||||
@@ -84,7 +88,7 @@ def _prefill_endpoint_for(
|
||||
return None
|
||||
|
||||
sources: set[InstanceId] = set()
|
||||
for link in instance_links:
|
||||
for link in state.instance_links.values():
|
||||
if decode_instance_id in link.decode_instances:
|
||||
sources.update(link.prefill_instances)
|
||||
sources.discard(decode_instance_id)
|
||||
@@ -126,8 +130,6 @@ class Master:
|
||||
local_event_receiver: Receiver[LocalForwarderEvent],
|
||||
global_event_sender: Sender[GlobalForwarderEvent],
|
||||
download_command_sender: Sender[ForwarderDownloadCommand],
|
||||
aggregator: LVAggregator,
|
||||
storage: Storage,
|
||||
):
|
||||
self.node_id = node_id
|
||||
self.session_id = session_id
|
||||
@@ -144,8 +146,6 @@ class Master:
|
||||
self._event_log = DiskEventLog(EXO_EVENT_LOG_DIR / "master")
|
||||
self._pending_traces: dict[TaskId, dict[int, list[TraceEventData]]] = {}
|
||||
self._expected_ranks: dict[TaskId, set[int]] = {}
|
||||
self.aggregator: LVAggregator = aggregator
|
||||
self.storage: Storage = storage
|
||||
|
||||
async def run(self):
|
||||
logger.info("Starting Master")
|
||||
@@ -182,21 +182,10 @@ class Master:
|
||||
pass
|
||||
case TextGeneration():
|
||||
# set-difference => prefill-only nodes
|
||||
instance_links: list[InstanceLink] = []
|
||||
prefill_only: set[InstanceId] = set()
|
||||
for _, link in (
|
||||
await self.storage.dump("instance_links/")
|
||||
).items():
|
||||
try:
|
||||
instance_links.append(
|
||||
InstanceLink.model_validate_json(link)
|
||||
)
|
||||
except ValidationError:
|
||||
continue
|
||||
|
||||
for link in instance_links:
|
||||
for link in self.state.instance_links.values():
|
||||
prefill_only.update(link.prefill_instances)
|
||||
for link in instance_links:
|
||||
for link in self.state.instance_links.values():
|
||||
prefill_only.difference_update(link.decode_instances)
|
||||
|
||||
for instance in self.state.instances.values():
|
||||
@@ -236,9 +225,7 @@ class Master:
|
||||
params = command.task_params.model_copy(
|
||||
update={
|
||||
"prefill_endpoint": _prefill_endpoint_for(
|
||||
self.state.with_aggregator(self.aggregator),
|
||||
instance_links,
|
||||
decode_instance_id,
|
||||
self.state, decode_instance_id
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -382,16 +369,15 @@ class Master:
|
||||
)
|
||||
generated_events.extend(transition_events)
|
||||
case PlaceInstance():
|
||||
state = self.state.with_aggregator(self.aggregator)
|
||||
placement = place_instance(
|
||||
command,
|
||||
state.topology,
|
||||
state.instances,
|
||||
state.node_memory,
|
||||
state.node_network,
|
||||
state.node_backends,
|
||||
download_status=state.downloads,
|
||||
node_rdma_ctl=state.node_rdma_ctl,
|
||||
self.state.topology,
|
||||
self.state.instances,
|
||||
self.state.node_memory,
|
||||
self.state.node_network,
|
||||
self.state.node_backends,
|
||||
download_status=self.state.downloads,
|
||||
node_rdma_ctl=self.state.node_rdma_ctl,
|
||||
)
|
||||
transition_events = get_transition_events(
|
||||
self.state.instances, placement, self.state.tasks
|
||||
@@ -442,6 +428,29 @@ class Master:
|
||||
f"Finished command {command.finished_command_id} finished"
|
||||
)
|
||||
|
||||
case AddCustomModelCard():
|
||||
generated_events.append(
|
||||
CustomModelCardAdded(model_card=command.model_card)
|
||||
)
|
||||
case DeleteCustomModelCard():
|
||||
generated_events.append(
|
||||
CustomModelCardDeleted(model_id=command.model_id)
|
||||
)
|
||||
case SetInstanceLink():
|
||||
link = InstanceLink(
|
||||
link_id=command.link_id,
|
||||
prefill_instances=list(
|
||||
dict.fromkeys(command.prefill_instances)
|
||||
),
|
||||
decode_instances=list(
|
||||
dict.fromkeys(command.decode_instances)
|
||||
),
|
||||
)
|
||||
generated_events.append(InstanceLinkCreated(link=link))
|
||||
case DeleteInstanceLink():
|
||||
generated_events.append(
|
||||
InstanceLinkDeleted(link_id=command.link_id)
|
||||
)
|
||||
case RequestEventLog():
|
||||
# We should just be able to send everything, since other buffers will ignore old messages
|
||||
# rate limit to 1000 at a time
|
||||
@@ -462,9 +471,7 @@ class Master:
|
||||
async def _plan(self) -> None:
|
||||
while True:
|
||||
# kill broken instances
|
||||
connected_node_ids = set(
|
||||
self.state.with_aggregator(self.aggregator).topology.list_nodes()
|
||||
)
|
||||
connected_node_ids = set(self.state.topology.list_nodes())
|
||||
for instance_id, instance in self.state.instances.items():
|
||||
for node_id in instance.shard_assignments.node_to_runner:
|
||||
if node_id not in connected_node_ids:
|
||||
@@ -474,9 +481,7 @@ class Master:
|
||||
break
|
||||
|
||||
# time out dead nodes
|
||||
for node_id, time in self.state.with_aggregator(
|
||||
self.aggregator
|
||||
).last_seen.items():
|
||||
for node_id, time in self.state.last_seen.items():
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
if now - time > timedelta(seconds=30):
|
||||
logger.info(f"Manually removing node {node_id} due to inactivity")
|
||||
|
||||
@@ -47,25 +47,6 @@ from exo.utils.channels import channel
|
||||
from exo.utils.info_gatherer.info_gatherer import NodeBackends
|
||||
|
||||
|
||||
class MockAggregator:
|
||||
def dump(self) -> dict[str, str]:
|
||||
return {}
|
||||
|
||||
|
||||
class MockStorage:
|
||||
async def get(self, _: str) -> None:
|
||||
return None
|
||||
|
||||
async def put(self, _1: str, _2: str) -> None:
|
||||
return None
|
||||
|
||||
async def delete(self, _: str) -> None:
|
||||
return None
|
||||
|
||||
async def dump(self, _: str) -> dict[str, str]:
|
||||
return {}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_master():
|
||||
node_id = get_node_zid()
|
||||
@@ -113,8 +94,6 @@ async def test_master():
|
||||
local_event_receiver=le_receiver,
|
||||
command_receiver=co_receiver,
|
||||
download_command_sender=fcds,
|
||||
aggregator=MockAggregator(), # pyright: ignore[reportArgumentType]
|
||||
storage=MockStorage(), # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
logger.info("run the master")
|
||||
async with anyio.create_task_group() as tg:
|
||||
|
||||
+124
-46
@@ -4,14 +4,19 @@ from datetime import datetime
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from exo.shared.types.common import NodeId
|
||||
from exo.shared.models.model_cards import ModelCard
|
||||
from exo.shared.types.common import ModelId, NodeId
|
||||
from exo.shared.types.events import (
|
||||
ChunkGenerated,
|
||||
CustomModelCardAdded,
|
||||
CustomModelCardDeleted,
|
||||
Event,
|
||||
IndexedEvent,
|
||||
InputChunkReceived,
|
||||
InstanceCreated,
|
||||
InstanceDeleted,
|
||||
InstanceLinkCreated,
|
||||
InstanceLinkDeleted,
|
||||
NodeDownloadProgress,
|
||||
NodeGatheredInfo,
|
||||
NodeTimedOut,
|
||||
@@ -27,6 +32,7 @@ from exo.shared.types.events import (
|
||||
TracesCollected,
|
||||
TracesMerged,
|
||||
)
|
||||
from exo.shared.types.instance_link import InstanceLink, InstanceLinkId
|
||||
from exo.shared.types.profiling import (
|
||||
NodeIdentity,
|
||||
NodeNetworkInfo,
|
||||
@@ -36,6 +42,7 @@ from exo.shared.types.profiling import (
|
||||
)
|
||||
from exo.shared.types.state import State
|
||||
from exo.shared.types.tasks import Task, TaskId, TaskStatus
|
||||
from exo.shared.types.topology import Connection, RDMAConnection
|
||||
from exo.shared.types.worker.downloads import DownloadProgress
|
||||
from exo.shared.types.worker.instances import Instance, InstanceId
|
||||
from exo.shared.types.worker.runners import (
|
||||
@@ -60,6 +67,18 @@ from exo.utils.info_gatherer.info_gatherer import (
|
||||
)
|
||||
|
||||
|
||||
def _is_rdma_ctl_enabled(
|
||||
node_id: NodeId, node_rdma_ctl: Mapping[NodeId, NodeRdmaCtlStatus]
|
||||
) -> bool:
|
||||
"""A node is RDMA-capable only if rdma_ctl status has been observed as enabled.
|
||||
|
||||
Missing entries default to ``False`` — if we have not yet observed (or the node
|
||||
cannot run) ``rdma_ctl``, it must not participate in an RDMA-backed instance.
|
||||
"""
|
||||
status = node_rdma_ctl.get(node_id)
|
||||
return status is not None and status.enabled
|
||||
|
||||
|
||||
def event_apply(event: Event, state: State) -> State:
|
||||
"""Apply an event to state."""
|
||||
match event:
|
||||
@@ -72,6 +91,10 @@ def event_apply(event: Event, state: State) -> State:
|
||||
| TracesMerged()
|
||||
): # Pass-through events that don't modify state
|
||||
return state
|
||||
case CustomModelCardAdded():
|
||||
return apply_custom_model_card_added(event, state)
|
||||
case CustomModelCardDeleted():
|
||||
return apply_custom_model_card_deleted(event, state)
|
||||
case InstanceCreated():
|
||||
return apply_instance_created(event, state)
|
||||
case InstanceDeleted():
|
||||
@@ -96,6 +119,10 @@ def event_apply(event: Event, state: State) -> State:
|
||||
return apply_topology_edge_created(event, state)
|
||||
case TopologyEdgeDeleted():
|
||||
return apply_topology_edge_deleted(event, state)
|
||||
case InstanceLinkCreated():
|
||||
return apply_instance_link_created(event, state)
|
||||
case InstanceLinkDeleted():
|
||||
return apply_instance_link_deleted(event, state)
|
||||
|
||||
|
||||
def apply(state: State, event: IndexedEvent) -> State:
|
||||
@@ -195,7 +222,38 @@ def apply_instance_deleted(event: InstanceDeleted, state: State) -> State:
|
||||
new_instances: Mapping[InstanceId, Instance] = {
|
||||
iid: inst for iid, inst in state.instances.items() if iid != event.instance_id
|
||||
}
|
||||
return state.model_copy(update={"instances": new_instances})
|
||||
new_links: dict[InstanceLinkId, InstanceLink] = {}
|
||||
for link_id, link in state.instance_links.items():
|
||||
prefill = [i for i in link.prefill_instances if i != event.instance_id]
|
||||
decode = [i for i in link.decode_instances if i != event.instance_id]
|
||||
if not prefill or not decode:
|
||||
continue
|
||||
if prefill == list(link.prefill_instances) and decode == list(
|
||||
link.decode_instances
|
||||
):
|
||||
new_links[link_id] = link
|
||||
else:
|
||||
new_links[link_id] = link.model_copy(
|
||||
update={"prefill_instances": prefill, "decode_instances": decode}
|
||||
)
|
||||
return state.model_copy(
|
||||
update={"instances": new_instances, "instance_links": new_links}
|
||||
)
|
||||
|
||||
|
||||
def apply_instance_link_created(event: InstanceLinkCreated, state: State) -> State:
|
||||
new_links: Mapping[InstanceLinkId, InstanceLink] = {
|
||||
**state.instance_links,
|
||||
event.link.link_id: event.link,
|
||||
}
|
||||
return state.model_copy(update={"instance_links": new_links})
|
||||
|
||||
|
||||
def apply_instance_link_deleted(event: InstanceLinkDeleted, state: State) -> State:
|
||||
new_links: Mapping[InstanceLinkId, InstanceLink] = {
|
||||
lid: link for lid, link in state.instance_links.items() if lid != event.link_id
|
||||
}
|
||||
return state.model_copy(update={"instance_links": new_links})
|
||||
|
||||
|
||||
def apply_runner_status_updated(event: RunnerStatusUpdated, state: State) -> State:
|
||||
@@ -350,26 +408,59 @@ def apply_node_gathered_info(event: NodeGatheredInfo, state: State) -> State:
|
||||
event.node_id: NodeThunderboltInfo(interfaces=info.idents),
|
||||
}
|
||||
case MacThunderboltConnections():
|
||||
update["node_thunderbolt_connections"] = {
|
||||
**state.node_thunderbolt_connections,
|
||||
event.node_id: info,
|
||||
conn_map = {
|
||||
tb_ident.domain_uuid: (nid, tb_ident.rdma_interface)
|
||||
for nid in state.node_thunderbolt
|
||||
for tb_ident in state.node_thunderbolt[nid].interfaces
|
||||
}
|
||||
source_is_rdma_enabled = _is_rdma_ctl_enabled(
|
||||
event.node_id, state.node_rdma_ctl
|
||||
)
|
||||
as_rdma_conns = [
|
||||
Connection(
|
||||
source=event.node_id,
|
||||
sink=conn_map[tb_conn.sink_uuid][0],
|
||||
edge=RDMAConnection(
|
||||
source_rdma_iface=conn_map[tb_conn.source_uuid][1],
|
||||
sink_rdma_iface=conn_map[tb_conn.sink_uuid][1],
|
||||
),
|
||||
)
|
||||
for tb_conn in info.conns
|
||||
if tb_conn.source_uuid in conn_map
|
||||
if tb_conn.sink_uuid in conn_map
|
||||
if source_is_rdma_enabled
|
||||
and _is_rdma_ctl_enabled(
|
||||
conn_map[tb_conn.sink_uuid][0], state.node_rdma_ctl
|
||||
)
|
||||
]
|
||||
topology.replace_all_out_rdma_connections(event.node_id, as_rdma_conns)
|
||||
case ThunderboltBridgeInfo():
|
||||
new_tb_bridge: dict[NodeId, ThunderboltBridgeStatus] = {
|
||||
**state.node_thunderbolt_bridge,
|
||||
event.node_id: info.status,
|
||||
}
|
||||
update["node_thunderbolt_bridge"] = new_tb_bridge
|
||||
update["thunderbolt_bridge_cycles"] = (
|
||||
topology.get_thunderbolt_bridge_cycles(
|
||||
new_tb_bridge, state.node_network
|
||||
# Only recompute cycles if the enabled status changed
|
||||
old_status = state.node_thunderbolt_bridge.get(event.node_id)
|
||||
old_enabled = old_status.enabled if old_status else False
|
||||
new_enabled = info.status.enabled
|
||||
if old_enabled != new_enabled:
|
||||
update["thunderbolt_bridge_cycles"] = (
|
||||
topology.get_thunderbolt_bridge_cycles(
|
||||
new_tb_bridge, state.node_network
|
||||
)
|
||||
)
|
||||
)
|
||||
case RdmaCtlStatus():
|
||||
update["node_rdma_ctl"] = {
|
||||
**state.node_rdma_ctl,
|
||||
event.node_id: NodeRdmaCtlStatus(enabled=info.enabled),
|
||||
}
|
||||
# If RDMA just got disabled on this node, drop any RDMA edges touching it
|
||||
# so placement / topology consumers cannot pick a disabled node for an
|
||||
# RDMA-backed instance. (Edges will repopulate on the next
|
||||
# MacThunderboltConnections poll once both endpoints are enabled again.)
|
||||
if not info.enabled:
|
||||
topology.remove_all_rdma_connections_touching(event.node_id)
|
||||
case NodeBackends():
|
||||
update["node_backends"] = {
|
||||
**state.node_backends,
|
||||
@@ -380,45 +471,32 @@ def apply_node_gathered_info(event: NodeGatheredInfo, state: State) -> State:
|
||||
|
||||
|
||||
def apply_topology_edge_created(event: TopologyEdgeCreated, state: State) -> State:
|
||||
source_connections = state.node_socket_connections.get(event.conn.source, {})
|
||||
sink_connections = source_connections.get(event.conn.sink, [])
|
||||
|
||||
update = {
|
||||
"node_socket_connections": {
|
||||
**state.node_socket_connections,
|
||||
event.conn.source: {
|
||||
**source_connections,
|
||||
event.conn.sink: sink_connections
|
||||
if event.conn.edge in sink_connections
|
||||
else [*sink_connections, event.conn.edge],
|
||||
},
|
||||
}
|
||||
}
|
||||
return state.model_copy(update=update)
|
||||
topology = copy.deepcopy(state.topology)
|
||||
topology.add_connection(event.conn)
|
||||
return state.model_copy(update={"topology": topology})
|
||||
|
||||
|
||||
def apply_topology_edge_deleted(event: TopologyEdgeDeleted, state: State) -> State:
|
||||
inner_update = {
|
||||
sink: final_edges
|
||||
for sink, edges in state.node_socket_connections.get(
|
||||
event.conn.source, {}
|
||||
).items()
|
||||
if (
|
||||
final_edges := [
|
||||
edge
|
||||
for edge in edges
|
||||
if (edge != event.conn.edge or sink != event.conn.sink)
|
||||
]
|
||||
)
|
||||
topology = copy.deepcopy(state.topology)
|
||||
topology.remove_connection(event.conn)
|
||||
# TODO: Clean up removing the reverse connection
|
||||
return state.model_copy(update={"topology": topology})
|
||||
|
||||
|
||||
def apply_custom_model_card_added(event: CustomModelCardAdded, state: State) -> State:
|
||||
new_cards: Mapping[ModelId, ModelCard] = {
|
||||
**state.custom_model_cards,
|
||||
event.model_card.model_id: event.model_card,
|
||||
}
|
||||
update = {
|
||||
"node_socket_connections": {
|
||||
source: maps
|
||||
for source, maps in {
|
||||
**state.node_socket_connections,
|
||||
event.conn.source: inner_update,
|
||||
}.items()
|
||||
if maps
|
||||
}
|
||||
return state.model_copy(update={"custom_model_cards": new_cards})
|
||||
|
||||
|
||||
def apply_custom_model_card_deleted(
|
||||
event: CustomModelCardDeleted, state: State
|
||||
) -> State:
|
||||
new_cards: Mapping[ModelId, ModelCard] = {
|
||||
model_id: card
|
||||
for model_id, card in state.custom_model_cards.items()
|
||||
if model_id != event.model_id
|
||||
}
|
||||
return state.model_copy(update=update)
|
||||
return state.model_copy(update={"custom_model_cards": new_cards})
|
||||
@@ -55,7 +55,7 @@ class _CardCache:
|
||||
except OSError as e:
|
||||
logger.warning(f"failed to save custom model card ({e.strerror})")
|
||||
|
||||
async def delete(self, model_id: ModelId) -> "ModelCard | None":
|
||||
async def pop(self, model_id: ModelId) -> "ModelCard | None":
|
||||
"""Delete a user-added custom model card. Returns True if deleted."""
|
||||
card_path = _custom_cards_dir / (ModelId(model_id).normalize() + ".toml")
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from exo.shared.apply import apply
|
||||
from exo.shared.models.model_cards import ModelCard, ModelTask
|
||||
from exo.shared.types.backends import Backend
|
||||
from exo.shared.types.common import ModelId
|
||||
from exo.shared.types.events import (
|
||||
CustomModelCardAdded,
|
||||
CustomModelCardDeleted,
|
||||
IndexedEvent,
|
||||
)
|
||||
from exo.shared.types.memory import Memory
|
||||
from exo.shared.types.state import State
|
||||
|
||||
|
||||
def _model_card(model_id: ModelId) -> ModelCard:
|
||||
return ModelCard(
|
||||
model_id=model_id,
|
||||
n_layers=1,
|
||||
storage_size=Memory.from_bytes(1),
|
||||
hidden_size=1,
|
||||
supports_tensor=True,
|
||||
tasks=[ModelTask.TextGeneration],
|
||||
backends=[Backend.MlxMetal],
|
||||
)
|
||||
|
||||
|
||||
def test_custom_model_card_added_is_reduced_into_state() -> None:
|
||||
card = _model_card(ModelId("custom/model"))
|
||||
|
||||
state = apply(
|
||||
State(),
|
||||
IndexedEvent(idx=0, event=CustomModelCardAdded(model_card=card)),
|
||||
)
|
||||
|
||||
assert state.custom_model_cards == {card.model_id: card}
|
||||
|
||||
|
||||
def test_custom_model_card_deleted_removes_card_from_state() -> None:
|
||||
card = _model_card(ModelId("custom/model"))
|
||||
state = State(custom_model_cards={card.model_id: card}, last_event_applied_idx=0)
|
||||
|
||||
state = apply(
|
||||
state,
|
||||
IndexedEvent(idx=1, event=CustomModelCardDeleted(model_id=card.model_id)),
|
||||
)
|
||||
|
||||
assert state.custom_model_cards == {}
|
||||
@@ -0,0 +1,72 @@
|
||||
from exo.shared.apply import (
|
||||
apply_instance_deleted,
|
||||
apply_instance_link_created,
|
||||
apply_instance_link_deleted,
|
||||
)
|
||||
from exo.shared.types.events import (
|
||||
InstanceDeleted,
|
||||
InstanceLinkCreated,
|
||||
InstanceLinkDeleted,
|
||||
)
|
||||
from exo.shared.types.instance_link import InstanceLink, InstanceLinkId
|
||||
from exo.shared.types.state import State
|
||||
from exo.shared.types.worker.instances import InstanceId
|
||||
|
||||
|
||||
def _link(
|
||||
prefill: list[InstanceId],
|
||||
decode: list[InstanceId],
|
||||
link_id: InstanceLinkId | None = None,
|
||||
) -> InstanceLink:
|
||||
return InstanceLink(
|
||||
link_id=link_id or InstanceLinkId(),
|
||||
prefill_instances=prefill,
|
||||
decode_instances=decode,
|
||||
)
|
||||
|
||||
|
||||
def test_create_link() -> None:
|
||||
state = State()
|
||||
link = _link([InstanceId("a")], [InstanceId("b")])
|
||||
new_state = apply_instance_link_created(InstanceLinkCreated(link=link), state)
|
||||
assert new_state.instance_links == {link.link_id: link}
|
||||
|
||||
|
||||
def test_update_replaces_existing_link() -> None:
|
||||
a, b, c = InstanceId("a"), InstanceId("b"), InstanceId("c")
|
||||
link = _link([a], [b])
|
||||
state = State(instance_links={link.link_id: link})
|
||||
|
||||
updated = link.model_copy(update={"decode_instances": [b, c]})
|
||||
new_state = apply_instance_link_created(InstanceLinkCreated(link=updated), state)
|
||||
assert set(new_state.instance_links[link.link_id].decode_instances) == {b, c}
|
||||
|
||||
|
||||
def test_delete_link() -> None:
|
||||
link = _link([InstanceId("a")], [InstanceId("b")])
|
||||
state = State(instance_links={link.link_id: link})
|
||||
|
||||
new_state = apply_instance_link_deleted(
|
||||
InstanceLinkDeleted(link_id=link.link_id), state
|
||||
)
|
||||
assert new_state.instance_links == {}
|
||||
|
||||
|
||||
def test_instance_deleted_strips_from_links() -> None:
|
||||
a, b, c = InstanceId("a"), InstanceId("b"), InstanceId("c")
|
||||
link = _link([a, c], [b])
|
||||
state = State(instance_links={link.link_id: link})
|
||||
|
||||
new_state = apply_instance_deleted(InstanceDeleted(instance_id=a), state)
|
||||
remaining = new_state.instance_links[link.link_id]
|
||||
assert remaining.prefill_instances == [c]
|
||||
assert remaining.decode_instances == [b]
|
||||
|
||||
|
||||
def test_instance_deleted_drops_link_when_role_empties() -> None:
|
||||
a, b = InstanceId("a"), InstanceId("b")
|
||||
link = _link([a], [b])
|
||||
state = State(instance_links={link.link_id: link})
|
||||
|
||||
new_state = apply_instance_deleted(InstanceDeleted(instance_id=a), state)
|
||||
assert link.link_id not in new_state.instance_links
|
||||
@@ -217,7 +217,7 @@ def test_topology_remove_all_rdma_connections_touching_keeps_socket_edges():
|
||||
)
|
||||
)
|
||||
socket_edge = SocketConnection(
|
||||
sink_multiaddr=Multiaddr(address="/ip4/10.0.0.1/tcp/8000"),
|
||||
sink_multiaddr=Multiaddr(address="/ip4/10.0.0.1/tcp/8000")
|
||||
)
|
||||
topology.add_connection(Connection(source=node_a, sink=node_b, edge=socket_edge))
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from exo.shared.types.common import NodeId
|
||||
from exo.shared.types.multiaddr import Multiaddr
|
||||
from exo.shared.types.state import State
|
||||
from exo.shared.types.topology import Connection, SocketConnection
|
||||
|
||||
|
||||
def test_state_serialization_roundtrip() -> None:
|
||||
"""Verify that State → JSON → State round-trip preserves topology."""
|
||||
|
||||
# --- build a simple state ------------------------------------------------
|
||||
node_a = NodeId("node-a")
|
||||
node_b = NodeId("node-b")
|
||||
|
||||
connection = Connection(
|
||||
source=node_a,
|
||||
sink=node_b,
|
||||
edge=SocketConnection(
|
||||
sink_multiaddr=Multiaddr(address="/ip4/127.0.0.1/tcp/10001"),
|
||||
),
|
||||
)
|
||||
|
||||
state = State()
|
||||
state.topology.add_connection(connection)
|
||||
|
||||
json_repr = state.model_dump_json()
|
||||
restored_state = State.model_validate_json(json_repr)
|
||||
|
||||
assert (
|
||||
state.topology.to_snapshot().nodes
|
||||
== restored_state.topology.to_snapshot().nodes
|
||||
)
|
||||
assert set(state.topology.to_snapshot().connections) == set(
|
||||
restored_state.topology.to_snapshot().connections
|
||||
)
|
||||
assert restored_state.model_dump_json() == json_repr
|
||||
@@ -1,8 +1,10 @@
|
||||
import contextlib
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterable
|
||||
|
||||
import rustworkx as rx
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from exo.shared.types.common import NodeId
|
||||
from exo.shared.types.profiling import (
|
||||
@@ -18,6 +20,15 @@ from exo.shared.types.topology import (
|
||||
)
|
||||
|
||||
|
||||
class TopologySnapshot(BaseModel):
|
||||
nodes: Sequence[NodeId]
|
||||
connections: Mapping[
|
||||
NodeId, Mapping[NodeId, Sequence[SocketConnection | RDMAConnection]]
|
||||
]
|
||||
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Topology:
|
||||
_graph: rx.PyDiGraph[NodeId, SocketConnection | RDMAConnection] = field(
|
||||
@@ -25,6 +36,28 @@ class Topology:
|
||||
)
|
||||
_vertex_indices: dict[NodeId, int] = field(init=False, default_factory=dict)
|
||||
|
||||
def to_snapshot(self) -> TopologySnapshot:
|
||||
return TopologySnapshot(
|
||||
nodes=list(self.list_nodes()), connections=self.map_connections()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_snapshot(cls, snapshot: TopologySnapshot) -> "Topology":
|
||||
topology = cls()
|
||||
|
||||
for node_id in snapshot.nodes:
|
||||
with contextlib.suppress(ValueError):
|
||||
topology.add_node(node_id)
|
||||
|
||||
for source in snapshot.connections:
|
||||
for sink in snapshot.connections[source]:
|
||||
for edge in snapshot.connections[source][sink]:
|
||||
topology.add_connection(
|
||||
Connection(source=source, sink=sink, edge=edge)
|
||||
)
|
||||
|
||||
return topology
|
||||
|
||||
def add_node(self, node_id: NodeId) -> None:
|
||||
if node_id in self._vertex_indices:
|
||||
return
|
||||
|
||||
@@ -7,6 +7,7 @@ from exo.api.types import (
|
||||
from exo.shared.models.model_cards import ModelCard, ModelId
|
||||
from exo.shared.types.chunks import InputImageChunk
|
||||
from exo.shared.types.common import CommandId, NodeId, SystemId
|
||||
from exo.shared.types.instance_link import InstanceLinkId
|
||||
from exo.shared.types.text_generation import TextGenerationTaskParams
|
||||
from exo.shared.types.worker.instances import Instance, InstanceId, InstanceMeta
|
||||
from exo.shared.types.worker.shards import Sharding, ShardMetadata
|
||||
@@ -81,6 +82,24 @@ class CancelDownload(BaseCommand):
|
||||
model_id: ModelId
|
||||
|
||||
|
||||
class AddCustomModelCard(BaseCommand):
|
||||
model_card: ModelCard
|
||||
|
||||
|
||||
class DeleteCustomModelCard(BaseCommand):
|
||||
model_id: ModelId
|
||||
|
||||
|
||||
class SetInstanceLink(BaseCommand):
|
||||
link_id: InstanceLinkId
|
||||
prefill_instances: list[InstanceId]
|
||||
decode_instances: list[InstanceId]
|
||||
|
||||
|
||||
class DeleteInstanceLink(BaseCommand):
|
||||
link_id: InstanceLinkId
|
||||
|
||||
|
||||
DownloadCommand = StartDownload | DeleteDownload | CancelDownload
|
||||
|
||||
|
||||
@@ -96,6 +115,10 @@ Command = (
|
||||
| TaskCancelled
|
||||
| TaskFinished
|
||||
| SendInputChunk
|
||||
| AddCustomModelCard
|
||||
| DeleteCustomModelCard
|
||||
| SetInstanceLink
|
||||
| DeleteInstanceLink
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ from typing import final
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from exo.shared.models.model_cards import ModelCard
|
||||
from exo.shared.topology import Connection
|
||||
from exo.shared.types.chunks import Chunk, InputImageChunk
|
||||
from exo.shared.types.common import CommandId, Id, NodeId, SessionId, SystemId
|
||||
from exo.shared.types.common import CommandId, Id, ModelId, NodeId, SessionId, SystemId
|
||||
from exo.shared.types.instance_link import InstanceLink, InstanceLinkId
|
||||
from exo.shared.types.tasks import Task, TaskId, TaskStatus
|
||||
from exo.shared.types.worker.downloads import DownloadProgress
|
||||
from exo.shared.types.worker.instances import Instance, InstanceId
|
||||
@@ -106,6 +108,14 @@ class TopologyEdgeDeleted(BaseEvent):
|
||||
conn: Connection
|
||||
|
||||
|
||||
class CustomModelCardAdded(BaseEvent):
|
||||
model_card: ModelCard
|
||||
|
||||
|
||||
class CustomModelCardDeleted(BaseEvent):
|
||||
model_id: ModelId
|
||||
|
||||
|
||||
@final
|
||||
class TraceEventData(FrozenModel):
|
||||
name: str
|
||||
@@ -128,6 +138,14 @@ class TracesMerged(BaseEvent):
|
||||
traces: list[TraceEventData]
|
||||
|
||||
|
||||
class InstanceLinkCreated(BaseEvent):
|
||||
link: InstanceLink
|
||||
|
||||
|
||||
class InstanceLinkDeleted(BaseEvent):
|
||||
link_id: InstanceLinkId
|
||||
|
||||
|
||||
Event = (
|
||||
TestEvent
|
||||
| TaskCreated
|
||||
@@ -147,6 +165,10 @@ Event = (
|
||||
| TopologyEdgeDeleted
|
||||
| TracesCollected
|
||||
| TracesMerged
|
||||
| CustomModelCardAdded
|
||||
| CustomModelCardDeleted
|
||||
| InstanceLinkCreated
|
||||
| InstanceLinkDeleted
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -16,10 +16,6 @@ class MemoryUsage(FrozenModel):
|
||||
swap_total: Memory
|
||||
swap_available: Memory
|
||||
|
||||
@classmethod
|
||||
def tag(cls) -> str:
|
||||
return cls.__name__
|
||||
|
||||
@classmethod
|
||||
def from_bytes(
|
||||
cls, *, ram_total: int, ram_available: int, swap_total: int, swap_available: int
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from collections.abc import Mapping, Sequence
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from exo_rs import LVAggregator
|
||||
from pydantic import ConfigDict, Field, model_serializer
|
||||
from pydantic import ConfigDict, Field, field_serializer, field_validator
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_core.core_schema import SerializerFunctionWrapHandler
|
||||
|
||||
from exo.shared.topology import Topology
|
||||
from exo.shared.models.model_cards import ModelCard
|
||||
from exo.shared.topology import Topology, TopologySnapshot
|
||||
from exo.shared.types.backends import Backend
|
||||
from exo.shared.types.common import NodeId
|
||||
from exo.shared.types.common import ModelId, NodeId
|
||||
from exo.shared.types.instance_link import InstanceLink, InstanceLinkId
|
||||
from exo.shared.types.profiling import (
|
||||
DiskUsage,
|
||||
MemoryUsage,
|
||||
@@ -21,15 +21,9 @@ from exo.shared.types.profiling import (
|
||||
ThunderboltBridgeStatus,
|
||||
)
|
||||
from exo.shared.types.tasks import Task, TaskId
|
||||
from exo.shared.types.topology import (
|
||||
Connection,
|
||||
RDMAConnection,
|
||||
SocketConnection,
|
||||
)
|
||||
from exo.shared.types.worker.downloads import DownloadProgress
|
||||
from exo.shared.types.worker.instances import Instance, InstanceId
|
||||
from exo.shared.types.worker.runners import RunnerId, RunnerStatus
|
||||
from exo.utils.info_gatherer.info_gatherer import MacThunderboltConnections
|
||||
from exo.utils.pydantic_ext import FrozenModel
|
||||
|
||||
|
||||
@@ -45,6 +39,7 @@ class State(FrozenModel):
|
||||
alias_generator=to_camel,
|
||||
validate_by_name=True,
|
||||
extra="forbid",
|
||||
# I want to reenable this ASAP, but it's causing an issue with TaskStatus
|
||||
strict=True,
|
||||
arbitrary_types_allowed=True,
|
||||
)
|
||||
@@ -53,6 +48,7 @@ class State(FrozenModel):
|
||||
downloads: Mapping[NodeId, Sequence[DownloadProgress]] = {}
|
||||
tasks: Mapping[TaskId, Task] = {}
|
||||
last_seen: Mapping[NodeId, datetime] = {}
|
||||
topology: Topology = Field(default_factory=Topology)
|
||||
last_event_applied_idx: int = Field(default=-1, ge=-1)
|
||||
|
||||
# Granular node state mappings (update independently at different frequencies)
|
||||
@@ -65,94 +61,34 @@ class State(FrozenModel):
|
||||
node_thunderbolt_bridge: Mapping[NodeId, ThunderboltBridgeStatus] = {}
|
||||
node_rdma_ctl: Mapping[NodeId, NodeRdmaCtlStatus] = {}
|
||||
node_backends: Mapping[NodeId, list[Backend]] = {}
|
||||
node_socket_connections: Mapping[
|
||||
NodeId, Mapping[NodeId, Sequence[SocketConnection]]
|
||||
] = {}
|
||||
node_thunderbolt_connections: Mapping[NodeId, MacThunderboltConnections] = {}
|
||||
|
||||
# Detected cycles where all nodes have Thunderbolt bridge enabled (>2 nodes)
|
||||
thunderbolt_bridge_cycles: Sequence[Sequence[NodeId]] = []
|
||||
|
||||
instance_links: Mapping[InstanceLinkId, InstanceLink] = {}
|
||||
prefill_server_ports: Mapping[RunnerId, int] = {}
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
|
||||
data = handler(self) # pyright: ignore[reportAny]
|
||||
data["topology"] = {
|
||||
"nodes": list(self.node_identities.keys()),
|
||||
"connections": self.topology.map_connections(),
|
||||
}
|
||||
return data # pyright: ignore[reportAny]
|
||||
# User-added model cards. Workers can reconcile their on-disk custom card cache
|
||||
custom_model_cards: Mapping[ModelId, ModelCard] = {}
|
||||
|
||||
@property
|
||||
def topology(self) -> Topology:
|
||||
topology = Topology()
|
||||
thunderbolt_by_uuid = {
|
||||
ident.domain_uuid: (node_id, ident.rdma_interface)
|
||||
for node_id, info in self.node_thunderbolt.items()
|
||||
for ident in info.interfaces
|
||||
}
|
||||
for node_id in self.node_identities:
|
||||
topology.add_node(node_id)
|
||||
@field_serializer("topology", mode="plain")
|
||||
def _encode_topology(self, value: Topology) -> TopologySnapshot:
|
||||
return value.to_snapshot()
|
||||
|
||||
for source, data in self.node_socket_connections.items():
|
||||
for sink, conns in data.items():
|
||||
for conn in conns:
|
||||
topology.add_connection(
|
||||
Connection(source=source, sink=sink, edge=conn)
|
||||
)
|
||||
@field_validator("topology", mode="before")
|
||||
@classmethod
|
||||
def _deserialize_topology(cls, value: object) -> Topology: # noqa: D401 – Pydantic validator signature
|
||||
"""Convert an incoming *value* into a :class:`Topology` instance.
|
||||
|
||||
for source, connections in self.node_thunderbolt_connections.items():
|
||||
if not self.node_rdma_ctl.get(
|
||||
source, NodeRdmaCtlStatus(enabled=False)
|
||||
).enabled:
|
||||
continue
|
||||
for connection in connections.conns:
|
||||
if (
|
||||
source_iface := thunderbolt_by_uuid.get(connection.source_uuid)
|
||||
) is None or (
|
||||
sink_iface := thunderbolt_by_uuid.get(connection.sink_uuid)
|
||||
) is None:
|
||||
continue
|
||||
if not self.node_rdma_ctl.get(
|
||||
sink_iface[0], NodeRdmaCtlStatus(enabled=False)
|
||||
).enabled:
|
||||
continue
|
||||
assert source_iface[0] == source, "registered invalid source uuid"
|
||||
topology.add_connection(
|
||||
Connection(
|
||||
source=source_iface[0],
|
||||
sink=sink_iface[0],
|
||||
edge=RDMAConnection(
|
||||
source_rdma_iface=source_iface[1],
|
||||
sink_rdma_iface=sink_iface[1],
|
||||
),
|
||||
)
|
||||
)
|
||||
Accepts either an already constructed :class:`Topology` or a mapping
|
||||
representing :class:`~shared.topology.TopologySnapshot`.
|
||||
"""
|
||||
|
||||
return topology
|
||||
if isinstance(value, Topology):
|
||||
return value
|
||||
|
||||
def with_aggregator(self, aggregator: LVAggregator) -> "State":
|
||||
from datetime import datetime, timezone
|
||||
if isinstance(value, Mapping): # likely a snapshot-dict coming from JSON
|
||||
snapshot = TopologySnapshot(**cast(dict[str, Any], value)) # type: ignore[arg-type]
|
||||
return Topology.from_snapshot(snapshot)
|
||||
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from exo.shared.apply import event_apply
|
||||
from exo.shared.types.events import NodeGatheredInfo
|
||||
from exo.utils.info_gatherer.info_gatherer import GatheredInfo
|
||||
|
||||
state = self.model_copy()
|
||||
for key, value in aggregator.dump().items():
|
||||
try:
|
||||
data = TypeAdapter[GatheredInfo](GatheredInfo).validate_json(value)
|
||||
node_id = NodeId(key.split("/")[0])
|
||||
event = NodeGatheredInfo(
|
||||
node_id=node_id, when=str(datetime.now(tz=timezone.utc)), info=data
|
||||
)
|
||||
state = event_apply(event, state)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"\n{'=' * 10}key: {key} with exception {str(e)}\nvalue: {value}{'=' * 10}\n"
|
||||
)
|
||||
|
||||
return state
|
||||
raise TypeError("Invalid representation for Topology field in State")
|
||||
@@ -10,13 +10,11 @@ from typing import Self, cast
|
||||
import anyio
|
||||
from anyio import fail_after, open_process, to_thread
|
||||
from anyio.streams.buffered import BufferedByteReceiveStream
|
||||
from exo_rs import LVPublisher, SessionHandle
|
||||
from loguru import logger
|
||||
from pydantic import ValidationError
|
||||
|
||||
from exo.shared.constants import EXO_CONFIG_FILE, EXO_DEFAULT_MODELS_DIR
|
||||
from exo.shared.types.backends import Backend
|
||||
from exo.shared.types.common import NodeId
|
||||
from exo.shared.types.memory import Memory
|
||||
from exo.shared.types.profiling import (
|
||||
DiskUsage,
|
||||
@@ -29,6 +27,7 @@ from exo.shared.types.thunderbolt import (
|
||||
ThunderboltConnectivity,
|
||||
ThunderboltIdentifier,
|
||||
)
|
||||
from exo.utils.channels import Sender
|
||||
from exo.utils.pydantic_ext import TaggedModel
|
||||
from exo.utils.task_group import TaskGroup
|
||||
|
||||
@@ -402,42 +401,10 @@ GatheredInfo = (
|
||||
|
||||
@dataclass
|
||||
class InfoGatherer:
|
||||
session_handle: SessionHandle
|
||||
node_id: NodeId
|
||||
info_senders: dict[str, LVPublisher] = field(init=False, default_factory=dict)
|
||||
info_sender: Sender[GatheredInfo]
|
||||
_tg: TaskGroup = field(init=False, default_factory=TaskGroup)
|
||||
_psutil_enabled: bool = field(init=False, default=False)
|
||||
|
||||
async def send(self, info: GatheredInfo):
|
||||
if (tag := info.tag()) not in self.info_senders:
|
||||
self.info_senders[tag] = self.session_handle.last_value_publisher(
|
||||
f"metrics/{self.node_id}/{tag}"
|
||||
)
|
||||
await self.info_senders[tag].put(info.model_dump_json())
|
||||
|
||||
async def run(self):
|
||||
async with self._tg as tg:
|
||||
if IS_DARWIN:
|
||||
tg.start_soon(self._monitor_macmon, 1)
|
||||
tg.start_soon(self._monitor_system_profiler_thunderbolt_data, 5)
|
||||
tg.start_soon(self._monitor_thunderbolt_bridge_status, 10)
|
||||
tg.start_soon(self._monitor_rdma_ctl_status, 10)
|
||||
if not IS_DARWIN:
|
||||
tg.start_soon(self._monitor_memory_usage, 1)
|
||||
tg.start_soon(self._watch_system_info, 10)
|
||||
tg.start_soon(self._monitor_misc, 60)
|
||||
tg.start_soon(self._monitor_static_info, 60)
|
||||
tg.start_soon(self._monitor_disk_usage, 30)
|
||||
|
||||
nc = await NodeConfig.gather()
|
||||
if nc is not None:
|
||||
await self.send(nc)
|
||||
|
||||
await self.send(await NodeBackends.gather())
|
||||
|
||||
def shutdown(self):
|
||||
self._tg.cancel_tasks()
|
||||
|
||||
async def _can_read_macmon_metrics(self, macmon_path: str) -> bool:
|
||||
try:
|
||||
with fail_after(5):
|
||||
@@ -474,11 +441,34 @@ class InfoGatherer:
|
||||
|
||||
return True
|
||||
|
||||
async def run(self):
|
||||
async with self._tg as tg:
|
||||
if IS_DARWIN:
|
||||
tg.start_soon(self._monitor_macmon, 1)
|
||||
tg.start_soon(self._monitor_system_profiler_thunderbolt_data, 5)
|
||||
tg.start_soon(self._monitor_thunderbolt_bridge_status, 10)
|
||||
tg.start_soon(self._monitor_rdma_ctl_status, 10)
|
||||
if not IS_DARWIN:
|
||||
tg.start_soon(self._monitor_memory_usage, 1)
|
||||
tg.start_soon(self._watch_system_info, 10)
|
||||
tg.start_soon(self._monitor_misc, 60)
|
||||
tg.start_soon(self._monitor_static_info, 60)
|
||||
tg.start_soon(self._monitor_disk_usage, 30)
|
||||
|
||||
nc = await NodeConfig.gather()
|
||||
if nc is not None:
|
||||
await self.info_sender.send(nc)
|
||||
|
||||
await self.info_sender.send(await NodeBackends.gather())
|
||||
|
||||
def shutdown(self):
|
||||
self._tg.cancel_tasks()
|
||||
|
||||
async def _monitor_static_info(self, static_info_poll_interval: float):
|
||||
while True:
|
||||
try:
|
||||
with fail_after(30):
|
||||
await self.send(await StaticNodeInformation.gather())
|
||||
await self.info_sender.send(await StaticNodeInformation.gather())
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).warning("Error gathering static node info")
|
||||
await anyio.sleep(static_info_poll_interval)
|
||||
@@ -487,7 +477,7 @@ class InfoGatherer:
|
||||
while True:
|
||||
try:
|
||||
with fail_after(10):
|
||||
await self.send(await MiscData.gather())
|
||||
await self.info_sender.send(await MiscData.gather())
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).warning("Error gathering misc data")
|
||||
await anyio.sleep(misc_poll_interval)
|
||||
@@ -508,10 +498,12 @@ class InfoGatherer:
|
||||
idents = [
|
||||
it for i in data if (it := i.ident(iface_map)) is not None
|
||||
]
|
||||
await self.send(MacThunderboltIdentifiers(idents=idents))
|
||||
await self.info_sender.send(
|
||||
MacThunderboltIdentifiers(idents=idents)
|
||||
)
|
||||
|
||||
conns = [it for i in data if (it := i.conn()) is not None]
|
||||
await self.send(MacThunderboltConnections(conns=conns))
|
||||
await self.info_sender.send(MacThunderboltConnections(conns=conns))
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).warning("Error gathering Thunderbolt data")
|
||||
await anyio.sleep(system_profiler_interval)
|
||||
@@ -528,7 +520,7 @@ class InfoGatherer:
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
await self.send(
|
||||
await self.info_sender.send(
|
||||
MemoryUsage.from_psutil(override_memory=override_memory)
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -540,7 +532,7 @@ class InfoGatherer:
|
||||
try:
|
||||
with fail_after(10):
|
||||
nics = await get_network_interfaces()
|
||||
await self.send(NodeNetworkInterfaces(ifaces=nics))
|
||||
await self.info_sender.send(NodeNetworkInterfaces(ifaces=nics))
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).warning("Error gathering network interfaces")
|
||||
await anyio.sleep(interface_watcher_interval)
|
||||
@@ -553,7 +545,7 @@ class InfoGatherer:
|
||||
with fail_after(30):
|
||||
curr = await ThunderboltBridgeInfo.gather()
|
||||
if curr is not None:
|
||||
await self.send(curr)
|
||||
await self.info_sender.send(curr)
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).warning(
|
||||
"Error gathering Thunderbolt Bridge status"
|
||||
@@ -565,7 +557,7 @@ class InfoGatherer:
|
||||
try:
|
||||
curr = await RdmaCtlStatus.gather()
|
||||
if curr is not None:
|
||||
await self.send(curr)
|
||||
await self.info_sender.send(curr)
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).warning("Error gathering RDMA ctl status")
|
||||
await anyio.sleep(rdma_ctl_poll_interval)
|
||||
@@ -574,7 +566,7 @@ class InfoGatherer:
|
||||
while True:
|
||||
try:
|
||||
with fail_after(5):
|
||||
await self.send(await NodeDiskUsage.gather())
|
||||
await self.info_sender.send(await NodeDiskUsage.gather())
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).warning("Error gathering disk usage")
|
||||
await anyio.sleep(disk_poll_interval)
|
||||
@@ -619,7 +611,7 @@ class InfoGatherer:
|
||||
)
|
||||
text = data.decode("utf-8", errors="replace").strip()
|
||||
metrics = MacmonMetrics.from_raw_json(text)
|
||||
await self.send(metrics)
|
||||
await self.info_sender.send(metrics)
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
f"MacMon produced no output for {read_timeout}s, restarting"
|
||||
|
||||
@@ -19,22 +19,18 @@ class FrozenModel(BaseModel):
|
||||
|
||||
|
||||
class TaggedModel(FrozenModel):
|
||||
@classmethod
|
||||
def tag(cls) -> str:
|
||||
return cls.__name__
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def _serialize(self, handler: SerializerFunctionWrapHandler):
|
||||
inner = handler(self) # pyright: ignore[reportAny]
|
||||
return {self.tag(): inner}
|
||||
return {self.__class__.__name__: inner}
|
||||
|
||||
@model_validator(mode="wrap")
|
||||
@classmethod
|
||||
def _validate(cls, v: Any, handler: ValidatorFunctionWrapHandler) -> Self: # pyright: ignore[reportAny]
|
||||
if isinstance(v, dict) and len(v) == 1 and cls.tag() in v: # pyright: ignore[reportUnknownArgumentType]
|
||||
return handler(v[cls.tag()]) # pyright: ignore[reportAny]
|
||||
if isinstance(v, dict) and len(v) == 1 and cls.__name__ in v: # pyright: ignore[reportUnknownArgumentType]
|
||||
return handler(v[cls.__name__]) # pyright: ignore[reportAny]
|
||||
|
||||
return handler(v) # pyright: ignore[reportAny]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.tag()}({super().__str__()})"
|
||||
return f"{self.__class__.__name__}({super().__str__()})"
|
||||
+18
-28
@@ -4,9 +4,7 @@ from datetime import datetime, timezone
|
||||
|
||||
import anyio
|
||||
from anyio import fail_after, to_thread
|
||||
from exo_rs import LVAggregator, SessionHandle
|
||||
from loguru import logger
|
||||
from pydantic import ValidationError
|
||||
|
||||
from exo.api.types import ImageEditsTaskParams
|
||||
from exo.download.download_utils import is_read_only_model_dir, resolve_existing_model
|
||||
@@ -16,8 +14,7 @@ from exo.routing.event_router import (
|
||||
)
|
||||
from exo.shared.apply import apply
|
||||
from exo.shared.constants import EXO_MAX_INSTANCE_RETRIES
|
||||
from exo.shared.models import model_cards
|
||||
from exo.shared.models.model_cards import ModelCard, ModelId
|
||||
from exo.shared.models.model_cards import ModelId, card_cache
|
||||
from exo.shared.types.chunks import InputImageChunk
|
||||
from exo.shared.types.commands import (
|
||||
DeleteInstance,
|
||||
@@ -56,7 +53,7 @@ from exo.shared.types.topology import Connection, SocketConnection
|
||||
from exo.shared.types.worker.downloads import DownloadCompleted
|
||||
from exo.shared.types.worker.instances import InstanceId
|
||||
from exo.shared.types.worker.runners import RunnerId
|
||||
from exo.utils.channels import Receiver, Sender
|
||||
from exo.utils.channels import Receiver, Sender, channel
|
||||
from exo.utils.info_gatherer.info_gatherer import GatheredInfo, InfoGatherer
|
||||
from exo.utils.info_gatherer.net_profile import check_reachable
|
||||
from exo.utils.keyed_backoff import KeyedBackoff
|
||||
@@ -76,7 +73,6 @@ class Worker:
|
||||
# but I think it's the correct way to be thinking about commands
|
||||
command_sender: Sender[ForwarderCommand],
|
||||
download_command_sender: Sender[ForwarderDownloadCommand],
|
||||
session_handle: SessionHandle,
|
||||
api_port: int,
|
||||
):
|
||||
self.node_id: NodeId = node_id
|
||||
@@ -102,17 +98,17 @@ class Worker:
|
||||
base=0.5, cap=10.0
|
||||
)
|
||||
self._stopped: anyio.Event = anyio.Event()
|
||||
self._sh: SessionHandle = session_handle
|
||||
self.aggregator: LVAggregator = session_handle.last_value_aggregator("metrics")
|
||||
|
||||
async def run(self):
|
||||
logger.info("Starting Worker")
|
||||
|
||||
info_gatherer: InfoGatherer = InfoGatherer(self._sh, self.node_id)
|
||||
info_send, info_recv = channel[GatheredInfo]()
|
||||
info_gatherer: InfoGatherer = InfoGatherer(info_send)
|
||||
|
||||
try:
|
||||
async with self._tg as tg:
|
||||
tg.start_soon(info_gatherer.run)
|
||||
tg.start_soon(self._forward_info, info_recv)
|
||||
tg.start_soon(self.plan_step)
|
||||
tg.start_soon(self._event_applier)
|
||||
tg.start_soon(self._poll_connection_updates)
|
||||
@@ -182,24 +178,17 @@ class Worker:
|
||||
] = img
|
||||
|
||||
async def _reconcile_custom_cards(self) -> None:
|
||||
storage = self._sh.storage_interface()
|
||||
while True:
|
||||
await anyio.sleep(10)
|
||||
target: list[ModelId] = []
|
||||
for _, value in (await storage.dump("custom_model_cards/")).items():
|
||||
try:
|
||||
card = ModelCard.model_validate_json(value)
|
||||
except ValidationError:
|
||||
await anyio.sleep(1)
|
||||
target = dict(self.state.custom_model_cards)
|
||||
for model_id, card in target.items():
|
||||
if card_cache.get(model_id) == card:
|
||||
continue
|
||||
target.append(card.model_id)
|
||||
if model_cards.card_cache.get(card.model_id) == card:
|
||||
continue
|
||||
logger.info(f"Registered new custom model card for {card.model_id}")
|
||||
await model_cards.card_cache.save(card)
|
||||
await card_cache.save(card)
|
||||
|
||||
for card in await model_cards.card_cache.list_all():
|
||||
for card in await card_cache.list_all():
|
||||
if card.model_id not in target:
|
||||
await model_cards.card_cache.delete(card.model_id)
|
||||
await card_cache.pop(card.model_id)
|
||||
|
||||
async def plan_step(self):
|
||||
while True:
|
||||
@@ -399,13 +388,14 @@ class Worker:
|
||||
|
||||
async def _poll_connection_updates(self):
|
||||
while True:
|
||||
state = self.state.with_aggregator(self.aggregator)
|
||||
edges = set(conn.edge for conn in state.topology.out_edges(self.node_id))
|
||||
edges = set(
|
||||
conn.edge for conn in self.state.topology.out_edges(self.node_id)
|
||||
)
|
||||
conns: defaultdict[NodeId, set[str]] = defaultdict(set)
|
||||
async for ip, nid in check_reachable(
|
||||
state.topology,
|
||||
self.state.topology,
|
||||
self.node_id,
|
||||
state.node_network,
|
||||
self.state.node_network,
|
||||
api_port=self.api_port,
|
||||
):
|
||||
if ip in conns[nid]:
|
||||
@@ -426,7 +416,7 @@ class Worker:
|
||||
)
|
||||
)
|
||||
|
||||
for conn in state.topology.out_edges(self.node_id):
|
||||
for conn in self.state.topology.out_edges(self.node_id):
|
||||
if not isinstance(conn.edge, SocketConnection):
|
||||
continue
|
||||
# ignore mDNS discovered connections
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ i=0
|
||||
for host; do
|
||||
colour=${colours[i++ % 4]}
|
||||
ssh -T -o BatchMode=yes -o ServerAliveInterval=30 "$host@$host" \
|
||||
"ENABLE_DISAGGREGATION=true EXO_ZENOH_NAMESPACE=$commit /nix/var/nix/profiles/default/bin/nix run $remote_installable" 2>&1 |
|
||||
"EXO_ZENOH_NAMESPACE=$commit /nix/var/nix/profiles/default/bin/nix run $remote_installable" 2>&1 |
|
||||
awk -v p="${colour}[${host}]${reset}" '{ print p $0; fflush() }' &
|
||||
done
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ build = [
|
||||
]
|
||||
mlx = [
|
||||
{ name = "mflux", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260512+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260506+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_x86_64.whl" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx-lm", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
@@ -461,7 +461,7 @@ mlx = [
|
||||
]
|
||||
mlx-cpu = [
|
||||
{ name = "mflux", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260512+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "(sys_platform == 'darwin' and extra == 'extra-3-exo-mlx-cpu') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260506+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "(sys_platform == 'darwin' and extra == 'extra-3-exo-mlx-cpu') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cpu') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra != 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra != 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra != 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_x86_64.whl" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cpu') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra != 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra != 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra != 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx-cpu", marker = "sys_platform == 'linux'" },
|
||||
@@ -473,7 +473,7 @@ mlx-cpu = [
|
||||
]
|
||||
mlx-cuda12 = [
|
||||
{ name = "mflux", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260512+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "(sys_platform == 'darwin' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260506+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "(sys_platform == 'darwin' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra != 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_x86_64.whl" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra != 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx-cuda-12", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx_cuda_12-0.32.0-py3-none-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra != 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
@@ -487,7 +487,7 @@ mlx-cuda12 = [
|
||||
]
|
||||
mlx-cuda13 = [
|
||||
{ name = "mflux", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260512+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "(sys_platform == 'darwin' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260506+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "(sys_platform == 'darwin' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_x86_64.whl" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx-cuda-13", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx_cuda_13-0.32.0-py3-none-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none')" },
|
||||
@@ -1343,7 +1343,7 @@ dependencies = [
|
||||
{ name = "hf-transfer", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "matplotlib", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260512+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260506+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_x86_64.whl" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
@@ -1374,7 +1374,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/55/fa/96d4cc7ada2833571
|
||||
|
||||
[[package]]
|
||||
name = "mlx"
|
||||
version = "0.32.0.dev20260512+cc3f3e60"
|
||||
version = "0.32.0.dev20260506+cc3f3e60"
|
||||
source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }
|
||||
resolution-markers = [
|
||||
"sys_platform == 'darwin'",
|
||||
@@ -1549,7 +1549,7 @@ version = "0.31.3"
|
||||
source = { git = "https://github.com/rltakashige/mlx-lm?branch=leo%2Fdeepseek-v4#6a3df6cd6b00a347ee40f12d97a182aaf86ea599" }
|
||||
dependencies = [
|
||||
{ name = "jinja2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260512+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260506+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
@@ -1565,7 +1565,7 @@ dependencies = [
|
||||
{ name = "datasets", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "fastapi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "miniaudio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260512+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0.dev20260506+cc3f3e60", source = { git = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv.git?branch=address-rdma-gpu-locks#cc3f3e60be1289506125f2fa19b73b05aa770df8" }, marker = "sys_platform == 'darwin' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_aarch64.whl" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine != 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx", version = "0.32.0", source = { url = "https://github.com/rltakashige/mlx-jaccl-fix-small-recv/releases/download/mlx_cuda/mlx-0.32.0-cp313-cp313-manylinux_2_35_x86_64.whl" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (platform_machine == 'aarch64' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (sys_platform != 'linux' and extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
{ name = "mlx-lm", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda12') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cpu' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-cuda13') or (extra == 'extra-3-exo-mlx-cuda12' and extra == 'extra-3-exo-mlx-none') or (extra == 'extra-3-exo-mlx-cuda13' and extra == 'extra-3-exo-mlx-none')" },
|
||||
|
||||
Reference in new issue
Block a user