QTSplus-3B is a Qwen2.5-VL–based multimodal LLM finetuned with Query‑Aware Token Selector (QTSplus), a lightweight visual token selection module that acts as an information gate between the vision encoder and the LLM.
The repository is designed around a conda‑based Python 3.11 environment with a CUDA‑enabled GPU.
1conda create -n qtsplus python=3.11 -y
2conda activate qtsplus
1conda install conda-forge::gcc=11 conda-forge::gxx=11 -y
2conda install nvidia/label/cuda-12.8.1::cuda-toolkit -y
3conda install av -c conda-forge -y
1pip install transformers==4.57.1
2DS_BUILD_CUTLASS_OPS=0 DS_BUILD_RAGGED_DEVICE_OPS=0 DS_BUILD_EVOFORMER_ATTN=0 pip install deepspeed
3pip install accelerate pandas wandb matplotlib scikit-learn datasets evaluate ftfy sentencepiece bitsandbytes
This wheel is specific to Linux x86_64, CUDA 12.8, PyTorch 2.9.0 and Python 3.11; if you deviate from this configuration, you will need to install a compatible FlashAttention build instead.
1import torch, glob, os
2from transformers import AutoModelForCausalLM, AutoProcessor
3from qwen_vl_utils import process_vision_info
4
5model_id = "AlpachinoNLP/QTSplus-3B"
6device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
7dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float16
8
9model = AutoModelForCausalLM.from_pretrained(model_id, trust_remote_code=True).to(dtype=dtype, device=device).eval()
10processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True)
11
12question = "Summarize the key events in this video."
13video_path = "/path/to/video.mp4"
14
15messages = [{
16 "role": "user",
17 "content": [
18 {"type": "video", "video": video_path, "max_pixels": 360*420, "fps": 1.0},
19 {"type": "text", "text": question},
20 ],
21}]
22
23chat = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
24_, video_inputs, video_kwargs = process_vision_info(messages, return_video_kwargs=True)
25
26inputs = processor(text=[chat], images=None, videos=video_inputs, padding=True, return_tensors="pt", **video_kwargs)
27inputs = inputs.to(dtype=torch.float16, device=device)
28
29# Pack vision inputs for QTSplus
30pixel_values_videos = inputs.pop("pixel_values_videos", None)
31video_grid_thw = inputs.pop("video_grid_thw", None)
32inputs.pop("second_per_grid_ts", None)
33vision_input = None
34if pixel_values_videos is not None and video_grid_thw is not None:
35 vision_input = {"pixel_values_videos": pixel_values_videos, "video_grid_thw": video_grid_thw}
36
37# Text ids from the question only (exclude special/system/vision tokens)
38question_ids = processor.tokenizer(question, return_tensors="pt", add_special_tokens=False).input_ids.to(dtype=torch.long, device=device)
39
40out_ids = model.generate(vision_input=vision_input, input_ids=inputs.input_ids, question_input_ids=question_ids, max_new_tokens=256)
41trimmed = [o[len(i):] for i, o in zip(inputs.input_ids, out_ids)]
42text = processor.batch_decode(trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=True)
43print(text[0])
1images_dir = "/path/to/images"
2image_list = sorted(glob.glob(os.path.join(images_dir, "*.jpg"))) or sorted(glob.glob(os.path.join(images_dir, "*.jpeg")))
3messages = [{
4 "role": "user",
5 "content": [
6 {"type": "video", "video": image_list},
7 {"type": "text", "text": "What story do these images tell?"},
8 ],
9}]
10
11chat = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
12_, video_inputs, video_kwargs = process_vision_info(messages, return_video_kwargs=True)
13inputs = processor(text=[chat], images=None, videos=video_inputs, padding=True, return_tensors="pt", **video_kwargs).to(dtype=torch.float16, device=device)
14
15pixel_values_videos = inputs.pop("pixel_values_videos", None)
16video_grid_thw = inputs.pop("video_grid_thw", None)
17inputs.pop("second_per_grid_ts", None)
18vision_input = {"pixel_values_videos": pixel_values_videos, "video_grid_thw": video_grid_thw}
19
20out = model.generate(vision_input=vision_input, input_ids=inputs.input_ids, max_new_tokens=256)
21print(processor.decode(out[0], skip_special_tokens=True))
1@misc{li2025seeingforesttreesqueryaware,
2 title = {Seeing the Forest and the Trees: Query-Aware Tokenizer for Long-Video Multimodal Language Models},
3 author = {Siyou Li and Huanan Wu and Juexi Shao and Yinghao Ma and Yujian Gan and Yihao Luo and Yuwei Wang and Dong Nie and Lu Wang and Wengqing Wu and Le Zhang and Massimo Poesio and Juntao Yu},
4 year = {2025},
5 eprint = {2511.11910},
6 archivePrefix= {arXiv},
7 primaryClass = {cs.CV},
8 url = {https://arxiv.org/abs/2511.11910}
9}