[diffusion] chore: change default seed to 42 (#23836)
This commit is contained in:
@@ -264,6 +264,11 @@ def extract_transfer_fields(req) -> tuple[dict, dict]:
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
if getattr(req, "generator", None) is not None:
|
||||
seed = getattr(req, "seed", None)
|
||||
if seed is not None:
|
||||
scalar_fields["seed"] = _to_json_serializable(seed)
|
||||
|
||||
if _debug_transfer:
|
||||
import torch as _torch
|
||||
|
||||
@@ -1316,9 +1321,13 @@ class SchedulerDisaggMixin:
|
||||
# Recreate torch.Generator from seed (not serializable over transfer)
|
||||
seed = scalar_fields.get("seed")
|
||||
if seed is not None:
|
||||
gen = torch.Generator(device="cpu")
|
||||
gen.manual_seed(int(seed))
|
||||
req.generator = gen
|
||||
if isinstance(seed, list):
|
||||
req.generator = [
|
||||
torch.Generator(device="cpu").manual_seed(int(item))
|
||||
for item in seed
|
||||
]
|
||||
else:
|
||||
req.generator = torch.Generator(device="cpu").manual_seed(int(seed))
|
||||
# Rebuild trace_ctx from the propagated __getstate__ dict so this role's
|
||||
# spans nest under the sender's trace (same mechanism SRT uses via pickle).
|
||||
if trace_state and trace_state.get("tracing_enable"):
|
||||
|
||||
@@ -35,7 +35,6 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
DEFAULT_SEED = 1024
|
||||
VERTEX_ROUTE = os.environ.get("AIP_PREDICT_ROUTE", "/vertex_generate")
|
||||
|
||||
|
||||
@@ -278,7 +277,6 @@ async def vertex_generate(vertex_req: VertexGenerateReqInput):
|
||||
rid,
|
||||
prompt=inst.get("prompt") or inst.get("text"),
|
||||
image_path=inst.get("image") or inst.get("image_url"),
|
||||
seed=params.get("seed", DEFAULT_SEED),
|
||||
num_frames=params.get("num_frames"),
|
||||
fps=params.get("fps"),
|
||||
width=params.get("width"),
|
||||
|
||||
@@ -225,7 +225,7 @@ async def edits(
|
||||
size: Optional[str] = Form(None),
|
||||
output_format: Optional[str] = Form(None),
|
||||
background: Optional[str] = Form("auto"),
|
||||
seed: Optional[int] = Form(1024),
|
||||
seed: Optional[int] = Form(None),
|
||||
generator_device: Optional[str] = Form("cuda"),
|
||||
user: Optional[str] = Form(None),
|
||||
negative_prompt: Optional[str] = Form(None),
|
||||
|
||||
@@ -44,7 +44,7 @@ class ImageGenerationsRequest(BaseModel):
|
||||
true_cfg_scale: Optional[float] = (
|
||||
None # for CFG vs guidance distillation (e.g., QwenImage)
|
||||
)
|
||||
seed: Optional[Union[int, List[int]]] = 1024
|
||||
seed: Optional[Union[int, List[int]]] = None
|
||||
generator_device: Optional[str] = "cuda"
|
||||
negative_prompt: Optional[str] = None
|
||||
output_quality: Optional[str] = "default"
|
||||
@@ -93,7 +93,7 @@ class VideoGenerationsRequest(BaseModel):
|
||||
size: Optional[str] = ""
|
||||
fps: Optional[int] = None
|
||||
num_frames: Optional[int] = None
|
||||
seed: Optional[Union[int, List[int]]] = 1024
|
||||
seed: Optional[Union[int, List[int]]] = None
|
||||
generator_device: Optional[str] = "cuda"
|
||||
# SGLang extensions
|
||||
width: Optional[int] = None
|
||||
|
||||
@@ -196,7 +196,7 @@ async def create_video(
|
||||
size: Optional[str] = Form(None),
|
||||
fps: Optional[int] = Form(None),
|
||||
num_frames: Optional[int] = Form(None),
|
||||
seed: Optional[int] = Form(1024),
|
||||
seed: Optional[int] = Form(None),
|
||||
generator_device: Optional[str] = Form("cuda"),
|
||||
negative_prompt: Optional[str] = Form(None),
|
||||
guidance_scale: Optional[float] = Form(None),
|
||||
|
||||
@@ -27,7 +27,7 @@ class GetWeightsChecksumReqInput:
|
||||
class RolloutRequest(BaseModel):
|
||||
prompt: str
|
||||
negative_prompt: Optional[str] = None
|
||||
seed: int = 1024
|
||||
seed: Optional[int] = None
|
||||
generator_device: str = "cuda"
|
||||
|
||||
width: Optional[int] = None
|
||||
|
||||
@@ -563,12 +563,9 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
||||
|
||||
# Get latents and embeddings
|
||||
latents = batch.latents
|
||||
prompt_embeds = batch.prompt_embeds
|
||||
# Removed Tensor truthiness assert to avoid GPU sync
|
||||
neg_prompt_embeds = None
|
||||
if batch.do_classifier_free_guidance:
|
||||
neg_prompt_embeds = batch.negative_prompt_embeds
|
||||
assert neg_prompt_embeds is not None
|
||||
assert batch.negative_prompt_embeds is not None
|
||||
# Removed Tensor truthiness assert to avoid GPU sync
|
||||
|
||||
should_preprocess_for_wan_ti2v = should_apply_wan_ti2v(batch, server_args)
|
||||
|
||||
@@ -22,7 +22,10 @@ from sglang.multimodal_gen.runtime.loader.component_loaders.transformer_loader i
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import set_forward_context
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import OutputBatch, Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.denoising import DenoisingStage
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.denoising import (
|
||||
DenoisingContext,
|
||||
DenoisingStage,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.validators import (
|
||||
StageValidators as V,
|
||||
)
|
||||
@@ -302,26 +305,25 @@ class Hunyuan3DShapeDenoisingStage(DenoisingStage):
|
||||
pos_cond_kwargs = {"encoder_hidden_states": cond}
|
||||
neg_cond_kwargs = {}
|
||||
|
||||
return {
|
||||
"extra_step_kwargs": extra_step_kwargs,
|
||||
"scheduler": scheduler,
|
||||
"target_dtype": target_dtype,
|
||||
"autocast_enabled": autocast_enabled,
|
||||
"timesteps": timesteps,
|
||||
"num_inference_steps": num_inference_steps,
|
||||
"num_warmup_steps": num_warmup_steps,
|
||||
"image_kwargs": {},
|
||||
"pos_cond_kwargs": pos_cond_kwargs,
|
||||
"neg_cond_kwargs": neg_cond_kwargs,
|
||||
"latents": latents,
|
||||
"prompt_embeds": batch.prompt_embeds,
|
||||
"neg_prompt_embeds": None,
|
||||
"boundary_timestep": None,
|
||||
"z": None,
|
||||
"reserved_frames_mask": None,
|
||||
"seq_len": None,
|
||||
"guidance": guidance,
|
||||
}
|
||||
return DenoisingContext(
|
||||
scheduler=scheduler,
|
||||
extra_step_kwargs=extra_step_kwargs,
|
||||
target_dtype=target_dtype,
|
||||
autocast_enabled=autocast_enabled,
|
||||
timesteps=timesteps,
|
||||
num_inference_steps=num_inference_steps,
|
||||
num_warmup_steps=num_warmup_steps,
|
||||
image_kwargs={},
|
||||
pos_cond_kwargs=pos_cond_kwargs,
|
||||
neg_cond_kwargs=neg_cond_kwargs,
|
||||
latents=latents,
|
||||
boundary_timestep=None,
|
||||
z=None,
|
||||
reserved_frames_mask=None,
|
||||
seq_len=None,
|
||||
guidance=guidance,
|
||||
is_warmup=batch.is_warmup,
|
||||
)
|
||||
|
||||
def _predict_noise(
|
||||
self,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PartitionItem:
|
||||
kind: str
|
||||
item_id: str
|
||||
est_time: float
|
||||
used_fallback_estimate: bool = False
|
||||
|
||||
|
||||
def partition_items_by_lpt(
|
||||
items: list[PartitionItem], num_partitions: int
|
||||
) -> list[list[PartitionItem]]:
|
||||
if not items or num_partitions <= 0:
|
||||
return []
|
||||
|
||||
sorted_items = sorted(
|
||||
items,
|
||||
key=lambda item: (-item.est_time, item.kind, item.item_id),
|
||||
)
|
||||
partitions: list[list[PartitionItem]] = [[] for _ in range(num_partitions)]
|
||||
partition_sums = [0.0] * num_partitions
|
||||
|
||||
for item in sorted_items:
|
||||
min_idx = partition_sums.index(min(partition_sums))
|
||||
partitions[min_idx].append(item)
|
||||
partition_sums[min_idx] += item.est_time
|
||||
|
||||
return partitions
|
||||
@@ -20,6 +20,10 @@ from pathlib import Path
|
||||
import tabulate
|
||||
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
from sglang.multimodal_gen.test.partitioning import (
|
||||
PartitionItem,
|
||||
partition_items_by_lpt,
|
||||
)
|
||||
from sglang.multimodal_gen.test.server.gpu_cases import (
|
||||
ONE_GPU_CASES,
|
||||
TWO_GPU_CASES,
|
||||
@@ -177,16 +181,21 @@ def auto_partition(
|
||||
if not cases or size <= 0:
|
||||
return []
|
||||
|
||||
sorted_cases = sorted(cases, key=lambda c: get_case_est_time(c.id), reverse=True)
|
||||
partitions: list[list[DiffusionTestCase]] = [[] for _ in range(size)]
|
||||
partition_sums = [0.0] * size
|
||||
case_by_id = {case.id: case for case in cases}
|
||||
items = [
|
||||
PartitionItem(kind="case", item_id=case.id, est_time=get_case_est_time(case.id))
|
||||
for case in cases
|
||||
]
|
||||
partitions = partition_items_by_lpt(items, size)
|
||||
if rank >= len(partitions):
|
||||
return []
|
||||
return [case_by_id[item.item_id] for item in partitions[rank]]
|
||||
|
||||
for case in sorted_cases:
|
||||
min_idx = partition_sums.index(min(partition_sums))
|
||||
partitions[min_idx].append(case)
|
||||
partition_sums[min_idx] += get_case_est_time(case.id)
|
||||
|
||||
return partitions[rank] if rank < size else []
|
||||
def get_suite_files_rel(suite: str, parametrized_only: bool = False) -> list[str]:
|
||||
if parametrized_only and suite in PARAMETRIZED_CASE_GROUPS:
|
||||
return [filename for filename, _ in PARAMETRIZED_CASE_GROUPS[suite]]
|
||||
return SUITES[suite]
|
||||
|
||||
|
||||
def _normalize_standalone_key(standalone_file: str) -> str:
|
||||
@@ -746,14 +755,18 @@ def run_pytest(
|
||||
)
|
||||
|
||||
|
||||
def partition_test_files(files, partition_id, total_partitions):
|
||||
def partition_items_by_index(
|
||||
items: list[str], partition_id: int, total_partitions: int
|
||||
) -> list[str]:
|
||||
return [
|
||||
file_path
|
||||
for i, file_path in enumerate(files)
|
||||
if i % total_partitions == partition_id
|
||||
item for i, item in enumerate(items) if i % total_partitions == partition_id
|
||||
]
|
||||
|
||||
|
||||
def partition_test_files(files, partition_id, total_partitions):
|
||||
return partition_items_by_index(files, partition_id, total_partitions)
|
||||
|
||||
|
||||
def run_component_accuracy_files(files, filter_expr=None, continue_on_error=False):
|
||||
exit_code = 0
|
||||
for file_path in files:
|
||||
|
||||
@@ -19,8 +19,13 @@ from pathlib import Path
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
from sglang.multimodal_gen.test.run_suite import (
|
||||
SUITES,
|
||||
PartitionItem,
|
||||
_maybe_pin_update_weights_model_pair,
|
||||
collect_test_items,
|
||||
get_case_est_time,
|
||||
get_suite_files_rel,
|
||||
parse_partition_plan,
|
||||
partition_items_by_lpt,
|
||||
run_pytest,
|
||||
)
|
||||
|
||||
@@ -67,6 +72,12 @@ def main():
|
||||
required=False,
|
||||
help="Specific case IDs to run (space-separated). If provided, only these cases will be run.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--partition-plan-json",
|
||||
type=str,
|
||||
required=False,
|
||||
help="Full partition plan JSON for the current suite.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -78,6 +89,10 @@ def main():
|
||||
parser.error(
|
||||
"Both --partition-id and --total-partitions must be provided together"
|
||||
)
|
||||
if args.partition_plan_json and (
|
||||
args.partition_id is None or args.total_partitions is None
|
||||
):
|
||||
parser.error("--partition-plan-json requires partition-id and total-partitions")
|
||||
|
||||
# Create output directory
|
||||
out_dir = Path(args.out_dir)
|
||||
@@ -98,8 +113,10 @@ def main():
|
||||
test_root_dir = current_file_path.parent.parent # scripts -> test
|
||||
target_dir = test_root_dir / "server"
|
||||
|
||||
# Get files from suite (same as run_suite.py)
|
||||
suite_files_rel = SUITES[args.suite]
|
||||
# GT generation only runs DiffusionTestCase parametrized cases. Standalone
|
||||
# server tests such as disagg validate behavior but do not produce GT images.
|
||||
suite_files_rel = get_suite_files_rel(args.suite, parametrized_only=True)
|
||||
|
||||
_maybe_pin_update_weights_model_pair(suite_files_rel)
|
||||
suite_files_abs = []
|
||||
for f_rel in suite_files_rel:
|
||||
@@ -113,30 +130,76 @@ def main():
|
||||
logger.error(f"No valid test files found for suite '{args.suite}'.")
|
||||
sys.exit(1)
|
||||
|
||||
# Build pytest filter for case_ids if provided
|
||||
partition_id = args.partition_id if args.partition_id is not None else 0
|
||||
total_partitions = args.total_partitions if args.total_partitions is not None else 1
|
||||
|
||||
selected_plan_case_ids = None
|
||||
if args.partition_plan_json:
|
||||
assignment = parse_partition_plan(
|
||||
suite=args.suite,
|
||||
partition_id=partition_id,
|
||||
total_partitions=total_partitions,
|
||||
plan_json=args.partition_plan_json,
|
||||
)
|
||||
selected_plan_case_ids = assignment.case_ids
|
||||
if args.case_ids:
|
||||
requested_case_ids = set(args.case_ids)
|
||||
selected_plan_case_ids = [
|
||||
case_id
|
||||
for case_id in selected_plan_case_ids
|
||||
if case_id in requested_case_ids
|
||||
]
|
||||
if not selected_plan_case_ids:
|
||||
logger.warning("No testcase cases assigned to this partition.")
|
||||
sys.exit(0)
|
||||
|
||||
# Build pytest filter for case_ids if provided.
|
||||
filter_expr = None
|
||||
if args.case_ids:
|
||||
if selected_plan_case_ids is not None:
|
||||
filters = [
|
||||
f"test_diffusion_generation[{case_id}]"
|
||||
for case_id in selected_plan_case_ids
|
||||
]
|
||||
filter_expr = " or ".join(filters)
|
||||
logger.info(f"Filtering by partition plan case IDs: {selected_plan_case_ids}")
|
||||
elif args.case_ids:
|
||||
# pytest parametrized test format: test_diffusion_generation[case_id]
|
||||
filters = [f"test_diffusion_generation[{case_id}]" for case_id in args.case_ids]
|
||||
filter_expr = " or ".join(filters)
|
||||
logger.info(f"Filtering by case IDs: {args.case_ids}")
|
||||
|
||||
# Collect all test items (same as run_suite.py)
|
||||
# Collect all test items and keep only testcase-based GT generators.
|
||||
all_test_items = collect_test_items(suite_files_abs, filter_expr=filter_expr)
|
||||
all_test_items = [
|
||||
item for item in all_test_items if "test_diffusion_generation[" in item
|
||||
]
|
||||
|
||||
if not all_test_items:
|
||||
logger.warning(f"No test items found for suite '{args.suite}'.")
|
||||
sys.exit(0)
|
||||
|
||||
# Partition by test items (same as run_suite.py)
|
||||
partition_id = args.partition_id if args.partition_id is not None else 0
|
||||
total_partitions = args.total_partitions if args.total_partitions is not None else 1
|
||||
if selected_plan_case_ids is not None:
|
||||
selected_case_id_set = set(selected_plan_case_ids)
|
||||
my_items = [
|
||||
item
|
||||
for item in all_test_items
|
||||
if item[item.index("[") + 1 : item.rindex("]")] in selected_case_id_set
|
||||
]
|
||||
else:
|
||||
# Partition by test items with the same LPT strategy used by CI partitioning.
|
||||
partition_items = []
|
||||
for item in all_test_items:
|
||||
case_id = item[item.index("[") + 1 : item.rindex("]")]
|
||||
partition_items.append(
|
||||
PartitionItem(
|
||||
kind="case",
|
||||
item_id=item,
|
||||
est_time=get_case_est_time(case_id),
|
||||
)
|
||||
)
|
||||
|
||||
my_items = [
|
||||
item
|
||||
for i, item in enumerate(all_test_items)
|
||||
if i % total_partitions == partition_id
|
||||
]
|
||||
partitions = partition_items_by_lpt(partition_items, total_partitions)
|
||||
my_items = [item.item_id for item in partitions[partition_id]]
|
||||
|
||||
logger.info(
|
||||
f"Partition {partition_id}/{total_partitions}: "
|
||||
|
||||
@@ -20,6 +20,8 @@ from __future__ import annotations
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.disaggregation.scheduler_mixin import (
|
||||
SchedulerDisaggMixin,
|
||||
extract_transfer_fields,
|
||||
@@ -74,6 +76,43 @@ def _roundtrip_scalar_fields(scalar_fields: dict) -> dict:
|
||||
|
||||
|
||||
class TestDisaggTracePropagation(unittest.TestCase):
|
||||
def test_transfer_keeps_seed_needed_to_rebuild_generator(self):
|
||||
req = Req(request_id="test-seed", prompt="x")
|
||||
req.generator = torch.Generator(device="cpu").manual_seed(req.seed)
|
||||
|
||||
_, scalar_fields = extract_transfer_fields(req)
|
||||
|
||||
self.assertEqual(scalar_fields["seed"], 42)
|
||||
|
||||
rebuilt = SchedulerDisaggMixin._build_disagg_req(None, dict(scalar_fields), {})
|
||||
self.assertIsInstance(rebuilt.generator, torch.Generator)
|
||||
self.assertEqual(rebuilt.seed, 42)
|
||||
|
||||
expected = torch.rand(
|
||||
(), generator=torch.Generator(device="cpu").manual_seed(42)
|
||||
)
|
||||
actual = torch.rand((), generator=rebuilt.generator)
|
||||
self.assertEqual(actual.item(), expected.item())
|
||||
|
||||
def test_build_disagg_req_rebuilds_generator_list(self):
|
||||
scalar_fields = {
|
||||
"request_id": "test-seed-list",
|
||||
"prompt": "x",
|
||||
"num_outputs_per_prompt": 2,
|
||||
"seed": [11, 12],
|
||||
}
|
||||
|
||||
rebuilt = SchedulerDisaggMixin._build_disagg_req(None, dict(scalar_fields), {})
|
||||
|
||||
self.assertEqual(rebuilt.seed, [11, 12])
|
||||
self.assertEqual(len(rebuilt.generator), 2)
|
||||
for seed, generator in zip(rebuilt.seed, rebuilt.generator):
|
||||
expected = torch.rand(
|
||||
(), generator=torch.Generator(device="cpu").manual_seed(seed)
|
||||
)
|
||||
actual = torch.rand((), generator=generator)
|
||||
self.assertEqual(actual.item(), expected.item())
|
||||
|
||||
def test_tracing_disabled_omits_trace_state(self):
|
||||
"""With a default TraceNullContext Req, no _trace_state is emitted and
|
||||
the JSON codec does not encounter any live OTel objects."""
|
||||
|
||||
Reference in New Issue
Block a user