Skip to content

phimoe.py subscripts rope_scaling unconditionally, so Phi-mini-MoE-instruct dies with a bare TypeError #1884

Description

@hijohnnylin
  • AI Usage Notice: We found this with Opus. Code is easily reproducible below.

microsoft/Phi-mini-MoE-instruct has "rope_scaling": null in its config.json. phimoe.py
reads four keys out of that field while building attention, so loading the checkpoint raises before
any weight is touched.

Versions: mlx-lm 0.31.3, mlx 0.32.2, Apple M3 Max.

Runnable repro

Part A needs no download and no checkpoint at all: ModelArgs.rope_scaling already defaults to
None
, and the only code that reads it subscripts it four times, so the port contradicts its own
declared default. Part B is the checkpoint that made this visible.

pip install "mlx-lm>=0.31.3"
python repro_phimoe.py          # Part A only -- instant, no download
python repro_phimoe.py --real   # adds Part B (~7.6 GB)
"""Repro: mlx-lm's phimoe port cannot build a model whose `rope_scaling` is null.

Part A needs no download and no checkpoint -- `ModelArgs.rope_scaling` already defaults to
None, and the code that reads it cannot accept None, so the port contradicts its own default.
Part B is the real checkpoint (~7.6 GB) that made this visible.

    pip install "mlx-lm>=0.31.3"
    python repro_phimoe.py          # Part A only
    python repro_phimoe.py --real   # adds Part B
"""

import sys
import traceback

from mlx_lm.models import phimoe

print("=" * 78)
print("A. mlx-lm's own default, no checkpoint and no download")
print("=" * 78)

args = phimoe.ModelArgs(
    # Small so the failure is quick; the shape has nothing to do with the bug.
    hidden_size=64,
    intermediate_size=64,
    num_hidden_layers=1,
    num_attention_heads=4,
    num_key_value_heads=4,
    vocab_size=32,
    num_local_experts=2,
    num_experts_per_tok=1,
)
print(f"ModelArgs.rope_scaling = {args.rope_scaling!r}   <- the dataclass's declared default")

try:
    phimoe.Model(args)
except TypeError:
    frames = traceback.extract_tb(sys.exc_info()[2])
    last = frames[-1]
    print(f"\n{type(sys.exc_info()[1]).__name__}: {sys.exc_info()[1]}")
    print(f"  raised at {last.filename.split('/')[-1]}:{last.lineno}")
    print(f"  line:     {last.line}")
    print("\nThe field defaults to None and the only code that reads it subscripts it four times.")
else:
    raise SystemExit("no error -- this mlx-lm version has been fixed")

if "--real" not in sys.argv:
    print("\n(pass --real to also run Part B against microsoft/Phi-mini-MoE-instruct)")
    raise SystemExit(0)

print()
print("=" * 78)
print("B. the checkpoint this was found on: microsoft/Phi-mini-MoE-instruct")
print("=" * 78)
print('Its config.json carries "rope_scaling": null, so the same line is reached.')

from mlx_lm import load  # noqa: E402

try:
    load("microsoft/Phi-mini-MoE-instruct")
except TypeError:
    frames = traceback.extract_tb(sys.exc_info()[2])
    ours = [f for f in frames if "mlx_lm" in (f.filename or "")]
    print(f"\n{type(sys.exc_info()[1]).__name__}: {sys.exc_info()[1]}")
    print(f"  raised at {ours[-1].filename.split('/')[-1]}:{ours[-1].lineno}")
    print(f"\nmlx_lm frames in the traceback: {len(ours)} of {len(frames)}")
    print("Nothing in the message names `rope_scaling`, the config, or the checkpoint, which is")
    print("why this reads as a bug in whatever called load().")
else:
    raise SystemExit("no error -- this mlx-lm version has been fixed")

Output on mlx-lm 0.31.3 / mlx 0.32.2, Apple M3 Max:

==============================================================================
A. mlx-lm's own default, no checkpoint and no download
==============================================================================
ModelArgs.rope_scaling = None   <- the dataclass's declared default

TypeError: 'NoneType' object is not subscriptable
  raised at phimoe.py:53
  line:     short_factor=args.rope_scaling["short_factor"],

The field defaults to None and the only code that reads it subscripts it four times.

==============================================================================
B. the checkpoint this was found on: microsoft/Phi-mini-MoE-instruct
==============================================================================
Its config.json carries "rope_scaling": null, so the same line is reached.

TypeError: 'NoneType' object is not subscriptable
  raised at phimoe.py:53

mlx_lm frames in the traceback: 6 of 7

Cause

mlx_lm/models/phimoe.py, in Attention.__init__:

self.rope = SuScaledRoPE(
    head_dim,
    base=args.rope_theta,
    max_position_embeddings=args.max_position_embeddings,
    original_max_position_embeddings=args.original_max_position_embeddings,
    short_factor=args.rope_scaling["short_factor"],   # <- line 53
    long_factor=args.rope_scaling["long_factor"],
    short_mscale=args.rope_scaling["short_mscale"],
    long_mscale=args.rope_scaling["long_mscale"],
)

rope_scaling is optional in the Phi-MoE config, and this is the only place it is read. A config
that omits it or sets it to null means plain RoPE.

The traceback is the other half of the problem: it contains only mlx-lm frames and a bare
TypeError with no field name, no config key and no checkpoint name, so it reads as a bug in
whatever called load(). It cost a release's worth of misattribution here before I read the port.

Suggested fix

Fall back to plain RoPE when the field is absent:

scaling = args.rope_scaling
if scaling is None:
    self.rope = nn.RoPE(head_dim, traditional=False, base=args.rope_theta)
else:
    self.rope = SuScaledRoPE(...)

A clearer failure would help even without the fallback — raising ValueError naming
rope_scaling and the checkpoint would have made this a one-minute diagnosis rather than an
afternoon.

I am happy to open a PR if that is useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions