ITADN

Strided slice .at(...).add corrupts overlap-add accumulation on MLX 0.31.2

#3676Openssmall256 创建于 2026-06-14
S
ssmall256commented
## Summary On `mlx 0.31.2`, repeated `out.at[:, offset:end].add(update)` over strided time slices can corrupt overlap-add accumulation by several thousand times, while equivalent slice assignment and pad-sum implementations reconstruct correctly. This surfaced in `demucs-mlx` as high-amplitude spikes for multi-segment `split=True` inference: https://github.com/ssmall256/demucs-mlx/issues/1 A downstream package release avoids this operation for affected runtimes, but the minimal reproduction below has no dependency on that package and appears reducible to MLX scatter-add behavior. ## Environment Observed on: - `mlx==0.31.2` - Python `3.11.8` - macOS 26 arm64 wheel (`mlx_metal-0.31.2-py3-none-macosx_26_0_arm64`) - Apple Silicon The same reduced overlap-add path did not corrupt output with `mlx==0.31.0` in the same environment and reduced repro. ## Minimal reproduction ```python import numpy as np import mlx.core as mx sr = 44100 segment_length = int(sr * 7.8) overlap = 0.25 stride = int((1 - overlap) * segment_length) length = int(20 * sr) offsets = list(range(0, length, stride)) t = np.arange(length) / sr x_np = 0.5 * np.sin(2 * np.pi * 220 * t) + 0.3 * np.sin(2 * np.pi * 440 * t) x_np = np.stack([x_np, np.roll(x_np, 100)]).astype(np.float32) x_np = (x_np / np.abs(x_np).max() * 0.9).astype(np.float32) x = mx.array(x_np) weight = mx.concatenate([ mx.arange(1, segment_length // 2 + 1), mx.arange(segment_length - segment_length // 2, 0, -1), ], axis=0) weight = (weight / mx.max(weight)).astype(x.dtype) def run_at_add(): out = mx.zeros_like(x) sw = mx.zeros((length,), dtype=x.dtype) for offset in offsets: this_len = min(segment_length, length - offset) end = offset + this_len w = weight[:this_len] update = w.reshape(1, -1) * x[:, offset:end] out = out.at[:, offset:end].add(update) sw = sw.at[offset:end].add(w) mx.eval(out, sw) y = out / sw.reshape(1, -1) mx.eval(y) return np.asarray(y), np.asarray(sw) def run_slice_assign(): out = mx.zeros_like(x) sw = mx.zeros((length,), dtype=x.dtype) for offset in offsets: this_len = min(segment_length, length - offset) end = offset + this_len w = weight[:this_len] update = w.reshape(1, -1) * x[:, offset:end] out[:, offset:end] = out[:, offset:end] + update sw[offset:end] = sw[offset:end] + w mx.eval(out, sw) y = out / sw.reshape(1, -1) mx.eval(y) return np.asarray(y), np.asarray(sw) def run_pad_sum(): out = mx.zeros_like(x) sw = mx.zeros((length,), dtype=x.dtype) for offset in offsets: this_len = min(segment_length, length - offset) end = offset + this_len w = weight[:this_len] update = w.reshape(1, -1) * x[:, offset:end] out = out + mx.pad(update, [(0, 0), (offset, length - end)]) sw = sw + mx.pad(w, [(offset, length - end)]) mx.eval(out, sw) y = out / sw.reshape(1, -1) mx.eval(y) return np.asarray(y), np.asarray(sw) for name, fn in [ ("at_add", run_at_add), ("slice_assign", run_slice_assign), ("pad_sum", run_pad_sum), ]: y, sw = fn() print(name, { "output_peak": float(np.abs(y).max()), "max_abs_error": float(np.max(np.abs(y - x_np))), "sum_weight_min": float(sw.min()), "sum_weight_max": float(sw.max()), }) ``` ## Actual on MLX 0.31.2 ```text at_add {'output_peak': 3349.296143, 'max_abs_error': 3349.27, 'sum_weight_min': 0.000006, 'sum_weight_max': 1.000000} slice_assign {'output_peak': 0.900000, 'max_abs_error': 1.19209e-07, 'sum_weight_min': 0.000006, 'sum_weight_max': 1.000000} pad_sum {'output_peak': 0.900000, 'max_abs_error': 1.19209e-07, 'sum_weight_min': 0.000006, 'sum_weight_max': 1.000000} ``` ## Expected `at_add`, `slice_assign`, and `pad_sum` should all reconstruct the identity input with peak around `0.9` and max absolute error around float32 tolerance. ## Notes - `sum_weight` remains correct in all variants, so the corruption appears specific to the accumulated `out` slice updates. - The failure is reproducible with no model weights or neural-network forward pass. - A downstream workaround is to avoid `.at(...).add(...)` for this accumulation pattern on the affected runtime.
2 条评论