#407·moshi

SeanetResnetBlock.reset_state() does not reset StreamingAdd buffers, breaking multi-call decode_step

Author: andhaiCreated Mar 31, 2026Updated Mar 31, 2026

When using Mimi.decode_step() across multiple independent audio generations (e.g. a TTS daemon serving repeated synthesis requests), audio quality degrades after the first call. The second and subsequent calls produce distorted audio with misaligned timing.

Root cause

SeanetResnetBlock.reset_state() resets the convolution blocks and the shortcut but does not reset self.streaming_add:

python
# moshi_mlx/modules/seanet.py
def reset_state(self):
    if self.shortcut is not None:
        self.shortcut.reset_state()
    for b in self.block:
        b.reset_state()
    # streaming_add._lhs and ._rhs are never cleared

StreamingAdd.step() buffers leftover partial samples in _lhs / _rhs when the residual branch and convolution branch produce different-length outputs. After a generation completes, these leftover samples persist. On the next generation, Mimi.reset_all() delegates to reset_state(), which misses StreamingAdd, so the stale samples get prepended to the new audio — misaligning every residual skip connection in the decoder.

The non-streaming __call__ path (Mimi.decode) is not affected because it uses plain xs + residual instead of StreamingAdd.step().

Fix

python
def reset_state(self):
    if self.shortcut is not None:
        self.shortcut.reset_state()
    for b in self.block:
        b.reset_state()
    self.streaming_add._lhs = None
    self.streaming_add._rhs = None

Reproduction

  1. Load a TTS model and Mimi codec once
  2. Call mimi.reset_all() then run a full generation using decode_step() in a streaming callback — audio is correct
  3. Call mimi.reset_all() again and run a second generation — audio is distorted with wrong timing

The first generation always works. Every subsequent generation degrades because StreamingAdd carries stale partial-frame buffers across the reset boundary.

Affected version

moshi_mlx 0.3.0