invoke-ai/InvokeAI · error · ValueError

Got {params.axes_dim} but expected positional dim {pe_dim}

Error message

Got {params.axes_dim} but expected positional dim {pe_dim}

What it means

InstantXControlNetFlux.__init__ computes the positional-embedding dimension as hidden_size // num_heads and requires the sum of params.axes_dim (the RoPE per-axis dims) to equal that value. If the FluxParams config supplies an axes_dim list whose elements don't sum to pe_dim, the model would be built with an inconsistent rotary embedding, so it refuses to construct. This is a config-integrity check mirroring the upstream flux reference model.

Source

Thrown at invokeai/backend/flux/controlnet/instantx_controlnet_flux.py:59

    def __init__(self, params: FluxParams, num_control_modes: int | None = None):
        """
        Args:
            params (FluxParams): The parameters for the FLUX model.
            num_control_modes (int | None, optional): The number of controlnet modes. If non-None, then the model is a
                'union controlnet' model and expects a mode conditioning input at runtime.
        """
        super().__init__()

        # The following modules mirror the base FLUX transformer model.
        # -------------------------------------------------------------
        self.params = params
        self.in_channels = params.in_channels
        self.out_channels = self.in_channels
        if params.hidden_size % params.num_heads != 0:
            raise ValueError(f"Hidden size {params.hidden_size} must be divisible by num_heads {params.num_heads}")
        pe_dim = params.hidden_size // params.num_heads
        if sum(params.axes_dim) != pe_dim:
            raise ValueError(f"Got {params.axes_dim} but expected positional dim {pe_dim}")
        self.hidden_size = params.hidden_size
        self.num_heads = params.num_heads
        self.pe_embedder = EmbedND(dim=pe_dim, theta=params.theta, axes_dim=params.axes_dim)
        self.img_in = nn.Linear(self.in_channels, self.hidden_size, bias=True)
        self.time_in = MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size)
        self.vector_in = MLPEmbedder(params.vec_in_dim, self.hidden_size)
        self.guidance_in = (
            MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size) if params.guidance_embed else nn.Identity()
        )
        self.txt_in = nn.Linear(params.context_in_dim, self.hidden_size)

        self.double_blocks = nn.ModuleList(
            [
                DoubleStreamBlock(
                    self.hidden_size,
                    self.num_heads,
                    mlp_ratio=params.mlp_ratio,
                    qkv_bias=params.qkv_bias,

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Set params.axes_dim so its elements sum to params.hidden_size // params.num_heads (default Flux: hidden_size=3072, num_heads=24 → pe_dim=128, axes_dim=[16,56,56]).
  2. Adjust num_heads to a divisor of hidden_size that makes hidden_size/num_heads equal your axes_dim sum.
  3. Restore the default FluxParams values matching the checkpoint you loaded.
  4. Verify the checkpoint's config JSON (hidden_size, num_heads, axes_dim) matches what you pass in.

Example fix

// before
params = FluxParams(in_channels=64, hidden_size=2048, num_heads=24, axes_dim=[16,56,56], ...)  # sum=128 != 2048/24
// after
params = FluxParams(in_channels=64, hidden_size=3072, num_heads=24, axes_dim=[16,56,56], ...)  # 3072/24=128 == sum(axes_dim)
Defensive patterns

Strategy: validation

Validate before calling

pe_dim = params.hidden_size // params.num_heads
assert params.hidden_size % params.num_heads == 0, "hidden_size must be divisible by num_heads"
assert sum(params.axes_dim) == pe_dim, f"sum(axes_dim)={sum(params.axes_dim)} != pe_dim={pe_dim}"

Type guard

def is_valid_flux_params(p) -> bool:
    return p.hidden_size % p.num_heads == 0 and sum(p.axes_dim) == p.hidden_size // p.num_heads

Try / catch

try:
    controlnet = InstantXControlNetFlux(params=params)
except ValueError as e:
    if "expected positional dim" in str(e):
        pe_dim = params.hidden_size // params.num_heads
        params = replace(params, axes_dim=scale_axes_dim(params.axes_dim, pe_dim))
        controlnet = InstantXControlNetFlux(params=params)
    else:
        raise

Prevention

When it happens

Trigger: Constructing InstantXControlNetFlux(params=FluxParams(...)) where sum(params.axes_dim) != params.hidden_size // params.num_heads — e.g. custom hidden_size/num_heads copied from another checkpoint while keeping default axes_dim=[16,56,56] (sums to 128).

Common situations: Adapting the ControlNet to a non-standard Flux variant or a distilled model with different head counts; hand-editing FluxParams; porting configs between Flux schnell/dev and XLabs/InstantX checkpoints where pe layouts differ.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/8a95a6a6f3488fdb. Report an issue: GitHub.