@@ -2883,3 +2883,88 @@ def get_alpha_scales(down_weight, alpha_key):
28832883
28842884 converted_state_dict = {f"transformer.{ k } " : v for k , v in converted_state_dict .items ()}
28852885 return converted_state_dict
2886+
2887+
2888+ def _convert_non_diffusers_ideogram4_lora_to_diffusers (state_dict ):
2889+ """
2890+ Convert non-diffusers Ideogram4 LoRA state dict to diffusers format.
2891+
2892+ Handles:
2893+ - `diffusion_model.` / `conditional_transformer.` prefix removal
2894+ - `lora_down`/`lora_up` (kohya) -> `lora_A`/`lora_B`, with `.alpha` folded into the weights
2895+ - fused `attention.qkv` -> split `to_q`/`to_k`/`to_v`; `attention.o` -> `to_out.0`
2896+ - `feed_forward.w1`/`w2`/`w3` and `adaln_modulation` map one-to-one
2897+ """
2898+ for prefix in ("diffusion_model." , "conditional_transformer." ):
2899+ if any (k .startswith (prefix ) for k in state_dict ):
2900+ state_dict = {k .removeprefix (prefix ): v for k , v in state_dict .items ()}
2901+ break
2902+
2903+ is_kohya = any (".lora_down.weight" in k for k in state_dict )
2904+ down_suffix = ".lora_down.weight" if is_kohya else ".lora_A.weight"
2905+ up_suffix = ".lora_up.weight" if is_kohya else ".lora_B.weight"
2906+
2907+ def get_alpha_scales (down_weight , alpha_key ):
2908+ rank = down_weight .shape [0 ]
2909+ alpha_tensor = state_dict .pop (alpha_key , None )
2910+ if alpha_tensor is None :
2911+ return 1.0 , 1.0
2912+ # LoRA is scaled by `alpha / rank` in the forward pass; split the factor between down and up.
2913+ scale = alpha_tensor .item () / rank
2914+ scale_down , scale_up = scale , 1.0
2915+ while scale_down * 2 < scale_up :
2916+ scale_down *= 2
2917+ scale_up /= 2
2918+ return scale_down , scale_up
2919+
2920+ def pull (base ):
2921+ """Pop the scaled (lora_A, lora_B) pair for a module path, or return None if absent."""
2922+ down_key = base + down_suffix
2923+ if down_key not in state_dict :
2924+ return None
2925+ down = state_dict .pop (down_key )
2926+ up = state_dict .pop (base + up_suffix )
2927+ scale_down , scale_up = get_alpha_scales (down , base + ".alpha" )
2928+ return down * scale_down , up * scale_up
2929+
2930+ num_layers = 0
2931+ for k in state_dict :
2932+ match = re .match (r"layers\.(\d+)\." , k )
2933+ if match :
2934+ num_layers = max (num_layers , int (match .group (1 )) + 1 )
2935+
2936+ converted_state_dict = {}
2937+ for i in range (num_layers ):
2938+ layer_prefix = f"layers.{ i } "
2939+
2940+ # Fused qkv -> split to_q / to_k / to_v (shared down/lora_A, chunk up/lora_B in thirds).
2941+ qkv = pull (f"{ layer_prefix } .attention.qkv" )
2942+ if qkv is not None :
2943+ down , up = qkv
2944+ up_q , up_k , up_v = torch .chunk (up , 3 , dim = 0 )
2945+ for proj , up_proj in (("to_q" , up_q ), ("to_k" , up_k ), ("to_v" , up_v )):
2946+ converted_state_dict [f"{ layer_prefix } .attention.{ proj } .lora_A.weight" ] = down .clone ()
2947+ converted_state_dict [f"{ layer_prefix } .attention.{ proj } .lora_B.weight" ] = up_proj .contiguous ()
2948+
2949+ # attention.o -> attention.to_out.0
2950+ out = pull (f"{ layer_prefix } .attention.o" )
2951+ if out is not None :
2952+ down , up = out
2953+ converted_state_dict [f"{ layer_prefix } .attention.to_out.0.lora_A.weight" ] = down
2954+ converted_state_dict [f"{ layer_prefix } .attention.to_out.0.lora_B.weight" ] = up
2955+
2956+ # feed_forward.{w1,w2,w3} and adaln_modulation map one-to-one.
2957+ for module in ("feed_forward.w1" , "feed_forward.w2" , "feed_forward.w3" , "adaln_modulation" ):
2958+ pair = pull (f"{ layer_prefix } .{ module } " )
2959+ if pair is not None :
2960+ down , up = pair
2961+ converted_state_dict [f"{ layer_prefix } .{ module } .lora_A.weight" ] = down
2962+ converted_state_dict [f"{ layer_prefix } .{ module } .lora_B.weight" ] = up
2963+
2964+ if len (state_dict ) > 0 :
2965+ raise ValueError (
2966+ f"`state_dict` should be empty at this point but has { sorted (state_dict .keys ())} . "
2967+ "This may be an unsupported Ideogram4 LoRA layout."
2968+ )
2969+
2970+ return {f"transformer.{ k } " : v for k , v in converted_state_dict .items ()}
0 commit comments