Visual Document Retrieval
Safetensors
sentence-transformers
colpali-engine
qwen3_5
vision-language
colbert
late-interaction
multi-vector
matryoshka
vidore
token-compression
Instructions to use tencent/EVIE-4.5B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use tencent/EVIE-4.5B with sentence-transformers:
from sentence_transformers import MultiVectorEncoder model = MultiVectorEncoder("tencent/EVIE-4.5B") queries = ["Which planet is known as the Red Planet?"] documents = [ "Venus is often called Earth's twin because of its similar size and proximity.", "Mars, known for its reddish appearance, is often referred to as the Red Planet.", "Jupiter, the largest planet in our solar system, has a prominent red spot.", ] query_embeddings = model.encode_query(queries) document_embeddings = model.encode_document(documents) similarities = model.similarity(query_embeddings, document_embeddings) print(similarities) - Notebooks
- Google Colab
- Kaggle
Download code/compress/cluster.py from tencent/EVIE-4.5B: direct link, hf CLI and curl.
- Browser
- Download file 7.37 kB
-
https://huggingface.co/tencent/EVIE-4.5B/resolve/main/code/compress/cluster.py
- Command line
-
hf download hf://tencent/EVIE-4.5B/code/compress/cluster.py
-
curl -L -o cluster.py https://huggingface.co/tencent/EVIE-4.5B/resolve/main/code/compress/cluster.py
7.37 kB
| #!/usr/bin/env python3 | |
| """HAC on a token dump. Image tokens only; no context; no re-encode. | |
| K and dim come from common.py (shipped: K=32, d=64). Cluster assignment uses | |
| sinusoidal position fusion; stored vectors are semantic means. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| import time | |
| from concurrent.futures import ThreadPoolExecutor | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "shared")) | |
| import torch | |
| from common import ( | |
| BUDGET, | |
| DUMP_PROTOCOL, | |
| HEAD_DIM, | |
| MERGE_SIZE, | |
| WEIGHT, | |
| atomic_torch_save, | |
| atomic_write_json, | |
| gib_per_million_pages, | |
| w_tag, | |
| ) | |
| from paths import forbid_venv_path | |
| _G_IMAGES = None | |
| _G_GRIDS = None | |
| _G_BUDGET = BUDGET | |
| _G_W = WEIGHT | |
| _G_MERGE = MERGE_SIZE | |
| def _cluster_one(index: int): | |
| from hac import hac_pool | |
| pooled = hac_pool( | |
| _G_IMAGES[index], | |
| _G_GRIDS[index], | |
| budget=_G_BUDGET, | |
| position_weight=_G_W, | |
| merge_size=_G_MERGE, | |
| ) | |
| return pooled.half().contiguous() | |
| def load_image_pages(docs_path: Path): | |
| blob = torch.load(docs_path, map_location="cpu", weights_only=False) | |
| if blob.get("dump_protocol") not in (DUMP_PROTOCOL, None) and "packed" not in blob: | |
| raise RuntimeError(f"unrecognized dump: {docs_path}") | |
| packed = blob["packed"] | |
| offsets = blob["offsets"] | |
| starts = blob["image_start"] | |
| ends = blob["image_end"] | |
| grids = blob["grid_thw"] | |
| n = int(blob["n"]) | |
| images = [] | |
| grid_list = [] | |
| for i in range(n): | |
| a, b = int(offsets[i]), int(offsets[i + 1]) | |
| full = packed[a:b] | |
| images.append(full[int(starts[i]) : int(ends[i])].float()) | |
| grid_list.append([int(x) for x in grids[i].tolist()]) | |
| return blob.get("ids"), images, grid_list | |
| def pad_pooled(pooled: list[torch.Tensor], budget: int, dim: int): | |
| n = len(pooled) | |
| out = torch.zeros(n, budget, dim, dtype=torch.float16) | |
| nvec = torch.zeros(n, dtype=torch.int64) | |
| for i, vec in enumerate(pooled): | |
| if vec.ndim != 2 or int(vec.shape[-1]) != dim: | |
| raise RuntimeError(f"pooled[{i}] shape {tuple(vec.shape)}") | |
| k = int(vec.shape[0]) | |
| if k > budget: | |
| raise RuntimeError(f"pooled[{i}] has {k} > budget {budget}") | |
| out[i, :k] = vec.half() | |
| nvec[i] = k | |
| return out, nvec | |
| def cluster_one_corpus(docs_path: Path, dest: Path, w: float, budget: int, workers: int, overwrite: bool): | |
| if dest.is_file() and not overwrite: | |
| print(f"[cluster] skip existing {dest}", flush=True) | |
| return json_sidecar_if_any(dest) | |
| ids, images, grids = load_image_pages(docs_path) | |
| n = len(images) | |
| global _G_IMAGES, _G_GRIDS, _G_BUDGET, _G_W, _G_MERGE | |
| _G_IMAGES = images | |
| _G_GRIDS = grids | |
| _G_BUDGET = int(budget) | |
| _G_W = float(w) | |
| _G_MERGE = MERGE_SIZE | |
| t0 = time.time() | |
| # Threads, not processes: sklearn releases the GIL, and fork-after-torch | |
| # deadlocks on this node (CUDA already initialized in the parent). | |
| if workers <= 1 or n <= 1: | |
| pooled = [_cluster_one(i) for i in range(n)] | |
| else: | |
| with ThreadPoolExecutor(max_workers=workers) as pool: | |
| pooled = list(pool.map(_cluster_one, range(n), chunksize=8)) | |
| padded, nvec = pad_pooled(pooled, budget, HEAD_DIM) | |
| dt = time.time() - t0 | |
| stored = [int(x) for x in nvec.tolist()] | |
| stats = { | |
| "pages": n, | |
| "budget": int(budget), | |
| "position_weight": float(w), | |
| "merge_size": MERGE_SIZE, | |
| "export_context": False, | |
| "avg_image_tokens": sum(int(t.shape[0]) for t in images) / n, | |
| "avg_stored_tokens": sum(stored) / n, | |
| "min_stored_tokens": min(stored), | |
| "max_stored_tokens": max(stored), | |
| "pages_below_budget": sum(s < budget for s in stored), | |
| "gib_per_million_pages": round(gib_per_million_pages(sum(stored) / n), 4), | |
| "compress_sec": round(dt, 2), | |
| "workers": workers, | |
| "corpus_id": docs_path.stem, | |
| } | |
| payload = { | |
| "ids": ids, | |
| "padded": padded, | |
| "nvec": nvec, | |
| "dim": HEAD_DIM, | |
| "budget": int(budget), | |
| "position_weight": float(w), | |
| "export_context": False, | |
| "dump_protocol": DUMP_PROTOCOL, | |
| "stats": stats, | |
| } | |
| atomic_torch_save(dest, payload) | |
| atomic_write_json(dest.with_suffix(".json"), stats) | |
| print( | |
| f"[cluster] K={budget} w={w} {docs_path.name}: {n} pages " | |
| f"avg_stored={stats['avg_stored_tokens']:.2f} {dt:.1f}s -> {dest}", | |
| flush=True, | |
| ) | |
| return stats | |
| def json_sidecar_if_any(dest: Path): | |
| side = dest.with_suffix(".json") | |
| if side.is_file(): | |
| return json.loads(side.read_text(encoding="utf-8")) | |
| return None | |
| def parse_args(): | |
| ap = argparse.ArgumentParser(description=__doc__) | |
| ap.add_argument("--dump-dir", required=True) | |
| ap.add_argument("--cluster-root", required=True) | |
| ap.add_argument("--budget", type=int, default=BUDGET) | |
| ap.add_argument("--workers", type=int, default=128) | |
| ap.add_argument("--overwrite", action="store_true") | |
| return ap.parse_args() | |
| def main(): | |
| args = parse_args() | |
| os.environ.setdefault("OMP_NUM_THREADS", "1") | |
| os.environ.setdefault("MKL_NUM_THREADS", "1") | |
| os.environ.setdefault("OPENBLAS_NUM_THREADS", "1") | |
| dump_root = forbid_venv_path(args.dump_dir, "dump-dir") | |
| forbid_venv_path(args.cluster_root, "cluster-root") | |
| docs_dir = dump_root / "docs" | |
| corpora = sorted(docs_dir.glob("*.pt")) | |
| if not corpora: | |
| raise SystemExit(f"no dumped corpora under {docs_dir}") | |
| weights = (WEIGHT,) | |
| workers = max(1, int(args.workers)) | |
| print( | |
| f"[cluster] corpora={len(corpora)} w={WEIGHT} budget={args.budget} " | |
| f"workers={workers} no_context=1", | |
| flush=True, | |
| ) | |
| summary = {"weights": {}, "dump_dir": str(dump_root), "budget": args.budget, "export_context": False} | |
| n_corpora = len(corpora) | |
| for w in weights: | |
| tag = w_tag(w) | |
| dest_dir = Path(args.cluster_root) / tag / "docs" | |
| dest_dir.mkdir(parents=True, exist_ok=True) | |
| done_path = Path(args.cluster_root) / tag / "DONE.json" | |
| existing_pts = list(dest_dir.glob("*.pt")) | |
| if ( | |
| not args.overwrite | |
| and done_path.is_file() | |
| and len(existing_pts) == n_corpora | |
| ): | |
| rec = json.loads(done_path.read_text(encoding="utf-8")) | |
| if int(rec.get("n_corpora") or 0) == n_corpora: | |
| print(f"[cluster] skip {tag} already DONE n_corpora={n_corpora}", flush=True) | |
| summary["weights"][tag] = rec | |
| continue | |
| per = [] | |
| for docs_path in corpora: | |
| dest = dest_dir / docs_path.name | |
| per.append( | |
| cluster_one_corpus( | |
| docs_path, dest, w=w, budget=args.budget, workers=workers, overwrite=args.overwrite | |
| ) | |
| ) | |
| summary["weights"][tag] = {"position_weight": w, "n_corpora": len(per), "corpora": per} | |
| atomic_write_json(done_path, summary["weights"][tag]) | |
| atomic_write_json(Path(args.cluster_root) / "DONE.json", summary) | |
| print(f"[cluster] done -> {Path(args.cluster_root) / 'DONE.json'}", flush=True) | |
| if __name__ == "__main__": | |
| main() | |