Spaces:
Running
Running
| """ITM cross-encoder rerank — the discarded head, wired for live queries. | |
| InternVideo2's checkpoint ships an `itm_head` that every load discarded | |
| ("Some weights ... were not used"). It is the cross-encoder half of the | |
| model's own retrieval recipe (BLIP-2 arXiv 2301.12597): text tokens | |
| cross-attend to video tokens, a 2-class head reads the fused CLS. It | |
| out-ranks everything else in this store — measured at k=1.5xsupport, | |
| ITM alone 0.38/0.25 against the cosine ensemble's 0.32/0.21 and the | |
| shipped RRF path's 0.29/0.23. | |
| Two measurements decide the shape of this module: | |
| NO POOLING. Vision tokens are 1,025 x 1,408 per episode = 3.24 GB | |
| corpus-wide, four times the raw source, so storing them in the store | |
| is out. Pooling them is worse than out: 16x16 -> 8x8 per frame drops | |
| Spearman against the full-token score to 0.18, and to NEGATIVE on two | |
| of three probe queries. Cross-attention does not tolerate a reduced | |
| key set. So the tokens are a disposable CACHE outside the store's | |
| byte budget, never a table. | |
| CASCADE, NOT SCAN. Reranking the top-N of the cheap fused ranking | |
| reaches the full-scan number exactly: N=500 gives 0.40/0.27, N=150 | |
| already gives 0.38/0.25, and every query with support under ~200 is | |
| saturated at N=150. So N scales with the ceiling k rather than the | |
| corpus, which is L7 ("ITM is evidence inside a candidate set, not | |
| authority over the corpus") arrived at from the cost side. | |
| ITM IS A STAGE, NOT A CHANNEL — and that distinction was measured, not | |
| chosen. Making it a weighted RRF voter and refitting cost 0.38 -> 0.27 | |
| for two compounding reasons: RRF converts every channel to RANKS, which | |
| throws away the logit margin's scale, and that scale is where the | |
| cross-encoder's separation lives (the 0.40 offline number came from | |
| adding z-scores, not ranks); and the k-ladder refit then optimized the | |
| ladder mean away from the 1.5x-support operating point the product | |
| metric uses. So ITM stays a rerank STAGE alongside NMS and the | |
| confidence cut - stages the fitter switches on and off rather than | |
| weights - and the fit models it as such. | |
| The cache is content-addressed by (episode, model) and capped; deleting | |
| it costs time, never correctness. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| from pathlib import Path | |
| import numpy as np | |
| _S: dict = {} | |
| # 3.24 GB holds the whole 1,122-episode corpus. This is a DISPOSABLE | |
| # cache under the store's _cache/, not a table: it never enters the | |
| # store's byte ledger, and deleting it costs recompute time, never | |
| # correctness. Override with ELIDEDB_ITM_CACHE_GB. | |
| CACHE_CAP_GB = float(__import__("os").environ.get( | |
| "ELIDEDB_ITM_CACHE_GB", "4.0")) | |
| NF = 4 | |
| def _head(): | |
| """The checkpoint's own 2-class matcher — AutoModel drops it because | |
| the vendored class never declares the attribute.""" | |
| if "head" in _S: | |
| return _S["head"], _S["m"], _S["dev"] | |
| import torch | |
| from .iv2 import MDIR, load_model | |
| sd = torch.load(f"{MDIR}/pytorch_model.bin", map_location="cpu", | |
| weights_only=True, mmap=True) | |
| m, dev = load_model() | |
| h = torch.nn.Linear(sd["itm_head.weight"].shape[1], 2) | |
| h.load_state_dict({"weight": sd["itm_head.weight"], | |
| "bias": sd["itm_head.bias"]}) | |
| h = h.to(dev, m.dtype).eval() | |
| _S["head"], _S["m"], _S["dev"] = h, m, dev | |
| return h, m, dev | |
| def _cache_dir(store): | |
| d = Path(store.dir) / "_cache" / "itm_tokens" | |
| d.mkdir(parents=True, exist_ok=True) | |
| return d | |
| def _cache_path(store, stream, ts): | |
| key = hashlib.sha1(f"{stream}|{ts}|iv2-1b|{NF}".encode()).hexdigest() | |
| return _cache_dir(store) / f"{key}.npy" | |
| def _evict(store): | |
| """Keep the disposable cache under CACHE_CAP_GB, oldest first.""" | |
| d = _cache_dir(store) | |
| files = sorted(d.glob("*.npy"), key=lambda p: p.stat().st_mtime) | |
| total = sum(p.stat().st_size for p in files) | |
| cap = CACHE_CAP_GB * 1e9 | |
| while total > cap and files: | |
| p = files.pop(0) | |
| total -= p.stat().st_size | |
| try: | |
| p.unlink() | |
| except OSError: | |
| pass | |
| def _vision_tokens(store, frames_tbl, key): | |
| """Cached (1, T, C) vision tokens for one episode.""" | |
| import cv2 | |
| import torch | |
| from .iv2 import V_MEAN, V_STD | |
| from .video import FrameSet | |
| s, a, b = key | |
| p = _cache_path(store, s, a) | |
| _, m, dev = _head() | |
| if p.exists(): | |
| arr = np.load(p) | |
| return torch.from_numpy(arr).to(dev, m.dtype) | |
| import pyarrow.compute as pc | |
| sel = frames_tbl.filter(pc.and_( | |
| pc.equal(frames_tbl.column("stream"), s), | |
| pc.and_(pc.greater_equal(frames_tbl.column("ts"), a), | |
| pc.less_equal(frames_tbl.column("ts"), b)))) | |
| if len(sel) < 2: | |
| return None | |
| pi = np.linspace(0, len(sel) - 1, min(NF, len(sel))).round().astype(int) | |
| try: | |
| dec = FrameSet(store, "frames", sel.take(pi)).decode(width=224) | |
| except Exception: | |
| return None | |
| fr = [f for _, f in sorted(dec)] | |
| if len(fr) < 2: | |
| return None | |
| fs = [cv2.resize(f, (224, 224)) for f in fr] | |
| x = (np.stack(fs).astype(np.float32) / 255.0 - V_MEAN) / V_STD | |
| px = torch.from_numpy(x).permute(0, 3, 1, 2)[None].to(dev, m.dtype) | |
| with torch.no_grad(): | |
| vis, _ = m.encode_vision(px, test=True) | |
| np.save(p, vis.cpu().numpy()) | |
| _evict(store) | |
| return vis | |
| def itm_scores(store, text, keys): | |
| """Logit margin P(match) - P(no match) for each candidate episode. | |
| Returns np.array aligned with `keys`; NaN where frames are | |
| undecodable. The vision pass dominates and is query-independent, so | |
| a repeated query over the same candidates is nearly free.""" | |
| import torch | |
| head, m, dev = _head() | |
| frames_tbl = store.table("frames").scan() | |
| tok = m.tokenizer(text, padding="max_length", truncation=True, | |
| max_length=m._config.max_txt_l, | |
| return_tensors="pt").to(dev) | |
| out = np.full(len(keys), np.nan, np.float32) | |
| for i, k in enumerate(keys): | |
| vis = _vision_tokens(store, frames_tbl, k) | |
| if vis is None: | |
| continue | |
| with torch.no_grad(): | |
| vam = torch.ones(vis.shape[:2], dtype=torch.long, device=dev) | |
| o = m.get_text_encoder()( | |
| tok.input_ids, attention_mask=tok.attention_mask, | |
| encoder_hidden_states=vis, encoder_attention_mask=vam, | |
| return_dict=True, mode="multi_modal") | |
| lg = head(o.last_hidden_state[:, 0]).float()[0] | |
| out[i] = float(lg[1] - lg[0]) | |
| return out | |
| def rerank_depth(k_max, n_total): | |
| """Candidates to rerank. Measured: N=150 saturates every query with | |
| support under ~200; only the 247-support query needed 500. Scaling | |
| with the ceiling rather than the corpus keeps the cost proportional | |
| to what the caller actually asked for.""" | |
| return int(min(max(150, 2 * k_max), n_total)) | |