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
File size: 4,171 Bytes
6e8abde | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | #!/usr/bin/env python3
"""Shipped HAC recipe: d64 × K32. Change HEAD_DIM / BUDGET here to match other published SKUs."""
from __future__ import annotations
import json
import math
import os
from pathlib import Path
import torch
HEAD_DIM = 64
BUDGET = 32
MVT = 1024
MERGE_SIZE = 2
WEIGHT = 0.1
DUMP_PROTOCOL = "evie-hac-d64-k32-mvt1024-imagespan"
REQUIRED_KS = (1, 5, 10)
REQUIRED_METRICS = tuple(
f"{metric}@{k}"
for metric in ("recall", "ndcg", "mrr", "map")
for k in REQUIRED_KS
)
_raw_model = os.environ.get("EVIE_4_5B_DIR") or os.environ.get("MODEL_DIR") or ""
MODEL_DIR = Path(_raw_model) if _raw_model else Path()
SOUP_DIR = MODEL_DIR
SOUP_NAME = MODEL_DIR.name if _raw_model else "EVIE-4.5B"
CEILING = {
"source": "EVIE-4.5B uncompressed d64",
"V1 nDCG@10": 91.77,
"V2 nDCG@10": 70.83,
"V3 nDCG@10": 63.56,
"JinaVDR nDCG@10": 80.77,
"Avg4 nDCG@10": 76.73,
}
def w_tag(w: float = WEIGHT) -> str:
return f"w{int(round(float(w) * 100)):02d}"
def gib_per_million_pages(vec_per_page: float = BUDGET, dim: int = HEAD_DIM, nbytes: int = 2) -> float:
return float(vec_per_page) * dim * nbytes * 1_000_000 / (2**30)
def atomic_write_json(path: Path, payload) -> None:
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_name(f".{path.name}.tmp")
tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
tmp.replace(path)
def atomic_torch_save(path: Path, payload) -> None:
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_name(f".{path.name}.tmp")
torch.save(payload, tmp)
tmp.replace(path)
def pack_seqs(seqs: list[torch.Tensor]) -> dict:
if not seqs:
raise ValueError("empty sequence list")
dim = int(seqs[0].shape[-1])
lengths = [int(s.shape[0]) for s in seqs]
packed = torch.empty(sum(lengths), dim, dtype=torch.float16)
offsets = torch.zeros(len(seqs) + 1, dtype=torch.int64)
pos = 0
for i, seq in enumerate(seqs):
if seq.ndim != 2 or int(seq.shape[-1]) != dim:
raise ValueError(f"seq {i} shape {tuple(seq.shape)} != [L,{dim}]")
n = int(seq.shape[0])
packed[pos : pos + n] = seq.detach().to(dtype=torch.float16, device="cpu")
pos += n
offsets[i + 1] = pos
return {"packed": packed, "offsets": offsets, "dim": dim, "n": len(seqs)}
def padded_from_pack(packed: torch.Tensor, offsets: torch.Tensor) -> torch.Tensor:
n = int(offsets.numel()) - 1
if n <= 0:
return packed.new_zeros((0, 0, packed.shape[-1]))
lengths = (offsets[1:] - offsets[:-1]).tolist()
lmax = max(lengths)
out = packed.new_zeros((n, lmax, packed.shape[-1]))
for i, length in enumerate(lengths):
a = int(offsets[i])
if length:
out[i, :length] = packed[a : a + length]
return out
def dump_ok(task_json: Path, dump_root: Path) -> bool:
try:
rec = json.loads(Path(task_json).read_text(encoding="utf-8"))
except Exception:
return False
if rec.get("dump_protocol") != DUMP_PROTOCOL:
return False
docs = dump_root / rec["docs"]
queries = dump_root / rec["queries"]
qrels = dump_root / rec["qrels"]
return docs.is_file() and queries.is_file() and qrels.is_file()
def load_qrels(path: Path) -> tuple[list[set[int]], list[dict[int, float]]]:
rec = json.loads(Path(path).read_text(encoding="utf-8"))
relevant = [set(int(x) for x in row) for row in rec["relevant"]]
graded = [{int(k): float(v) for k, v in row.items()} for row in rec["graded"]]
return relevant, graded
def dump_qrels(path: Path, relevant, graded) -> None:
payload = {
"relevant": [sorted(int(x) for x in row) for row in relevant],
"graded": [{str(int(k)): float(v) for k, v in row.items()} for row in graded],
}
atomic_write_json(path, payload)
def has_complete_metrics(metrics) -> bool:
if not isinstance(metrics, dict):
return False
return all(
isinstance(metrics.get(name), (int, float)) and math.isfinite(float(metrics[name]))
for name in REQUIRED_METRICS
)
|