pytorch - 💡(How to fix) Fix torch.export.save / torch.export.load does not support opaque type constants registered via register_opaque_type

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

torch.export.save fails with RuntimeError: Unsupported constant type when the ExportedProgram contains constants whose types are registered via torch._library.opaque_object.register_opaque_type. This blocks any custom operator workflow that uses opaque types as graph-captured constants (e.g. engine handles, runtime contexts, etc.).

Error Message

Export succeeded Constants: {'lifted_custom_0': 'MyEngine'} Traceback (most recent call last): File "repro.py", line 36, in <module> torch.export.save(ep, "/tmp/model.pt2") File ".../torch/export/init.py", line 274, in save package_pt2( File ".../torch/export/pt2_archive/_package.py", line 723, in package_pt2 _package_exported_programs( File ".../torch/export/pt2_archive/_package.py", line 614, in _package_exported_programs constants_config = _package_constants( ^^^^^^^^^^^^^^^^^^^ File ".../torch/export/pt2_archive/_package.py", line 510, in _package_constants raise RuntimeError(f"Unsupported constant type: {type(constant)}") RuntimeError: Unsupported constant type: <class 'main.MyEngine'>

Root Cause

Three locations in the torch.export internals only handle torch.Tensor and torch._C.ScriptObject, and do not account for Python opaque types:

1. torch/export/pt2_archive/_package.py_package_constants

The constant categorization loop raises on anything that isn't a Tensor or ScriptObject:

for constant_fqn, constant in exported_program.constants.items():
    if isinstance(constant, torch.Tensor):
        ...
    elif isinstance(constant, torch._C.ScriptObject):
        custom_objects.append((constant_fqn, constant))
    else:
        raise RuntimeError(f"Unsupported constant type: {type(constant)}")

Opaque types are picklable Python objects but are not ScriptObjects, so they cannot use torch._C._pickle_save. They should be serialized with standard pickle.dumps.

2. torch/export/pt2_archive/_package.py_load_constants

The loading path uses torch._C._pickle_load_obj for everything under CUSTOM_OBJ_FILENAME_PREFIX, which fails for objects serialized with Python pickle:

elif path_name.startswith(CUSTOM_OBJ_FILENAME_PREFIX):
    constant_bytes = archive_reader.read_bytes(...)
    constants[constant_fqn] = torch._C._pickle_load_obj(constant_bytes)

3. torch/export/unflatten.py_assign_attr

When unlifting constants back onto the GraphModule, only torch.Tensor and torch.ScriptObject are permitted:

elif attr_kind == _AttrKind.CONSTANT:
    if not isinstance(from_obj, (torch.Tensor, torch.ScriptObject)):
        raise AssertionError(
            f"expected torch.Tensor or torch.ScriptObject for CONSTANT attr_kind, got {type(from_obj)}"
        )

Code Example

from torch._library.opaque_object import register_opaque_type
from torch._opaque_base import OpaqueBase
class MyEngine(OpaqueBase):
    def __getstate__(self): ...
    def __setstate__(self, state): ...
register_opaque_type(MyEngine, typ="reference")

---

import torch
from torch._library.opaque_object import register_opaque_type
from torch._opaque_base import OpaqueBase
class MyEngine(OpaqueBase):
    def __init__(self, data):
        self.data = data
    def __getstate__(self):
        return {"data": self.data}
    def __setstate__(self, state):
        self.data = state["data"]
register_opaque_type(MyEngine, typ="reference")
@torch.library.custom_op("mylib::run_engine", mutates_args=())
def run_engine(x: torch.Tensor, engine: MyEngine) -> torch.Tensor:
    return x * engine.data
@run_engine.register_fake
def run_engine_fake(x: torch.Tensor, engine: MyEngine) -> torch.Tensor:
    return torch.empty_like(x)
class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.engine = MyEngine(2.0)
    def forward(self, x):
        return torch.ops.mylib.run_engine(x, self.engine)
m = Model()
ep = torch.export.export(m, (torch.randn(4),))
print("Export succeeded")
print("Constants:", {k: type(v).__name__ for k, v in ep.constants.items()})
torch.export.save(ep, "/tmp/model.pt2")  # RuntimeError here
print("Save succeeded")

---

Export succeeded
Constants: {'lifted_custom_0': 'MyEngine'}
Traceback (most recent call last):
  File "repro.py", line 36, in <module>
    torch.export.save(ep, "/tmp/model.pt2")
  File ".../torch/export/__init__.py", line 274, in save
    package_pt2(
  File ".../torch/export/pt2_archive/_package.py", line 723, in package_pt2
    _package_exported_programs(
  File ".../torch/export/pt2_archive/_package.py", line 614, in _package_exported_programs
    constants_config = _package_constants(
                       ^^^^^^^^^^^^^^^^^^^
  File ".../torch/export/pt2_archive/_package.py", line 510, in _package_constants
    raise RuntimeError(f"Unsupported constant type: {type(constant)}")
RuntimeError: Unsupported constant type: <class '__main__.MyEngine'>

---

for constant_fqn, constant in exported_program.constants.items():
    if isinstance(constant, torch.Tensor):
        ...
    elif isinstance(constant, torch._C.ScriptObject):
        custom_objects.append((constant_fqn, constant))
    else:
        raise RuntimeError(f"Unsupported constant type: {type(constant)}")

---

elif path_name.startswith(CUSTOM_OBJ_FILENAME_PREFIX):
    constant_bytes = archive_reader.read_bytes(...)
    constants[constant_fqn] = torch._C._pickle_load_obj(constant_bytes)

---

elif attr_kind == _AttrKind.CONSTANT:
    if not isinstance(from_obj, (torch.Tensor, torch.ScriptObject)):
        raise AssertionError(
            f"expected torch.Tensor or torch.ScriptObject for CONSTANT attr_kind, got {type(from_obj)}"
        )
RAW_BUFFERClick to expand / collapse

🚀 The feature, motivation and pitch

torch.export.save / torch.export.load does not support opaque type constants registered via register_opaque_type

Description

torch.export.save fails with RuntimeError: Unsupported constant type when the ExportedProgram contains constants whose types are registered via torch._library.opaque_object.register_opaque_type. This blocks any custom operator workflow that uses opaque types as graph-captured constants (e.g. engine handles, runtime contexts, etc.).

Motivation

Custom operator libraries (e.g. Torch-TensorRT) register Python classes as opaque types so they can be captured as constants in an exported graph:

from torch._library.opaque_object import register_opaque_type
from torch._opaque_base import OpaqueBase
class MyEngine(OpaqueBase):
    def __getstate__(self): ...
    def __setstate__(self, state): ...
register_opaque_type(MyEngine, typ="reference")

These objects flow through the graph as constants and are arguments to custom ops. torch.export.export traces them correctly and they appear in exported_program.constants. However, torch.export.save cannot serialize them because _package_constants only recognizes torch.Tensor and torch._C.ScriptObject.

Steps to Reproduce

import torch
from torch._library.opaque_object import register_opaque_type
from torch._opaque_base import OpaqueBase
class MyEngine(OpaqueBase):
    def __init__(self, data):
        self.data = data
    def __getstate__(self):
        return {"data": self.data}
    def __setstate__(self, state):
        self.data = state["data"]
register_opaque_type(MyEngine, typ="reference")
@torch.library.custom_op("mylib::run_engine", mutates_args=())
def run_engine(x: torch.Tensor, engine: MyEngine) -> torch.Tensor:
    return x * engine.data
@run_engine.register_fake
def run_engine_fake(x: torch.Tensor, engine: MyEngine) -> torch.Tensor:
    return torch.empty_like(x)
class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.engine = MyEngine(2.0)
    def forward(self, x):
        return torch.ops.mylib.run_engine(x, self.engine)
m = Model()
ep = torch.export.export(m, (torch.randn(4),))
print("Export succeeded")
print("Constants:", {k: type(v).__name__ for k, v in ep.constants.items()})
torch.export.save(ep, "/tmp/model.pt2")  # RuntimeError here
print("Save succeeded")

Error:

Export succeeded
Constants: {'lifted_custom_0': 'MyEngine'}
Traceback (most recent call last):
  File "repro.py", line 36, in <module>
    torch.export.save(ep, "/tmp/model.pt2")
  File ".../torch/export/__init__.py", line 274, in save
    package_pt2(
  File ".../torch/export/pt2_archive/_package.py", line 723, in package_pt2
    _package_exported_programs(
  File ".../torch/export/pt2_archive/_package.py", line 614, in _package_exported_programs
    constants_config = _package_constants(
                       ^^^^^^^^^^^^^^^^^^^
  File ".../torch/export/pt2_archive/_package.py", line 510, in _package_constants
    raise RuntimeError(f"Unsupported constant type: {type(constant)}")
RuntimeError: Unsupported constant type: <class '__main__.MyEngine'>

Root Cause

Three locations in the torch.export internals only handle torch.Tensor and torch._C.ScriptObject, and do not account for Python opaque types:

1. torch/export/pt2_archive/_package.py_package_constants

The constant categorization loop raises on anything that isn't a Tensor or ScriptObject:

for constant_fqn, constant in exported_program.constants.items():
    if isinstance(constant, torch.Tensor):
        ...
    elif isinstance(constant, torch._C.ScriptObject):
        custom_objects.append((constant_fqn, constant))
    else:
        raise RuntimeError(f"Unsupported constant type: {type(constant)}")

Opaque types are picklable Python objects but are not ScriptObjects, so they cannot use torch._C._pickle_save. They should be serialized with standard pickle.dumps.

2. torch/export/pt2_archive/_package.py_load_constants

The loading path uses torch._C._pickle_load_obj for everything under CUSTOM_OBJ_FILENAME_PREFIX, which fails for objects serialized with Python pickle:

elif path_name.startswith(CUSTOM_OBJ_FILENAME_PREFIX):
    constant_bytes = archive_reader.read_bytes(...)
    constants[constant_fqn] = torch._C._pickle_load_obj(constant_bytes)

3. torch/export/unflatten.py_assign_attr

When unlifting constants back onto the GraphModule, only torch.Tensor and torch.ScriptObject are permitted:

elif attr_kind == _AttrKind.CONSTANT:
    if not isinstance(from_obj, (torch.Tensor, torch.ScriptObject)):
        raise AssertionError(
            f"expected torch.Tensor or torch.ScriptObject for CONSTANT attr_kind, got {type(from_obj)}"
        )

Proposed Fix

Treat registered opaque types (is_opaque_type) as a third constant category alongside Tensor and ScriptObject. Serialize them with pickle.dumps (since they are Python objects, not C++ ScriptObjects), deserialize with pickle.loads, and allow them through the _assign_attr type check.

Versions

  • PyTorch: 2.12.0.dev20260409+cu130 (nightly)
  • Python: 3.12

Alternatives

No response

Additional context

No response

cc @chauhang @penguinwu @avikchaudhuri @zhxchen17 @tugsbayasgalan @angelayi @ydwu4

extent analysis

TL;DR

Modify the torch.export internals to support serialization and deserialization of opaque types registered via register_opaque_type by treating them as a third constant category.

Guidance

  • Identify the locations in the torch.export internals that need to be modified to support opaque types, specifically _package_constants, _load_constants, and _assign_attr.
  • Update the constant categorization loop in _package_constants to handle opaque types by checking if an object is an instance of a registered opaque type using is_opaque_type.
  • Use pickle.dumps to serialize opaque types and pickle.loads to deserialize them in _load_constants.
  • Modify the type check in _assign_attr to allow opaque types by adding a check for is_opaque_type.

Example

# In _package_constants
for constant_fqn, constant in exported_program.constants.items():
    if isinstance(constant, torch.Tensor):
        ...
    elif isinstance(constant, torch._C.ScriptObject):
        ...
    elif is_opaque_type(type(constant)):
        constant_bytes = pickle.dumps(constant)
        # Save the serialized constant

# In _load_constants
elif path_name.startswith(CUSTOM_OBJ_FILENAME_PREFIX):
    constant_bytes = archive_reader.read_bytes(...)
    if is_opaque_type(type(constant)):
        constant = pickle.loads(constant_bytes)
    else:
        constant = torch._C._pickle_load_obj(constant_bytes)

# In _assign_attr
elif attr_kind == _AttrKind.CONSTANT:
    if not isinstance(from_obj, (torch.Tensor, torch.ScriptObject)) and not is_opaque_type(type(from_obj)):
        raise AssertionError(
            f"expected torch.Tensor, torch.ScriptObject, or opaque type for CONSTANT attr_kind, got {type(from_obj)}"
        )

Notes

The proposed fix requires modifying the PyTorch library, which may not be feasible for all users. An alternative approach could be to create a custom serialization and deserialization mechanism for opaque types outside of the torch.export internals.

Recommendation

Apply the proposed fix to the torch.export internals to support serialization and deserialization of opaque types, as it provides a more comprehensive solution to the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

pytorch - 💡(How to fix) Fix torch.export.save / torch.export.load does not support opaque type constants registered via register_opaque_type