[diffusion] fix: fix hunyuan3d stale extension lock hangs (#35989)

This commit is contained in:
Mick
2026-08-22 23:08:44 +08:00
committed by GitHub
parent d315eb7250
commit 46cb12ab45
3 changed files with 111 additions and 33 deletions
+1
View File
@@ -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