Repository

Qwen-Video-Edit

Qwen-Video-Edit: Instruction-Based Video Editing by Repurposing an Image Editing Model , Yossi Gandelsman, Michaël Gharbi, Qixing Huang A long video edited chunk by chunk with diff · License unknown · Install via pip · Maintenance unknown

License
Unknown
file
purpose
projections.py
trainable in/out projections + warm-start init
dataset.py
Ditto-format (source, edited, instruction) video pairs

Specs

  • filepurpose
  • projections.pytrainable in/out projections + warm-start init
  • dataset.pyDitto-format (source, edited, instruction) video pairs
  • model.pyvideo-token model_fn + training module
  • train.pysingle-node multi-GPU training (torchrun)
  • infer.pylong-video editing + Wan2.2 enhancement pipeline
  • empirical.pyzero-training demos: image-grid & latent-grid editing
  • diffsynth/vendored DiffSynth-Studio (pinned snapshot; models/qwen_image_dit.py carries our grid RoPE patch)
  • wan/vendored Wan2.2 enhancement code from Ditto (with an SDPA fallback so flash-attn is optional)
  • rope_patch.pyguard that verifies the vendored patched diffsynth is the one imported
  • licenses/upstream licenses for the vendored code

README

Qwen-Video-Edit: Instruction-Based Video Editing by Repurposing an Image Editing Model

Yunpeng Bai, Yossi Gandelsman, Michaël Gharbi, Qixing Huang

[📄 Paper] [🌐 Project Page] [📦 Model Weights]


Long-video editing demo: source (left) vs. edited (right)

A long video edited chunk by chunk with different instructions — source on the
left, our result on the right. Full-quality videos play on the
project page.

Instruction-based video editing by repurposing an image editing model:
Qwen-Image-Edit's DiT directly edits Wan 2.1 video-VAE latents, bridged by
two tiny trainable projections warm-started from the DiT's own input/output
layers. No video-pretrained transformer required.

source video ──(frozen Wan2.1 VAE)──> video latents (16ch, temporal 4x)
      │                                       │
      │                              trainable in-projection
      │                                       ▼
prompt + frame-grid preview ──> Qwen-Image-Edit DiT (LoRA or full FT)
   (Qwen2.5-VL branch)                        │  grid RoPE over latent frames
                                     trainable out-projection
                                              ▼
                       edited latents ──(frozen Wan2.1 VAE)──> edited video
                                              │
                       (optional) Wan2.2 denoising-enhancement (Ditto)

Key ideas:

  • Warm-started projections: input = Conv3d(16→D, k=(1,2,2)) initialized
    from the DiT's img_in (mathematically exact — a static video is embedded
    identically to a Qwen image); output initialized from proj_out. The
    model edits video tokens from step 0 and training only learns the
    distribution shift.
  • Grid positional encoding: the T latent frames are laid out as tiles of
    one virtual big image (each frame keeps a spatial offset), matching the
    image model's prior. Implemented as a runtime patch of DiffSynth's
    QwenEmbedRope (rope_patch.py) — no vendored model code.

Repository layout

file purpose
projections.py trainable in/out projections + warm-start init
dataset.py Ditto-format (source, edited, instruction) video pairs
model.py video-token model_fn + training module
train.py single-node multi-GPU training (torchrun)
infer.py long-video editing + Wan2.2 enhancement pipeline
empirical.py zero-training demos: image-grid & latent-grid editing
diffsynth/ vendored DiffSynth-Studio (pinned snapshot; models/qwen_image_dit.py carries our grid RoPE patch)
wan/ vendored Wan2.2 enhancement code from Ditto (with an SDPA fallback so flash-attn is optional)
rope_patch.py guard that verifies the vendored patched diffsynth is the one imported
licenses/ upstream licenses for the vendored code

Setup

Python 3.10–3.12 with a CUDA build of PyTorch. Create a fresh environment
(either flavor):

# option A: conda
conda create -n qwen-video-edit python=3.12 -y
conda activate qwen-video-edit

# option B: venv
python3.12 -m venv ./envs/qwen-video-edit
source ./envs/qwen-video-edit/bin/activate

Then install the dependencies and set the base-weight cache location:

pip install -r requirements.txt
export DIFFSYNTH_MODEL_BASE_PATH=/path/to/model_cache   # add to your shell rc for persistence

Verify the environment (should print your torch version with
cuda available: True, and confirm the vendored patched DiffSynth is the
one being imported):

python -c "import torch; print(torch.__version__, 'cuda available:', torch.cuda.is_available())"
python -c "import rope_patch; rope_patch.apply(); print('vendored diffsynth OK')"

