Verlex documentation

Everything you need to run your code in the cloud with Verlex.

On this page

Quick Start

1

Get your API key

Grab your API key from the dashboard.

2

Install

bash
pip install verlex

See Installation for details.

3

Run

Run your code on a cloud GPU:

python
import verlex

# Define your function
def train_model():
    import torch
    model = torch.nn.Linear(100, 10)
    # Your training code here...
    return {"accuracy": 0.95}

# Run it on a cloud GPU - that's it!
result = verlex.cloud(train_model, gpu="A100", api_key="gw_your_key")
print(result)

Expected output: {'accuracy': 0.95}

Installation

Install Verlex using pip (requires Python 3.10+):

bash
pip install verlex

Basic Usage

Pass Your API Key

Every function accepts api_key directly, no context manager needed:

python
import verlex

# Run in the cloud, one line
result = verlex.cloud(my_function, api_key="gw_your_key")

api_key and the resource keywords are keyword-only, so always write them as name=value. There is no separate "mode" argument or test environment: the gw_live_ and gw_test_ prefixes only name the key type; every key runs against the live service with live billing.

Passing Inputs

Your function's inputs come right after the function. A function that takes a single input accepts a bare value; a function that takes two or more inputs must receive them as one list:

python
# No inputs
verlex.cloud(train_model)

# One input, a bare value is fine
verlex.cloud(square, 5)

# Two or more inputs, pass them as a single list
verlex.cloud(train, [dataset, epochs])

Specifying Resources

Every hardware field is optional, and only the fields you set are pinned. Whatever you leave out is sized from your code and chosen for cost and availability, so asking for a GPU and nothing else is the normal case:

python
# One GPU pinned, nothing else: cpu, memory and provider are chosen for you
result = verlex.cloud(train_model, [data], gpu="A100")

# Pin more when you need it — each field is independent
result = verlex.cloud(train_model, [data], gpu="H100", gpu_count=8)
result = verlex.cloud(train_model, [data], cpu=4, memory="16 GB")

# Pin the cloud too
result = verlex.cloud(train_model, [data], gpu="H100", provider="runpod")

Passing the same machine around, or building it up in code? Wrap it in a Hardware object — identical meaning, one value:

python
from verlex import Hardware

big = Hardware(gpu="H100", gpu_count=8, memory="512 GB")

result = verlex.cloud(train_model, [data], big)            # positionally
result = verlex.cloud(train_model, [data], hardware=big)   # or by name

# Same fields, all optional — Hardware(gpu="A100") pins only the GPU
Hardware(gpu="A100").pinned()   # {'gpu': 'A100'}

Give a list to offer alternatives; Verlex takes the cheapest one available. Pinning a single provider disables cross-cloud failover for that job:

python
# Whichever of these is cheapest and available
result = verlex.cloud(train_model, [data], gpu=["H100", "A100"])

# Same for providers: a list lets Verlex choose, a single name pins hard
result = verlex.cloud(train_model, [data], gpu="A100", provider=["runpod", "gcp"])

Providers you can pin: aws, gcp, azure, verda, runpod, vast, jarvislabs, hyperstack, tensordock, lyceum, beam, northflank, novita, cerebrium.

Values are checked in your process, before anything is provisioned: a misspelled option answers "did you mean gpu=?", and gpu="64 GB" tells you it belongs in memory=. A Hardware object and a keyword that disagree about the same field raise rather than one silently winning.

The full set of job options:

python
result = verlex.cloud(
    train_model,
    [data],
    gpu="A100",        # GPU type
    cpu=8,             # vCPU cores
    memory="64GB",     # memory
    provider="aws",    # pin to a cloud (omit to let Verlex pick the cheapest)
    timeout=7200,      # 2 hour timeout
    pip_packages=["numpy==1.26.4"],  # extra packages, always installed
    python_version="3.11",  # match your local Python
)

Execution Modes

One flag controls your price-speed tradeoff:

Performance fast=True

Immediate execution

Best for time-sensitive workloads

Standard fast=False

Up to 10 min wait, lower price

Best for batch jobs, cost-sensitive work

python
# Performance mode, immediate execution
result = verlex.cloud(my_function, api_key="gw_your_key", fast=True)

