# Two-Stage Split Training This document introduces split training, which can automatically divide the training process into two stages, reducing VRAM usage while accelerating training speed. (Split training is an experimental feature that has not yet undergone large-scale validation. If you encounter any issues while using it, please submit an issue on GitHub.) ## Split Training In the training process of most models, a large amount of computation occurs in "preprocessing," i.e., "computations unrelated to the denoising model," including VAE encoding, text encoding, etc. When the corresponding model parameters are fixed, the results of these computations are repetitive. For each data sample, the computational results are identical across multiple epochs. Therefore, we provide a "split training" feature that can automatically analyze and split the training process. For standard supervised training of ordinary text-to-image models, the splitting process is straightforward. It only requires splitting the computation of all [`Pipeline Units`](../Developer_Guide/Building_a_Pipeline.md#units) into the first stage, storing the computational results to disk, and then reading these results from disk in the second stage for subsequent computations. However, if gradient backpropagation is required during preprocessing, the situation becomes extremely complex. To address this, we introduced a computational graph splitting algorithm to analyze how to split the computation. ## Enabling Split Training Split training already supports [Standard Supervised Training](../Training/Supervised_Fine_Tuning.md) and [Direct Distillation Training](../Training/Direct_Distill.md). The `--task` parameter in the training command controls this. Taking LoRA training of the Qwen-Image model as an example, the pre-split training command is: ```shell modelscope download --dataset DiffSynth-Studio/diffsynth_example_dataset --include "qwen_image/Qwen-Image/*" --local_dir ./data/diffsynth_example_dataset accelerate launch examples/qwen_image/model_training/train.py \ --dataset_base_path data/diffsynth_example_dataset/qwen_image/Qwen-Image \ --dataset_metadata_path data/diffsynth_example_dataset/qwen_image/Qwen-Image/metadata.csv \ --max_pixels 1048576 \ --dataset_repeat 50 \ --model_id_with_origin_paths "Qwen/Qwen-Image:transformer/diffusion_pytorch_model*.safetensors,Qwen/Qwen-Image:text_encoder/model*.safetensors,Qwen/Qwen-Image:vae/diffusion_pytorch_model.safetensors" \ --learning_rate 1e-4 \ --num_epochs 5 \ --remove_prefix_in_ckpt "pipe.dit." \ --output_path "./models/train/Qwen-Image_lora" \ --lora_base_model "dit" \ --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" \ --lora_rank 32 \ --use_gradient_checkpointing \ --dataset_num_workers 8 \ --find_unused_parameters ``` After splitting, in the first stage, make the following modifications: * Change `--dataset_repeat` to 1 to avoid redundant computation * Change `--output_path` to the path where the first-stage computation results are saved * Add the additional parameter `--task "sft:data_process"` * Fill in `offload_models` with the models that do not require forward computation, in the same format as `model_id_with_origin_paths` * Alternatively, you can directly remove from `--model_id_with_origin_paths` the models that do not require forward computation. However, you must ensure that the corresponding models are not indirectly invoked in the pipeline, which means you need to understand the internal details of the Pipeline. ```shell accelerate launch examples/qwen_image/model_training/train.py \ --dataset_base_path data/diffsynth_example_dataset/qwen_image/Qwen-Image \ --dataset_metadata_path data/diffsynth_example_dataset/qwen_image/Qwen-Image/metadata.csv \ --max_pixels 1048576 \ --dataset_repeat 1 \ --model_id_with_origin_paths "Qwen/Qwen-Image:text_encoder/model*.safetensors,Qwen/Qwen-Image:vae/diffusion_pytorch_model.safetensors,Qwen/Qwen-Image:transformer/diffusion_pytorch_model*.safetensors" \ --offload_models "Qwen/Qwen-Image:transformer/diffusion_pytorch_model*.safetensors" \ --learning_rate 1e-4 \ --num_epochs 5 \ --remove_prefix_in_ckpt "pipe.dit." \ --output_path "./models/train/Qwen-Image-LoRA-splited-cache" \ --lora_base_model "dit" \ --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" \ --lora_rank 32 \ --use_gradient_checkpointing \ --dataset_num_workers 8 \ --find_unused_parameters \ --task "sft:data_process" ``` In the second stage, make the following modifications: * Change `--dataset_base_path` to the `--output_path` of the first stage * Remove `--dataset_metadata_path` * Add the additional parameter `--task "sft:train"` * Fill in `offload_models` with the models that do not require forward computation, in the same format as `model_id_with_origin_paths` * Alternatively, you can directly remove from `--model_id_with_origin_paths` the models that do not require forward computation. However, you must ensure that the corresponding models are not indirectly invoked in the pipeline, which means you need to understand the internal details of the Pipeline. ```shell accelerate launch examples/qwen_image/model_training/train.py \ --dataset_base_path "./models/train/Qwen-Image-LoRA-splited-cache" \ --max_pixels 1048576 \ --dataset_repeat 50 \ --model_id_with_origin_paths "Qwen/Qwen-Image:text_encoder/model*.safetensors,Qwen/Qwen-Image:vae/diffusion_pytorch_model.safetensors,Qwen/Qwen-Image:transformer/diffusion_pytorch_model*.safetensors" \ --offload_models "Qwen/Qwen-Image:text_encoder/model*.safetensors,Qwen/Qwen-Image:vae/diffusion_pytorch_model.safetensors" \ --learning_rate 1e-4 \ --num_epochs 5 \ --remove_prefix_in_ckpt "pipe.dit." \ --output_path "./models/train/Qwen-Image-LoRA-splited" \ --lora_base_model "dit" \ --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" \ --lora_rank 32 \ --use_gradient_checkpointing \ --dataset_num_workers 8 \ --find_unused_parameters \ --task "sft:train" ``` We provide sample training scripts and validation scripts located at `examples/qwen_image/model_training/special/split_training`. ## Principles of the Computational Graph Splitting Algorithm The training framework splits the computational units in the `Pipeline` through the `split_pipeline_units` method of `DiffusionTrainingModule`. The following describes the detailed principles of the computational graph splitting algorithm. ### Problem Definition To precisely characterize the splitting process, we first formalize the computation pipeline. Suppose the pipeline consists of $n$ computational units ([`Pipeline Unit`](../Developer_Guide/Building_a_Pipeline.md#units)), and let the set of units be $V=\{u_1,u_2,\dots,u_n\}$. Each unit $u\in V$ has the following properties: * Input parameter set $\operatorname{in}(u)$: declared by `input_params`, `input_params_posi` and `input_params_nega`, representing the data items that must be read before the computation of $u$; * Output parameter set $\operatorname{out}(u)$: declared by `output_params`, representing the data items produced and written into the data cache after the computation of $u$; * Associated model set $\mathcal{M}(u)$: declared by `onload_model_names`, representing the models that the computation of $u$ depends on. All parameters constitute the parameter space $\mathcal{P}=\bigcup_{u\in V}\left(\operatorname{in}(u)\cup\operatorname{out}(u)\right)$. **Definition 1 (Data Dependency Edge)** Let $p\in\mathcal{P}$ be a parameter. If there exist units $u_i,u_j\in V$ such that $p\in\operatorname{out}(u_i)\cap\operatorname{in}(u_j)$, and $u_i$ is the most recent producer of $p$ (i.e., the unit with the latest execution order among all units that produce $p$), then there exists a data dependency edge $(u_i,u_j)$ between $u_i$ and $u_j$, whose semantics is that the computation of $u_j$ must occur after the computation of $u_i$ completes. Accordingly, the computation pipeline is abstracted as a directed acyclic graph $G=(V,E)$, where $E$ is the set of all data dependency edges. **Definition 2 (Directly Related Unit)** Given a set of models $\mathcal{W}$ that require gradient backpropagation (specified by `trainable_models` and `lora_base_model`, which are respectively the model components being trained and the model components being trained with LoRA). If a unit $u\in V$ satisfies $\mathcal{M}(u)\cap\mathcal{W}\neq\varnothing$, then $u$ is called a directly related unit, whose computation involves the invocation of trainable models. **Definition 3 (Computational Graph Splitting Problem)** Given a graph $G=(V,E)$ and a model set $\mathcal{W}$, find a bipartition $(V_1,V_2)$ of $V$ such that $V_1$ is the minimal set that contains all directly related units and satisfies the following closure conditions, with $V_2=V\setminus V_1$: (C1) Forward closure: if $u\in V_1$ and $(u,v)\in E$, then $v\in V_1$; that is, $V_2$ contains no unit that depends on the outputs of $V_1$; (C2) Updating-chain closure: for any parameter $p\in\mathcal{P}$, let its updating chain $\mathbf{c}(p)=(u^{(1)},u^{(2)},\dots,u^{(k)})$ be the sequence of all units that produce $p$ in execution order. If $p$ is first consumed at $u^{(i)}$ within $V_1$ and $i