Files
nvlabs--longlive/fouroversix/scripts/resources.py
T
2026-07-13 12:31:40 +08:00

389 lines
12 KiB
Python

from __future__ import annotations
import configparser
import os
import shutil
import subprocess
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING
import modal
import tomllib
if TYPE_CHECKING:
from collections.abc import Callable
FOUROVERSIX_CACHE_PATH = Path("/fouroversix")
FOUROVERSIX_INSTALL_PATH = Path("/root/fouroversix")
KERNEL_DEV_MODE = os.getenv("KERNEL_DEV_MODE", "0") == "1"
app = modal.App("fouroversix")
cache_volume = modal.Volume.from_name("fouroversix", create_if_missing=True)
hf_secret = modal.Secret.from_name("huggingface-secret")
wandb_secret = modal.Secret.from_name("wandb-secret")
class Dependency(str, Enum):
"""Dependencies to add to the base image."""
awq = "awq"
fast_hadamard_transform = "fast_hadamard_transform"
flame = "flame"
flash_attention = "flash_attention"
fouroversix = "fouroversix"
fp_quant = "fp_quant"
qutlass = "qutlass"
spinquant = "spinquant"
transformer_engine = "transformer_engine"
class Submodule(str, Enum):
"""Submodules of Four Over Six to add to the base image."""
cutlass = "cutlass"
flame = "flame"
fast_hadamard_transform = "fast_hadamard_transform"
fp_quant = "fp_quant"
llm_awq = "llm_awq"
qutlass = "qutlass"
spinquant = "spinquant"
def has_untracked_or_unstaged_changes(self) -> bool:
"""Check if the submodule has untracked or unstaged changes."""
git_status = subprocess.run(
[
"git",
"-C",
self.get_local_path(),
"status",
"--porcelain",
],
check=False,
stderr=subprocess.DEVNULL,
stdout=subprocess.PIPE,
text=True,
)
return bool(git_status.stdout.strip())
def get_install_path(self) -> str:
"""Get the path where this submodule will be installed in the Modal image."""
return f"{FOUROVERSIX_INSTALL_PATH}/{self.get_local_path()}"
def get_local_path(self) -> str:
"""Get the path of the submodule relative to the root directory."""
return f"third_party/{self.value.replace('_', '-')}"
def get_remote_url(self) -> str:
"""Get the remote URL of the submodule."""
gitmodules_path = Path(__file__).parent.parent / ".gitmodules"
if not gitmodules_path.exists():
gitmodules_path = FOUROVERSIX_INSTALL_PATH / ".gitmodules"
with gitmodules_path.open() as f:
# Remove leading whitespace to make it a valid INI file
gitmodules_contents = "\n".join(line.lstrip() for line in f.readlines())
config = configparser.ConfigParser()
config.read_string(gitmodules_contents)
for section in config.sections():
if config[section]["path"] == self.get_local_path():
url = config[section]["url"]
break
if url.startswith("https://"):
return url
msg = f"Unsupported remote URL format: {url}"
raise ValueError(msg)
cuda_version_to_image_tag = {
"12.8": "nvcr.io/nvidia/cuda-dl-base:25.03-cuda12.8-devel-ubuntu24.04",
"12.9": "nvcr.io/nvidia/cuda-dl-base:25.06-cuda12.9-devel-ubuntu24.04",
"13.0": "nvcr.io/nvidia/cuda-dl-base:25.09-cuda13.0-devel-ubuntu24.04",
"13.1": "nvcr.io/nvidia/cuda-dl-base:25.12-cuda13.1-devel-ubuntu24.04",
}
def add_submodule(img: modal.Image, submodule: Submodule) -> modal.Image:
if submodule.has_untracked_or_unstaged_changes():
# Submodule has uncommitted changes, build image with local copy
return img.add_local_dir(
submodule.get_local_path(),
submodule.get_install_path(),
copy=True,
)
# Submodule has no uncommitted changes, download from remote to save time
return img.run_commands(
f"git clone {submodule.get_remote_url()} {submodule.get_install_path()}",
)
def install_flash_attn() -> None:
subprocess.run(
["pip", "install", "flash-attn", "--no-build-isolation"],
check=False,
)
def install_fouroversix() -> None:
subprocess.run(
[
"pip",
"install",
"--no-deps",
"--no-build-isolation",
"-e",
FOUROVERSIX_INSTALL_PATH.as_posix(),
],
check=False,
)
def install_fouroversix_non_editable() -> None:
shutil.copytree(
FOUROVERSIX_CACHE_PATH / "build",
FOUROVERSIX_INSTALL_PATH / "build",
)
subprocess.run(
["python", "setup.py", "build_ext", "--inplace"],
check=False,
)
shutil.copytree(
FOUROVERSIX_INSTALL_PATH / "build",
FOUROVERSIX_CACHE_PATH / "build",
dirs_exist_ok=True,
)
subprocess.run(
[
"pip",
"install",
"--no-deps",
"--no-build-isolation",
FOUROVERSIX_INSTALL_PATH.as_posix(),
],
check=False,
)
def install_qutlass() -> None:
subprocess.run(
[
"pip",
"install",
"--no-build-isolation",
Submodule.qutlass.get_install_path(),
],
check=False,
)
def get_image( # noqa: C901, PLR0912
dependencies: list[Dependency] | None = None,
*,
cuda_version: str = "12.9",
deploy: bool = False,
extra_env: dict[str, str] | None = None,
extra_pip_dependencies: list[str] | None = None,
include_tests: bool = False,
python_version: str = "3.13",
pytorch_version: str = "2.10.0",
run_before_copy: Callable[[modal.Image], modal.Image] | None = None,
) -> modal.Image:
if dependencies is None:
dependencies = [Dependency.fouroversix]
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
if not pyproject_path.exists():
pyproject_path = Path(__file__).parent.parent / "fouroversix" / "pyproject.toml"
with pyproject_path.open("rb") as f:
pyproject_data = tomllib.load(f)
img = (
modal.Image.from_registry(
cuda_version_to_image_tag[cuda_version],
add_python=python_version,
)
.entrypoint([])
.apt_install("clang", "git")
.uv_pip_install(*pyproject_data["build-system"]["requires"], "numpy")
.uv_pip_install(
f"torch=={pytorch_version}",
extra_index_url=(
f"https://download.pytorch.org/whl/cu{cuda_version.replace('.', '')}"
),
)
)
for dependency in dependencies:
if dependency == Dependency.awq:
img = add_submodule(img, Submodule.llm_awq).run_commands(
f"pip install --no-deps -e {Submodule.llm_awq.get_install_path()}",
)
if dependency == Dependency.fast_hadamard_transform:
img = add_submodule(img, Submodule.fast_hadamard_transform).run_commands(
f"pip install {Submodule.fast_hadamard_transform.get_install_path()} "
"--no-build-isolation",
)
if dependency == Dependency.flame:
img = (
img.apt_install("pciutils")
.uv_pip_install(
"flash-linear-attention",
"ninja",
"psutil",
"git+https://github.com/pytorch/torchtitan.git@0b44d4c",
"tyro",
"wheel",
)
.run_commands(
"git clone https://github.com/fla-org/flame.git "
f"{FOUROVERSIX_INSTALL_PATH}/third_party/flame",
f"pip install -e {FOUROVERSIX_INSTALL_PATH}/third_party/flame",
)
)
if dependency == Dependency.flash_attention:
img = img.run_function(
install_flash_attn,
cpu=64,
memory=128 * 1024,
gpu="B200",
)
if dependency == Dependency.fouroversix:
img = (
add_submodule(
img.env(
{"CUDA_ARCHS": "100", "FORCE_BUILD": "1", "MAX_JOBS": "32"},
),
Submodule.cutlass,
)
.add_local_file(
"pyproject.toml",
f"{FOUROVERSIX_INSTALL_PATH}/pyproject.toml",
copy=True,
)
.uv_pip_install(
*pyproject_data["project"]["optional-dependencies"]["evals"],
)
.add_local_file(
"setup.py",
f"{FOUROVERSIX_INSTALL_PATH}/setup.py",
copy=True,
)
.add_local_file(
"src/fouroversix/__init__.py",
f"{FOUROVERSIX_INSTALL_PATH}/src/fouroversix/__init__.py",
copy=True,
)
)
if KERNEL_DEV_MODE:
img = (
img.add_local_file(
"README.md",
f"{FOUROVERSIX_INSTALL_PATH}/README.md",
copy=True,
)
.add_local_file(
"LICENSE.md",
f"{FOUROVERSIX_INSTALL_PATH}/LICENSE.md",
copy=True,
)
.workdir(FOUROVERSIX_INSTALL_PATH)
)
img = img.add_local_dir(
"src/fouroversix/csrc",
f"{FOUROVERSIX_INSTALL_PATH}/src/fouroversix/csrc",
copy=True,
)
if not KERNEL_DEV_MODE:
img = img.run_function(install_fouroversix, cpu=32, memory=64 * 1024)
if dependency == Dependency.fp_quant:
img = add_submodule(img, Submodule.fp_quant).run_commands(
f"pip install {Submodule.fp_quant.get_install_path()}/inference_lib",
)
if dependency == Dependency.qutlass:
img = (
add_submodule(img.apt_install("cmake"), Submodule.qutlass)
.env({"MAX_JOBS": "32"})
.run_function(install_qutlass, gpu="B200", cpu=32, memory=64 * 1024)
)
if dependency == Dependency.spinquant:
img = add_submodule(img, Submodule.spinquant)
if dependency == Dependency.transformer_engine:
img = img.uv_pip_install(
"transformer_engine[pytorch]",
extra_options="--no-build-isolation",
)
if extra_pip_dependencies is not None:
img = img.uv_pip_install(*extra_pip_dependencies)
img = img.env({"HF_HOME": FOUROVERSIX_CACHE_PATH.as_posix(), **(extra_env or {})})
if run_before_copy is not None:
img = run_before_copy(img)
# Add source files after all dependencies are added so we can avoid rebuilding when
# they change
for dependency in dependencies:
if dependency == Dependency.flame:
img = (
img.add_local_dir(
"third_party/flame/custom_models",
f"{FOUROVERSIX_INSTALL_PATH}/third_party/flame/custom_models",
)
.add_local_dir(
"scripts/train/configs",
f"{FOUROVERSIX_INSTALL_PATH}/scripts/train/configs",
)
.add_local_file(
"third_party/flame/train.sh",
f"{FOUROVERSIX_INSTALL_PATH}/third_party/flame/train.sh",
)
)
if dependency == Dependency.fouroversix:
img = img.add_local_dir(
"src",
f"{FOUROVERSIX_INSTALL_PATH}/src",
copy=deploy or KERNEL_DEV_MODE,
ignore=lambda p: p.suffix == ".so",
).add_local_file(
".gitmodules",
f"{FOUROVERSIX_INSTALL_PATH}/.gitmodules",
copy=deploy or KERNEL_DEV_MODE,
)
if KERNEL_DEV_MODE:
img = img.run_function(
install_fouroversix_non_editable,
cpu=32,
memory=64 * 1024,
volumes={FOUROVERSIX_CACHE_PATH.as_posix(): cache_volume},
)
if include_tests:
img = img.add_local_dir("tests", f"{FOUROVERSIX_INSTALL_PATH}/tests")
return img