# Standard mode (default), wait for the lowest price
result = verlex.cloud(my_function, api_key="gw_your_key")

Performance mode (fast=True) requires the Performance plan ($10/mo). Standard mode (fast=False) is available on every plan.

Pricing & Billing

Two parts: a monthly plan, then a per-job cost. Verlex passes the provider's live hardware price through at cost and adds a fixed service fee per GPU-hour, prorated per second. There is no percentage markup on the hardware. The Standard plan is free forever ($0/month); the Performance plan ($10/month) unlocks fast mode and 300 GB of storage.

The service-fee tier is derived from the hardware's FP16 tensor performance (TFLOPS):

TierExample hardwareStandard (fast=False)Performance (fast=True)
CPU (CPU-only jobs)No GPU$0.02/hr$0.02/hr
Small (<185 TFLOPS)T4, L4, A10, RTX 3090$0.10/GPU-hr$0.20/GPU-hr
Mid (<600 TFLOPS)A100, L40S, RTX 4090$0.30/GPU-hr$0.45/GPU-hr
Large (<1500 TFLOPS)H100, H200, MI300X$0.40/GPU-hr$0.60/GPU-hr
Flagship (≥1500 TFLOPS)B200, B300, GB200$0.50/GPU-hr$0.75/GPU-hr

The service fee is charged per GPU-hour. There is no cold-start surcharge; a cold machine and a warm one cost the same.

  • Per-second billing, 1 second minimum: you pay only for the time your job actually runs. Both the provider cost and the service fee are prorated to the second.
  • Prepaid credits: buy credits in the dashboard (minimum top-up $10). Each job places a hold and settles the exact amount when it finishes.
  • Auto top-up (opt-in): adds credits automatically with your saved card when the balance runs low (default: $50 added when it drops below $10).
  • Funds-based execution: a job runs until your credits are exhausted, it stops making progress (idle CPU/GPU with no output for a sustained period), or it finishes, with a hard 7-day backstop. The optional timeout argument is a client-side wait bound; when it expires, the client cancels the remote job.

Providers, Failover & Serverless

Every job is priced across 10 clouds (AWS, GCP, Azure, Verda, RunPod, Vast.ai, JarvisLabs, Hyperstack, TensorDock, Lyceum) plus serverless container lanes for small jobs, and routed to the cheapest machine that fits.

python
import verlex

# Default: priced across 10 clouds, routed to the cheapest machine that fits
result = verlex.cloud(train_model, gpu="A100", api_key="gw_your_key")

# Provider list: cost comparison and failover stay inside your subset
result = verlex.cloud(
    train_model,
    gpu="A100",
    provider=["runpod", "gcp", "verda"],
    api_key="gw_your_key",
)

# Hard pin: one provider, no cross-provider failover if it is out of stock
result = verlex.cloud(train_model, gpu="A100", provider="aws", api_key="gw_your_key")

Failover: if a launch fails or a region is out of stock, Verlex fails over automatically, always cheapest first:

  1. 01Cheapest provider, primary region
  2. 02Nearby regions on the same provider
  3. 03Next cheapest cloud
  4. 04Substitute GPU (unless pinned)

Your job does not fail because one cloud ran out of capacity.

Pinning: a single pinned provider disables cross-provider failover: if that cloud has no capacity, the job fails instead of moving. Prefer a provider list, which keeps cost comparison and failover inside your chosen subset.

Serverless: small jobs are routed to serverless container lanes (billed per second) when that is cheaper than a VM, or faster within budget on the Performance plan. This happens automatically; nothing to configure.

Files & Workspace Sync

Send local files up to the VM before your job runs and bring generated files back when it finishes. Pass a Workspace to any cloud call. Auto mode mirrors your project root (respecting .gitignore) and returns whatever your code creates or changes; or list explicit Upload and Output specs when you want full control over what goes up and what comes back.

python
import verlex
from verlex import Workspace

def train():
    from pathlib import Path
    Path("runs").mkdir(exist_ok=True)
    Path("runs/loss.json").write_text('{"loss": 0.42}')
    Path("model.pt").write_text("weights")
    return {"status": "done"}

