[diffusion] fix: fix hunyuan3d stale extension lock hangs (#35989)
This commit is contained in:
@@ -76,6 +76,7 @@ dependencies = [
|
|||||||
"tiktoken",
|
"tiktoken",
|
||||||
"tilelang==0.1.12",
|
"tilelang==0.1.12",
|
||||||
"timm==1.0.16",
|
"timm==1.0.16",
|
||||||
|
"tokenizers==0.22.2", # 0.23.0rc0 is incompatible with transformers' CLIPTokenizer.
|
||||||
"tokenspeed_mla==0.1.8",
|
"tokenspeed_mla==0.1.8",
|
||||||
"tomli ; python_version < '3.11'",
|
"tomli ; python_version < '3.11'",
|
||||||
"torch==2.13.0",
|
"torch==2.13.0",
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import fcntl
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Sequence
|
from typing import Any, Iterator, Sequence
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
@@ -81,6 +83,28 @@ def _is_recoverable_load_error(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _extension_build_lock(build_directory: Path) -> Iterator[None]:
|
||||||
|
"""Serialize builds and discard PyTorch lock files left by dead processes."""
|
||||||
|
build_directory.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
lock_path = build_directory.parent / f".{build_directory.name}.sglang.lock"
|
||||||
|
with lock_path.open("a+") as lock_file:
|
||||||
|
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
||||||
|
try:
|
||||||
|
torch_lock_path = build_directory / "lock"
|
||||||
|
if torch_lock_path.exists():
|
||||||
|
logger.warning(
|
||||||
|
"Removing stale PyTorch extension lock for %s at %s",
|
||||||
|
build_directory.name,
|
||||||
|
torch_lock_path,
|
||||||
|
)
|
||||||
|
torch_lock_path.unlink(missing_ok=True)
|
||||||
|
build_directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
||||||
|
|
||||||
|
|
||||||
def load_extension_with_recovery(
|
def load_extension_with_recovery(
|
||||||
name: str,
|
name: str,
|
||||||
sources: Sequence[str],
|
sources: Sequence[str],
|
||||||
@@ -90,18 +114,22 @@ def load_extension_with_recovery(
|
|||||||
) -> Any:
|
) -> Any:
|
||||||
from torch.utils.cpp_extension import load
|
from torch.utils.cpp_extension import load
|
||||||
|
|
||||||
try:
|
build_directory = _get_build_directory(name)
|
||||||
return load(
|
load_kwargs = {
|
||||||
name=name,
|
"name": name,
|
||||||
sources=list(sources),
|
"sources": list(sources),
|
||||||
extra_cflags=None if extra_cflags is None else list(extra_cflags),
|
"extra_cflags": None if extra_cflags is None else list(extra_cflags),
|
||||||
extra_cuda_cflags=(
|
"extra_cuda_cflags": (
|
||||||
None if extra_cuda_cflags is None else list(extra_cuda_cflags)
|
None if extra_cuda_cflags is None else list(extra_cuda_cflags)
|
||||||
),
|
),
|
||||||
verbose=verbose,
|
"build_directory": str(build_directory),
|
||||||
)
|
"verbose": verbose,
|
||||||
|
}
|
||||||
|
|
||||||
|
with _extension_build_lock(build_directory):
|
||||||
|
try:
|
||||||
|
return load(**load_kwargs)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
build_directory = _get_build_directory(name)
|
|
||||||
if not _is_recoverable_load_error(exc, name, build_directory):
|
if not _is_recoverable_load_error(exc, name, build_directory):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -114,16 +142,8 @@ def load_extension_with_recovery(
|
|||||||
sys.modules.pop(name, None)
|
sys.modules.pop(name, None)
|
||||||
if build_directory.exists():
|
if build_directory.exists():
|
||||||
shutil.rmtree(build_directory)
|
shutil.rmtree(build_directory)
|
||||||
|
build_directory.mkdir(parents=True)
|
||||||
return load(
|
return load(**load_kwargs)
|
||||||
name=name,
|
|
||||||
sources=list(sources),
|
|
||||||
extra_cflags=None if extra_cflags is None else list(extra_cflags),
|
|
||||||
extra_cuda_cflags=(
|
|
||||||
None if extra_cuda_cflags is None else list(extra_cuda_cflags)
|
|
||||||
),
|
|
||||||
verbose=verbose,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["load_extension_with_recovery"]
|
__all__ = ["load_extension_with_recovery"]
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from sglang.kernels.ops.diffusion.ext.loader import load_extension_with_recovery
|
||||||
|
|
||||||
|
|
||||||
|
def test_stale_torch_lock_is_removed_before_loading(tmp_path: Path):
|
||||||
|
build_directory = tmp_path / "test_extension"
|
||||||
|
build_directory.mkdir()
|
||||||
|
torch_lock_path = build_directory / "lock"
|
||||||
|
torch_lock_path.touch()
|
||||||
|
|
||||||
|
expected = object()
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"sglang.kernels.ops.diffusion.ext.loader._get_build_directory",
|
||||||
|
return_value=build_directory,
|
||||||
|
),
|
||||||
|
patch("torch.utils.cpp_extension.load", return_value=expected) as load,
|
||||||
|
):
|
||||||
|
result = load_extension_with_recovery("test_extension", ["source.cpp"])
|
||||||
|
|
||||||
|
assert result is expected
|
||||||
|
assert not torch_lock_path.exists()
|
||||||
|
load.assert_called_once_with(
|
||||||
|
name="test_extension",
|
||||||
|
sources=["source.cpp"],
|
||||||
|
extra_cflags=None,
|
||||||
|
extra_cuda_cflags=None,
|
||||||
|
build_directory=str(build_directory),
|
||||||
|
verbose=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_broken_extension_is_rebuilt_under_the_same_lock(tmp_path: Path):
|
||||||
|
build_directory = tmp_path / "test_extension"
|
||||||
|
build_directory.mkdir()
|
||||||
|
expected = object()
|
||||||
|
load_error = OSError(f"{build_directory}/test_extension.so: file too short")
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"sglang.kernels.ops.diffusion.ext.loader._get_build_directory",
|
||||||
|
return_value=build_directory,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"torch.utils.cpp_extension.load",
|
||||||
|
side_effect=[load_error, expected],
|
||||||
|
) as load,
|
||||||
|
):
|
||||||
|
result = load_extension_with_recovery("test_extension", ["source.cpp"])
|
||||||
|
|
||||||
|
assert result is expected
|
||||||
|
assert build_directory.is_dir()
|
||||||
|
assert load.call_count == 2
|
||||||
Reference in New Issue
Block a user