执行摘要
修复扩散 nightly CI 的端口竞争、OOM 和失败检测问题
夜间 CI 中存在端口竞争、CUDA OOM 导致挂起、LTX-2.3 模型在 CFG parallel 2 下 OOM 等问题,此 PR 旨在通过端口严格分配、失败检测和内存优化来稳定 CI。
值得精读,尤其是端口去重和 OOM 检测模式的设计,可为其他 CI 模块参考。
夜间 CI 中存在端口竞争、CUDA OOM 导致挂起、LTX-2.3 模型在 CFG parallel 2 下 OOM 等问题,此 PR 旨在通过端口严格分配、失败检测和内存优化来稳定 CI。
值得精读,尤其是端口去重和 OOM 检测模式的设计,可为其他 CI 模块参考。
scripts/ci/utils/diffusion/run_comparison.py 中引入 _is_port_available 和 _require_ports_available,启动前预检查端口可用性;_build_sglang_cmd 增加 --strict-ports 和专用的 master/scheduler 端口偏移。 _cleanup_sglang_processes 通过 killall_sglang.sh 彻底清理;wait_for_health 中检测 CUDA OOM 日志(匹配 SERVER_FATAL_ERROR_PATTERNS),将故障立即标记为失败而非挂起;warmup 请求失败改为致命。 python/sglang/multimodal_gen/runtime/pipelines/ltx_2_pipeline.py 中新增 _release_stage2_for_low_vram,在 prepare_after_request 中当低 VRAM 模式 prefetch stage1 前主动释放 stage2(transformer_2)GPU 内存,降低峰值。 python/sglang/multimodal_gen/runtime/server_args.py 中 _adjust_network_ports 在 --strict-ports 下检查端口重复;settle_port 新增 avoid 参数避免多个服务占用同一端口。 comparison_configs.json 对 LTX-2.3 增加 --cfg-parallel-size 2;nightly 工作流跳过手动 dispatch 时的 sglang-ci-data 发布以避免权限错误。| 文件 | 模块 | 状态 | 重要度 |
|---|---|---|---|
scripts/ci/utils/diffusion/run_comparison.py |
CI 脚本 | modified | 6.86 |
python/sglang/multimodal_gen/runtime/server_args.py |
服务配置 | modified | 6.51 |
python/sglang/multimodal_gen/runtime/pipelines/ltx_2_pipeline.py |
扩散管道 | modified | 6.37 |
scripts/ci/utils/diffusion/comparison_configs.json |
CI 配置 | modified | 2.55 |
scripts/ci/utils/diffusion/run_comparison.py
infrastructure
核心 CI 脚本,引入端口预检、OOM 日志检测、进程清理机制,大幅提升 CI 鲁棒性。
# scripts/ci/utils/diffusion/run_comparison.py
import socket
import subprocess
# 定义端口偏移常量
SGLANG_MASTER_PORT_OFFSET = 5
SGLANG_SCHEDULER_PORT_OFFSET = 55
# CUDA OOM 错误模式
SERVER_FATAL_ERROR_PATTERNS = (
"CUDA out of memory",
"torch.OutOfMemoryError",
)
def _is_port_available(port: int) -> bool:
"""检查端口是否可用,使用 SO_REUSEADDR 避免 TIME_WAIT 干扰"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind(("127.0.0.1", 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:
"""调用 killall_sglang.sh 彻底清理残余 SGLang 进程"""
KILLALL_SCRIPT = Path(__file__).parents[3] / "killall_sglang.sh"
if KILLALL_SCRIPT.exists():
subprocess.run(["bash", str(KILLALL_SCRIPT)], capture_output=True, check=False)
def kill_server(proc: subprocess.Popen) -> None:
"""终止服务进程树并清理 GPU 进程"""
if proc.poll() is None:
# 先尝试 SIGTERM
try:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
except (ProcessLookupError, PermissionError):
pass
try:
proc.wait(timeout=30)
except subprocess.TimeoutExpired:
# 超时则强杀
try:
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
except (ProcessLookupError, PermissionError):
pass
proc.wait(timeout=10)
# 确保 GPU 残留进程被清理
_cleanup_sglang_processes()
python/sglang/multimodal_gen/runtime/server_args.py
core-logic
服务参数配置核心,强化端口去重逻辑,新增 avoid 参数避免多端口冲突,提升部署安全性。
# python/sglang/multimodal_gen/runtime/server_args.py ( 部分 )
def _adjust_network_ports(self):
needs_http = self.disagg_role in (RoleType.MONOLITHIC, RoleType.SERVER)
if self.strict_ports:
# 收集所有请求端口,检测重复
requested_ports = []
if needs_http:
requested_ports.append((self.port, "HTTP"))
requested_ports.append((self.scheduler_port, "Scheduler"))
if self.master_port is not None:
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:
# 非 strict 模式:依次分配并记录已占用端口
settled_ports: set[int] = set()
if needs_http:
self.port = self.settle_port(self.port)
settled_ports.add(self.port)
initial_scheduler_port = self.scheduler_port + (
random.randint(0, 100) if self.scheduler_port == 5555 else 0
)
self.scheduler_port = self.settle_port(
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 settle_port(self, port, port_inc=42, max_attempts=100, avoid=None):
"""查找可用端口,avoid 参数避免与已占用端口冲突"""
avoid = avoid or set()
attempts = 0
original_port = port
while attempts < max_attempts:
if port not in avoid and is_port_available(port):
if attempts > 0:
logger.info(f"Port {original_port} was unavailable, using port {port}")
return port
attempts += 1
if port < 60000:
port += port_inc
else:
port = original_port + attempts
raise RuntimeError(f"Could not settle port after {max_attempts} attempts")
python/sglang/multimodal_gen/runtime/pipelines/ltx_2_pipeline.py
core-logic
LTX-2.3 管道新增 stage2 释放逻辑,关键修复低 VRAM 模式下的 OOM。
# python/sglang/multimodal_gen/runtime/pipelines/ltx_2_pipeline.py ( 部分 )
def _release_stage2_for_low_vram(self) -> None:
"""在低 VRAM 模式下,释放 stage2 DiT 模块(transformer_2)的 GPU 内存,
为 stage1 的加载腾出空间,避免峰值 OOM。"""
stage2_module = self.pipeline.get_module("transformer_2")
# 检查 stage2 参数的设备类型
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":
# 将模块从 GPU 释放到 CPU 快照
self._release_module_to_cpu_snapshot("transformer_2")
def prepare_after_request(self, module, use, state):
"""请求结束后准备阶段,低 VRAM 模式下先释放 stage2 再 prefetch stage1"""
phase = self._phase(use)
if phase != "stage1":
return
if self.server_args.dit_cpu_offload:
target_module = self.pipeline.get_module("transformer")
if self._module_is_on_gpu(target_module):
self._record_component_ready("transformer")
elif not self._snapshot_strategy.is_ready("transformer"):
if self._snapshot_low_vram_mode:
# 在 prefetch stage1 前释放 stage2,降低内存峰值
self._release_stage2_for_low_vram()
self._snapshot_strategy.prefetch_component("transformer", target_module)
else:
self._record_component_ready("transformer")
self.manager._sync_refinement_stage_transformer("stage1")
self.manager._active_phase = "stage1"
当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。
_is_port_available 使用 SO_REUSEADDR 可能误报可用,但仅用于预检,后续 listen 仍可能失败。 _cleanup_sglang_processes 调用 killall_sglang.sh 可能误杀其他 SGLang 进程。 范围:扩散 Nightly CI 流程、LTX-2.3 模型部署、服务端口配置。
程度:CI 稳定性显著提升(减少挂机和超时),低 VRAM 用户避免 OOM 崩溃;服务器端口行为更安全(去重)。
当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。
参与讨论