Skip to content

fix: wrap generate() in inference_mode() to stop a host-RAM leak from watermarking - #164

Open
malammar wants to merge 1 commit into
devnen:mainfrom
malammar:upstream-pr/watermark-inference-mode
Open

fix: wrap generate() in inference_mode() to stop a host-RAM leak from watermarking#164
malammar wants to merge 1 commit into
devnen:mainfrom
malammar:upstream-pr/watermark-inference-mode

Conversation

@malammar

Copy link
Copy Markdown

Summary

ChatterboxTTS.generate()/ChatterboxTurboTTS.generate() internally call perth's apply_watermark(), which runs its own conv-based encoder forward pass with no no_grad()/inference_mode() guard of its own. engine.py's call site only wraps generate() in torch.autocast(), which doesn't disable gradient tracking. Result: autograd builds and retains a full backward graph for the watermarking forward pass on every single call, none of which is ever backpropped through — a real host-RAM leak under sustained load.

How it was found

Traced with py-spy (caught worker processes mid-request inside apply_watermark at peak memory) and confirmed with memray attach (no code changes or restart needed — attaches to a live process) against a running deployment under sustained load. get_leaked_allocation_records() full stack traces showed the never-freed allocations rooted at generate()apply_watermark() → perth's encoder.forward(), one 500MB-1GB+ allocation per call.

Fix

Add torch.inference_mode() around the existing generate() call site. Covers the internal watermark call too since it's a context manager, not a decorator — no vendored code needs to change.

Verification

Measured against a 6-worker deployment (P102-100 GPUs), same load (concurrency 15, ~700 requests over 220s), same voice, memray-tracked before and after an equivalent fix applied to a downstream fork with additional batching call sites that hit the same underlying bug:

worker RSS growth over the test memray leaked (never-freed) bytes biggest single leaked allocation
before +2.2 GB 2523 MB 992.8 MB
after +90 MB 322 MB 166.5 MB

The small residual left isn't growing across the run and RSS stayed flat post-fix — looks like a one-time-per-shape cuDNN algorithm workspace cache (bounded, independent of grad mode), not the runaway per-request leak this fixes.

🤖 Generated with Claude Code

… watermarking

perth's apply_watermark() (called internally by ChatterboxTTS.generate()/
ChatterboxTurboTTS.generate()) runs its own conv-based encoder forward
pass with no no_grad()/inference_mode() guard of its own. The call site
in engine.py only wrapped generate() in torch.autocast(), which doesn't
disable gradient tracking -- so autograd built and retained a full
backward graph for that forward pass on every single call, none of which
is ever backpropped through. Under sustained load this leaks real host
RAM that never gets freed.

Fix: add torch.inference_mode() around the same call site. No vendored
code needs to change -- inference_mode() as a context manager covers
everything called inside it, including the internal watermark step.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant