-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal-swarm.sh
More file actions
executable file
·280 lines (249 loc) · 10.1 KB
/
local-swarm.sh
File metadata and controls
executable file
·280 lines (249 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/usr/bin/env bash
#
# local-swarm.sh — spin up a real multi-peer IntelNav chain on one
# machine. Three intelnav-node daemons each host a contiguous layer
# range of Qwen 2.5 · 0.5B (24 blocks split 0..6 / 6..12 / 12..18 /
# 18..24). The client (intelnav) drives a chain through them as
# layers 0..6 + head; the three daemons cover 6..24.
#
# This isn't a mock — every wire is the real protocol. The daemons
# really run forward_server, the client really uses ChainDriver to
# assemble the chain over TCP. The only sandbox is "all on one box";
# in production the same code runs across many machines.
#
# Usage:
# bash scripts/local-swarm.sh setup # one-off: prepare slices
# bash scripts/local-swarm.sh start # spawn the 3 daemons
# bash scripts/local-swarm.sh chat # open the TUI, chain wired
# bash scripts/local-swarm.sh ask "..." # one-shot prompt through the chain
# bash scripts/local-swarm.sh stop # kill all daemons
# bash scripts/local-swarm.sh status # ports + pids
#
# Requirements:
# - intelnav and intelnav-node on PATH
# - libllama auto-discovered (~/.cache/intelnav/libllama/bin)
# - Qwen 2.5 · 0.5B in your local cache (run intelnav once and
# hit /models → Enter on the row; the GGUF lands in
# ~/.local/share/intelnav/models)
#
# Layout under /tmp/intelnav-swarm:
# peer-a/ {config, data, log} port 7717 layers 6..12
# peer-b/ {config, data, log} port 7718 layers 12..18
# peer-c/ {config, data, log} port 7719 layers 18..24
set -euo pipefail
ROOT=/tmp/intelnav-swarm
GGUF_NAME="qwen2.5-0.5b-instruct-q4_k_m.gguf"
USER_GGUF="${HOME}/.local/share/intelnav/models/${GGUF_NAME}"
USER_TOK="${HOME}/.local/share/intelnav/models/qwen2.5-0.5b-instruct-q4_k_m.tokenizer.json"
# Model CID. The chat client sends the GGUF file stem as the wire
# `model_cid` in SessionInit (see chain_driver.rs); the daemon's
# forward_server matches against the cid recorded in kept_ranges.json.
# For this sandbox we use the file stem on both sides, so .shards/<cid>/
# = .shards/<filename without .gguf>/.
MODEL_CID="qwen2.5-0.5b-instruct-q4_k_m"
# Three peers, three middle/tail slices. Port range 17717+ chosen
# to avoid collisions with the user's main daemon (which usually
# binds 7717 / 8765 / 4001).
PEER_PORTS=(17717 17718 17719)
PEER_NAMES=(peer-a peer-b peer-c)
PEER_RANGES=("6 12" "12 18" "18 24")
LIBP2P_PORTS=(14101 14102 14103)
CHUNKS_PORTS=(18101 18102 18103)
err() { printf "\033[31m%s\033[0m\n" "$*" >&2; }
say() { printf "\033[36m%s\033[0m\n" "$*"; }
ok() { printf "\033[32m%s\033[0m\n" "$*"; }
require() {
if ! command -v "$1" >/dev/null 2>&1; then
err "missing: $1 (install or symlink target/release into PATH)"; exit 1
fi
}
# ----------------------------------------------------------------------
# setup — prepare the four peer dirs.
# ----------------------------------------------------------------------
cmd_setup() {
require intelnav
require intelnav-node
if [ ! -f "$USER_GGUF" ]; then
err "missing $USER_GGUF"
err "run \`intelnav\`, /models → highlight Qwen 0.5B → Enter, then re-run setup."
exit 1
fi
say "→ wiping any prior swarm state at $ROOT"
rm -rf "$ROOT"
mkdir -p "$ROOT"
for i in "${!PEER_NAMES[@]}"; do
local name="${PEER_NAMES[$i]}"
local range="${PEER_RANGES[$i]}"
local libp2p_port="${LIBP2P_PORTS[$i]}"
local chunks_port="${CHUNKS_PORTS[$i]}"
local forward_port="${PEER_PORTS[$i]}"
local dir="$ROOT/$name"
mkdir -p \
"$dir/config/intelnav" \
"$dir/data/intelnav/models/.shards/$MODEL_CID" \
"$dir/log"
# Each peer owns one slice. forward_server reads
# kept_ranges.json on demand and lazy-loads the GGUF for
# whichever range the chain is asking about. Since
# gguf_path is the full GGUF, no chunking / stitching is
# needed for this sandbox.
cat > "$dir/data/intelnav/models/.shards/$MODEL_CID/kept_ranges.json" <<EOF
{
"model_cid": "$MODEL_CID",
"display_name": "Qwen 2.5 · 0.5B · Instruct",
"block_count": 24,
"gguf_path": "$USER_GGUF",
"kept": [[${range/ /, }]]
}
EOF
# config.toml — hand-pinned so the auto-config in firstrun
# doesn't drift the ports.
cat > "$dir/config/intelnav/config.toml" <<EOF
mode = "network"
default_model = "qwen2.5-0.5b-instruct-q4_k_m"
default_tier = "lan"
allow_wan = false
quorum = 1
device = "auto"
relay_only = false
libp2p_listen = "/ip4/127.0.0.1/tcp/$libp2p_port"
chunks_addr = "127.0.0.1:$chunks_port"
forward_addr = "127.0.0.1:$forward_port"
bootstrap = []
EOF
done
ok "✓ swarm prepared at $ROOT"
ls -la "$ROOT"
}
# ----------------------------------------------------------------------
# start / stop / status — daemon lifecycle.
# ----------------------------------------------------------------------
peer_pid_file() { echo "$ROOT/$1/log/pid"; }
peer_log_file() { echo "$ROOT/$1/log/out.log"; }
cmd_start() {
[ -d "$ROOT" ] || { err "no swarm — run \`local-swarm.sh setup\` first"; exit 1; }
require intelnav-node
for i in "${!PEER_NAMES[@]}"; do
local name="${PEER_NAMES[$i]}"
local dir="$ROOT/$name"
local pidfile; pidfile="$(peer_pid_file "$name")"
local logfile; logfile="$(peer_log_file "$name")"
if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
say "→ $name already running (pid $(cat "$pidfile"))"
continue
fi
XDG_CONFIG_HOME="$dir/config" \
XDG_DATA_HOME="$dir/data" \
INTELNAV_RELAY_ONLY=0 \
intelnav-node > "$logfile" 2>&1 &
echo $! > "$pidfile"
ok "✓ started $name pid=$! port=${PEER_PORTS[$i]} layers=${PEER_RANGES[$i]}"
done
say "→ waiting 2 s for daemons to bind..."
sleep 2
for i in "${!PEER_NAMES[@]}"; do
local port="${PEER_PORTS[$i]}"
if (echo > /dev/tcp/127.0.0.1/$port) 2>/dev/null; then
ok " ✓ ${PEER_NAMES[$i]}: TCP $port reachable"
else
err " ✗ ${PEER_NAMES[$i]}: TCP $port NOT reachable — see $(peer_log_file "${PEER_NAMES[$i]}")"
fi
done
}
cmd_stop() {
[ -d "$ROOT" ] || { say "no swarm to stop"; exit 0; }
for name in "${PEER_NAMES[@]}"; do
local pidfile; pidfile="$(peer_pid_file "$name")"
if [ -f "$pidfile" ]; then
local pid; pid="$(cat "$pidfile")"
if kill -0 "$pid" 2>/dev/null; then
kill -TERM "$pid"
ok "✓ stopped $name (pid $pid)"
fi
rm -f "$pidfile"
fi
done
}
cmd_status() {
[ -d "$ROOT" ] || { say "no swarm — run \`local-swarm.sh setup\`"; exit 0; }
for i in "${!PEER_NAMES[@]}"; do
local name="${PEER_NAMES[$i]}"
local pidfile; pidfile="$(peer_pid_file "$name")"
local pid="—"
local state="stopped"
if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
pid="$(cat "$pidfile")"; state="running"
fi
printf " %-7s %-10s pid=%s forward=127.0.0.1:%s layers=%s\n" \
"$name" "$state" "$pid" "${PEER_PORTS[$i]}" "${PEER_RANGES[$i]}"
done
}
# ----------------------------------------------------------------------
# chat / ask — drive a chain through the swarm.
# ----------------------------------------------------------------------
cmd_chat() {
require intelnav
# The chat client also needs an isolated env (its own peer.key,
# config) so it doesn't collide with the user's main install.
local cdir="$ROOT/client"
mkdir -p "$cdir/config/intelnav" "$cdir/data/intelnav/models"
# Symlink the model into the client's models dir so it can be
# picked as the active model.
ln -sf "$USER_GGUF" "$cdir/data/intelnav/models/$GGUF_NAME"
ln -sf "$USER_TOK" "$cdir/data/intelnav/models/qwen2.5-0.5b-instruct-q4_k_m.tokenizer.json"
cat > "$cdir/config/intelnav/config.toml" <<EOF
mode = "network"
default_model = "qwen2.5-0.5b-instruct-q4_k_m"
default_tier = "lan"
allow_wan = true
quorum = 1
device = "auto"
relay_only = true
libp2p_listen = "/ip4/127.0.0.1/tcp/4100"
peers = ["127.0.0.1:17717", "127.0.0.1:17718", "127.0.0.1:17719"]
splits = [6, 12, 18]
bootstrap = []
EOF
say "→ chat client → 0..6 (driver) → peers @ 6..12, 12..18, 18..24"
XDG_CONFIG_HOME="$cdir/config" \
XDG_DATA_HOME="$cdir/data" \
INTELNAV_RELAY_ONLY=1 \
intelnav
}
cmd_ask() {
require intelnav
local cdir="$ROOT/client"
local prompt="${1:-what is 17 squared?}"
if [ ! -d "$cdir" ]; then
err "no client config — run \`local-swarm.sh chat\` once to bootstrap, or just run again"
# Build minimal client config so ask works standalone.
mkdir -p "$cdir/config/intelnav" "$cdir/data/intelnav/models"
ln -sf "$USER_GGUF" "$cdir/data/intelnav/models/$GGUF_NAME"
ln -sf "$USER_TOK" "$cdir/data/intelnav/models/qwen2.5-0.5b-instruct-q4_k_m.tokenizer.json"
cat > "$cdir/config/intelnav/config.toml" <<EOF
mode = "network"
default_model = "qwen2.5-0.5b-instruct-q4_k_m"
peers = ["127.0.0.1:17717", "127.0.0.1:17718", "127.0.0.1:17719"]
splits = [6, 12, 18]
relay_only = true
EOF
fi
say "→ asking the swarm: $prompt"
echo "$prompt" | XDG_CONFIG_HOME="$cdir/config" \
XDG_DATA_HOME="$cdir/data" \
INTELNAV_RELAY_ONLY=1 \
intelnav --mode network ask --model qwen2.5-0.5b-instruct-q4_k_m
}
# ----------------------------------------------------------------------
# Entry.
# ----------------------------------------------------------------------
case "${1:-}" in
setup) cmd_setup ;;
start) cmd_start ;;
stop) cmd_stop ;;
status) cmd_status ;;
chat) cmd_chat ;;
ask) shift; cmd_ask "${1:-}" ;;
"") sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//' ;;
*) err "unknown command: $1"; sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;;
esac