# Auto mode mirrors your project root (respecting .gitignore) up to the VM,
# then returns new and changed files to the same paths locally
result = verlex.cloud(train, workspace=Workspace(), gpu="A100", api_key="gw_your_key")

# ./runs/loss.json and ./model.pt now exist locally
python
import verlex
from verlex import Workspace, Upload, Output

def finetune():
    import json
    from pathlib import Path
    cfg = json.load(open("config.json"))  # uploaded and renamed below
    Path("runs").mkdir(exist_ok=True)
    Path("runs/epoch1.ckpt").write_text("weights")  # trained on ./dataset
    Path("model.pt").write_text("final weights")
    return cfg["name"]

ws = Workspace(
    root=None,  # implied once uploads are listed; spelled out here for clarity
    uploads=[
        Upload("./configs/run.json", to="config.json"),  # rename into the workspace
        Upload("./dataset"),  # whole folder, hierarchy kept
    ],
    outputs=[
        Output(path="model.pt"),  # bring a single file back
        Output(pattern="runs/*.ckpt"),  # plus files matched by glob
    ],
)

result = verlex.cloud(finetune, workspace=ws, gpu="A100", api_key="gw_your_key")

Your function runs inside the synced workspace, so relative paths like open('data/train.csv') work unchanged. Listing explicit Upload specs replaces the auto mirror — you get exactly the files you named, and nothing else; pass root='auto' if you want the project mirror alongside them. Explicit Output specs never affect what goes up. There is no hard size limit by default: Verlex warns once the workspace passes 100 MB, so set a ceiling with Workspace(max_size_bytes=...) if you need one. Import Workspace, Upload, and Output from the top-level verlex package.

Dependencies

Verlex scans your function's source, pins the versions of imported packages installed locally, and bundles local .py modules automatically. When you need certainty, pass pip_packages=: those packages are authoritative, always installed, and a failed install fails the job loudly instead of continuing silently.

python
import verlex

def train():
    import numpy as np
    import torch
    from my_helpers import preprocess  # local .py modules are bundled automatically
    # ... your training code ...
    return {"loss": 0.03}

# Auto-detect: imported packages are pinned to your local versions
result = verlex.cloud(train, api_key="gw_your_key")

# Authoritative installs: always installed, a failed install fails the job
result = verlex.cloud(
    train,
    api_key="gw_your_key",
    pip_packages=["torch==2.3.1", "numpy==1.26.4"],
    python_version="3.11",  # 3.10, 3.11, 3.12, or 3.13
)

python_version controls the interpreter on the cloud side: supported values are 3.10, 3.11, 3.12, and 3.13. By default Verlex matches your local interpreter.

Pre-warming

Kill the cold start before it happens. Call verlex.prewarm(your_function) and Verlex analyzes it, boots the right hardware, and hands you back a handle once the machine is ready (pass wait=False to get the handle immediately and let it boot in the background while your code runs). Calling the handle runs your function on that machine with no provisioning wait, and the machine stays attached to the handle between calls, so every call is warm. Release it with handle.release() or a with block when you are done; unreleased handles release themselves when your program exits, and a hard kill is covered by the server-side idle timeout.

New in 0.13.0: prewarm() replaces the old repo-scanning pre_warm(), which was removed.

python
import verlex

def train(batch):
    import torch
    ...

wf = verlex.prewarm(train, gpu="A100", api_key="gw_your_key")  # A100 starts booting NOW

data = load_and_clean()      # local preprocessing runs meanwhile

result = wf(data)            # runs train(data) on the warm machine
more = wf(other_data)        # still warm, the VM stays attached

# Define inputs/outputs with a Workspace, just like verlex.cloud —
# it's a per-call argument on the warm handle:
from verlex import Workspace, Upload, Output
out = wf(data, workspace=Workspace(
    uploads=[Upload("./configs/run.json", to="config.json")],  # sync inputs up
    outputs=[Output(pattern="checkpoints/*.ckpt")],            # bring results back
))

wf.release()                 # recommended, not required: frees the machine now.
                             # If you skip it, the handle auto-releases when your
                             # program exits (atexit). A hard kill that skips atexit
                             # is covered by the server-side idle timeout instead.

