jax-ml/jax · error · NotImplementedError
Cannot permute last two dimensions with leading dimensions.
Error message
Cannot permute last two dimensions with leading dimensions.
What it means
Raised by the fuser's transpose usage rule: only permutations that keep the last two dimensions in the last two positions (possibly swapped) are supported. The check set(permutation[-2:]) != {permutation[-1], permutation[-2]} catches permutations where the trailing dims were moved into leading positions or vice versa, which the block-index machinery cannot express.
Source
Thrown at jax/_src/pallas/fuser/block_spec.py:2074
new_permutation = [p for p in permuted_block_dims if p is not None]
return jax.lax.transpose(x, permutation=new_permutation)
@register_pull_block_spec_rule(lax.transpose_p)
def _transpose_pull_rule(
ctx: PullRuleContext,
block_transform: BlockIndexTransform,
*,
permutation: tuple[int, ...],
):
block_shape = block_transform.block_shape
new_shape = tuple(block_shape[i] for i in permutation)
aval_in = ctx.avals_in[0]
assert isinstance(aval_in, core.ShapedArray)
assert len(block_shape) == len(aval_in.shape)
if set(permutation[-2:]) != {permutation[-1], permutation[-2]}:
raise NotImplementedError(
'Cannot permute last two dimensions with leading dimensions.'
)
def new_block_index_transform(*idxs):
original_idxs = block_transform.block_index_transform(*idxs)
return tuple(original_idxs[i] for i in permutation)
return [block_transform.replace(
block_shape=new_shape,
block_index_transform=new_block_index_transform)]
@register_eval_rule(lax.split_p)
def _split_eval_rule(
eval_ctx: KernelEvalContext, x, sizes: Sequence[int], axis: int
):
del eval_ctx
return lax.split(x, sizes=sizes, axis=axis)View on GitHub (pinned to 1e1c6a8fc0)
Solutions
- Restructure to only swap the last two axes (single lax.transpose((..., 1, 0)) on the tail) and handle leading-axis reordering via block index maps or separate reshapes
- Move the general transpose outside the fused kernel and pass the pre-transposed array in
- Express leading-dim permutations through grid/index_map instead of an in-kernel transpose
Example fix
// before y = jnp.transpose(x, (2, 0, 1)) # moves last dim to front: unsupported // after y = jnp.swapaxes(x, -1, -2) # only permutes last two dims: supported
Defensive patterns
Strategy: validation
Validate before calling
def transpose_supported(perm) -> bool:
perm = list(perm)
return set(perm[-2:]) == {perm[-1], perm[-2]}
assert transpose_supported(perm), 'only last-two-dims permutation supported' Type guard
def is_tail_only_perm(perm: tuple[int, ...]) -> bool:
return sorted(perm[-2:]) == [len(perm) - 2, len(perm) - 1] Try / catch
try:
y = fused_t(x, perm)
except NotImplementedError as e:
if 'Cannot permute last two' in str(e):
y = jnp.transpose(x, perm) # outside fusion
else:
raise Prevention
- Restrict in-kernel transposes to swapping the last two axes
- Move N-D transposes out of the fused region
- Encode leading-dim reordering in index_map instead
When it happens
Trigger: Calling lax.transpose / jnp.transpose inside a fused Pallas region with a permutation that moves one of the last two axes to a leading position, e.g. jnp.transpose(x, (2, 0, 1)) for a 3-D operand.
Common situations: General N-D transposes (like NCHW<->NHWC with more than 2 trailing dims, or batched matrix transposes mixing batch axes with matrix axes); refactoring existing kernels that reshape+transpose for matmul layout; JAX version upgrades tightening transpose support in the fuser.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- for transpose support, subclass {type(self)} must implement
- Explicit sharding is not currently supported in the pallas-t
- group_offset is not currently supported in the pallas-triton
- get not supported yet
- get not supported yet for block shape {b}
AI-assisted analysis of jax-ml/jax@1e1c6a8fc0 (2026-08-27).
Data as JSON: /api/errors/ddb6f8adbac02877.
Report an issue: GitHub.