From 5727c391892f2bbae69512eba32488d84e1edd1c Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sun, 19 Jul 2026 00:53:36 +0300 Subject: [PATCH] Fix CLIPSeg multi-prompt crash: missing padding/truncation on tokenizer call Live rental run hit it immediately on 'detail_targets' with more than one target: CLIPSegProcessor tokenizes each prompt to a different length, and without padding=True torch can't stack them into a batch ('excessive nesting... type list where int is expected'). Reproduced against the real CIDAS/clipseg-rd64-refined model before and after the fix. Single-target runs were unaffected (batch of 1 never needed padding), which is why the earlier probe steps didn't catch it. Co-Authored-By: Claude Fable 5 --- detailing.py | 7 ++++++- tests/test_detailing.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/detailing.py b/detailing.py index 96adea14..43918672 100644 --- a/detailing.py +++ b/detailing.py @@ -224,8 +224,13 @@ def _clipseg_heat(frames, prompts, debug=False): with torch.no_grad(): for _, img in frames: pil = Image.fromarray((img.numpy() * 255.0).astype(np.uint8)) + # padding=True is mandatory once len(prompts) > 1: CLIPSeg's tokenizer + # otherwise returns one variable-length id list per prompt and torch can't + # stack them into a batch ("excessive nesting... type list where int is + # expected"). truncation=True guards the rare very-long user-typed target. inputs = processor( - text=list(prompts), images=[pil] * len(prompts), return_tensors="pt") + text=list(prompts), images=[pil] * len(prompts), return_tensors="pt", + padding=True, truncation=True) logits = model(**inputs).logits # [P, 352, 352] (or [352, 352] for one prompt) if logits.dim() == 2: logits = logits[None] diff --git a/tests/test_detailing.py b/tests/test_detailing.py index 1430600f..b3705287 100644 --- a/tests/test_detailing.py +++ b/tests/test_detailing.py @@ -99,6 +99,41 @@ def test_find_tube_enforces_minimum_edge(): assert (x1 - x0) >= detailing.MIN_TUBE_EDGE +# --------------------------------------------------------------------------- +# _clipseg_heat +# --------------------------------------------------------------------------- + +def test_clipseg_heat_passes_padding_and_truncation(monkeypatch): + """Regression: a multi-prompt call without padding=True crashes CLIPSeg's + tokenizer ("excessive nesting... type list where int is expected") the moment + detail_targets has more than one entry — caught live on a real rental run. + truncation=True is the same call, cheap to require alongside it.""" + calls = [] + + class _FakeInputs(dict): + def to(self, *a, **k): + return self + + class _FakeProcessor: + def __call__(self, text, images, return_tensors, **kwargs): + calls.append(kwargs) + return _FakeInputs() + + class _FakeModel: + def __call__(self, **inputs): + n = 2 + return types.SimpleNamespace(logits=torch.zeros(n, 8, 8)) + + def eval(self): + return self + + monkeypatch.setattr(detailing, "_get_clipseg", lambda: (_FakeProcessor(), _FakeModel())) + frames = [(0, torch.zeros(16, 16, 3))] + detailing._clipseg_heat(frames, ["hands", "feet"]) + assert calls and calls[0].get("padding") is True + assert calls[0].get("truncation") is True + + # --------------------------------------------------------------------------- # resolve_upsampler_name # ---------------------------------------------------------------------------