7.5 KiB
Compressed-image SFT — Final Results
Target: maximize GPT-4.1 LLM-judge accuracy on test_hn_with_answer.jsonl (500 examples) for Qwen3-VL-4B under 2x / 3x / 5x / 9x pixel compression, without inflating visual-token count at inference.
Judge: gpt-4.1-2025-04-14, SimpleQA A/B/C template (sft/eval_baseline.py).
Headline table
| Compression | base (no SFT) | best specialized | best universal (one adapter) | specialized config |
|---|---|---|---|---|
| 0x (original) | 0.958 | — | — | ceiling |
| 2x | 0.904 | 0.948 | 0.940 | r=128, 2ep, lr 2e-5 (LLM-only sufficient) |
| 3x | 0.826 | 0.894 | 0.892 | r=256, 1ep, lr 1e-5, LLM+ViT |
| 5x | 0.554 | 0.730 | 0.692 | r=128, 2ep, lr 3e-5, LLM+ViT |
| 9x | 0.180 | 0.378 | 0.302 | r=512, 2ep, lr 1e-5, LLM+ViT |
Universal adapter is within 0.2–7.6 points of each specialized adapter — at 3x essentially tied, at 9x still meaningfully behind.
Key breakthrough: unfreezing the ViT
lora_target: all alone does not train the ViT in LlamaFactory — freeze_vision_tower: true is the default. Setting it to false unlocks LoRA on the ViT and gives big jumps at high compression:
| Compression | LLM-only | LLM+ViT | Δ |
|---|---|---|---|
| 2x | 0.948 | 0.948 | 0 |
| 3x | 0.882 | 0.894 | +1.2 |
| 5x | 0.634 | 0.730 | +9.6 |
| 9x | 0.272 | 0.378 | +8.2 |
Rule: the harder the compression, the more the ViT needs adaptation. 2x base is already near-ceiling so ViT LoRA adds nothing; 9x has the most distribution shift from pretraining so ViT capacity helps most (+30% relative).
What worked
- LoRA + ViT unfrozen → primary mechanism for 5x/9x gains.
- Bigger LoRA rank at high compression: 9x went r=32→128→256→512 for +2.4, +1.2, +0.8, +0.8 (diminishing but monotonic).
- Mixed-compression training (universal adapter): single LoRA trained on 2x+3x+5x+9x samples simultaneously. With r=256 + ViT unfrozen, the gap to specialized is only 0.2–7.6 points.
- Two epochs at 2x/3x/5x; one epoch at 9x (overfits faster with bigger rank).
What did not work
| Experiment | Outcome |
|---|---|
| Full FT (lr 1e-5) at 5x/9x | grad_norm 10+, output collapses (empty strings, 0000 artifacts). 9x fell to 0.176, below base 0.180. LoRA's implicit regularization wins. |
| Pre-upscaling 9x → original dim before ViT | Gives big accuracy boost (EM 0.78) but inflates visual tokens ~9×, defeating the compression use case. Rejected. |
| LoRA dropout 0.1 at 9x | No change in ceiling (~0.27). |
| More epochs (3+) at 5x | 0.620 at 3ep < 0.634 at 2ep. Overfit. |
| Rank 256 at 5x | 0.656 < 0.730 at r=128. Capacity saturated at r=128 for 5x. |
Think / CoT training (30k GPT-generated reasoning traces, <think>reasoning</think>answer targets) |
9x peak 0.286 < non-think 0.378. Traces generated blindly by GPT (no image) produce plausible-but-wrong reasoning at 9x where the image is unreadable. Model learns to fabricate confident reasoning that doesn't help the final answer. |
Compression-damage vs SFT-recovery
0x : ██████████████████████████████████████████ 0.958 (ceiling)
2x : ███████████████████████████████████████▏ 0.904 → SFT 0.948 (−1.0 gap, 81% recovered)
3x : ████████████████████████████████████ 0.826 → SFT 0.894 (−6.4 gap, 52% recovered)
5x : ████████████████████████ 0.554 → SFT 0.730 (−22.8 gap, 44% recovered)
9x : ███████▌ 0.180 → SFT 0.378 (−58.0 gap, 25% recovered)
SFT recovery at 2x/3x: 50-80%. At 5x/9x: 25-44% — residual gap is the physical compression limit the ViT can't undo.
Full experiment matrix
2x
| run | r | ep | lr | ViT | LLM-judge peak |
|---|---|---|---|---|---|
| base | — | — | — | — | 0.904 |
| v1 (LLM-only) | 128 | 2 | 2e-5 | frozen | 0.948 |
| llmvit-v1 | 128 | 2 | 2e-5 | trained | 0.948 (tied) |
3x
| run | r | ep | lr | ViT | peak |
|---|---|---|---|---|---|
| base | — | — | — | — | 0.826 |
| v1 | 32 | 1 | 1e-5 | frozen | 0.854 |
| v2 | 128 | 2 | 2e-5 | frozen | 0.878 |
| v3 | 256 | 2 | 2e-5 | frozen | 0.882 |
| llmvit-v1 | 256 | 2 | 2e-5 | trained | 0.884 (then collapsed) |
| llmvit-v2 | 256 | 1 | 1e-5 | trained | 0.894 |
5x
| run | r | ep | lr | ViT | peak |
|---|---|---|---|---|---|
| base | — | — | — | — | 0.554 |
| v1 | 32 | 1 | 1e-5 | frozen | 0.598 |
| v2 | 32 | 2 | 2e-5 | frozen | 0.628 |
| v3 | 128 | 2 | 3e-5 | frozen | 0.634 |
| v4 | 256 | 2 | 2e-5 | frozen | 0.620 |
| v5 | 128 | 3 | 3e-5 | frozen | 0.620 |
| fullft | — | 1 | 1e-5 | n/a | 0.562 (bad) |
| llmvit-v1 | 128 | 2 | 3e-5 | trained | 0.730 |
| llmvit-v2 | 256 | 2 | 2e-5 | trained | 0.656 |
9x
| run | r | ep | lr | ViT | peak |
|---|---|---|---|---|---|
| base | — | — | — | — | 0.180 |
| v1 | 32 | 1 | 1e-5 | frozen | 0.228 |
| v2 | 128 | 2 | 3e-5 | frozen | 0.252 |
| v3 | 256 | 1 | 2e-5 | frozen | 0.264 |
| v4 | 512 | 1 | 1.5e-5 | frozen | 0.272 |
| v5 | 256 | 2 | 1e-5 dropout 0.1 | frozen | 0.266 |
| fullft | — | 1 | 1e-5 | n/a | 0.176 (broken) |
| llmvit-v1 | 256 | 1 | 2e-5 | trained | 0.354 |
| llmvit-v2 | 512 | 2 | 1e-5 | trained | 0.378 |
| think-v1 | 256 | 2 | 1e-5 | trained (+think) | 0.286 (worse) |
Universal (one adapter for all compressions)
Trained on concatenated 2x+3x+5x+9x data (416k examples, 1 epoch).
| run | r | lr | ViT | 2x | 3x | 5x | 9x |
|---|---|---|---|---|---|---|---|
| v1 | 128 | 2e-5 | frozen | 0.924 | 0.862 | 0.620 | 0.250 |
| llmvit-v1 | 128 | 2e-5 | trained | 0.920 | 0.844 | 0.664 | 0.272 |
| llmvit-v2 | 256 | 1e-5 | trained | 0.940 | 0.892 | 0.692 | 0.302 |
Shipped artifacts (HuggingFace)
Five LoRA adapters pushed to Chrisyichuan:
qwen3vl-4b-wiki-screenshot-2x-lora— 0.948 at 2xqwen3vl-4b-wiki-screenshot-3x-lora— 0.894 at 3xqwen3vl-4b-wiki-screenshot-5x-lora— 0.730 at 5xqwen3vl-4b-wiki-screenshot-9x-lora— 0.378 at 9xqwen3vl-4b-wiki-screenshot-universal-lora— one LoRA, 0.940/0.892/0.692/0.302 across all four compressions
Practical recommendation
For most deployments, ship the universal adapter: 1.28 GB LoRA, one merge at load time, handles any of 2x/3x/5x/9x with near-specialized accuracy (within 0.2–7.6 LLM-judge points). If you know you'll only ever serve a specific compression level, use the specialized adapter for that level.
For 2x/3x the adapters are essentially production-ready (<10 point drop from uncompressed). 5x is usable when accuracy tolerance is moderate. 9x remains difficult — 0.378 is still far from the 0.958 ceiling, and this is dominated by the physical unreadability of 9×-compressed text rather than a capacity bottleneck we can solve with more training.
Files
- Configs:
sft/train_qwen3vl_*.yaml - Train logs:
logs/sft_train/sft_*.log - Eval JSONs:
sft/eval_out/*.json - Trace generator:
sft/generate_think_traces.py(think failed but kept for reference) - Mixed-data builder:
sft/prepare_mixed_data.py - Eval fanout:
sft/eval_fanout.sh - HF push:
sft/push_to_hf.py,sft/push_universal_to_hf.py - W&B project: https://wandb.ai/yichuan_wang-uc-berkeley-electrical-engineering-computer/qwen3vl-compressed-sft