Note: run all commands from the repo root -- the vendored diffsynth/ and
wan/ packages must shadow any pip-installed copies (the second check above
fails loudly if they don't).

Download checkpoints

Our fine-tuned editing checkpoint (360p; trained with --num_frames 45 --video_max_pixels 245760 --latent_mode wan_compressed --pe_mode grid — inference flags must match):

hf download yunpeng1998/Qwen-Video-Edit 360P/step-30000.safetensors \
  --local-dir ./checkpoints
# -> ./checkpoints/360P/step-30000.safetensors

Base weights. Easiest: do nothing — DiffSynth's loader auto-downloads
them (ModelScope) into DIFFSYNTH_MODEL_BASE_PATH on first run. To
pre-download from Hugging Face instead (same directory layout, only the
needed subfolders):

# Qwen-Image-Edit DiT (~40GB)
hf download Qwen/Qwen-Image-Edit --include "transformer/*" \
  --local-dir $DIFFSYNTH_MODEL_BASE_PATH/Qwen/Qwen-Image-Edit

# Qwen text encoder (~15GB) + image VAE + tokenizer
hf download Qwen/Qwen-Image --include "text_encoder/*" "vae/*" "tokenizer/*" \
  --local-dir $DIFFSYNTH_MODEL_BASE_PATH/Qwen/Qwen-Image

# Qwen-Image-Edit processor (VL image preprocessing config)
hf download Qwen/Qwen-Image-Edit --include "processor/*" \
  --local-dir $DIFFSYNTH_MODEL_BASE_PATH/Qwen/Qwen-Image-Edit

# Wan 2.1 video VAE (the latent space the DiT edits in)
hf download Wan-AI/Wan2.1-T2V-1.3B --include "Wan2.1_VAE.pth" \
  --local-dir $DIFFSYNTH_MODEL_BASE_PATH/Wan-AI/Wan2.1-T2V-1.3B

# Wan 2.2 (enhancement stage, ~80GB)
hf download Wan-AI/Wan2.2-T2V-A14B \
  --local-dir /models/Wan-AI/Wan2.2-T2V-A14B

With everything pre-downloaded you can set DIFFSYNTH_SKIP_DOWNLOAD=true to
keep the loader fully offline.

Inference (long video, one prompt per chunk + enhancement)

The pipeline has two stages that run sequentially on one GPU, chained in
memory
(no intermediate un-enhanced files): (1) each line of prompts.txt
edits one num_frames window of the source video, in order, stopping when
prompts or frames run out; (2) every edited chunk is refined by Wan2.2
denoising-enhancement (re-noise a few steps, denoise with Wan2.2 -- the
Ditto recipe). Wan2.2 uses the same
Wan2.1 VAE as the editing model.

python infer.py \
  --source_video ./examples/example1.mp4 --prompts_file ./examples/prompts.txt \
  --checkpoint ./checkpoints/360P/step-30000.safetensors \
  --wan22_ckpt_dir /models/Wan-AI/Wan2.2-T2V-A14B \
  --num_frames 45 --video_max_pixels 245760 \
  --output_dir ./out

--latent_mode / --pe_mode / --num_frames must match the
checkpoint's training configuration
. Outputs *_chunk000.mp4 (+ same-name
.txt with the prompt) in temporal order; the script prints the ffmpeg
concat command. --skip_enhance writes the raw edited chunks (debugging
only). flash-attn speeds up the enhancement stage but is optional (the
vendored attention has an SDPA fallback).

Training

Data: the Ditto-1M dataset —
(source video, edited video, instruction) triplets. Download per their
instructions so you have videos/ and metadata JSONs (lists of
{"source_path", "edited_path", "instruction"} relative to the video root).

torchrun --nproc_per_node=8 train.py \
  --meta_paths /data/ditto/metadata/training_metadata/global.json \
  --video_root /data/ditto/videos \
  --num_frames 45 --video_max_pixels 245760 \
  --lora_base_model dit --lora_rank 32 \
  --lora_target_modules "to_q,to_k,to_v,add_q_proj,add_k_proj,add_v_proj,to_out.0,to_add_out,img_mlp.net.2,img_mod.1,txt_mlp.net.2,txt_mod.1" \
  --learning_rate 1e-4 \
  --output_path ./checkpoints --save_steps 1000
  • Full fine-tuning: replace the --lora_* flags with
    --trainable_models dit --learning_rate 1e-5 (ZeRO-2 + CPU optimizer
    offload engages automatically; add --zero_stage 3 to also shard
    parameters — needed for long sequences, e.g. 81-frame 720p).
  • Higher resolution / more frames: --num_frames 81 --video_max_pixels 921600
    with --zero_stage 3 --use_gradient_checkpointing_offload.
  • Checkpoints are trainable-weights-only .safetensors, loadable directly by
    infer.py --checkpoint. Resume weights with --resume_from_checkpoint.

Empirical demos (no training needed)

The observations this project is built on, runnable with stock checkpoints:

# 1. An image edit model can edit "video as a contact sheet":
python empirical.py --mode image_grid --frames_dir ./snowboard --uniform_sample \
  --prompt "Replace the skier on the image with a robot, ensuring the pose matches the original person, and maintaining consistency of the robot across frames."

# 2. The same works when each frame is VAE-encoded SEPARATELY and the DiT
#    sees concatenated per-frame tokens with grid positional encodings
python empirical.py --mode latent_grid --frames_dir ./snowboard --uniform_sample \
  --prompt "Replace the skier on the image with a robot, ensuring the pose matches the original person, and maintaining consistency of the robot across frames."

Input (input_grid.png) and result (edited_grid.png), zero training:

input contact sheet edited contact sheet

--frames_dir holds rows×cols (default 9) frames sorted by filename.

License / acknowledgements

Built on DiffSynth-Studio,
Qwen-Image-Edit,
Wan 2.1/2.2 and
Ditto. Vendored code retains its upstream
licenses -- see licenses/LICENSE.diffsynth-studio and
licenses/LICENSE.ditto.