11import functools
22import gc
3+ import inspect
34import logging
45import os
56from dataclasses import dataclass , field
@@ -29,6 +30,13 @@ def annotate_pipeline(pipe):
2930
3031 Monkey-patches bound methods so they appear as named spans in the trace.
3132 Non-invasive — no source modifications required.
33+
34+ Sub-component methods are patched at the **class level** (via
35+ setattr(type(component), ...)) rather than on the instance. This
36+ ensures Python's descriptor protocol re-binds the wrapper to whichever
37+ instance accesses it, so shallow-copied components (e.g. the duplicated
38+ audio_scheduler inside LTX2) call their own logic rather than the
39+ original instance's.
3240 """
3341 annotations = [
3442 ("transformer" , "forward" , "transformer_forward" ),
@@ -45,7 +53,16 @@ def annotate_pipeline(pipe):
4553 method = getattr (component , method_name , None )
4654 if method is None :
4755 continue
48- setattr (component , method_name , annotate (method , label ))
56+ if inspect .ismethod (method ):
57+ # Wrap the underlying function and patch at the class level so
58+ # that the descriptor protocol correctly rebinds the wrapper to
59+ # whichever instance accesses it. This prevents instance-
60+ # isolation bugs when a component is shallow-copied after
61+ # annotation (e.g. audio_scheduler = copy.copy(self.scheduler)
62+ # in the LTX2 pipeline).
63+ setattr (type (component ), method_name , annotate (method .__func__ , label ))
64+ else :
65+ setattr (component , method_name , annotate (method , label ))
4966
5067 # Annotate pipeline-level methods
5168 if hasattr (pipe , "encode_prompt" ):
0 commit comments