
Lium GPU Cloud
io.github.Datura-aiPublished by GitHub account Datura-aiv0.41.7Updated Nov 7, 2026
Rent NVIDIA GPU pods on Lium, run commands on them and remove them, billed per second.
Overview
Lets an assistant rent NVIDIA GPU pods on Lium, run commands and copy files on them, and remove them, billed per second.
- What you can do
- List available GPU nodes and price a job before renting
- Rent pods by node or filters, with TTL or budget caps, and remove or reboot them
- Run commands, stream logs, forward ports, copy and sync files, and copy between pods
- Check balance, hourly burn, spend per pod and past run records
- Manage volumes, multi-node clusters, backups and scheduled terminations
- Inspect account audit events and API key scopes and budgets
- When to use it
- Useful when you want an assistant to spin up GPU compute for training or inference, run a job on a rented machine, and tear it down, without leaving the chat. Also handy for checking Lium balance and spend before and after renting.
- Requirements
- Runs locally over stdio, installed from PyPI as lium.io with uvx (the MCP mode needs the mcp extra). Needs a Lium account and an API key, supplied as the LIUM_API_KEY environment variable or saved once with lium init. Network access to Lium is required.
Installation
In SourceWeft
- Open Lium GPU Cloud in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
Lium — Python SDK & CLI
lium.io is a Python package that provides both a command-line interface and a Python SDK for managing GPU pods on the Lium platform. Install it once — use whichever interface fits the job.
Lium
Installation
Python package
Binary install (macOS amd64/arm64 / Linux amd64/arm64)
Fresh binary installs place a managed symlink at ~/.lium/bin/lium that points to a
versioned binary under ~/.lium/versions/<version>/lium.
Quick Start
CLI
First hour on a pod
A few things that save time on a freshly rented pod (full version in docs/getting-started.rst):
- Always pass
--ttl(or--until) tolium up; a pod bills until it is removed. - The pod's local volume (
/rooton the standard templates;pod.volume_pathin the SDK) is the only pathlium bkcan back up and the one encryption covers; an attached Volume is under/mnt; everything else (/workspace,/tmp) is plain container filesystem, neither encrypted nor backup-able. Keep weights, datasets and the Hugging Face cache on the volume:mkdir -p /root/hf /root/logs; export HF_HOME=/root/hf HF_HUB_ENABLE_HF_TRANSFER=1. - Ubuntu 24.04 images: use a venv (
python -m venv /root/venv) orexport PIP_BREAK_SYSTEM_PACKAGES=1beforepip install. - Blackwell GPUs (B200, B300, RTX PRO 6000, RTX 5090) need a cu128+ PyTorch build:
pip install torch --index-url https://download.pytorch.org/whl/cu130. FlashAttention-3 is Hopper-only; use FlashAttention-4 or cuDNN attention on Blackwell. - Missing tools:
apt-get update && apt-get install -y ffmpeg rsync. - Background jobs:
nohup setsid cmd > /root/logs/x.log 2>&1 < /dev/null &. - Check utilisation:
nvidia-smi --query-gpu=timestamp,index,utilization.gpu,memory.used --format=csv -l 5 > /root/logs/gpu.csv &.
SDK
The SDK mirrors the CLI's capabilities for programmatic use. Two entry points: the @lium.machine decorator for quickly offloading isolated functions, and the Lium() client for long-lived orchestration code.
High-level decorator — annotate a function and offload work to a GPU pod. machine is "<count>x<gpu>" or "<gpu>" ("1xH200", "A100", "2xRTX4090"; count defaults to 1; the GPU is named as lium ls --gpu takes it and matched whole, so "A100" never rents an RTX A1000) and the cheapest matching node is rented; timeout= (default 1 h) bounds the run and the pod's lifetime. Arguments travel as a pickle (your own bytes, loaded on your own pod); the result comes back as a JSON envelope plus an .npz sidecar for numpy arrays read with allow_pickle=False, so nothing the pod writes is unpickled on your machine. What round-trips: None/bool/int/float/str/bytes, list/tuple/set/frozenset/dict of those, datetime/date/time/timedelta, Decimal, pathlib.Path, uuid.UUID, numpy.ndarray (any dtype without Python objects) and numpy scalars — anything else is a lium.ResultEncodingError on the pod naming the type (return .tolist(), dict(x), x.value instead). Only the function's own def is sent, so import inside it and pass everything else as arguments. A remote exception is re-raised with its type when that type is a builtin (except ValueError works; other types arrive as lium.RemoteExecutionError with the name), with lium.RemoteExecutionError (remote traceback, exit code, output) as its cause:
keep_warm=300 keeps the pod five minutes for the next call or the next run of the script; infer.map(prompts) runs every item on one pod; infer.local(...) runs the function here (local=True / LIUM_MACHINE_LOCAL=1 does so for every call); infer.close() removes a warm pod. Progress goes to stderr (quiet=True to silence):
Direct SDK usage follows the same pattern:
wait_ready() raises PodStartError — with .pod, .status, .history and .cause (what the backend recorded, e.g. Container creation failed due to ... (failure_step: ssh_connect)) — when the pod reaches FAILED/CREATION_FAILED/STOPPED/BROKEN or disappears from the pod list, so a dead pod is not mistaken for a slow one. Pass on_poll=lambda pod, status, elapsed: ... to be told about every poll. lium up is bounded by --timeout SECONDS (default 900) for the whole rent, prints waiting for <pod>… <STATUS> (<n> s) while it waits, and exits 1 naming the pod when the budget runs out; --ready-timeout caps only the wait.
A long job goes on a pod the caller keeps: detach=True starts it in the background and returns at once, and the pod stays up until you remove it. lium.ls() lists the nodes when you want to name one; up(wait=True) rents it and returns the ready pod.
For work that must not outlive the code using it, rental() rents a named node for a with block and removes the pod on the way out, whatever happened inside — so run the work to completion inside the block (a detached job started here would be killed with the pod). rent() above is the other way in: it picks the node by spec and hands you a pod you own.
lium.pod_by_name("job") finds a pod by name, huid or id.
A server or a training run should outlive the call that starts it. run_background() starts it detached with a PID file, an exit-code file and a log on the pod (plus the process's boot id and start time in a .id file, so status() and kill() never take a PID reused after a pod restart for the job; a job without that file reads gone and is not signalled), and the returned Job knows how to wait for it:
lium.wait_for_port(pod, 8000) probes a port without a job (a template that serves on start), and lium.wait_ready(pod, ready_port=8000) waits for RUNNING and the port together.
Multi-node clusters — N whole nodes on one InfiniBand/RoCE fabric, rented as one order, each with a private overlay address:
up_cluster() raises ClusterNotListedError when the nodes are, or may be, rented but the pod list did not show a whole cluster (.confirmed says whether the API confirmed the order, .pod_ids and .listed what it named and what was listed): do not rent again, list the pods to find the cluster. wait_cluster_ready() raises PodStartError at once when a member fails or is missing from the pod list. rm_cluster() sends one DELETE /clusters/{cluster_id}: the API removes every member and answers one row per member (pod, huid, name, node_rank, success, message, error); a member that failed is reported and the rest are still removed, so call again to retry it. A 404 (the cluster is gone, or the API has no such route) raises LiumNotFoundError and nothing is removed; there is no per-pod fallback.
Full API reference: https://docs.lium.io/developers/sdk/reference
lium.ssh(pod) returns the pod's ssh command with -i <key> and the pinned host-key options
described under Configuration; pass refresh=True to rebuild it from the pod's current host and port
after a restart (lium.refresh_pod(pod) re-reads one pod by id or huid; LiumNotFoundError when it
is gone). lium ps --format json and lium describe (table and --json) show the same command
without -i as ssh_command (the key path lives in the SDK config, not in the pod record); the
JSON keeps the API's raw value as ssh_cmd.
Documentation
- CLI docs: https://docs.lium.io/developers/cli/overview
- SDK docs: https://docs.lium.io/developers/sdk
- Exit codes and the JSON error envelope: docs/exit-codes.md — what a script or agent gets back when a command fails (
--format json,LIUM_OUTPUT=json). - Agents and scripts: docs/agents.md — the non-interactive path end to end (env-var auth, JSON output, exit codes,
up → exec → rsync → rm, pod gotchas).
Binary Releases
- Supported binary targets:
darwin-amd64,darwin-arm64,linux-amd64,linux-arm64 - Maintainers can build locally with
bash scripts/build.sh [macos|linux|all](Linux builds go throughDockerfile.build) - A release is a GitHub release published on a
vX.Y.Ztag (.github/workflows/release.yml): the version is the tag (hatch-vcs; nothing in the tree is bumped), the workflow builds the four binaries with.sha256checksums, uploads them (plusinstall.sh, a combinedchecksums.txtand the sdist/wheel) to the release and then clears the pre-release flag; a separate job publishes the sdist/wheel to PyPI as soon as the Python build passes, independent of the binaries. Create the release with--prereleasesolatestdoes not point at it before the assets are uploaded. - Changes are recorded as fragments in
changelog.d/(one file per PR, named after its ticket; seechangelog.d/README.md) and folded intoCHANGELOG.mdbyscripts/changelog.pyat release time.
CLI Reference
The lium CLI exposes the full pod lifecycle. Run lium --help to see everything, or browse the reference below.
Core Commands
lium signup- Create an account from the terminal and store its API keylium init- Initialize configuration for an existing account (API key, SSH keys);--api-key <key>for machines without a browser;--forceto log in againlium completion [bash|zsh|fish] [--install]- Print or install shell tab completionlium balance- Show the account balance (add--format jsonfor machine-readable output)lium whoami- Show which API key is in use, where it came from, and the account it belongs tolium ls [--gpu TYPE] [--count N] [--country CODE] [--min-vram GB] [--max-price USD] [--tier spot|secure] [--format json]- List available nodeslium up [NODE_ID]- Create a pod (NODE_ID is the HUID or UUID fromlium ls, or its row number; or use filters like--gpu,--count,--country; cap it with--ttl 6hor--budget 12.50;--jsonprints the ready pod as JSON instead of opening SSH)lium ps [--sort KEY] [--filter KEY=VALUE] [--key NAME|ID] [--watch N] [--wide] [--format json]- List active pods; the#column is the row numberrm/ssh/exec/scpaccept in the same shell, for 10 minutes, and only while the pod shown on that row is still listed — the rows of the last listing, in the order shown (sorted or filtered). Use the huid in scripts. A PENDING pod gets a line under the table with the backend's start estimate and phase, worded aslium upwords it (eager-wolf-aa PENDING · est. ready in ~18 s (phase: pulling image)); its--format jsonrow carries the same text aseta_hint(nullwhen the backend sent neither).lium spend [--format json]- Hourly burn, estimated spend per pod, balance and runwaylium record <pod> [--format json]- What a run ran on (GPU, image, driver/CUDA, interconnect) and what it cost; works after removal with the pod UUIDlium quote <node> --hours N [--gpus N] [--volume-gb N] [--backup-gb N] [--format json]- A job's compute, volume and backup cost on a node at billing's rates, before rentinglium describe <POD>- Full manifest of one pod: ports, GPU, template, billing, last lifecycle event (why it is REBOOT_FAILED/BROKEN) and the node's disk health (add--jsonfor machine-readable output). A deleted pod can still be described by its id: you get the events the backend kept for it and the reason it went away.lium ssh <POD>- SSH into a podlium exec <POD> <COMMAND>- Execute command on pod (--jsonfor stdout/stderr/exit_code;-d/--detachstarts it in the background and returns immediately;--max-output-bytes Nsets the output limit, default 64 MiB)lium logs <POD>- Stream a pod's container logslium port-forward <POD> <PORT>- Forward a local port to a pod portlium scp <POD> <LOCAL_FILE> [REMOTE_PATH]- Copy files to pods (add-dto download from pods)lium rsync <POD> <LOCAL_DIR> [REMOTE_PATH]- Sync directories to pods (--bwlimit,--exclude,--delete,--progress; resumes on re-run)lium cp <SRC_POD>:<PATH> <DST_POD>:<PATH>- Copy files from one pod to another over SSHlium rm <POD>- Remove/stop a pod (--name-onlyto refuselium psrow numbers in scripts;--format jsonprints what was removed with its estimated spend;--rating 1-5 --feedback "..."rates the node in the same call)lium reboot <POD>- Reboot a podlium reset-gpu <POD> [--gpu N ...]- Reset a pod's hung GPUs; stops the pod's processes, keeps its files; waits 1-2 min for the resultlium gpu-clocks <POD> --lock MIN,MAX | --reset [--gpu N ...]- Lock a pod's GPU core clocks to a MHz range, or reset them to the default; the pod keeps runninglium audit [--pod POD] [--since 24h] [--key ID]- Who did what to the account's pods, and when: every rent, reboot, edit and delete with the session or API key that requested it (add--jsonfor machine-readable output)lium audit --account [--action pod.] [--source cli] [--since 7d] [--cursor <next_cursor>]- The account audit log: every request that changed something (pods, keys, logins, balance, settings, team members) with the client and IP it came from; your own IPs only, 90 days (--jsonprints the page withnext_cursor)lium update <POD> --jupyter <PORT>- Install Jupyter Notebook on a pod, served on that internal port (--jupyteris the only update; without it the command printsNo updates specified)lium templates [SEARCH] [--arch hopper|blackwell] [--format json]- List Docker templates with the CUDA build and the GPU generations it runs onlium fund- Fund account with TAO from your wallet (--alpha -k <hotkey> -a <USD>pays with alpha stake instead; add--netuid <N>to pay from any subnet Lium accepts, the primary subnet otherwise)lium topup create -a <USD> -c <COIN> -n <NETWORK>- Top up with a stablecoin (lium topup currencieslists them)lium topup card -a <USD> [--card <pm_id>] [--yes]- Charge a card saved on the account, with no browser; the API key needs thebillingscope (not released: the platform switch is off). Asks first;--yesskips the question and--jsonneeds it. Sent once with an idempotency key; a lost answer or a 202 without a payment_intent_id exits 6 ("the charge may have gone through" / "still being confirmed") with the key and the same amount to re-run with, never a bare retry. A 202 with a payment_intent_id is success (exit 0). The same key with a different amount is a new charge. A 3-D Secure step-up (CARD_AUTHENTICATION_REQUIREDwith a URL) exits 6 with the URL to confirm at, the transaction id and the exact re-run with the same--idempotency-key; a new key would open a second paymentlium transfer <EMAIL|USER_ID> <USD> [--note TEXT] [--yes] [--json]- Send part of your balance to another account; the API key needs thebillingscope. Asks first;--yesskips the question and--jsonneeds it. Fails non-zero with the API's code:self_transfer,insufficient_balance,not_found(no account with that e-mail or id),recipient_unavailable(a deleted or blocked account), ormissing_scope(exit 6, with how to make a billing key). AMOUNT is rounded to the cent (half up, as the API does) before the question. A lost or unrecorded answer (also Ctrl-C while waiting, or the same key still in progress) exits 6 withtransfer_outcome_unknownand the key: check transfer history on the Billing page of the account that owns the billing key, and if it is not there contact support with the key; do not run it again, with that key or a new onelium ssh-keys list|sync- SSH public keys registered on the account
ls, ps, spend, templates, balance and describe all accept --format json (and --json); rm accepts --format json; up accepts --json. All of them print a JSON error envelope on stderr when the command fails, so the same flag works across commands in scripts.
Volume Commands
lium volumes list- List all volumeslium volumes new <NAME>- Create a new volumelium volumes rm <TARGETS>- Remove volumes by HUID or id, or by their row numbers in the lastlium volumeslist (1or1,3); row numbers need a list from the same shell under 10 minutes old, or the command asks you to list again
Cluster Commands
lium clusters- Fabrics (InfiniBand/RoCE) with free nodes that can be rented as one multi-node clusterlium clusters up <FABRIC> --nodes N -n <NAME>- Rent N whole nodes of one fabric as a single all-or-nothing clusterlium clusters ps- Your clusterslium clusters show <CLUSTER> [--hostfile | --torchrun RANK]- Members with rank, overlay IP and SSH; launcher materiallium clusters rm <CLUSTER>- Remove every member in one API call (exit 5 when the cluster is gone)
Backup Commands
lium bk show <POD>- Show backup configuration for a podlium bk set <POD> --path <PATH>- Configure automatic backupslium bk logs <POD>- View backup logslium bk now <POD>- Trigger immediate backuplium bk cancel --id <BACKUP_ID>- Cancel an active backup and retain its historylium bk delete --id <BACKUP_ID>- Delete stored data for a completed backuplium bk restore <POD> --id <BACKUP_ID>- Restore from backuplium bk restore-cancel --id <RESTORE_ID>- Cancel an active restorelium bk rm <POD>- Remove backup configuration
Schedule Commands
lium schedules list- List scheduled terminationslium schedules rm <POD>- Cancel scheduled termination
Workspace Commands
Teams share a workspace whose billing owner pays. An API key is bound to one workspace, so --workspace NAME on any command means "use the key saved for NAME" and nothing else. On a server without workspaces these commands say so (exit 3) and every other command behaves as today.
lium workspaces [list]- The workspace this key acts in (the role shown is the account's the key runs as — the billing owner's for a team key); every workspace you belong to, with your own role, afterlium workspaces loginlium workspaces members [WORKSPACE]- Members, roles and who payslium workspaces use <WORKSPACE>- Default workspace for every command (~/.lium/config.ini); run it asLIUM_API_KEY=<a key bound to it> lium workspaces use <WORKSPACE>to save that key for--workspacelium workspaces login- Sign in once (e-mail + password) for the session-only subcommands below; needs no API key, and with none savedlium keysruns on the session alonelium workspaces create <NAME> [--use]- Create a workspace; you are its owner and billing ownerlium workspaces invite <EMAIL> [WORKSPACE] [--role member|admin|owner]- E-mail an invitation (no account needed yet)lium workspaces remove <USER_ID_OR_EMAIL> [WORKSPACE] [--yes]- Remove a member, asks first (owners and the billing owner cannot be; the server says so)lium workspaces transfer-billing <USER_ID_OR_EMAIL> [WORKSPACE] [--yes]- Hand the bill to another member (asks first)lium workspaces delete [WORKSPACE] [--yes]- Delete a workspace, asks first (owners; refused while pods run or volumes exist); drops its config sectionlium keys list [--workspace W]/lium keys create <NAME> [--scope read|rent|manage|billing]… [--daily-budget USD] [--monthly-budget USD] [--max-budget USD] [--pod-visibility own|account] [--workspace W] [--save]- API keys per workspace;--savekeeps the key for--workspace. Without--scopea key getsread,rent,manage—billing(card payments, credit transfers, crypto payments through the API) only when named, with a warning first, and alone:--scope billingwith any other scope is refused here, before any request (exit 2). Budgets — per UTC day, per UTC month, over the key's lifetime — delete the key's pods (data outside a volume is lost) and refuse a rent, a pod schedule change or a top-up through the key once its pods were billed that much (budget_exceeded, exit 6, naming the window hit);--pod-visibility ownshows the key only the pods it rented,accountevery pod — not passed, the server's default decides.listshows scopes, spent/budget per window and the active pods per key.--scope billingand its money-route rule (card payments, credit transfers and crypto payments through the API need a key holdingbilling), budgets, visibility and the pod count need a newer Lium server. Budgets, visibility or the pod count asked of a server that does not record them (each judged on its own):createrefuses (exit 2; a key the server minted uncapped is revoked) unless--allow-unbudgetedis passedlium keys show <NAME|ID>/lium keys scopes- One key with "What this key can do" in the server's words (GET /keys/scopes), spent/budget per window, its pods and the last requests its budget refused (GET /keys/{id}/refusals: how many today, the newest one's route, amount and window) / every scope and pod-visibility value with what it allows. Both need a newer Lium server: an older one answersnot_foundforscopesand shows the scope names alone inshowlium keys budget <NAME|ID> [--daily-budget USD] [--monthly-budget USD] [--max-budget USD] [--no-daily-budget] [--no-monthly-budget] [--no-max-budget]- Set or clear a key's budgets after creation (PATCH /keys/{id}; at least $1, whole cents, daily ≤ monthly ≤ max; a budget not named is left alone). Scopes and pod visibility stay fixed at creation. Needs a server with per-key budgets (an older server has no such route, and a window it did not record is refused, exit 2)lium ps --key <NAME|ID>/lium billing history [--key <NAME|ID>] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--format json]- The pods one API key rented / what the ledger charged per pod, removed pods included, for the account or one key (GET /billing/statement). The per-key filter needs a server that stamps pods and charges with their key: an older server cannot filter, and both commands then say so and show the account's figures as the account's. A key name needslium workspaces login; an id does notlium --workspace NAME <command>/LIUM_WORKSPACE=NAME- Run one command with the key saved for NAME (refused, exit 2, when none is saved);ps,ls,up,rmprint the workspace they act in, and when that key turns out to act elsewhereup/rmrefuse (exit 2) whileps/lswarn.lium workspaces …andlium keys …themselves run withLIUM_API_KEY, else NAME's saved key, else the stored default's saved key, else[api] api_key, so they can mint or save the missing key
Configuration Commands
lium config show- Show all configurationlium config get <KEY>- Get configuration valuelium config set <KEY> <VALUE>- Set configuration valuelium config unset <KEY>- Remove configuration keylium config edit- Edit configuration filelium config path- Show configuration file pathlium config reset- Reset all configuration
Provider Commands
lium provider … is the provider-side CLI — full automation parity with the provider portal at https://provider.lium.io: portal authentication, node lifecycle, central-miner-server configuration, batch sync, billing, and machine-request queries. Hotkey registration is still handled separately via btcli subnet register.
Group-level flags inherited by every subcommand: -w/--coldkey, -k/--hotkey, --portal-url, --json, --debug, -y/--yes, --dry-run. Persist wallet identity once with lium config set provider.coldkey <NAME> and lium config set provider.hotkey <NAME>. Spend-affecting subcommands run a persona prompt unless --yes or LIUM_PROVIDER_ACK=1 is set.
Account without a key (Google or e-mail): create an API token at https://provider.lium.io/settings (API tokens), then export LIUM_PROVIDER_TOKEN=lpk_... (or save it with lium provider portal login --token); lium provider commands then run without --hotkey. An e-mail account can also run lium provider portal login --email <address>.
lium provider portal {login,logout,whoami}- Manage the cached portal JWTlium provider status [--netuid 51]- Aggregated provider snapshot (registration, portal session, nodes, validator weights)lium provider node list|get|add|rm|update-price|update-gpu|address- Node lifecycle on the portal;node list [--all | --miner-hotkey HK]shows the active hotkey's nodes by default,--allevery provider'slium provider node address <NODE_ID> <IP> <PORT>- Change a node's IP and port in place (keeps its id and history), e.g. after an ISP changes the IPlium provider node min-gpu set|unset <NODE_ID> [COUNT]- Min GPU count for rental matchmakinglium provider node pods <NODE_ID>- Pods currently rented on a nodelium provider node machine-requests <NODE_ID>- Pending tenant requests on a nodelium provider node notice-period set <NODE_ID> --start <ISO 8601> --minutes N | --permanent [--reason TEXT]- Open a notice period: a maintenance window of up to 60 minutes, or a permanent removallium provider node notice-period unset <NODE_ID>- Close the node's notice periodlium provider node notify-added <NODE_ID> --request-id <REQ>- Mark a tenant machine request fulfilledlium provider config show|opt-in|opt-out|set-email|set-subscriptions- Portal-account configuration (incl. lium.io central miner server toggle)lium provider sync from-miner-server|to-miner-server- Batch node-state sync between portal and the central miner serverlium provider billing list [--from YYYY-MM-DD] [--to YYYY-MM-DD]- Your daily earnings ledger (the signed-in provider's own rows; last 30 days by default, at most 90)lium provider machine-request list|get- Pending tenant machine requests (per-request detail only once one of your nodes is verified by a validator; before thatlistreturns counts per GPU class and hourly budget band, andgetexits 2 withPORTAL_FORBIDDEN)lium provider machine list|estimate- Machine catalogue + reward estimates
Full reference with every flag and runnable examples: https://docs.lium.io/developers/cli/reference/provider.
Other Commands
lium mcp- Serve Lium as an MCP server over stdio for agent hosts (pip install "lium.io[mcp]"; see MCP server for agent hosts)lium theme dark|light- Set the CLI colour theme (the argument is required; there is noauto; the value is stored as[ui] theme—lium config get ui.themereads it back)lium mine- Set up this host as a provider nodelium mine --register <TOKEN>- Same, then add the node to your portal account from what the host reports and wait until it is listed (token from the portal's Add Node page; the account, and what the node reports under, come from the token — no-k)sudo lium gpu-splitting setup [--device /dev/...] [--yes]- Prepare Docker storage for LIUM GPU splittinglium gpu-splitting check [--device /dev/...]- Inspect the host and print the GPU-splitting planlium gpu-splitting verify- Verify Docker storage matches LIUM GPU-splitting requirements
Command Examples
lium ls --format json fields
One object per node, sorted as the table is; the names are stable:
The listing asks for GET /executors?view=summary, about 1 KB per node instead of about 8.6 KB. The summary view carries the nvlink and interconnect keys that link, nvlink, p2p and interconnect read. Lium.ls(view="full") returns the whole public node scrape in ExecutorInfo.specs, without what the renters' pods are doing (docker containers, GPU processes, per-GPU load, memory in use, current clocks and PCIe link); read your own pod's load with Lium.gpu_stats.
Features
- Dual Interface: Same package ships both the
liumCLI and a Python SDK (lium.sdk.Lium+@lium.machinedecorator) - Pareto Optimization:
lscommand shows optimal nodes with ★ indicator - Flexible Pod Creation: Use node index or auto-select with filters (GPU type, count, country)
- Index Selection: Use numbers from
lsoutput in commands - Full-Width Tables: Clean, readable terminal output
- Cost Tracking: See spending and hourly rates in
ps - Interactive Setup:
initcommand for easy onboarding - Volume Management: Create and attach persistent storage volumes
- Backup & Restore: Automated backups with configurable frequency and retention
- Auto-Termination: Schedule pods to terminate after duration or at specific time
- Jupyter Integration: One-command Jupyter installation on pods
- Theme Support: Light, dark, or auto-detect themes for better visibility
Configuration
Configuration is stored in ~/.lium/config.ini:
You can also use environment variables:
LIUM_API_KEY takes precedence over the config file. To see which key a shell is using, run lium whoami (or lium balance / lium config get api.api_key): they print the key's fingerprint and source (env:LIUM_API_KEY or config:~/.lium/config.ini [api] api_key), and authentication errors name the same key.
With workspaces, lium workspaces use (when the key it runs with acts there) and lium keys create --save add:
Key resolution: an explicit --workspace / LIUM_WORKSPACE uses the key saved for it and nothing else (exit 2 when none is saved). Otherwise, first match wins: LIUM_API_API_KEY / LIUM_API_KEY (the env key, in the CLI's order), the key saved for [workspaces] active, [api] api_key. The [workspace.<name>] section is written when a key is saved (lium keys create --save, or lium workspaces use run with a key that acts there); lium workspaces delete drops it. Sections are keyed by the lower-cased name, so a save into a section that already holds another workspace's id (two workspaces with one name) is refused (exit 2) rather than overwriting the first one's key — drop that section or rename one of the workspaces first.
For a machine with no browser — an agent's sandbox, CI, a container — lium init --api-key <key> checks the
key against /users/me, saves it to the file with mode 600 and sets up the SSH key, without opening anything or
asking anything; --json prints {"ok", "api_key_source", "saved_from", "env_key", "active_workspace", "config_path", "ssh_key_path"} — api_key_source is the same value lium whoami --json prints: where the next
command reads the key by the Key resolution order above (env:LIUM_API_KEY, config:<path> [api] api_key, or a
[workspace.<name>] key when lium workspaces use selected one — then active_workspace names it and the text
output warns that it wins over the key just saved), saved_from how this run got it (flag, env, config,
session, browser). --json needs --api-key or an exported key; the browser flows print for a person. A
refused key exits 3 (invalid_api_key), an API that cannot be reached exits 3 (api_unreachable), an empty value
exits 2 (empty_api_key); none of them saves anything, and the hint says so. With LIUM_WORKSPACE / -w set,
lium init exits 2: commands then run with the key saved for that workspace, which init does not write — use
lium keys create <name> --workspace <ws> --save. With LIUM_API_KEY (or LIUM_API_API_KEY) already exported,
lium init skips the browser, sets up the SSH key and says the key is coming from the environment — the SSH path
is written to the file, the key is not; --api-key warns when a key is also exported (env_key in the JSON).
Login keys can expire or be revoked, so lium init next to a saved key checks it against
/users/me first. When the API rejects it (401, or a 403 saying the key's workspace is gone or its creator left it) the normal login runs
(Your saved API key has expired or was revoked. Starting a new login…); without a terminal no browser is opened
and lium init exits 6 (saved_key_rejected). Any other 403 (a blocked account, a firewall page), a 429, a 5xx or
no answer keeps the key with a warning. lium init --force logs in again even when the key still works, and
lium init --session <ID> exchanges the session even next to a saved key. The saved key is replaced only once the
new login has produced a key (the config file is written to a temporary file and renamed over the old one), so an
aborted browser login or an unapproved session leaves the old key in place.
SSH host keys of pods are pinned on first use under ~/.lium/known_hosts/<pod-id>
(lium ssh, lium up, and the SDK's exec, stream_exec, rsync). reboot, edit,
switch_template and rm drop the pin themselves (the container, and its key, are replaced).
A pod that later presents a different key is rejected — the SDK raises LiumHostKeyError,
lium ssh stops with OpenSSH's own "host identification has changed" message — after a
reboot the platform did on its own, or an interception; delete that file if the pod was
legitimately re-provisioned. Fingerprints are SHA256:…, as ssh-keygen -lf prints them.
LIUM_SSH_INSECURE=1 restores the old accept-anything behaviour (each accepted key is
reported on stderr). lium ssh runs OpenSSH with an argument list built from the
pod's user, address and port; the API's connection string is never handed to a shell.
Scripts and agents (non-interactive use)
The CLI never waits on a prompt it cannot show. When stdin is not a terminal, or
LIUM_NONINTERACTIVE=1 is set, a command that would have asked a question either
takes its documented default or fails immediately (exit code 2) with a hint naming
the flag to pass:
MCP server for agent hosts
lium mcp serves Lium as a Model Context Protocol server over stdio, so Claude Desktop, Claude Code, Cursor or any
MCP host can rent and use GPUs itself. Tools: list_gpus, rent_pod, list_pods, run_command, remove_pod,
get_balance. It uses the API key lium init saved, or LIUM_API_KEY.
Without installing anything, with uv:
rent_pod with dry_run: true quotes a node first (tier, reliability, cost per hour, your balance) and rents
nothing, and enable_backups: false rents without the automatic backup. A rented pod is billed per second until remove_pod. The hosted MCP server at https://docs.lium.io/mcp
(no key, read-only) is separate: it searches the docs and gives live GPU prices and availability; rent_pod takes
the node ids its search_gpus returns.
Crash reporting (opt-in, off by default)
The CLI never sends telemetry unless you turn it on:
When on, an unexpected error (a bug, shown as Unexpected error: …) is reported once with the
exception, its stack trace, the command name (lium up), the CLI version, the Python version,
the OS and the API host the CLI is configured for (lium.io, or your LIUM_BASE_URL). API errors,
usage errors, arguments, option values and local variables are never sent; the exception message is
sent with the values you passed on the command line (a pod name, a path), home-directory paths
(macOS, Linux and Windows), e-mails and API keys cut out of it. Reports go to Lium's Sentry project;
LIUM_SENTRY_DSN points them somewhere else (a self-hosted GlitchTip, for example) and
LIUM_SENTRY_DSN= (empty) keeps them off even when enabled. A value that is not a DSN prints one
warning on stderr and keeps reporting off; the command itself still runs.
Requirements
- Python 3.10 – 3.14 (
requires-python = ">=3.10, <3.15") - The chain commands (
lium provider …,lium fund) need theproviderextra —pip install 'lium.io[provider]'— whose chain libraries build only on Python < 3.14; on 3.14 the install stays quiet and the CLI explains the gap when a provider command runs
Development
The repository is a uv project (uv.lock); CI installs with uv sync --frozen --extra dev --extra provider
and runs the unit tests on Python 3.10 and 3.12:
.github/workflows/ci.yml (CI - Build Verification) runs on every PR: the unit tests, the packaging-inputs
check, an sdist/wheel build and the binary-target matrix check (test/test_release_binary_targets.py). The binary
builds (Linux amd64/arm64 in Docker, macOS arm64) run only when a packaging input changes (pyproject.toml,
uv.lock, lium.spec, lium_entry.py, Dockerfile.build, scripts/install.sh, scripts/linux_bundle_report.py,
ci.yml, release.yml); macOS Intel never builds on a PR. A manual dispatch (gh workflow run ci.yml --ref <branch>)
builds all five targets and runs the live e2e (./e2e/run.sh, see e2e/README.md), which otherwise runs once a day
on a schedule and never on a PR. ci-ok (the aggregate of the test, packaging,
wheel and target-matrix jobs) and e2e-live are the two status checks the main ruleset requires.
License
MIT License - see LICENSE file for details.
Source: README.md at commit 82eb7f0
Tools
0Version history
17- v0.41.7LatestNov 7, 2026
- v0.41.4Nov 5, 2026
- v0.41.3Nov 4, 2026
- v0.41.2Nov 3, 2026
- v0.41.1Nov 3, 2026
- v0.41.0Nov 3, 2026
- v0.38.1Oct 30, 2026
- v0.35.1Oct 29, 2026
- v0.30.1Oct 26, 2026
- v0.29.2Oct 26, 2026
- v0.29.0Oct 26, 2026
- v0.28.2Oct 23, 2026
- v0.28.1Oct 23, 2026
- v0.28.0Oct 22, 2026
- v0.27.2Oct 22, 2026
- v0.26.0Oct 21, 2026
- v0.25.0Oct 20, 2026
Score
50% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts