{"record":{"id":"f19d782d1c81f056","repo":"huggingface/transformers","slug":"cannot-flatten-a-bound-method-for-pytree-context","errorCode":null,"errorMessage":"Cannot flatten a bound method for pytree context","messagePattern":"Cannot flatten a bound method for pytree context","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/transformers/exporters/exporter_dynamo.py","lineNumber":513,"sourceCode":"    cls = type(obj)\n    if isinstance(obj, dict):  # dict subclasses (OrderedDict, etc.)\n        return {\n            \"_t\": \"map\",\n            \"p\": _class_to_path(cls),\n            \"v\": {k: _flatten_to_context(v, tensors) for k, v in obj.items()},\n        }\n    if isinstance(obj, (tuple, list, set, frozenset)):  # sequences/sets incl. NamedTuple\n        return {\n            \"_t\": \"seq\",\n            \"p\": _class_to_path(cls),\n            \"v\": [_flatten_to_context(i, tensors) for i in obj],\n        }\n    if isinstance(obj, types.MethodType):\n        # A bound method can't be flattened into pytree context. Models shouldn't bind methods onto\n        # objects they return at forward time (e.g. recurrent_gemma binds `get_seq_length`/\n        # `get_mask_sizes` onto its `DynamicCache`) — that pattern isn't exportable; such a model is\n        # skipped until the binding is refactored away (e.g. into a `Cache` subclass).\n        raise TypeError(\"Cannot flatten a bound method for pytree context\")\n    if hasattr(obj, \"__dict__\"):\n        state = {k: _flatten_to_context(v, tensors) for k, v in vars(obj).items()}\n        return {\"_t\": \"obj\", \"p\": _class_to_path(cls), \"s\": state}\n\n    raise TypeError(f\"Cannot flatten {type(obj).__name__} for pytree context\")\n\n\ndef _unflatten_from_context(ctx: Any, tensors: list) -> Any:\n    \"\"\"Reconstruct an object from its JSON-native context, substituting tensor index markers.\"\"\"\n    # --- Pure Python / JSON-native ---\n    if ctx is None or type(ctx) in (bool, int, float, str):\n        return ctx\n    if type(ctx) is list:\n        return [_unflatten_from_context(i, tensors) for i in ctx]\n    if type(ctx) is dict and \"_t\" not in ctx:\n        return {k: _unflatten_from_context(v, tensors) for k, v in ctx.items()}\n\n    # --- Torch objects ---","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/huggingface/transformers/blob/a597f974857b3d92939971296bc0deb93d33d780/src/transformers/exporters/exporter_dynamo.py#L495-L531","documentation":"During serialization of a traced program's pytree context, _flatten_to_context refuses to serialize bound methods (types.MethodType). This happens when a model's forward attaches methods onto objects it returns at trace time — the comment cites recurrent_gemma binding get_seq_length/get_mask_sizes onto its DynamicCache. Bound methods cannot be captured in an ExportedProgram's constants, so the pattern is unexportable by design; the model is skipped until the binding is refactored.","triggerScenarios":"Exporting recurrent_gemma (or any model whose Cache gets methods bound at forward time) with DynamoExporter; the flattening walks the model's outputs/state and hits the MethodType branch.","commonSituations":"Exporting new recurrent models that dynamically extend DynamicCache with helper methods; a fine-tuned or community model that copied the bind-methods-onto-cache pattern.","solutions":["Refactor the model: move get_seq_length/get_mask_sizes into a proper Cache subclass instead of binding them at forward time.","Use a model revision where the binding was already refactored away (check the model repo for updates).","As a maintainer of a custom model, never attach methods to returned objects; subclass the cache class instead."],"exampleFix":"# before (model code, unexportable)\ncache.get_seq_length = lambda: cache.seen_tokens  # bound at forward time\n\n# after (model code)\nclass MyCache(DynamicCache):\n    def get_seq_length(self):  # real method on a Cache subclass\n        return self.seen_tokens","handlingStrategy":"type-guard","validationCode":"# Detect the anti-pattern before exporting (model-side check):\nimport types\n\ndef binds_methods_at_forward(model) -> bool:\n    # smoke-trace a single step and inspect returned cache for bound methods\n    out = model(**sample_inputs)\n    cache = getattr(out, \"past_key_values\", None)\n    return any(isinstance(v, types.MethodType) for v in (vars(cache).values() if cache else []))","typeGuard":"def is_exportable_cache(cache) -> bool:\n    import types\n    return not any(isinstance(v, types.MethodType) for v in vars(cache).values())","tryCatchPattern":"try:\n    DynamoExporter().export(model, inputs, cfg)\nexcept TypeError as e:\n    if \"bound method\" in str(e):\n        logger.warning(\"%s binds methods onto its cache; skip or refactor model\", model.config.name_or_path)\n        raise","preventionTips":["Never attach methods to objects returned from forward; subclass DynamicCache instead","For recurrent_gemma-class models, check for a refactored revision before exporting","Run a one-step forward and assert no MethodType appears in the returned cache state"],"tags":["export","dynamo","pytree","model-limitation"],"backgroundTag":null,"analyzedSha":"a597f974857b3d92939971296bc0deb93d33d780","analyzedAt":"2026-08-14T18:24:08.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}