unslothai/unsloth · error · ValueError

cannot rotate {fqn!r}: not an nn.Linear on this model

Error message

cannot rotate {fqn!r}: not an nn.Linear on this model

What it means

During offline weight rotation, one of the requested fully-qualified module names resolved to something that is not an nn.Linear (or to nothing the isinstance check accepts). Only plain Linears can be block-rotated, so the builder refuses rather than rotating a wrong module type.

Source

Thrown at studio/backend/core/inference/diffusion_convrot.py:315

    transformer: Any,
    fqns: Iterable[str],
    group_size: int = DEFAULT_CONVROT_GROUPSIZE,
) -> tuple[str, ...]:
    """OFFLINE half: rotate the weights of ``fqns`` and install the online rotation on each.

    Call BEFORE ``quantize_``, on a dense model: the whole point is that the quantizer sees the
    flatter distribution. Returns the fqns rotated, in the order given. Raises on anything it
    cannot rotate, so a builder can never record a set larger than the one it actually applied."""
    from torch import nn

    if not is_power_of_four(group_size):
        raise ValueError(f"ConvRot group size must be a power of 4, got {group_size!r}")
    modules = dict(transformer.named_modules())
    rotated: list[str] = []
    for fqn in fqns:
        module = modules.get(fqn)
        if not isinstance(module, nn.Linear):
            raise ValueError(f"cannot rotate {fqn!r}: not an nn.Linear on this model")
        rotate_convrot_weight_(module, group_size)
        _install_rotation(module, group_size)
        rotated.append(fqn)
    return tuple(rotated)


def apply_activation_rotation(
    transformer: Any,
    metadata: Any,
    *,
    logger: Any = None,
) -> tuple[str, ...]:
    """ONLINE half: install the input rotation on exactly the fqns ``metadata`` records.

    Returns the fqns rotated, or ``()`` when ``metadata`` declares no rotation -- the plain
    artifacts, which have to be left exactly as they are. RAISES on any other outcome: an
    unusable contract, an fqn this model does not have, a target that is not a Linear, an
    ``in_features`` the recorded group does not divide, or a Linear already rotated. The prequant

View on GitHub (pinned to 203007d190)

Solutions

  1. Print dict(transformer.named_modules()) and correct the fqn to name an actual nn.Linear
  2. Regenerate the fqn list from the live model (filter isinstance(module, nn.Linear)) instead of hardcoding it
  3. If the layer genuinely changed type in this architecture, remove it from the rotation set

Example fix

# before
apply_rotation(transformer, ["model.layers.0.mlp"], 64)  # module is the MLP block

# after
apply_rotation(transformer, ["model.layers.0.mlp.down_proj"], 64)  # an nn.Linear
Defensive patterns

Strategy: type-guard

Validate before calling

from torch import nn

def linear_fqns(transformer) -> list[str]:
    return [fqn for fqn, m in transformer.named_modules() if isinstance(m, nn.Linear)]

Type guard

from torch import nn

def is_plain_linear(module) -> bool:
    """True only for modules the offline rotation accepts."""
    return isinstance(module, nn.Linear)

Try / catch

try:
    apply_rotation(transformer, fqns, group_size)
except ValueError as e:
    raise BuildError(str(e)) from e  # regenerate the fqn list from this model, do not edit by hand

Prevention

When it happens

Trigger: Passing an fqn naming a Conv1d, Embedding, LayerNorm, or a wrapper/override of Linear that fails isinstance(module, nn.Linear); also an fqn that resolves to None is caught by the same check. Happens when the fqn list was written for a different architecture revision.

Common situations: Model architecture updated and a target layer became a fused/quantized wrapper or was renamed; fqn list generated by an older naming pass (e.g. missing or extra '.'); targeting attention projections that this revision implements as a single bundled module.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/8d7dbec8b5d1d65a. Report an issue: GitHub.