[diffusion] feat: Add Configurable Generator Device and Seed Support via API (#14366)

Co-authored-by: niehen6174 <niehen.6174@gmail.com>
Co-authored-by: Mick <mickjagger19@icloud.com>
This commit is contained in:
WenhaoZhang
2025-12-05 00:25:09 +08:00
committed by GitHub
co-authored by niehen6174 Mick
parent 11d33c0e8f
commit 788628b56f
8 changed files with 43 additions and 5 deletions
@@ -101,6 +101,7 @@ class SamplingParams:
# Batch info # Batch info
num_outputs_per_prompt: int = 1 num_outputs_per_prompt: int = 1
seed: int = 1024 seed: int = 1024
generator_device: str = "cuda" # Device for random generator: "cuda" or "cpu"
# Original dimensions (before VAE scaling) # Original dimensions (before VAE scaling)
num_frames: int = 125 num_frames: int = 125
@@ -393,6 +394,13 @@ class SamplingParams:
default=SamplingParams.seed, default=SamplingParams.seed,
help="Random seed for generation", help="Random seed for generation",
) )
parser.add_argument(
"--generator-device",
type=str,
default=SamplingParams.generator_device,
choices=["cuda", "cpu"],
help="Device for random generator (cuda or cpu). Default: cuda",
)
parser.add_argument( parser.add_argument(
"--num-frames", "--num-frames",
type=int, type=int,
@@ -53,6 +53,8 @@ def _build_sampling_params_from_request(
output_format: Optional[str], output_format: Optional[str],
background: Optional[str], background: Optional[str],
image_path: Optional[str] = None, image_path: Optional[str] = None,
seed: Optional[int] = None,
generator_device: Optional[str] = None,
) -> SamplingParams: ) -> SamplingParams:
if size is None: if size is None:
width, height = None, None width, height = None, None
@@ -73,6 +75,8 @@ def _build_sampling_params_from_request(
save_output=True, save_output=True,
server_args=server_args, server_args=server_args,
output_file_name=f"{request_id}.{ext}", output_file_name=f"{request_id}.{ext}",
seed=seed,
generator_device=generator_device,
) )
return sampling_params return sampling_params
@@ -88,6 +92,7 @@ def _build_req_from_sampling(s: SamplingParams) -> Req:
fps=1, fps=1,
num_frames=s.num_frames, num_frames=s.num_frames,
seed=s.seed, seed=s.seed,
generator_device=s.generator_device,
output_path=s.output_path, output_path=s.output_path,
output_file_name=s.output_file_name, output_file_name=s.output_file_name,
num_outputs_per_prompt=s.num_outputs_per_prompt, num_outputs_per_prompt=s.num_outputs_per_prompt,
@@ -107,6 +112,8 @@ async def generations(
size=request.size, size=request.size,
output_format=request.output_format, output_format=request.output_format,
background=request.background, background=request.background,
seed=request.seed,
generator_device=request.generator_device,
) )
batch = prepare_request( batch = prepare_request(
server_args=get_global_server_args(), server_args=get_global_server_args(),
@@ -155,6 +162,8 @@ async def edits(
size: Optional[str] = Form(None), size: Optional[str] = Form(None),
output_format: Optional[str] = Form(None), output_format: Optional[str] = Form(None),
background: Optional[str] = Form("auto"), background: Optional[str] = Form("auto"),
seed: Optional[int] = Form(1024),
generator_device: Optional[str] = Form("cuda"),
user: Optional[str] = Form(None), user: Optional[str] = Form(None),
): ):
request_id = generate_request_id() request_id = generate_request_id()
@@ -178,6 +187,8 @@ async def edits(
output_format=output_format, output_format=output_format,
background=background, background=background,
image_path=input_path, image_path=input_path,
seed=seed,
generator_device=generator_device,
) )
batch = _build_req_from_sampling(sampling) batch = _build_req_from_sampling(sampling)
@@ -26,6 +26,8 @@ class ImageGenerationsRequest(BaseModel):
style: Optional[str] = "vivid" style: Optional[str] = "vivid"
background: Optional[str] = "auto" # transparent | opaque | auto background: Optional[str] = "auto" # transparent | opaque | auto
output_format: Optional[str] = None # png | jpeg | webp output_format: Optional[str] = None # png | jpeg | webp
seed: Optional[int] = 1024
generator_device: Optional[str] = "cuda"
user: Optional[str] = None user: Optional[str] = None
@@ -54,6 +56,8 @@ class VideoGenerationsRequest(BaseModel):
size: Optional[str] = "720x1280" size: Optional[str] = "720x1280"
fps: Optional[int] = None fps: Optional[int] = None
num_frames: Optional[int] = None num_frames: Optional[int] = None
seed: Optional[int] = 1024
generator_device: Optional[str] = "cuda"
class VideoListResponse(BaseModel): class VideoListResponse(BaseModel):
@@ -73,6 +73,8 @@ def _build_sampling_params_from_request(
save_output=True, save_output=True,
server_args=server_args, server_args=server_args,
output_file_name=request_id, output_file_name=request_id,
seed=request.seed,
generator_device=request.generator_device,
) )
return sampling_params return sampling_params
@@ -88,6 +88,7 @@ class Req:
num_outputs_per_prompt: int = 1 num_outputs_per_prompt: int = 1
seed: int | None = None seed: int | None = None
seeds: list[int] | None = None seeds: list[int] | None = None
generator_device: str = "cuda" # Device for random generator: "cuda" or "cpu"
# Tracking if embeddings are already processed # Tracking if embeddings are already processed
is_prompt_processed: bool = False is_prompt_processed: bool = False
@@ -344,7 +344,8 @@ class CausalDMDDenoisingStage(DenoisingStage):
if isinstance(batch.generator, list) if isinstance(batch.generator, list)
else batch.generator else batch.generator
), ),
).to(self.device) device=self.device,
)
noise_btchw = noise noise_btchw = noise
noise_latents_btchw = self.scheduler.add_noise( noise_latents_btchw = self.scheduler.add_noise(
pred_video_btchw.flatten(0, 1), pred_video_btchw.flatten(0, 1),
@@ -166,7 +166,8 @@ class DmdDenoisingStage(DenoisingStage):
video_raw_latent_shape, video_raw_latent_shape,
dtype=pred_video.dtype, dtype=pred_video.dtype,
generator=batch.generator[0], generator=batch.generator[0],
).to(self.device) device=self.device,
)
latents = self.scheduler.add_noise( latents = self.scheduler.add_noise(
pred_video.flatten(0, 1), pred_video.flatten(0, 1),
noise.flatten(0, 1), noise.flatten(0, 1),
@@ -53,9 +53,19 @@ class InputValidationStage(PipelineStage):
assert seed is not None assert seed is not None
seeds = [seed + i for i in range(num_videos_per_prompt)] seeds = [seed + i for i in range(num_videos_per_prompt)]
batch.seeds = seeds batch.seeds = seeds
# Peiyuan: using GPU seed will cause A100 and H100 to generate different results...
# FIXME: the generator's in latent preparation stage seems to be different from seeds # Create generators based on generator_device parameter
batch.generator = [torch.Generator("cpu").manual_seed(seed) for seed in seeds] # Note: This will overwrite any existing batch.generator
generator_device = batch.generator_device
if generator_device == "cpu":
device_str = "cpu"
else:
device_str = "cuda" if torch.cuda.is_available() else "cpu"
batch.generator = [
torch.Generator(device_str).manual_seed(seed) for seed in seeds
]
def preprocess_condition_image( def preprocess_condition_image(
self, self,