Skip to content

Commit 9ca8b28

Browse files
committed
Address more PR review comments
1 parent 00f3715 commit 9ca8b28

6 files changed

Lines changed: 38 additions & 80 deletions

File tree

examples/inference/basic/basic_glm_image.py

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,56 @@
33
GLM-Image inference example using FastVideo.
44
55
This example demonstrates how to generate images using the GLM-Image model
6-
through FastVideo's pipeline infrastructure.
6+
through FastVideo's VideoGenerator API.
77
88
Usage:
99
python examples/inference/basic/basic_glm_image.py
1010
"""
1111

1212
import os
1313

14-
# Set environment variables for single-GPU distributed setup
15-
os.environ.setdefault("MASTER_ADDR", "127.0.0.1")
16-
os.environ.setdefault("MASTER_PORT", "29501")
17-
os.environ.setdefault("RANK", "0")
18-
os.environ.setdefault("WORLD_SIZE", "1")
14+
from PIL import Image
1915

20-
from fastvideo.pipelines.basic.glm_image import GlmImagePipeline
21-
from fastvideo.pipelines.pipeline_batch_info import ForwardBatch
16+
from fastvideo import VideoGenerator
2217

2318
OUTPUT_PATH = "image_output"
2419

2520

26-
2721
def main():
28-
# Load the GLM-Image pipeline
29-
pipe = GlmImagePipeline.from_pretrained(
22+
# FastVideo will automatically use the optimal default arguments for GLM-Image.
23+
generator = VideoGenerator.from_pretrained(
3024
"zai-org/GLM-Image",
3125
num_gpus=1,
3226
trust_remote_code=True,
3327
)
3428

35-
# Create a batch with generation parameters
29+
# Generate images - use save_video=False and return_frames=True
30+
# to get raw frames that we can save as images
3631
prompt = (
3732
"A beautiful landscape photography with rolling hills, "
3833
"a winding river, and a vibrant sunset in the background. "
3934
"Warm golden light, photorealistic style."
4035
)
4136

42-
batch = ForwardBatch(
43-
data_type="image",
44-
prompt=prompt,
45-
negative_prompt="",
37+
os.makedirs(OUTPUT_PATH, exist_ok=True)
38+
39+
# Generate first image
40+
result = generator.generate_video(
41+
prompt,
42+
output_path=OUTPUT_PATH,
43+
save_video=False,
44+
return_frames=True,
4645
height=1024,
4746
width=1024,
4847
num_inference_steps=50,
49-
guidance_scale=7.5,
50-
guidance_rescale=0.7,
51-
do_classifier_free_guidance=True,
52-
num_frames=1,
53-
seed=42,
48+
guidance_scale=7.5,
5449
)
5550

56-
# Generate the image
57-
result = pipe.forward(batch, pipe.fastvideo_args)
58-
59-
# Output is in result.output as a tensor [B, C, T, H, W]
60-
print(f"Generated image tensor shape: {result.output.shape}")
61-
62-
# Save the output image
63-
import torch
64-
from torchvision.utils import save_image
65-
66-
os.makedirs(OUTPUT_PATH, exist_ok=True)
67-
# result.output is [B, C, 1, H, W] for images
68-
image_tensor = result.output.squeeze(2) # [B, C, H, W]
69-
save_path = os.path.join(OUTPUT_PATH, "output.png")
70-
save_image(image_tensor, save_path)
71-
print(f"Image saved to {save_path}")
51+
# Save as PNG image (result is a list of frames, take the first/only one)
52+
if result and len(result) > 0:
53+
img = Image.fromarray(result[0])
54+
img.save(os.path.join(OUTPUT_PATH, "landscape.png"))
55+
print(f"Saved image to {OUTPUT_PATH}/landscape.png")
7256

7357

7458
if __name__ == "__main__":

fastvideo/attention/utils/flash_attn_no_pad.py

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,7 @@ def flash_attn_no_pad(qkv,
3030
nheads = qkv.shape[-2]
3131
x = rearrange(qkv, "b s three h d -> b s (three h d)")
3232

33-
# Handle version differences in flash_attn unpad_input
34-
unpad_output = unpad_input(x, key_padding_mask)
35-
if len(unpad_output) == 5:
36-
x_unpad, indices, cu_seqlens, max_s, used_seqlens_in_batch = unpad_output
37-
else:
38-
# Older versions return 4 values or different order?
39-
# Typically: x_unpad, indices, cu_seqlens, max_s
40-
x_unpad, indices, cu_seqlens, max_s = unpad_output[:4]
33+
x_unpad, indices, cu_seqlens, max_s, _ = unpad_input(x, key_padding_mask)
4134

4235
x_unpad = rearrange(x_unpad,
4336
"nnz (three h d) -> nnz three h d",
@@ -76,32 +69,12 @@ def flash_attn_no_pad_v3(qkv,
7669
batch_size, seqlen, _, nheads, head_dim = qkv.shape
7770
query, key, value = qkv.unbind(dim=2)
7871

79-
# Handle version differences in flash_attn unpad_input for query
80-
unpad_output_q = unpad_input(rearrange(query, "b s h d -> b s (h d)"),
81-
key_padding_mask)
82-
if len(unpad_output_q) >= 4:
83-
query_unpad, indices, cu_seqlens_q, max_seqlen_q = unpad_output_q[:4]
84-
else:
85-
raise ValueError(
86-
f"Unexpected unpad_input output length: {len(unpad_output_q)}")
87-
88-
# Handle version differences for key
89-
unpad_output_k = unpad_input(rearrange(key, "b s h d -> b s (h d)"),
90-
key_padding_mask)
91-
if len(unpad_output_k) >= 3:
92-
key_unpad, _, cu_seqlens_k = unpad_output_k[:3]
93-
else:
94-
raise ValueError(
95-
f"Unexpected unpad_input output length: {len(unpad_output_k)}")
96-
97-
# Handle version differences for value
98-
unpad_output_v = unpad_input(rearrange(value, "b s h d -> b s (h d)"),
99-
key_padding_mask)
100-
if len(unpad_output_v) >= 1:
101-
value_unpad = unpad_output_v[0]
102-
else:
103-
raise ValueError(
104-
f"Unexpected unpad_input output length: {len(unpad_output_v)}")
72+
query_unpad, indices, cu_seqlens_q, max_seqlen_q, _ = unpad_input(
73+
rearrange(query, "b s h d -> b s (h d)"), key_padding_mask)
74+
key_unpad, _, cu_seqlens_k, _, _ = unpad_input(
75+
rearrange(key, "b s h d -> b s (h d)"), key_padding_mask)
76+
value_unpad, _, _, _, _ = unpad_input(
77+
rearrange(value, "b s h d -> b s (h d)"), key_padding_mask)
10578

10679
query_unpad = rearrange(query_unpad, "nnz (h d) -> nnz h d", h=nheads)
10780
key_unpad = rearrange(key_unpad, "nnz (h d) -> nnz h d", h=nheads)

fastvideo/configs/models/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ def update_model_arch(self, source_model_dict: dict[str, Any]) -> None:
5151
for key, value in source_model_dict.items():
5252
if key in valid_fields:
5353
setattr(arch_config, key, value)
54-
# Skip unknown fields - HF configs often have extra fields we don't need
54+
else:
55+
raise AttributeError(
56+
f"{type(arch_config).__name__} has no field '{key}'")
5557

5658
if hasattr(arch_config, "__post_init__"):
5759
arch_config.__post_init__()

fastvideo/models/dits/glm_image.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,9 @@ def forward(
423423
# Match SGLang: text part uses provided mask, image part is always 1
424424
mix_attn_mask[:, :text_seq_length] = text_attn_mask.float().to(device)
425425

426-
# Use 2D mask (B, L) for simplified backend handling.
427-
# flash_attn/unpad_input and standard SDPA can both handle this.
428-
attention_mask_kv = (mix_attn_mask > 0) # (B, L)
426+
# Convert to SDPA format: (B, 1, 1, L) for key-padding style mask
427+
# True = attend, False = ignore (will be converted to additive mask by SDPA)
428+
attention_mask_kv = (mix_attn_mask > 0).unsqueeze(1).unsqueeze(2) # (B, 1, 1, L)
429429
else:
430430
attention_mask_kv = None
431431

fastvideo/pipelines/composed_pipeline_base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ def load_modules(
287287
# remove keys that are not pipeline modules
288288
model_index.pop("_class_name")
289289
model_index.pop("_diffusers_version")
290+
model_index.pop("_name_or_path", None)
290291
model_index.pop("workload_type", None)
291292
if "boundary_ratio" in model_index and model_index[
292293
"boundary_ratio"] is not None:
@@ -330,8 +331,6 @@ def load_modules(
330331

331332
modules = {}
332333
for module_name, value in model_index.items():
333-
if module_name.startswith("_"):
334-
continue
335334
transformers_or_diffusers, architecture = value
336335
if transformers_or_diffusers is None:
337336
logger.warning(

fastvideo/pipelines/stages/decoding.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,10 @@ def forward(
244244

245245
# Apply post-decoding hook if configured
246246
# This allows pipelines to apply custom transformations after VAE decoding
247-
if hasattr(fastvideo_args.pipeline_config, 'post_decoding') and \
248-
fastvideo_args.pipeline_config.post_decoding is not None:
247+
post_decoding_hook = getattr(fastvideo_args.pipeline_config, 'post_decoding', None)
248+
if post_decoding_hook is not None:
249249
logger.debug("Applying post_decoding hook")
250-
frames = fastvideo_args.pipeline_config.post_decoding(frames)
250+
frames = post_decoding_hook(frames)
251251

252252
# Convert to CPU float32 for compatibility
253253
frames = frames.cpu().float()

0 commit comments

Comments
 (0)