Views
No views yet

| Method | Tokens | R@10 | nDCG@10 |
|---|---|---|---|
| SeqResize | 64 | 41.1 | 38.5 |
| MemTok | 64 | 48.7 | 44.8 |
| H-Pool | 64 | 49.2 | 46.5 |
| AGC (this model) | 64 | 49.6 | 46.3 |
| Initial weights | Qwen2.5-Omni-3B-Instruct (thinker) |
| Architecture | Qwen2.5-Omni (thinker) with bidirectional attention |
| Hidden dimension | 2048 |
| Compression method | AGC (Attention-Guided Clustering) |
| Universal query tokens | 64 learned universal query tokens (<|mem0|> – <|mem63|>) |
| Default budget | 64 vectors per document |
| Scoring | ColBERT-style MaxSim (late interaction) |
| Normalization | L2-normalized embeddings |
| Query prefix | "Query: " |
| Passage prefix | "Passage: " |
| Precision | bfloat16 |
| Training video frames | 24 |
| Audio sampling rate | 4KHz |
1import torch
2from transformers import AutoProcessor
3from qwen_omni_utils import process_mm_info
4
5from src.arguments import ModelArguments
6from src.encoder.select_encoder import AttentionSelectEncoder
7from src.models.qwen2_5_omni_embed.qwen2_5_omni_embed import Qwen2_5OmniForEmbedding
8from src.utils import get_appending_token_strings
9
10MODEL_ID = "PLACEHOLDER"
11VIDEO_PATH = "PLACEHOLDER"
12AUDIO_PATH = "PLACEHOLDER"
13NUM_PROXY_TOKENS = 64
14APPENDING_SUFFIX = "".join(get_appending_token_strings(NUM_PROXY_TOKENS))
15
16# --- Setup ---
17model_args = ModelArguments(
18 model_name_or_path=MODEL_ID,
19 pooling="select",
20 normalize=True,
21 num_appending_token=NUM_PROXY_TOKENS,
22 use_cluster_pooling=True,
23 use_attn_weight_cluster_pooling=True,
24 attn_implementation="flash_attention_2",
25)
26
27processor = AutoProcessor.from_pretrained(MODEL_ID)
28model = AttentionSelectEncoder.load(
29 Qwen2_5OmniForEmbedding,
30 model_args,
31 attn_implementation=model_args.attn_implementation,
32 dtype=torch.bfloat16,
33)
34model = model.to("cuda").eval()
35
36# --- Encode a video+audio document ---
37passage_messages = [
38 {
39 "role": "user",
40 "content": [
41 {"type": "text", "text": "Passage: "},
42 {"type": "video", "video": VIDEO_PATH, "nframes": 24, "max_pixels": 75264, "min_pixels": 65856},
43 {"type": "audio", "audio": AUDIO_PATH},
44 ],
45 }
46]
47text = processor.apply_chat_template(passage_messages, tokenize=False, add_generation_prompt=False)
48text += APPENDING_SUFFIX
49audio_inputs, image_inputs, video_inputs = process_mm_info([passage_messages], use_audio_in_video=False)
50passage_inputs = processor(
51 text=[text], images=image_inputs, videos=video_inputs, audio=audio_inputs, padding=True, return_tensors="pt",
52).to("cuda")
53
54with torch.amp.autocast(device_type="cuda", dtype=torch.bfloat16):
55 with torch.inference_mode():
56 doc_embeddings, doc_mask = model.encode(passage_inputs, is_query=False)
57 print(doc_embeddings.shape)
58 # doc_embeddings: (1, 64, 2048) — 64 compressed AGC vectors
59
60# --- Encode a text query ---
61query_messages = [{"role": "user", "content": [{"type": "text", "text": "Query: a person is cooking"}]}]
62query_text = processor.apply_chat_template(query_messages, tokenize=False, add_generation_prompt=False)
63query_inputs = processor(text=[query_text], padding=True, return_tensors="pt").to("cuda")
64
65with torch.amp.autocast(device_type="cuda", dtype=torch.bfloat16):
66 with torch.inference_mode():
67 query_embeddings, query_mask = model.encode(query_inputs, is_query=True)
68 print(query_embeddings.shape)
69
70# --- ColBERT MaxSim scoring ---
71score = model.compute_similarity(query_embeddings, doc_embeddings, query_mask, doc_mask)
72print(f"Similarity score: {score.item():.4f}")1@misc{qin2026multivectorindexcompressionmodality,
2 title={Multi-Vector Index Compression in Any Modality},
3 author={Hanxiang Qin and Alexander Martin and Rohan Jha and Chunsheng Zuo and Reno Kriz and Benjamin Van Durme},
4 year={2026},
5 eprint={2602.21202},
6 archivePrefix={arXiv},
7 primaryClass={cs.IR},
8 url={https://arxiv.org/abs/2602.21202},
9}