Files
2026-07-13 13:18:33 +08:00

221 lines
7.7 KiB
Python

# Copyright (c) Snowflake.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import os
import shlex
from pathlib import Path
import modal
ROOT_PATH = Path(__file__).parents[1]
DEFAULT_MODAL_TORCH_PRESET = "2.10.0-cuda12.8"
DEFAULT_MODAL_TRANSFORMERS_SOURCE = "git"
MODAL_TORCH_PRESETS = {
"2.7.1-cuda12.8": {
"image": "pytorch/pytorch:2.7.1-cuda12.8-cudnn9-devel",
"torch_package": "torch==2.7.1",
"torchvision_package": "torchvision==0.22.1",
"torch_test_version": "2.7",
"cuda_test_version": "12.8",
},
"2.8.0-cuda12.8": {
"image": "pytorch/pytorch:2.8.0-cuda12.8-cudnn9-devel",
"torch_package": "torch==2.8.0",
"torchvision_package": "torchvision==0.23.0",
"torch_test_version": "2.8",
"cuda_test_version": "12.8",
},
"2.9.1-cuda12.8": {
"image": "pytorch/pytorch:2.9.1-cuda12.8-cudnn9-devel",
"torch_package": "torch==2.9.1",
"torchvision_package": "torchvision==0.24.1",
"torch_test_version": "2.9",
"cuda_test_version": "12.8",
},
"2.10.0-cuda12.8": {
"image": "pytorch/pytorch:2.10.0-cuda12.8-cudnn9-devel",
"torch_package": "torch==2.10.0",
"torchvision_package": "torchvision==0.25.0",
"torch_test_version": "2.10",
"cuda_test_version": "12.8",
},
"2.11.0-cuda12.8": {
"image": "pytorch/pytorch:2.11.0-cuda12.8-cudnn9-devel",
"torch_package": "torch==2.11.0",
"torchvision_package": "torchvision==0.26.0",
"torch_test_version": "2.11",
"cuda_test_version": "12.8",
},
}
PYTORCH_CUDA_128_INDEX_URL = "https://download.pytorch.org/whl/cu128"
def resolve_modal_torch_config():
selected_preset = os.environ.get("MODAL_TORCH_PRESET") or DEFAULT_MODAL_TORCH_PRESET
try:
preset_config = MODAL_TORCH_PRESETS[selected_preset]
except KeyError as exc:
supported = ", ".join(sorted(MODAL_TORCH_PRESETS))
raise ValueError(f"Unsupported MODAL_TORCH_PRESET={selected_preset!r}; supported values: {supported}") from exc
return {
"preset": selected_preset,
**preset_config,
}
def resolve_modal_transformers_config():
transformers_source = os.environ.get("MODAL_TRANSFORMERS_SOURCE") or DEFAULT_MODAL_TRANSFORMERS_SOURCE
supported_sources = {"requirements", "git"}
if transformers_source not in supported_sources:
supported = ", ".join(sorted(supported_sources))
raise ValueError(
f"Unsupported MODAL_TRANSFORMERS_SOURCE={transformers_source!r}; supported values: {supported}")
transformers_ref = os.environ.get("MODAL_TRANSFORMERS_REF") or ""
if transformers_source == "git" and not transformers_ref:
transformers_ref = "main"
return {
"source": transformers_source,
"ref": transformers_ref,
}
def transformers_override_commands():
if MODAL_TRANSFORMERS_CONFIG["source"] == "requirements":
return ()
transformers_ref = shlex.quote(MODAL_TRANSFORMERS_CONFIG["ref"])
return (
"rm -rf /tmp/transformers",
"git clone --filter=blob:none https://github.com/huggingface/transformers /tmp/transformers",
"cd /tmp/transformers && "
f"git checkout {transformers_ref} && "
"resolved_ref=$(git rev-parse HEAD) && "
"echo \"Resolved Transformers git ref: ${resolved_ref}\" && "
"pip install .",
)
def torch_package_reinstall_command():
command = [
"pip",
"install",
"--force-reinstall",
"--no-cache-dir",
"--index-url",
PYTORCH_CUDA_128_INDEX_URL,
MODAL_TORCH_CONFIG["torch_package"],
MODAL_TORCH_CONFIG["torchvision_package"],
]
return " ".join(shlex.quote(part) for part in command)
MODAL_TORCH_CONFIG = resolve_modal_torch_config()
MODAL_TRANSFORMERS_CONFIG = resolve_modal_transformers_config()
MODAL_TORCH_IMAGE = MODAL_TORCH_CONFIG["image"]
MODAL_TORCH_TEST_VERSION = MODAL_TORCH_CONFIG["torch_test_version"]
MODAL_CUDA_TEST_VERSION = MODAL_TORCH_CONFIG["cuda_test_version"]
# Full test sub-tree this workflow runs by default.
DEFAULT_TEST_TARGET = "tests/unit/v1/"
def resolve_selected_tests():
"""pytest targets selected by the diff-driven fetcher (ci/tests_fetcher.py).
The fetcher writes one target per line (individual test files for a narrowed
run, or just ``tests/unit/v1`` for a full run). The ``collect-tests`` CI job
produces this file and the ``deploy`` job downloads it into place before
``modal run``. We read it here on the runner (at image-build time) and bake the
targets into the image env so the remote ``pytest`` picks them up.
Falls back to the whole v1 suite when the file is missing or empty (local runs,
or selection skipped), so behavior is unchanged unless a selection is provided.
"""
env_path = os.environ.get("DS_TEST_LIST_FILE", "")
list_file = Path(env_path) if env_path else ROOT_PATH / "ci" / ".test_selection" / "test_list.txt"
if not list_file.is_absolute():
list_file = ROOT_PATH / list_file
try:
targets = [line.strip() for line in list_file.read_text().splitlines() if line.strip()]
except OSError:
targets = []
return targets or [DEFAULT_TEST_TARGET]
SELECTED_TESTS = resolve_selected_tests()
# yapf: disable
image = (modal.Image
.from_registry(MODAL_TORCH_IMAGE, add_python="3.10")
.env({
"MODAL_TORCH_PRESET": MODAL_TORCH_CONFIG["preset"],
"MODAL_TRANSFORMERS_SOURCE": MODAL_TRANSFORMERS_CONFIG["source"],
"MODAL_TRANSFORMERS_REF": MODAL_TRANSFORMERS_CONFIG["ref"],
"DS_SELECTED_TESTS": " ".join(SELECTED_TESTS),
})
.run_commands("apt update && apt install -y git libaio-dev")
.pip_install_from_requirements(ROOT_PATH / "requirements/requirements.txt", gpu="any")
.pip_install_from_requirements(ROOT_PATH / "requirements/requirements-dev.txt", gpu="any")
.pip_install_from_requirements(ROOT_PATH / "requirements/requirements-deepcompile.txt", gpu="any")
.run_commands(torch_package_reinstall_command())
)
transformers_commands = transformers_override_commands()
if transformers_commands:
image = image.run_commands(*transformers_commands)
image = (image
.add_local_dir(ROOT_PATH , remote_path="/root/", copy=True)
.run_commands("pip install /root")
.add_local_dir(ROOT_PATH / "accelerator", remote_path="/root/deepspeed/accelerator")
.add_local_dir(ROOT_PATH / "csrc", remote_path="/root/deepspeed/ops/csrc")
.add_local_dir(ROOT_PATH / "op_builder", remote_path="/root/deepspeed/ops/op_builder")
)
app = modal.App("deepspeedai-torch-latest-ci", image=image)
@app.function(
gpu="l40s:2",
timeout=3600,
)
def pytest():
import subprocess
subprocess.run(
[
"python",
"-c",
"import json, torch, torchvision, transformers; "
"print('Modal Python package versions: ' + json.dumps({"
"'torch': torch.__version__, "
"'torch_cuda': torch.version.cuda, "
"'torchvision': torchvision.__version__, "
"'transformers': transformers.__version__"
"}, sort_keys=True))",
],
check=True,
cwd=ROOT_PATH / ".",
)
selected_tests = os.environ.get("DS_SELECTED_TESTS", DEFAULT_TEST_TARGET).split()
print(f"Running pytest on diff-selected targets: {selected_tests}")
subprocess.run(
[
"pytest",
"-n",
"4",
"--verbose",
*selected_tests,
f"--torch_ver={MODAL_TORCH_TEST_VERSION}",
f"--cuda_ver={MODAL_CUDA_TEST_VERSION}",
],
check=True,
cwd=ROOT_PATH / ".",
)