SPA (HRDiT)
The fix for repeating structures when FLUX goes big
- model
- Patched Model
You push FLUX to 2K and the buildings start duplicating. The crowd clones itself. Rows of windows collapse into repeating mush. That's not a seed problem - it's the model's positional encoding running out of distribution, and it has a name in the HRDiT paper: spatial disorder. SPA (Spatial Position Alignment) is the training-free patch that fixes exactly this, and it's the quality half of HRDiT - the speed half is its sibling HAP node in the same pack.
Here's the mechanism. DiT models like FLUX use RoPE positional encodings that were trained for a roughly 1024px grid. Beyond that, positions the model has never seen make different parts of the image look interchangeable, so attention starts treating far-apart regions as the same thing - hence the repetition. SPA bundles token positions into groups of N, then slides the bundle boundary over each axis (2s − 1 variants) and averages the attention outputs across those variants. Note the careful bit: it averages attention outputs, never the RoPE matrices themselves. The bundled positions stay inside the model's trained distribution, so distinct structure stays distinct. It's a static patch - no timestep dependence, unlike DyPE's per-step modulation - and it's resolution-aware: inside the model's trained extent (≤ 1024px) it's an automatic no-op, so there's zero cost at native res.
The inputs that matter
Required: model, width and height (these must match your empty latent - a mismatch is the classic silent failure here), model_type (leave on auto), and enable_spa. Then the optional knobs:
bundle_size(default 0 = auto) - tokens per bundle, the paper's N. Auto picks the minimal compression that keeps every bundled position in-distribution; explicit values the paper recommends are 3 at 2K, 5 at 4K.1turns it off.spa_steps(default 3) - SPA runs only on the first 3 denoising steps, where composition is set; later steps run plain attention at baseline speed.0= every step (slower, backward-compatible).spa_layer_filter(default "") - restrict SPA to specific layers, e.g."0-18,38-57", if you want to spend its overhead only where structure forms.proportional_attention(off) - HRDiT's logit scaling for long sequences; exact no-op below 1024px, and off by default to stay bit-identical to previous behavior.
Output is a single Patched Model - wire it from your loader through this node into the KSampler.
The one rule that matters
SPA is mutually exclusive with DyPE and SEGA. Apply only one position patch. If your failure mode is structural repetition at 2K–4K, SPA is the right tool; if you want to push to extreme resolutions with dynamic extrapolation, that's DyPE's job. Stacking them will just fight each other. Also note Nunchaku isn't supported - it logs a warning and passes the model through unchanged.
Installing it
SPA ships inside ComfyUI-DyPE. Via ComfyUI Manager, search "ComfyUI-DyPE" and install, or:
cd ComfyUI/custom_nodes
git clone https://github.com/wildminder/ComfyUI-DyPE.git
Restart and you're done - no extra pip packages (the pack's requirements are just torch and numpy) and no model downloads.
The honest assessment
The overhead is bounded but real: roughly 1.3–1.8× total inference time at 2K/4K with defaults, which is the price of running several averaged attention passes on the leading steps. The pack's parent method, DyPE, earned a mixed-but-real community reception on Reddit - praise for distributing detail at high res, complaints about stretched output at non-square aspect ratios and zero VRAM savings. SPA is newer and quieter on the graph, but its design sidesteps the stretching complaint: a single shared bundle size is used for both axes so non-square images keep their aspect ratio. Expect it to fix repetition, not to make 4K sampling cheap - that part is HAP's job.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | The model to patch with SPA. | |
| width | INT | 102416–8192 | Target image width. Must match the width of your empty latent. |
| height | INT | 102416–8192 | Target image height. Must match the height of your empty latent. |
| model_type | COMBO | auto | Specify the model architecture. 'auto' usually works. |
| enable_spa | BOOLEAN | true | Enable or disable SPA. When disabled, the base RoPE is emitted unchanged. |
| bundle_sizeopt | INT | 00–256 | SPA bundle size N (HRDiT paper): tokens per bundle. 0 = auto (minimal compression that keeps every bundled position in-distribution). 1 = off (plain passthrough). 2..8 = explicit (paper recommends 3 at 2K, 5 at 4K). While the grid is inside the model's trained extent (e.g. <= 1024px) SPA is automatically a no-op. Explicit N is floored by the in-distribution minimum so bundled positions never go out of distribution; the averaged-pass count is capped at 15. A single shared bundle size is used for BOTH axes so non-square images keep their aspect ratio (no horizontal squish). Legacy values >= 32 (old group_num semantics) are treated as auto with a warning. |
| spa_start_sigmaopt | FLOAT | 1.000–1 | Optional sigma-threshold gate (AND-combined with spa_steps): SPA runs only while the current sigma is ABOVE this threshold. 1.0 = no sigma gating (default). Lower values make later steps run at baseline speed. |
| spa_stepsopt | INT | 30–100 | Step gating (HRDiT applies SPA only on leading denoising steps): number of LEADING steps on which SPA is active. 3 = HRDiT default (recommended speed/quality tradeoff). 0 = active on every step (backward compatible, slower). A new generation (sigma jump-up) resets the counter. Later steps run plain attention at baseline speed. |
| spa_layer_filteropt | STRING | Per-layer SPA filter (HRDiT set_spa_filter): restrict the averaged-pass SPA to a subset of transformer layers. Flat layer-index spec: '0-18,38-57' (inclusive ranges, comma-separated) or a single index '3'. Empty = every layer (default). Filtered-out layers run plain attention; the layer counter and HAP are unaffected. Invalid specs raise an error. | |
| proportional_attentionopt | BOOLEAN | false | HRDiT proportional attention scaling: scales the attention logits by sqrt(ln(seq_len)/ln(train_seq_len)) to compensate entropy dilution on long sequences. Exact no-op at/below the trained extent (1024px). Off by default (bit-identical to previous behaviour). Either the SPA or the HAP node may enable it. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| Patched Model | MODEL | The model patched with SPA. |