Skip to content

alignment_stream_analyzer re-patches self_attn.forward on every init: RecursionError, then silent ~1s outputs #349

Description

@jswillia

Summary

AlignmentStreamAnalyzer patches self_attn.forward every time it is constructed, capturing the previous forward as original_forward and never restoring it. Each construction therefore adds one more layer to the call chain. After enough generations in a single ComfyUI process, calling the layer recurses through every accumulated wrapper and raises RecursionError.

The failure is quiet: generation then returns a very short clip, and ComfyUI still reports the prompt as successful.

Where

engines/chatterbox/models/t3/inference/alignment_stream_analyzer.py (current main):

# Backup original forward
original_forward = target_layer.forward
def patched_forward(self, *args, **kwargs):
    kwargs['output_attentions'] = True
    return original_forward(*args, **kwargs)

# TODO: how to unpatch it?
target_layer.forward = MethodType(patched_forward, target_layer)

Because target_layer.forward has already been replaced by a previous run, the next original_forward is the previous patched_forward, not the model's own method.

What we saw

A long-running ComfyUI (~24h, a few hundred TTS generations) reached:

File ".../alignment_stream_analyzer.py", line 84, in patched_forward
    return original_forward(*args, **kwargs)
  [Previous line repeated 1479 more times]
RecursionError: maximum recursion depth exceeded

750 occurrences in the log. From the first one onward, every generation returned ~1 second of audio regardless of input length — a 174-character line and a 984-character line produced identically-sized output — while each prompt was still reported as executed successfully (Prompt executed in 0.29 seconds).

Restarting ComfyUI clears it completely: the same 218-character input went from 1.0s to 11.7s of audio immediately after a restart. It then accumulates again with use.

Suggested fix

Make the patch idempotent, and keep a handle so it can be undone:

if not getattr(target_layer, "_asa_patched", False):
    original_forward = target_layer.forward
    def patched_forward(self, *args, **kwargs):
        kwargs['output_attentions'] = True
        return original_forward(*args, **kwargs)
    target_layer.forward = MethodType(patched_forward, target_layer)
    target_layer._asa_original_forward = original_forward
    target_layer._asa_patched = True

and restore target_layer.forward = target_layer._asa_original_forward (plus hook_handle.remove()) when the analyzer is finished with. Guarding alone is enough to stop the unbounded growth.

Note for anyone hitting this

The symptom is uniform, very short outputs with successful prompt status. It is worth checking generated duration against expected duration rather than trusting success, because the short clips are valid, decodable, non-silent audio.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions