From 8ae739ad9cffcffd80db76ec1f6fea49c5a4f49c Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:34:08 +0300 Subject: [PATCH] perf(studio): embed the category descriptions once per encoder, not once per run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CATEGORY_DESCRIPTIONS is a class attribute of eight fixed strings, so the vectors depend only on the encoder. They were re-embedded every run — eight full passes of Qwen3-VL-32B on H3 — to arrive at the same eight vectors. Held for the process, keyed by the encoder, behind a WEAK reference: pinning a text encoder in VRAM to save eight passes would be the wrong trade. An identity check sits on top of the id() key because ids are reused once a model is freed, and a stale hit would classify phrases against another encoder's geometry. Not a cache of generated content — nothing user-typed goes into it. Co-Authored-By: Claude Opus 5 --- conditioning.py | 26 ++++++++++ tests/test_category_vector_cache.py | 77 +++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tests/test_category_vector_cache.py diff --git a/conditioning.py b/conditioning.py index 5d1d2fa6..95721795 100644 --- a/conditioning.py +++ b/conditioning.py @@ -7,6 +7,7 @@ import os import random import re import time +import weakref from datetime import datetime, timezone from hashlib import md5 from typing import Optional @@ -5175,12 +5176,29 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): scores[category] = float(((sim.item() + 1.0) * 0.5)) return scores + # Eight fixed strings embedded to classify phrases by cosine similarity. They are + # CONSTANT — CATEGORY_DESCRIPTIONS is a class attribute, so the result depends only on the + # encoder. Held for the process rather than the run, keyed by the encoder object, because + # re-embedding them every run costs eight full encoder passes to arrive at the same eight + # vectors. This is not a cache of generated content: nothing user-typed goes into it, and + # a different encoder gets a different key. + _V2_CATEGORY_VECTOR_CACHE = {} + def _v2_category_vectors(self, clip, encode_cache=None): if isinstance(encode_cache, dict): cache_key = ("category_vectors", id(clip)) cached = encode_cache.get(cache_key) if cached is not None: return cached + held = type(self)._V2_CATEGORY_VECTOR_CACHE.get(id(clip)) + if held is not None and held[0]() is clip: + # A WEAK ref, and `is clip` on top of it. Weak so the entry never keeps an encoder + # alive — pinning a text encoder in VRAM to save eight passes would be a bad + # trade. The identity check guards the id() key, since ids are reused once a + # model is freed and a stale entry would classify against another encoder. + if isinstance(encode_cache, dict): + encode_cache[cache_key] = held[1] + return held[1] vectors = {} for category, description in self.CATEGORY_DESCRIPTIONS.items(): cond, _, _ = self._v2_encode_prompt(clip, description, encode_cache=encode_cache, @@ -5188,6 +5206,14 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): vector = self._v2_conditioning_vector(cond) if vector is not None: vectors[category] = vector.cpu() + if vectors: + try: + ref = weakref.ref(clip) + except TypeError: # not weak-referenceable: hold nothing rather than hold hard + ref = None + if ref is not None: + type(self)._V2_CATEGORY_VECTOR_CACHE.clear() + type(self)._V2_CATEGORY_VECTOR_CACHE[id(clip)] = (ref, vectors) if isinstance(encode_cache, dict): encode_cache[cache_key] = vectors return vectors diff --git a/tests/test_category_vector_cache.py b/tests/test_category_vector_cache.py new file mode 100644 index 00000000..51d8b5d6 --- /dev/null +++ b/tests/test_category_vector_cache.py @@ -0,0 +1,77 @@ +"""The eight category descriptions are constant, so they are embedded once per encoder. + +CATEGORY_DESCRIPTIONS is a class attribute; the vectors depend only on the encoder. Embedding +them every run cost eight full encoder passes to arrive at the same eight vectors — on H3, +eight passes of Qwen3-VL-32B. +""" +import gc +import sys + +import pytest +import torch + +sys.path.insert(0, ".") + + +class _Clip: + """Counts how many times it was asked to encode.""" + def __init__(self): + self.calls = 0 + + +@pytest.fixture +def refiner(): + from conditioning import FunPackVideoRefinerV2 + FunPackVideoRefinerV2._V2_CATEGORY_VECTOR_CACHE.clear() + return FunPackVideoRefinerV2() + + +@pytest.fixture(autouse=True) +def _stub_encode(monkeypatch): + from conditioning import FunPackVideoRefinerV2 + + def fake(self, clip, text, encode_cache=None, purpose="prompt", **kw): + clip.calls += 1 + return torch.ones(1, 4, 8), {}, "" + monkeypatch.setattr(FunPackVideoRefinerV2, "_v2_encode_prompt", fake) + + +def test_the_second_run_encodes_nothing(refiner): + clip = _Clip() + refiner._v2_category_vectors(clip, encode_cache={}) + first = clip.calls + assert first == len(refiner.CATEGORY_DESCRIPTIONS) + refiner._v2_category_vectors(clip, encode_cache={}) # a fresh run's cache + assert clip.calls == first + + +def test_a_different_encoder_is_embedded_again(refiner): + a, b = _Clip(), _Clip() + refiner._v2_category_vectors(a, encode_cache={}) + refiner._v2_category_vectors(b, encode_cache={}) + assert b.calls == len(refiner.CATEGORY_DESCRIPTIONS) + + +def test_the_cache_does_not_keep_an_encoder_alive(refiner): + """A held encoder is a text encoder pinned in VRAM. Saving eight passes is not worth it.""" + clip = _Clip() + refiner._v2_category_vectors(clip, encode_cache={}) + entry = next(iter(type(refiner)._V2_CATEGORY_VECTOR_CACHE.values())) + del clip + gc.collect() + assert entry[0]() is None + + +def test_a_recycled_id_is_not_mistaken_for_the_same_encoder(refiner): + """ids are reused once an object is freed; a stale hit would classify a phrase against + another encoder's geometry.""" + clip = _Clip() + refiner._v2_category_vectors(clip, encode_cache={}) + stale_id = next(iter(type(refiner)._V2_CATEGORY_VECTOR_CACHE)) + del clip + gc.collect() + other = _Clip() + type(refiner)._V2_CATEGORY_VECTOR_CACHE[id(other)] = \ + type(refiner)._V2_CATEGORY_VECTOR_CACHE.get(stale_id, (lambda: None, {})) + refiner._v2_category_vectors(other, encode_cache={}) + assert other.calls == len(refiner.CATEGORY_DESCRIPTIONS)