Files
wehub-resource-sync 542cfa195c
CI / Frontend build (push) Failing after 9m6s
CI / Plugin validate (push) Failing after 9m27s
CI / Python lint (push) Failing after 16m1s
CI / Tests (push) Successful in 18m0s
Deploy / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:27 +08:00

143 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.27.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
1. **LoRA + ViT unfrozen** → primary mechanism for 5x/9x gains.
2. **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).
3. **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.27.6 points.
4. **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](https://huggingface.co/Chrisyichuan):
- `qwen3vl-4b-wiki-screenshot-2x-lora` — 0.948 at 2x
- `qwen3vl-4b-wiki-screenshot-3x-lora` — 0.894 at 3x
- `qwen3vl-4b-wiki-screenshot-5x-lora` — 0.730 at 5x
- `qwen3vl-4b-wiki-screenshot-9x-lora` — 0.378 at 9x
- `qwen3vl-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.27.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