Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions examples/slurm_inspect/demo_slurm.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
from rich.console import Console
from rich.table import Table

from sifter import find_latest_container, get_jobs
from sifter import api

from isambard_container_tools._helpers.pre_download import (
DatasetSpec,
pre_download_datasets,
pre_download_models,
)
from isambard_container_tools.engines.vllm import get_num_nodes_gpus, submit_job
from isambard_container_tools.engines.vllm.serve import find_latest_container
from isambard_container_tools.engines.vllm.serve import _parse_value # noqa: PLC2701

LOG_DIR = Path(__file__).resolve().parents[2] / "logs"
Expand Down Expand Up @@ -109,7 +110,7 @@ def get_results(jobs: list[JobInfo], console: Console) -> dict[str, dict[str, st
id_to_job = {j.job_id: j for j in jobs}

while pending:
states = {j.job_id: j.state for jid in pending for j in get_jobs(job_id=jid)}
states = {j.job_id: j.state for jid in pending for j in api.status(job_id=jid)}
for job_id in list(pending):
state = states.get(job_id, "UNKNOWN")
if any(state.startswith(s) for s in terminal_states):
Expand Down Expand Up @@ -213,7 +214,7 @@ def demo_slurm(

# Use the plain vLLM container (without vllm-lens) for vanilla runs
if vanilla and container is None:
container = find_latest_container(prefix="vllm-")
container = find_latest_container()

# Compute serving config and submit jobs
jobs: list[JobInfo] = []
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"python-dotenv==1.2.2",
"pyyaml==6.0.3",
"rich==15.0.0",
"sifter==0.2.0",
"sifter-build==2.0.0",
"typer==0.26.7",
]
description="Isambard HPC container management utilities"
Expand Down Expand Up @@ -40,7 +40,7 @@
[tool.uv]
environments=["sys_platform == 'linux'"]
[tool.uv.sources]
sifter=[{git="https://github.com/UKGovernmentBEIS/sifter.git"}]
sifter-build=[{git="https://github.com/UKGovernmentBEIS/sifter-build.git", tag="2.0.0"}]
torch=[{index="pytorch-cu126", marker="sys_platform == 'linux'"}]

[dependency-groups]
Expand Down
138 changes: 69 additions & 69 deletions sifter.yaml
Original file line number Diff line number Diff line change
@@ -1,98 +1,98 @@
# Isambard Containers Build Manifest
#
# Build all: sifter build --all
# Build one: sifter build vllm-0.17.1
# Preview: sifter build --all --dry-run
# Push to S3: sifter push --release
# Pull from S3: sifter pull vllm-0.18.0_0.1.0.sif
# Build all: sifter build --all
# Build one: sifter build vllm-0.23.0
# Preview: sifter build --all --dry-run

name: isambard-containers

builds:
# ============================================================
# Base image: CUDA 12.6 + PyTorch 2.9.1 for Isambard GH200
# ============================================================
pytorch-2.9.1-cu126_0.1.0:
steps:
- path: definitions/pytorch/pytorch.def
args:
PYTORCH_VERSION: "2.9.1"
CUDA_VERSION: "12.6"
pytorch-2.9.1-cu126:
tag: "0.1.0"
definition: definitions/pytorch/pytorch.def
args:
PYTORCH_VERSION: "2.9.1"
CUDA_VERSION: "12.6"

pytorch-2.11.0-cu129_0.1.0:
steps:
- path: definitions/pytorch/pytorch.def
args:
PYTORCH_VERSION: "2.11.0"
CUDA_VERSION: "12.9"
CUDA_IMAGE: "nvidia/cuda:12.9.0-cudnn-devel-ubuntu24.04"
pytorch-2.11.0-cu129:
tag: "0.1.0"
definition: definitions/pytorch/pytorch.def
args:
PYTORCH_VERSION: "2.11.0"
CUDA_VERSION: "12.9"
CUDA_IMAGE: "nvidia/cuda:12.9.0-cudnn-devel-ubuntu24.04"

pytorch-2.11.0-cu1302_0.1.0:
steps:
- path: definitions/pytorch/pytorch.def
args:
PYTORCH_VERSION: "2.11.0"
CUDA_VERSION: "13.0.2"
CUDA_IMAGE: "nvidia/cuda:13.0.2-cudnn-devel-ubuntu24.04"
pytorch-2.11.0-cu1302:
tag: "0.1.0"
definition: definitions/pytorch/pytorch.def
args:
PYTORCH_VERSION: "2.11.0"
CUDA_VERSION: "13.0.2"
CUDA_IMAGE: "nvidia/cuda:13.0.2-cudnn-devel-ubuntu24.04"

# ============================================================
# vLLM
# ============================================================
vllm-0.23.0_0.1.0:
base: pytorch-2.11.0-cu1302_0.1.0
steps:
- path: definitions/vllm/vllm.def
args:
VLLM_VERSION: "v0.23.0"
TRANSFORMERS_OVERRIDE: "==5.12.1"
FLASHINFER_VERSION: "0.6.12"
vllm-0.23.0:
tag: "0.1.0"
needs: pytorch-2.11.0-cu1302:0.1.0
definition: definitions/vllm/vllm.def
args:
VLLM_VERSION: "v0.23.0"
TRANSFORMERS_OVERRIDE: "==5.12.1"
FLASHINFER_VERSION: "0.6.12"

# ============================================================
# vLLM + vllm-lens variants (includes activation inspection plugin)
# ============================================================
vllm-lens-0.23.0_0.1.0:
base: pytorch-2.11.0-cu1302_0.1.0
steps:
- path: definitions/vllm/vllm.def
args:
VLLM_VERSION: "v0.23.0"
TRANSFORMERS_OVERRIDE: "==5.12.1"
FLASHINFER_VERSION: "0.6.12"
VLLM_LENS_VERSION: "1.1.0"
VLLM_USE_V2_MODEL_RUNNER: "0" # vllm-lens not yet v2-runner compatible
vllm-lens-0.23.0:
tag: "0.1.0"
needs: pytorch-2.11.0-cu1302:0.1.0
definition: definitions/vllm/vllm.def
args:
VLLM_VERSION: "v0.23.0"
TRANSFORMERS_OVERRIDE: "==5.12.1"
FLASHINFER_VERSION: "0.6.12"
VLLM_LENS_VERSION: "1.1.0"
VLLM_USE_V2_MODEL_RUNNER: "0" # vllm-lens not yet v2-runner compatible

# ============================================================
# TRL - training layer on top of vllm
# ============================================================
trl-1.7.0_0.1.0:
base: vllm-0.23.0_0.1.0
steps:
- path: definitions/trl/trl.def
args:
TRL_VERSION: "1.7.0"
# aisi-inspect-tools is a private AISI repo cloned over SSH.
# External builders without access should set this to "false".
INSTALL_AISI_INSPECT_TOOLS: "true"
trl-1.7.0:
tag: "0.1.0"
needs: vllm-0.23.0:0.1.0
definition: definitions/trl/trl.def
args:
TRL_VERSION: "1.7.0"
# aisi-inspect-tools is a private AISI repo cloned over SSH.
# External builders without access should set this to "false".
INSTALL_AISI_INSPECT_TOOLS: "true"

# ============================================================
# vLLM head
# ============================================================
# vllm-lens-head-7_jun_2026_0.1.0:
# base: pytorch-2.11.0-cu1302_0.1.0
# steps:
# - path: definitions/vllm/vllm.def
# args:
# VLLM_VERSION: "main"
# VLLM_COMMIT: "228bcc436b0f09cf1824f00fd44f8f9c94060940"
# TRANSFORMERS_OVERRIDE: "==5.12.1"
# FLASHINFER_VERSION: "0.6.12"
# VLLM_LENS_VERSION: "1.1.0"
# VLLM_USE_V2_MODEL_RUNNER: "0" # vllm-lens not yet v2-runner compatible
# vllm-lens-head-7-jun-2026:
# tag: "0.1.0"
# needs: pytorch-2.11.0-cu1302:0.1.0
# definition: definitions/vllm/vllm.def
# args:
# VLLM_VERSION: "main"
# VLLM_COMMIT: "228bcc436b0f09cf1824f00fd44f8f9c94060940"
# TRANSFORMERS_OVERRIDE: "==5.12.1"
# FLASHINFER_VERSION: "0.6.12"
# VLLM_LENS_VERSION: "1.1.0"
# VLLM_USE_V2_MODEL_RUNNER: "0"

# ============================================================
# SGLang (not verified as working across all models yet)
# ============================================================
sglang-0.5.6_0.2.0:
base: pytorch-2.9.1-cu126_0.1.0
steps:
- path: definitions/sglang/sglang.def
args:
SGLANG_VERSION: "v0.5.6.post2"
sglang-0.5.6:
tag: "0.2.0"
needs: pytorch-2.9.1-cu126:0.1.0
definition: definitions/sglang/sglang.def
args:
SGLANG_VERSION: "v0.5.6.post2"
7 changes: 1 addition & 6 deletions src/isambard_container_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
from sifter import find_latest_container, resolve_container

__all__ = [
"find_latest_container",
"resolve_container",
]
"""Isambard HPC container management utilities."""
22 changes: 20 additions & 2 deletions src/isambard_container_tools/benchmarks/run_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import typer

from sifter import resolve_container
from sifter import api

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -68,7 +68,25 @@ def main(
partition = "interactive"
reservation = "interactive"

container_path = resolve_container(container)
# Accept a .sif path as-is, or resolve a sifter ref ("<name>" / "<name>:<tag>").
if container.endswith(".sif"):
container_path = container
else:
ref_name, _, ref_tag = container.partition(":")
if ref_tag:
container_path = next(
(
str(img.sif_path)
for img in api.list_local_sifs()
if img.name == ref_name and img.tag == ref_tag
),
container,
)
else:
try:
container_path = str(api.latest(ref_name))
except FileNotFoundError:
container_path = container
if not Path(container_path).exists():
logger.error("Container not found: %s", container_path)
raise typer.Exit(1)
Expand Down
79 changes: 66 additions & 13 deletions src/isambard_container_tools/engines/vllm/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@
from rich.console import Console
from rich.table import Table

from sifter import (
find_latest_container as _sifter_find_latest,
get_jobs,
resolve_container,
)
from sifter import api
from isambard_container_tools.engines.vllm.recipes import (
apply_exclusive_defaults,
dict_to_cli_args,
Expand All @@ -55,15 +51,55 @@
app = typer.Typer(help="Submit a vLLM serving job to SLURM.")


def _version_key(version: str) -> tuple[int, ...]:
"""Sort key for a dotted version (numeric parts; non-numeric sort low)."""
return tuple(int(p) if p.isdigit() else -1 for p in version.split("."))


def _latest_family_build(family: str) -> str:
"""Highest-versioned local build name in a vLLM family (e.g. `vllm-0.23.0`).

Containers are named `<family>-<version>`; pick the highest <version>
present locally, skipping deeper families (so `vllm` ignores `vllm-lens-*`).
"""
prefix = f"{family}-"
builds: dict[str, str] = {} # version -> build name
for img in api.list_local_sifs():
if img.name.startswith(prefix):
version = img.name[len(prefix) :]
# skip deeper families (vllm-lens-*, *-head-*) under a shorter prefix
if "-" not in version:
builds[version] = img.name
if not builds:
raise FileNotFoundError(family)
return builds[max(builds, key=_version_key)]


def find_latest_container(
vllm_version: str | None = None, *, vllm_lens: bool = False
) -> str:
"""Find the latest vLLM container in the sifter registry."""
prefix = "vllm-lens-" if vllm_lens else "vllm-"
"""Find the latest local vLLM container built or pulled via sifter.

Containers are named `<family>-<version>` (e.g. `vllm-0.23.0`) and tagged by
build version. With *vllm_version*, resolve that family member; otherwise
pick the highest-versioned member of the family present locally.
"""
family = "vllm-lens" if vllm_lens else "vllm"
try:
return _sifter_find_latest(prefix=prefix, version=vllm_version)
except RuntimeError as exc:
logger.error("%s", exc)
if vllm_version is not None:
# Recipes may carry a leading "v"; build names don't.
name = f"{family}-{vllm_version.removeprefix('v')}"
else:
name = _latest_family_build(family)
return str(api.latest(name))
except FileNotFoundError as exc:
logger.error(
"No local %s container found%s — build or pull it first "
"(e.g. `sifter pull %s-<version>:<tag>`).",
family,
f" for version {vllm_version}" if vllm_version else "",
family,
)
raise typer.Exit(1) from exc


Expand All @@ -78,8 +114,25 @@ def resolve_vllm_container(
"""
if container is None:
resolved = find_latest_container(get_vllm_version(model), vllm_lens=vllm_lens)
elif container.endswith(".sif"):
resolved = container
else:
resolved = resolve_container(container)
# sifter ref: "<name>" (latest local tag) or "<name>:<tag>".
ref_name, _, ref_tag = container.partition(":")
if ref_tag:
resolved = next(
(
str(img.sif_path)
for img in api.list_local_sifs()
if img.name == ref_name and img.tag == ref_tag
),
container,
)
else:
try:
resolved = str(api.latest(ref_name))
except FileNotFoundError:
resolved = container

if not Path(resolved).exists():
logger.error("Container not found: %s", resolved)
Expand Down Expand Up @@ -419,7 +472,7 @@ def _handle_sigint(_sig: int, _frame: object) -> None:
console.print(f"Check status: scontrol show job {job_id}")
return

jobs = get_jobs(job_id=str(job_id))
jobs = api.status(job_id=str(job_id))
if not jobs:
console.print(f"WARNING: Job {job_id} not found.", style="yellow")
return
Expand All @@ -438,7 +491,7 @@ def _handle_sigint(_sig: int, _frame: object) -> None:
return

if state == "RUNNING":
head_node = job.batch_host or job.node
head_node = job.batch_host or job.nodes
if not head_node:
console.print(
"WARNING: Could not determine head node.", style="yellow"
Expand Down
Loading
Loading