Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9fd09a4
fix(ui): improve DyPE field ordering and add 'On' preset option
Pfannkuchensack Jan 27, 2026
e2e9028
Merge branch 'main' into fix/dype-ui-ordering
Pfannkuchensack Jan 27, 2026
fa52310
Chore Ruff check fix
Pfannkuchensack Jan 27, 2026
f862944
Merge branch 'fix/dype-ui-ordering' of https://github.com/Pfannkuchen…
Pfannkuchensack Jan 27, 2026
6d46d6d
fix(flux): remove .value from dype_preset logging
Pfannkuchensack Jan 27, 2026
cc61320
fix(tests): update DyPE tests for Literal type change
Pfannkuchensack Jan 27, 2026
cf830d5
feat(flux): add DyPE scale and exponent controls to Linear UI
Pfannkuchensack Jan 28, 2026
f122a4e
fix(flux): apply DyPE scale/exponent and add metadata recall
Pfannkuchensack Jan 28, 2026
bc6a85e
fix(flux): apply DyPE scale/exponent and add metadata recall
Pfannkuchensack Jan 28, 2026
2f7141d
Merge branch 'fix/dype-ui-ordering' of https://github.com/Pfannkuchen…
Pfannkuchensack Jan 28, 2026
1773a61
feat(ui): show DyPE scale/exponent only when preset is "on"
Pfannkuchensack Jan 28, 2026
1e73ea4
Merge branch 'main' into fix/dype-ui-ordering
JPPhoto Jan 28, 2026
1f65e98
Merge branch 'main' into fix/dype-ui-ordering
JPPhoto Jan 29, 2026
04b35ca
fix(dype): only allow custom scale/exponent with 'on' preset
Pfannkuchensack Jan 29, 2026
db03682
refactor(dype): rename 'on' preset to 'manual'
Pfannkuchensack Jan 29, 2026
eca95c3
refactor(dype): rename 'on' preset to 'manual'
Pfannkuchensack Jan 29, 2026
bd543c5
Merge branch 'fix/dype-ui-ordering' of https://github.com/Pfannkuchen…
Pfannkuchensack Jan 29, 2026
57cd58c
Merge branch 'main' into fix/dype-ui-ordering
JPPhoto Jan 29, 2026
60083bc
Merge branch 'fix/dype-ui-ordering' of https://github.com/Pfannkuchen…
Pfannkuchensack Jan 29, 2026
7931be4
fix(dype): update remaining 'on' references to 'manual'
Pfannkuchensack Jan 29, 2026
8cb8d5f
Merge branch 'main' into fix/dype-ui-ordering
JPPhoto Jan 29, 2026
e934540
Merge branch 'main' into fix/dype-ui-ordering
lstein Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions invokeai/app/invocations/flux_denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
from invokeai.backend.flux.controlnet.instantx_controlnet_flux import InstantXControlNetFlux
from invokeai.backend.flux.controlnet.xlabs_controlnet_flux import XLabsControlNetFlux
from invokeai.backend.flux.denoise import denoise
from invokeai.backend.flux.dype.presets import DyPEPreset, get_dype_config_from_preset
from invokeai.backend.flux.dype.presets import (
DYPE_PRESET_LABELS,
DYPE_PRESET_OFF,
DyPEPreset,
get_dype_config_from_preset,
)
from invokeai.backend.flux.extensions.dype_extension import DyPEExtension
from invokeai.backend.flux.extensions.instantx_controlnet_extension import InstantXControlNetExtension
from invokeai.backend.flux.extensions.kontext_extension import KontextExtension
Expand Down Expand Up @@ -66,7 +71,7 @@
title="FLUX Denoise",
tags=["image", "flux"],
category="image",
version="4.3.0",
version="4.5.0",
)
class FluxDenoiseInvocation(BaseInvocation):
"""Run denoising process with a FLUX transformer model."""
Expand Down Expand Up @@ -170,20 +175,24 @@ class FluxDenoiseInvocation(BaseInvocation):

# DyPE (Dynamic Position Extrapolation) for high-resolution generation
dype_preset: DyPEPreset = InputField(
default=DyPEPreset.OFF,
default=DYPE_PRESET_OFF,
description="DyPE preset for high-resolution generation. 'auto' enables automatically for resolutions > 1536px. '4k' uses optimized settings for 4K output.",
ui_order=100,
ui_choice_labels=DYPE_PRESET_LABELS,
)
dype_scale: Optional[float] = InputField(
default=None,
ge=0.0,
le=8.0,
description="DyPE magnitude (λs). Higher values = stronger extrapolation. Only used when dype_preset is not 'off'.",
ui_order=101,
)
dype_exponent: Optional[float] = InputField(
default=None,
ge=0.0,
le=1000.0,
description="DyPE decay speed (λt). Controls transition from low to high frequency detail. Only used when dype_preset is not 'off'.",
ui_order=102,
)

