Borrow your friends' GPUs: a terminal-based distributed compute mesh in pure Python
Project description
gpumesh
Borrow your friends' GPUs. A terminal-based distributed compute mesh written in
pure Python (stdlib only — torch, psutil, pyngrok are optional extras).
You have ML work to run but a weak laptop. Your friend has a strong GPU sitting
idle. gpumesh turns any group of machines into a small compute cluster: one
machine coordinates, any number of machines join with a single command, and work
is split between them proportionally to how fast each machine is.
your laptop (coordinator) friend's laptop (worker)
┌───────────────────────┐ HTTP/JSON ┌────────────────────────┐
│ job queue │◄─────────────►│ hardware probe │
│ capability scheduler │ (optional │ matmul benchmark │
│ SQLite (WAL) │ ngrok │ sandboxed subprocess │
│ heartbeats + reaper │ tunnel) │ executor │
└──────────▲────────────┘ └────────────────────────┘
│ submit / status
client CLI
Quick start
pip install -e .
Machine 1 — start the coordinator:
gpumesh serve --port 8000
# prints a token and a ready-made join command, e.g.
# gpumesh join http://192.168.1.5:8000 --token Kv3xP9qL2mNa
Add --public (with pip install pyngrok) to get a public URL friends can
reach over the internet.
Machine 2..N — join as a worker (one command, as promised):
gpumesh join http://192.168.1.5:8000 --token Kv3xP9qL2mNa
The worker probes its hardware (CUDA GPU → Apple Silicon → CPU), benchmarks itself with a matmul, and starts pulling tasks. Your own laptop can join too — then both machines compute in parallel.
Submit work:
gpumesh submit examples/grid_search.py --payloads examples/payloads.json \
--url http://192.168.1.5:8000 --token Kv3xP9qL2mNa --wait
A job is a Python script plus a JSON list of payloads (shards). Each payload
becomes one task; an optional "cost" field marks how heavy it is. Heavy
tasks go to fast machines, light tasks to slow ones.
Watch the mesh:
gpumesh workers --url ... --token ...
gpumesh status JOB_ID --url ... --token ...
Writing your own task script
The contract is two lines of glue:
import json, sys
payload = json.load(sys.stdin) # your shard's parameters
# ... do the work (os.environ["GPUMESH_DEVICE"] tells you cuda/mps/cpu) ...
print(json.dumps({"answer": 42})) # result = last stdout line, as JSON
Anything data-parallel fits: hyperparameter search, batch inference, cross-validation folds, rendering chunks, brute-force search shards.
How the scheduler splits work
- Every worker benchmarks itself at join time → a GFLOP/s score.
- Pending tasks are sorted by
cost. - When a worker asks for work, its score percentile among live workers picks the matching percentile of task costs — the strongest machine gets the heaviest remaining task, the weakest gets the lightest.
- Tasks are leased, not given away: if a worker dies (heartbeats stop) or the lease expires, the reaper re-queues the task for someone else (max 3 attempts, then marked failed).
Design notes (the interview section)
- Networking — hand-rolled JSON-over-HTTP protocol on
http.server(no framework), worker heartbeats, poll-based task leasing that survives flaky tunnels, shared-token auth, ngrok tunneling for NAT traversal. - DBMS — SQLite in WAL mode; schema with foreign keys and indexes; atomic
lease acquisition via a conditional
UPDATE ... WHERE status='pending'so two workers can never grab the same task; transactions around multi-row writes. - Operating systems — every task runs in a fresh subprocess in its own
session (process group); timeouts kill the whole process tree with
SIGKILL; POSIXRLIMIT_CPUcaps runaway loops; coordinator uses threads with a lock around the shared DB connection. - Distributed systems — capability-based scheduling, lease/heartbeat failure detection, idempotent re-queue with bounded retries, work-stealing style pull model (workers pull; the coordinator never needs to reach them through NAT).
- Task-parallel, not tensor-parallel — splitting a single model's tensors across machines over the internet dies on latency (that's what NCCL + InfiniBand are for). Sharding independent work units is the model serverless GPU platforms (e.g. RunPod) actually use.
Security warning
Workers execute code submitted to the coordinator. That is the point of the tool, and it is also remote code execution by design. Only share your coordinator URL + token with people you trust, treat the token as a password, and don't leave a public tunnel running unattended. The subprocess sandbox limits accidents, not attackers.
Layout
gpumesh/
cli.py command line entry point (serve / join / submit / status / workers)
server.py coordinator: threaded HTTP JSON API + lease reaper
db.py SQLite layer: workers, jobs, tasks, atomic leasing
worker.py agent loop: register, heartbeat, lease, execute, report
sandbox.py subprocess isolation: timeouts, process-group kill, rlimits
capability.py hardware probe + matmul benchmark -> capability score
client.py job submission and status polling
tunnel.py optional ngrok public URL
examples/
grid_search.py hyperparameter-search demo task (pure Python)
payloads.json six shards with varying cost
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gpumesh-0.2.0.tar.gz.
File metadata
- Download URL: gpumesh-0.2.0.tar.gz
- Upload date:
- Size: 27.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
581b8c9f452f4fba0ab07845789e1047d28b2e18eb4ae4039b6662672f17d64f
|
|
| MD5 |
6b01a6bb5776175d731ce5887b88086e
|
|
| BLAKE2b-256 |
71d0baf8eb9994b4e60fcdaae7b8d76793b787226f9d8d4e6a0dc345f7042f40
|
File details
Details for the file gpumesh-0.2.0-py3-none-any.whl.
File metadata
- Download URL: gpumesh-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
217058ec46754bacdc143871b8847e4c333c742f469dbcf672750a1db077e29c
|
|
| MD5 |
64382267816494678879b5e6a84b0a4e
|
|
| BLAKE2b-256 |
eccadedd6bf9d8c52faa8d08e3103a65746c06e81b464f7f53384d297677942e
|