DonutLoRAStack
The block-weighted LoRA stacker the pack is built around
- lora_stack
- lora_stack
This is the node the pack is named around. DonutLoRAStack gives you three LoRA slots, and for each one - on top of the usual model/CLIP weights - a block vector: a per-layer list of strengths that lets you say "apply this LoRA hard in the middle blocks, barely in the out blocks." Normal LoRA stacking treats a LoRA as one scalar; this treats it as 12 numbers for SDXL, 17 for SD 1.5, 30 for Z-Image, 28 for Krea 2, with presets so you never have to type them. The README calls it "block-weighted LoRA stacking with presets," and that's the honest one-line summary.
Why block weighting exists: LoRA blocks do different jobs. Early blocks hold composition and structure, middle blocks hold style and detail, late blocks hold fine texture. Stack two full-strength character LoRAs and they fight over the same middle blocks; block weighting is how you park each LoRA where it earns its keep. The community has clearly adopted it - the author's own Z-Image LoRA workflow is a known quantity on CivitAI, and there's at least one real thread of a user chasing "improving DonutLoraStack" for character LoRAs on Z-Image with bbox detailers, mostly around the apply side being finicky.
How it works
For each of the three slots: a switch (On/Off), the lora_name picker, model_weight and clip_weight (defaults 1, range -1000 to 1000 so you can go negative), a block_preset dropdown, and a block_vector string.
The clever part is how preset meets vector: block_preset is a UI helper that populates block_vector with a comma-separated strength list - SDXL-IN-MID:1,1,1,1,1,1,1,1,1,1,1,0,0 style. The model_type selector at the top (Auto / SDXL / SD15 / FLUX / ZIT / ZIT-NE / KREA2) filters which presets you're offered by architecture, and the placeholder text reminds you the vector length differs per model. The actual apply path always uses the block_vector - the source notes the preset "only populates the field via JS."
There's also a civitai_lookup toggle (default On) that fetches LoRA info from CivitAI when the stack applies - the README notes it needs an API key in settings. And an optional lora_stack input, so you can chain this onto a stack built by a previous node.
Output and the next step
Output is a lora_stack of type LORA_STACK - an abstract pipe type, not a model. The node's job ends there; DonutApplyLoRAStack (same pack) is what turns it into model + CLIP patches for your sampler. That's the pairing you'll actually wire: DonutLoRAStack → DonutApplyLoRAStack → KSampler.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/DonutsDelivery/ComfyUI-DonutNodes.git donutnodes
cd donutnodes
python -m pip install -r requirements.txt
or ComfyUI Manager → "DonutNodes," restart.
Where people get burned
The apply side is where the pack's own fix history lives - there's a whole CACHE_INVALIDATION_FIX.md and a cache-invalidation validation suite in the repo about merge results going stale when you change strengths. If you tweak a weight and the output doesn't change, that's your bug, and the fix is usually clearing ComfyUI's model cache or restarting. Second: block vectors must match the model's block count - a 12-length SDXL vector fed to a Z-Image stack is silent garbage, which is exactly what the model_type filter exists to prevent. And on negative weights: they're supported, they're a legit technique for subtracting a LoRA's influence, but they amplify any fragility in the apply path.
Inputs (21)
| Name | Type | Default | Description |
|---|---|---|---|
| model_type | COMBO | Auto | Filter block presets by model architecture. Auto shows all presets. |
| switch_1 | COMBO | 2 options: Off, On | |
| lora_name_1 | COMBO | 1 options: None | |
| model_weight_1 | FLOAT | 1.00-1000–1000 | — |
| clip_weight_1 | FLOAT | 1.00-1000–1000 | — |
| block_preset_1 | COMBO | None | Select a preset to populate block_vector_1. |
| block_vector_1 | STRING | — | |
| switch_2 | COMBO | 2 options: Off, On | |
| lora_name_2 | COMBO | 1 options: None | |
| model_weight_2 | FLOAT | 1.00-1000–1000 | — |
| clip_weight_2 | FLOAT | 1.00-1000–1000 | — |
| block_preset_2 | COMBO | None | Select a preset to populate block_vector_2. |
| block_vector_2 | STRING | — | |
| switch_3 | COMBO | 2 options: Off, On | |
| lora_name_3 | COMBO | 1 options: None | |
| model_weight_3 | FLOAT | 1.00-1000–1000 | — |
| clip_weight_3 | FLOAT | 1.00-1000–1000 | — |
| block_preset_3 | COMBO | None | Select a preset to populate block_vector_3. |
| block_vector_3 | STRING | — | |
| civitai_lookup | COMBO | On | Fetch LoRA info from CivitAI (requires API key in settings) |
| lora_stackopt | LORA_STACK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| lora_stack | LORA_STACK | — |