@torch.no_grad()
Expand Down Expand Up @@ -464,9 +473,13 @@ def _run_diffusion(
target_width=self.width,
)
context.logger.info(
f"DyPE enabled: {self.width}x{self.height}, preset={self.dype_preset.value}, "
f"scale={dype_config.dype_scale:.2f}, method={dype_config.method}"
f"DyPE enabled: resolution={self.width}x{self.height}, preset={self.dype_preset}, "
f"method={dype_config.method}, scale={dype_config.dype_scale:.2f}, "
f"exponent={dype_config.dype_exponent:.2f}, start_sigma={dype_config.dype_start_sigma:.2f}, "
f"base_resolution={dype_config.base_resolution}"
)
else:
context.logger.debug(f"DyPE disabled: resolution={self.width}x{self.height}, preset={self.dype_preset}")

x = denoise(
model=transformer,
Expand Down
15 changes: 14 additions & 1 deletion invokeai/backend/flux/dype/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,24 @@

from invokeai.backend.flux.dype.base import DyPEConfig
from invokeai.backend.flux.dype.embed import DyPEEmbedND
from invokeai.backend.flux.dype.presets import DyPEPreset, get_dype_config_for_resolution
from invokeai.backend.flux.dype.presets import (
DYPE_PRESET_4K,
DYPE_PRESET_AUTO,
DYPE_PRESET_LABELS,
DYPE_PRESET_MANUAL,
DYPE_PRESET_OFF,
DyPEPreset,
get_dype_config_for_resolution,
)

__all__ = [
"DyPEConfig",
"DyPEEmbedND",
"DyPEPreset",
"DYPE_PRESET_OFF",
"DYPE_PRESET_MANUAL",
"DYPE_PRESET_AUTO",
"DYPE_PRESET_4K",
"DYPE_PRESET_LABELS",
"get_dype_config_for_resolution",
]
46 changes: 40 additions & 6 deletions invokeai/backend/flux/dype/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,17 @@ def compute_vision_yarn_freqs(
The NTK-aware approach smoothly interpolates frequencies to cover larger
position ranges without breaking the attention patterns.

DyPE (Dynamic Position Extrapolation) modulates the NTK scaling based on
the current timestep - stronger extrapolation in early steps (global structure),
weaker in late steps (fine details).

Args:
pos: Position tensor
dim: Embedding dimension
theta: RoPE base frequency
scale_h: Height scaling factor
scale_w: Width scaling factor
current_sigma: Current noise level (reserved for future timestep-aware scaling)
current_sigma: Current noise level (1.0 = full noise, 0.0 = clean)
dype_config: DyPE configuration

Returns:
Expand All @@ -124,7 +128,24 @@ def compute_vision_yarn_freqs(
# This increases the wavelength of position encodings proportionally
if scale > 1.0:
ntk_alpha = scale ** (dim / (dim - 2))
scaled_theta = theta * ntk_alpha

# Apply timestep-dependent DyPE modulation
# mscale controls how strongly we apply the NTK extrapolation
# Early steps (high sigma): stronger extrapolation for global structure
# Late steps (low sigma): weaker extrapolation for fine details
mscale = get_timestep_mscale(
scale=scale,
current_sigma=current_sigma,
dype_scale=dype_config.dype_scale,
dype_exponent=dype_config.dype_exponent,
dype_start_sigma=dype_config.dype_start_sigma,
)

# Modulate NTK alpha by mscale
# When mscale > 1: interpolate towards stronger extrapolation
# When mscale = 1: use base NTK alpha
modulated_alpha = 1.0 + (ntk_alpha - 1.0) * mscale
scaled_theta = theta * modulated_alpha
else:
scaled_theta = theta

Expand All @@ -151,14 +172,15 @@ def compute_yarn_freqs(
) -> tuple[Tensor, Tensor]:
"""Compute RoPE frequencies using YARN/NTK method.

Uses NTK-aware theta scaling for high-resolution support.
Uses NTK-aware theta scaling for high-resolution support with
timestep-dependent DyPE modulation.

Args:
pos: Position tensor
dim: Embedding dimension
theta: RoPE base frequency
scale: Uniform scaling factor
current_sigma: Current noise level (reserved for future use)
current_sigma: Current noise level (1.0 = full noise, 0.0 = clean)
dype_config: DyPE configuration

Returns:
Expand All @@ -169,10 +191,22 @@ def compute_yarn_freqs(
device = pos.device
dtype = torch.float64 if device.type != "mps" else torch.float32

# NTK-aware theta scaling
# NTK-aware theta scaling with DyPE modulation
if scale > 1.0:
ntk_alpha = scale ** (dim / (dim - 2))
scaled_theta = theta * ntk_alpha

# Apply timestep-dependent DyPE modulation
mscale = get_timestep_mscale(
scale=scale,
current_sigma=current_sigma,
dype_scale=dype_config.dype_scale,
dype_exponent=dype_config.dype_exponent,
dype_start_sigma=dype_config.dype_start_sigma,
)

# Modulate NTK alpha by mscale
modulated_alpha = 1.0 + (ntk_alpha - 1.0) * mscale
scaled_theta = theta * modulated_alpha
else:
scaled_theta = theta

Expand Down
77 changes: 42 additions & 35 deletions invokeai/backend/flux/dype/presets.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
"""DyPE presets and automatic configuration."""

from dataclasses import dataclass
from enum import Enum
from typing import Literal

from invokeai.backend.flux.dype.base import DyPEConfig


class DyPEPreset(str, Enum):
"""Predefined DyPE configurations."""

OFF = "off" # DyPE disabled
AUTO = "auto" # Automatically enable based on resolution
PRESET_4K = "4k" # Optimized for 3840x2160 / 4096x2160
# DyPE preset type - using Literal for proper frontend dropdown support
DyPEPreset = Literal["off", "manual", "auto", "4k"]

# Constants for preset values
DYPE_PRESET_OFF: DyPEPreset = "off"
DYPE_PRESET_MANUAL: DyPEPreset = "manual"
DYPE_PRESET_AUTO: DyPEPreset = "auto"
DYPE_PRESET_4K: DyPEPreset = "4k"

# Human-readable labels for the UI
DYPE_PRESET_LABELS: dict[str, str] = {
"off": "Off",
"manual": "Manual",
"auto": "Auto (>1536px)",
"4k": "4K Optimized",
}


@dataclass
Expand All @@ -27,7 +36,7 @@ class DyPEPresetConfig:

# Predefined preset configurations
DYPE_PRESETS: dict[DyPEPreset, DyPEPresetConfig] = {
DyPEPreset.PRESET_4K: DyPEPresetConfig(
DYPE_PRESET_4K: DyPEPresetConfig(
base_resolution=1024,
method="vision_yarn",
dype_scale=2.0,
Expand Down Expand Up @@ -92,41 +101,39 @@ def get_dype_config_from_preset(
preset: The DyPE preset to use
width: Target image width
height: Target image height
custom_scale: Optional custom dype_scale (overrides preset)
custom_exponent: Optional custom dype_exponent (overrides preset)
custom_scale: Optional custom dype_scale (only used with 'on' preset)
custom_exponent: Optional custom dype_exponent (only used with 'on' preset)

Returns:
DyPEConfig if DyPE should be enabled, None otherwise
"""
if preset == DyPEPreset.OFF:
# Check if custom values are provided even with preset=OFF
if custom_scale is not None:
return DyPEConfig(
enable_dype=True,
base_resolution=1024,
method="vision_yarn",
dype_scale=custom_scale,
dype_exponent=custom_exponent if custom_exponent is not None else 2.0,
dype_start_sigma=1.0,
)
if preset == DYPE_PRESET_OFF:
return None

if preset == DyPEPreset.AUTO:
config = get_dype_config_for_resolution(
if preset == DYPE_PRESET_MANUAL:
# Manual mode - custom values can override defaults
max_dim = max(width, height)
scale = max_dim / 1024
dynamic_dype_scale = min(2.0 * scale, 8.0)
return DyPEConfig(
enable_dype=True,
base_resolution=1024,
method="vision_yarn",
dype_scale=custom_scale if custom_scale is not None else dynamic_dype_scale,
dype_exponent=custom_exponent if custom_exponent is not None else 2.0,
dype_start_sigma=1.0,
)

if preset == DYPE_PRESET_AUTO:
# Auto preset - custom values are ignored
return get_dype_config_for_resolution(
width=width,
height=height,
base_resolution=1024,
activation_threshold=1536,
)
# Apply custom overrides if provided
if config is not None:
if custom_scale is not None:
config.dype_scale = custom_scale
if custom_exponent is not None:
config.dype_exponent = custom_exponent
return config

# Use preset configuration

# Use preset configuration (4K etc.) - custom values are ignored
preset_config = DYPE_PRESETS.get(preset)
if preset_config is None:
return None
Expand All @@ -135,7 +142,7 @@ def get_dype_config_from_preset(
enable_dype=True,
base_resolution=preset_config.base_resolution,
method=preset_config.method,
dype_scale=custom_scale if custom_scale is not None else preset_config.dype_scale,
dype_exponent=custom_exponent if custom_exponent is not None else preset_config.dype_exponent,
dype_scale=preset_config.dype_scale,
dype_exponent=preset_config.dype_exponent,
dype_start_sigma=preset_config.dype_start_sigma,
)
22 changes: 21 additions & 1 deletion invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,9 @@
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)",
"clipSkip": "$t(parameters.clipSkip)",
"createdBy": "Created By",
"dypePreset": "DyPE Preset",
"dypePreset": "$t(parameters.dypePreset)",
"dypeScale": "$t(parameters.dypeScale)",
"dypeExponent": "$t(parameters.dypeExponent)",
"generationMode": "Generation Mode",
"guidance": "Guidance",
"height": "Height",
Expand Down Expand Up @@ -1381,6 +1383,8 @@
"scaledWidth": "Scaled W",
"scheduler": "Scheduler",
"dypePreset": "DyPE",
"dypeScale": "DyPE λs",
"dypeExponent": "DyPE λt",
"seamlessXAxis": "Seamless X Axis",
"seamlessYAxis": "Seamless Y Axis",
"colorCompensation": "Color Compensation",
Expand Down Expand Up @@ -1626,6 +1630,22 @@
"Off: Standard generation. Auto: Automatically enables for images > 1536px. 4K: Optimized settings for 4K resolution output."
]
},
"fluxDypeScale": {
"heading": "DyPE Scale (λs)",
"paragraphs": [
"Controls the magnitude of the DyPE modulation. Higher values = stronger extrapolation.",
"Default: 2.0. Range: 0.0-8.0."
]
},
"fluxDypeExponent": {
"heading": "DyPE Exponent (λt)",
"paragraphs": [
"Controls the strength of the dynamic effect over time.",
"2.0: Recommended for 4K+ resolutions. Aggressive schedule that transitions quickly to clean up artifacts.",
"1.0: Good starting point for ~2K-3K resolutions.",
"0.5: Gentler schedule for resolutions just above native (1024px)."
]
},
"seedVarianceEnhancer": {
"heading": "Seed Variance Enhancer",
"paragraphs": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import denoisingStrength from 'public/assets/images/denoising-strength.png';
export type Feature =
| 'clipSkip'
| 'fluxDypePreset'
| 'fluxDypeScale'
| 'fluxDypeExponent'
| 'hrf'
| 'paramNegativeConditioning'
| 'paramPositiveConditioning'
Expand Down Expand Up @@ -92,6 +94,12 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
fluxDypePreset: {
placement: 'right',
},
fluxDypeScale: {
placement: 'right',
},
fluxDypeExponent: {
placement: 'right',
},
inpainting: {
href: 'https://support.invoke.ai/support/solutions/articles/151000096702-inpainting-outpainting-and-bounding-box',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ const slice = createSlice({
setFluxScheduler: (state, action: PayloadAction<'euler' | 'heun' | 'lcm'>) => {
state.fluxScheduler = action.payload;
},
setFluxDypePreset: (state, action: PayloadAction<'off' | 'auto' | '4k'>) => {
setFluxDypePreset: (state, action: PayloadAction<'off' | 'manual' | 'auto' | '4k'>) => {
state.fluxDypePreset = action.payload;
},
setFluxDypeScale: (state, action: PayloadAction<number>) => {
state.fluxDypeScale = action.payload;
},
setFluxDypeExponent: (state, action: PayloadAction<number>) => {
state.fluxDypeExponent = action.payload;
},
setZImageScheduler: (state, action: PayloadAction<'euler' | 'heun' | 'lcm'>) => {
state.zImageScheduler = action.payload;
},
Expand Down Expand Up @@ -488,6 +494,8 @@ export const {
setScheduler,
setFluxScheduler,
setFluxDypePreset,
setFluxDypeScale,
setFluxDypeExponent,
setZImageScheduler,
setZImageSeedVarianceEnabled,
setZImageSeedVarianceStrength,
Expand Down Expand Up @@ -638,6 +646,8 @@ export const selectModelSupportsOptimizedDenoising = createSelector(
export const selectScheduler = createParamsSelector((params) => params.scheduler);
export const selectFluxScheduler = createParamsSelector((params) => params.fluxScheduler);
export const selectFluxDypePreset = createParamsSelector((params) => params.fluxDypePreset);
export const selectFluxDypeScale = createParamsSelector((params) => params.fluxDypeScale);
export const selectFluxDypeExponent = createParamsSelector((params) => params.fluxDypeExponent);
export const selectZImageScheduler = createParamsSelector((params) => params.zImageScheduler);
export const selectZImageSeedVarianceEnabled = createParamsSelector((params) => params.zImageSeedVarianceEnabled);
export const selectZImageSeedVarianceStrength = createParamsSelector((params) => params.zImageSeedVarianceStrength);
Expand Down
Loading