[diffusion] CI: fix nightly CI (#25241)

This commit is contained in:
Mick
2026-05-16 16:55:30 +08:00
committed by GitHub
parent af26b71ae8
commit 7f37ffae9d
4 changed files with 112 additions and 27 deletions
@@ -553,6 +553,8 @@ class LTX2SnapshotResidencyStrategy(LTX2TwoStageResidencyStrategy):
if self._module_is_on_gpu(target_module): if self._module_is_on_gpu(target_module):
self._record_component_ready("transformer") self._record_component_ready("transformer")
elif not self._snapshot_strategy.is_ready("transformer"): elif not self._snapshot_strategy.is_ready("transformer"):
if self._snapshot_low_vram_mode:
self._release_stage2_for_low_vram()
self._snapshot_strategy.prefetch_component("transformer", target_module) self._snapshot_strategy.prefetch_component("transformer", target_module)
else: else:
self._record_component_ready("transformer") self._record_component_ready("transformer")
@@ -623,6 +625,16 @@ class LTX2SnapshotResidencyStrategy(LTX2TwoStageResidencyStrategy):
if stage1_param is not None and stage1_param.device.type == "cuda": if stage1_param is not None and stage1_param.device.type == "cuda":
self._release_module_to_cpu_snapshot("transformer") self._release_module_to_cpu_snapshot("transformer")
def _release_stage2_for_low_vram(self) -> None:
stage2_module = self.pipeline.get_module("transformer_2")
stage2_param = (
next(stage2_module.parameters(), None)
if stage2_module is not None
else None
)
if stage2_param is not None and stage2_param.device.type == "cuda":
self._release_module_to_cpu_snapshot("transformer_2")
def _record_component_ready(self, module_name: str) -> None: def _record_component_ready(self, module_name: str) -> None:
self._snapshot_strategy.record_ready( self._snapshot_strategy.record_ready(
module_name, self.pipeline.get_module(module_name) module_name, self.pipeline.get_module(module_name)
@@ -677,19 +677,37 @@ class ServerArgs(DisaggArgsMixin):
) )
if self.strict_ports: if self.strict_ports:
requested_ports = []
if needs_http: if needs_http:
self._require_port(self.port, "HTTP") requested_ports.append((self.port, "HTTP"))
self._require_port(self.scheduler_port, "Scheduler") requested_ports.append((self.scheduler_port, "Scheduler"))
if self.master_port is not None: if self.master_port is not None:
self._require_port(self.master_port, "Master") requested_ports.append((self.master_port, "Master"))
seen_ports: dict[int, str] = {}
for port, name in requested_ports:
if port in seen_ports:
raise RuntimeError(
f"{name} port {port} duplicates {seen_ports[port]} port and "
"--strict-ports is enabled."
)
seen_ports[port] = name
self._require_port(port, name)
else: else:
settled_ports: set[int] = set()
if needs_http: if needs_http:
self.port = self.settle_port(self.port) self.port = self.settle_port(self.port)
settled_ports.add(self.port)
initial_scheduler_port = self.scheduler_port + ( initial_scheduler_port = self.scheduler_port + (
random.randint(0, 100) if self.scheduler_port == 5555 else 0 random.randint(0, 100) if self.scheduler_port == 5555 else 0
) )
self.scheduler_port = self.settle_port(initial_scheduler_port) self.scheduler_port = self.settle_port(
self.master_port = self.settle_port(self.master_port, 37) initial_scheduler_port, avoid=settled_ports
)
settled_ports.add(self.scheduler_port)
if self.master_port is not None:
self.master_port = self.settle_port(
self.master_port, 37, avoid=settled_ports
)
def _adjust_parallelism(self): def _adjust_parallelism(self):
sp_unspecified = self.sp_degree is None sp_unspecified = self.sp_degree is None
@@ -1467,16 +1485,21 @@ class ServerArgs(DisaggArgsMixin):
return f"tcp://{scheduler_host}:{self.scheduler_port}" return f"tcp://{scheduler_host}:{self.scheduler_port}"
def settle_port( def settle_port(
self, port: int, port_inc: int = 42, max_attempts: int = 100 self,
port: int,
port_inc: int = 42,
max_attempts: int = 100,
avoid: set[int] | None = None,
) -> int: ) -> int:
""" """
Find an available port with retry logic. Find an available port with retry logic.
""" """
attempts = 0 attempts = 0
original_port = port original_port = port
avoid = avoid or set()
while attempts < max_attempts: while attempts < max_attempts:
if is_port_available(port): if port not in avoid and is_port_available(port):
if attempts > 0: if attempts > 0:
logger.info( logger.info(
f"Port {original_port} was unavailable, using port {port} instead" f"Port {original_port} was unavailable, using port {port} instead"
@@ -148,7 +148,7 @@
"num_gpus": 2, "num_gpus": 2,
"frameworks": { "frameworks": {
"sglang": { "sglang": {
"serve_args": "--enable-torch-compile --warmup --pipeline-class-name LTX2TwoStagePipeline", "serve_args": "--enable-torch-compile --warmup --pipeline-class-name LTX2TwoStagePipeline --cfg-parallel-size 2",
"extra_env": {} "extra_env": {}
} }
} }
+61 -11
View File
@@ -23,6 +23,7 @@ import io
import json import json
import os import os
import signal import signal
import socket
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
@@ -40,11 +41,17 @@ CONFIGS_PATH = Path(__file__).parent / "comparison_configs.json"
INSTALL_SCRIPT = Path(__file__).parents[1] / "install_comparison_frameworks.sh" INSTALL_SCRIPT = Path(__file__).parents[1] / "install_comparison_frameworks.sh"
DEFAULT_HOST = "127.0.0.1" DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 30000 DEFAULT_PORT = 30000
SGLANG_MASTER_PORT_OFFSET = 5
SGLANG_SCHEDULER_PORT_OFFSET = 55
HEALTH_TIMEOUT = ( HEALTH_TIMEOUT = (
2400 # seconds (40 min — FLUX.2-dev needs ~10 min download + torch.compile) 2400 # seconds (40 min — FLUX.2-dev needs ~10 min download + torch.compile)
) )
REQUEST_TIMEOUT = 1200 # seconds REQUEST_TIMEOUT = 1200 # seconds
GPU_CLEAR_WAIT = 15 # seconds between framework runs GPU_CLEAR_WAIT = 15 # seconds between framework runs
SERVER_FATAL_ERROR_PATTERNS = (
"CUDA out of memory",
"torch.OutOfMemoryError",
)
# Frameworks that need separate installation (conflict with sglang's deps) # Frameworks that need separate installation (conflict with sglang's deps)
INSTALLABLE_FRAMEWORKS = {"vllm-omni", "lightx2v"} INSTALLABLE_FRAMEWORKS = {"vllm-omni", "lightx2v"}
@@ -69,6 +76,11 @@ def _build_sglang_cmd(case: dict, fw_cfg: dict, port: int) -> list[str]:
str(port), str(port),
"--host", "--host",
DEFAULT_HOST, DEFAULT_HOST,
"--strict-ports",
"--master-port",
str(port + SGLANG_MASTER_PORT_OFFSET),
"--scheduler-port",
str(port + SGLANG_SCHEDULER_PORT_OFFSET),
] ]
if case["num_gpus"] > 1: if case["num_gpus"] > 1:
cmd += ["--num-gpus", str(case["num_gpus"])] cmd += ["--num-gpus", str(case["num_gpus"])]
@@ -240,10 +252,34 @@ def wait_for_health(
KILLALL_SCRIPT = Path(__file__).parents[3] / "killall_sglang.sh" KILLALL_SCRIPT = Path(__file__).parents[3] / "killall_sglang.sh"
def _is_port_available(port: int) -> bool:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((DEFAULT_HOST, port))
except OSError:
return False
return True
def _require_ports_available(ports: list[int]) -> None:
unavailable = [port for port in ports if not _is_port_available(port)]
if unavailable:
raise RuntimeError(f"Required port(s) unavailable before launch: {unavailable}")
def _cleanup_sglang_processes() -> None:
if KILLALL_SCRIPT.exists():
subprocess.run(
["bash", str(KILLALL_SCRIPT)],
timeout=30,
capture_output=True,
)
def kill_server(proc: subprocess.Popen) -> None: def kill_server(proc: subprocess.Popen) -> None:
"""Kill server process tree and clean up GPU processes.""" """Kill server process tree and clean up GPU processes."""
if proc.poll() is not None: if proc.poll() is None:
return
try: try:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM) os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
except (ProcessLookupError, PermissionError): except (ProcessLookupError, PermissionError):
@@ -257,12 +293,7 @@ def kill_server(proc: subprocess.Popen) -> None:
pass pass
proc.wait(timeout=10) proc.wait(timeout=10)
# Use killall_sglang.sh for thorough cleanup (esp. multi-GPU workers) # Use killall_sglang.sh for thorough cleanup (esp. multi-GPU workers)
if KILLALL_SCRIPT.exists(): _cleanup_sglang_processes()
subprocess.run(
["bash", str(KILLALL_SCRIPT)],
timeout=30,
capture_output=True,
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -711,9 +742,20 @@ def run_single(
log_file = log_dir / f"{case['id']}_{framework}.log" log_file = log_dir / f"{case['id']}_{framework}.log"
log_fh = open(log_file, "w", encoding="utf-8", buffering=1) log_fh = open(log_file, "w", encoding="utf-8", buffering=1)
log_thread = None log_thread = None
server_error = {}
proc = None proc = None
try: try:
if framework == "sglang":
_cleanup_sglang_processes()
_require_ports_available(
[
port,
port + SGLANG_MASTER_PORT_OFFSET,
port + SGLANG_SCHEDULER_PORT_OFFSET,
]
)
proc = subprocess.Popen( proc = subprocess.Popen(
cmd, cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@@ -731,6 +773,14 @@ def run_single(
sys.stdout.write(f" [server] {line}") sys.stdout.write(f" [server] {line}")
sys.stdout.flush() sys.stdout.flush()
fh.write(line) fh.write(line)
if not server_error and any(
pattern in line for pattern in SERVER_FATAL_ERROR_PATTERNS
):
server_error["message"] = line.strip()
try:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
except (ProcessLookupError, PermissionError):
pass
except ValueError: except ValueError:
pass # pipe closed pass # pipe closed
@@ -752,7 +802,7 @@ def run_single(
try: try:
send_request(base_url, warmup_case, framework, config) send_request(base_url, warmup_case, framework, config)
except Exception as e: except Exception as e:
print(f" Warmup request {wi} failed (non-fatal): {e}") raise RuntimeError(f"Warmup request {wi} failed: {e}") from e
# Measured request — pass perf_dump_path for SGLang server-side timing # Measured request — pass perf_dump_path for SGLang server-side timing
if perf_dump_path and os.path.exists(perf_dump_path): if perf_dump_path and os.path.exists(perf_dump_path):
@@ -764,8 +814,8 @@ def run_single(
result["latency_s"] = round(latency, 3) result["latency_s"] = round(latency, 3)
except Exception as e: except Exception as e:
result["error"] = str(e) result["error"] = server_error.get("message", str(e))
print(f" ERROR: {e}") print(f" ERROR: {result['error']}")
finally: finally:
if proc: if proc:
kill_server(proc) kill_server(proc)