Files
ethanfel-ComfyUI-LoRA-Optim…/docs/acestep-merge-optimization.md
T
EthanfelandClaude Opus 4.6 abe3433e16 docs: replace exclusive cross-attention claim with v1.5 paper quote
The doc previously stated voice identity enters "exclusively" through
cross-attention, which was an inference not supported by any source.
Replace with the actual quote from the ACE-Step 1.5 paper and clarify
that voice info propagates through self-attention/MLP after injection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 17:19:38 +02:00

399 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ACE-Step 1.5 LoRA Merge Optimization
Technical reference for all architecture-specific choices made in ZImage LoRA Merger
for ACE-Step 1.5 music generation model LoRAs.
## ACE-Step 1.5 Architecture Overview
ACE-Step 1.5 is a 2B-parameter Diffusion Transformer (DiT) for music generation.
It uses a Qwen3-based architecture with 24 transformer blocks, each containing
self-attention, cross-attention, and MLP (SwiGLU) sub-modules.
### Model Configuration
| Parameter | Value |
|-----------|-------|
| hidden_size | 2048 |
| num_hidden_layers | 24 |
| num_attention_heads | 16 |
| num_key_value_heads | 8 (GQA) |
| head_dim | 128 |
| intermediate_size | 6144 |
| sliding_window | 128 |
| text_hidden_dim | 1024 |
| timbre_hidden_dim | 64 |
| dtype | bfloat16 |
Source: [ACE-Step/Ace-Step1.5 config.json](https://huggingface.co/ACE-Step/Ace-Step1.5/blob/main/config.json)
### Hybrid Attention Pattern
Self-attention alternates between two modes across the 24 layers:
- **Odd layers (0, 2, 4, ..., 22)**: Sliding Window Attention (window=128).
Captures local acoustic nuances — timbre texture, transients, articulation.
- **Even layers (1, 3, 5, ..., 23)**: Global Group Query Attention (GQA).
Maintains long-range rhythmic and melodic consistency.
Cross-attention **always uses full/global attention** regardless of layer index.
This ensures voice identity from the timbre encoder has unrestricted access to
every position in the audio sequence.
Source: [ACE-Step 1.5 paper (arXiv:2602.00744)](https://arxiv.org/html/2602.00744v3),
[Architecture analysis (zenn.dev)](https://zenn.dev/asap/articles/6a717d7a68ec02?locale=en)
### Conditioning Stack
Three conditioning pathways are concatenated along the token axis and injected
via cross-attention into every DiT layer:
1. **Qwen3-0.6B caption embeddings** — text descriptions of genre, mood, style
2. **AceStepLyricEncoder** (8-layer bidirectional transformer) — lyric content
3. **AceStepTimbreEncoder** (4-layer bidirectional transformer) — voice/instrument
timbre from reference audio (30s context window, 25Hz latent via 1D VAE)
The timbre encoder processes reference audio into a 2048-dimensional embedding.
When no reference audio is provided, 30 seconds of silence is used as input.
Non-padded tokens from all three sources are packed left before concatenation
to prevent padding artifacts in attention.
Source: [ACE-Step 1.5 model code](https://github.com/ace-step/ACE-Step-1.5/blob/main/acestep/models/xl_turbo/modeling_acestep_v15_xl_turbo.py),
[ACE-Step 1.5 Explained (Substack)](https://artintech.substack.com/p/ace-step-15-explained)
### DiT Layer Structure (AceStepDiTLayer)
Each of the 24 layers contains:
```
AceStepDiTLayer:
self_attn_norm (Qwen3RMSNorm)
self_attn (AceStepAttention — sliding or global per layer_types)
cross_attn_norm (Qwen3RMSNorm)
cross_attn (AceStepAttention — always global)
mlp_norm (Qwen3RMSNorm)
mlp (Qwen3MLP — SwiGLU: gate_proj, up_proj, down_proj)
scale_shift_table (nn.Parameter, shape [1, 6, hidden_size] — AdaLN modulation from timestep)
```
The scale_shift_table provides 6 modulation values per layer:
`(shift_msa, scale_msa, gate_msa, c_shift_msa, c_scale_msa, c_gate_msa)`
used for adaptive layer norm on self-attention and MLP outputs.
Source: [ACE-Step 1.5 model code (xl_turbo)](https://github.com/ace-step/ACE-Step-1.5/blob/main/acestep/models/xl_turbo/modeling_acestep_v15_xl_turbo.py)
### LoRA Key Structure (v1.5)
v1.5 uses PEFT-format LoRA adapters. Canonical key patterns after normalization:
| Module | Key Pattern | Shape (lora_A) | Shape (lora_B) |
|--------|-------------|-----------------|-----------------|
| Self-attention | `diffusion_model.layers.{0-23}.self_attn.{q,k,v,o}_proj` | [r, 2048] | q/o: [2048, r], k/v: [1024, r] |
| Cross-attention | `diffusion_model.layers.{0-23}.cross_attn.{q,k,v,o}_proj` | [r, 2048] | q/o: [2048, r], k/v: [1024, r] |
| MLP | `diffusion_model.layers.{0-23}.mlp.{gate,up,down}_proj` | gate/up: [r, 2048], down: [r, 6144] | gate/up: [6144, r], down: [2048, r] |
Note: k/v projections output 1024 (8 KV heads x 128 head_dim) due to GQA,
while q/o projections output 2048 (16 heads x 128 head_dim).
Community LoRAs commonly target all three module types (self_attn, cross_attn, mlp)
and may use DoRA with per-module rank patterns via Side-Step's Fisher Information
analysis.
Source: [HuggingFace community LoRAs](https://huggingface.co/6san/symphonic_metal_lora_for_ace-step_v15),
[Side-Step toolkit](https://github.com/koda-dernet/Side-Step)
## Voice-Critical Layer Analysis
When merging a vocal LoRA with a music/style LoRA, voice identity is at risk
of being diluted. Understanding which layers carry voice information guides
the merge strategy.
### Priority ranking for voice preservation
| Priority | Module type | Reasoning |
|----------|------------|-----------|
| **Highest** | `cross_attn` (all 24 layers) | Primary voice-identity pathway. Attends to timbre encoder output (voice characteristics) concatenated with text/lyric conditioning. Always uses full global attention — never sliding window. |
| **High** | `self_attn` in sliding window layers (odd: 0, 2, 4, ...) | Captures local acoustic details — timbre texture, vocal transients, articulation within 128-token windows. |
| **Medium** | `self_attn` in global layers (even: 1, 3, 5, ...) | Long-range structure — melody contour, rhythmic patterns, song form. More style than voice. |
| **Lower** | `mlp` (all layers) | General audio feature transformation. Less specific to voice identity. |
### Why cross-attention is critical
In ACE-Step 1.5, the model operates on a "unified conditioning stack where
Qwen3-0.6B caption embeddings are concatenated with dedicated timbre and lyric
encoders and injected via Cross-Attention" (arXiv:2602.00744v3, Section 3.2).
Cross-attention is the primary injection point for voice conditioning, though
once injected, voice information propagates through self-attention and MLP
layers as well.
When a vocal LoRA is trained, its cross-attention weights learn to respond
strongly to the voice's timbre representation. When a music LoRA is trained
on instrumental data, its cross-attention weights learn to respond to
genre/style conditioning. These are typically orthogonal — they encode
different aspects of the conditioning signal.
Naive averaging or TIES trimming on cross-attention layers destroys the
vocal LoRA's learned response to timbre conditioning.
Source: [ACE-Step 1.5 paper (arXiv:2602.00744v3)](https://arxiv.org/html/2602.00744v3),
[Voice cloning discussion (Issue #259)](https://github.com/ace-step/ACE-Step/issues/259),
[Architecture analysis (zenn.dev)](https://zenn.dev/asap/articles/6a717d7a68ec02?locale=en)
## Merge Optimization Decisions
### 1. Dedicated `acestep_dit` Architecture Preset
**Decision**: Create a separate preset instead of sharing the generic `dit` preset.
**Justification**: The generic `dit` preset is tuned for image/video diffusion
transformers (Flux, WAN, Z-Image, LTX) where all attention layers serve similar
functions. In ACE-Step, cross-attention has a fundamentally different role
(voice conditioning) that requires more conservative merge behavior.
**Preset values and their rationale**:
#### `ties_conflict_threshold`: 0.25 → 0.35
The TIES conflict threshold determines when the merger switches from
weighted_average/SLERP to TIES (trim-elect-merge) mode. TIES aggressively
trims low-magnitude values and resolves sign conflicts by majority vote.
For vocal + music LoRA merging, TIES trimming is destructive to voice:
- Cross-attention layers encode orthogonal concepts (voice timbre vs genre)
- TIES trims the minority-magnitude signal, which could be the voice
- Sign conflicts in cross-attn are expected (different conditioning responses)
but don't represent real semantic conflict
Raising the threshold from 0.25 to 0.35 means TIES only activates for
genuinely high conflict (>35% excess), causing more prefixes to use
SLERP (magnitude-preserving interpolation) instead.
#### `orthogonal_cos_sim_max`: 0.25 → 0.30
This threshold determines when two LoRAs are classified as "orthogonal"
(independent signals). Orthogonal LoRAs get routed to weighted_average → SLERP
instead of TIES, since ~50% sign conflict is the statistical baseline for
independent vectors and doesn't represent real conflict.
Vocal and music LoRAs are typically orthogonal in most layers (cosine
similarity near zero). Widening the orthogonal band from 0.25 to 0.30
catches more of these cases, ensuring SLERP is used to preserve both
voice and music signals at full magnitude.
#### `orthogonal_conflict_max`: 0.60 → 0.65
The maximum conflict ratio allowed for the orthogonal classification.
Slightly raised to accommodate the naturally higher base-rate conflict
seen in audio LoRA pairs.
#### `dare_ideal_density`: 0.8 → 0.85
When DARE sparsification is used, this controls the target density
(fraction of parameters kept). Higher density preserves more signal,
which is important for voice quality — dropping 15% of voice-encoding
parameters is less destructive than dropping 20%.
#### `auto_strength_orthogonal_floor`: 0.85 → 1.0
When vocal and music LoRAs are orthogonal, auto-strength normally
attenuates the merge to prevent magnitude explosion. For ACE-Step,
full magnitude preservation (floor = 1.0) is critical because:
- Audio LoRAs encode energy/volume information in weight magnitude
- Attenuation reduces vocal presence in the generated output
- Orthogonal signals don't interfere, so no magnitude reduction is needed
This matches the behavior already applied to video architectures (WAN, LTX)
via `_VIDEO_ARCH_ORTHOGONAL_FLOOR`, where motion energy similarly requires
magnitude preservation.
Source for video precedent: existing `_VIDEO_ARCH_ORTHOGONAL_FLOOR` in codebase
### 2. Orthogonal Floor Override
**Decision**: Add ACE-Step to `_VIDEO_ARCH_ORTHOGONAL_FLOOR` with value 1.0.
**Justification**: Same reasoning as the preset floor. This is a secondary
enforcement point that catches edge cases where the preset floor might be
overridden by other logic. Ensures orthogonal vocal + music LoRA merges
always preserve full magnitude.
### 3. v1.0 Format Detection and Normalization
**Decision**: Support ACE-Step v1.0 LoRA format alongside v1.5.
**Justification**: v1.0 LoRAs use a completely different key structure
(`transformer_blocks.N.attn.to_q` vs v1.5's `layers.N.self_attn.q_proj`)
and include unique voice-critical keys (`speaker_embedder`, `lyric_encoder`)
that are absent in v1.5.
Without v1.0 support, these LoRAs were misdetected as WAN (because
`transformer_blocks.0.cross_attn` matches the WAN detector's `blocks.` check)
and would be incorrectly normalized.
#### v1.0 → v1.5 Key Mapping
| v1.0 pattern | v1.5 canonical form |
|-------------|-------------------|
| `transformer_blocks.N` | `layers.N` |
| `.attn.` (bare) | `.self_attn.` |
| `.to_q.` / `.to_k.` / `.to_v.` | `.q_proj.` / `.k_proj.` / `.v_proj.` |
| `.to_out.0.` | `.o_proj.` |
| `speaker_embedder` | `diffusion_model.speaker_embedder` |
| `lyric_encoder.encoders.N.self_attn.linear_q` | `diffusion_model.lyric_encoder.encoders.N.self_attn.q_proj` |
Source: [ACE-Step v1.0 repo](https://github.com/ace-step/ACE-Step),
[Chinese RAP LoRA](https://huggingface.co/ACE-Step/ACE-Step-v1-chinese-rap-LoRA)
#### v1.0 Voice-Critical Keys
- **`speaker_embedder`**: Directly encodes voice timbre as a 512-dim embedding
projected to 2560-dim for cross-attention. Trained with 50% dropout in the
base model, so the remaining signal is concentrated and critical.
During merge, if only the vocal LoRA targets this prefix, it becomes a
single-LoRA case → `weighted_sum` → full preservation.
- **`lyric_encoder`**: 6-block conformer encoding lyric content. Less
voice-specific but encodes pronunciation/phonetic patterns that contribute
to vocal character.
Source: [ACE-Step v1.0 paper (arXiv:2506.00045)](https://arxiv.org/html/2506.00045v1)
### 4. Bug Fix: `to_out` → `o_proj` Mapping
**Decision**: Fix the regex that mapped `to_out` → `out_proj` (wrong) to
correctly produce `o_proj`, and handle v1.0's `to_out.0.` suffix.
**Before (broken)**:
```python
re.sub(r"\.to_(q|k|v|out)\.", lambda m: f".{m.group(1)}_proj.", new_k)
# to_out → out_proj (WRONG — v1.5 uses o_proj)
# to_out.0 → not matched (the .0. after to_out breaks the pattern)
```
**After (fixed)**:
```python
re.sub(r"\.to_(q|k|v)\.", lambda m: f".{m.group(1)}_proj.", new_k)
re.sub(r"\.to_out\.0\.", ".o_proj.", new_k)
re.sub(r"\.to_out\.", ".o_proj.", new_k)
```
This bug would cause v1.0 LoRA output projection keys to not match v1.5
keys, breaking cross-LoRA prefix grouping when merging a v1.0 LoRA with
a v1.5 LoRA.
### 5. WAN Detection Refinement
**Decision**: Exclude `transformer_blocks` from the WAN architecture detector.
**Justification**: WAN uses bare `blocks.N` while ACE-Step v1.0 uses
`transformer_blocks.N`. The substring `blocks.` appears in both, causing
v1.0 ACE-Step LoRAs to be misdetected as WAN. The fix checks for
`transformer_blocks` and skips the WAN path.
## How the Existing Merge System Benefits ACE-Step
The per-prefix adaptive merge strategy already provides significant advantages
over the community's standard merge tool (`MERGE-LORA.py`), which performs
blind weighted-sum across all layers.
### Per-prefix strategy selection
For each LoRA key prefix (e.g., `layers.5.cross_attn.q_proj`), the merger:
1. Computes pairwise conflict metrics (sign conflict, cosine similarity,
excess conflict, subspace overlap, magnitude ratio)
2. Classifies the relationship (orthogonal, aligned, conflicting)
3. Selects the optimal strategy:
- **SLERP** for orthogonal signals (preserves both at full magnitude)
- **Consensus** for aligned signals (Fisher-proxy importance weighting)
- **TIES** for genuine conflicts (trim + elect sign + disjoint merge)
- **Weighted sum** for single-LoRA prefixes
### Expected behavior for vocal + music LoRA merge
| Prefix type | Expected relationship | Expected strategy | Voice outcome |
|-------------|----------------------|-------------------|---------------|
| `cross_attn` | Orthogonal (voice vs genre conditioning) | SLERP | Both voice and genre preserved |
| `self_attn` (odd/sliding) | Low conflict (different local patterns) | SLERP or weighted_average | Timbre details preserved |
| `self_attn` (even/global) | Variable | Auto-selected | Structure blended |
| `mlp` | Low-medium conflict | SLERP or weighted_average | General features blended |
| `speaker_embedder` (v1.0) | Single-LoRA (only vocal targets it) | Weighted sum | Full voice preservation |
### Comparison with community MERGE-LORA.py
| Feature | MERGE-LORA.py | ZImage LoRA Merger |
|---------|---------------|-------------------|
| Merge method | Weighted sum only | TIES, SLERP, consensus, weighted avg, auto-select |
| Conflict handling | None (opposing signals cancel) | Per-prefix conflict analysis |
| Architecture awareness | None | ACE-Step detected, `acestep_dit` preset applied |
| Per-layer control | None (same weights everywhere) | Per-prefix adaptive strategy |
| Voice preservation | Manual strength tuning only | Automatic via orthogonal detection + SLERP |
| Magnitude preservation | Optional RMS clamp | Auto-strength with orthogonal floor = 1.0 |
| Rank handling | SVD truncation to min(r1, r2) | Configurable SVD compression |
| Multi-LoRA | 2 at a time (chain for 3+) | N-way native |
Source for MERGE-LORA.py: [DisturbingTheField HuggingFace repos](https://huggingface.co/DisturbingTheField/ACE-Step-v1.5-raspy-vocal-and-instrumental-5-LoRAs)
## Practical Recommendations for ACE-Step Users
### Merging ACE-Step LoRAs
1. Load LoRAs into a **LoRA Stack** node and connect to a **LoRA Optimizer** node
(or use **LoRA AutoTuner** for automatic quality optimization)
2. Architecture preset will auto-detect as `acestep_dit`
3. Use `per_prefix` optimization mode (default)
4. Keep `normalize_keys` enabled (handles v1.0/v1.5/Kohya/PEFT format differences)
5. Set LoRA scale to 0.3–0.5 at inference for best results
This applies to all ACE-Step LoRA merges — vocal + music, genre + genre,
instrument + style, etc. The `acestep_dit` preset is tuned for preserving
distinct signals (voice, genre, instrument character) that would otherwise
be diluted by naive averaging.
### If voice quality degrades (vocal + music merges)
- Try `merge_strategy_override: slerp` to force SLERP on all prefixes
- Reduce the music LoRA's strength relative to the vocal LoRA
- Check the merge report — cross_attn prefixes should show SLERP, not TIES
### Training tips for better merge compatibility
These are from the ACE-Step community, not from our merge code:
- Don't isolate vocals before training — keep instrumentals in training data
- Use trigger words to tag the singer consistently
- Reduce `ssl_coeff` (mHuBERT loss weight) to 0.1 or 0 for stronger voice capture
- Use Side-Step's Fisher Information analysis for adaptive per-layer rank allocation
Source: [Voice cloning discussion (Issue #259)](https://github.com/ace-step/ACE-Step/issues/259),
[Side-Step toolkit](https://github.com/koda-dernet/Side-Step)
## Test Coverage
25 tests cover ACE-Step-specific functionality:
- **7 detection tests**: v1.0 (transformer_blocks, speaker_embedder, lyric_encoder)
and v1.5 (PEFT, bare layers, cross_attn, MLP-only negative case)
- **12 normalization tests**: v1.5 PEFT prefix stripping, bare layers prefix,
Kohya underscore conversion, MLP keys, v1.0→v1.5 mapping (transformer_blocks→layers,
attn→self_attn, to_q→q_proj, to_out.0→o_proj, cross_attn preserved),
speaker_embedder, lyric_encoder (linear_q/v→q_proj/v_proj), no double-prefixing
- **6 preset tests**: dedicated preset mapping, wider orthogonal band, higher
TIES threshold, full magnitude preservation, auto-resolve, manual override
## References
- [ACE-Step 1.5 GitHub](https://github.com/ace-step/ACE-Step-1.5)
- [ACE-Step 1.5 Paper (arXiv:2602.00744)](https://arxiv.org/html/2602.00744v3)
- [ACE-Step v1.0 Paper (arXiv:2506.00045)](https://arxiv.org/abs/2506.00045)
- [ACE-Step 1.5 config.json](https://huggingface.co/ACE-Step/Ace-Step1.5/blob/main/config.json)
- [ACE-Step 1.5 Model Code](https://github.com/ace-step/ACE-Step-1.5/blob/main/acestep/models/xl_turbo/modeling_acestep_v15_xl_turbo.py)
- [ACE-Step 1.5 Explained (Substack)](https://artintech.substack.com/p/ace-step-15-explained)
- [ACE-Step Architecture Analysis (zenn.dev)](https://zenn.dev/asap/articles/6a717d7a68ec02?locale=en)
- [Voice Cloning Issue #259](https://github.com/ace-step/ACE-Step/issues/259)
- [Side-Step Training Toolkit](https://github.com/koda-dernet/Side-Step)
- [Community Merged LoRAs (DisturbingTheField)](https://huggingface.co/DisturbingTheField/ACE-Step-v1.5-raspy-vocal-and-instrumental-5-LoRAs)
- [Community Merged LoRAs (acoustic guitar)](https://huggingface.co/DisturbingTheField/ACE-Step-v1.5-acoustic-guitar-and-a-merge-LoRA)
- [LoRA Training Tutorial](https://github.com/ace-step/ACE-Step-1.5/blob/main/docs/en/LoRA_Training_Tutorial.md)
- [Musician's Guide Discussion](https://github.com/ace-step/ACE-Step-1.5/discussions/235)
- [LoRA Discussion/Library](https://github.com/ace-step/ACE-Step-1.5/discussions/338)