Lab

GPU job queue

Daily driver

A router on my Mac that sends AI work to GPU machines at home over a private network.

Queue

queue depth 2 Refresh
node-1critique + taskbusy

Running

1760883302-9f3a7c21task
qwen3-coder:30bpreempted
elapsed 12:41
PreemptDelete

Queued

1760883355-1d8e44b0task
qwen3-coder:30bpreempted
elapsed 12:41
PreemptDelete
node-2critiquebusy

Running

1760883310-c7a2e5f0critique
deepseek-r1:32bpreempted
elapsed 02:07
PreemptDelete

Queued

1760883371-6be0a913critique
gemma3:27bpreempted
elapsed 02:07
PreemptDelete
The queue page: one running job per node, a reorderable list behind it, and preempt and delete by job id. Mockup with sample data.
$ gpuq critique plan.md
fan-out 2 critique nodes
node-1 1760883310-4be01d97 qwen3-coder:30b done 2 findings 58s
node-2 1760883310-c7a2e5f0 deepseek-r1:32b done 3 findings 41s
$ gpuq task "add a retry budget to the export job"
1760883402-9f3a7c21 -> node-1 (first free node by rank)
preview https://router.example.invalid/jobs/1760883402-9f3a7c21
$ gpuq critique --parent 1760883402-9f3a7c21 --node node-1
error 409 a critique never runs on its parent task's node
not retried
$ curl -s https://router.example.invalid/health
{"ok":true,"queue_depth":2}
A client run: a critique fans out to two nodes, a task returns a job id and a preview link, and a refused placement fails loudly. Mockup with sample data.

How it works

Mac routerjob client node 1critique + task node 2critique queuequeue critique, taskcritique
  1. A client sends a job. It tells the router on my Mac whether it is a critique or a long task.
  2. The router picks nodes. A critique fans out to every critique node at once. A task goes to the first free node by rank.
  3. A guard checks the placement. A critique never lands on its parent task's node, and the router refuses it with a 409.
  4. Each node runs one job at a time. The rest wait in that node's queue, which I can reorder, preempt or clear.
  5. The result comes back and the model unloads. The job id and preview link return to the client, and the model is released with keep_alive 0 to free VRAM.

Built with

Decisions