[diffusion] fix: make warmup image initialization rank-safe (#21817)
This commit is contained in:
@@ -4,12 +4,13 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
|
import tempfile
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
import zmq
|
import zmq
|
||||||
|
|
||||||
from sglang.multimodal_gen.configs.pipeline_configs.base import ModelTaskType
|
from sglang.multimodal_gen.runtime.distributed import get_world_group
|
||||||
from sglang.multimodal_gen.runtime.entrypoints.openai.utils import (
|
from sglang.multimodal_gen.runtime.entrypoints.openai.utils import (
|
||||||
_parse_size,
|
_parse_size,
|
||||||
save_image_to_path,
|
save_image_to_path,
|
||||||
@@ -198,32 +199,24 @@ class Scheduler:
|
|||||||
# insert warmup reqs constructed with each warmup-resolution
|
# insert warmup reqs constructed with each warmup-resolution
|
||||||
self._warmup_total = len(self.server_args.warmup_resolutions)
|
self._warmup_total = len(self.server_args.warmup_resolutions)
|
||||||
self._warmup_processed = 0
|
self._warmup_processed = 0
|
||||||
|
task_type = self.server_args.pipeline_config.task_type
|
||||||
|
|
||||||
|
requires_warmup_image = task_type.accepts_image_input()
|
||||||
|
warmup_input_path = None
|
||||||
|
if requires_warmup_image:
|
||||||
|
warmup_input_path = self._prepare_shared_warmup_image_path()
|
||||||
|
|
||||||
for resolution in self.server_args.warmup_resolutions:
|
for resolution in self.server_args.warmup_resolutions:
|
||||||
width, height = _parse_size(resolution)
|
width, height = _parse_size(resolution)
|
||||||
task_type = self.server_args.pipeline_config.task_type
|
|
||||||
|
|
||||||
if task_type in (
|
if requires_warmup_image:
|
||||||
ModelTaskType.I2I,
|
|
||||||
ModelTaskType.TI2I,
|
|
||||||
ModelTaskType.I2V,
|
|
||||||
ModelTaskType.TI2V,
|
|
||||||
):
|
|
||||||
uploads_dir = os.path.join("outputs", "uploads")
|
|
||||||
os.makedirs(uploads_dir, exist_ok=True)
|
|
||||||
input_path = asyncio.run(
|
|
||||||
save_image_to_path(
|
|
||||||
MINIMUM_PICTURE_BASE64_FOR_WARMUP,
|
|
||||||
os.path.join(uploads_dir, "warmup_image.jpg"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
req = Req(
|
req = Req(
|
||||||
data_type=task_type.data_type(),
|
data_type=task_type.data_type(),
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
prompt="",
|
prompt="",
|
||||||
negative_prompt="",
|
negative_prompt="",
|
||||||
image_path=[input_path],
|
image_path=[warmup_input_path],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
req = Req(
|
req = Req(
|
||||||
@@ -237,6 +230,53 @@ class Scheduler:
|
|||||||
# if server is warmed-up, set this flag to avoid req-based warmup
|
# if server is warmed-up, set this flag to avoid req-based warmup
|
||||||
self.warmed_up = True
|
self.warmed_up = True
|
||||||
|
|
||||||
|
def _prepare_shared_warmup_image_path(self) -> str:
|
||||||
|
world_group = get_world_group()
|
||||||
|
src_rank = world_group.ranks[0]
|
||||||
|
|
||||||
|
warmup_sync: dict[str, str | None]
|
||||||
|
if world_group.rank == src_rank:
|
||||||
|
try:
|
||||||
|
if self.server_args.input_save_path is not None:
|
||||||
|
uploads_dir = self.server_args.input_save_path
|
||||||
|
os.makedirs(uploads_dir, exist_ok=True)
|
||||||
|
else:
|
||||||
|
uploads_dir = tempfile.mkdtemp(prefix="sglang_input_")
|
||||||
|
warmup_image_base = os.path.join(uploads_dir, "warmup_image")
|
||||||
|
input_path = asyncio.run(
|
||||||
|
save_image_to_path(
|
||||||
|
MINIMUM_PICTURE_BASE64_FOR_WARMUP,
|
||||||
|
warmup_image_base,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
warmup_sync = {"input_path": input_path, "error": None}
|
||||||
|
except Exception as e:
|
||||||
|
warmup_sync = {"input_path": None, "error": str(e)}
|
||||||
|
else:
|
||||||
|
warmup_sync = {}
|
||||||
|
|
||||||
|
# Sync rank 0's warmup-image write result (path or error) to all ranks.
|
||||||
|
warmup_sync = broadcast_pyobj(
|
||||||
|
warmup_sync,
|
||||||
|
world_group.rank,
|
||||||
|
world_group.cpu_group,
|
||||||
|
src=src_rank,
|
||||||
|
)
|
||||||
|
if not isinstance(warmup_sync, dict):
|
||||||
|
raise RuntimeError("Invalid warmup sync payload received across ranks")
|
||||||
|
|
||||||
|
error = warmup_sync.get("error")
|
||||||
|
if error is not None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Warmup image preparation failed on rank {src_rank}: {error}"
|
||||||
|
)
|
||||||
|
|
||||||
|
input_path = warmup_sync.get("input_path")
|
||||||
|
if not isinstance(input_path, str) or not input_path:
|
||||||
|
raise RuntimeError("Warmup image preparation returned empty input path")
|
||||||
|
|
||||||
|
return input_path
|
||||||
|
|
||||||
def process_received_reqs_with_req_based_warmup(
|
def process_received_reqs_with_req_based_warmup(
|
||||||
self, recv_reqs: List[tuple[bytes, Any]]
|
self, recv_reqs: List[tuple[bytes, Any]]
|
||||||
) -> List[tuple[bytes, Any]]:
|
) -> List[tuple[bytes, Any]]:
|
||||||
@@ -406,7 +446,7 @@ class Scheduler:
|
|||||||
f"Warmup req ({self._warmup_processed}/{self._warmup_total}) processing failed"
|
f"Warmup req ({self._warmup_processed}/{self._warmup_total}) processing failed"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(f"Warmup req processing failed")
|
logger.info("Warmup req processing failed")
|
||||||
|
|
||||||
# TODO: Support sending back to multiple identities if batched
|
# TODO: Support sending back to multiple identities if batched
|
||||||
self.return_result(output_batch, identities[0], is_warmup=is_warmup)
|
self.return_result(output_batch, identities[0], is_warmup=is_warmup)
|
||||||
|
|||||||
Reference in New Issue
Block a user