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 CHANGED
@@ -1,8 +1,48 @@
1
1
  # blitzwing
2
2
 
3
- <!-- mod: default discovery http://34.70.57.65:9000 (0.2.7) -->
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
- Join a Blitzwing mother swarm as a compute contributor. One command no repo clone, no ngrok, no port forwarding.
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:** run inside **WSL** (Petals needs Linux). Install Node in WSL, then `npm i -g blitzwing`.
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
- ## Use
56
+ If your default Python is 3.12 or newer, pin 3.11 before joining:
16
57
 
17
58
  ```bash
18
- blitzwing
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
- The wizard:
65
+ ---
22
66
 
23
- 1. Discovers mothers (no mother URL to type)
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 status
32
- blitzwing leave
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
- ## Env (optional)
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 BLITZWING_DISCOVERY_URL=http://127.0.0.1:9000 # override discovery
39
- export BLITZWING_HEDERA_ACCOUNT_ID=0.0.123456 # prefill payout account
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzwing",
3
- "version": "0.2.7",
3
+ "version": "0.2.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"
@@ -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 = logits[0, -1, :]
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())
@@ -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)