#1906·YOLOX

Checkpoint loading breaks on PyTorch 2.6+: `torch.load` defaults to `weights_only=True`

Author: HikeAndMapCreated Aug 29, 2026Updated Aug 29, 2026

torch.load calls in tools/export_onnx.py and yolox/core/trainer.py (resume_train) don't pass weights_only, so on PyTorch >=2.6 they pick up the new default of True, which uses a restricted unpickler. Checkpoints that were evaluated at least once fail to load with UnpicklingError.

Root cause

yolox/core/trainer.py's save_ckpt stores whatever ap value is passed in as "curr_ap":

python
ckpt_state = {
    "start_epoch": self.epoch + 1,
    "model": save_model.state_dict(),
    "optimizer": self.optimizer.state_dict(),
    "best_ap": self.best_ap,
    "curr_ap": ap,
}

evaluate_and_save_model passes ap50_95, which comes from _do_python_eval's return np.mean(mAPs), mAPs[0] in yolox/data/datasets/voc.py - a raw numpy.float64, not a Python float. PyTorch 2.6's weights_only=True unpickler rejects numpy._core.multiarray.scalar by default, so any checkpoint saved through evaluate_and_save_model (i.e. best_ckpt.pth, last_epoch_ckpt.pth) fails to load with plain torch.load(ckpt_file, ...).

after_epoch's save_ckpt(ckpt_name="latest") never passes ap (defaults to None), so latest_ckpt.pth doesn't hit this - which is why resuming training can work fine while loading best_ckpt.pth (e.g. for ONNX export) fails.

Environment

  • PyTorch: 2.13.0 (also affects any 2.6+)
  • YOLOX: fresh clone of main, installed via pip install -e .

Steps to reproduce

  1. Train to at least one evaluation epoch so best_ckpt.pth/last_epoch_ckpt.pth gets written.
  2. Try to load that checkpoint, e.g. via tools/export_onnx.py -c .../best_ckpt.pth.
  3. Traceback:
File ".../torch/serialization.py", line 1609, in load
    raise pickle.UnpicklingError(_get_wo_message(str(e))) from None
_pickle.UnpicklingError: Weights only load failed ...
WeightsUnpickler error: Unsupported global: GLOBAL numpy._core.multiarray.scalar was not an allowed global by default.

Suggested fix

Pass weights_only=False explicitly at each torch.load call site (these are trusted, locally-produced checkpoints, not untrusted downloads):

python
ckpt = torch.load(ckpt_file, map_location="cpu", weights_only=False)

Alternatively, cast ap to a plain Python float before storing it in the checkpoint dict (float(ap) in save_ckpt), which would let curr_ap load fine under the restricted unpickler without needing weights_only=False anywhere. Either fix resolves it - verified the weights_only=False approach end-to-end (checkpoint loaded, export succeeded).

Source: Megvii-BaseDetection/YOLOX