swactor/mvp-node-image-gguf-shape.md

167 lines
5.2 KiB
Markdown

# MVP node image + GGUF deployment shape
## Goal
Prepare the first deployment-test node image for one rented GPU node.
The image should be minimal: swactor MVP node binary plus tinygrad with CUDA support. The node should stay at the swactor level; Python is only the compute helper, not the top-level application runtime.
The first GGUF target is a small ~1B parameter model fetched whole from HuggingFace and cached on the node.
## Existing reference
The closest existing image is in `apps/old-pipeline-parallel-inference`:
- `Dockerfile.base`
- CUDA runtime/base image layering;
- Python, tinygrad, numpy;
- NVRTC runtime;
- CUDA headers copied from a builder stage;
- sshd;
- PID-1 entrypoint.
- `Dockerfile`
- thin code layer over the base image;
- copies Rust binaries and worker script.
- `pp_entrypoint.sh`
- starts sshd deterministically;
- accepts VastAI-injected public keys;
- runs the worker as a child;
- keeps the container alive after worker exit for postmortem.
- `pp_tinygrad_worker.py`
- contains practical GGUF/tinygrad loading lessons;
- fetches model artifacts;
- uses tinygrad tokenizer/model code;
- old runtime enters the Python worker protocol directly.
The new image should reuse the packaging/runtime lessons, not the old pipeline-parallel application shape.
## Runtime architecture
```text
VastAI or Docker provisioner
-> container entrypoint starts sshd
-> entrypoint starts mvp node binary
-> mvp node joins orchestrator over iroh/swactor
-> mvp node registers node actor(s)
-> mvp node starts tinygrad helper as a child process
-> helper fetches/caches GGUF
-> helper loads model on CUDA
-> orchestrator drives prompt/inference via swactor messages
```
## Binary shape
Add or identify a dedicated deployment node binary, for example:
```text
crates/mvp-system/src/bin/mvp-node.rs
```
Responsibilities:
- read deployment env/config;
- start iroh/swactor runtime;
- join the orchestrator;
- register the MVP node actor path;
- start and supervise the tinygrad helper process;
- emit a ready JSON line on stdout once the swactor node is reachable;
- continue pumping runtime until shutdown or fault.
This should be the deployment form of the existing local node role, not coupled to local-only TCP/test assumptions.
## Image shape
Layering should follow the old image pattern:
```text
mvp-node-base
CUDA runtime
NVRTC runtime library
CUDA headers required by tinygrad runtime JIT
python3
tinygrad + numpy
openssh-server
mvp entrypoint
mvp-node
/usr/local/bin/mvp-node
/usr/local/share/mvp/tinygrad_worker.py
```
NVRTC is needed because tinygrad's CUDA backend JIT-compiles kernels at runtime. The old image installs the runtime library in the final image and copies CUDA headers from a builder stage; keep that lesson unless a better tinygrad-compatible base is chosen.
## Entrypoint shape
The entrypoint should:
- install/accept SSH public keys from VastAI-compatible env;
- start `sshd` before the node binary;
- print bootstrap progress to stdout/stderr for SSH datastream capture;
- run `/usr/local/bin/mvp-node` as a child;
- tee node output to a log file readable over SSH;
- keep PID 1 alive after node crash for postmortem.
This mirrors the old `pp_entrypoint.sh` behavior, with MVP names and paths.
## GGUF shape
First deployment target:
- whole-file HuggingFace GGUF fetch;
- small ~1B parameter model;
- node-local cache path;
- progress/fault observations surfaced through the node/datastream path;
- tokenizer from GGUF or adjacent tokenizer source, depending on what the chosen model path supports.
Existing MVP planning code already has concepts for this:
- `run_plan.rs::GgufSource::{LocalPath, HuggingFaceGguf}`;
- `TokenizerSource::{EmbeddedGguf, LocalPath}`.
Known gap:
- actor/runtime weight commands currently collapse to test artifacts in the node-agent/stage-controller path.
- real `GgufSource` must be carried through to the node/worker load command before this deployment path is real.
## Tinygrad helper shape
Python helper remains subordinate to swactor.
Responsibilities:
- import tinygrad lazily;
- verify CUDA with a small tensor realization;
- fetch/cache the requested GGUF;
- load the model/tokenizer;
- execute inference operations requested by the swactor node;
- emit structured lifecycle/progress/fault events.
Do not make Python the deployment app entrypoint. The app entrypoint is the swactor node binary.
## Local verification target
This workstation has a GPU and Docker CUDA should work. The deployment image should be verified locally before VastAI.
Target E2E:
1. Build base image.
2. Build thin MVP node image.
3. Run with `docker run --gpus all`.
4. Confirm bootstrap stdout/stderr are capturable.
5. Confirm `mvp-node` starts and joins the orchestrator path.
6. Confirm native datastream frame collection from the container.
7. Confirm tinygrad imports and realizes a CUDA tensor.
8. Confirm the chosen GGUF is fetched/cached from HuggingFace.
9. Confirm one prompt/inference request completes.
The Docker E2E should use the same image shape expected by VastAI; only provisioning differs.
## Non-goals for this doc
- designing the full prompt loop schema;
- full VastAI provisioning policy;
- sharded/range GGUF fetching;
- multi-node pipeline-parallel layout.