{"record":{"id":"180d3e3a6cd41012","repo":"invoke-ai/InvokeAI","slug":"got-params-axes-dim-but-expected-positional-dim-180d3e","errorCode":null,"errorMessage":"Got {params.axes_dim} but expected positional dim {pe_dim}","messagePattern":"Got (.+?) but expected positional dim (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"invokeai/backend/flux/controlnet/xlabs_controlnet_flux.py","lineNumber":39,"sourceCode":"    \"\"\"A ControlNet model for FLUX.\n\n    The architecture is very similar to the base FLUX model, with the following differences:\n    - A `controlnet_depth` parameter is passed to control the number of double_blocks that the ControlNet is applied to.\n      In order to keep the ControlNet small, this is typically much less than the depth of the base FLUX model.\n    - There is a set of `controlnet_blocks` that are applied to the output of each double_block.\n    \"\"\"\n\n    def __init__(self, params: FluxParams, controlnet_depth: int = 2):\n        super().__init__()\n\n        self.params = params\n        self.in_channels = params.in_channels\n        self.out_channels = self.in_channels\n        if params.hidden_size % params.num_heads != 0:\n            raise ValueError(f\"Hidden size {params.hidden_size} must be divisible by num_heads {params.num_heads}\")\n        pe_dim = params.hidden_size // params.num_heads\n        if sum(params.axes_dim) != pe_dim:\n            raise ValueError(f\"Got {params.axes_dim} but expected positional dim {pe_dim}\")\n        self.hidden_size = params.hidden_size\n        self.num_heads = params.num_heads\n        self.pe_embedder = EmbedND(dim=pe_dim, theta=params.theta, axes_dim=params.axes_dim)\n        self.img_in = torch.nn.Linear(self.in_channels, self.hidden_size, bias=True)\n        self.time_in = MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size)\n        self.vector_in = MLPEmbedder(params.vec_in_dim, self.hidden_size)\n        self.guidance_in = (\n            MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size) if params.guidance_embed else torch.nn.Identity()\n        )\n        self.txt_in = torch.nn.Linear(params.context_in_dim, self.hidden_size)\n\n        self.double_blocks = torch.nn.ModuleList(\n            [\n                DoubleStreamBlock(\n                    self.hidden_size,\n                    self.num_heads,\n                    mlp_ratio=params.mlp_ratio,\n                    qkv_bias=params.qkv_bias,","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/backend/flux/controlnet/xlabs_controlnet_flux.py#L21-L57","documentation":"After checking head divisibility, XLabsControlNetFlux.__init__ derives pe_dim = hidden_size // num_heads and requires sum(params.axes_dim) — the total rotary-embedding dimension across axes — to equal pe_dim. Mismatched axes_dim means the EmbedND positional embedder would produce embeddings incompatible with the attention head dim, so init fails.","triggerScenarios":"Constructing XLabsControlNetFlux where sum(params.axes_dim) != params.hidden_size // params.num_heads — typically default axes_dim=[16,56,56] (128) paired with a modified hidden_size/num_heads giving a different pe_dim.","commonSituations":"Scaling hidden_size for a custom model while keeping stock axes_dim; porting XLabs IP-Adapter/ControlNet configs onto a differently-shaped Flux backbone; typos in config files (e.g. axes_dim=[16,56] summing to 72).","solutions":["Set axes_dim so its sum equals hidden_size // num_heads (stock Flux: [16,56,56] for pe_dim=128).","Recompute axes_dim proportionally if you change hidden_size or num_heads.","Load params from the checkpoint's official config instead of hand-building FluxParams.","Assert sum(params.axes_dim) == params.hidden_size // params.num_heads in setup code to fail fast with a clearer message."],"exampleFix":"// before\nFluxParams(hidden_size=2048, num_heads=16, axes_dim=[16,56,56])  # pe_dim=128 != sum=128? -> ensure: 2048/16=128 OK; bad case: num_heads=32 → pe_dim=64\n// after\nFluxParams(hidden_size=2048, num_heads=16, axes_dim=[16,56,56])  # 2048//16 = 128 == 16+56+56","handlingStrategy":"validation","validationCode":"pe_dim = params.hidden_size // params.num_heads\nassert params.hidden_size % params.num_heads == 0\nassert sum(params.axes_dim) == pe_dim, f\"axes_dim sum {sum(params.axes_dim)} != pe_dim {pe_dim}\"","typeGuard":"def has_valid_rope_config(p) -> bool:\n    return p.hidden_size % p.num_heads == 0 and sum(p.axes_dim) == p.hidden_size // p.num_heads","tryCatchPattern":"try:\n    controlnet = XLabsControlNetFlux(params=params)\nexcept ValueError as e:\n    if \"expected positional dim\" in str(e):\n        params = replace(params, axes_dim=rescale_axes(params.axes_dim, params.hidden_size // params.num_heads))\n        controlnet = XLabsControlNetFlux(params=params)\n    else:\n        raise","preventionTips":["Treat axes_dim, hidden_size, and num_heads as one coupled config unit.","When scaling the model, rescale axes_dim proportionally so its sum tracks hidden_size/num_heads.","Centralize FluxParams construction in one factory function with these assertions."],"tags":["config","validation","positional-embedding","flux"],"backgroundTag":"axes-dim-positional-dim-mismatch","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}