Views
No views yet
Qwen2-VL-2B-Instruct student
fine-tuned with LoRA to reproduce the screenshot descriptions of a larger
Qwen2-VL-7B-Instruct teacher (sequence-level knowledge distillation).Qwen/Qwen2-VL-7B-Instruct (4-bit)Qwen/Qwen2-VL-2B-Instruct (this repo ships a LoRA adapter)Status: proof of concept. Validated end-to-end on an Apple M4 Pro (MPS): train loss 0.80 → 0.39, and the reloaded adapter generates in the trained format. The numbers below are from a small PoC run; a full-scale run is tracked in the repo.
| model | ROUGE-L | BLEU |
|---|---|---|
| distilled student | 0.178 | 0.019 |
| untrained baseline | 0.153 | 0.018 |
| model | params (B) | latency p50 (ms) | throughput (img/s) | peak mem (GB) |
|---|---|---|---|---|
| teacher (Qwen2-VL-7B) | 8.29 | 1538 | 0.63 | 5.8 |
| student (Qwen2-VL-2B) | 2.21 | 651 | 1.52 | 2.4 |


Note: against the short human references (median 7 words), ROUGE-L/BLEU undersell the verbose teacher — the 7B teacher actually scores lower on ROUGE-L (0.164) than the distilled student (0.178). LLM-as-judge / CIDEr would reward content over brevity-matching; the unambiguous win here is efficiency.
1import torch
2from peft import PeftModel
3from PIL import Image
4from transformers import AutoProcessor, Qwen2VLForConditionalGeneration
5
6BASE = "Qwen/Qwen2-VL-2B-Instruct"
7ADAPTER = "p00rt/qwen2-vl-2b-screenshots-distill"
8
9processor = AutoProcessor.from_pretrained(BASE, min_pixels=200704, max_pixels=401408)
10model = Qwen2VLForConditionalGeneration.from_pretrained(BASE, torch_dtype=torch.bfloat16)
11model = PeftModel.from_pretrained(model, ADAPTER).eval()
12
13image = Image.open("screenshot.png")
14prompt = ("Describe this UI screenshot in one sentence, then list the key "
15 "interface elements as a comma-separated list.")
16messages = [{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": prompt}]}]
17text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
18inputs = processor(text=[text], images=[image], return_tensors="pt").to(model.device)
19out = model.generate(**inputs, max_new_tokens=128)
20print(processor.batch_decode(out[:, inputs["input_ids"].shape[1]:], skip_special_tokens=True)[0])max_pixels = 512·28·28) so large screenshots fit the
context window.transformers + peft (hf), on CUDA or Apple MPS.Qwen/Qwen2-VL-2B-Instruct: see the base model's license.