# Or auto-release with a context manager:
with verlex.prewarm(train, "A100") as wf:
    result = wf(data)

# Scope mode: prewarm() with no function sizes hardware from THIS file's
# code and binds no function. You pass the function to run as the first
# argument on every call (workspace= stays a keyword, so it comes last):
wf = verlex.prewarm(gpu="A10")          # no function -> scope mode
result = wf(train, data)                # function is the first call arg
out = wf(train, data, workspace=Workspace())  # same, with a Workspace
wf.release()

There are two ways to call prewarm, and they change how you invoke the handle:

Bound modeScope mode
How you call itverlex.prewarm(train, ...)verlex.prewarm() with no function
What the handle bindsA specific function, pinned to the warm VM.No function; the calling file's code is analyzed to size hardware.
Invocationwf(data) just its argumentswf(train, data) the function to run is the first argument of every call
Where hardware comes fromThe kwargs you pass (gpu=, cpu=, memory=, provider=) or the analysis of the bound function.The same kwargs or the calling-file analysis.
Workspace argumentwf(data, workspace=Workspace())wf(train, data, workspace=Workspace()) the function stays first
  • workspace= works the same as on any other cloud call: it syncs inputs up before the function runs and brings generated outputs back. Because it is a keyword, its position never changes, and it is per-call, so different calls on the same warm VM can carry different Workspaces.
  • Multi-GPU cluster jobs, spot jobs, and workloads that resolve to the serverless lane skip pre-warming; they are already routed optimally.
  • If the warm session expires before you call, the handle still works and simply provisions normally.

Billing: you pay only for the time the warm machine actually exists at the real instance rate, never for analysis time, failed warm-ups, or skipped sessions. Idle time between calls is billed in segments; job runtime is billed as a normal job.

The machine is released by whichever of these comes first:

TriggerWhen
Idle timeoutAfter 10 idle minutes, counted from the moment the machine is ready; each call or status check resets the clock.
Lifetime capAt 1 hour of total session lifetime.
Manual releaseAs soon as you call release() (or the with block exits).
Running jobNever mid-job: a job already running on the machine is not interrupted by these timers.

Automatic Cloud Offloading

Don't want to manage when code runs in the cloud? Let Verlex decide automatically. When your system's CPU, memory, or GPU usage exceeds the threshold, heavy functions are transparently offloaded to the cheapest cloud provider.

python
import verlex

# Pass your API key directly
verlex.overflow(api_key="gw_your_key")

# Your code runs normally.
# When CPU, memory, or GPU exceeds 85%, functions go to the cloud.
data = load_data()
result = train_model(data)   # system overloaded? → cloud
evaluate(result)             # resources free → runs locally

Parameters

ParameterTypeDefaultDescription
api_keystr | NoneNoneYour Verlex API key.
fastboolFalseTrue = Performance mode (immediate execution, premium pricing). False = Standard mode.
thresholdfloat85.0CPU / memory usage percentage that triggers cloud offloading. Also used as the GPU threshold if gpu_threshold is not set.
gpu_thresholdfloat | NoneNoneGPU utilization or memory % that triggers cloud offloading. Defaults to threshold if not set. Requires pynvml and an NVIDIA GPU.
check_intervalfloat0.5Seconds between CPU/memory/GPU samples.
api_urlstr | NoneNoneOverride the Verlex API URL.
verboseboolTruePrint offload activity and status to the console.

Defaults: With just an API key, overflow monitors CPU, memory, and GPU. If any of them exceeds 85% usage, functions are automatically offloaded to the cloud in standard mode.

Note: GPU monitoring uses pynvml (included with the base install) and needs an NVIDIA GPU. Without one, overflow gracefully falls back to CPU and memory monitoring only.

CPU and memory monitoring need psutil. Install with: pip install 'verlex[overflow]'

AI assistants (MCP)

Connect Claude, Codex, Gemini CLI — or any MCP-compatible assistant — to your Verlex account and drive it in plain language: "run this fine-tune on an A100, show me the log as it goes, and kill it if the loss blows up".

