blitzwing 0.1.5 → 0.1.8
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/package.json +2 -2
- package/src/config.js +6 -3
- package/src/install.js +96 -19
- package/src/wizard.js +22 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzwing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Interactive setup wizard to join a Blitzwing Petals mother swarm as a compute contributor",
|
|
5
5
|
"bin": {
|
|
6
6
|
"blitzwing": "bin/blitzwing.js"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"config": {
|
|
25
|
-
"discoveryUrl": "
|
|
25
|
+
"discoveryUrl": "http://35.238.86.1:9000"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^0.9.1",
|
package/src/config.js
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
export const DEFAULT_DISCOVERY_URL =
|
|
3
3
|
process.env.BLITZWING_DISCOVERY_URL ||
|
|
4
4
|
process.env.npm_package_config_discoveryUrl ||
|
|
5
|
-
"
|
|
5
|
+
"http://35.238.86.1:9000";
|
|
6
6
|
|
|
7
7
|
export const HOME_DIR = process.env.BLITZWING_HOME || `${process.env.HOME || process.env.USERPROFILE}/.blitzwing`;
|
|
8
8
|
export const STATE_PATH = `${HOME_DIR}/contributor.json`;
|
|
9
9
|
export const VENV_PATH = `${HOME_DIR}/venv`;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Windows + WSL on same PC: avoid 8001/31337 — WSL forwards those on 127.0.0.1.
|
|
11
|
+
const defaultShardPort = process.platform === "win32" ? 8011 : 8001;
|
|
12
|
+
const defaultPetalsPort = process.platform === "win32" ? 31338 : 31337;
|
|
13
|
+
export const SHARD_PORT = Number(process.env.BLITZWING_SHARD_PORT || defaultShardPort);
|
|
14
|
+
export const PETALS_PORT = Number(process.env.BLITZWING_PETALS_PORT || defaultPetalsPort);
|
package/src/install.js
CHANGED
|
@@ -101,13 +101,35 @@ export function ensurePython() {
|
|
|
101
101
|
|
|
102
102
|
function petalsImportable(python) {
|
|
103
103
|
try {
|
|
104
|
-
execFileSync(
|
|
104
|
+
execFileSync(
|
|
105
|
+
python,
|
|
106
|
+
["-c", "import petals.cli.run_server"],
|
|
107
|
+
{ stdio: "ignore" }
|
|
108
|
+
);
|
|
105
109
|
return true;
|
|
106
110
|
} catch {
|
|
107
111
|
return false;
|
|
108
112
|
}
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
function uvicornImportable(python) {
|
|
116
|
+
try {
|
|
117
|
+
execFileSync(python, ["-c", "import uvicorn, fastapi"], { stdio: "ignore" });
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function ensureShardManagerDeps(python, pip, env, onLog) {
|
|
125
|
+
if (uvicornImportable(python)) return;
|
|
126
|
+
onLog?.("Installing shard manager deps (uvicorn, fastapi)...");
|
|
127
|
+
execFileSync(python, ["-m", "pip", "install", "fastapi", "uvicorn[standard]", "pydantic"], {
|
|
128
|
+
stdio: "inherit",
|
|
129
|
+
env,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
111
133
|
export function ensureVenvAndPetals({ onLog }) {
|
|
112
134
|
const { py } = ensurePython();
|
|
113
135
|
fs.mkdirSync(HOME_DIR, { recursive: true });
|
|
@@ -125,29 +147,43 @@ export function ensureVenvAndPetals({ onLog }) {
|
|
|
125
147
|
};
|
|
126
148
|
|
|
127
149
|
if (petalsImportable(python)) {
|
|
150
|
+
ensureShardManagerDeps(python, pip, env, onLog);
|
|
128
151
|
onLog?.("Petals already installed — skipping dependency install");
|
|
152
|
+
fs.writeFileSync(path.join(HOME_DIR, "python"), python + "\n");
|
|
129
153
|
return { python, pip };
|
|
130
154
|
}
|
|
131
155
|
|
|
156
|
+
if (process.platform === "win32") {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"Petals cannot be installed on native Windows (hivemind requires uvloop/Linux).\n" +
|
|
159
|
+
"Run the contributor inside WSL instead:\n" +
|
|
160
|
+
" wsl bash /mnt/c/Users/MSI/Desktop/blitzwing/scripts/local_wsl_contributor_join.sh\n" +
|
|
161
|
+
"Or run the full E2E script:\n" +
|
|
162
|
+
" powershell -File scripts/run_local_e2e.ps1"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
132
166
|
onLog?.("Installing CPU PyTorch + Petals (may take a few minutes on first run)...");
|
|
133
|
-
execFileSync(
|
|
167
|
+
execFileSync(python, ["-m", "pip", "install", "-U", "pip", "wheel", "setuptools<81"], {
|
|
134
168
|
stdio: "inherit",
|
|
135
169
|
env,
|
|
136
170
|
});
|
|
137
171
|
execFileSync(
|
|
138
|
-
|
|
139
|
-
["install", "torch", "--index-url", "https://download.pytorch.org/whl/cpu"],
|
|
172
|
+
python,
|
|
173
|
+
["-m", "pip", "install", "torch", "--index-url", "https://download.pytorch.org/whl/cpu"],
|
|
140
174
|
{ stdio: "inherit", env }
|
|
141
175
|
);
|
|
142
176
|
// Needed to compile hivemind protobufs during --no-build-isolation install
|
|
143
|
-
execFileSync(
|
|
177
|
+
execFileSync(python, ["-m", "pip", "install", "grpcio", "grpcio-tools", "protobuf"], {
|
|
144
178
|
stdio: "inherit",
|
|
145
179
|
env,
|
|
146
180
|
});
|
|
147
181
|
// Hivemind first with no build isolation (needs pkg_resources from setuptools<81)
|
|
148
182
|
execFileSync(
|
|
149
|
-
|
|
183
|
+
python,
|
|
150
184
|
[
|
|
185
|
+
"-m",
|
|
186
|
+
"pip",
|
|
151
187
|
"install",
|
|
152
188
|
"--no-build-isolation",
|
|
153
189
|
"git+https://github.com/learning-at-home/hivemind.git@213bff98a62accb91f254e2afdccbf1d69ebdea9",
|
|
@@ -155,15 +191,16 @@ export function ensureVenvAndPetals({ onLog }) {
|
|
|
155
191
|
{ stdio: "inherit", env }
|
|
156
192
|
);
|
|
157
193
|
execFileSync(
|
|
158
|
-
|
|
159
|
-
["install", "--no-build-isolation", "git+https://github.com/bigscience-workshop/petals.git"],
|
|
194
|
+
python,
|
|
195
|
+
["-m", "pip", "install", "--no-build-isolation", "git+https://github.com/bigscience-workshop/petals.git"],
|
|
160
196
|
{ stdio: "inherit", env }
|
|
161
197
|
);
|
|
162
|
-
execFileSync(
|
|
198
|
+
execFileSync(python, ["-m", "pip", "install", "fastapi", "uvicorn[standard]", "pydantic", "httpx"], {
|
|
163
199
|
stdio: "inherit",
|
|
164
200
|
env,
|
|
165
201
|
});
|
|
166
202
|
|
|
203
|
+
fs.writeFileSync(path.join(HOME_DIR, "python"), python + "\n");
|
|
167
204
|
return { python, pip };
|
|
168
205
|
}
|
|
169
206
|
|
|
@@ -202,8 +239,12 @@ class Mgr:
|
|
|
202
239
|
self.block_indices = os.environ["BLOCK_INDICES"]
|
|
203
240
|
peers = [p.strip() for p in os.environ.get("INITIAL_PEERS", "").split(",") if p.strip()]
|
|
204
241
|
self.initial_peers = peers
|
|
242
|
+
announce_raw = os.environ.get("ANNOUNCE_MADDRS", "")
|
|
243
|
+
self.announce_maddrs = [a.strip() for a in announce_raw.split(",") if a.strip()]
|
|
205
244
|
self.python = os.environ.get("PETALS_PYTHON", "python")
|
|
245
|
+
self.petals_log = os.environ.get("PETALS_LOG", os.path.expanduser("~/.blitzwing/petals.log"))
|
|
206
246
|
self._proc = None
|
|
247
|
+
self._log_fp = None
|
|
207
248
|
self._lock = threading.Lock()
|
|
208
249
|
self.last_exit_code = None
|
|
209
250
|
|
|
@@ -212,7 +253,9 @@ class Mgr:
|
|
|
212
253
|
"--device", "cpu", "--quant_type", "none",
|
|
213
254
|
"--block_indices", bi, "--port", str(self.port),
|
|
214
255
|
"--identity_path", self.identity_path, "--num_handlers", "1"]
|
|
215
|
-
if self.
|
|
256
|
+
if self.announce_maddrs:
|
|
257
|
+
c += ["--announce_maddrs", *self.announce_maddrs]
|
|
258
|
+
elif self.public_ip:
|
|
216
259
|
c += ["--public_ip", self.public_ip]
|
|
217
260
|
if self.initial_peers:
|
|
218
261
|
c += ["--initial_peers", *self.initial_peers]
|
|
@@ -223,7 +266,15 @@ class Mgr:
|
|
|
223
266
|
if bi: self.block_indices = bi
|
|
224
267
|
if self._proc and self._proc.poll() is None:
|
|
225
268
|
return
|
|
226
|
-
self.
|
|
269
|
+
if self._log_fp is None:
|
|
270
|
+
os.makedirs(os.path.dirname(self.petals_log) or ".", exist_ok=True)
|
|
271
|
+
self._log_fp = open(self.petals_log, "a", encoding="utf-8")
|
|
272
|
+
popen_kwargs = {"stdout": self._log_fp, "stderr": subprocess.STDOUT}
|
|
273
|
+
if os.name == "nt":
|
|
274
|
+
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
275
|
+
else:
|
|
276
|
+
popen_kwargs["preexec_fn"] = os.setsid
|
|
277
|
+
self._proc = subprocess.Popen(self.cmd(self.block_indices), **popen_kwargs)
|
|
227
278
|
self.last_exit_code = None
|
|
228
279
|
|
|
229
280
|
def stop(self):
|
|
@@ -232,7 +283,13 @@ class Mgr:
|
|
|
232
283
|
self._proc = None
|
|
233
284
|
return
|
|
234
285
|
p = self._proc
|
|
235
|
-
|
|
286
|
+
try:
|
|
287
|
+
if os.name == "nt":
|
|
288
|
+
p.send_signal(signal.CTRL_BREAK_EVENT)
|
|
289
|
+
else:
|
|
290
|
+
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
|
|
291
|
+
except Exception:
|
|
292
|
+
p.send_signal(signal.SIGTERM)
|
|
236
293
|
try: p.wait(timeout=30)
|
|
237
294
|
except Exception:
|
|
238
295
|
p.kill(); p.wait(timeout=10)
|
|
@@ -243,6 +300,9 @@ class Mgr:
|
|
|
243
300
|
self.stop(); time.sleep(1); self.start(bi)
|
|
244
301
|
|
|
245
302
|
def status(self):
|
|
303
|
+
if self._proc is not None and self._proc.poll() is not None:
|
|
304
|
+
self.last_exit_code = self._proc.returncode
|
|
305
|
+
self._proc = None
|
|
246
306
|
running = self._proc is not None and self._proc.poll() is None
|
|
247
307
|
return StatusResponse(
|
|
248
308
|
running=running,
|
|
@@ -292,12 +352,13 @@ export function startShardManagerProcess({
|
|
|
292
352
|
}) {
|
|
293
353
|
const appPath = writeLocalShardManager();
|
|
294
354
|
const out = fs.openSync(logPath, "a");
|
|
355
|
+
const petalsPy = petalsImportable(venvPython()) ? venvPython() : python;
|
|
295
356
|
const child = spawn(
|
|
296
|
-
|
|
357
|
+
petalsPy,
|
|
297
358
|
["-m", "uvicorn", `shard_manager_app:app`, "--host", "0.0.0.0", "--port", String(SHARD_PORT)],
|
|
298
359
|
{
|
|
299
360
|
cwd: path.dirname(appPath),
|
|
300
|
-
env: { ...process.env, ...env, PETALS_PYTHON:
|
|
361
|
+
env: { ...process.env, ...env, PETALS_PYTHON: petalsPy, PETALS_LOG: path.join(HOME_DIR, "petals.log") },
|
|
301
362
|
detached: true,
|
|
302
363
|
stdio: ["ignore", out, out],
|
|
303
364
|
}
|
|
@@ -306,21 +367,37 @@ export function startShardManagerProcess({
|
|
|
306
367
|
return { pid: child.pid, logPath, appPath };
|
|
307
368
|
}
|
|
308
369
|
|
|
309
|
-
export async function waitForShardRunning({ timeoutMs = 300000 } = {}) {
|
|
370
|
+
export async function waitForShardRunning({ timeoutMs = 300000, statusHost } = {}) {
|
|
371
|
+
const host = statusHost || process.env.BLITZWING_STATUS_HOST || "127.0.0.1";
|
|
372
|
+
const petalsLog = path.join(HOME_DIR, "petals.log");
|
|
310
373
|
const start = Date.now();
|
|
311
374
|
while (Date.now() - start < timeoutMs) {
|
|
312
375
|
try {
|
|
313
|
-
const res = await fetch(`http
|
|
376
|
+
const res = await fetch(`http://${host}:${SHARD_PORT}/status`);
|
|
314
377
|
if (res.ok) {
|
|
315
378
|
const body = await res.json();
|
|
316
|
-
if (body.running) return body;
|
|
379
|
+
if (body.running && body.pid) return body;
|
|
380
|
+
if (body.last_exit_code != null) {
|
|
381
|
+
const tail = fs.existsSync(petalsLog)
|
|
382
|
+
? fs.readFileSync(petalsLog, "utf8").trim().split("\n").slice(-5).join("\n")
|
|
383
|
+
: "";
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Petals exited (code ${body.last_exit_code}). ${tail || "See " + petalsLog}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
317
388
|
}
|
|
318
|
-
} catch {
|
|
389
|
+
} catch (err) {
|
|
390
|
+
if (err instanceof Error && err.message.startsWith("Petals exited")) throw err;
|
|
319
391
|
/* retry */
|
|
320
392
|
}
|
|
321
393
|
await new Promise((r) => setTimeout(r, 2000));
|
|
322
394
|
}
|
|
323
|
-
|
|
395
|
+
const tail = fs.existsSync(petalsLog)
|
|
396
|
+
? fs.readFileSync(petalsLog, "utf8").trim().split("\n").slice(-5).join("\n")
|
|
397
|
+
: "";
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Timed out waiting for Petals on http://${host}:${SHARD_PORT}/status. ${tail || "See " + petalsLog}`
|
|
400
|
+
);
|
|
324
401
|
}
|
|
325
402
|
|
|
326
403
|
export { SHARD_PORT, PETALS_PORT, venvPython };
|
package/src/wizard.js
CHANGED
|
@@ -142,8 +142,22 @@ async function wizard(args) {
|
|
|
142
142
|
process.exit(0);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
const hederaAccount = await p.text({
|
|
146
|
+
message: "Hedera account ID for layer payouts (e.g. 0.0.123456)",
|
|
147
|
+
initialValue: process.env.BLITZWING_HEDERA_ACCOUNT_ID || "",
|
|
148
|
+
validate(v) {
|
|
149
|
+
const s = String(v || "").trim();
|
|
150
|
+
if (!/^0\.0\.\d+$/.test(s)) return "Enter a Hedera account like 0.0.123456";
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
if (p.isCancel(hederaAccount)) {
|
|
154
|
+
p.cancel("Setup cancelled");
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
const hederaAccountId = String(hederaAccount).trim();
|
|
158
|
+
|
|
145
159
|
const confirm = await p.confirm({
|
|
146
|
-
message: `Join ${selected.model} hosting ${layersN} layers from ${String(publicIp).trim()}?`,
|
|
160
|
+
message: `Join ${selected.model} hosting ${layersN} layers from ${String(publicIp).trim()} paying to ${hederaAccountId}?`,
|
|
147
161
|
initialValue: true,
|
|
148
162
|
});
|
|
149
163
|
if (p.isCancel(confirm) || !confirm) {
|
|
@@ -176,6 +190,7 @@ async function wizard(args) {
|
|
|
176
190
|
layers: layersN,
|
|
177
191
|
public_ip: String(publicIp).trim(),
|
|
178
192
|
shard_manager_url: shardManagerUrl,
|
|
193
|
+
hedera_account_id: hederaAccountId,
|
|
179
194
|
});
|
|
180
195
|
} catch (err) {
|
|
181
196
|
spin.stop("Join rejected");
|
|
@@ -202,7 +217,7 @@ async function wizard(args) {
|
|
|
202
217
|
});
|
|
203
218
|
|
|
204
219
|
try {
|
|
205
|
-
await waitForShardRunning({ timeoutMs: 600000 });
|
|
220
|
+
await waitForShardRunning({ timeoutMs: 600000, statusHost: String(publicIp).trim() });
|
|
206
221
|
} catch (err) {
|
|
207
222
|
spin.stop("Petals did not become ready");
|
|
208
223
|
p.cancel(`${err.message}. See ${logPath}`);
|
|
@@ -230,6 +245,7 @@ async function wizard(args) {
|
|
|
230
245
|
shard_manager_url: shardManagerUrl,
|
|
231
246
|
shard_pid: pid,
|
|
232
247
|
discovery_url: args.discoveryUrl,
|
|
248
|
+
hedera_account_id: hederaAccountId,
|
|
233
249
|
joined_at: new Date().toISOString(),
|
|
234
250
|
};
|
|
235
251
|
saveState(state);
|
|
@@ -281,7 +297,8 @@ async function showStatus() {
|
|
|
281
297
|
console.log(` mother: ${state.mother_url}`);
|
|
282
298
|
console.log(` public_ip: ${state.public_ip}`);
|
|
283
299
|
try {
|
|
284
|
-
const
|
|
300
|
+
const statusHost = state.public_ip || "127.0.0.1";
|
|
301
|
+
const res = await fetch(`http://${statusHost}:${SHARD_PORT}/status`);
|
|
285
302
|
if (res.ok) {
|
|
286
303
|
const st = await res.json();
|
|
287
304
|
console.log(` petals_running: ${st.running}`);
|
|
@@ -308,7 +325,8 @@ async function doLeave() {
|
|
|
308
325
|
p.log.warn(`Mother leave call failed: ${err.message}`);
|
|
309
326
|
}
|
|
310
327
|
try {
|
|
311
|
-
|
|
328
|
+
const statusHost = state.public_ip || "127.0.0.1";
|
|
329
|
+
await fetch(`http://${statusHost}:${SHARD_PORT}/stop`, { method: "POST" });
|
|
312
330
|
} catch {
|
|
313
331
|
/* ignore */
|
|
314
332
|
}
|