Recipes
Turn an sbatch script you have already tested into a recipe that obleth can launch on Slurm again and again, with the few settings someone launching it may change.
5 min readUpdated for v2.4.0Edit on GitHub
/deployments?tab=recipes)Write a recipe
- 1
Start from a script
On Deployments › Recipes, choose New recipe and paste a script you already run on the cluster. To change a library recipe, choose Copy to edit.
- 2
Add the header
Put the YAML header between
---fences above the script: at leastname,engine,model_type,api_model_nameandport. Declare what someone launching it may change asinputs, and write{{name}}where each goes in the script. - 3
Save and launch
Save it, then choose Launch on its row. See Launch on Slurm.
---
name: Qwen3 8B
description: "Qwen3 8B on one GPU with vLLM."
engine: vllm
kind: model
model: Qwen/Qwen3-8B
weights_gb: 17
model_type: chat
api_model_name: qwen3-8b
port: 8000
target_replicas: 1
max_job_failures: 2
env:
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
inputs:
- name: context
label: Context length
type: choice
options: [16384, 32768]
default: 32768
- name: tools
label: Tool calling
type: flag
default: true
adds: --enable-auto-tool-choice --tool-call-parser hermes
capabilities: [function_calling, tool_choice]
- name: image
label: Container image
type: path
default: "{{cluster.image.vllm}}"
required: true
- name: cache
label: Weight cache
type: path
default: "{{cluster.cache}}"
required: true
---
#!/bin/bash -l
#SBATCH --gres=gpu:1
#SBATCH --cpus-per-task=8
#SBATCH --mem=48G
set -euo pipefail
{{cluster.setup}}
export HF_HOME={{cache}}
apptainer exec --nv --bind {{cache}} {{image}} \
vllm serve Qwen/Qwen3-8B \
--served-model-name {{api_model_name}} \
--host 0.0.0.0 --port "$OBLETH_SERVING_PORT" \
--max-model-len {{context}} \
{{tools}}
obleth submits the script after a short block it adds to bind the replica's port, with env exported at the top and Unix line endings. The server must listen on $OBLETH_SERVING_PORT, not a fixed port, and serve the model under the API model name it is launched as, which {{api_model_name}} fills in. slurmrestd ignores #SBATCH lines in a submitted script, so obleth reads them into the job's settings and leaves them out of the script it stores.
Header fields
| Field | Required | Default | Meaning |
|---|---|---|---|
name | yes | Shown on the Recipes tab and in New deployment | |
description | no | A short summary | |
engine | yes | For example vllm, sglang, llamacpp, ollama | |
kind | no | model | model serves one model; engine serves any model and asks for it at launch |
model | no | The Hugging Face repo or Ollama tag served, for the picker and the fit check | |
weights_gb | no | Size of the weights, for the fit check | |
requires | no | What the image must provide, such as a minimum engine version | |
model_type | yes | The model's type, such as chat | |
api_model_name | yes | The API model name suggested at launch | |
port | yes | The base of each replica's port window | |
health_path | no | / for ollama, otherwise /health | The path that must answer 2xx before a replica serves |
target_replicas | no | 2 | Replicas to keep running |
min_replicas | no | 1 | Serving replicas needed before the model counts as healthy |
max_job_failures | no | 3 | Stop after this many failed launches; 0 never stops |
node_options, node_notes | no | Node counts offered as Runs on, and a sentence per count on what it means | |
env | no | Environment variables exported at the top of the job | |
log_output_dir | no | the #SBATCH --output folder, else the cluster's Job logs | Folder for each job's .out and .err |
inputs | no | Values asked for at launch (below) | |
tested | no | Where the recipe was run and served (below) | |
based_on | no | On a saved recipe, the recipe it was saved from |
Placement fields are optional too: partition, gres, cpus_per_task, mem, time_limit, nodes, account, qos, constraints, exclude. Leave them out to keep a recipe usable on any cluster and choose them at launch. What you choose at launch wins over the header, and the header wins over the script's #SBATCH lines. mem: 0 asks for all of each node's memory.
Inputs
Each input is a setting someone launching the recipe may change. It has a name (a letter or underscore, then letters, digits or underscores), and optionally a label, help text, a default and required: true.
type | What it takes |
|---|---|
text | Any text; the default type |
choice | One of options |
number | A number within min and max, shown with unit |
path | A path on the cluster |
flag | On or off. On, {{name}} becomes adds; off, it becomes empty |
A flag can also declare capabilities, from function_calling, tool_choice, response_schema and vision. When the flag is on at launch, the model obleth creates gets those capabilities, so a recipe whose tool flag adds a tool parser launches a model flagged for function calling, and image generation and web search work on it.
by_nodes gives an input a default per node count, such as {"1": "270", "4": "20"}, for a value that follows Runs on.
At launch, each declared {{name}} gets its value, or its default. An optional input left empty becomes empty; a required one without a value stops the launch. Undeclared {{…}} and shell ${…} and $(…) pass through untouched.
Cluster values
A default can name a cluster value, so no recipe hard-codes a path. Each comes from Cluster defaults on the Slurm connection:
| Token | Cluster default |
|---|---|
{{cluster.cache}} | Weight cache |
{{cluster.images}} | Images folder |
{{cluster.image.<engine>}} | Image per engine: a full path, or a file name in the images folder |
{{cluster.logs}} | Job logs |
{{cluster.setup}} | Load before launch: lines run at the top of the job; empty when unset |
A launch is refused while a value the script needs is unset, and says which one to set.
The older variables
variables: (name, label, default, required) still works: each is read as a text input. When a name is in both lists, the typed input wins.
Record where it ran
A recipe can list the runs that served it under tested, newest first:
tested:
- hardware: "1× 80 GB GPU"
engine: "vLLM <version>"
date: 2026-10-01
result: "What you saw: time to ready, speed, tool calls."
hardware is required. New deployment shows each run as a sentence, and the Library and Engines lists say where a recipe last served.
The Recipes tab
The tab has three lists. Each row shows the recipe's engine, the values it asks for and the deployments launched from it; a library row also says where it last served. A recipe that can't be read is still listed, with the reason.
- Saved recipes are your team's, stored by the gateway: a library recipe or engine with the values that worked. Save one at launch with Also save these settings as a recipe, or from a serving deployment's page with Save as recipe, which writes its values into the source recipe's header and keeps cluster defaults as
{{cluster.*}}references. You can Edit and Delete them; deployments launched from a deleted recipe keep running. - Library recipes are the
*.recipefiles withkind: modelinOBLETH_RECIPES_DIR(default:recipes/in the dashboard's working directory), named by their file stem. They can't be changed from the dashboard. The repository ships a set incontrol-plane/recipes/, each with where it was tested. - Engines are the files with
kind: engine: one per engine (vLLM, SGLang, llama.cpp and Ollama) in the shipped set. They serve a Hugging Face model that has no recipe yet, and are offered after you pick one under From Hugging Face.
The old /recipes page redirects here.
Management API
Saved recipes are managed with GET and POST /api/v1/recipes and PUT and DELETE /api/v1/recipes/{id}, each with a name and the recipe body. File recipes are read by the dashboard from its recipes directory; it reads only files directly in that directory, and refuses a path or link that leads outside it.