Your assistant runs the connector locally and it talks to Verlex with your Verlex API key. Verlex never receives your Anthropic, OpenAI or Google credentials, and never sees your prompts.

Install

bash
pip install "verlex[mcp]>=0.17.0"

# Storing your key once here means the configs below
# can carry no secret at all.
verlex login

Connect your assistant

Claude Code

bash
claude mcp add verlex -- verlex-mcp

# View only: no submitting, cancelling or spending
claude mcp add verlex -- verlex-mcp --read-only

Claude Desktop

Add this to claude_desktop_config.json — %APPDATA%\Claude\ on Windows, ~/Library/Application Support/Claude/ on macOS, ~/.config/Claude/ on Linux.

json
{
  "mcpServers": {
    "verlex": {
      "command": "verlex-mcp",
      "env": { "VERLEX_MCP_MAX_SPEND": "25" }
    }
  }
}

Codex

toml
[mcp_servers.verlex]
command = "verlex-mcp"
env = { VERLEX_MCP_MAX_SPEND = "25" }

Gemini CLI

json
{
  "mcpServers": {
    "verlex": {
      "command": "verlex-mcp",
      "timeout": 60000
    }
  }
}

Keep any client request timeout at 60 seconds or more: log tailing holds a request open for up to 25 seconds while it waits for new output.

What you can ask

Once connected, your assistant can do all of this on its own:

  • "What GPUs can I get, and what would an A100 cost me for two hours?"
  • "Run this script on an A100 and show me the output as it goes."
  • "Cancel that job if you see the loss go to NaN."
  • "List my last 10 jobs and what each one cost."
  • "How much have I spent this month, and how much credit is left?"

What it can reach

Ten tools: identity, GPU catalog and live pricing, cost estimates, script submission, job list and status, live log tailing, cancellation, usage summaries and billing. Each run is an independent job on a fresh machine — persistent nodes and file transfer are coming next.

Safety

The connector is a thin client holding your own API key, so it can only ever see and do what you can. There is no database access and no shared credential. On top of that:

  • Anything that spends money or cancels work needs an explicit confirmation step, so it always surfaces as a visible action rather than a silent side effect.
  • Every submission is priced first, and the estimate comes back with the job so you see the cost up front.
  • A per-session spend cap (default $25), a one-GPU limit and a 60-minute duration limit are enforced before any request is sent.
  • Job output is fenced and labelled as untrusted data, so instructions hidden inside a log cannot redirect your assistant.

Read-only mode

Add --read-only to the command and only the viewing tools are registered — nothing can be submitted, cancelled or spent. For a guarantee enforced by Verlex itself rather than by the connector, create the key with the jobs:read scope.

Authentication

Get Your API Key

Sign up at verlex.dev to get your API key.

Using Your API Key

python
import verlex

# Pass api_key directly to any function
result = verlex.cloud(my_function, api_key="gw_your_key")

# Or use environment variable (VERLEX_API_KEY)
result = verlex.cloud(my_function)  # picks up from env

Error Handling

Verlex raises specific exceptions so you can handle failures gracefully:

python
from verlex.errors import (
    VerlexError,              # Base class for all Verlex errors
    AuthenticationError,      # Invalid or missing credentials
    InsufficientCreditsError, # Not enough credits to run the job
    ProviderMaintenanceError, # Pinned provider is under maintenance
    JobFailedError,           # Job execution failed in the cloud
    JobTimeoutError,          # Job exceeded its timeout
    SerializationError,       # Function could not be serialized
    NetworkError,             # Connection to Verlex API failed
    RateLimitError,           # Too many requests
)

import verlex

try:
    result = verlex.cloud(train_model, api_key="gw_your_key", gpu="A100")
except InsufficientCreditsError as e:
    print(f"Need more credits: {e.required} required, {e.available} available")
except JobFailedError as e:
    print(f"Job {e.job_id} failed in the cloud. Logs:\n{e.logs}")
except ProviderMaintenanceError:
    print("That provider is under maintenance. Pick another or drop the pin")
except JobTimeoutError:
    print("Job took too long. Increase timeout or optimize your code")
except VerlexError as e:
    print(f"Something went wrong: {e}")

Available GPUs

