Skip to content

[Bug]: LoHa output incompatible with ComfyUI — kernel size mismatch between hada_w1_a (1×1) and hada_w1_b (3×3) #1647

Description

@DRCX

What happened?

LoHa output incompatible with ComfyUI — kernel size mismatch between hada_w1_a (1×1) and hada_w1_b (3×3)

Summary

When training a LoHa (LyCORIS) for SDXL using OneTrainer and then loading the resulting .safetensors in ComfyUI, the model fails with:

RuntimeError: self must be a matrix

The root cause is that OneTrainer saves LoHa convolution layer weights with inconsistent kernel sizes between hada_w1_a (1×1) and hada_w1_b (3×3). ComfyUI's LoHa loader expects both matrices to share the same kernel size, and its torch.mm() call cannot operate on the mismatched 4D tensors.

Environment

  • OneTrainer: master branch, commit 4d239422 (ot_revision from safetensors metadata)
  • ComfyUI: 0.28.0
  • PyTorch: 2.9.1+cu130
  • Python: 3.13.11
  • Base model: SDXL 1.0 Base (sd_xl_base_1.0.safetensors)
  • LoHa config:
    • peft_type: LOHA
    • lora_rank: 16
    • lora_alpha: 8.0
    • layer_filter_preset: attn-mlp
    • output_model_format: KOHYA_LORA
    • output_dtype: FLOAT_16
  • Dataset: 482 images at 512×132 (EU4 event illustrations)
  • Training: 11 epochs completed (~4900 steps), smooth loss converged at 0.115

Reproduction

  1. Train a LoHa in OneTrainer with peft_type: LOHA on any SDXL base model
  2. Copy the output .safetensors to ComfyUI's models/loras/ directory
  3. In ComfyUI, create a workflow: Load Checkpoint → Load LoRA → KSampler
  4. Set LoRA strength to 0.7
  5. Run the KSampler

Expected: LoHa loads and applies correctly.

Actual: ComfyUI logs hundreds of lora key not loaded warnings, then crashes with RuntimeError: self must be a matrix.

Error Details

ComfyUI error location: comfy/weight_adapter/loha.py, line 263

m1 = torch.mm(
    comfy.model_management.cast_to_device(w1a, weight.device, intermediate_dtype),
    comfy.model_management.cast_to_device(w1b, weight.device, intermediate_dtype),
)

torch.mm() requires both inputs to be 2D matrices, but OneTrainer saves them as 4D tensors with mismatched kernel sizes.

Root Cause Analysis

Tensor shapes in the OneTrainer output

Inspected using safetensors.torch.safe_open:

Layer: unet.conv_in
  hada_w1_a: shape = torch.Size([320, 16, 1, 1])   # kernel = 1×1
  hada_w1_b: shape = torch.Size([16, 4, 3, 3])     # kernel = 3×3  ⚠️ mismatch

Layer: unet.down_blocks.0.resnets.0.conv1
  hada_w1_a: shape = torch.Size([320, 16, 1, 1])   # kernel = 1×1
  hada_w1_b: shape = torch.Size([16, 320, 3, 3])   # kernel = 3×3  ⚠️ mismatch

Layer: unet.down_blocks.0.attentions.0.transformer_blocks.0.attn1.to_q
  hada_w1_a: shape = torch.Size([out, 16, 1, 1])   # kernel = 1×1
  hada_w1_b: shape = torch.Size([16, in, 3, 3])    # kernel = 3×3  ⚠️ mismatch

What ComfyUI expects

Looking at comfy/weight_adapter/loha.py:

  1. CP decomposition path (line 234-261): uses torch.einsum with 4D tensors — works with any kernel size, but requires t1/t2 tensors (CP decomposition) which OneTrainer does not save.

  2. Standard LoHa path (line 262-278): uses torch.mm(w1a, w1b) — requires both tensors to be 2D matrices (or at least have matching shapes that can be flattened consistently).

Since OneTrainer does not save CP decomposition tensors (t1/t2), ComfyUI falls through to the standard path, where torch.mm() fails on the 4D mismatched tensors.

Statistics from a real training output

  • Total LoHa layers in the safetensors: 794
  • Sampled 50 layers: 48 with kernel mismatch, 2 with matching kernels
  • Almost all conv layers have w1_a = 1×1 and w1_b = 3×3

This appears to be a fixed design choice in OneTrainer's LoHa implementation, not a per-layer configuration error.

ComfyUI Warning Logs (excerpt)

[WARNING] lora key not loaded: unet.up_blocks.0.attentions.2.transformer_blocks.8.attn2.to_out.0.hada_w2_a
[WARNING] lora key not loaded: unet.up_blocks.0.attentions.2.transformer_blocks.8.attn2.to_out.0.hada_w2_b
[WARNING] lora key not loaded: unet.up_blocks.0.attentions.2.transformer_blocks.8.attn2.to_q.alpha
[WARNING] lora key not loaded: unet.up_blocks.0.attentions.2.transformer_blocks.8.attn2.to_q.hada_w1_a
[WARNING] lora key not loaded: unet.up_blocks.0.attentions.2.transformer_blocks.8.attn2.to_q.hada_w1_b
... (hundreds more)

Metadata from the safetensors file

ss_base_model_version: sdxl_
modelspec.architecture: stable-diffusion-xl-v1-base/lora
modelspec.title: Stable Diffusion XL 1.0 Base LoRA
ot_revision: 4d239422
ot_branch: master

The metadata correctly identifies this as an SDXL LoRA, so the issue is not a model type mismatch.

Workaround

Switch from LoHa to LoCon (peft_type: LOCON) in the training config. LoCon output is handled correctly by ComfyUI's native LoRA loader.

Suggested Fixes

Any of the following would resolve the incompatibility:

  1. Match kernel sizes: Save both hada_w1_a and hada_w1_b with the same kernel size (either both 1×1 for linear-only, or both 3×3 for full conv), matching the convention used by the official LyCORIS project.

  2. Flatten to 2D: Save hada_w1_a as [out, rank] and hada_w1_b as [rank, in * k * k] (or vice versa), so ComfyUI's torch.mm() path works directly.

  3. Add CP decomposition tensors: Save t1/t2 tensors so ComfyUI uses the torch.einsum path instead of torch.mm().

  4. Document the limitation: If this is an intentional design choice, add a note in the wiki's LoRA page stating that LoHa output requires a custom ComfyUI node (similar to how OFTv2 is documented as needing Koratahiu/ComfyUI-OFTv2).

References

What did you expect would happen?

No response

Relevant log output

Generate and upload debug_report.log

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfollowupFailure to provide config or other info or needs followup

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions