zandervdm commited on
Commit
5a4eb04
Β·
verified Β·
1 Parent(s): b1f9cc9

Add model card

Browse files
Files changed (1) hide show
  1. README.md +285 -0
README.md ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ base_model: Qwen/Qwen3-VL-2B-Instruct
4
+ pipeline_tag: image-text-to-text
5
+ library_name: transformers
6
+ tags:
7
+ - vision-language
8
+ - image-classification
9
+ - calibrated-probabilities
10
+ - qwen3-vl
11
+ ---
12
+
13
+ # Qevi-2B
14
+
15
+ A full fine-tune of [Qwen3-VL-2B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-2B-Instruct) that
16
+ answers **typed, closed questions about images** by reading the model's own logits instead of
17
+ generating text.
18
+
19
+ It is not a chat model. Ask it "is there a ladder in this image?" and it returns
20
+ `P(Yes) = 0.97`, not a sentence. In exchange for giving up open-ended answers you get
21
+ substantially better accuracy on closed questions, calibrated probabilities you can threshold on,
22
+ and up to ~21x faster inference when asking many questions about one image.
23
+
24
+ | | Base Qwen3-VL-2B | **Qevi-2B** |
25
+ |---|---|---|
26
+ | Accuracy, in-domain | 0.855 | **0.977** |
27
+ | Accuracy, held-out domains | 0.745 | **0.889** |
28
+ | Expected Calibration Error, held-out | 0.160 | **0.054** |
29
+
30
+ ---
31
+
32
+ ## Read this before you use it
33
+
34
+ **This model must be used through a logit readout, not `.generate()`.** All the numbers above are
35
+ measured by reading the LM-head logits at the position where the model would begin its reply,
36
+ restricted to the allowed answer tokens. If you call `.generate()` and parse the text, you will not
37
+ reproduce them, because that is not the path the model was fine-tuned on.
38
+
39
+ The readout is ~30 lines of ordinary transformers code and needs no `trust_remote_code`. A
40
+ self-contained version is below; the [`qevi`](#the-qevi-package) package adds question packing.
41
+
42
+ ---
43
+
44
+ ## Usage
45
+
46
+ ### Minimal, dependency-free (one question)
47
+
48
+ ```python
49
+ import torch
50
+ from PIL import Image
51
+ from transformers import AutoProcessor, Qwen3VLForConditionalGeneration
52
+
53
+ REPO = "MeerDevelopment/Qevi-2B"
54
+ model = Qwen3VLForConditionalGeneration.from_pretrained(REPO, dtype=torch.bfloat16).cuda().eval()
55
+ processor = AutoProcessor.from_pretrained(REPO)
56
+ tok = processor.tokenizer
57
+
58
+ image = Image.open("photo.jpg").convert("RGB")
59
+ statement = "There is a ladder in this image."
60
+ answers = ["Yes", "No"]
61
+
62
+ prompt = (
63
+ "<|im_start|>user\n<|vision_start|><|vision_end|>"
64
+ f"Statement: {statement}\n"
65
+ "Is this statement true of the image? Answer Yes or No."
66
+ "<|im_end|>\n<|im_start|>assistant\n"
67
+ )
68
+
69
+ inputs = processor(text=[prompt], images=[image], return_tensors="pt").to(model.device)
70
+ with torch.no_grad():
71
+ logits = model(**inputs).logits[0, -1] # scores at the sentinel position
72
+
73
+ cand = [tok.encode(a, add_special_tokens=False)[0] for a in answers]
74
+ probs = torch.softmax(logits[cand].float() / 2.45, dim=-1) # 2.45 = calibration temperature
75
+ print(dict(zip(answers, probs.tolist()))) # {'Yes': 0.97, 'No': 0.03}
76
+ ```
77
+
78
+ ### With the `qevi` package (many questions, one forward pass)
79
+
80
+ ```python
81
+ from qevi import build_readouts, forward_logits, softmax, noul_question, choice_question_generic
82
+
83
+ specs = [
84
+ (noul_question("There is a ladder in this image."), ["Yes", "No"]),
85
+ (noul_question("There is a bicycle in this image."), ["Yes", "No"]),
86
+ (choice_question_generic("Which is shown?", ["ladder", "bicycle",
87
+ "car", "person"]), ["A", "B", "C", "D"]),
88
+ ]
89
+ readouts = build_readouts(tok, specs)
90
+ logit_sets = forward_logits(model, processor, image, readouts) # ONE forward pass
91
+
92
+ for lg in logit_sets:
93
+ print([round(p, 4) for p in softmax(lg, T=2.45)])
94
+ ```
95
+
96
+ All three questions share a single encoding of the image and are isolated from each other by a
97
+ block-diagonal attention mask, so the answers are identical to asking them separately
98
+ (verified bitwise, see [Order invariance](#order-invariance)).
99
+
100
+ ---
101
+
102
+ ## Question types
103
+
104
+ | Type | Answer alphabet | Calibration temperature |
105
+ |---|---|---|
106
+ | `noul` | `["Yes", "No"]` | 2.45 |
107
+ | `choice` | `["A", "B", ...]`, up to 26 options | 1.9 |
108
+ | `score` | ordered level words, e.g. `["Poor","Fair","Good","Excellent"]` | 3.0 |
109
+
110
+ Temperatures were fitted on held-out data; dividing logits by them before softmax is what makes
111
+ the returned probabilities trustworthy (held-out ECE ~0.03-0.05 rather than ~0.16).
112
+
113
+ ---
114
+
115
+ ## Evaluation
116
+
117
+ Scored against **base Qwen3-VL-2B-Instruct run through the identical readout path**, on the same
118
+ images, in the same session. Both models therefore differ only in their weights, which is what
119
+ isolates the effect of fine-tuning from the effect of the readout. Neither model saw any of these
120
+ images during training.
121
+
122
+ ### In-domain (17,100 questions / 5,700 images, domains seen in training)
123
+
124
+ | Metric | Base 2B | Qevi-2B | Ξ” |
125
+ |---|---|---|---|
126
+ | Accuracy | 0.855 | **0.977** | +0.122 |
127
+ | Brier (lower better) | 0.231 | **0.039** | βˆ’0.192 |
128
+ | ECE (lower better) | 0.088 | **0.025** | βˆ’0.063 |
129
+ | Overconfidence gap | +0.088 | **βˆ’0.015** | βˆ’0.103 |
130
+
131
+ ### Held-out (10,800 questions / 3,600 images, 12 domains **never trained on**)
132
+
133
+ | Metric | Base 2B | Qevi-2B | Ξ” |
134
+ |---|---|---|---|
135
+ | Accuracy | 0.745 | **0.889** | +0.144 |
136
+ | Brier | 0.407 | **0.183** | βˆ’0.224 |
137
+ | ECE | 0.160 | **0.054** | βˆ’0.106 |
138
+ | Overconfidence gap | +0.160 | **+0.054** | βˆ’0.106 |
139
+
140
+ The model improved **more** on unseen domains (+14.4) than on trained ones (+12.2), which is the
141
+ main evidence that it learned a transferable skill rather than memorising the corpus.
142
+
143
+ > **A note on the baseline.** The corpus stores `teacher_probs` recorded when it was built, and an
144
+ > earlier draft of this card used those as the baseline. Three of the 31 groups (`food101`,
145
+ > `flowers102`, and the content-safety set) had been labelled by the larger Qwen3-VL-**8B**, so
146
+ > that baseline was a mixture of two models rather than the 2B this model was fine-tuned from.
147
+ > The tables above instead come from re-running base 2B over both eval sets directly. The
148
+ > difference is small (in-domain 0.855 vs the mixed 0.863; held-out 0.745 vs 0.741) and moves the
149
+ > headline slightly in this model's favour, but the comparison is now exactly what it claims to be.
150
+
151
+ ### Generalisation to unseen corruptions
152
+
153
+ Training included only `clean`, `contrast`, `gaussian_noise` and `impulse_noise`. The other four
154
+ corruptions were held out entirely, and gained just as much:
155
+
156
+ | Satellite imagery | Seen in training? | Base | Qevi-2B |
157
+ |---|---|---|---|
158
+ | clean / contrast / gaussian / impulse | yes | 0.54–0.72 | 0.96–0.98 |
159
+ | jpeg / motion blur / pixelate / spatter | **no** | 0.45–0.61 | 0.89–0.97 |
160
+
161
+ ### Known regressions
162
+
163
+ Two of 31 domains got **worse**. Stated plainly because they bound what this model is good for:
164
+
165
+ - **German traffic signs (GTSRB): 0.834 vs 0.879 base.** The only true regression. No traffic-sign
166
+ data was in the training corpus, so nothing anchored this domain against weight drift. If signs
167
+ matter to you, use the base model or fine-tune further with sign data included.
168
+ - **Pixelated car models: 0.474 vs 0.501 base.** Both models are near the floor. Distinguishing 196
169
+ car models after heavy pixelation is close to impossible; the information is not in the image.
170
+
171
+ ### Speed
172
+
173
+ Packed inference vs. asking the same questions one at a time. Both paths read logits rather than
174
+ generating, so this measures the benefit of packing alone and is not flattered by comparing
175
+ against text generation. RTX 3090, bfloat16, CUDA-synchronised, median of 3 runs after warm-up:
176
+
177
+ | Questions per image | 1 | 5 | 10 | 20 | 30 |
178
+ |---|---|---|---|---|---|
179
+ | Packed (ms) | 504 | 509 | 518 | 577 | 618 |
180
+ | One at a time (ms) | 501 | 2,504 | 5,002 | 9,988 | 15,013 |
181
+ | **Speedup** | 0.99x | **4.9x** | **9.7x** | **17.3x** | **24.3x** |
182
+
183
+ Marginal cost of each additional question is ~5 ms on this hardware, against ~500 ms to encode the
184
+ image; that ratio is the whole mechanism.
185
+
186
+ **There is no benefit at N=1** (0.99x, i.e. parity), which is expected: packing one question is
187
+ just asking one question. The gain comes entirely from amortising the image encode, so it grows
188
+ with the number of questions you ask per image.
189
+
190
+ Absolute latencies are image-dependent (a larger image means more vision tokens) and
191
+ hardware-dependent; the speedup ratio is the portable number.
192
+
193
+ ### Order invariance
194
+
195
+ Permuting question order yields bitwise-identical logits within float tolerance (atol 1e-5). On
196
+ choice and score tasks, 90/90 images produced byte-for-byte identical predictions packed vs.
197
+ unpacked. Packing does not silently change answers.
198
+
199
+ ---
200
+
201
+ ## Training
202
+
203
+ | | |
204
+ |---|---|
205
+ | Base | Qwen3-VL-2B-Instruct (2,127,532,032 params, Apache 2.0) |
206
+ | Method | Full fine-tune, all parameters trainable (no LoRA) |
207
+ | Objective | Cross-entropy over candidate-answer logits only, label smoothing 0.05 |
208
+ | Data | 85,500 questions over 28,500 images, 3 questions per image |
209
+ | Epochs | 1 |
210
+ | LR | 2e-5, OneCycle, 10% warm-up |
211
+ | Batch | 8 images via gradient accumulation |
212
+ | Optimiser | 8-bit AdamW (bitsandbytes), grad clip 1.0 |
213
+ | Precision | bfloat16, gradient checkpointing enabled |
214
+ | Hardware | 2x RTX 3090, ~2.5 hours |
215
+
216
+ Training targeted **ground-truth labels** from the source datasets, not the base model's
217
+ predictions, so the model learns to be correct rather than to imitate its teacher.
218
+
219
+ ### Training data
220
+
221
+ Assembled from 31 publicly available image datasets spanning natural photos, sketches, paintings,
222
+ clipart, satellite imagery, textures, fine-grained species and product categories, plus synthetic
223
+ image corruptions. **The corpus itself is not redistributed with this model.**
224
+
225
+ ## Licensing
226
+
227
+ **Weights: CC BY-NC 4.0 (non-commercial).** Not because the base model requires it, but because of
228
+ training-data provenance. Of the twelve source dataset families, only EuroSAT and the
229
+ content-safety set are permissively licensed (MIT); the other ten (CUB-200-2011, Stanford Cars,
230
+ Food-101, SUN397, Oxford Flowers-102, Oxford-IIIT Pets, DTD, GTSRB, DomainNet, RESISC45) are
231
+ distributed for academic or research use only. Whether a research-only dataset licence reaches the
232
+ weights of a model trained on it is genuinely unsettled, so this release takes the cautious side
233
+ rather than granting commercial rights it may not be in a position to grant.
234
+
235
+ **Code: Apache 2.0.** The [`qevi`](#the-qevi-package) package carries no data-provenance
236
+ encumbrance and is freely usable, including commercially.
237
+
238
+ **Base model: Apache 2.0** ([Qwen3-VL-2B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-2B-Instruct)),
239
+ unaffected by the above.
240
+
241
+ **If you need commercially-licensable weights:** the method, the code and the base model are all
242
+ permissive. Retrain using the `qevi` package on data you own or that is licensed for commercial
243
+ use, and the resulting weights carry no encumbrance from this corpus. The recipe is fully
244
+ documented above (1 epoch, lr 2e-5, ~5 GPU-hours on two consumer cards).
245
+
246
+ ---
247
+
248
+ ## Limitations
249
+
250
+ - **Closed answers only.** Every question needs a finite answer set declared up front. It cannot
251
+ caption, describe, or answer something you did not anticipate.
252
+ - **Free-form generation survives, but answers get terser.** The fine-tune optimised only the
253
+ typed-readout objective, so we checked whether ordinary `.generate()` still works. It does:
254
+ across 5 held-out images x 3 open prompts, greedy decoding produced coherent, grammatical,
255
+ on-topic text with no repetition collapse (degeneracy 0.002 vs 0.001 for base) and no empty
256
+ outputs. The measurable change is **length: mean 28.3 generated tokens vs 50.7 for the base**,
257
+ a ~44% drop. Having been trained on one-word answers, the model answers open questions more
258
+ directly ("Spiders" where the base writes "The main object in this image is a spider.").
259
+ This is a behavioural shift, not a capability loss, but if you want verbose descriptions the
260
+ base model is the better choice.
261
+ - **Answer tokens must be single-token-distinguishable.** Candidates are matched on their first
262
+ token, so options sharing a first token (e.g. "Poor" vs "Poorly") collide.
263
+ - **English prompts only**, matching the prompt templates used in training.
264
+ - **Not a safety classifier.** One training domain involved content-severity labels, but this model
265
+ has not been validated for moderation use and should not be deployed for it without its own
266
+ evaluation.
267
+
268
+ ## The `qevi` package
269
+
270
+ Packing, prompt construction and the readout live in a small Apache-2.0 package
271
+ (~350 lines, torch only):
272
+
273
+ ```bash
274
+ pip install qevi
275
+ ```
276
+
277
+ ## Citation
278
+
279
+ ```bibtex
280
+ @misc{qevi2b,
281
+ title = {Qevi-2B: typed question answering over images without text generation},
282
+ year = {2026},
283
+ note = {Full fine-tune of Qwen3-VL-2B-Instruct with packed native-logit readout}
284
+ }
285
+ ```