Sell your GPU
A graphics card that sits idle most of the day is a listing: agents rendering, transcoding,
analysing hours of video or training a model send it jobs and pay per job, and the machine
earns while you are away from it. Nothing of yours is exposed: every job runs in a container the
buyer names, with the buyer's files at /in, and only what the job writes to /out leaves the
box.
What you need
- Docker on the machine.
docker infomust answer; without it the listing refuses with "docker is not installed on this node" or "docker is installed but its daemon does not answer", each naming the fix. - For a GPU listing, the NVIDIA container toolkit.
docker infomust show the NVIDIA runtime, else: "this node's docker has no NVIDIA runtime, so GPU jobs cannot run: install nvidia-container-toolkit, runsudo nvidia-ctk runtime configure --runtime=dockerand restart docker".npm run hardware:checkinoffchain/prints what the machine has, GPUs, VRAM, Docker and the runtime included. - A node: the local stack, or the same one command on any machine (Sell a service).
The two templates
compute-own-gpu (GPU job on your own machine, category compute:gpu) and compute-own-cpu
(CPU job on your own machine, compute:serverless) are the templates. Both carry local: true
and requires: a GPU for the first, two CPU cores and 4 GB of RAM for the second, so the
catalogue lists them only on a machine that meets that, and says why when it skips one.
A buyer's call is run {image, command, inputs?, env?, timeoutSeconds?}: the node pulls the
image, downloads each {url, path} under /in, runs docker run --rm (with --gpus all on the
GPU listing) under the limits below, captures stdout and stderr, and returns every file the job
wrote to /out as URLs served by the node, with the exit code. A job still running after the
sync window comes back as a job id, "still running after 60 s; call status with this jobId", and
status {jobId} reports running, succeeded, failed or timeout.
| Limit | GPU template | CPU template | Field |
|---|---|---|---|
| Longest job | 3,600 s | 1,800 s | maxSeconds |
Output written to /out | 2,048 MB | 1,024 MB | maxOutputMb |
Inputs downloaded to /in | 8,192 MB | 4,096 MB | maxInputMb |
| Jobs at once | 1 | 2 | maxConcurrent |
| Answer within, else a job id | 60 s | 60 s | syncSeconds |
| Minute rate in the tool's description | 0.02 USDC | 0.005 USDC | pricePerMinute |
| Price per job | 0.25 USDC | the listing's price | priceUsdc |
images restricts which images may run (empty means any); network is none, bridge or
host; memory and cpus cap the container. Edit pricePerMinute and priceUsdc to yours:
the buyer pays the listing's price per job, and the minute rate in the description is what an
agent reads to size a long job.
List it
On the local stack the bootstrap lists both when docker info answers, the GPU one only with the
NVIDIA runtime. On any node:
cd offchain
npm run templates -- add compute-own-gpu --out services.json
AIOUM_SERVICES_CONFIG=./services.json npm run multinode
Or from the Earn page: Sell your hardware shows the two templates with what each needs
("needs Docker and a GPU", "needs Docker, 2 CPUs and 4 GB RAM"); pick one, set the price and your
stake, press Run the test call (it runs alpine:3.20 and writes hello.txt to /out), and
Go live now. From the
command line, aioum sell add --template compute-own-gpu --price 0.25; from Claude,
aioum_sell_add with template: "compute-own-gpu".
Jobs live under <AIOUM_NODE_DIR>/compute/<jobId>; output files are served from the node's
/media path at AIOUM_NODE_PUBLIC_URL.
Who needs it
The video, image and training pipelines require a GPU: analysing reference clips, running a
vision model over frames, rendering and training run on one, never on a CPU. The need is met by
the node's own GPU first (what hardware.check reports, Apple Silicon included) and otherwise by
a compute:gpu listing that runs jobs, this template on someone else's machine, or by a GPU
rented for the project, which answers the same run call from the compute pod below. A GPU
price feed under compute:gpu is a listing, not a GPU. When neither is there the pipeline's card reads a GPU for frame analysis and
rendering: none on this machine; add a compute key (RUNPOD_API_KEY, …) to rent one, or run the
node on a machine with a GPU, and Use this stays off.
When the node has no GPU and a listing is live, the runner's GPU jobs carry the heavy steps
to it: analyse_frames samples one frame a second from each reference clip with PyAV and runs a
captioning model (Salesforce/blip-image-captioning-base, fp16 on CUDA) over them as a docker
job in pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime, returning one caption per frame and the
frames; assemble concatenates the shots, mixes narration and music and encodes with
h264_nvenc in jrottenberg/ffmpeg:7.1-nvidia. Each is the exact run call the listing
executes, paid from the run like any other call; a job that finds no CUDA device ends with exit
3 rather than falling back to the CPU, and the answer names the listing and its device, which
the run log repeats as ran on …. The listing's node adds its own docker settings
(--gpus all, the optimizer's --shm-size).
Rent one for a project
With a rental key on the runner (RUNPOD_API_KEY or VAST_API_KEY) and no listing that runs
jobs, the setup rents the GPU the heavy stages need for the length of the project
(Build an agent network). What it rents is a pod running the compute pod image,
offchain/docker/compute-pod: CUDA PyTorch 2.6, node 22, ffmpeg with NVENC and the frame job's
packages, with a job server that answers the contract above, run and status, directly in
the pod's container. The repository's workflow, .github/workflows/compute-pod-image.yml, builds
the image on every change to offchain/docker/compute-pod and pushes it to
ghcr.io/oum-dev/aioum-compute-pod, tagged with the commit and latest; the runner names it with
AIOUM_POD_IMAGE (default ghcr.io/oum-dev/aioum-compute-pod:latest). A fork's workflow pushes
under the fork's own owner, ghcr.io/<owner>/aioum-compute-pod, so a fork sets AIOUM_POD_IMAGE
to that. GHCR makes the package private on its first push: make it public in its settings on
GitHub, since the runner hands the provider no registry credentials. The image is the first
choice, and never a blocker: the runner asks the registry for it before renting, and when the
answer is not 200 (private, unpublished, no such tag, unreachable) it rents the public base the
image is built on, pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime, and bootstraps the pod at
start: the start command (RunPod's dockerStartCmd, Vast.ai's onstart) installs node 22 and
the same static ffmpeg, writes the job server from the pod's environment (AIOUM_POD_SERVER_B64,
gzip+base64 in pieces of 4000 characters), installs the frame job's packages and starts the
server. That pod's /live reports bootstrapped: pytorch base + node + ffmpeg, the Hardware row
adds "bootstrapped at start (no registry image needed)", and the wait for /live is a few minutes
instead of seconds. To build the image yourself:
What the pod does for a project: the frame job captions sampled frames, the style job reads
images and frames for style (a caption, the eight dominant colours by k-means on the GPU with
their shares, flatness and edge density, and CLIP likeness to the reference frames you name),
and the assembly cuts the shots with the narration and the music. A file in the workspace on
your node goes to the pod as an upload the runner ships (PUT /upload/<name> on the job server,
the run naming the upload id), since the pod cannot reach your node; a GPU listing's job takes
only http(s) URLs its node can fetch. When a project pauses, its pod is kept for
AIOUM_AGENTS_RENTAL_IDLE_MIN minutes (30) in case it resumes, then torn down by its id; a resume
finds it up, or rents again the way the setup did before the stage runs.
docker build -t ghcr.io/oum-dev/aioum-compute-pod:latest offchain/docker/compute-pod
docker push ghcr.io/oum-dev/aioum-compute-pod:latest
What happens, every line of it in the setup's log: the runner reads the instance's hourly price
and checks it against half the budget, creates the pod named aioum-<project id>-<short> with a
token of this project in its environment (AIOUM_POD_TOKEN) and port 8000 exposed (RunPod reaches
it at https://<pod id>-8000.proxy.runpod.net; Vast.ai on the instance's public IP and the port it
mapped, the job server started by onstart), keeps the provider's id on the project record the
moment it exists, waits for the pod and then for its job server's /live, and the Hardware row
reads "renting a GeForce RTX 4090 on RunPod for frame analysis and render (…) · up". From then on
the run's GPU jobs go to that pod with the token, nothing paid per job: run ignores the image
(the pod is the container; the assembly names ffmpeg), downloads the inputs to /in, runs the
command, and serves every file the job wrote to /out behind the token or the job's own key; the
runner pulls them into your workspace under gpu/<job>/<id>/ and the log reads ran on RunPod
RTX 4090 (rented for this project). Before the job server listens it encodes one second of
video through h264_nvenc, and /live carries the result as encoders: {h264_nvenc: true},
or {h264_nvenc: false, reason} in ffmpeg's words. Two secure-cloud RTX 4090 hosts refused the
session (OpenEncodeSessionEx failed: unsupported device (2)) although the build lists the
encoder, so the assembly names h264_nvenc only when the pod said it works and libx264
(-preset veryfast -crf 20, the pod's CPU) otherwise; the Hardware line says which as the pod
comes up, the assembly's rented estimate takes the rate that holds, and the run log reads
assembly encoded with libx264 on the rented GPU: NVENC not available on this host (…). When
the project ends the pod is terminated by the id on the
record, and only that id: the runner never lists your account's pods, never matches by name, never
touches a pod it did not create, and a rental from before a runner restart is terminated from the
record it left. Lambda and DigitalOcean keys name a price and an instance but rent nothing: they
launch bare VMs reached over SSH, where the pod cannot ride. A pod whose port never opens or whose
job server never answers (the image not pushed where the provider can pull it) is terminated and
the row says so.
Next: MCP servers as listings — any MCP server as a paid listing, verified only inside a container, and the host-access rule.