blitzwing 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/package.json +2 -1
- package/runtime/app.py +398 -0
- package/runtime/http_chain.py +189 -0
- package/runtime/inference.py +13 -0
- package/runtime/local_runner.py +148 -0
- package/runtime/tensor_codec.py +26 -0
- package/src/heartbeat.js +4 -1
- package/src/install.js +51 -180
- package/src/tunnel.js +231 -0
- package/src/wizard.js +68 -105
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Petals client that only talks to the local server (no remote DHT)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional, Tuple
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_range(block_indices: str) -> Tuple[int, int]:
|
|
19
|
+
start_s, end_s = block_indices.split(":")
|
|
20
|
+
return int(start_s), int(end_s)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _peer_from_log(log_path: str, port: int) -> Optional[str]:
|
|
24
|
+
try:
|
|
25
|
+
text = Path(log_path).read_text(encoding="utf-8", errors="ignore")
|
|
26
|
+
except OSError:
|
|
27
|
+
return None
|
|
28
|
+
matches = re.findall(
|
|
29
|
+
r"Running a server on \['[^']+/p2p/([A-Za-z0-9]+)'\]",
|
|
30
|
+
text,
|
|
31
|
+
)
|
|
32
|
+
if not matches:
|
|
33
|
+
return None
|
|
34
|
+
return f"/ip4/127.0.0.1/tcp/{port}/p2p/{matches[-1]}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LocalShardRunner:
|
|
38
|
+
"""Forward pass through this node's Petals server blocks only."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
model_name: str,
|
|
43
|
+
block_indices: str,
|
|
44
|
+
*,
|
|
45
|
+
local_port: int,
|
|
46
|
+
log_path: Optional[str] = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
self.model_name = model_name
|
|
49
|
+
self.block_start, self.block_end = parse_range(block_indices)
|
|
50
|
+
self.local_port = local_port
|
|
51
|
+
if log_path:
|
|
52
|
+
self.log_path = log_path
|
|
53
|
+
else:
|
|
54
|
+
explicit = os.getenv("PETALS_SERVER_LOG") or os.getenv("CONTRIB_SHARD_LOG")
|
|
55
|
+
if explicit:
|
|
56
|
+
self.log_path = explicit
|
|
57
|
+
else:
|
|
58
|
+
self.log_path = str(Path.home() / ".blitzwing" / "petals.log")
|
|
59
|
+
self._model = None
|
|
60
|
+
self._tokenizer = None
|
|
61
|
+
self._lock = threading.Lock()
|
|
62
|
+
|
|
63
|
+
def _resolve_local_peer(self, *, timeout_seconds: float = 90.0) -> str:
|
|
64
|
+
deadline = time.time() + timeout_seconds
|
|
65
|
+
while time.time() < deadline:
|
|
66
|
+
peer = _peer_from_log(self.log_path, self.local_port)
|
|
67
|
+
if peer:
|
|
68
|
+
return peer
|
|
69
|
+
time.sleep(1.0)
|
|
70
|
+
raise RuntimeError(f"Local Petals peer not found in {self.log_path}")
|
|
71
|
+
|
|
72
|
+
def _ensure_loaded(self) -> None:
|
|
73
|
+
with self._lock:
|
|
74
|
+
if self._model is not None:
|
|
75
|
+
return
|
|
76
|
+
local_peer = self._resolve_local_peer()
|
|
77
|
+
from transformers import AutoTokenizer
|
|
78
|
+
|
|
79
|
+
from petals import AutoDistributedModelForCausalLM
|
|
80
|
+
|
|
81
|
+
logger.info(
|
|
82
|
+
"Loading local-only Petals client blocks=%s:%s peer=%s",
|
|
83
|
+
self.block_start,
|
|
84
|
+
self.block_end,
|
|
85
|
+
local_peer,
|
|
86
|
+
)
|
|
87
|
+
self._tokenizer = AutoTokenizer.from_pretrained(self.model_name, use_fast=True)
|
|
88
|
+
if self._tokenizer.pad_token is None:
|
|
89
|
+
self._tokenizer.pad_token = self._tokenizer.eos_token
|
|
90
|
+
self._model = AutoDistributedModelForCausalLM.from_pretrained(
|
|
91
|
+
self.model_name,
|
|
92
|
+
initial_peers=[local_peer],
|
|
93
|
+
torch_dtype=torch.float32,
|
|
94
|
+
update_period=30,
|
|
95
|
+
)
|
|
96
|
+
logger.info("Local-only Petals client ready")
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def model(self):
|
|
100
|
+
self._ensure_loaded()
|
|
101
|
+
assert self._model is not None
|
|
102
|
+
return self._model
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def tokenizer(self):
|
|
106
|
+
self._ensure_loaded()
|
|
107
|
+
assert self._tokenizer is not None
|
|
108
|
+
return self._tokenizer
|
|
109
|
+
|
|
110
|
+
def _layers(self):
|
|
111
|
+
m = self.model
|
|
112
|
+
if hasattr(m, "model") and hasattr(m.model, "layers"):
|
|
113
|
+
return m.model.layers
|
|
114
|
+
if hasattr(m, "transformer") and hasattr(m.transformer, "h"):
|
|
115
|
+
return m.transformer.h
|
|
116
|
+
raise RuntimeError("Could not locate transformer layers")
|
|
117
|
+
|
|
118
|
+
def _embed(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
119
|
+
m = self.model
|
|
120
|
+
if hasattr(m, "model") and hasattr(m.model, "embed_tokens"):
|
|
121
|
+
return m.model.embed_tokens(input_ids)
|
|
122
|
+
if hasattr(m, "transformer") and hasattr(m.transformer, "word_embeddings"):
|
|
123
|
+
return m.transformer.word_embeddings(input_ids)
|
|
124
|
+
raise RuntimeError("Could not locate embedding layer")
|
|
125
|
+
|
|
126
|
+
def _lm_head(self, hidden: torch.Tensor) -> torch.Tensor:
|
|
127
|
+
m = self.model
|
|
128
|
+
if hasattr(m, "lm_head"):
|
|
129
|
+
return m.lm_head(hidden)
|
|
130
|
+
if hasattr(m, "model") and hasattr(m.model, "lm_head"):
|
|
131
|
+
return m.model.lm_head(hidden)
|
|
132
|
+
raise RuntimeError("Could not locate lm_head")
|
|
133
|
+
|
|
134
|
+
def forward_prefix(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
135
|
+
hidden = self._embed(input_ids)
|
|
136
|
+
layers = self._layers()
|
|
137
|
+
for idx in range(self.block_start, self.block_end):
|
|
138
|
+
hidden = layers[idx](hidden)
|
|
139
|
+
return hidden
|
|
140
|
+
|
|
141
|
+
def forward_tail(self, hidden: torch.Tensor) -> torch.Tensor:
|
|
142
|
+
layers = self._layers()
|
|
143
|
+
for idx in range(self.block_start, self.block_end):
|
|
144
|
+
hidden = layers[idx](hidden)
|
|
145
|
+
return hidden
|
|
146
|
+
|
|
147
|
+
def logits_from_hidden(self, hidden: torch.Tensor) -> torch.Tensor:
|
|
148
|
+
return self._lm_head(hidden)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Serialize torch tensors for HTTP transfer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def tensor_to_payload(tensor: torch.Tensor) -> Dict[str, Any]:
|
|
13
|
+
arr = tensor.detach().cpu().contiguous().numpy()
|
|
14
|
+
return {
|
|
15
|
+
"b64": base64.b64encode(arr.tobytes()).decode("ascii"),
|
|
16
|
+
"shape": list(arr.shape),
|
|
17
|
+
"dtype": str(arr.dtype),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def tensor_from_payload(payload: Dict[str, Any]) -> torch.Tensor:
|
|
22
|
+
arr = np.frombuffer(
|
|
23
|
+
base64.b64decode(payload["b64"]),
|
|
24
|
+
dtype=np.dtype(payload["dtype"]),
|
|
25
|
+
).reshape(payload["shape"])
|
|
26
|
+
return torch.from_numpy(arr.copy())
|
package/src/heartbeat.js
CHANGED
|
@@ -31,7 +31,10 @@ async function beat() {
|
|
|
31
31
|
"Content-Type": "application/json",
|
|
32
32
|
"ngrok-skip-browser-warning": "true",
|
|
33
33
|
},
|
|
34
|
-
body: JSON.stringify({
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
host_id: hostId,
|
|
36
|
+
block_indices: process.env.BLOCK_INDICES || undefined,
|
|
37
|
+
}),
|
|
35
38
|
});
|
|
36
39
|
} catch {}
|
|
37
40
|
}
|
package/src/install.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { spawn, execFileSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { HOME_DIR, VENV_PATH, SHARD_PORT, PETALS_PORT } from "./config.js";
|
|
5
6
|
import { which } from "./net.js";
|
|
6
7
|
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const PACKAGE_ROOT = path.join(__dirname, "..");
|
|
10
|
+
const RUNTIME_SRC = path.join(PACKAGE_ROOT, "runtime");
|
|
11
|
+
const RUNTIME_DEST = path.join(HOME_DIR, "runtime");
|
|
12
|
+
|
|
7
13
|
function venvPython() {
|
|
8
14
|
if (process.platform === "win32") {
|
|
9
15
|
return path.join(VENV_PATH, "Scripts", "python.exe");
|
|
@@ -45,7 +51,6 @@ function pinnedPythonPath() {
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export function ensurePython() {
|
|
48
|
-
// Petals/hivemind break on 3.12+. Never fall back to a too-new interpreter.
|
|
49
54
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
50
55
|
const candidates = [];
|
|
51
56
|
const envBin = process.env.BLITZWING_PYTHON || process.env.PETALS_PYTHON;
|
|
@@ -101,11 +106,7 @@ export function ensurePython() {
|
|
|
101
106
|
|
|
102
107
|
function petalsImportable(python) {
|
|
103
108
|
try {
|
|
104
|
-
execFileSync(
|
|
105
|
-
python,
|
|
106
|
-
["-c", "import petals.cli.run_server"],
|
|
107
|
-
{ stdio: "ignore" }
|
|
108
|
-
);
|
|
109
|
+
execFileSync(python, ["-c", "import petals.cli.run_server"], { stdio: "ignore" });
|
|
109
110
|
return true;
|
|
110
111
|
} catch {
|
|
111
112
|
return false;
|
|
@@ -114,20 +115,21 @@ function petalsImportable(python) {
|
|
|
114
115
|
|
|
115
116
|
function uvicornImportable(python) {
|
|
116
117
|
try {
|
|
117
|
-
execFileSync(python, ["-c", "import uvicorn, fastapi"], { stdio: "ignore" });
|
|
118
|
+
execFileSync(python, ["-c", "import uvicorn, fastapi, httpx, numpy"], { stdio: "ignore" });
|
|
118
119
|
return true;
|
|
119
120
|
} catch {
|
|
120
121
|
return false;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
function ensureShardManagerDeps(python,
|
|
125
|
+
function ensureShardManagerDeps(python, _pip, env, onLog) {
|
|
125
126
|
if (uvicornImportable(python)) return;
|
|
126
|
-
onLog?.("Installing shard manager deps (uvicorn, fastapi)...");
|
|
127
|
-
execFileSync(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
onLog?.("Installing shard manager deps (uvicorn, fastapi, httpx, numpy)...");
|
|
128
|
+
execFileSync(
|
|
129
|
+
python,
|
|
130
|
+
["-m", "pip", "install", "fastapi", "uvicorn[standard]", "pydantic", "httpx", "numpy"],
|
|
131
|
+
{ stdio: "inherit", env }
|
|
132
|
+
);
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
export function ensureVenvAndPetals({ onLog }) {
|
|
@@ -155,11 +157,10 @@ export function ensureVenvAndPetals({ onLog }) {
|
|
|
155
157
|
|
|
156
158
|
if (process.platform === "win32") {
|
|
157
159
|
throw new Error(
|
|
158
|
-
"Petals cannot
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
" powershell -File scripts/run_local_e2e.ps1"
|
|
160
|
+
"Petals cannot run on native Windows (needs Linux).\n" +
|
|
161
|
+
"Install Node in WSL, then:\n" +
|
|
162
|
+
" npm i -g blitzwing\n" +
|
|
163
|
+
" blitzwing"
|
|
163
164
|
);
|
|
164
165
|
}
|
|
165
166
|
|
|
@@ -173,12 +174,10 @@ export function ensureVenvAndPetals({ onLog }) {
|
|
|
173
174
|
["-m", "pip", "install", "torch", "--index-url", "https://download.pytorch.org/whl/cpu"],
|
|
174
175
|
{ stdio: "inherit", env }
|
|
175
176
|
);
|
|
176
|
-
// Needed to compile hivemind protobufs during --no-build-isolation install
|
|
177
177
|
execFileSync(python, ["-m", "pip", "install", "grpcio", "grpcio-tools", "protobuf"], {
|
|
178
178
|
stdio: "inherit",
|
|
179
179
|
env,
|
|
180
180
|
});
|
|
181
|
-
// Hivemind first with no build isolation (needs pkg_resources from setuptools<81)
|
|
182
181
|
execFileSync(
|
|
183
182
|
python,
|
|
184
183
|
[
|
|
@@ -195,180 +194,53 @@ export function ensureVenvAndPetals({ onLog }) {
|
|
|
195
194
|
["-m", "pip", "install", "--no-build-isolation", "git+https://github.com/bigscience-workshop/petals.git"],
|
|
196
195
|
{ stdio: "inherit", env }
|
|
197
196
|
);
|
|
198
|
-
execFileSync(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
execFileSync(
|
|
198
|
+
python,
|
|
199
|
+
["-m", "pip", "install", "fastapi", "uvicorn[standard]", "pydantic", "httpx", "numpy"],
|
|
200
|
+
{ stdio: "inherit", env }
|
|
201
|
+
);
|
|
202
202
|
|
|
203
203
|
fs.writeFileSync(path.join(HOME_DIR, "python"), python + "\n");
|
|
204
204
|
return { python, pip };
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
208
|
+
* Copy packaged runtime Python modules into ~/.blitzwing/runtime.
|
|
209
|
+
* Contributors never need the monorepo — everything ships in the npm package.
|
|
210
210
|
*/
|
|
211
|
-
export function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
block_indices: str = Field(..., pattern=r"^\\d+:\\d+$")
|
|
223
|
-
initial_peers: Optional[List[str]] = None
|
|
224
|
-
|
|
225
|
-
class StatusResponse(BaseModel):
|
|
226
|
-
running: bool
|
|
227
|
-
pid: Optional[int] = None
|
|
228
|
-
block_indices: Optional[str] = None
|
|
229
|
-
model: str
|
|
230
|
-
public_ip: Optional[str] = None
|
|
231
|
-
port: int
|
|
232
|
-
last_exit_code: Optional[int] = None
|
|
233
|
-
|
|
234
|
-
class Mgr:
|
|
235
|
-
def __init__(self):
|
|
236
|
-
self.model = os.environ["MODEL_NAME"]
|
|
237
|
-
self.public_ip = os.environ.get("PUBLIC_IP")
|
|
238
|
-
self.port = int(os.environ.get("PETALS_PORT", "31337"))
|
|
239
|
-
self.identity_path = os.environ.get("IDENTITY_PATH", os.path.expanduser("~/.blitzwing/petals-identity"))
|
|
240
|
-
self.block_indices = os.environ["BLOCK_INDICES"]
|
|
241
|
-
peers = [p.strip() for p in os.environ.get("INITIAL_PEERS", "").split(",") if p.strip()]
|
|
242
|
-
self.initial_peers = peers
|
|
243
|
-
announce_raw = os.environ.get("ANNOUNCE_MADDRS", "")
|
|
244
|
-
self.announce_maddrs = [a.strip() for a in announce_raw.split(",") if a.strip()]
|
|
245
|
-
self.python = os.environ.get("PETALS_PYTHON", "python")
|
|
246
|
-
self.petals_log = os.environ.get("PETALS_LOG", os.path.expanduser("~/.blitzwing/petals.log"))
|
|
247
|
-
self._proc = None
|
|
248
|
-
self._log_fp = None
|
|
249
|
-
self._lock = threading.Lock()
|
|
250
|
-
self.last_exit_code = None
|
|
251
|
-
|
|
252
|
-
def cmd(self, bi):
|
|
253
|
-
c = [self.python, "-m", "petals.cli.run_server", self.model,
|
|
254
|
-
"--device", "cpu", "--quant_type", "none",
|
|
255
|
-
"--block_indices", bi, "--port", str(self.port),
|
|
256
|
-
"--identity_path", self.identity_path, "--num_handlers", "1"]
|
|
257
|
-
if self.announce_maddrs:
|
|
258
|
-
c += ["--announce_maddrs", *self.announce_maddrs]
|
|
259
|
-
elif self.public_ip:
|
|
260
|
-
c += ["--public_ip", self.public_ip]
|
|
261
|
-
if self.initial_peers:
|
|
262
|
-
c += ["--initial_peers", *self.initial_peers]
|
|
263
|
-
use_auto_relay = os.environ.get("PETALS_USE_AUTO_RELAY", "1") not in ("0", "false", "False")
|
|
264
|
-
if not self.announce_maddrs and use_auto_relay:
|
|
265
|
-
pass
|
|
266
|
-
elif not use_auto_relay:
|
|
267
|
-
c += ["--no_auto_relay"]
|
|
268
|
-
if os.environ.get("PETALS_SKIP_REACHABILITY_CHECK", "1") in ("1", "true", "True"):
|
|
269
|
-
c += ["--skip_reachability_check"]
|
|
270
|
-
return c
|
|
271
|
-
|
|
272
|
-
def start(self, bi=None):
|
|
273
|
-
with self._lock:
|
|
274
|
-
if bi: self.block_indices = bi
|
|
275
|
-
if self._proc and self._proc.poll() is None:
|
|
276
|
-
return
|
|
277
|
-
if self._log_fp is None:
|
|
278
|
-
os.makedirs(os.path.dirname(self.petals_log) or ".", exist_ok=True)
|
|
279
|
-
self._log_fp = open(self.petals_log, "a", encoding="utf-8")
|
|
280
|
-
popen_kwargs = {"stdout": self._log_fp, "stderr": subprocess.STDOUT}
|
|
281
|
-
if os.name == "nt":
|
|
282
|
-
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
283
|
-
else:
|
|
284
|
-
popen_kwargs["preexec_fn"] = os.setsid
|
|
285
|
-
self._proc = subprocess.Popen(self.cmd(self.block_indices), **popen_kwargs)
|
|
286
|
-
self.last_exit_code = None
|
|
287
|
-
|
|
288
|
-
def stop(self):
|
|
289
|
-
with self._lock:
|
|
290
|
-
if not self._proc or self._proc.poll() is not None:
|
|
291
|
-
self._proc = None
|
|
292
|
-
return
|
|
293
|
-
p = self._proc
|
|
294
|
-
try:
|
|
295
|
-
if os.name == "nt":
|
|
296
|
-
p.send_signal(signal.CTRL_BREAK_EVENT)
|
|
297
|
-
else:
|
|
298
|
-
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
|
|
299
|
-
except Exception:
|
|
300
|
-
p.send_signal(signal.SIGTERM)
|
|
301
|
-
try: p.wait(timeout=30)
|
|
302
|
-
except Exception:
|
|
303
|
-
p.kill(); p.wait(timeout=10)
|
|
304
|
-
self.last_exit_code = p.returncode
|
|
305
|
-
self._proc = None
|
|
306
|
-
|
|
307
|
-
def reload(self, bi, initial_peers=None):
|
|
308
|
-
if initial_peers is not None:
|
|
309
|
-
self.initial_peers = initial_peers
|
|
310
|
-
self.stop(); time.sleep(1); self.start(bi)
|
|
311
|
-
|
|
312
|
-
def status(self):
|
|
313
|
-
if self._proc is not None and self._proc.poll() is not None:
|
|
314
|
-
self.last_exit_code = self._proc.returncode
|
|
315
|
-
self._proc = None
|
|
316
|
-
running = self._proc is not None and self._proc.poll() is None
|
|
317
|
-
return StatusResponse(
|
|
318
|
-
running=running,
|
|
319
|
-
pid=(self._proc.pid if running else None),
|
|
320
|
-
block_indices=self.block_indices,
|
|
321
|
-
model=self.model,
|
|
322
|
-
public_ip=self.public_ip,
|
|
323
|
-
port=self.port,
|
|
324
|
-
last_exit_code=self.last_exit_code,
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
mgr = Mgr()
|
|
328
|
-
app = FastAPI()
|
|
329
|
-
|
|
330
|
-
@app.on_event("startup")
|
|
331
|
-
def _up():
|
|
332
|
-
mgr.start()
|
|
333
|
-
|
|
334
|
-
@app.on_event("shutdown")
|
|
335
|
-
def _down():
|
|
336
|
-
mgr.stop()
|
|
337
|
-
|
|
338
|
-
@app.get("/status")
|
|
339
|
-
def status():
|
|
340
|
-
return mgr.status()
|
|
341
|
-
|
|
342
|
-
@app.post("/reload")
|
|
343
|
-
def reload(body: ReloadRequest):
|
|
344
|
-
a,b = map(int, body.block_indices.split(":"))
|
|
345
|
-
if b <= a: raise HTTPException(400, "bad range")
|
|
346
|
-
mgr.reload(body.block_indices, initial_peers=body.initial_peers)
|
|
347
|
-
time.sleep(0.5)
|
|
348
|
-
return mgr.status()
|
|
211
|
+
export function syncRuntimeFiles() {
|
|
212
|
+
if (!fs.existsSync(RUNTIME_SRC)) {
|
|
213
|
+
throw new Error(`CLI runtime missing at ${RUNTIME_SRC} — reinstall the blitzwing package`);
|
|
214
|
+
}
|
|
215
|
+
fs.mkdirSync(RUNTIME_DEST, { recursive: true });
|
|
216
|
+
for (const name of fs.readdirSync(RUNTIME_SRC)) {
|
|
217
|
+
if (!name.endsWith(".py")) continue;
|
|
218
|
+
fs.copyFileSync(path.join(RUNTIME_SRC, name), path.join(RUNTIME_DEST, name));
|
|
219
|
+
}
|
|
220
|
+
return path.join(RUNTIME_DEST, "app.py");
|
|
221
|
+
}
|
|
349
222
|
|
|
350
|
-
@
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
`;
|
|
354
|
-
fs.writeFileSync(appPath, code);
|
|
355
|
-
return appPath;
|
|
223
|
+
/** @deprecated use syncRuntimeFiles */
|
|
224
|
+
export function writeLocalShardManager() {
|
|
225
|
+
return syncRuntimeFiles();
|
|
356
226
|
}
|
|
357
227
|
|
|
358
|
-
export function startShardManagerProcess({
|
|
359
|
-
|
|
360
|
-
env,
|
|
361
|
-
logPath,
|
|
362
|
-
}) {
|
|
363
|
-
const appPath = writeLocalShardManager();
|
|
228
|
+
export function startShardManagerProcess({ python, env, logPath }) {
|
|
229
|
+
const appPath = syncRuntimeFiles();
|
|
364
230
|
const out = fs.openSync(logPath, "a");
|
|
365
231
|
const petalsPy = petalsImportable(venvPython()) ? venvPython() : python;
|
|
366
232
|
const child = spawn(
|
|
367
233
|
petalsPy,
|
|
368
|
-
["-m", "uvicorn",
|
|
234
|
+
["-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", String(SHARD_PORT)],
|
|
369
235
|
{
|
|
370
236
|
cwd: path.dirname(appPath),
|
|
371
|
-
env: {
|
|
237
|
+
env: {
|
|
238
|
+
...process.env,
|
|
239
|
+
...env,
|
|
240
|
+
PETALS_PYTHON: petalsPy,
|
|
241
|
+
PETALS_LOG: path.join(HOME_DIR, "petals.log"),
|
|
242
|
+
PYTHONPATH: path.dirname(appPath),
|
|
243
|
+
},
|
|
372
244
|
detached: true,
|
|
373
245
|
stdio: ["ignore", out, out],
|
|
374
246
|
}
|
|
@@ -398,7 +270,6 @@ export async function waitForShardRunning({ timeoutMs = 300000, statusHost } = {
|
|
|
398
270
|
}
|
|
399
271
|
} catch (err) {
|
|
400
272
|
if (err instanceof Error && err.message.startsWith("Petals exited")) throw err;
|
|
401
|
-
/* retry */
|
|
402
273
|
}
|
|
403
274
|
await new Promise((r) => setTimeout(r, 2000));
|
|
404
275
|
}
|
|
@@ -410,4 +281,4 @@ export async function waitForShardRunning({ timeoutMs = 300000, statusHost } = {
|
|
|
410
281
|
);
|
|
411
282
|
}
|
|
412
283
|
|
|
413
|
-
export { SHARD_PORT, PETALS_PORT, venvPython };
|
|
284
|
+
export { SHARD_PORT, PETALS_PORT, venvPython, RUNTIME_DEST };
|