Compare commits

...
Author SHA1 Message Date
SolitaryThinker 632d56f61b revert 2025-08-09 06:32:36 +00:00
SolitaryThinker ef281dc698 demo release 2025-08-07 03:19:10 +00:00
SolitaryThinker 32d92ce38d demo 2025-08-06 04:00:47 +00:00
SolitaryThinker d8b04897c7 final demo 2025-08-05 21:18:51 +00:00
SolitaryThinker 700de312e4 update 2025-08-04 16:41:54 +00:00
SolitaryThinker 85f0a112c1 update 2025-08-04 14:15:16 +00:00
SolitaryThinker 77e7d995f3 start scripts 2025-08-04 05:56:54 +00:00
SolitaryThinker 8a93b834c1 metrics and fixes 2025-08-04 05:41:01 +00:00
SolitaryThinker 8b2f2b556c update 2025-08-04 05:30:09 +00:00
SolitaryThinker 242e38e98d fix 2025-08-04 02:12:57 +00:00
SolitaryThinker 21fa907a9d add demo 2025-08-04 02:12:55 +00:00
37 changed files with 2038 additions and 267 deletions
+200
View File
@@ -0,0 +1,200 @@
A person reading a book with words that float off the pages and form pictures.
A person diving into a pool of liquid crystal, creating ripples of light.
A handheld shot chasing after a group of friends laughing and playing on the beach at sunset.
A mysterious ancient temple hidden in the jungle.
A high-speed train navigating a steep descent.
a toy robot wearing blue jeans and a white t shirt taking a pleasant stroll in Antarctica during a winter storm
A cheetah accelerating to full speed while chasing its prey.
A serene orchard is in full bloom, with trees heavy with blossoms and bees buzzing around, darting from flower to flower in a display of natural harmony.
A little child let out a big yawn
Subtle reflections of a woman on the window of a train moving at hyper-speed in a Japanese city.
A truck left along the edge of a cliff, revealing the stunning coastal landscape below with waves crashing against the rocks.
A red bird transforms into a flag
A zoom-out from a single leaf on a tree to reveal the entire forest, showcasing the vastness and diversity of the woodland.
A slow-motion video of a liquid droplet bouncing on a water-repellent surface.
Static camera shot. A dinasour running near some lions and chasing them away.
an adorable kangaroo wearing purple overalls and cowboy boots taking a pleasant stroll in Mumbai India during a beautiful sunset
A zoom-in on an artist's brush touching the canvas, highlighting the texture of the paint and the strokes being made.
an old man wearing blue jeans and a white t shirt taking a pleasant stroll in Mumbai India during a colorful festival
A woman is ascending to the sky from the ground
View out a window of a giant strange creature walking in rundown city at night, one single street lamp dimly lighting the area.
An arc shot around a lone tree in a vast, foggy field at dawn, revealing the changing light and shadows.
A person sculpting a statue out of a waterfall, the water solidifying under their touch.
The person's forehead creased with concentration as she worked on a challenging puzzle.
The person's cheeks flushed with pleasure as she savored a delicious meal.
Hand-drawn simple line art, a young kid looking up into space with a wondrous expression on his face.
A crab made of different jewlery is walking on the beach. As it walks, it drops different jewelry pieces like diamonds, pearls, etc
Gold coins are falling out when elevator door opens
the scene transitions from huge waves into a snowy mountain at sunset
a giant cathedral is completely filled with cats. there are cats everywhere you look. a man enters the cathedral and bows before the giant cat king sitting on a throne.
A mother dog gently picks up a piece of meat and carefully places it in her puppy's bowl, her eyes filled with warmth and care as she watches her little one eat.
A soap bubble floating in the air, displaying iridescent colors that shift and change as it moves through different angles of light.
A truck left alongside a train moving through the countryside, matching its speed and revealing the changing landscape.
An astronaut walking between stone buildings.
A close-up shot of the person's face reveals his fear and desperation as he navigates the ship through the storm.
A frozen lake slowly cracking and thawing as spring arrives, with sheets of ice breaking apart and drifting across the surface.
A FPV shot zooming through a tunnel into a vibrant underwater space.
a toy robot wearing blue jeans and a white t shirt taking a pleasant stroll in Mumbai India during a colorful festival
A person sips on a smoothie, the cool and fruity flavors refreshing her mouth.
In a vibrant theater, a magician in dazzling attire stands center stage, pulling a comically oversized rubber chicken from an ornate, old-fashioned box. His costume shimmers under the stage lights, adding to the spectacle. The crowd erupts in laughter and applause, their faces filled with joy and amazement. The magician's expression hints at mischievous delight as he holds up the rubber chicken, his performance bringing cheer to the audience.
A hamster running on a spinning wheel.
A quaint village nestled in a valley is surrounded by blooming cherry blossoms, with petals drifting through the air as villagers go about their daily activities, adding life to the scene.
In a tranquil forest clearing, a sparkling waterfall cascades down into a clear pool, surrounded by lush greenery and flowers, with occasional birds fluttering by.
A woman beamed with pride as she watched her child perform on stage.
an adorable kangaroo wearing blue jeans and a white t shirt taking a pleasant stroll in Mumbai India during a winter storm
A man is eating salad
An Asian girl wearing a bright yellow T-shirt and white pants is Hip-Hop dancing
nighttime footage of a hermit crab using an incandescent lightbulb as its shell
a toy robot wearing a green dress and a sun hat taking a pleasant stroll in Antarctica during a beautiful sunset
A goat operating a food truck, serving gourmet grilled cheese sandwiches to a line of animals.
Macro shot. Man in an antique scuba helmet with dark glass walking out of a flower
A bustling train station in the heart of a vibrant city.
Light filtering through a canopy of autumn leaves, casting warm, dappled patterns of yellow, orange, and red onto the ground.
Chimneys in the setting sun
A longboarder accelerating downhill, carving through turns.
A couple runs through a sudden downpour, laughing and splashing in puddles as they try to find shelter.
A glass of iced coffee condensing water on the outside, with droplets forming and sliding down the glass in slow motion.
macro shot of a leaf showing tiny trains moving through its veins
A corgi wearing sunglasses walks on the beach of a tropical island
Borneo wildlife on the Kinabatangan River
A beautiful silhouette animation shows a wolf howling at the moon, feeling lonely, until it finds its pack.
an adorable kangaroo wearing blue jeans and a white t shirt taking a pleasant stroll in Johannesburg South Africa during a colorful festival
A green monster made of plants walks through an airport.
A close up view of a glass sphere that has a zen garden within it. There is a small dwarf in the sphere who is raking the zen garden and creating patterns in the sand.
A person on a scooter colliding with a park bench, the scooter tipping over.
A tilt-up from a city street, ascending to show the skyline with its mix of modern and historic architecture.
A chef tossing a pancake into the air and catching it.
A woman whispering a secret into a friend's ear.
A vulture circling high in the sky.
A medieval castle overlooking a bustling renaissance fair.
a toy robot wearing purple overalls and cowboy boots taking a pleasant stroll in Mumbai India during a beautiful sunset
A man standing in front of a burning building giving the 'thumbs up' sign.
The person's cheeks flushed with embarrassment as he told a funny story.
Llamas and Emus are playing chess
A woman sipping a steaming cup of tea.
A tree root bursting through the seat of an ancient, weathered bench, intertwining with the wood.
Smoke rises from the chimney of a cozy log cabin nestled in the woods, with soft light glowing from the windows, suggesting a warm and inviting atmosphere.
A close-up of sparkling water being poured into a glass, capturing the detailed flow and bubbles.
a woman wearing blue jeans and a white t shirt taking a pleasant stroll in Antarctica during a beautiful sunset
The Glenfinnan Viaduct is a historic railway bridge in Scotland, UK, that crosses over the west highland line between the towns of Mallaig and Fort William. It is a stunning sight as a steam train leaves the bridge, traveling over the arch-covered viaduct. The landscape is dotted with lush greenery and rocky mountains, creating a picturesque backdrop for the train journey. The sky is blue and the sun is shining, making for a beautiful day to explore this majestic spot.
A piece of elastic fabric being pulled and stretched, then returning to its original size when the tension is released.
a woman wearing a green dress and a sun hat taking a pleasant stroll in Antarctica during a beautiful sunset
A video of a water jet cutting through metal, showing the powerful and precise movement of water.
Car mirrors and sunsets
Giant Pandas are eating hot noodles in a Chinese restaurant
A rally car taking a fast turn on a track
a toy robot wearing purple overalls and cowboy boots taking a pleasant stroll in Mumbai India during a colorful festival
A crystal-clear icicle slowly dripping as it melts in the warmth of the midday sun, each drop sparkling as it falls.
A tilt-down from a chandelier in a grand hall, revealing the ornate decor and people mingling below.
A man is playing the drums under the water
A person playing an electric guitar made of lightning, with thunderous sound waves.
A person floating in a bubble, drifting over a bustling cityscape.
A tilt-down from a starry night sky, revealing a quiet forest clearing bathed in moonlight.
A pan right through a dense jungle, moving past lush vegetation and exotic wildlife.
Close-up of a man eating an apple.
A low-angle shot of a dancer leaping gracefully into the air, making their movement appear even more dynamic and powerful.
A woman is search her bag trying to find something.
A bulldozer clears debris from a demolished building, making way for new construction.
A man sighed in relief as the doctor delivered the good news.
A tsunami coming through an alley in Bulgaria, dynamic movement.
Blooming Flowers
A push-in through a dense crowd at a festival, moving towards a performer on stage who is captivating the audience.
A truck right through a tranquil garden, moving past blooming flowers, trees, and a small fountain.
The person's eyes sparkled with excitement as he greeted a friend.
A person playing chess with a robot on a floating platform above the ocean.
A gentle breeze rustles the leaves as someone walks down a serene forest path, sunlight filtering through the trees and shifting patterns on the ground as branches sway.
A rollercoaster ride from a city to a desert and then to an ice world
A pan left across an ancient library, moving from shelf to shelf, showcasing rows of leather-bound books.
A mother otter floating on her back in a river, cradling her pup on her stomach to keep it safe and warm in the gentle current.
an adorable kangaroo wearing purple overalls and cowboy boots taking a pleasant stroll in Johannesburg South Africa during a colorful festival
a woman wearing a green dress and a sun hat taking a pleasant stroll in Mumbai India during a colorful festival
A delicate layer of morning frost melting off a flower petal, the tiny droplets glistening like diamonds in the light.
A panda is cooking for her child, her child is next to her.
Macro shot of a man wearing an antique diving helmet with dark glass and a jetpack walking on the veins of a leaf. Realistic style
an old man wearing purple overalls and cowboy boots taking a pleasant stroll in Johannesburg South Africa during a beautiful sunset
A girl is unfolding a birthday gift.
A pencil drawing an architectural plan.
A handheld camera following a dog running through a park, bouncing and tilting as it captures the dog's joyful exploration.
A pan left across a serene beach at sunrise, moving from the darkened shore to the brightening horizon.
A group of people are clapping to celebrate
Vendors set up stalls at a bustling farmer’s market, displaying fresh fruits and vegetables, while people stroll through, selecting produce and enjoying the lively atmosphere.
A police helicopter hovers above a high-speed chase, guiding officers on the ground to apprehend a suspect.
A paper origami dragon riding a boat in waves. Realistic style.
A close-up of a droplet of dew forming on a leaf, capturing the detailed surface tension.
a toy robot wearing blue jeans and a white t shirt taking a pleasant stroll in Mumbai India during a beautiful sunset
A dry rainbow rose is coming back to life.
A glass falling off a table and shattering on the floor.
A marathon runner crossing the finish line after a grueling race.
A zoom-in on a drop of morning dew on a leaf, showing the reflection of the surrounding world within it.
A child blowing on hot cocoa to cool it down.
A squad of futsal players showcasing their skills on an indoor court.
A princess is brushing her long golden hair in the garden.
A close-up of a pair of eyes, revealing the subtle emotions and reflections within them.
A tracking shot of a group of cyclists racing through a forest trail, with trees and foliage rushing by.
A woman yawning widely at the end of a long day.
an old man wearing a green dress and a sun hat taking a pleasant stroll in Johannesburg South Africa during a colorful festival
Hidden within a garden, an ancient fountain trickles with water, surrounded by vibrant flowers and lush greenery that seem to whisper secrets of the past.
A Chinese man sits at a table and eats noodles with chopsticks
A pink pig running fast toward the camera in an alley in Tokyo.
Strange creatures move through a mysterious, foggy marsh, their silhouettes barely visible through the dense mist as they navigate the eerie, otherworldly landscape.
Tour of an art gallery with many beautiful works of art in different styles.
FPV flying through a colorful coral lined streets of an underwater suburban neighborhood.
Aerial view of Santorini during the blue hour, showcasing the stunning architecture of white Cycladic buildings with blue domes. The caldera views are breathtaking, and the lighting creates a beautiful, serene atmosphere.
Camera zoom out. A couple walking along the beach as the sun sets over the ocean.
an extreme close up shot of a woman's eye, with her iris appearing as earth
a woman wearing purple overalls and cowboy boots taking a pleasant stroll in Mumbai India during a colorful festival
an old man wearing a green dress and a sun hat taking a pleasant stroll in Mumbai India during a winter storm
an adorable kangaroo wearing blue jeans and a white t shirt taking a pleasant stroll in Antarctica during a winter storm
A martial artist breaking a board with a powerful punch.
People gather on a peaceful beach at sunset, a bonfire crackling as they sit around, enjoying the warmth and the sight of the sun dipping below the horizon.
A close-up of a waterfall, showing the detailed movement of water as it crashes down.
A child is blowing bubbles
a woman wearing a green dress and a sun hat taking a pleasant stroll in Johannesburg South Africa during a winter storm
A wide-angle perspective of a serene lake surrounded by mountains, reflecting the sky and creating a sense of infinite space.
The person's eyebrows arched in skepticism as she listened to a dubious claim.
an old man wearing blue jeans and a white t shirt taking a pleasant stroll in Mumbai India during a beautiful sunset
a woman wearing a green dress and a sun hat taking a pleasant stroll in Johannesburg South Africa during a colorful festival
a woman wearing purple overalls and cowboy boots taking a pleasant stroll in Antarctica during a colorful festival
a toy robot wearing blue jeans and a white t shirt taking a pleasant stroll in Antarctica during a colorful festival
A chef flips a pancake and puts cream on it.
An astronaut runs on the surface of the moon, the low angle shot shows the vast background of the moon, the movement is smooth and appears lightweight
A man's face lit up with happiness as he received a heartfelt compliment.
A futuristic spaceport hums with activity as ships of various shapes and sizes take off and land on multiple platforms, their engines glowing with vibrant colors.
A person knitting a scarf using beams of light instead of yarn.
A pedestal up from the edge of a canyon, gradually revealing the expansive landscape and river below.
a woman wearing purple overalls and cowboy boots taking a pleasant stroll in Johannesburg South Africa during a colorful festival
an old man wearing blue jeans and a white t shirt taking a pleasant stroll in Johannesburg South Africa during a colorful festival
A person walking up a staircase made of clouds leading to a floating castle.
Monks meditate in a serene mountaintop temple, sitting in quiet reflection as the wind gently moves through the surrounding trees, creating a sense of peace and tranquility.
An aerial shot of a bustling city intersection at rush hour, capturing the organized chaos of cars and pedestrians.
A pair of hands skillfully knitting a colorful scarf, the yarn winding through their fingers with each stitch.
Close-up, a Chinese child is eating dumplings
A kite losing wind and falling to the ground.
Bioluminescent waves gently wash ashore on a deserted beach, illuminating the sand with each cresting wave as a figure walks along the water's edge, leaving glowing footprints.
A red panda taking a bite of a pizza
A close-up shot of a young woman driving a car, looking thoughtful, blurred green forest visible through the rainy car window.
A high-speed video of a splash created by a stone thrown into a pond.
A metal rod being bent slightly by a force and then springing back to its original straight shape when the force is removed.
A hedgehog in a knight's armor, riding a toy horse into a medieval castle.
A bird made of fresh oranges rushes out of the orange
A low altitude first person perspective camera tracking shot of a soccer player's feet dribbling the ball on the groud in a soccer field, Sports Videography, Motion Tracking camera shot
A tranquil island retreat features swaying palm trees and hammocks strung between them, inviting guests to relax and enjoy the serene beauty of the surroundings.
a spooky haunted mansion, with friendly jack o lanterns and ghost characters welcoming trick or treaters to the entrance, tilt shift photography
A coconut tree made of dollar bills at sunset, with bills falling off like leaves.
A motocross bike accelerating out of a tight turn on a dirt track.
A tranquil Zen garden with a gently flowing stream and koi fish.
A green monster made of leaves walks through the airport, carrying a suitcase.
A time-lapse of a frost-covered leaf gradually thawing in the morning sunlight, with tiny water droplets forming and trickling down.
A woman practicing her archery skills at a range.
A slow-motion video of ink being injected into a tank of water, creating intricate and beautiful patterns.
a woman wearing blue jeans and a white t shirt taking a pleasant stroll in Johannesburg South Africa during a winter storm
The person's forehead creased with worry as he listened to bad news.
An arc shot around a grand piano being played in an empty concert hall, the motion revealing the intricate details of the instrument.
A person conducting a symphony of animals in a forest clearing.
A truck right alongside a flowing river, capturing the movement of the water and the surrounding forest.
A rocket blasting off from the launch pad, accelerating rapidly into the sky.
Workers move through a picturesque vineyard during the harvest season, carefully picking grapes and placing them into baskets as the sun bathes the vines in a warm glow.
A person is eating an ice cream.
An over-the-shoulder perspective of a chef meticulously plating a dish in a bustling kitchen.
A man looked away in shame when confronted with his wrongdoing.
A person is savoring a slice of pizza at a pizzeria.
+28 -13
View File
@@ -1,6 +1,10 @@
from fastvideo import VideoGenerator
# from fastvideo.configs.sample import SamplingParam
from fastvideo.configs.sample import SamplingParam
import os
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "VIDEO_SPARSE_ATTN"
OUTPUT_PATH = "video_samples"
def main():
@@ -8,30 +12,41 @@ def main():
# model.
# If a local path is provided, FastVideo will make a best effort
# attempt to identify the optimal arguments.
model_name = "FastVideo/FastWan2.1-T2V-14B-Diffusers"
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
model_name,
# FastVideo will automatically handle distributed setup
num_gpus=1,
use_fsdp_inference=True,
dit_cpu_offload=False,
vae_cpu_offload=False,
text_encoder_cpu_offload=True,
text_encoder_cpu_offload=False,
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
pin_cpu_memory=False,
pin_cpu_memory=True,
# image_encoder_cpu_offload=False,
)
# sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
# sampling_param.num_frames = 45
# sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"
sampling_param = SamplingParam.from_pretrained(model_name)
# sampling_param.image_path = "test.jpg"
# sampling_param.num_inference_steps = 0
# Generate videos with the same simple API, regardless of GPU count
prompt = (
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
"wide with interest. The playful yet serene atmosphere is complemented by soft "
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
)
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True)
i2v_prompt = "An astronaut hatching from an egg, on the surface of the moon, the darkness and depth of space realised in the background. High quality, ultrarealistic detail and breath-taking movie-like camera shot."
i2v_prompt = "A little girl is packing a suitcase and the contents starts flying out of the suitcase everywhere."
prompt = i2v_prompt
# prompt = (
# "A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
# "wide with interest. The playful yet serene atmosphere is complemented by soft "
# "natural light filtering through the petals. Mid-shot, warm and cheerful tones."
# )
results = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param)
stage_names = results["stage_names"]
stage_execution_times = results["stage_execution_times"]
# print(logging_info)
print(stage_names)
print(stage_execution_times)
# video = generator.generate_video(prompt, sampling_param=sampling_param, output_path="wan_t2v_videos/")
return
# Generate another video with a different prompt, without reloading the
# model!
+2 -1
View File
@@ -31,6 +31,7 @@ def main():
prompt = (
"A neon-lit alley in futuristic Tokyo during a heavy rainstorm at night. The puddles reflect glowing signs in kanji, advertising ramen, karaoke, and VR arcades. A woman in a translucent raincoat walks briskly with an LED umbrella. Steam rises from a street food cart, and a cat darts across the screen. Raindrops are visible on the camera lens, creating a cinematic bokeh effect."
)
prompt = "A vintage train snakes through the mountains, its plume of white steam rising dramatically against the jagged peaks. The cars glint in the late afternoon sun, their deep crimson and gold accents lending a touch of elegance. The tracks carve a precarious path along the cliffside, revealing glimpses of a roaring river far below. Inside, passengers peer out the large windows, their faces lit with awe as the landscape unfolds."
start_time = time.perf_counter()
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param)
end_time = time.perf_counter()
@@ -45,7 +46,7 @@ def main():
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
"cinematic.")
start_time = time.perf_counter()
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True)
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=False)
end_time = time.perf_counter()
gen_time2 = end_time - start_time
+38
View File
@@ -0,0 +1,38 @@
from fastvideo import VideoGenerator
from fastvideo.configs.sample import SamplingParam
OUTPUT_PATH = "video_samples"
def main():
# FastVideo will automatically use the optimal default arguments for the
# model.
# If a local path is provided, FastVideo will make a best effort
# attempt to identify the optimal arguments.
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-I2V-14B-480P-Diffusers",
# FastVideo will automatically handle distributed setup
num_gpus=2,
use_fsdp_inference=True,
dit_cpu_offload=False,
vae_cpu_offload=False,
text_encoder_cpu_offload=False,
image_encoder_cpu_offload=False,
)
sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-I2V-14B-480P-Diffusers")
sampling_param.num_frames = 61
sampling_param.num_inference_steps = 40
sampling_param.guidance_scale = 5.0
sampling_param.height = 448
sampling_param.width = 832
sampling_param.seed = 1024
sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"
# Generate videos with the same simple API, regardless of GPU count
prompt = (
"An astronaut hatching from an egg, on the surface of the moon, the darkness and depth of space realised in the background. High quality, ultrarealistic detail and breath-taking movie-like camera shot."
)
video = generator.generate_video(prompt, sampling_param=sampling_param, output_path=OUTPUT_PATH, save_video=True)
if __name__ == "__main__":
main()
-59
View File
@@ -1,59 +0,0 @@
# FastVideo Gradio Demo
This is a Gradio-based web interface for generating videos using the FastVideo framework. The demo allows users to create videos from text prompts with various customization options.
## Overview
The demo uses the FastVideo framework to generate videos based on text prompts. It provides a simple web interface built with Gradio that allows users to:
- Enter text prompts to generate videos
- Customize video parameters (dimensions, number of frames, etc.)
- Use negative prompts to guide the generation process
- Set or randomize seeds for reproducibility
---
## Usage
Run the demo with:
```bash
python examples/inference/gradio/gradio_demo.py
```
This will start a web server at `http://0.0.0.0:7860` where you can access the interface.
---
## Model Initialization
This demo initializes a `VideoGenerator` with the minimum required arguments for inference. Users can seamlessly adjust inference options between generations, including prompts, resolution, video length, or even the number of inference steps, *without ever needing to reload the model*.
## Video Generation
The core functionality is in the `generate_video` function, which:
1. Processes user inputs
2. Uses the FastVideo VideoGenerator from earlier to run inference (`generator.generate_video()`)
3. Returns an output path that Gradio uses to display the generated video
## Gradio Interface
The interface is built with several components:
- A text input for the prompt
- A video display for the result
- Inference options in a collapsible accordion:
- Height and width sliders
- Number of frames slider
- Guidance scale slider
- Inference steps slider
- Negative prompt options
- Seed controls
### Inference Options
- **Height/Width**: Control the resolution of the generated video
- **Number of Frames**: Set how many frames to generate
- **Guidance Scale**: Control how closely the generation follows the prompt
- **Inference Steps**: More steps can improve quality but take longer
- **Negative Prompt**: Specify what you don't want to see in the video
- **Seed**: Control randomness for reproducible results
-169
View File
@@ -1,169 +0,0 @@
import argparse
import os
from copy import deepcopy
import gradio as gr
import torch
from fastvideo import VideoGenerator
from fastvideo.configs.sample.base import SamplingParam
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="FastVideo Gradio Demo")
parser.add_argument("--model_path",
type=str,
default="FastVideo/FastHunyuan-diffusers",
help="Path to the model")
parser.add_argument("--num_gpus",
type=int,
default=1,
help="Number of GPUs to use")
parser.add_argument("--output_path",
type=str,
default="outputs",
help="Path to save generated videos")
parsed_args = parser.parse_args()
# args = FastVideoArgs(model_path="FastVideo/FastHunyuan-Diffusers", num_gpus=2)
generator = VideoGenerator.from_pretrained(
model_path=parsed_args.model_path, num_gpus=parsed_args.num_gpus)
default_params = SamplingParam.from_pretrained(parsed_args.model_path)
def generate_video(
prompt,
negative_prompt,
use_negative_prompt,
seed,
guidance_scale,
num_frames,
height,
width,
num_inference_steps,
randomize_seed=False,
):
params = deepcopy(default_params)
params.prompt = prompt
params.negative_prompt = negative_prompt
params.seed = seed
params.guidance_scale = guidance_scale
params.num_frames = num_frames
params.height = height
params.width = width
params.num_inference_steps = num_inference_steps
if randomize_seed:
params.seed = torch.randint(0, 1000000, (1, )).item()
if not use_negative_prompt:
params.negative_prompt = None
generator.generate_video(prompt=prompt, sampling_param=params)
output_path = os.path.join(parsed_args.output_path,
f"{params.prompt[:100]}.mp4")
return output_path, params.seed
examples = [
"A hand enters the frame, pulling a sheet of plastic wrap over three balls of dough placed on a wooden surface. The plastic wrap is stretched to cover the dough more securely. The hand adjusts the wrap, ensuring that it is tight and smooth over the dough. The scene focuses on the hand’s movements as it secures the edges of the plastic wrap. No new objects appear, and the camera remains stationary, focusing on the action of covering the dough.",
"A vintage train snakes through the mountains, its plume of white steam rising dramatically against the jagged peaks. The cars glint in the late afternoon sun, their deep crimson and gold accents lending a touch of elegance. The tracks carve a precarious path along the cliffside, revealing glimpses of a roaring river far below. Inside, passengers peer out the large windows, their faces lit with awe as the landscape unfolds.",
"A crowded rooftop bar buzzes with energy, the city skyline twinkling like a field of stars in the background. Strings of fairy lights hang above, casting a warm, golden glow over the scene. Groups of people gather around high tables, their laughter blending with the soft rhythm of live jazz. The aroma of freshly mixed cocktails and charred appetizers wafts through the air, mingling with the cool night breeze.",
]
with gr.Blocks() as demo:
gr.Markdown("# FastVideo Inference Demo")
with gr.Group():
with gr.Row():
prompt = gr.Text(
label="Prompt",
show_label=False,
max_lines=1,
placeholder="Enter your prompt",
container=False,
)
run_button = gr.Button("Run", scale=0)
result = gr.Video(label="Result", show_label=False)
with gr.Accordion("Advanced options", open=False):
with gr.Group():
with gr.Row():
height = gr.Slider(
label="Height",
minimum=256,
maximum=1024,
step=32,
value=default_params.height,
)
width = gr.Slider(label="Width",
minimum=256,
maximum=1024,
step=32,
value=default_params.width)
with gr.Row():
num_frames = gr.Slider(
label="Number of Frames",
minimum=21,
maximum=163,
value=default_params.num_frames,
)
guidance_scale = gr.Slider(
label="Guidance Scale",
minimum=1,
maximum=12,
value=default_params.guidance_scale,
)
num_inference_steps = gr.Slider(
label="Inference Steps",
minimum=4,
maximum=100,
value=default_params.num_inference_steps,
)
with gr.Row():
use_negative_prompt = gr.Checkbox(
label="Use negative prompt", value=False)
negative_prompt = gr.Text(
label="Negative prompt",
max_lines=1,
placeholder="Enter a negative prompt",
visible=False,
)
seed = gr.Slider(label="Seed",
minimum=0,
maximum=1000000,
step=1,
value=default_params.seed)
randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
seed_output = gr.Number(label="Used Seed")
gr.Examples(examples=examples, inputs=prompt)
use_negative_prompt.change(
fn=lambda x: gr.update(visible=x),
inputs=use_negative_prompt,
outputs=default_params.negative_prompt,
)
run_button.click(
fn=generate_video,
inputs=[
prompt,
negative_prompt,
use_negative_prompt,
seed,
guidance_scale,
num_frames,
height,
width,
num_inference_steps,
randomize_seed,
],
outputs=[result, seed_output],
)
demo.queue(max_size=20).launch(server_name="0.0.0.0", server_port=7860)
@@ -0,0 +1,730 @@
import argparse
import os
import requests
import base64
import time
import gradio as gr
from fastvideo.configs.sample.base import SamplingParam
MODEL_PATH_MAPPING = {
"FastWan2.1-T2V-1.3B": "FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
"FastWan2.2-TI2V-5B-FullAttn": "FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers",
}
class RayServeClient:
def __init__(self, backend_url: str):
self.backend_url = backend_url
self.session = requests.Session()
def check_health(self) -> bool:
try:
response = self.session.get(f"{self.backend_url}/health", timeout=5)
return response.status_code == 200
except requests.exceptions.RequestException:
return False
def generate_video(self, request_data: dict) -> dict:
start_time = time.time()
try:
headers = {"Content-Type": "application/json"}
response = self.session.post(
f"{self.backend_url}/generate_video",
json=request_data,
headers=headers,
timeout=300
)
round_trip_time = time.time() - start_time
if response.status_code == 200:
result = response.json()
backend_total = result.get("total_time", 0)
network_time = round_trip_time - backend_total
result["network_time"] = network_time
return result
else:
return {"success": False, "error_message": f"HTTP {response.status_code}: {response.text}"}
except requests.exceptions.RequestException as e:
return {"success": False, "error_message": f"Request failed: {str(e)}"}
def save_video_from_base64(video_data: str, output_dir: str, prompt: str) -> str:
if not video_data:
return None
try:
if video_data.startswith('data:video/'):
video_data = video_data.split(',')[1]
video_bytes = base64.b64decode(video_data)
safe_prompt = prompt[:50].replace(' ', '_').replace('/', '_').replace('\\', '_')
video_filename = f"{safe_prompt}.mp4"
video_path = os.path.join(output_dir, video_filename)
os.makedirs(output_dir, exist_ok=True)
with open(video_path, 'wb') as f:
f.write(video_bytes)
return video_path
except Exception as e:
print(f"Failed to save video: {e}")
return None
def create_timing_display(inference_time, encoding_time, network_time, total_time, stage_execution_times, num_frames):
dit_denoising_time = f"{stage_execution_times[5]:.2f}s" if len(stage_execution_times) > 5 else "N/A"
timing_html = f"""
<div style="margin: 10px 0;">
<h3 style="text-align: center; margin-bottom: 10px;">⏱️ Timing Breakdown</h3>
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 10px;">
<div class="timing-card timing-card-highlight">
<div style="font-size: 20px;">🚀</div>
<div style="font-weight: bold; margin: 3px 0; font-size: 14px;">DiT Denoising</div>
<div style="font-size: 16px; color: #ffa200; font-weight: bold;">{dit_denoising_time}</div>
</div>
<div class="timing-card">
<div style="font-size: 20px;">🧠</div>
<div style="font-weight: bold; margin: 3px 0; font-size: 14px;">E2E (w. vae/text encoder)</div>
<div style="font-size: 16px; color: #2563eb;">{inference_time:.2f}s</div>
</div>
<div class="timing-card">
<div style="font-size: 20px;">🎬</div>
<div style="font-weight: bold; margin: 3px 0; font-size: 14px;">Video Encoding</div>
<div style="font-size: 16px; color: #dc2626;">{encoding_time:.2f}s</div>
</div>
<div class="timing-card">
<div style="font-size: 20px;">🌐</div>
<div style="font-weight: bold; margin: 3px 0; font-size: 14px;">Network Transfer</div>
<div style="font-size: 16px; color: #059669;">{network_time:.2f}s</div>
</div>
<div class="timing-card">
<div style="font-size: 20px;">📊</div>
<div style="font-weight: bold; margin: 3px 0; font-size: 14px;">Total Processing</div>
<div style="font-size: 18px; color: #0277bd;">{total_time:.2f}s</div>
</div>
</div>"""
if inference_time > 0:
fps = num_frames / inference_time
timing_html += f"""
<div class="performance-card" style="margin-top: 15px;">
<span style="font-weight: bold;">Generation Speed: </span>
<span style="font-size: 18px; color: #6366f1; font-weight: bold;">{fps:.1f} frames/second</span>
</div>"""
return timing_html + "</div>"
def load_example_prompts():
def contains_chinese(text):
return any('\u4e00' <= char <= '\u9fff' for char in text)
def load_from_file(filepath):
prompts, labels = [], []
try:
with open(filepath, "r", encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not contains_chinese(line):
label = line[:100] + "..." if len(line) > 100 else line
labels.append(label)
prompts.append(line)
except Exception as e:
print(f"Warning: Could not read {filepath}: {e}")
return prompts, labels
examples, example_labels = load_from_file("prompts/prompts_final.txt")
if not examples:
examples = ["A crowded rooftop bar buzzes with energy, the city skyline twinkling like a field of stars in the background."]
example_labels = ["Crowded rooftop bar at night"]
return examples, example_labels
def create_gradio_interface(backend_url: str, default_params: dict[str, SamplingParam]):
client = RayServeClient(backend_url)
def generate_video(
prompt, negative_prompt, use_negative_prompt, seed, guidance_scale,
num_frames, height, width, randomize_seed, model_selection, progress
):
if not client.check_health():
return None, f"Backend is not available. Please check if Ray Serve is running at {backend_url}", ""
# Validate dimensions
max_pixels = 720 * 1280
if height * width > max_pixels:
return None, f"Video dimensions too large. Maximum: 720x1280 pixels", ""
if progress:
progress(0.1, desc="Checking backend health...")
request_data = {
"prompt": prompt,
"negative_prompt": negative_prompt,
"use_negative_prompt": use_negative_prompt,
"seed": seed,
"guidance_scale": guidance_scale,
"num_frames": num_frames,
"height": height,
"width": width,
"randomize_seed": randomize_seed,
"return_frames": False,
"image_path": None,
"model_path": MODEL_PATH_MAPPING.get(model_selection, "FastVideo/FastWan2.1-T2V-1.3B-Diffusers")
}
if progress:
progress(0.4, desc="Generating video...")
response = client.generate_video(request_data)
if progress:
progress(0.8, desc="Processing response...")
if response.get("success", False):
video_data = response.get("video_data", "")
used_seed = response.get("seed", seed)
inference_time = response.get("inference_time", 0.0)
encoding_time = response.get("encoding_time", 0.0)
total_time = response.get("total_time", 0.0)
network_time = response.get("network_time", 0.0)
stage_execution_times = response.get("stage_execution_times", [])
timing_details = create_timing_display(
inference_time, encoding_time, network_time, total_time,
stage_execution_times, num_frames
)
if video_data:
if progress:
progress(0.9, desc="Saving video...")
video_path = save_video_from_base64(video_data, "outputs", prompt)
if progress:
progress(1.0, desc="Generation complete!")
if video_path and os.path.exists(video_path):
return video_path, used_seed, timing_details
else:
return None, "Failed to save video", ""
else:
return None, "No video data received from backend", ""
else:
error_msg = response.get("error_message", "Unknown error occurred")
return None, f"Generation failed: {error_msg}", ""
examples, example_labels = load_example_prompts()
theme = gr.themes.Base().set(
button_primary_background_fill="#2563eb",
button_primary_background_fill_hover="#1d4ed8",
button_primary_text_color="white",
slider_color="#2563eb",
checkbox_background_color_selected="#2563eb",
)
def get_default_values(model_name):
model_path = MODEL_PATH_MAPPING.get(model_name)
if model_path and model_path in default_params:
params = default_params[model_path]
return {
'height': params.height,
'width': params.width,
'num_frames': params.num_frames,
'guidance_scale': params.guidance_scale,
'seed': params.seed,
}
return {
'height': 448,
'width': 832,
'num_frames': 61,
'guidance_scale': 3.0,
'seed': 1024,
}
initial_values = get_default_values("FastWan2.1-T2V-1.3B")
with gr.Blocks(title="FastWan", theme=theme) as demo:
gr.Image("fastvideo-logos/main/svg/full.svg", show_label=False, container=False, height=80)
gr.HTML("""
<div style="text-align: center; margin-bottom: 10px;">
<p style="font-size: 18px;"> Make Video Generation Go Blurrrrrrr </p>
<p style="font-size: 18px;"> <a href="https://github.com/hao-ai-lab/FastVideo/tree/main" target="_blank">Code</a> | <a href="https://hao-ai-lab.github.io/blogs/fastvideo_post_training/" target="_blank">Blog</a> | <a href="https://hao-ai-lab.github.io/FastVideo/" target="_blank">Docs</a> </p>
</div>
""")
with gr.Accordion("🎥 What Is FastVideo?", open=False):
gr.HTML("""
<div style="padding: 20px; line-height: 1.6;">
<p style="font-size: 16px; margin-bottom: 15px;">
FastVideo is an inference and post-training framework for diffusion models. It features an end-to-end unified pipeline for accelerating diffusion models, starting from data preprocessing to model training, finetuning, distillation, and inference. FastVideo is designed to be modular and extensible, allowing users to easily add new optimizations and techniques. Whether it is training-free optimizations or post-training optimizations, FastVideo has you covered.
</p>
</div>
""")
with gr.Row():
model_selection = gr.Dropdown(
choices=list(MODEL_PATH_MAPPING.keys()),
value="FastWan2.1-T2V-1.3B",
label="Select Model",
interactive=True
)
with gr.Row():
example_dropdown = gr.Dropdown(
choices=example_labels,
label="Example Prompts",
value=None,
interactive=True,
allow_custom_value=False
)
with gr.Row():
with gr.Column(scale=6):
prompt = gr.Text(
label="Prompt",
show_label=False,
max_lines=3,
placeholder="Describe your scene...",
container=False,
lines=3,
autofocus=True,
)
with gr.Column(scale=1, min_width=120, elem_classes="center-button"):
run_button = gr.Button("Run", variant="primary", size="lg")
with gr.Row():
with gr.Column():
error_output = gr.Text(label="Error", visible=False)
timing_display = gr.Markdown(label="Timing Breakdown", visible=False)
with gr.Row(equal_height=True, elem_classes="main-content-row"):
with gr.Column(scale=1, elem_classes="advanced-options-column"):
with gr.Group():
gr.HTML("<div style='margin: 0 0 15px 0; text-align: center; font-size: 16px;'>Advanced Options</div>")
with gr.Row():
height = gr.Number(
label="Height",
value=initial_values['height'],
interactive=False,
container=True
)
width = gr.Number(
label="Width",
value=initial_values['width'],
interactive=False,
container=True
)
with gr.Row():
num_frames = gr.Number(
label="Number of Frames",
value=initial_values['num_frames'],
interactive=False,
container=True
)
guidance_scale = gr.Slider(
label="Guidance Scale",
minimum=1,
maximum=12,
value=initial_values['guidance_scale'],
)
with gr.Row():
use_negative_prompt = gr.Checkbox(
label="Use negative prompt", value=False)
negative_prompt = gr.Text(
label="Negative prompt",
max_lines=3,
lines=3,
placeholder="Enter a negative prompt",
visible=False,
)
seed = gr.Slider(
label="Seed",
minimum=0,
maximum=1000000,
step=1,
value=initial_values['seed'],
)
randomize_seed = gr.Checkbox(label="Randomize seed", value=False)
seed_output = gr.Number(label="Used Seed")
with gr.Column(scale=1, elem_classes="video-column"):
result = gr.Video(
label="Generated Video",
show_label=True,
height=466,
width=600,
container=True,
elem_classes="video-component"
)
gr.HTML("""
<style>
.center-button {
display: flex !important;
justify-content: center !important;
height: 100% !important;
padding-top: 1.4em !important;
}
.gradio-container {
max-width: 1200px !important;
margin: 0 auto !important;
}
.main {
max-width: 1200px !important;
margin: 0 auto !important;
}
.gr-form, .gr-box, .gr-group {
max-width: 1200px !important;
}
.gr-video {
max-width: 500px !important;
margin: 0 auto !important;
}
.main-content-row {
display: flex !important;
align-items: flex-start !important;
min-height: 500px !important;
gap: 20px !important;
}
.advanced-options-column,
.video-column {
display: flex !important;
flex-direction: column !important;
flex: 1 !important;
min-height: 400px !important;
align-items: stretch !important;
}
.video-column > * {
margin-top: 0 !important;
}
.video-column .gr-video,
.video-component {
margin-top: 0 !important;
padding-top: 0 !important;
}
.video-column .gr-video .gr-form {
margin-top: 0 !important;
}
.advanced-options-column .gr-group,
.video-column .gr-video {
margin-top: 0 !important;
vertical-align: top !important;
}
.advanced-options-column > *:last-child,
.video-column > *:last-child {
flex-grow: 0 !important;
}
@media (max-width: 1400px) {
.main-content-row {
min-height: 600px !important;
}
.advanced-options-column,
.video-column {
min-height: 600px !important;
}
}
@media (max-width: 1200px) {
.main-content-row {
flex-direction: column !important;
align-items: stretch !important;
}
.advanced-options-column,
.video-column {
min-height: auto !important;
width: 100% !important;
}
}
.timing-card {
background: var(--background-fill-secondary) !important;
border: 1px solid var(--border-color-primary) !important;
color: var(--body-text-color) !important;
padding: 10px;
border-radius: 8px;
text-align: center;
min-height: 80px;
display: flex;
flex-direction: column;
justify-content: center;
}
.timing-card-highlight {
background: var(--background-fill-primary) !important;
border: 2px solid var(--color-accent) !important;
}
.performance-card {
background: var(--background-fill-secondary) !important;
border: 1px solid var(--border-color-primary) !important;
color: var(--body-text-color) !important;
padding: 10px;
border-radius: 6px;
text-align: center;
}
.gr-number input[readonly] {
background-color: var(--background-fill-secondary) !important;
border: 1px solid var(--border-color-primary) !important;
color: var(--body-text-color-subdued) !important;
cursor: default !important;
text-align: center !important;
font-weight: 500 !important;
}
</style>
""")
def on_example_select(example_label):
if example_label and example_label in example_labels:
index = example_labels.index(example_label)
return examples[index]
return ""
example_dropdown.change(
fn=on_example_select,
inputs=example_dropdown,
outputs=prompt,
)
gr.HTML("""
<div style="text-align: center; margin-top: 10px; margin-bottom: 15px;">
<p style="font-size: 16px; margin: 0;">The compute for this demo is generously provided by <a href="https://www.gmicloud.ai/" target="_blank">GMI Cloud</a>. Note that this demo is meant to showcase FastWan's quality and that under a large number of requests, generation speed may be affected. We are also rate-limiting users to 3 requests per minute.</p>
</div>
""")
use_negative_prompt.change(
fn=lambda x: gr.update(visible=x),
inputs=use_negative_prompt,
outputs=negative_prompt,
)
def on_model_selection_change(selected_model):
if not selected_model:
selected_model = "FastWan2.1-T2V-1.3B"
model_path = MODEL_PATH_MAPPING.get(selected_model)
if model_path and model_path in default_params:
params = default_params[model_path]
return (
gr.update(value=params.height),
gr.update(value=params.width),
gr.update(value=params.num_frames),
gr.update(value=params.guidance_scale),
gr.update(value=params.seed),
)
return (
gr.update(value=448),
gr.update(value=832),
gr.update(value=61),
gr.update(value=3.0),
gr.update(value=1024),
)
model_selection.change(
fn=on_model_selection_change,
inputs=model_selection,
outputs=[height, width, num_frames, guidance_scale, seed],
)
def handle_generation(*args, progress=None, request: gr.Request = None):
model_selection, prompt, negative_prompt, use_negative_prompt, seed, guidance_scale, num_frames, height, width, randomize_seed = args
result_path, seed_or_error, timing_details = generate_video(
prompt, negative_prompt, use_negative_prompt, seed, guidance_scale,
num_frames, height, width, randomize_seed, model_selection, progress
)
if result_path and os.path.exists(result_path):
return (
result_path,
seed_or_error,
gr.update(visible=False),
gr.update(visible=True, value=timing_details),
)
else:
return (
None,
seed_or_error,
gr.update(visible=True, value=seed_or_error),
gr.update(visible=False),
)
run_button.click(
fn=handle_generation,
inputs=[
model_selection,
prompt,
negative_prompt,
use_negative_prompt,
seed,
guidance_scale,
num_frames,
height,
width,
randomize_seed,
],
outputs=[result, seed_output, error_output, timing_display],
concurrency_limit=20,
)
return demo
def main():
parser = argparse.ArgumentParser(description="FastVideo Gradio Frontend")
parser.add_argument("--backend_url", type=str, default="http://localhost:8000",
help="URL of the Ray Serve backend")
parser.add_argument("--t2v_model_paths", type=str,
default="FastVideo/FastWan2.1-T2V-1.3B-Diffusers,FastVideo/FastWan2.1-T2V-14B-Diffusers",
help="Comma separated list of paths to the T2V model(s)")
parser.add_argument("--host", type=str, default="0.0.0.0",
help="Host to bind to")
parser.add_argument("--port", type=int, default=7860,
help="Port to bind to")
args = parser.parse_args()
default_params = {}
model_paths = args.t2v_model_paths.split(",")
for model_path in model_paths:
default_params[model_path] = SamplingParam.from_pretrained(model_path)
demo = create_gradio_interface(args.backend_url, default_params)
print(f"Starting Gradio frontend at http://{args.host}:{args.port}")
print(f"Backend URL: {args.backend_url}")
print(f"T2V Models: {args.t2v_model_paths}")
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, FileResponse
import uvicorn
app = FastAPI()
@app.get("/logo.png")
def get_logo():
return FileResponse(
"fastvideo-logos/main/svg/full.svg",
media_type="image/svg+xml",
headers={
"Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*"
}
)
@app.get("/favicon.ico")
def get_favicon():
favicon_path = "fastvideo-logos/main/svg/icon-simple.svg"
if os.path.exists(favicon_path):
return FileResponse(
favicon_path,
media_type="image/svg+xml",
headers={
"Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*"
}
)
else:
raise HTTPException(status_code=404, detail="Favicon not found")
@app.get("/", response_class=HTMLResponse)
def index(request: Request):
base_url = str(request.base_url).rstrip('/')
return f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FastWan</title>
<meta name="title" content="FastWan">
<meta name="description" content="Make video generation go blurrrrrrr">
<meta name="keywords" content="FastVideo, video generation, AI, machine learning, FastWan">
<meta property="og:type" content="website">
<meta property="og:url" content="{base_url}/">
<meta property="og:title" content="FastWan">
<meta property="og:description" content="Make video generation go blurrrrrrr">
<meta property="og:image" content="{base_url}/logo.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:site_name" content="FastWan">
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="{base_url}/">
<meta property="twitter:title" content="FastWan">
<meta property="twitter:description" content="Make video generation go blurrrrrrr">
<meta property="twitter:image" content="{base_url}/logo.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon.ico">
<link rel="apple-touch-icon" href="/favicon.ico">
<style>
body, html {{
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}}
iframe {{
width: 100%;
height: 100vh;
border: none;
}}
</style>
</head>
<body>
<iframe src="/gradio" width="100%" height="100%" style="border: none;"></iframe>
</body>
</html>
"""
app = gr.mount_gradio_app(
app,
demo,
path="/gradio",
allowed_paths=[os.path.abspath("outputs"), os.path.abspath("fastvideo-logos")]
)
uvicorn.run(app, host=args.host, port=args.port)
if __name__ == "__main__":
main()
@@ -0,0 +1,379 @@
import time
import os
import torch
import base64
import io
from copy import deepcopy
from typing import Dict, Any, Optional, List
import signal
import sys
import ray
from ray import serve
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import numpy as np
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
import imageio
from ray.serve.handle import DeploymentHandle
from prometheus_client import Counter, Histogram, generate_latest
NUM_GPUS = 16
DEFAULT_FPS = 16
SEED_RANGE_MAX = 1_000_000
SUPPORTED_MODELS = [
"FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
"FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers",
]
MODEL_CONFIGS = {
"1.3B": {
"num_cpus": 2,
"text_encoder_cpu_offload": False,
"dit_cpu_offload": False,
"vae_cpu_offload": False,
"VSA_sparsity": 0.8,
},
"14B": {
"num_cpus": 16,
"text_encoder_cpu_offload": True,
"dit_cpu_offload": True,
"vae_cpu_offload": False,
"VSA_sparsity": 0.9,
}
}
class VideoGenerationRequest(BaseModel):
prompt: str
negative_prompt: Optional[str] = None
use_negative_prompt: bool = False
seed: int = 42
guidance_scale: float = 7.5
num_frames: int = 21
height: int = 448
width: int = 832
randomize_seed: bool = False
return_frames: bool = False
model_path: Optional[str] = None
class VideoGenerationResponse(BaseModel):
video_data: Optional[str] = None
seed: int
success: bool
error_message: Optional[str] = None
generation_time: Optional[float] = None
model_load_time: Optional[float] = None
inference_time: Optional[float] = None
encoding_time: Optional[float] = None
total_time: Optional[float] = None
stage_names: Optional[List[str]] = None
stage_execution_times: Optional[List[float]] = None
def encode_video_to_base64(frames: List[np.ndarray], fps: int = DEFAULT_FPS) -> str:
if not frames:
return ""
try:
buffer = io.BytesIO()
imageio.mimsave(buffer, frames, fps=fps, format="mp4")
buffer.seek(0)
video_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
return f"data:video/mp4;base64,{video_base64}"
except Exception as e:
print(f"Warning: Failed to encode video: {e}")
return ""
def setup_model_environment(model_path: str) -> None:
if "fullattn" in model_path.lower():
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "FLASH_ATTN"
else:
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "VIDEO_SPARSE_ATTN"
os.environ["FASTVIDEO_STAGE_LOGGING"] = "1"
def process_generation_result(result: Any) -> tuple[List[np.ndarray], float, List[str], List[float]]:
frames = result if isinstance(result, list) else result.get("frames", [])
generation_time = result.get("generation_time", 0.0) if isinstance(result, dict) else 0.0
logging_info = result.get("logging_info", None)
if logging_info:
stage_names = logging_info.get_execution_order()
stage_execution_times = [
logging_info.get_stage_info(stage_name).get("execution_time", 0.0)
for stage_name in stage_names
]
else:
stage_names = []
stage_execution_times = []
return frames, generation_time, stage_names, stage_execution_times
def prepare_sampling_params(video_request: VideoGenerationRequest, default_params: Any) -> Any:
params = deepcopy(default_params)
params.prompt = video_request.prompt
if video_request.use_negative_prompt:
params.negative_prompt = video_request.negative_prompt
params.seed = (video_request.seed if not video_request.randomize_seed
else torch.randint(0, SEED_RANGE_MAX, (1,)).item())
params.randomize_seed = video_request.randomize_seed
params.guidance_scale = video_request.guidance_scale
params.num_frames = video_request.num_frames
params.height = video_request.height
params.width = video_request.width
params.save_video = False
params.return_frames = False
return params
class BaseModelDeployment:
def __init__(self, model_path: str, output_path: str = "outputs"):
self.model_path = model_path
self.output_path = output_path
self.generator = None
self.default_params = None
os.makedirs(self.output_path, exist_ok=True)
setup_model_environment(self.model_path)
def _initialize_generator(self, config: Dict[str, Any]) -> None:
from fastvideo.entrypoints.video_generator import VideoGenerator
from fastvideo.configs.sample.base import SamplingParam
print(f"Initializing model: {self.model_path}")
self.generator = VideoGenerator.from_pretrained(
model_path=self.model_path,
num_gpus=1,
use_fsdp_inference=True,
text_encoder_cpu_offload=config["text_encoder_cpu_offload"],
dit_cpu_offload=config["dit_cpu_offload"],
vae_cpu_offload=config["vae_cpu_offload"],
VSA_sparsity=config["VSA_sparsity"],
enable_stage_verification=False,
)
self.default_params = SamplingParam.from_pretrained(self.model_path)
def generate_video(self, video_request: VideoGenerationRequest) -> VideoGenerationResponse:
total_start_time = time.time()
params = prepare_sampling_params(video_request, self.default_params)
inference_start_time = time.time()
result = self.generator.generate_video(
prompt=video_request.prompt,
sampling_param=params,
save_video=False,
return_frames=False,
)
inference_time = time.time() - inference_start_time
frames, generation_time, stage_names, stage_execution_times = process_generation_result(result)
encoding_start_time = time.time()
video_data = encode_video_to_base64(frames, fps=DEFAULT_FPS)
encoding_time = time.time() - encoding_start_time
total_time = time.time() - total_start_time
return VideoGenerationResponse(
video_data=video_data,
seed=params.seed,
success=True,
generation_time=generation_time,
inference_time=inference_time,
encoding_time=encoding_time,
total_time=total_time,
stage_names=stage_names,
stage_execution_times=stage_execution_times,
)
@serve.deployment(
ray_actor_options={"num_cpus": 2, "num_gpus": 1, "runtime_env": {"conda": "fv"}},
)
class T2VModelDeployment(BaseModelDeployment):
def __init__(self, t2v_model_path: str, output_path: str = "outputs"):
super().__init__(t2v_model_path, output_path)
self._initialize_generator(MODEL_CONFIGS["1.3B"])
print("✅ T2V model initialized successfully")
@serve.deployment(
ray_actor_options={"num_cpus": 16, "num_gpus": 1, "runtime_env": {"conda": "fv"}},
)
class T2V14BModelDeployment(BaseModelDeployment):
def __init__(self, t2v_14b_model_path: str, output_path: str = "outputs"):
super().__init__(t2v_14b_model_path, output_path)
# Override environment for 14B model
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "VIDEO_SPARSE_ATTN"
self._initialize_generator(MODEL_CONFIGS["14B"])
print("✅ T2V 14B model initialized successfully")
app = FastAPI()
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@serve.deployment(num_replicas=50, ray_actor_options={"num_cpus": 2})
@serve.ingress(app)
class FastVideoAPI:
def __init__(self, t2v_deployments: Dict[str, DeploymentHandle]):
self.t2v_deployments = t2v_deployments
# Initialize Prometheus metrics
self.request_count = Counter('fastvideo_requests_total', 'Total FastVideo requests', ['model_type', 'status'])
self.request_duration = Histogram('fastvideo_request_duration_seconds', 'FastVideo request duration', ['model_type'])
self.video_generation_time = Histogram('fastvideo_video_generation_seconds', 'Video generation time', ['model_type'])
def _get_model_name(self, model_path: Optional[str]) -> str:
return model_path.split('/')[-1] if model_path else "unknown"
def _record_metrics(self, model_name: str, status: str, duration: float, response: Optional[VideoGenerationResponse] = None) -> None:
self.request_count.labels(model_type=model_name, status=status).inc()
self.request_duration.labels(model_type=model_name).observe(duration)
if response and hasattr(response, 'generation_time') and response.generation_time:
self.video_generation_time.labels(model_type=model_name).observe(response.generation_time)
@app.post("/generate_video", response_model=VideoGenerationResponse)
@limiter.limit("10/minute")
async def generate_video(self, request: Request, video_request: VideoGenerationRequest) -> VideoGenerationResponse:
"""Route the request to the appropriate model deployment based on model_path."""
start_time = time.time()
model_name = self._get_model_name(video_request.model_path)
try:
if video_request.model_path not in self.t2v_deployments:
raise ValueError(f"Model {video_request.model_path} not found")
response_ref = self.t2v_deployments[video_request.model_path].generate_video.remote(video_request)
response = await response_ref
self._record_metrics(model_name, "success", time.time() - start_time, response)
return response
except Exception as e:
self._record_metrics(model_name, "error", time.time() - start_time)
return VideoGenerationResponse(
video_data=None,
seed=video_request.seed,
success=False,
error_message=str(e),
generation_time=0,
inference_time=0,
encoding_time=0,
total_time=0,
)
@app.get("/health")
@limiter.limit("10/minute")
async def health_check(self, request: Request) -> Dict[str, str]:
return {"status": "healthy"}
@app.get("/metrics")
async def metrics(self) -> Response:
return Response(generate_latest(), media_type="text/plain")
def validate_configuration(model_paths: List[str], replicas: List[int]) -> None:
assert len(model_paths) == len(replicas), "Number of models and replicas must match"
assert sum(replicas) <= NUM_GPUS, f"Total replicas ({sum(replicas)}) must be <= {NUM_GPUS}"
for model, replica_count in zip(model_paths, replicas):
assert model in SUPPORTED_MODELS, f"Model {model} not supported"
assert replica_count > 0, f"Replicas must be greater than 0"
def start_ray_serve(
*,
t2v_model_paths: str,
t2v_model_replicas: str,
output_path: str = "outputs",
host: str = "0.0.0.0",
port: int = 8000,
) -> None:
if not ray.is_initialized():
ray.init()
model_paths = t2v_model_paths.split(",")
replicas = [int(r) for r in t2v_model_replicas.split(",")]
validate_configuration(model_paths, replicas)
t2v_deps = {}
for model_path, replica_count in zip(model_paths, replicas):
t2v_dep = T2VModelDeployment.options(num_replicas=replica_count).bind(model_path, output_path)
t2v_deps[model_path] = t2v_dep
api = FastVideoAPI.bind(t2v_deps)
serve.run(api, route_prefix="/", name="fast_video")
print(f"Ray Serve backend started at http://{host}:{port}")
for model_path, replica_count in zip(model_paths, replicas):
print(f"T2V Model: {model_path} | Replicas: {replica_count}")
print(f"Health check: http://{host}:{port}/health")
print(f"Video generation endpoint: http://{host}:{port}/generate_video")
def setup_signal_handlers() -> None:
signal.signal(signal.SIGINT, lambda *_: sys.exit(0))
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="FastVideo Ray Serve Backend")
parser.add_argument("--t2v_model_paths",
type=str,
default="FastVideo/FastWan2.1-T2V-1.3B-Diffusers,FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers",
help="Comma separated list of paths to the T2V model(s)")
parser.add_argument("--t2v_model_replicas",
type=str,
default="4,4",
help="Comma separated list of number of replicas for the T2V model(s)")
parser.add_argument("--output_path",
type=str,
default="outputs",
help="Path to save generated videos")
parser.add_argument("--host",
type=str,
default="0.0.0.0",
help="Host to bind to")
parser.add_argument("--port",
type=int,
default=8000,
help="Port to bind to")
args = parser.parse_args()
model_paths = args.t2v_model_paths.split(",")
replicas = [int(r) for r in args.t2v_model_replicas.split(",")]
validate_configuration(model_paths, replicas)
start_ray_serve(
t2v_model_paths=args.t2v_model_paths,
t2v_model_replicas=args.t2v_model_replicas,
output_path=args.output_path,
host=args.host,
port=args.port,
)
setup_signal_handlers()
print("✅ FastVideo backend is running. Press Ctrl-C to stop.")
while True:
time.sleep(3600)
+3
View File
@@ -0,0 +1,3 @@
python examples/inference/gradio/start_ray_serve_app.py \
--t2v_model_paths "FastVideo/FastWan2.1-T2V-1.3B-Diffusers,FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers" \
--t2v_model_replicas "4,4"
@@ -0,0 +1,257 @@
"""
Startup script for FastVideo with Ray Serve backend and Gradio frontend.
This script starts both the backend and frontend services.
"""
import argparse
import os
import subprocess
import sys
import time
import threading
import signal
import requests
from pathlib import Path
from typing import Dict, Any, Optional
DEFAULT_BACKEND_HOST = "0.0.0.0"
DEFAULT_BACKEND_PORT = 8000
DEFAULT_FRONTEND_HOST = "0.0.0.0"
DEFAULT_FRONTEND_PORT = 7860
DEFAULT_OUTPUT_PATH = "outputs"
DEFAULT_T2V_MODELS = "FastVideo/FastWan2.1-T2V-1.3B-Diffusers,FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers"
DEFAULT_T2V_REPLICAS = "4,4"
HEALTH_CHECK_TIMEOUT = 5
HEALTH_CHECK_MAX_RETRIES = 100
HEALTH_CHECK_INTERVAL = 2
PROCESS_SHUTDOWN_TIMEOUT = 5
PROCESS_MONITOR_INTERVAL = 1
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
class ServiceManager:
def __init__(self, args: argparse.Namespace):
self.args = args
self.backend_process: Optional[subprocess.Popen] = None
self.frontend_process: Optional[subprocess.Popen] = None
self.backend_url = f"http://{args.backend_host}:{args.backend_port}"
def check_backend_health(self, max_retries: int = HEALTH_CHECK_MAX_RETRIES) -> bool:
health_url = f"{self.backend_url}/health"
for attempt in range(max_retries):
try:
response = requests.get(health_url, timeout=HEALTH_CHECK_TIMEOUT)
if response.status_code == 200:
print(f"✅ Backend is healthy at {self.backend_url}")
return True
except requests.exceptions.RequestException:
pass
if attempt < max_retries - 1:
print(f"⏳ Waiting for backend to start... ({attempt + 1}/{max_retries})")
time.sleep(HEALTH_CHECK_INTERVAL)
print(f"❌ Backend failed to start within {max_retries * HEALTH_CHECK_INTERVAL} seconds")
return False
def _create_monitor_thread(self, process: subprocess.Popen, service_name: str) -> threading.Thread:
def monitor():
if process.stdout:
for line in process.stdout:
print(f"[{service_name}] {line.rstrip()}")
thread = threading.Thread(target=monitor, daemon=True)
thread.start()
return thread
def _start_service(self, script_name: str, args_dict: Dict[str, Any], service_name: str) -> subprocess.Popen:
script_path = Path(__file__).parent / script_name
cmd = [sys.executable, str(script_path)]
for key, value in args_dict.items():
cmd.extend([f"--{key}", str(value)])
print(f"🚀 Starting {service_name}...")
print(f"Command: {' '.join(cmd)}")
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
bufsize=1
)
self._create_monitor_thread(process, service_name.upper())
return process
def start_backend(self) -> subprocess.Popen:
backend_args = {
"t2v_model_paths": self.args.t2v_model_paths,
"t2v_model_replicas": self.args.t2v_model_replicas,
"output_path": self.args.output_path,
"host": self.args.backend_host,
"port": self.args.backend_port
}
self.backend_process = self._start_service("ray_serve_backend.py", backend_args, "backend")
return self.backend_process
def start_frontend(self) -> subprocess.Popen:
frontend_args = {
"backend_url": self.backend_url,
"t2v_model_paths": self.args.t2v_model_paths,
"host": self.args.frontend_host,
"port": self.args.frontend_port
}
self.frontend_process = self._start_service("gradio_frontend.py", frontend_args, "frontend")
return self.frontend_process
def shutdown_services(self) -> None:
print("\n🛑 Shutting down services...")
processes = []
if self.frontend_process:
self.frontend_process.terminate()
processes.append(("frontend", self.frontend_process))
if self.backend_process:
self.backend_process.terminate()
processes.append(("backend", self.backend_process))
for name, process in processes:
try:
process.wait(timeout=PROCESS_SHUTDOWN_TIMEOUT)
print(f"✅ {name.capitalize()} stopped gracefully")
except subprocess.TimeoutExpired:
print(f"⚠️ Force killing {name} process...")
process.kill()
print("✅ All services stopped")
def monitor_processes(self) -> None:
if not self.backend_process or not self.frontend_process:
print("❌ Processes not properly initialized")
return
try:
while True:
if self.frontend_process.poll() is not None:
print("❌ Frontend process died unexpectedly")
break
if self.backend_process.poll() is not None:
print("❌ Backend process died unexpectedly")
break
time.sleep(PROCESS_MONITOR_INTERVAL)
except KeyboardInterrupt:
pass
self.shutdown_services()
def setup_signal_handlers(service_manager: ServiceManager) -> None:
def signal_handler(signum: int, frame: Any) -> None:
service_manager.shutdown_services()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
def print_startup_info(args: argparse.Namespace) -> None:
print("🎬 FastVideo Ray Serve App")
print("=" * 50)
print(f"T2V Models: {args.t2v_model_paths}")
print(f"T2V Model Replicas: {args.t2v_model_replicas}")
print(f"Output: {args.output_path}")
print(f"Backend: http://{args.backend_host}:{args.backend_port}")
print(f"Frontend: http://{args.frontend_host}:{args.frontend_port}")
print("=" * 50)
def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="FastVideo Ray Serve App")
parser.add_argument("--t2v_model_paths",
type=str,
default=DEFAULT_T2V_MODELS,
help="Comma separated list of paths to the T2V model(s)")
parser.add_argument("--t2v_model_replicas",
type=str,
default=DEFAULT_T2V_REPLICAS,
help="Comma separated list of number of replicas for the T2V model(s)")
parser.add_argument("--output_path",
type=str,
default=DEFAULT_OUTPUT_PATH,
help="Path to save generated videos")
parser.add_argument("--backend_host",
type=str,
default=DEFAULT_BACKEND_HOST,
help="Backend host to bind to")
parser.add_argument("--backend_port",
type=int,
default=DEFAULT_BACKEND_PORT,
help="Backend port to bind to")
parser.add_argument("--frontend_host",
type=str,
default=DEFAULT_FRONTEND_HOST,
help="Frontend host to bind to")
parser.add_argument("--frontend_port",
type=int,
default=DEFAULT_FRONTEND_PORT,
help="Frontend port to bind to")
parser.add_argument("--skip_backend_check",
action="store_true",
help="Skip backend health check")
return parser.parse_args()
def main() -> None:
args = parse_arguments()
os.makedirs(args.output_path, exist_ok=True)
print_startup_info(args)
service_manager = ServiceManager(args)
setup_signal_handlers(service_manager)
try:
service_manager.start_backend()
if not args.skip_backend_check:
if not service_manager.check_backend_health():
print("❌ Backend failed to start. Terminating...")
service_manager.shutdown_services()
sys.exit(1)
service_manager.start_frontend()
print("\n🎉 Both services are starting up!")
print(f"📺 Frontend will be available at: http://{args.frontend_host}:{args.frontend_port}")
print(f"🔧 Backend API will be available at: http://{args.backend_host}:{args.backend_port}")
print("\nPress Ctrl+C to stop both services...")
service_manager.monitor_processes()
except Exception as e:
print(f"❌ Unexpected error: {e}")
service_manager.shutdown_services()
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,14 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.691925)" fill="white"/>
<path d="M86.4467 27.9646L64.2778 97.1314H84.6732L91.7672 71.4156L124.445 71.4156L134.898 57.2275L96.201 57.2275L100.635 43.9262H146.844L159.278 27.9646H86.4467Z" fill="#0C0D6F"/>
<path d="M158.423 107.852H131.821L91.6798 160.17L84.5858 107.852H64.4273L73.2948 174.359H95.4637L158.423 107.852Z" fill="#0C0D6F"/>
<path d="M86.4467 27.9646L64.2778 97.1314H84.6732L91.7672 71.4156L124.445 71.4156L134.898 57.2275L96.201 57.2275L100.635 43.9262H146.844L159.278 27.9646H86.4467Z" stroke="#0C0D6F" stroke-width="1.77351"/>
<path d="M158.423 107.852H131.821L91.6798 160.17L84.5858 107.852H64.4273L73.2948 174.359H95.4637L158.423 107.852Z" stroke="#0C0D6F" stroke-width="1.77351"/>
<path d="M158.297 107.923H131.694L166.713 34.6841L97.1109 126.545H118.393L95.3374 174.429L158.297 107.923Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.77351" stroke-miterlimit="16"/>
<path d="M53.6055 107.773L62.473 174.279L67.7935 174.279L58.926 107.772L53.6055 107.773Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.77351"/>
<path d="M42.9648 107.773L51.8324 174.279L53.6059 174.279L44.7384 107.772L42.9648 107.773Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.77351"/>
<path d="M32.3232 107.772L41.1908 174.279L42.0775 174.279L33.21 107.772L32.3232 107.772Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="0.886754"/>
<path d="M53.6055 97.1314L75.7743 27.9646H81.0949L58.926 97.1314H53.6055Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.77351"/>
<path d="M42.9648 97.1314L65.1337 27.9646H66.9072L44.7384 97.1314H42.9648Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.77351"/>
<path d="M32.3232 97.1315L54.4921 27.9646H55.3789L33.21 97.1315H32.3232Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="0.886754"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,7 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.308105)" fill="white"/>
<path d="M49.8511 145.66L78.6319 55.8637H85.5394L56.7585 145.66H49.8511Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="2.30244"/>
<path d="M36.0376 145.66L64.8185 55.8637H67.1209L38.3401 145.66H36.0376Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="2.30244"/>
<path d="M22.2222 145.66L51.003 55.8637H52.1543L23.3734 145.66H22.2222Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.15122"/>
<path d="M92.4465 55.8648L63.666 145.66H90.144L99.3538 112.275H144.251L150.007 93.855H105.11L110.866 76.5868H173.032L178.788 55.8648H92.4465Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="2.30244"/>
</svg>

After

Width:  |  Height:  |  Size: 779 B

@@ -0,0 +1,8 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.308105)" fill="white"/>
<path d="M49.8511 145.66L78.6319 55.8637H85.5394L56.7585 145.66H49.8511Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="2.30244"/>
<path d="M36.0376 145.66L64.8185 55.8637H67.1209L38.3401 145.66H36.0376Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="2.30244"/>
<path d="M22.2222 145.66L51.003 55.8637H52.1543L23.3734 145.66H22.2222Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.15122"/>
<path d="M92.4465 55.8648L63.666 145.66H90.144L99.3538 112.275H144.251L150.007 93.855H105.11L110.866 76.5868H173.032L178.788 55.8648H92.4465Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="2.30244"/>
<path d="M131.799 93.8999H104.705L134.343 30.1061L69.483 112.866H91.1583L67.6768 161.635L131.799 93.8999Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.80627" stroke-miterlimit="16"/>
</svg>

After

Width:  |  Height:  |  Size: 966 B

@@ -0,0 +1,14 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.691925)" fill="white"/>
<path d="M89.2368 40.0858L70.8901 97.3273H87.769L93.64 76.0452L120.684 76.0453L129.334 64.3034L97.3093 64.3034L100.979 53.2954H139.22L149.511 40.0858H89.2368Z" fill="#0C0D6F"/>
<path d="M148.804 106.199H126.788L93.5676 149.498L87.6967 106.199H71.0138L78.3525 161.239H96.6991L148.804 106.199Z" fill="#0C0D6F"/>
<path d="M89.2368 40.0858L70.8901 97.3273H87.769L93.64 76.0452L120.684 76.0453L129.334 64.3034L97.3093 64.3034L100.979 53.2954H139.22L149.511 40.0858H89.2368Z" stroke="#0C0D6F" stroke-width="1.46773"/>
<path d="M148.804 106.199H126.788L93.5676 149.498L87.6967 106.199H71.0138L78.3525 161.239H96.6991L148.804 106.199Z" stroke="#0C0D6F" stroke-width="1.46773"/>
<path d="M148.699 106.258H126.683L155.664 45.6468L98.062 121.669H115.675L96.5942 161.298L148.699 106.258Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.46773" stroke-miterlimit="16"/>
<path d="M62.0576 106.134L69.3963 161.174L73.7995 161.174L66.4608 106.134L62.0576 106.134Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.46773"/>
<path d="M53.2515 106.134L60.5901 161.174L62.0579 161.174L54.7192 106.134L53.2515 106.134Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.46773"/>
<path d="M44.4443 106.134L51.783 161.174L52.5169 161.174L45.1782 106.134L44.4443 106.134Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="0.733866"/>
<path d="M62.0576 97.3273L80.4043 40.0858H84.8075L66.4608 97.3273H62.0576Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.46773"/>
<path d="M53.2515 97.3273L71.5981 40.0858H73.0658L54.7192 97.3273H53.2515Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="1.46773"/>
<path d="M44.4443 97.3274L62.791 40.0859H63.5248L45.1782 97.3274H44.4443Z" fill="#0C0D6F" stroke="#0C0D6F" stroke-width="0.733866"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+18
View File
@@ -0,0 +1,18 @@
<svg width="252" height="105" viewBox="0 0 252 105" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M89.4843 55.5457H101.361L87.7028 101H74.638L89.4843 55.5457Z" fill="#356CFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M96.0167 1.00057H112.645L118.583 48.273H104.924L103.737 39.7882H79.9827L67.5117 55.5457H85.3273L43.1638 101H28.3174L22.3789 55.5457H33.6621L38.4129 91.3031L58.604 68.2729H44.3515L96.0167 1.00057ZM100.768 13.1217L87.7028 29.4852H103.143L100.768 13.1217Z" fill="#356CFF"/>
<path d="M37.2252 1.00057L22.3789 48.273H36.0375L40.7884 30.6974L62.6727 30.6974L69.6727 21.0005L43.7576 21.0004L46.7269 11.9096L77.6727 11.9096L86 1.00057L37.2252 1.00057Z" fill="#356CFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.488 55.5457L94.2351 101C94.2351 101 105.518 101 120.959 101C136.399 101 144.078 93.0133 148.276 79.788C152.432 68.0157 153.027 55.5457 136.399 55.5457C119.771 55.5457 108.488 55.5457 108.488 55.5457ZM109.081 90.697L116.802 65.8487C116.802 65.8487 120.959 65.8487 132.242 65.8487C143.525 65.8487 137.586 78.5759 135.211 84.0304C133.307 88.4021 127.491 90.697 122.74 90.697C117.989 90.697 109.081 90.697 109.081 90.697Z" fill="#356CFF"/>
<path d="M173.188 1.00056L168.625 11.9096C168.625 11.9096 149.386 11.9092 142.525 11.9095C135.664 11.9098 136.586 20.3944 141.337 20.3944H159.747C168.654 20.3944 166.961 33.6899 163.904 38.5761C160.467 44.0675 157.371 48.273 148.463 48.273L125.188 48.273L124 37.97L147.87 37.97C153.808 37.97 156.184 29.4852 151.433 29.4852H131.836C120.142 29.4852 125.897 1.00043 141.337 1.00043L173.188 1.00056Z" fill="#356CFF"/>
<path d="M179.938 1.00056L175.688 11.9096L191.221 11.9096L179.938 48.273H192.409L203.692 11.9096L219.132 11.9095L223.289 1.00043L179.938 1.00056Z" fill="#356CFF"/>
<path d="M161.341 55.5457H202.845L198.5 65.8487H169.654L167.279 73.7268H188.5L184.749 82.8177H164.31L161.934 90.697H190.251L186.624 101H146.494L161.341 55.5457Z" fill="#356CFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M230.821 54.9391C255.169 54.9391 251.776 67.0602 249.231 77.9692C246.686 88.8783 240.917 101 217.757 101C194.596 101 195.606 88.8783 199.347 77.9692C203.089 67.0602 206.473 54.9391 230.821 54.9391ZM237.948 77.9692C239.984 70.6965 240.917 65.242 228.446 65.242C215.975 65.242 211.818 71.9087 210.037 77.9692C208.255 84.0298 208.255 91.3025 219.538 91.3025C230.821 91.3025 235.911 85.2419 237.948 77.9692Z" fill="#356CFF"/>
<path d="M173.188 1.00056L168.625 11.9096C168.625 11.9096 149.386 11.9092 142.525 11.9095C135.664 11.9098 136.586 20.3944 141.337 20.3944M173.188 1.00056C173.188 1.00056 156.777 1.00043 141.337 1.00043M173.188 1.00056L141.337 1.00043M141.337 20.3944C146.088 20.3944 150.839 20.3944 159.747 20.3944M141.337 20.3944H159.747M159.747 20.3944C168.654 20.3944 166.961 33.6899 163.904 38.5761C160.467 44.0675 157.371 48.273 148.463 48.273M148.463 48.273C139.556 48.273 125.188 48.273 125.188 48.273M148.463 48.273L125.188 48.273M125.188 48.273L124 37.97M124 37.97C124 37.97 141.931 37.97 147.87 37.97M124 37.97L147.87 37.97M147.87 37.97C153.808 37.97 156.184 29.4852 151.433 29.4852M151.433 29.4852C146.682 29.4852 138.962 29.4852 131.836 29.4852M151.433 29.4852H131.836M131.836 29.4852C120.142 29.4852 125.897 1.00043 141.337 1.00043M37.2252 1.00057L22.3789 48.273H36.0375L40.7884 30.6974L62.6727 30.6974L69.6727 21.0005L43.7576 21.0004L46.7269 11.9096L77.6727 11.9096L86 1.00057L37.2252 1.00057ZM96.0167 1.00057H112.645L118.583 48.273H104.924L103.737 39.7882H79.9827L67.5117 55.5457H85.3273L43.1638 101H28.3174L22.3789 55.5457H33.6621L38.4129 91.3031L58.604 68.2729H44.3515L96.0167 1.00057ZM87.7028 29.4852L100.768 13.1217L103.143 29.4852H87.7028ZM89.4843 55.5457H101.361L87.7028 101H74.638L89.4843 55.5457ZM108.488 55.5457L94.2351 101C94.2351 101 105.518 101 120.959 101C136.399 101 144.078 93.0133 148.276 79.788C152.432 68.0157 153.027 55.5457 136.399 55.5457C119.771 55.5457 108.488 55.5457 108.488 55.5457ZM116.802 65.8487L109.081 90.697C109.081 90.697 117.989 90.697 122.74 90.697C127.491 90.697 133.307 88.4021 135.211 84.0304C137.586 78.5759 143.525 65.8487 132.242 65.8487C120.959 65.8487 116.802 65.8487 116.802 65.8487ZM179.938 1.00056L175.688 11.9096L191.221 11.9096L179.938 48.273H192.409L203.692 11.9096L219.132 11.9095L223.289 1.00043L179.938 1.00056ZM161.341 55.5457H202.845L198.5 65.8487H169.654L167.279 73.7268H188.5L184.749 82.8177H164.31L161.934 90.697H190.251L186.624 101H146.494L161.341 55.5457ZM230.821 54.9391C255.169 54.9391 251.776 67.0602 249.231 77.9692C246.686 88.8783 240.917 101 217.757 101C194.596 101 195.606 88.8783 199.347 77.9692C203.089 67.0602 206.473 54.9391 230.821 54.9391ZM228.446 65.242C240.917 65.242 239.984 70.6965 237.948 77.9692C235.911 85.2419 230.821 91.3025 219.538 91.3025C208.255 91.3025 208.255 84.0298 210.037 77.9692C211.818 71.9087 215.975 65.242 228.446 65.242Z" stroke="#356CFF" stroke-width="1.18771"/>
<path d="M15.2524 55.5451L21.191 100.999L24.7541 100.999L18.8156 55.5451L15.2524 55.5451Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.18771"/>
<path d="M8.12646 55.5451L14.065 100.999L15.2527 100.999L9.31417 55.5451L8.12646 55.5451Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.18771"/>
<path d="M1 55.5451L6.93853 100.999L7.53239 100.999L1.59385 55.5451L1 55.5451Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.593853"/>
<path d="M15.2524 48.2724L30.0988 1H33.6619L18.8156 48.2724H15.2524Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.18771"/>
<path d="M8.12646 48.2724L22.9728 1H24.1605L9.31417 48.2724H8.12646Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.18771"/>
<path d="M1 48.2724L15.8463 1H16.4402L1.59385 48.2724H1Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.593853"/>
<path d="M85.3271 55.5457H67.5116L87 12.7363L44.3513 68.2729H58.6038L43.1636 101L85.3271 55.5457Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.18771" stroke-miterlimit="16"/>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

+14
View File
@@ -0,0 +1,14 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.691925)" fill="black"/>
<path d="M86.4467 27.9646L64.2778 97.1314H84.6732L91.7672 71.4156L124.445 71.4156L134.898 57.2275L96.201 57.2275L100.635 43.9262H146.844L159.278 27.9646H86.4467Z" fill="#356CFF"/>
<path d="M158.423 107.852H131.821L91.6798 160.17L84.5858 107.852H64.4273L73.2948 174.359H95.4637L158.423 107.852Z" fill="#356CFF"/>
<path d="M86.4467 27.9646L64.2778 97.1314H84.6732L91.7672 71.4156L124.445 71.4156L134.898 57.2275L96.201 57.2275L100.635 43.9262H146.844L159.278 27.9646H86.4467Z" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M158.423 107.852H131.821L91.6798 160.17L84.5858 107.852H64.4273L73.2948 174.359H95.4637L158.423 107.852Z" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M158.297 107.923H131.694L166.713 34.6841L97.1109 126.545H118.393L95.3374 174.429L158.297 107.923Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.77351" stroke-miterlimit="16"/>
<path d="M53.6055 107.773L62.473 174.279L67.7935 174.279L58.926 107.772L53.6055 107.773Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M42.9648 107.773L51.8324 174.279L53.6059 174.279L44.7384 107.772L42.9648 107.773Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M32.3232 107.772L41.1908 174.279L42.0775 174.279L33.21 107.772L32.3232 107.772Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.886754"/>
<path d="M53.6055 97.1314L75.7743 27.9646H81.0949L58.926 97.1314H53.6055Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M42.9648 97.1314L65.1337 27.9646H66.9072L44.7384 97.1314H42.9648Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M32.3232 97.1315L54.4921 27.9646H55.3789L33.21 97.1315H32.3232Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.886754"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,7 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.308105)" fill="black"/>
<path d="M49.8511 145.66L78.6319 55.8637H85.5394L56.7585 145.66H49.8511Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M36.0376 145.66L64.8185 55.8637H67.1209L38.3401 145.66H36.0376Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M22.2222 145.66L51.003 55.8637H52.1543L23.3734 145.66H22.2222Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.15122"/>
<path d="M92.4465 55.8648L63.666 145.66H90.144L99.3538 112.275H144.251L150.007 93.855H105.11L110.866 76.5868H173.032L178.788 55.8648H92.4465Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
</svg>

After

Width:  |  Height:  |  Size: 779 B

@@ -0,0 +1,8 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.308105)" fill="black"/>
<path d="M49.8511 145.66L78.6319 55.8637H85.5394L56.7585 145.66H49.8511Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M36.0376 145.66L64.8185 55.8637H67.1209L38.3401 145.66H36.0376Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M22.2222 145.66L51.003 55.8637H52.1543L23.3734 145.66H22.2222Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.15122"/>
<path d="M92.4465 55.8648L63.666 145.66H90.144L99.3538 112.275H144.251L150.007 93.855H105.11L110.866 76.5868H173.032L178.788 55.8648H92.4465Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M131.799 93.8999H104.705L134.343 30.1061L69.483 112.866H91.1583L67.6768 161.635L131.799 93.8999Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.80627" stroke-miterlimit="16"/>
</svg>

After

Width:  |  Height:  |  Size: 966 B

@@ -0,0 +1,7 @@
<svg width="160" height="144" viewBox="0 0 160 144" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.8511 122.66L57.6319 32.8637H64.5394L35.7585 122.66H28.8511Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M15.0376 122.66L43.8185 32.8637H46.1209L17.3401 122.66H15.0376Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M1.22217 122.66L30.003 32.8637H31.1543L2.3734 122.66H1.22217Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.15122"/>
<path d="M71.4465 32.8648L42.666 122.66H69.144L78.3538 89.2745H123.251L129.007 70.855H84.1099L89.866 53.5868H152.032L157.788 32.8648H71.4465Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M110.799 70.8999H83.7053L113.343 7.10608L48.483 89.8657H70.1583L46.6768 138.635L110.799 70.8999Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.80627" stroke-miterlimit="16"/>
</svg>

After

Width:  |  Height:  |  Size: 885 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="160" height="93" viewBox="0 0 160 93" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.8511 91.66L57.6319 1.86368H64.5394L35.7585 91.66H28.8511Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M15.0376 91.66L43.8185 1.86368H46.1209L17.3401 91.66H15.0376Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
<path d="M1.22217 91.66L30.003 1.86366H31.1543L2.3734 91.66H1.22217Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.15122"/>
<path d="M71.4465 1.86483L42.666 91.6599H69.144L78.3538 58.2746H123.251L129.007 39.855H84.1099L89.866 22.5868H152.032L157.788 1.86483H71.4465Z" fill="#356CFF" stroke="#356CFF" stroke-width="2.30244"/>
</svg>

After

Width:  |  Height:  |  Size: 691 B

+14
View File
@@ -0,0 +1,14 @@
<svg width="200" height="201" viewBox="0 0 200 201" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" transform="translate(0 0.691925)" fill="black"/>
<path d="M89.2368 40.0858L70.8901 97.3273H87.769L93.64 76.0452L120.684 76.0453L129.334 64.3034L97.3093 64.3034L100.979 53.2954H139.22L149.511 40.0858H89.2368Z" fill="#356CFF"/>
<path d="M148.804 106.199H126.788L93.5676 149.498L87.6967 106.199H71.0138L78.3525 161.239H96.6991L148.804 106.199Z" fill="#356CFF"/>
<path d="M89.2368 40.0858L70.8901 97.3273H87.769L93.64 76.0452L120.684 76.0453L129.334 64.3034L97.3093 64.3034L100.979 53.2954H139.22L149.511 40.0858H89.2368Z" stroke="#356CFF" stroke-width="1.46773"/>
<path d="M148.804 106.199H126.788L93.5676 149.498L87.6967 106.199H71.0138L78.3525 161.239H96.6991L148.804 106.199Z" stroke="#356CFF" stroke-width="1.46773"/>
<path d="M148.699 106.258H126.683L155.664 45.6468L98.062 121.669H115.675L96.5942 161.298L148.699 106.258Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.46773" stroke-miterlimit="16"/>
<path d="M62.0576 106.134L69.3963 161.174L73.7995 161.174L66.4608 106.134L62.0576 106.134Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.46773"/>
<path d="M53.2515 106.134L60.5901 161.174L62.0579 161.174L54.7192 106.134L53.2515 106.134Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.46773"/>
<path d="M44.4443 106.134L51.783 161.174L52.5169 161.174L45.1782 106.134L44.4443 106.134Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.733866"/>
<path d="M62.0576 97.3273L80.4043 40.0858H84.8075L66.4608 97.3273H62.0576Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.46773"/>
<path d="M53.2515 97.3273L71.5981 40.0858H73.0658L54.7192 97.3273H53.2515Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.46773"/>
<path d="M44.4443 97.3274L62.791 40.0859H63.5248L45.1782 97.3274H44.4443Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.733866"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg width="141" height="153" viewBox="0 0 141 153" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M55.4467 0.964615L33.2778 70.1314H53.6732L60.7672 44.4156L93.4453 44.4156L103.898 30.2275L65.201 30.2275L69.6347 16.9262H115.844L128.278 0.964615H55.4467Z" fill="#356CFF"/>
<path d="M127.423 80.852H100.821L60.6798 133.17L53.5858 80.852H33.4273L42.2948 147.359H64.4637L127.423 80.852Z" fill="#356CFF"/>
<path d="M55.4467 0.964615L33.2778 70.1314H53.6732L60.7672 44.4156L93.4453 44.4156L103.898 30.2275L65.201 30.2275L69.6347 16.9262H115.844L128.278 0.964615H55.4467Z" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M127.423 80.852H100.821L60.6798 133.17L53.5858 80.852H33.4273L42.2948 147.359H64.4637L127.423 80.852Z" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M127.297 80.9227H100.694L135.713 7.68411L66.1109 99.5445H87.393L64.3374 147.429L127.297 80.9227Z" fill="#FDC717" stroke="#FDC717" stroke-width="1.77351" stroke-miterlimit="16"/>
<path d="M22.6055 80.7725L31.473 147.279L36.7935 147.279L27.926 80.7725L22.6055 80.7725Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M11.9648 80.7725L20.8324 147.279L22.6059 147.279L13.7384 80.7725L11.9648 80.7725Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M1.32324 80.7725L10.1908 147.279L11.0775 147.279L2.21 80.7725L1.32324 80.7725Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.886754"/>
<path d="M22.6055 70.1314L44.7743 0.964615H50.0949L27.926 70.1314H22.6055Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M11.9648 70.1314L34.1337 0.964615H35.9072L13.7384 70.1314H11.9648Z" fill="#356CFF" stroke="#356CFF" stroke-width="1.77351"/>
<path d="M1.32324 70.1315L23.4921 0.964645H24.3789L2.21 70.1315H1.32324Z" fill="#356CFF" stroke="#356CFF" stroke-width="0.886754"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+3
View File
@@ -85,6 +85,9 @@ class PipelineConfig:
# DMD parameters
dmd_denoising_steps: list[int] | None = field(default=None)
# Wan2.2 TI2V parameters
ti2v_task: bool = False
# Compilation
# enable_torch_compile: bool = False
+3 -1
View File
@@ -31,7 +31,9 @@ PIPE_NAME_TO_CONFIG: dict[str, type[PipelineConfig]] = {
"FastVideo/FastWan2.2-TI2V-5B-Diffusers": FastWan2_2_TI2V_5B_Config,
"FastVideo/stepvideo-t2v-diffusers": StepVideoT2VConfig,
"FastVideo/Wan2.1-VSA-T2V-14B-720P-Diffusers": WanT2V720PConfig,
"Wan-AI/Wan2.2-TI2V-5B-Diffusers": WanT2V720PConfig
"Wan-AI/Wan2.2-TI2V-5B-Diffusers": WanT2V720PConfig,
"Wan-AI/Wan2.2-T2V-A14B-Diffusers": WanT2V480PConfig,
"Wan-AI/Wan2.2-I2V-A14B-Diffusers": WanI2V480PConfig,
# Add other specific weight variants
}
+15 -10
View File
@@ -6,12 +6,19 @@ from typing import Any
from fastvideo.configs.sample.hunyuan import (FastHunyuanSamplingParam,
HunyuanSamplingParam)
from fastvideo.configs.sample.stepvideo import StepVideoT2VSamplingParam
from fastvideo.configs.sample.wan import (FastWanT2V480PConfig,
Wan2_2_TI2V_5B_SamplingParam,
WanI2V_14B_480P_SamplingParam,
WanI2V_14B_720P_SamplingParam,
WanT2V_1_3B_SamplingParam,
WanT2V_14B_SamplingParam)
# isort: off
from fastvideo.configs.sample.wan import (
FastWanT2V480PConfig,
Wan2_2_I2V_A14B_SamplingParam,
Wan2_2_T2V_A14B_SamplingParam,
Wan2_2_TI2V_5B_SamplingParam,
WanI2V_14B_480P_SamplingParam,
WanI2V_14B_720P_SamplingParam,
WanT2V_1_3B_SamplingParam,
WanT2V_14B_SamplingParam,
)
# isort: on
from fastvideo.logger import init_logger
from fastvideo.utils import (maybe_download_model_index,
verify_model_config_and_directory)
@@ -29,10 +36,8 @@ SAMPLING_PARAM_REGISTRY: dict[str, Any] = {
"FastVideo/FastWan2.1-T2V-1.3B-Diffusers": FastWanT2V480PConfig,
"Wan-AI/Wan2.2-TI2V-5B-Diffusers": Wan2_2_TI2V_5B_SamplingParam,
"FastVideo/FastWan2.2-TI2V-5B-Diffusers": Wan2_2_TI2V_5B_SamplingParam,
# "Wan-AI/Wan2.2-T2V-A14B-Diffusers":
# Wan2_2_T2V_A14B_SamplingParam,
# "Wan-AI/Wan2.2-I2V-A14B-Diffusers":
# Wan2_2_I2V_A14B_SamplingParam,
"Wan-AI/Wan2.2-T2V-A14B-Diffusers": Wan2_2_T2V_A14B_SamplingParam,
"Wan-AI/Wan2.2-I2V-A14B-Diffusers": Wan2_2_I2V_A14B_SamplingParam,
# Add other specific weight variants
}
+8 -2
View File
@@ -129,9 +129,15 @@ class Wan2_2_TI2V_5B_SamplingParam(Wan2_2_Base_SamplingParam):
@dataclass
class Wan2_2_T2V_A14B_SamplingParam(Wan2_2_Base_SamplingParam):
pass
guidance_scale: float = 4.0
guidance_scale_2: float = 3.0
num_inference_steps: int = 40
fps: int = 16
@dataclass
class Wan2_2_I2V_A14B_SamplingParam(Wan2_2_Base_SamplingParam):
pass
guidance_scale: float = 3.5
guidance_scale_2: float = 3.5
num_inference_steps: int = 40
fps: int = 16
+10 -3
View File
@@ -9,6 +9,7 @@ diffusion models.
import math
import os
import time
from copy import deepcopy
from typing import Any
import imageio
@@ -202,6 +203,8 @@ class VideoGenerator:
if sampling_param is None:
sampling_param = SamplingParam.from_pretrained(
fastvideo_args.model_path)
else:
sampling_param = deepcopy(sampling_param)
kwargs["prompt"] = prompt
sampling_param.update(kwargs)
@@ -275,6 +278,7 @@ class VideoGenerator:
width: {target_width}
video_length: {sampling_param.num_frames}
prompt: {prompt}
image_path: {sampling_param.image_path}
neg_prompt: {sampling_param.negative_prompt}
seed: {sampling_param.seed}
infer_steps: {sampling_param.num_inference_steps}
@@ -303,8 +307,9 @@ class VideoGenerator:
# Run inference
start_time = time.perf_counter()
output_batch = self.executor.execute_forward(batch, fastvideo_args)
samples = output_batch
output_batch = self.executor.execute_forward(batch, fastvideo_args)
samples = output_batch.output
logging_info = output_batch.logging_info
gen_time = time.perf_counter() - start_time
logger.info("Generated successfully in %.2f seconds", gen_time)
@@ -334,9 +339,11 @@ class VideoGenerator:
else:
return {
"samples": samples,
"frames": frames,
"prompts": prompt,
"size": (target_height, target_width, batch.num_frames),
"generation_time": gen_time
"generation_time": gen_time,
"logging_info": logging_info,
}
def set_lora_adapter(self,
+1
View File
@@ -82,6 +82,7 @@ class WanTimeTextImageEmbedding(nn.Module):
encoder_hidden_states: torch.Tensor,
encoder_hidden_states_image: torch.Tensor | None = None,
):
logger.info(f"WTF timestep shape: {timestep.shape}")
temb = self.time_embedder(timestep)
timestep_proj = self.time_modulation(temb)
@@ -61,6 +61,7 @@ class WanPipeline(LoRAPipeline, ComposedPipelineBase):
self.add_stage(stage_name="denoising_stage",
stage=DenoisingStage(
transformer=self.get_module("transformer"),
transformer_2=self.get_module("transformer_2", None),
scheduler=self.get_module("scheduler"),
pipeline=self))
@@ -16,6 +16,41 @@ import torch
from fastvideo.attention import AttentionMetadata
from fastvideo.configs.sample.teacache import TeaCacheParams, WanTeaCacheParams
from collections import OrderedDict
from typing import Any, Dict, List
import time
class PipelineLoggingInfo:
"""Simple approach using OrderedDict to track stage metrics."""
def __init__(self):
# OrderedDict preserves insertion order and allows easy access
self.stages: OrderedDict[str, Dict[str, Any]] = OrderedDict()
def add_stage_execution_time(self, stage_name: str, execution_time: float):
"""Add execution time for a stage."""
if stage_name not in self.stages:
self.stages[stage_name] = {}
self.stages[stage_name]['execution_time'] = execution_time
self.stages[stage_name]['timestamp'] = time.time()
def add_stage_metric(self, stage_name: str, metric_name: str, value: Any):
"""Add any metric for a stage."""
if stage_name not in self.stages:
self.stages[stage_name] = {}
self.stages[stage_name][metric_name] = value
def get_stage_info(self, stage_name: str) -> Dict[str, Any]:
"""Get all info for a specific stage."""
return self.stages.get(stage_name, {})
def get_execution_order(self) -> List[str]:
"""Get stages in execution order."""
return list(self.stages.keys())
def get_total_execution_time(self) -> float:
"""Get total pipeline execution time."""
return sum(stage.get('execution_time', 0) for stage in self.stages.values())
@dataclass
@@ -128,6 +163,9 @@ class ForwardBatch:
# VSA parameters
VSA_sparsity: float = 0.0
# Logging info
logging_info: PipelineLoggingInfo = field(default_factory=PipelineLoggingInfo)
def __post_init__(self):
"""Initialize dependent fields after dataclass initialization."""
+3 -3
View File
@@ -65,7 +65,7 @@ class _PipelineRegistry:
arch = _PIPELINE_NAME_TO_ARCHITECTURE_NAME[pipeline_name_in_config]
return set(self.pipelines[pipeline_type.value][arch].keys())
def _load_preprocessing_pipeline_cls(
def _load_preprocess_pipeline_cls(
self, workload_type: WorkloadType,
arch: str) -> type[ComposedPipelineBase] | None:
if workload_type == WorkloadType.I2V:
@@ -90,7 +90,7 @@ class _PipelineRegistry:
return None
if pipeline_type == PipelineType.PREPROCESS:
return self._load_preprocessing_pipeline_cls(workload_type, arch)
return self._load_preprocess_pipeline_cls(workload_type, arch)
elif pipeline_type == PipelineType.BASIC:
return self.pipelines[
pipeline_type.value][arch][pipeline_name_in_config]
@@ -131,7 +131,7 @@ def import_pipeline_classes(
Import pipeline classes based on the pipeline type and workload type.
Args:
pipeline_types: The pipeline types to load (basic, preprocessing, training).
pipeline_types: The pipeline types to load (basic, preprocess, training).
If None, loads all types.
Returns:
+1
View File
@@ -155,6 +155,7 @@ class PipelineStage(ABC):
execution_time = time.perf_counter() - start_time
logger.info("[%s] Execution completed in %s ms", stage_name,
execution_time * 1000)
batch.logging_info.add_stage_execution_time(stage_name, execution_time)
except Exception as e:
execution_time = time.perf_counter() - start_time
logger.error("[%s] Error during execution after %s ms: %s",
+70 -3
View File
@@ -30,7 +30,7 @@ from fastvideo.pipelines.stages.base import PipelineStage
from fastvideo.pipelines.stages.validators import StageValidators as V
from fastvideo.pipelines.stages.validators import VerificationResult
from fastvideo.platforms import AttentionBackendEnum
from fastvideo.utils import dict_to_3d_list
from fastvideo.utils import dict_to_3d_list, masks_like
try:
from fastvideo.attention.backends.sliding_tile_attn import (
@@ -57,10 +57,11 @@ class DenoisingStage(PipelineStage):
the initial noise into the final output.
"""
def __init__(self, transformer, scheduler, pipeline=None) -> None:
def __init__(self, transformer, scheduler, vae=None, pipeline=None) -> None:
super().__init__()
self.transformer = transformer
self.scheduler = scheduler
self.vae = vae
self.pipeline = weakref.ref(pipeline) if pipeline else None
attn_head_size = self.transformer.hidden_size // self.transformer.num_attention_heads
self.attn_backend = get_attn_backend(
@@ -184,8 +185,43 @@ class DenoisingStage(PipelineStage):
assert neg_prompt_embeds is not None
assert torch.isnan(neg_prompt_embeds[0]).sum() == 0
latent_model_input = latents.to(target_dtype)
assert latent_model_input.shape[0] == 1, "only support batch size 1"
if fastvideo_args.pipeline_config.ti2v_task and batch.pil_image is not None:
logger.info("===========Using TI2V task===========")
# TI2V directly replaces the first frame of the latent with
# the image latent instead of appending along the channel dim
assert batch.image_latent is None, "TI2V task should not have image latents"
assert self.vae is not None, "VAE is not provided for TI2V task"
z = self.vae.encode(batch.pil_image).mean.float()
logger.info(f"z shape: {z.shape}")
logger.info(f"latent_model_input shape: {latent_model_input.shape}")
latent_model_input = latent_model_input.squeeze(0)
mask1, mask2 = masks_like([latent_model_input], zero=True)
# logger.info(f"mask1 shape: {mask1.shape}")
# logger.info(f"mask2 shape: {mask2.shape}")
latent_model_input = (1. -
mask2[0]) * z + mask2[0] * latent_model_input
# latent_model_input = latent_model_input.unsqueeze(0)
latent_model_input = latent_model_input.to(get_local_torch_device())
latents = latent_model_input
F = batch.num_frames
temporal_scale = fastvideo_args.pipeline_config.vae_config.arch_config.scale_factor_temporal
spatial_scale = fastvideo_args.pipeline_config.vae_config.arch_config.scale_factor_spatial
patch_size = fastvideo_args.pipeline_config.dit_config.arch_config.patch_size
seq_len = ((F - 1) // temporal_scale +
1) * (batch.height // spatial_scale) * (
batch.width // spatial_scale) // (patch_size[1] *
patch_size[2])
import math
seq_len = int(math.ceil(seq_len / sp_world_size)) * sp_world_size
logger.info("latents shape: %s", latents.shape)
# Run denoising loop
with self.progress_bar(total=num_inference_steps) as progress_bar:
# logger.info(f"seq_len: {seq_len}")
logger.info(f"init timesteps: {timesteps}")
for i, t in enumerate(timesteps):
# Skip if interrupted
if hasattr(self, 'interrupt') and self.interrupt:
@@ -194,15 +230,42 @@ class DenoisingStage(PipelineStage):
# Expand latents for I2V
latent_model_input = latents.to(target_dtype)
if batch.image_latent is not None:
assert not fastvideo_args.pipeline_config.ti2v_task, "image latents should not be provided for TI2V task"
latent_model_input = torch.cat(
[latent_model_input, batch.image_latent],
dim=1).to(target_dtype)
if fastvideo_args.pipeline_config.ti2v_task and batch.pil_image is not None:
logger.info(f"before ti2v timestep: {t}")
timestep = [t]
timestep = torch.stack(timestep).to(
get_local_torch_device())
logger.info(f"mask2 shape: {mask2[0].shape}")
logger.info(f"mask[0][0] shape: {mask2[0][0].shape}")
logger.info(
f"mask[0][0][:, ::2, ::2] shape: {mask2[0][0][:, ::2, ::2].shape}"
)
temp_ts = (mask2[0][0][:, ::2, ::2] * timestep).flatten()
logger.info(f"temp_ts: {temp_ts}")
logger.info(f"temp_ts shape: {temp_ts.shape}")
temp_ts = torch.cat([
temp_ts,
temp_ts.new_ones(seq_len - temp_ts.size(0)) * timestep
])
# timestep = temp_ts.unsqueeze(0)
timestep = temp_ts
logger.info(f"after ti2v timestep: {timestep}")
t = timestep
# else:
t_expand = t.repeat(latent_model_input.shape[0])
# logger.info(f"t_expand shape: {t_expand.shape}")
# logger.info(f"t_expand: {t_expand}")
assert torch.isnan(latent_model_input).sum() == 0
latent_model_input = self.scheduler.scale_model_input(
latent_model_input, t)
# Prepare inputs for transformer
t_expand = t.repeat(latent_model_input.shape[0])
guidance_expand = (
torch.tensor(
[fastvideo_args.pipeline_config.embedded_cfg_scale] *
@@ -303,6 +366,10 @@ class DenoisingStage(PipelineStage):
latents,
**extra_step_kwargs,
return_dict=False)[0]
if fastvideo_args.pipeline_config.ti2v_task and batch.pil_image is not None:
latents = latents.squeeze(0)
latents = (1. - mask2[0]) * z + mask2[0] * latents
# latents = latents.unsqueeze(0)
# Update progress bar
if i == len(timesteps) - 1 or (
@@ -12,6 +12,9 @@ from fastvideo.pipelines.pipeline_batch_info import ForwardBatch
from fastvideo.pipelines.stages.base import PipelineStage
from fastvideo.pipelines.stages.validators import (StageValidators,
VerificationResult)
from fastvideo.utils import best_output_size
from PIL import Image
import torchvision.transforms.functional as TF
logger = init_logger(__name__)
@@ -100,6 +103,35 @@ class InputValidationStage(PipelineStage):
image = load_image(batch.image_path)
batch.pil_image = image
img = batch.pil_image
ih, iw = img.height, img.width
logger.info(f"img height: {ih}, img width: {iw}")
patch_size = fastvideo_args.pipeline_config.dit_config.arch_config.patch_size
vae_stride = fastvideo_args.pipeline_config.vae_config.arch_config.scale_factor_spatial
logger.info(f"patch_size: {patch_size}, vae_stride: {vae_stride}")
dh, dw = patch_size[1] * vae_stride, patch_size[2] * vae_stride
max_area = 704 * 1280
ow, oh = best_output_size(iw, ih, dw, dh, max_area)
scale = max(ow / iw, oh / ih)
img = img.resize((round(iw * scale), round(ih * scale)), Image.LANCZOS)
logger.info(f"resized img height: {img.height}, img width: {img.width}")
# center-crop
x1 = (img.width - ow) // 2
y1 = (img.height - oh) // 2
img = img.crop((x1, y1, x1 + ow, y1 + oh))
assert img.width == ow and img.height == oh
# logger.info(f"img shape: {img.shape}")
# to tensor
img = TF.to_tensor(img).sub_(0.5).div_(0.5).to(self.device).unsqueeze(1)
logger.info(f"img shape: {img.shape}")
img = img.unsqueeze(0)
batch.height = oh
batch.width = ow
batch.pil_image = img
return batch
def verify_input(self, batch: ForwardBatch,
+63
View File
@@ -812,3 +812,66 @@ def set_random_seed(seed: int) -> None:
@lru_cache(maxsize=1)
def is_vsa_available() -> bool:
return importlib.util.find_spec("vsa") is not None
# adapted from: https://github.com/Wan-Video/Wan2.2/blob/main/wan/utils/utils.py
def masks_like(tensor,
zero=False,
generator=None,
p=0.2) -> tuple[list[torch.Tensor], list[torch.Tensor]]:
assert isinstance(tensor, list)
out1 = [torch.ones(u.shape, dtype=u.dtype, device=u.device) for u in tensor]
out2 = [torch.ones(u.shape, dtype=u.dtype, device=u.device) for u in tensor]
if zero:
if generator is not None:
for u, v in zip(out1, out2, strict=False):
random_num = torch.rand(1,
generator=generator,
device=generator.device).item()
if random_num < p:
u[:, 0] = torch.normal(mean=-3.5,
std=0.5,
size=(1, ),
device=u.device,
generator=generator).expand_as(
u[:, 0]).exp()
v[:, 0] = torch.zeros_like(v[:, 0])
else:
u[:, 0] = u[:, 0]
v[:, 0] = v[:, 0]
else:
for u, v in zip(out1, out2, strict=False):
u[:, 0] = torch.zeros_like(u[:, 0])
v[:, 0] = torch.zeros_like(v[:, 0])
return out1, out2
# adapted from: https://github.com/Wan-Video/Wan2.2/blob/main/wan/utils/utils.py
def best_output_size(w, h, dw, dh, expected_area):
# float output size
ratio = w / h
ow = (expected_area * ratio)**0.5
oh = expected_area / ow
# process width first
ow1 = int(ow // dw * dw)
oh1 = int(expected_area / ow1 // dh * dh)
assert ow1 % dw == 0 and oh1 % dh == 0 and ow1 * oh1 <= expected_area
ratio1 = ow1 / oh1
# process height first
oh2 = int(oh // dh * dh)
ow2 = int(expected_area / oh2 // dw * dw)
assert oh2 % dh == 0 and ow2 % dw == 0 and ow2 * oh2 <= expected_area
ratio2 = ow2 / oh2
# compare ratios
if max(ratio / ratio1, ratio1 / ratio) < max(ratio / ratio2,
ratio2 / ratio):
return ow1, oh1
else:
return ow2, oh2
+5 -1
View File
@@ -21,6 +21,7 @@ from fastvideo.pipelines import ForwardBatch, build_pipeline
from fastvideo.platforms import current_platform
from fastvideo.utils import (get_exception_traceback,
kill_itself_when_parent_died)
import fastvideo.envs as envs
logger = init_logger(__name__)
@@ -140,7 +141,10 @@ class Worker:
fastvideo_args = recv_rpc['kwargs']['fastvideo_args']
output_batch = self.execute_forward(forward_batch,
fastvideo_args)
self.pipe.send({"output_batch": output_batch.output.cpu()})
logging_info = None
if envs.FASTVIDEO_STAGE_LOGGING:
logging_info = output_batch.logging_info
self.pipe.send({"output_batch": output_batch.output.cpu(), "logging_info": logging_info})
elif method_name == 'set_lora_adapter':
lora_nickname = recv_rpc['kwargs']['lora_nickname']
lora_path = recv_rpc['kwargs']['lora_path']
+18 -2
View File
@@ -15,6 +15,7 @@ from fastvideo.logger import init_logger
from fastvideo.pipelines.pipeline_batch_info import ForwardBatch
from fastvideo.worker.executor import Executor
from fastvideo.worker.gpu_worker import run_worker_process
import fastvideo.envs as envs
logger = init_logger(__name__)
@@ -40,7 +41,8 @@ class MultiprocExecutor(Executor):
logger.info("Using provided master port: %s", self.master_port)
else:
# Auto-find available port
for port in range(29503, 65535):
import random
for port in range(29503 + random.randint(0, 10000), 65535):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
if s.connect_ex(('localhost', port)) != 0:
self.master_port = port
@@ -80,7 +82,21 @@ class MultiprocExecutor(Executor):
"forward_batch": forward_batch,
"fastvideo_args": fastvideo_args
})
return cast(ForwardBatch, responses[0]["output_batch"])
output = responses[0]["output_batch"]
logging_info = None
if envs.FASTVIDEO_STAGE_LOGGING:
logging_info = responses[0]["logging_info"]
else:
logging_info = None
result_batch = ForwardBatch(
data_type=forward_batch.data_type,
output=output,
logging_info=logging_info
)
return result_batch
def set_lora_adapter(self,
lora_nickname: str,