perf(studio): embed the category descriptions once per encoder, not once per run
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7cd063302a
commit
8ae739ad9c
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user