blitzwing 0.2.7 → 0.2.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/README.md +160 -17
- package/package.json +1 -1
- package/runtime/http_chain.py +22 -1
- package/runtime/local_runner.py +26 -0
package/README.md
CHANGED
|
@@ -1,8 +1,48 @@
|
|
|
1
1
|
# blitzwing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Join a decentralized LLM inference swarm as a compute contributor — and earn HBAR.**
|
|
4
|
+
One command. No repo clone. No port forwarding. No GPU required.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
`blitzwing` is the contributor client for the [Blitzwing](https://github.com/Marshal-AM/blitzwing)
|
|
7
|
+
network: an OpenAI-compatible large-language-model inference system where a model is split
|
|
8
|
+
layer-by-layer across many ordinary machines. You host a slice of the model's layers, and you're paid
|
|
9
|
+
**HBAR on Hedera** proportional to how many layers you host — every time a request routes through you.
|
|
10
|
+
|
|
11
|
+
Because the default model runs on CPU and the CLI opens a free outbound tunnel for you, a plain laptop
|
|
12
|
+
behind a home router can join and start earning in minutes.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## How you earn
|
|
17
|
+
|
|
18
|
+
When you join, you tell the wizard how many layers your machine can host and give it your Hedera
|
|
19
|
+
account ID. From then on, your machine computes its slice of the model whenever a request passes
|
|
20
|
+
through it. For each paid request, you receive:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
your payout = layers_you_host × per-layer price
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
With the network's defaults, each layer is worth **0.1 HBAR**, so hosting 8 layers earns **0.8 HBAR**
|
|
27
|
+
per request you help serve. Payouts land in your Hedera account within seconds, with a
|
|
28
|
+
consensus-timestamped audit record you can verify on HashScan. You provide only a **public account ID**
|
|
29
|
+
— your private keys never leave your machine.
|
|
30
|
+
|
|
31
|
+
For the full picture of how the network works, see the
|
|
32
|
+
[main Blitzwing README](https://github.com/Marshal-AM/blitzwing#readme).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
| Requirement | Notes |
|
|
39
|
+
|-------------|-------|
|
|
40
|
+
| **Node.js ≥ 18** | Runs this CLI. |
|
|
41
|
+
| **Python 3.10 or 3.11** | Runs the local inference runtime. **Python 3.12+ is not supported** — the CLI will reject it and tell you how to pin 3.11 with `uv`. |
|
|
42
|
+
| **Linux or WSL** | The compute engine needs Linux. On Windows, run everything inside **WSL** — native Windows is not supported. |
|
|
43
|
+
| **A Hedera testnet account** | Your `0.0.N` account ID for payouts. Create one at the Hedera Developer Portal and fund it from the faucet. |
|
|
44
|
+
|
|
45
|
+
---
|
|
6
46
|
|
|
7
47
|
## Install
|
|
8
48
|
|
|
@@ -10,31 +50,134 @@ Join a Blitzwing mother swarm as a compute contributor. One command — no repo
|
|
|
10
50
|
npm i -g blitzwing
|
|
11
51
|
```
|
|
12
52
|
|
|
13
|
-
**Windows:**
|
|
53
|
+
**Windows:** open a **WSL** shell, install Node.js inside WSL, then run `npm i -g blitzwing` there.
|
|
54
|
+
The compute runtime cannot run on native Windows.
|
|
14
55
|
|
|
15
|
-
|
|
56
|
+
If your default Python is 3.12 or newer, pin 3.11 before joining:
|
|
16
57
|
|
|
17
58
|
```bash
|
|
18
|
-
|
|
59
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
60
|
+
source "$HOME/.local/bin/env"
|
|
61
|
+
uv python install 3.11
|
|
62
|
+
export BLITZWING_PYTHON="$(uv python find 3.11)"
|
|
19
63
|
```
|
|
20
64
|
|
|
21
|
-
|
|
65
|
+
---
|
|
22
66
|
|
|
23
|
-
|
|
24
|
-
2. Asks which model and how many layers
|
|
25
|
-
3. Asks for your **Hedera account ID** (`0.0.N`) for payouts
|
|
26
|
-
4. Installs Petals if needed
|
|
27
|
-
5. Opens a free **Cloudflare Quick Tunnel** (no account / token)
|
|
28
|
-
6. Joins the swarm and starts heartbeats
|
|
67
|
+
## Usage
|
|
29
68
|
|
|
30
69
|
```bash
|
|
31
|
-
blitzwing
|
|
32
|
-
blitzwing
|
|
70
|
+
blitzwing # interactive setup wizard (recommended) — discover, join, serve, earn
|
|
71
|
+
blitzwing status # show this machine's contributor status
|
|
72
|
+
blitzwing leave # leave the swarm; your layers are reclaimed
|
|
73
|
+
blitzwing help # usage (also: blitzwing --help, blitzwing -h)
|
|
33
74
|
```
|
|
34
75
|
|
|
35
|
-
|
|
76
|
+
| Command | What it does |
|
|
77
|
+
|---------|--------------|
|
|
78
|
+
| `blitzwing` | Runs the interactive wizard: discovers models, asks which model and how many layers to host, asks for your Hedera account, installs the runtime on first run, opens a free Cloudflare tunnel, joins the swarm, and starts heartbeating. |
|
|
79
|
+
| `blitzwing status` | Prints your saved node state (model, host id, layers hosted, block range, mother URL, tunnel URL, Hedera account, ENS name) plus the live running state of your local engine. |
|
|
80
|
+
| `blitzwing leave` | Tells the mother you're leaving, stops your local engine and tunnel, and clears local state. Your layers are reclaimed by the swarm. |
|
|
81
|
+
| `blitzwing help` | Prints help. |
|
|
82
|
+
|
|
83
|
+
| Flag | Effect |
|
|
84
|
+
|------|--------|
|
|
85
|
+
| `--discovery-url <url>` | Override the Discovery Service URL for this run. |
|
|
86
|
+
| `--help`, `-h` | Show help. |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## What the wizard does
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
1. Discover models from the Discovery Service (no mother URL to type)
|
|
94
|
+
2. Pick a model and choose how many layers to host (1 … max available)
|
|
95
|
+
3. Enter your Hedera account ID (0.0.N) for payouts
|
|
96
|
+
4. Install the local inference runtime (first run only — creates a venv)
|
|
97
|
+
5. Open a free Cloudflare Quick Tunnel (no account, no token)
|
|
98
|
+
6. Request a layer assignment from the mother and start serving
|
|
99
|
+
7. Finalize the handoff; start a background heartbeat daemon
|
|
100
|
+
8. ✅ You're online and earning — your block range and ENS name are printed
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You never clone the repository — the Python runtime ships inside this npm package and is installed into
|
|
104
|
+
`~/.blitzwing/` on first run.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Environment variables
|
|
109
|
+
|
|
110
|
+
All optional — the wizard prompts for what it needs. Set these to prefill answers or run
|
|
111
|
+
non-interactively.
|
|
112
|
+
|
|
113
|
+
| Variable | Purpose | Default |
|
|
114
|
+
|----------|---------|---------|
|
|
115
|
+
| `BLITZWING_DISCOVERY_URL` | Override the Discovery Service URL. | baked-in network default |
|
|
116
|
+
| `BLITZWING_HEDERA_ACCOUNT_ID` | Prefill your Hedera payout account (`0.0.N`). Falls back to `HEDERA_ACCOUNT_ID`. | — |
|
|
117
|
+
| `BLITZWING_LAYERS` | Layer count to host (for non-interactive joins). | prompted |
|
|
118
|
+
| `BLITZWING_NONINTERACTIVE` / `BLITZWING_YES` | Run without prompts (also auto-enabled when stdin isn't a TTY). | off |
|
|
119
|
+
| `BLITZWING_SHARD_PORT` | Local shard-manager HTTP port. | `8001` (Linux) / `8011` (Windows+WSL) |
|
|
120
|
+
| `BLITZWING_HOME` | State/logs/venv directory. | `~/.blitzwing` |
|
|
121
|
+
| `BLITZWING_PYTHON` | Explicit Python 3.10/3.11 interpreter path. | auto-detected |
|
|
122
|
+
|
|
123
|
+
### Non-interactive / CI join
|
|
36
124
|
|
|
37
125
|
```bash
|
|
38
|
-
export
|
|
39
|
-
export
|
|
126
|
+
export BLITZWING_HEDERA_ACCOUNT_ID=0.0.123456
|
|
127
|
+
export BLITZWING_LAYERS=8
|
|
128
|
+
export BLITZWING_NONINTERACTIVE=1
|
|
129
|
+
blitzwing
|
|
130
|
+
blitzwing status
|
|
40
131
|
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Files & state
|
|
136
|
+
|
|
137
|
+
Everything the CLI creates lives under `~/.blitzwing/` (or `$BLITZWING_HOME`):
|
|
138
|
+
|
|
139
|
+
| Path | Contents |
|
|
140
|
+
|------|----------|
|
|
141
|
+
| `contributor.json` | Your saved node state (model, host id, block range, mother URL, tunnel, Hedera account, ENS name). |
|
|
142
|
+
| `venv/` | The Python virtual environment for the inference runtime. |
|
|
143
|
+
| `runtime/` | The bundled runtime copied from this package. |
|
|
144
|
+
| `*.log` (e.g. `shard_manager.log`, `cloudflared.log`) | Logs for the local engine and tunnel. |
|
|
145
|
+
| identity file | Your persistent swarm peer identity (kept across restarts). |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Running on a public cloud VM
|
|
150
|
+
|
|
151
|
+
For an always-on contributor, a small Linux VM with a public IP is ideal:
|
|
152
|
+
|
|
153
|
+
1. Open inbound TCP **31337** (swarm P2P) and **8001** (shard manager).
|
|
154
|
+
2. Install Node 18+ and Python 3.11.
|
|
155
|
+
3. `npm i -g blitzwing`
|
|
156
|
+
4. `blitzwing` → choose the **public IP / cloud VM** option and enter the VM's public IPv4.
|
|
157
|
+
5. Provide your Hedera account for payouts.
|
|
158
|
+
|
|
159
|
+
See [`docs/contributor-setup.md`](https://github.com/Marshal-AM/blitzwing/blob/main/docs/contributor-setup.md)
|
|
160
|
+
and [`docs/gcp-setup.md`](https://github.com/Marshal-AM/blitzwing/blob/main/docs/gcp-setup.md).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Troubleshooting
|
|
165
|
+
|
|
166
|
+
| Symptom | Fix |
|
|
167
|
+
|---------|-----|
|
|
168
|
+
| *"Could not reach discovery"* | The Discovery Service is unreachable. Check your connection, or set `BLITZWING_DISCOVERY_URL` / `--discovery-url` to a reachable discovery endpoint. |
|
|
169
|
+
| *"No mothers registered"* | No swarm is live for any model yet. Ask the operator to bootstrap a mother, then retry. |
|
|
170
|
+
| *Join rejected — max layers* | You asked for more layers than are currently free. Retry with a smaller number; the error reports the max available. |
|
|
171
|
+
| *Python version error* | You're on Python 3.12+. Install and pin 3.11 with `uv` (see [Install](#install)) and set `BLITZWING_PYTHON`. |
|
|
172
|
+
| *"cannot run on native Windows"* | Run inside WSL — the compute engine needs Linux. |
|
|
173
|
+
| *Engine didn't become ready* | Check the logs in `~/.blitzwing/` (`shard_manager.log` and the engine log). First-run model download can take time on a slow link. |
|
|
174
|
+
| *Want a clean restart* | `blitzwing leave`, then `blitzwing` again. |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT.
|
|
181
|
+
|
|
182
|
+
- Homepage & full docs: https://github.com/Marshal-AM/blitzwing
|
|
183
|
+
- How the network works (architecture, Hedera, ENS): [main README](https://github.com/Marshal-AM/blitzwing#readme)
|
package/package.json
CHANGED
package/runtime/http_chain.py
CHANGED
|
@@ -17,6 +17,22 @@ from tensor_codec import tensor_from_payload, tensor_to_payload
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _apply_repetition_penalty(
|
|
21
|
+
logits: torch.Tensor,
|
|
22
|
+
generated: Sequence[int],
|
|
23
|
+
penalty: float,
|
|
24
|
+
) -> torch.Tensor:
|
|
25
|
+
if penalty <= 1.0 or not generated:
|
|
26
|
+
return logits
|
|
27
|
+
out = logits.clone()
|
|
28
|
+
for token_id in generated:
|
|
29
|
+
if out[token_id] < 0:
|
|
30
|
+
out[token_id] *= penalty
|
|
31
|
+
else:
|
|
32
|
+
out[token_id] /= penalty
|
|
33
|
+
return out
|
|
34
|
+
|
|
35
|
+
|
|
20
36
|
def _is_public_http_url(url: str) -> bool:
|
|
21
37
|
if not url:
|
|
22
38
|
return False
|
|
@@ -171,6 +187,7 @@ class HttpChainInference:
|
|
|
171
187
|
input_ids = tokenizer(prompt, return_tensors="pt")["input_ids"]
|
|
172
188
|
prompt_tokens = int(input_ids.shape[-1])
|
|
173
189
|
max_new_tokens = max(1, min(int(max_tokens), 512))
|
|
190
|
+
repetition_penalty = float(os.getenv("HTTP_CHAIN_REPETITION_PENALTY", "1.1"))
|
|
174
191
|
hop_desc = " -> ".join(
|
|
175
192
|
f"{h.get('host_id')}[{h.get('block_indices')}]" for h in hosts
|
|
176
193
|
)
|
|
@@ -220,7 +237,11 @@ class HttpChainInference:
|
|
|
220
237
|
hidden = self.runner.forward_tail(hidden)
|
|
221
238
|
|
|
222
239
|
logits = self.runner.logits_from_hidden(hidden)
|
|
223
|
-
next_logits =
|
|
240
|
+
next_logits = _apply_repetition_penalty(
|
|
241
|
+
logits[0, -1, :],
|
|
242
|
+
generated,
|
|
243
|
+
repetition_penalty,
|
|
244
|
+
)
|
|
224
245
|
|
|
225
246
|
if temperature <= 0:
|
|
226
247
|
next_id = int(torch.argmax(next_logits).item())
|
package/runtime/local_runner.py
CHANGED
|
@@ -123,6 +123,30 @@ class LocalShardRunner:
|
|
|
123
123
|
return m.transformer.word_embeddings(input_ids)
|
|
124
124
|
raise RuntimeError("Could not locate embedding layer")
|
|
125
125
|
|
|
126
|
+
def _total_layers(self) -> int:
|
|
127
|
+
env_total = os.getenv("TOTAL_LAYERS", "").strip()
|
|
128
|
+
if env_total.isdigit():
|
|
129
|
+
return int(env_total)
|
|
130
|
+
m = self.model
|
|
131
|
+
config = getattr(m, "config", None)
|
|
132
|
+
if config is not None:
|
|
133
|
+
for key in ("num_hidden_layers", "n_layer", "num_layers"):
|
|
134
|
+
value = getattr(config, key, None)
|
|
135
|
+
if isinstance(value, int) and value > 0:
|
|
136
|
+
return value
|
|
137
|
+
return self.block_end
|
|
138
|
+
|
|
139
|
+
def _is_tail_host(self) -> bool:
|
|
140
|
+
return self.block_end >= self._total_layers()
|
|
141
|
+
|
|
142
|
+
def _final_norm(self, hidden: torch.Tensor) -> torch.Tensor:
|
|
143
|
+
m = self.model
|
|
144
|
+
if hasattr(m, "model") and hasattr(m.model, "norm"):
|
|
145
|
+
return m.model.norm(hidden)
|
|
146
|
+
if hasattr(m, "transformer") and hasattr(m.transformer, "ln_f"):
|
|
147
|
+
return m.transformer.ln_f(hidden)
|
|
148
|
+
return hidden
|
|
149
|
+
|
|
126
150
|
def _lm_head(self, hidden: torch.Tensor) -> torch.Tensor:
|
|
127
151
|
m = self.model
|
|
128
152
|
if hasattr(m, "lm_head"):
|
|
@@ -145,4 +169,6 @@ class LocalShardRunner:
|
|
|
145
169
|
return hidden
|
|
146
170
|
|
|
147
171
|
def logits_from_hidden(self, hidden: torch.Tensor) -> torch.Tensor:
|
|
172
|
+
if self._is_tail_host():
|
|
173
|
+
hidden = self._final_norm(hidden)
|
|
148
174
|
return self._lm_head(hidden)
|