Backends
A backend provides compute resources for task execution. v0.1 ships with local, slurm, docker, and kubernetes backends.
Default behavior (simplest)
If you omit backends: entirely, sflow creates a default local backend:
- backend:
local(synthetic allocation:localhost,localhost-1, ...) - default operator:
bash
This is why a minimal workflow with just workflow: and tasks: works without any backend/operator config.
Explicit local backend
Explicit local backend example:
version: "0.1"
backends:
- name: local
type: local
default: true
workflow:
name: wf
tasks:
- name: hello
script:
- echo hello
The local backend can also simulate multiple nodes on one machine: set
nodes: N (default 1) and sflow synthesizes hosts localhost, localhost-1, …
so you can rehearse multi-node placement locally. Like the other bare-host backends
it honors offload_task_logs (default true) to write each task's <task>.log
directly instead of streaming through the driver.
Slurm backend
Slurm backend example:
version: "0.1"
backends:
- name: slurm_cluster
type: slurm
default: true
account: "your_slurm_account"
partition: "your_slurm_partition"
time: "00:10:00"
nodes: 1
gpus_per_node: 8 # required for sflow planning; set to 0 for CPU-only partitions
workflow:
name: wf
tasks:
- name: slurm_task
script:
- echo hello
Set gpus_per_node: 0 to target a CPU-only Slurm partition. With zero capacity,
tasks that declare resources.gpus will be rejected up front with a clear error,
preventing silent CUDA failures at runtime.
gpus_per_node describes cluster topology for sflow resource planning and GPU
index assignment. It does not add --gpus-per-node to salloc. If your cluster
requires that Slurm allocation flag, add it explicitly in backend extra_args.
Notes:
-
If you don't specify
task.operator, the backend chooses its default operator:- local backend →
bash - slurm backend →
srun - docker backend →
docker_run - kubernetes backend → no default; declare an explicit
k8soperator (the workload image lives on the operator)
- local backend →
-
You can run
sflowasynchronously viasbatch:sbatchreturns immediately with a job id;sflowruns inside the batch allocation.- In this mode,
sflowwill reuse the current allocation (no extrasalloc). - Make sure your
--workspace-dir/--output-dirpoint to a shared filesystem so you can inspect logs while it runs. - Controller-provided
SLURM_*/SLURMD_*environment variables are preserved for tasks, even if workflow variables use the same names. sflow also exposes backend-agnostic aliases such asSFLOW_BACKEND_JOB_IDandSFLOW_BACKEND_NODELIST.
Example:
sbatch --job-name=sflow --output=sflow-%j.out --wrap "cd $SLURM_SUBMIT_DIR && sflow run --file sflow.yaml"
Besides the common fields shown above (account, partition, time, nodes,
gpus_per_node, extra_args, include_nodes/exclude_nodes), the Slurm backend
also accepts:
| Field | Default | Description |
|---|---|---|
job_name | workflow name | salloc --job-name. Falls back to the backend name, then is set to the workflow name at resolve time. |
offload_task_logs | true | Have srun write each task's <task>.log on the compute side (via --output) instead of streaming every line through the driver. Auto-falls back to streaming on an interactive TTY / --tui. Also toggled by --offload-task-logs / --no-offload-task-logs or SFLOW_OFFLOAD_TASK_LOGS. |
timeaccepts either an"HH:MM:SS"string or an integer number of minutes.
Cluster-specific flags (extra_args)
Some Slurm clusters require additional flags for job submission (e.g., GPU resources, network segments, or custom policies). Use the extra_args section to pass these cluster-specific options:
version: "0.1"
backends:
- name: gpu_cluster
type: slurm
default: true
account: "myproject"
partition: "gpu"
time: "01:00:00"
nodes: 2
gpus_per_node: 8
extra_args:
- "--gpus-per-node=8"
- "--segment=2"
- "--exclusive"
workflow:
name: wf
tasks:
- name: gpu_task
script:
- nvidia-smi
- echo "Running on GPU nodes"
Common cluster-specific flags include:
| Flag | Description |
|---|---|
--gpus-per-node=N | Request N GPUs per node |
--segment=<name> | Target a specific network segment or job class, usually GB200 / GB300 |
--exclusive | Request exclusive node access |
--mem=<size> | Memory per node (e.g., 128G) |
Check your cluster's documentation or run sinfo / scontrol show partition to discover available partitions, segments, and resource constraints.
When using sflow batch mode, you can also pass extra Slurm flags directly via the -e flag without modifying the YAML file:
sflow batch -f workflow.yaml -e "--gpus-per-node=8" -e "--segment=2"
This is useful for quick adjustments or when testing different cluster configurations.
GPU placement inside the step
On a GRES partition slurmstepd overwrites the CUDA_VISIBLE_DEVICES sflow exports, so
every step sees the whole allocation and every rank picks device 0 — several tasks that
were planned onto different GPUs all pile onto the same one. sflow therefore no longer
trusts the inherited value. A prelude sourced inside each Slurm step probes the devices
the step can actually see, looks up the physical UUIDs the driver resolved this task's
plan to (SFLOW_PLANNED_GPU_UUIDS), and re-exports the indices those same cards have
here. Matching by UUID rather than by index is also what fixes pyxis/enroot containers,
which renumber devices from 0.
This is on by default for any Slurm task with a resources.gpus slice. The one thing
that opts out is srun gpus_per_task, because Slurm already carves GPUs per rank there.
- Audit record. Each task writes
<task>/sflow_gpus.log(sflow_gpus.<node>.logper node on multi-node tasks): planned indices and UUIDs, the inherited environment, the visible index→UUID map, and the final selection. It is deliberately not dot-prefixed so artifact browsers show it. TheGPU Assignmentsection ofsflow_summary.logis built from it. - Hard failure
exit 97. If a planned card is not visible at all, the step holds fewer GPUs than planned, or a planned slot is out of range, the step aborts with exit code 97 rather than silently running on the wrong device. When Slurm — not sflow — chose the devices, sflow degrades to index arithmetic instead of failing. - Graceful skips. If
CUDA_VISIBLE_DEVICESis not a plain comma-separated list of non-negative integers (for example a workflow variable of that name shadowing it), or the placement script cannot be staged, sflow warns and falls back to the previous behavior.
NVIDIA_VISIBLE_DEVICES is no longer exported to srun steps. Containers therefore see all
of the node's GPUs — NVML consumers such as nvidia-smi and DCGM lose device isolation —
in exchange for the planned host-numbered slice being addressable at all. The Docker
backend is unaffected: it keeps isolation via --gpus device=<uuid>.
Selecting or excluding nodes (all backends)
Restrict which cluster nodes a run may use with two backend-agnostic controls that apply to every backend:
- CLI flags on
sflow runandsflow batch:--include-nodesand--exclude-nodes. Both accept comma-separated lists, quoted whitespace-separated lists, and/or repeated flags (--exclude-nodes a,b,--exclude-nodes "a b",--exclude-nodes a --exclude-nodes b). - YAML fields on any backend:
include_nodesandexclude_nodes. CLI values are unioned over the recipe's values.
include_nodes restricts the candidate pool to the listed hosts; exclude_nodes
removes the listed hosts. A host may not appear in both.
# Keep this run off two flaky nodes, across whatever backend the recipe uses.
sflow run -f workflow.yaml --exclude-nodes gpu-07,gpu-16
# Pin an experiment to specific hosts.
sflow run -f workflow.yaml --include-nodes gpu-01,gpu-02
backends:
- name: gpu_cluster
type: slurm
account: myproject
partition: gpu
time: "01:00:00"
nodes: 2
gpus_per_node: 8
exclude_nodes: [gpu-07, gpu-16]
Each backend translates the lists to its native node selection:
| Backend | include_nodes | exclude_nodes |
|---|---|---|
| Slurm | salloc/#SBATCH --nodelist= (reused allocations are filtered in-process) | salloc/#SBATCH --exclude= |
| Kubernetes | kubernetes.io/hostname In nodeAffinity on the reservation pods | kubernetes.io/hostname NotIn nodeAffinity |
Docker (with hosts:) | keep only matching hosts in the pool | drop matching hosts from the pool |
Local / Docker without hosts: | ignored (single machine) with a warning | ignored with a warning |
Complex Slurm hostlist expressions (e.g. node[01-05]) should be passed through
Slurm's own flags via extra_args / -e; --include-nodes / --exclude-nodes
take plain hostnames.
Docker backend
The Docker backend uses a synthetic local allocation for planning and launches tasks
through the docker_run operator, which invokes docker run:
version: "0.1"
backends:
- name: docker
type: docker
default: true
image: ubuntu:22.04
nodes: 1
gpus_per_node: 0
workflow:
name: docker_hello_world
tasks:
- name: hello
script:
- echo "hello from docker"
For GPU tasks, install the NVIDIA container toolkit on the Docker host and set
gpus_per_node to the host GPU count. When a task declares resources.gpus,
sflow reserves that many physical GPUs and launches the container with
--gpus "device=<uuid,...>" so it sees exactly those GPUs (as
CUDA_VISIBLE_DEVICES=0,1,… inside). A task that declares no GPUs gets
NVIDIA_VISIBLE_DEVICES=void and sees none. See
GPU reservation below.
Docker backend fields beyond image/nodes/gpus_per_node:
| Field | Default | Description |
|---|---|---|
mounts |