Aryan3108 commited on
Commit
45c83c9
·
verified ·
1 Parent(s): f6cafdf

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -8,17 +8,16 @@ tags:
8
  library_name: sparsevlm
9
  ---
10
 
11
- # SparseVLM — Production Inference Acceleration for Vision-Language Models
12
 
 
13
  [![Paper](https://img.shields.io/badge/ICML_2025-Paper-blue)](https://arxiv.org/abs/2410.04417)
14
  [![License](https://img.shields.io/badge/License-Apache_2.0-green)](LICENSE)
15
  [![Tests](https://github.com/aryanchauhan31/SparseVLM/actions/workflows/tests.yml/badge.svg)](https://github.com/aryanchauhan31/SparseVLM/actions)
16
 
17
- Training-free visual token sparsification for Qwen2.5-VL.
18
- **2–4× faster inference. <3% accuracy drop. One function call.**
19
 
20
- Based on the ICML 2025 paper by Zhang et al.:
21
- [SparseVLM: Visual Token Sparsification for Efficient VLM Inference](https://arxiv.org/abs/2410.04417)
22
 
23
  ---
24
 
@@ -28,7 +27,7 @@ Based on the ICML 2025 paper by Zhang et al.:
28
  pip install sparsevlm
29
  ```
30
 
31
- **Requirements:** Python 3.10+, PyTorch 2.1+, Triton 2.1+
32
 
33
  ---
34
 
@@ -38,28 +37,31 @@ pip install sparsevlm
38
  import torch
39
  from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
40
  from sparsevlm import sparsevlm_generate
 
41
 
42
  model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
43
  "Qwen/Qwen2.5-VL-7B-Instruct",
44
  torch_dtype=torch.bfloat16,
45
  device_map="auto",
46
- attn_implementation="eager", # required for attention-weight scoring
47
  )
48
  processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct")
49
 
50
- # Prepare inputs normally
51
  messages = [{"role": "user", "content": [
52
  {"type": "image", "image": image},
53
- {"type": "text", "text": "Describe this image."}
54
  ]}]
55
  text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
56
  inputs = processor(text=[text], images=[image], return_tensors="pt").to("cuda")
57
 
58
- # Run SparseVLM — keeps top-64 visual tokens out of 256 (25%)
 
 
59
  output = sparsevlm_generate(
60
  model, processor, inputs,
61
- n_vis=256, # visual tokens in your sequence
62
- keep_n_vis=64, # keep 25% — tune this
63
  max_new_tokens=256,
64
  )
65
  print(processor.decode(output[0][1:], skip_special_tokens=True))
@@ -67,53 +69,126 @@ print(processor.decode(output[0][1:], skip_special_tokens=True))
67
 
68
  ---
69
 
70
- ## Benchmark
 
 
71
 
72
- A100 40GB, Qwen2.5-VL-7B-Instruct, batch size 1.
73
- **Replace these with your numbers from `python benchmark/bench_layer1.py`.**
74
 
75
- | Tokens retained | Latency | Speedup | MME | TextVQA |
76
  |---|---|---|---|---|
77
- | 256 (100%) | 48ms | 1.0× | 100% | 100% |
78
- | 128 (50%) | 22ms | 2.2× | 98.2% | 97.6% |
79
- | 96 (37%) | 18ms | 2.7× | 97.1% | 96.4% |
80
- | 64 (25%) | 14ms | 3.4× | 95.3% | 94.1% |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  ---
83
 
84
  ## How it works
85
 
86
- SparseVLM hooks into the LLM decoder's attention layers and reuses
87
- attention weights the model already computes — zero extra parameters.
88
 
89
- At each target layer:
90
- 1. **Rater selection** — text tokens with above-average visual attention
91
- 2. **Visual token scoring** — sum of rater attention per visual token
92
- 3. **Rank-adaptive pruning** — rank(A_rater) sets the pruning ratio
93
- 4. **Token recycling** — pruned tokens clustered into compact representations
94
 
95
- Three-layer optimisation stack:
96
- - **Layer 1** — Triton sparse attention kernel + sketch rank (15-50× faster than SVD)
97
- - **Layer 2** — FlashAttention varlen, variable-length packing (no padding waste)
98
- - **Layer 3** — CUDA graph bucketing (zero kernel-launch overhead)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  ---
101
 
102
- ## Configuration
 
 
103
 
104
  ```python
105
- state = apply_sparsevlm(
106
- model,
107
- n_vis=256, # visual tokens per image
108
- target_layers=None, # default: every 4th layer from layer 2
109
- min_keep=32, # never prune below this
110
- tau=0.5, # recycling fraction
111
- theta=0.5, # cluster ratio
 
 
 
 
112
  )
 
 
 
 
 
 
 
 
 
 
 
 
113
  ```
114
 
115
  ---
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  ## Citation
118
 
119
  ```bibtex
@@ -127,8 +202,4 @@ state = apply_sparsevlm(
127
  }
128
  ```
129
 
130
- ---
131
-
132
- ## License
133
-
134
- Apache 2.0
 
8
  library_name: sparsevlm
9
  ---
10
 
11
+ # SparseVLM
12
 
13
+ [![PyPI](https://img.shields.io/pypi/v/sparsevlm)](https://pypi.org/project/sparsevlm/)
14
  [![Paper](https://img.shields.io/badge/ICML_2025-Paper-blue)](https://arxiv.org/abs/2410.04417)
15
  [![License](https://img.shields.io/badge/License-Apache_2.0-green)](LICENSE)
16
  [![Tests](https://github.com/aryanchauhan31/SparseVLM/actions/workflows/tests.yml/badge.svg)](https://github.com/aryanchauhan31/SparseVLM/actions)
17
 
18
+ Training-free visual token pruning for Qwen2.5-VL. Scores visual tokens by how much text attends to them, prunes the unimportant ones from the KV cache, and decodes with the smaller cache.
 
19
 
20
+ Based on [SparseVLM: Visual Token Sparsification for Efficient VLM Inference](https://arxiv.org/abs/2410.04417) (ICML 2025).
 
21
 
22
  ---
23
 
 
27
  pip install sparsevlm
28
  ```
29
 
30
+ Requirements: Python 3.10+, PyTorch 2.1+, transformers 4.49+
31
 
32
  ---
33
 
 
37
  import torch
38
  from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
39
  from sparsevlm import sparsevlm_generate
40
+ from PIL import Image
41
 
42
  model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
43
  "Qwen/Qwen2.5-VL-7B-Instruct",
44
  torch_dtype=torch.bfloat16,
45
  device_map="auto",
46
+ attn_implementation="eager",
47
  )
48
  processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct")
49
 
50
+ image = Image.open("your_image.jpg")
51
  messages = [{"role": "user", "content": [
52
  {"type": "image", "image": image},
53
+ {"type": "text", "text": "Describe this image in detail."}
54
  ]}]
55
  text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
56
  inputs = processor(text=[text], images=[image], return_tensors="pt").to("cuda")
57
 
58
+ # count visual tokens
59
+ n_vis = int((inputs["image_grid_thw"][0].prod() / 4).item())
60
+
61
  output = sparsevlm_generate(
62
  model, processor, inputs,
63
+ n_vis=n_vis,
64
+ keep_n_vis=n_vis // 4, # keep 25% of visual tokens
65
  max_new_tokens=256,
66
  )
67
  print(processor.decode(output[0][1:], skip_special_tokens=True))
 
69
 
70
  ---
71
 
72
+ ## Benchmark results
73
+
74
+ Measured on **NVIDIA A100-SXM4-40GB**, Qwen2.5-VL-7B-Instruct, bfloat16, SDPA attention.
75
 
76
+ ### Real photo — Fuji mountain + Milky Way (4928×2773px, 16320 visual tokens)
 
77
 
78
+ | Config | Tokens kept | Time | Speedup | Output quality |
79
  |---|---|---|---|---|
80
+ | Baseline | 16320 (100%) | 9738ms | 1.00× | ✅ Identifies Fuji, Milky Way, snow cap, star colors |
81
+ | SparseVLM 50% | 8192 | 9441ms | 1.03× | ✅ Same quality |
82
+ | SparseVLM 25% | 4080 | 9297ms | 1.05× | ✅ All key details preserved |
83
+ | SparseVLM 10% | 1632 | 9425ms | 1.03× | ✅ Still correctly describes scene |
84
+
85
+ > **Key result:** Full 4K image (16K tokens) runs without OOM. Without SparseVLM's hook-based scoring, the 16K-token image requires materialising a 15GB attention matrix and crashes. The scorer computes only the text→visual submatrix (35 × 16320 = 32MB instead of 15GB).
86
+
87
+ ### Resized photo (896×504px, 576 visual tokens), batch=1
88
+
89
+ | Tokens kept | Time | Speedup |
90
+ |---|---|---|
91
+ | 576 (100%) | 2167ms | 1.00× |
92
+ | 288 (50%) | 1685ms | 1.29× |
93
+ | **144 (25%)** | **1565ms** | **1.39×** |
94
+ | 72 (12%) | 1620ms | 1.34× |
95
+
96
+ ### When to expect larger speedup
97
+
98
+ Speedup grows when the KV cache is large relative to model weights:
99
+
100
+ | Scenario | Expected speedup |
101
+ |---|---|
102
+ | Single image, short generation | ~1.1–1.4× |
103
+ | Single image, 256+ output tokens | ~1.5–2.5× |
104
+ | Batch=32, high-res images | ~2–4× |
105
+ | Very long visual context (10K+ tokens) | ~2–4× |
106
 
107
  ---
108
 
109
  ## How it works
110
 
111
+ ### Token scoring (no extra parameters)
 
112
 
113
+ At decoder layer 2, a lightweight hook intercepts the attention projection and computes:
 
 
 
 
114
 
115
+ ```
116
+ A_tv = Q_text @ K_visual^T # only the text→visual submatrix
117
+ # 35 × 16320 instead of 16320 × 16320
118
+ score_i = sum over text tokens of attention to visual token i
119
+ ```
120
+
121
+ Visual tokens with high scores are important to the text query. Low-score tokens are pruned from the KV cache before decoding starts.
122
+
123
+ ### KV cache pruning
124
+
125
+ After scoring, the KV cache is sliced to keep only the top-K visual entries plus all text entries. The model then decodes with a smaller cache — fewer keys to attend over per decode step.
126
+
127
+ ```
128
+ Prefill: build KV cache for all 16320 visual tokens
129
+ Score: rank each visual token by text attention (32MB op)
130
+ Prune: keep top-K, drop the rest
131
+ Decode: attend over K + N_text keys instead of 16320 + N_text
132
+ ```
133
+
134
+ ### Position fix (`rope_deltas`)
135
+
136
+ After pruning, Qwen2.5-VL's internal position counter (`rope_deltas`) is adjusted so decode tokens get correct positional embeddings despite the shorter cache.
137
 
138
  ---
139
 
140
+ ## API
141
+
142
+ ### `sparsevlm_generate`
143
 
144
  ```python
145
+ from sparsevlm import sparsevlm_generate
146
+
147
+ output = sparsevlm_generate(
148
+ model, # Qwen2_5_VLForConditionalGeneration
149
+ processor, # AutoProcessor
150
+ inputs, # dict from processor(...)
151
+ n_vis, # total visual tokens in the sequence
152
+ keep_n_vis, # how many to keep (e.g. n_vis // 4 for 25%)
153
+ max_new_tokens=256, # generation length
154
+ target_layer=2, # which layer to score from (default 2)
155
+ device="cuda", # primary device
156
  )
157
+ # returns: token ids [B, max_new_tokens]
158
+ ```
159
+
160
+ ### `apply_sparsevlm` / `remove_hooks` (hook-based API)
161
+
162
+ ```python
163
+ from sparsevlm import apply_sparsevlm, reset_n_vis, remove_hooks
164
+
165
+ state = apply_sparsevlm(model, n_vis=256)
166
+ reset_n_vis(state, n_vis=256) # call before each generate
167
+ output = model.generate(...)
168
+ remove_hooks(state)
169
  ```
170
 
171
  ---
172
 
173
+ ## Model support
174
+
175
+ | Model | Status |
176
+ |---|---|
177
+ | Qwen/Qwen2.5-VL-7B-Instruct | ✅ Tested |
178
+ | Qwen/Qwen2.5-VL-3B-Instruct | ✅ Should work |
179
+ | Qwen/Qwen2.5-VL-72B-Instruct | ✅ Should work |
180
+ | Qwen/Qwen2-VL-* | ✅ Legacy support |
181
+
182
+ ---
183
+
184
+ ## Limitations
185
+
186
+ - Requires `attn_implementation="eager"` or `"sdpa"`. Flash Attention 2 (separate package) is not required.
187
+ - Speedup is modest (~1.1–1.4×) for single-image, short-generation use cases. The gain comes from long generations, high-resolution images, or batched serving.
188
+ - Currently tested with Qwen2.5-VL. Other VLM families would need architecture-specific adaptation.
189
+
190
+ ---
191
+
192
  ## Citation
193
 
194
  ```bibtex
 
202
  }
203
  ```
204
 
205
+ Apache 2.0 license.
 
 
 
 
dist/sparsevlm-0.1.2-py3-none-any.whl ADDED
Binary file (16.7 kB). View file
 
dist/sparsevlm-0.1.2.tar.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a38ff574431f01b8a0d79a7525a8e82fc80a67d48a2d22a44f20e346f1a145b5
3
+ size 19535
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "sparsevlm"
7
- version = "0.1.2"
8
  description = "Training-free visual token sparsification for vision-language models (ICML 2025)"
9
  readme = "README.md"
10
  license = { text = "Apache-2.0" }
 
4
 
5
  [project]
6
  name = "sparsevlm"
7
+ version = "0.1.3"
8
  description = "Training-free visual token sparsification for vision-language models (ICML 2025)"
9
  readme = "README.md"
10
  license = { text = "Apache-2.0" }
sparsevlm.egg-info/PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
  Metadata-Version: 2.4
2
  Name: sparsevlm
3
- Version: 0.1.1
4
  Summary: Training-free visual token sparsification for vision-language models (ICML 2025)
5
  Author-email: Aryan Chauhan <chauhanaryan31801@gmail.com>
6
  License: Apache-2.0
@@ -67,7 +67,7 @@ pip install sparsevlm
67
  ```python
68
  import torch
69
  from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
70
- from sparsevlm import apply_sparsevlm, reset_n_vis, remove_hooks
71
 
72
  model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
73
  "Qwen/Qwen2.5-VL-7B-Instruct",
@@ -77,16 +77,22 @@ model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
77
  )
78
  processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct")
79
 
80
- # Enable SparseVLM — no retraining needed
81
- state = apply_sparsevlm(model, n_vis=256)
82
-
83
- # Reset before each new image forward pass
84
- reset_n_vis(state, n_vis=256)
85
- inputs = processor(images=image, text=prompt, return_tensors="pt").to("cuda")
86
- output = model.generate(**inputs, max_new_tokens=256)
87
-
88
- # Remove hooks when done
89
- remove_hooks(state)
 
 
 
 
 
 
90
  ```
91
 
92
  ---
 
1
  Metadata-Version: 2.4
2
  Name: sparsevlm
3
+ Version: 0.1.2
4
  Summary: Training-free visual token sparsification for vision-language models (ICML 2025)
5
  Author-email: Aryan Chauhan <chauhanaryan31801@gmail.com>
6
  License: Apache-2.0
 
67
  ```python
68
  import torch
69
  from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
70
+ from sparsevlm import sparsevlm_generate
71
 
72
  model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
73
  "Qwen/Qwen2.5-VL-7B-Instruct",
 
77
  )
78
  processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct")
79
 
80
+ # Prepare inputs normally
81
+ messages = [{"role": "user", "content": [
82
+ {"type": "image", "image": image},
83
+ {"type": "text", "text": "Describe this image."}
84
+ ]}]
85
+ text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
86
+ inputs = processor(text=[text], images=[image], return_tensors="pt").to("cuda")
87
+
88
+ # Run SparseVLM — keeps top-64 visual tokens out of 256 (25%)
89
+ output = sparsevlm_generate(
90
+ model, processor, inputs,
91
+ n_vis=256, # visual tokens in your sequence
92
+ keep_n_vis=64, # keep 25% — tune this
93
+ max_new_tokens=256,
94
+ )
95
+ print(processor.decode(output[0][1:], skip_special_tokens=True))
96
  ```
97
 
98
  ---
sparsevlm.egg-info/SOURCES.txt CHANGED
@@ -6,6 +6,7 @@ kernels/sparse_attn.py
6
  kernels/token_scorer.py
7
  kernels/varlen_packing.py
8
  sparsevlm/__init__.py
 
9
  sparsevlm/patch.py
10
  sparsevlm/scheduler.py
11
  sparsevlm.egg-info/PKG-INFO
 
6
  kernels/token_scorer.py
7
  kernels/varlen_packing.py
8
  sparsevlm/__init__.py
9
+ sparsevlm/generate.py
10
  sparsevlm/patch.py
11
  sparsevlm/scheduler.py
12
  sparsevlm.egg-info/PKG-INFO
sparsevlm/__init__.py CHANGED
@@ -46,4 +46,4 @@ def apply_sparsevlm(
46
 
47
  __all__ = ["apply_sparsevlm", "reset_n_vis", "unpatch_qwen2vl",
48
  "remove_hooks", "sparsevlm_generate"]
49
- __version__ = "0.1.2"
 
46
 
47
  __all__ = ["apply_sparsevlm", "reset_n_vis", "unpatch_qwen2vl",
48
  "remove_hooks", "sparsevlm_generate"]
49
+ __version__ = "0.1.3"