Files
miles/docker/build.py
T
cd464a1c4c Bump sglang to v0.5.18 (#2714)
Co-authored-by: Zhichen Zeng <zhichen.zeng@radixark.ai>
Co-authored-by: Zhichenzzz <zczeng@uw.edu>
2026-08-27 01:08:34 -07:00

212 lines
6.4 KiB
Python

#!/usr/bin/env python3
# doc-dev: docs/ci/02-docker-build.md
"""Build and push Miles Docker images.
Usage:
python docker/build.py --variant cu13 --image-tag dev --push # multi-arch (amd64+arm64)
python docker/build.py --variant cu13-x86 --image-tag dev --push # single arch
python docker/build.py --variant cu12-x86 --image-tag latest
python docker/build.py --variant cu13 --image-tag dev --dry-run
"""
import os
import subprocess
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
import image_inputs
import typer
CACHE_DIR = "/tmp/miles-docker-cache"
REPO_ROOT = Path(__file__).resolve().parent.parent
VARIANTS = {
"cu13": {
"image": "radixark/miles",
"platforms": ["linux/amd64", "linux/arm64"],
"tag_postfix": "",
"build_args": {},
},
"cu13-x86": {
"image": "radixark/miles",
"platforms": ["linux/amd64"],
"tag_postfix": "",
"build_args": {},
},
"cu13-aarch64": {
"image": "radixark/miles",
"platforms": ["linux/arm64"],
"tag_postfix": "",
"build_args": {},
},
"cu12-x86": {
"image": "radixark/miles",
"platforms": ["linux/amd64"],
"tag_postfix": "-cu12",
"build_args": {
"ENABLE_CUDA_13": "0",
"SGLANG_IMAGE_TAG": "v0.5.18-cu129",
"WHEELS_TAG_X86": "cu129-x86_64",
},
},
"rocm700-mi35x": {
"image": "rocm/sgl-dev",
"tag_postfix": "-rocm700-mi35x",
"tag_prefix": "miles",
"dockerfile": "docker/Dockerfile.rocm",
"build_args": {
"GPU_ARCH": "gfx950",
"SGLANG_IMAGE_REPO": "rocm/sgl-dev",
"SGLANG_IMAGE_TAG": "v0.5.14-rocm700-mi35x-20260627",
"SGLANG_USE_ROCM700A": "1",
},
},
"rocm700-mi30x": {
"image": "rocm/sgl-dev",
"tag_postfix": "-rocm700-mi30x",
"tag_prefix": "miles",
"dockerfile": "docker/Dockerfile.rocm",
"build_args": {
"GPU_ARCH": "gfx942",
"SGLANG_IMAGE_TAG": "v0.5.10-rocm700-mi30x",
"SGLANG_USE_ROCM700A": "1",
},
},
"rocm720-mi35x": {
"image": "rocm/sgl-dev",
"tag_postfix": "-rocm720-mi35x",
"tag_prefix": "miles",
"dockerfile": "docker/Dockerfile.rocm",
"build_args": {
"GPU_ARCH": "gfx950",
"SGLANG_IMAGE_REPO": "rocm/sgl-dev",
"SGLANG_IMAGE_TAG": "v0.5.16-rocm720-mi35x-20260730",
"APPLY_ROCR_VMMFIX": "1",
"TE_USE_WHEEL": "1",
},
},
}
def run(cmd: list[str], dry_run: bool) -> None:
print(f"+ {' '.join(cmd)}", flush=True)
if dry_run:
return
subprocess.run(cmd, check=True)
def build_and_push(
variant: str,
image_tag: str,
dry_run: bool,
dockerfile: str,
push: bool = False,
custom_tag: str = "",
extra_build_args: list[str] | None = None,
) -> None:
extra_build_args = extra_build_args or []
config = VARIANTS[variant]
# A variant may pin its own Dockerfile (e.g. ROCm); otherwise use the CLI default.
dockerfile = config.get("dockerfile", dockerfile)
image = config["image"]
postfix = config.get("tag_postfix", "")
platforms = config.get("platforms")
if image_tag == "latest":
tags = [f"{image}:latest{postfix}"]
elif image_tag == "dev":
prefix = config.get("tag_prefix", "dev")
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M")
tags = [f"{image}:{prefix}{postfix}", f"{image}:{prefix}{postfix}-{timestamp}"]
elif image_tag == "custom":
if not custom_tag:
raise typer.BadParameter("--custom-tag is required when --image-tag is custom")
tags = [f"{image}:{custom_tag}{postfix}"]
else:
raise typer.BadParameter(f"Unknown image tag: {image_tag}")
cmd = [
"docker",
"buildx",
"build",
"-f",
dockerfile,
]
if platforms:
cmd += ["--platform", ",".join(platforms)]
if push:
cmd += ["--push"]
# Proxy args (pass through if set in environment, check both cases)
for arg_name in ["HTTP_PROXY", "HTTPS_PROXY"]:
value = os.environ.get(arg_name.lower()) or os.environ.get(arg_name)
if value:
cmd += ["--build-arg", f"{arg_name}={value}"]
cmd += ["--build-arg", "NO_PROXY=localhost,127.0.0.1"]
# Variant-specific build args
for key, value in config.get("build_args", {}).items():
cmd += ["--build-arg", f"{key}={value}"]
# Caller overrides (e.g. release builds pinning SGLANG_COMMIT / MILES_COMMIT
# from release-lock.json) come last so they win over variant defaults.
for spec in extra_build_args:
assert "=" in spec, f"--build-arg expects KEY=VALUE, got {spec!r}"
cmd += ["--build-arg", spec]
# CI reads this back off the published tag to skip rebuilds whose inputs are unchanged.
cmd += ["--label", f"{image_inputs.LABEL_KEY}={image_inputs.compute()}"]
for tag in tags:
cmd += ["-t", tag]
# Context is repo root
cmd += ["."]
print(f"\n=== Building {' '.join(tags)} ===", flush=True)
run(cmd, dry_run)
class Variant(str, Enum):
cu13 = "cu13"
cu13_x86 = "cu13-x86"
cu13_aarch64 = "cu13-aarch64"
cu12_x86 = "cu12-x86"
rocm700_mi35x = "rocm700-mi35x"
rocm700_mi30x = "rocm700-mi30x"
rocm720_mi35x = "rocm720-mi35x"
class ImageTag(str, Enum):
latest = "latest"
dev = "dev"
custom = "custom"
def main(
variant: Variant = typer.Option(..., help="Build variant to use."), # noqa: B008
image_tag: ImageTag = typer.Option(..., help="Tag mode: latest, dev, or custom."), # noqa: B008
dockerfile: str = typer.Option("docker/Dockerfile", help="Path to the Dockerfile."), # noqa: B008
dry_run: bool = typer.Option(False, help="Print commands without executing them."), # noqa: B008
push: bool = typer.Option(False, help="Push images to registry after building."), # noqa: B008
custom_tag: str = typer.Option("", help="Custom tag name (required when --image-tag is custom)."), # noqa: B008
build_arg: list[str] = typer.Option([], help="Extra KEY=VALUE build-arg (repeatable)."), # noqa: B008
) -> None:
build_and_push(
variant.value,
image_tag.value,
dry_run,
dockerfile,
push=push,
custom_tag=custom_tag,
extra_build_args=build_arg,
)
if __name__ == "__main__":
typer.run(main)