hsmin92 commited on
Commit
97f0b41
·
verified ·
1 Parent(s): ff32ca7

Update model card with right-sized 8 GB serving config and measured tuning data

Browse files
Files changed (1) hide show
  1. README.md +112 -35
README.md CHANGED
@@ -95,19 +95,19 @@ calibration and does not carry this caveat.
95
  | Quantization backend | compressed-tensors 0.17.0 |
96
  | Attention backend | FlashAttention (auto-selected) |
97
 
98
- Measured at startup with `--gpu-memory-utilization 0.86 --max-model-len 8192
99
- --max-num-seqs 1 --max-num-batched-tokens 4096 --dtype bfloat16`:
100
 
101
  ```text
102
- Model loading took 3.84 GiB memory
103
- Available KV cache memory: 2.66 GiB
104
- GPU KV cache size: 19,344 tokens
105
- Maximum concurrency for 8,192 tokens per request: 2.36x
106
- Graph capturing finished in 1 secs, took 0.00 GiB
107
  ```
108
 
109
- CUDA graphs capture successfully and full FP16 KV cache fits at an 8,192-token
110
- context — neither is possible with the FP8 build on the same 8 GB card.
 
 
111
 
112
  ## Serving with vLLM
113
 
@@ -118,20 +118,26 @@ vllm serve hsmin92/internvl35-4b-awq-w4a16-g128 \
118
  --served-model-name internvl35-awq \
119
  --dtype bfloat16 \
120
  --max-model-len 8192 \
121
- --max-num-seqs 1 \
122
  --max-num-batched-tokens 4096 \
123
  --limit-mm-per-prompt '{"image":2,"video":0}' \
124
- --gpu-memory-utilization 0.86 \
125
- --trust-remote-code
 
 
 
126
  ```
127
 
128
  Then use the OpenAI-compatible endpoint at `http://127.0.0.1:8000/v1`.
129
 
 
 
 
130
  ### Do not use `--dtype half`
131
 
132
  On this checkpoint family `--dtype half` produces a server that starts
133
- normally, returns HTTP 200 from `/health`, reports a healthy Docker
134
- healthcheck — and answers every request with `!!!!!!!!`. Dequantization
135
  overflows the FP16 range, the logits become NaN, and argmax selects token 0.
136
  It fails silently, so a liveness probe will not catch it.
137
 
@@ -139,18 +145,80 @@ The checkpoint declares `bfloat16` in `config.json` and Ampere supports BF16
139
  natively at the same memory cost. Always send a real request after a
140
  configuration change, not just a health check.
141
 
142
- ### `--max-num-batched-tokens` and multi-image requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- This value also sizes the multimodal encoder cache. InternVL dynamic tiling
145
- allows up to 12 patches plus a thumbnail, so a single image can reach
146
- **3,329 embedding tokens**. With a smaller value, large images are rejected:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  ```text
149
  image item with 2816 embedding tokens, which exceeds the
150
  pre-allocated encoder cache size 2048
151
  ```
152
 
153
- Use at least 4096 if you send high-resolution or wide-aspect images.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  ### Image token cost is driven by aspect ratio, not size
156
 
@@ -162,35 +230,45 @@ resolution. Measured on this model:
162
  | 448×448 | 257 | 1 |
163
  | 896×448 | 769 | 3 |
164
  | 960×544 | 769 | 3 |
165
- | 200×150 | **3,329** | **13** |
166
  | 800×450 | 769 | 3 |
167
- | 4000×300 | **3,329** | **13** |
 
 
168
 
169
  A 200×150 crop costs **4.3× more** than an 800×450 image, because 4:3 maps
170
  exactly onto a 4×3 tile grid and the crop is upscaled to 1792×1344 for no
171
- added information. When sending region crops, letterbox them into a fixed
172
- 448×448 canvas (preserving aspect ratio, no upscaling) to make cost constant.
 
 
 
 
 
173
 
174
  ## Choosing between the FP8 and AWQ builds
175
 
