⬡ Polyhedron LoRA Stack
The node for the 15-LoRA workflow nobody else handles well
- model
- clip
- MODEL
- CLIP
- debug_info
- uls_config_out
- trigger_words
Most LoRA loaders are fine for two or three LoRAs. Stack fifteen and you meet multi-LoRA interference: washed-out detail, concepts cancelling each other, and no way to see what's even active. The Polyhedron LoRA Stack is the flagship node of this pack, and it's built for exactly that case - it treats a big LoRA stack like a total-conversion mod for your model, organised in semantic groups, applied in a defined order, with merge modes and cleanup switches that fight the interference directly.
The core idea is groups. Instead of a flat list of fifteen rows applied in load order, rows live in ordered groups - acc → style → scene → motion → subject → detail → custom - applied broad-to-specific. Character LoRAs aren't averaged against style LoRAs; each group gets its own merge mode and its members hold each other apart instead of averaging each other away. And it's model-agnostic: WAN 2.1/2.2, FLUX, SDXL, SD 1.5 - no model-specific assumptions. For dual-noise architectures you run two side by side, one per expert, and the README's example templates show exactly that pattern.
The merge modes
Each group picks one of three:
- Sequential (SEQ) - classic stacking via ComfyUI's native cached loader. The default, always works.
- Combined (CONCAT) - rank concatenation. Mathematically identical to SEQ, but a gentler float path.
- Smooth Mix (DARE) - CONCAT plus a Bernoulli mask. Channel Drop removes whole rank channels (LoRA-aware), Element Drop removes single tensor elements (the classic DARE paper). Density auto-scales with group size, and the seed derives deterministically from LoRA names and weights - same workflow, same merge, every time.
On top of CONCAT/DARE sit two cleanup switches. Trim deterministically drops each LoRA's weakest rank channels by contribution magnitude before merging - the direct counter to the "many quiet LoRAs → grey fog" effect - with strengths from Auto/Gentle down to Max (50%). Resolve is TIES sign-election: per weight, the majority direction wins, only agreeing LoRAs are averaged, then the result is repacked to low rank via truncated SVD so it hits the same hand-off path. Both are greyed out under SEQ, where they can't apply. The README's calibration note is worth respecting: the recommended Trim range is 70–80% (Medium/Light); Max cuts into the quiet rank channels where a concept's distinguishing features live, and the known failure mode is concept → silhouette.
The inputs and outputs
model in (required), clip in (optional), uls_config is the serialized config string the UI manages for you. Out: MODEL, CLIP, debug_info, uls_config_out (feed it to the Inspector, Merge Analyzer, or Token Counter), and trigger_words - the collected triggers of every active LoRA, read from .uls-meta.json → .txt → the safetensors header in priority order. One-click insert those into your prompt node, which is where the pack's Token Counter starts to matter: with the triggers wired in, you can see how much of WAN's 512-token budget they eat.
There's also a per-row CLIP strength (v302): shift-click a weight to decouple a row's text-encoder strength from its model weight - only relevant for SDXL/SD1.5, since WAN LoRAs carry no text-encoder keys.
Installing it and the renderer gotcha
It's the heart of the Polyhedron Suite pack - ComfyUI Manager search "Polyhedron Suite", or:
cd ComfyUI/custom_nodes
git clone https://github.com/PolyhedronAI/ComfyUI-PolyhedronLoRAStack.git
# restart ComfyUI
No hard dependencies. The one gotcha that will confuse you on a fresh ComfyUI: the Stack's UI is hand-drawn on the classic LiteGraph canvas, and the new "Modern Node Design" (Nodes 2.0) renderer shows it as an empty node. Disable that renderer in Settings - the node shows the hint inline - and your rows come back. All the backend nodes work under both renderers.
And one wiring trap the source calls out loudly: don't feed this node the Select Model Switch's string output directly. The switch hands out a filename; a filename where a MODEL is expected poisons every consumer until an unrelated node crashes. Feed the switch into a loader's combo input, and wire that loader's MODEL output here.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | — | |
| clipopt | CLIP | — | |
| uls_configopt | STRING | {"rows":[],"mult":1.0} | — |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| MODEL | MODEL | — |
| CLIP | CLIP | — |
| debug_info | STRING | — |
| uls_config_out | STRING | — |
| trigger_words | STRING | — |