Request a GPU by name with gpu="H100", or as Hardware(gpu="H100"). Verlex finds the cheapest available instance across 10 clouds, including AWS, GCP, Azure, Verda, RunPod, Vast.ai, JarvisLabs, Hyperstack, TensorDock, and Lyceum, plus serverless container lanes for small jobs.

GPUMemoryBest for
T416 GBInference, small training, budget-friendly
L424 GBInference, fine-tuning, video processing
A1024 GBMixed inference and training
V10016 GBGeneral ML training
A10040/80 GBLarge model training, LLM fine-tuning
L40S48 GBLarge-scale inference and training
H10080 GBLargest models, fastest training
H200141 GBMaximum memory, cutting-edge workloads
B200192 GBNext-gen Blackwell, highest throughput
B300288 GBBlackwell Ultra, largest memory and FP4 throughput
RTX 3090 / 4090 / 509024-32 GBBudget inference and fine-tuning (consumer cards)

If you don't specify a GPU, Verlex auto-detects your code's needs and picks the best option. Availability and pricing vary by provider. Verlex always routes to the cheapest option.

Storage & Quotas

Job inputs, outputs, workspace files, and checkpoints live in Verlex object storage and count against your plan's storage quota:

Standard

25 GB of included storage.

Performance

300 GB of included storage.

Enterprise

1 TB+ of included storage: 1 TB by default, custom amounts on request.

Workspace upload blobs are stored by content hash and expire automatically after a period of non-use, so re-uploads of unchanged files are fast.

Checkpoints & Spot Recovery

Spot capacity is 60-80% cheaper than on-demand, but the provider can reclaim it at any time. Verlex handles the recovery: anything your job writes into the checkpoint directory is synced to object storage while it runs (every 60 seconds, every 20 seconds on short-notice providers, and immediately when a preemption notice arrives).

When a spot instance is reclaimed, the job is automatically re-enqueued on the next cheapest spot capacity (a different provider whenever one is stocked), the checkpoint directory is restored before your code runs again, and the reclaimed time is not billed. If spot keeps getting reclaimed, or no spot capacity fits the requested hardware anywhere, the job finishes on on-demand capacity so it always completes.

Write checkpoints, resume on restart

python
import os, pickle

def train():
    # Set inside every Verlex job; synced to object storage while you run.
    ckpt_dir = os.environ.get("GATEWAY_CHECKPOINT_DIR", "./ckpt")
    state_file = os.path.join(ckpt_dir, "state.pkl")

    start = 0
    if os.path.exists(state_file):
        with open(state_file, "rb") as f:
            start = pickle.load(f)["epoch"]

    for epoch in range(start, 100):
        run_one_epoch()
        tmp = state_file + ".tmp"
        with open(tmp, "wb") as f:
            pickle.dump({"epoch": epoch + 1}, f)
        os.replace(tmp, state_file)  # atomic: never syncs a torn file

Frameworks resume automatically

HuggingFace Trainer, PyTorch Lightning, and Keras jobs resume with no code changes: their checkpoint output is redirected into the synced directory and the latest checkpoint is picked up on restart. For plain Python state you can also use the injected helper: state = gw.resume() at the start, gw.checkpoint(epoch=epoch, total=total) each step.

The same restore also runs when an on-demand instance dies and the job is retried on fresh hardware. On providers with no advance reclaim notice, progress since the last periodic sync (at most the sync interval) can be lost.

Teams

Corporate accounts get organization-backed teams: a shared org credit pool, email invitations, and roles (owner, admin, member).

Admins allocate credits from the pool to members and can claw back unused allocations; members can also contribute personal credits to the pool. Everything is managed from the dashboard.

Spending Limits

Set daily or monthly spending caps from your dashboard to prevent unexpected charges. When a limit is reached, Verlex can block new jobs, warn you, or send a notification.

Block

New jobs are rejected until the next period or until you raise the limit.

Warn

Jobs still run, but you get a warning when approaching the limit.

Notify

You receive a notification when the limit is reached. Jobs continue.

Configure spending limits in your dashboard settings. You can also enable auto top-up to automatically add credits when your balance gets low.

Ready to get started?

Create an account and start running code in the cloud.