176
- Measured on the same RTX 3070, same vLLM version, same settings:
 
177
 
178
  | | FP8 Dynamic | **AWQ W4A16 G128** |
179
  |---|---|---|
180
  | Weights on GPU | 5.51 GiB | **3.84 GiB** |
181
- | KV cache available | 0.61 GiB | **2.66 GiB** |
182
- | KV cache capacity | 8,928 tokens | **19,344 tokens** |
183
- | Max concurrency @ 8192 ctx | 1.09× | **2.36×** |
184
  | KV cache dtype needed for 8192 ctx | fp8 (compromise) | **fp16** |
185
  | CUDA graphs on 8 GB | not possible | **yes** |
186
- | Text generation | 13–17 tok/s | **~55 tok/s** |
 
 
187
  | Calibration data required | **no** | yes (see above) |
188
  | Weight precision | 8-bit | 4-bit |
189
 
190
- AWQ is decisively better on memory and speed. **Accuracy has not been compared
191
- between the two builds, or against the BF16 base model.** 4-bit weights and
192
- out-of-domain calibration are both reasons to expect the AWQ build to degrade
193
- first on hard inputs. Benchmark both on your own data before choosing.
 
 
 
194
 
195
  ## Limitations
196
 
@@ -198,12 +276,11 @@ first on hard inputs. Benchmark both on your own data before choosing.
198
  - **No accuracy benchmark has been published for this checkpoint.** Functional
199
  validation only: text generation, a 7k-token context, and multi-image
200
  (full frame + crop) requests all produce coherent, correct answers on simple
