Remote GPU and long jobs
computeRun GPU and long jobs on your own Modal, Replicate or RunPod account, or a local program, as workflows with progress, cancel, cost limits and results in files.
computeFree in your app, Apache-2.0Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
Run GPU and long jobs on your own Modal, Replicate or RunPod account, or a local program, as workflows with progress, cancel, cost limits and results in files.
At a glance
compute:
heartbeat: 60s
poll: 15s
results: { label: personal, max-size: 2GB, types: ["image/*"] }
limits: { per-tenant: $50/month }
targets:
upscale:
driver: replicate
model: nightmareai/real-esrgan:f121d640bd286e1fdc67f9799164c1d5be36ff74576ee11c803ae5b665dd46aa
region: us
timeout: 30m
price: $0.0014/s
max-cost: $2import { compute } from "mainsdk/experimental/compute";
// refused before submit if the worst case could pass $2
const { jobId } = await compute.run("upscale", { scale: 4 }, { files: [image], maxCost: "$2" });
const job = await compute.get(jobId);Drivers
4 drivers. Select one to see its line in main.yaml.
compute:
driver: localChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-COMPUTE-0001 | Checkmainsdk check, before deploy | A target not declared under compute.targets |
MAIN-COMPUTE-0008 | Checkmainsdk check, before deploy | A target isn't pinned: no model version, app or endpoint id |
MAIN-COMPUTE-0013 | Checkmainsdk check, before deploy | A target has no price or timeout, or a timeout over 24h |
MAIN-COMPUTE-0002 | RuntimeError responses and logs | Refused before submit: the worst case (price times timeout) passes maxCost, a compute.limits ceiling or the agent's budget (402) |
MAIN-COMPUTE-0003 | RuntimeError responses and logs | The running cost reached maxCost; the job was cancelled at the provider |
MAIN-COMPUTE-0004 | RuntimeError responses and logs | No heartbeat for three periods; the job is lost and was cancelled at the provider |
MAIN-COMPUTE-0005 | RuntimeError responses and logs | The provider failed submit, status or cancel after retries (502) |
MAIN-COMPUTE-0006 | RuntimeError responses and logs | The job passed its timeout and was cancelled |
MAIN-COMPUTE-0007 | RuntimeError responses and logs | Labelled input to a target outside the tenant's region (403) |
MAIN-COMPUTE-0009 | RuntimeError responses and logs | An output is too large or of a type not in compute.results.types (413 or 415) |
MAIN-COMPUTE-0010 | RuntimeError responses and logs | No job with that id in the current tenant (404) |
MAIN-COMPUTE-0011 | RuntimeError responses and logs | Cancel of a finished job (409) |
MAIN-COMPUTE-0012 | RuntimeError responses and logs | A provider callback failed verification (400) |
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| compute@1 contract and pinned targets | Phase 2 | 0.6 | compute#contract |
| Jobs as workflow runs | Phase 2 | 0.6 | compute#run |
| Wait on a job as one workflow step | Phase 2 | 0.6 | compute#step |
| Cost limits before and during a job | Phase 2 | 0.6 | compute#cost |
| Heartbeat, progress and lost jobs | Phase 2 | 0.6 | compute#heartbeat |
| Results into files | Phase 2 | 0.6 | compute#results |
| Region check for labelled input | Phase 2 | 0.6 | compute#region |
| Verified provider callbacks | Phase 2 | 0.6 | compute#callbacks |
| Modal, Replicate, RunPod and local drivers | Phase 2 | 0.6 | compute#drivers |
Quick start
compute:
heartbeat: 60s
limits: { per-tenant: $50/month }
targets:
upscale:
driver: replicate
model: nightmareai/real-esrgan:f121d640bd286e1fdc67f9799164c1d5be36ff74576ee11c803ae5b665dd46aa
region: us
timeout: 30m
price: $0.0014/s
max-cost: $2import { compute, type ComputeRunOptions } from "mainsdk/experimental/compute";
declare const image: NonNullable<ComputeRunOptions["files"]>[number]; // a file stored with `files`
// refused before submit if the worst case could pass $2
const { jobId } = await compute.run("upscale", { scale: 4 }, { files: [image], maxCost: "$2" });
const job = await compute.get(jobId);Calls
| Call | What it does |
|---|---|
compute.run(target, input, { files?, maxCost?, timeout?, agent?, idempotencyKey? }) | Submits a job to a target; refuses it first if the worst-case cost passes the limit. |
compute.get(jobId) | Returns a job's state, progress, cost and result. |
compute.list({ target?, state?, cursor?, limit? }) | Pages through the current tenant's jobs. |
compute.cancel(jobId, { reason }) | Cancels a job at the provider and records why. |
compute.step(name, target, input, opts?) | Runs a job as a step inside a workflow and returns its result. |
Configuration
compute:
heartbeat: 60s # no sign of life for three of these and the job is lost
poll: 15s # how often the workflow asks the provider
results: { label: personal, max-size: 2GB, types: ["image/*", "video/*", "audio/*", "application/json"] }
limits: { per-tenant: $50/month, total: $500/month }
targets:
upscale:
driver: replicate # modal | replicate | runpod | local
model: nightmareai/real-esrgan:<64 hex>
region: us # required unless driver is local
timeout: 30m # at most 24h
price: $0.0014/s
max-cost: $2
retries: 0 # 0 to 5A target with no price or timeout stops the app from starting (MAIN-COMPUTE-0013). A local target names a program that is also declared under permissions.processes.
What you get
compute.run(target, input, { files?, maxCost?, timeout?, agent?, idempotencyKey? })
compute.get(jobId)
compute.list({ target?, state?, cursor?, limit? })
compute.cancel(jobId, { reason })
compute.step(name, target, input, opts?) // as one step of a workflow
compute.callback(call, request) // verify a provider callback and wake the runA job is queued, running, completed, failed, cancelled or lost. It is a run of the built-in workflow mainsdk.compute (check, submit, poll, fetch, finish), so it survives a restart. Progress is published on the realtime channel compute:<jobId> and the cost on the meter metric compute.cost, in micro-dollars.
Examples
Cancel a job and read its result
import { compute } from "mainsdk/experimental/compute";
export async function finish(jobId: string) {
const job = await compute.get(jobId);
if (job.state === "running") await compute.cancel(jobId, { reason: "no longer needed" });
return job.result;
}Wait inside a workflow
import { compute } from "mainsdk/experimental/compute";
export async function upscaleStep() {
return compute.step("upscale-step", "upscale", { scale: 4 });
}Errors and checks
| Code | When | Fix |
|---|---|---|
MAIN-COMPUTE-0001 | A target is not declared under compute.targets (also a check on a literal in code) | Declare it in main.yaml, or fix the name |
MAIN-COMPUTE-0002 | The worst case (price times timeout) passes maxCost, a limit or the agent's budget | Raise the limit on purpose, or shorten the timeout |
MAIN-COMPUTE-0003 | The running cost reached maxCost; the job was cancelled | Raise maxCost or fix the job |
MAIN-COMPUTE-0004 | No sign of life for three heartbeats; the job is lost | Check the provider; set retries |
MAIN-COMPUTE-0005 | The provider failed or did not answer | Retry later |
MAIN-COMPUTE-0006 | The job passed its timeout | Raise timeout or shorten the job |
MAIN-COMPUTE-0007 | Labelled input and a target outside the tenant's region | Use a target in the tenant's region |
MAIN-COMPUTE-0008 | A target is not pinned (check) | Add the version hash, app and function, or endpoint id |
MAIN-COMPUTE-0009 | An output is too large or of a type not allowed | Adjust results on purpose |
MAIN-COMPUTE-0010 | No job with that id in this tenant | Use a job id from this tenant |
MAIN-COMPUTE-0011 | Cancel of a finished job | Nothing to cancel |
MAIN-COMPUTE-0012 | A provider callback failed verification | Check the webhook secret |
MAIN-COMPUTE-0013 | A target has no price, no timeout, or a timeout over 24h | Give it a price and a timeout of 24h or less |
Swapping drivers
compute:
targets:
upscale:
driver: runpod # was replicate
endpoint: abc123Your calls stay the same. See Swap a driver.
Testing
FakeComputeProvider, ParkingFlows and computeConformance come from mainsdk/testing. See Run a GPU job and Test your app.
Security and privacy
- MainSDK never hosts models; jobs run on your provider account, and a driver declares that it never trains on inputs.
- Output is fetched only from the driver's declared hosts, then stored through
fileswith owner scope, scanning and limits. - Labelled data goes only to a target in the tenant's region.
get,listandcancelsee only the current tenant's jobs; a callback only wakes the run, it never carries state.
FAQ
Can a job cost more than I set? No. The worst case is reserved before submit and a running job is cancelled when its cost reaches maxCost.
Next
- File uploads: where the input comes from.
- Workflows: the engine a job runs on.
- Limits and metering: watch the spend.
Was this page helpful?