201
- synthetic probes.
202
  - Calibration is general-purpose photography; see [Calibration](#calibration).
203
  - The vision tower and output head remain BF16 and are a meaningful share of
204
  the loaded weights, so the size reduction is smaller than 4/16 would suggest.
205
- - The bundled `chat_template.jinja` has no tool-calling support. Passing
206
- `tools=[...]` is silently ignored regardless of vLLM's tool-parser flags.
207
  - VRAM figures depend on driver, desktop applications, context length,
208
  multimodal limits, and vLLM version.
209
 
 
95
  | Quantization backend | compressed-tensors 0.17.0 |
96
  | Attention backend | FlashAttention (auto-selected) |
97
 
98
+ Startup with the command below:
 
99
 
100
  ```text
101
+ Model loading took 3.84 GiB memory and 5.08 seconds
102
+ GPU KV cache size: 12,288 tokens
103
+ Maximum concurrency for 8,192 tokens per request: 1.50x
104
+ Graph capturing finished in 1 secs, took 0.09 GiB
 
105
  ```
106
 
107
+ Total process footprint is about **6.6 GB**, leaving roughly 0.9 GB free on an
108
+ 8 GB card that is also driving a desktop. CUDA graphs capture successfully and
109
+ a full FP16 KV cache is used at an 8,192-token context — neither is possible
110
+ with the FP8 build on the same card.
111
 
112
  ## Serving with vLLM
113
 
 
118
  --served-model-name internvl35-awq \
119
  --dtype bfloat16 \
120
  --max-model-len 8192 \
121
+ --max-num-seqs 8 \
122
  --max-num-batched-tokens 4096 \
123
  --limit-mm-per-prompt '{"image":2,"video":0}' \
124
+ --gpu-memory-utilization 0.79 \
125
+ --kv-cache-memory-bytes 1728M \
126
+ --trust-remote-code \
127
+ --enable-auto-tool-choice \
128
+ --tool-call-parser hermes
129
  ```
130
 
131
  Then use the OpenAI-compatible endpoint at `http://127.0.0.1:8000/v1`.
132
 
133
+ Each flag is explained below — several of them are not obvious, and two of
134
+ them fail in ways that a health check will not catch.
135
+
136
  ### Do not use `--dtype half`
137
 
138
  On this checkpoint family `--dtype half` produces a server that starts
139
+ normally, returns HTTP 200 from `/health`, reports a healthy container
140
+ health check — and answers every request with `!!!!!!!!`. Dequantization
141
  overflows the FP16 range, the logits become NaN, and argmax selects token 0.
142
  It fails silently, so a liveness probe will not catch it.
143
 
 
145
  natively at the same memory cost. Always send a real request after a
146
  configuration change, not just a health check.
147
 
148
+ ### Pin the KV cache instead of raising `--gpu-memory-utilization`
149
+
150
+ Without `--kv-cache-memory-bytes`, vLLM expands the KV cache to fill whatever
151
+ `--gpu-memory-utilization` allows. On this model that wastes VRAM for no gain
152
+ and makes the cache size drift between restarts with whatever the desktop
153
+ happens to be using:
154
+
155
+ | | utilization only (0.86) | pinned KV (0.79 + 1728M) |
156
+ |---|---|---|
157
+ | KV cache | 16,128 – 19,344 tokens (varies per restart) | **12,288 tokens (fixed)** |
158
+ | Process VRAM | 7.9 GB | **6.6 GB** |
159
+ | Throughput, 16 concurrent | 126.1 tok/s | 124.4 tok/s |
160
+
161
+ 1.3 GB is recovered for a throughput difference inside measurement noise.
162
+ Size the cache from the workload instead: 12,288 tokens holds one full-length
163
+ 8,192-token request with room to spare, or about 11 typical requests of a
164
+ 960×544 frame plus a normalized crop (~1,084 tokens each).
165
 
166
+ **`--gpu-memory-utilization` is a fraction of the free memory the process can
167
+ see, which is not what host `nvidia-smi` reports.** Under WSL2 the container
168
+ saw 6.93 GiB while the Windows host reported 7.48 GiB free, so the effective
169
+ ceiling was 0.866, and 0.87 was rejected outright:
170
+
171
+ ```text
172
+ Free memory on device cuda:0 (6.93/8.0 GiB) on startup is less than
173
+ desired GPU memory utilization (0.87, 6.96 GiB)
174
+ ```
175
+
176
+ ### `--max-num-seqs`: leaving it at the default costs throughput
177
+
178
+ The vLLM default is 128. When more concurrent requests arrive than the KV
179
+ cache can hold, vLLM preempts and recomputes, and throughput collapses below
180
+ what a smaller limit achieves. Measured at ~1,084 prompt tokens per request
181
+ with a 16,128-token cache:
182
+
183
+ | Concurrent | `--max-num-seqs 128` | `--max-num-seqs 8` |
184
+ |---|---|---|
185
+ | 1 | 0.84 s / 63.3 tok/s | 0.84 s / 63.4 tok/s |
186
+ | 2 | 1.19 s / 87.4 tok/s | 1.18 s / 88.2 tok/s |
187
+ | 4 | 1.87 s / 105.8 tok/s | 1.19 s / 167.2 tok/s |
188
+ | 8 | 3.26 s / 119.7 tok/s | 3.22 s / 120.5 tok/s |
189
+ | **16** | **11.82 s / 68.9 tok/s** | **6.54 s / 126.1 tok/s** |
190
+
191
+ Pick a limit the cache can actually sustain. Do **not** derive it from the
192
+ `Maximum concurrency for 8,192 tokens per request` line in the startup log —
193
+ that figure assumes every request fills the full context window, which real
194
+ requests rarely do.
195
+
196
+ ### `--max-num-batched-tokens` also sizes the multimodal encoder cache
197
+
198
+ This is not a VRAM tuning knob. The vLLM default of 2048 rejects large images
199
+ outright, on any card:
200
 
201
  ```text
202
  image item with 2816 embedding tokens, which exceeds the
203
  pre-allocated encoder cache size 2048
204
  ```
205
 
206
+ InternVL dynamic tiling allows up to 12 patches plus a thumbnail, so a single
207
+ image can reach **3,329 embedding tokens**. Use at least 4096.
208
+
209
+ ### Tool-call flags are needed even without tool calling
210
+
211
+ `--enable-auto-tool-choice` and `--tool-call-parser` are required for the
212
+ server to *accept* requests that carry a `tool_choice` field. Open WebUI sends
213
+ `tool_choice: "auto"` by default, so omitting the flags makes it fail with:
214
+
215
+ ```text
216
+ "auto" tool choice requires --enable-auto-tool-choice and
217
+ --tool-call-parser to be set
218
+ ```
219
+
220
+ Actual tool calling still does not work: the bundled `chat_template.jinja` has
221
+ no tool rendering, so `tools=[...]` is silently ignored.
222
 
223
  ### Image token cost is driven by aspect ratio, not size
224
 
 
230
  | 448×448 | 257 | 1 |
231
  | 896×448 | 769 | 3 |
232
  | 960×544 | 769 | 3 |
 
233
  | 800×450 | 769 | 3 |
234
+ | 2048×2048 | 2,561 | 10 |
235
+ | **200×150** | **3,329** | **13** |
236
+ | 4000×300 | 3,329 | 13 |
237
 
238
  A 200×150 crop costs **4.3× more** than an 800×450 image, because 4:3 maps
239
  exactly onto a 4×3 tile grid and the crop is upscaled to 1792×1344 for no
240
+ added information. Across randomly sized crops of a 960×544 frame, the total
241
+ prompt cost swung between 1,040 and 4,112 tokens.
242
+
243
+ When sending region crops, letterbox them into a fixed 448×448 canvas
244
+ (preserving aspect ratio, no upscaling) to make cost constant at ~1,065 tokens
245
+ per request. On a small-text reading task this was 4× faster than sending the
246
+ raw crop, with no loss of accuracy down to 14 px glyphs.
247
 
248
  ## Choosing between the FP8 and AWQ builds
249
 
250
+ Measured on the same RTX 3070, same vLLM version, each tuned to its own best
251
+ settings on this card:
252
 
253
  | | FP8 Dynamic | **AWQ W4A16 G128** |
254
  |---|---|---|
255
  | Weights on GPU | 5.51 GiB | **3.84 GiB** |
256
+ | KV cache | 9,088 tokens (fp8 KV) | **12,288 tokens (fp16 KV)** |
 
 
257
  | KV cache dtype needed for 8192 ctx | fp8 (compromise) | **fp16** |
258
  | CUDA graphs on 8 GB | not possible | **yes** |
259
+ | `--gpu-memory-utilization` needed | 0.85 (near the 0.866 ceiling) | **0.79** |
260
+ | Process VRAM | 7.8 GB | **6.6 GB** |
261
+ | Text generation, single request | 12–18 tok/s | **~2–4× faster** (18–63 tok/s, varies with warm-up) |
262
  | Calibration data required | **no** | yes (see above) |
263
  | Weight precision | 8-bit | 4-bit |
264
 
265
+ AWQ is decisively better on memory and speed, and it is the only one of the
266
+ two that fits comfortably on an 8 GB card without compromises.
267
+
268
+ **Accuracy has not been compared between the two builds, or against the BF16
269
+ base model.** 4-bit weights and out-of-domain calibration are both reasons to
270
+ expect the AWQ build to degrade first on hard inputs. Benchmark both on your
271
+ own data before choosing.
272
 
273
  ## Limitations
274
 
 
276
  - **No accuracy benchmark has been published for this checkpoint.** Functional
277
  validation only: text generation, a 7k-token context, and multi-image
278
  (full frame + crop) requests all produce coherent, correct answers on simple
279
+ synthetic probes, both from a local directory and downloaded from this repo.
280
  - Calibration is general-purpose photography; see [Calibration](#calibration).
281
  - The vision tower and output head remain BF16 and are a meaningful share of
282
  the loaded weights, so the size reduction is smaller than 4/16 would suggest.
283
+ - The bundled `chat_template.jinja` has no tool-calling support.
 
284
  - VRAM figures depend on driver, desktop applications, context length,
285
  multimodal limits, and vLLM version.
286