# Migration grounding VLM — qwen2.5vl Ollama → vLLM/Transformers (Qwen3-VL) Date plan : 2026-05-09 (rédigé le 2026-05-08 au soir) Branche cible : feature/<à-créer-demain> État actuel : grounding passe par Ollama, qwen2.5vl:7b en split CPU/GPU 42/58. ## 1. Constat Deux problèmes structurels relevés le 8 mai 2026. ### 1.1. VRAM saturée `qwen2.5vl:7b` chargé via Ollama pèse 14 GB en mémoire totale alors que la machine n'a que 12 GB de VRAM. Ollama bascule en split CPU/GPU 42/58. Latence mesurée par appel grounding : ~11 s. À titre de référence, `qwen3-vl:8b` (6 GB) tient en full GPU et descend à 1.7 s sur le même cas. ### 1.2. Bug d'échelle bbox_2d (root cause documentée) La doc officielle Qwen2.5-VL précise que les coordonnées renvoyées sont dans la résolution post-`smart_resize`, pas dans la résolution de l'image envoyée par le client. Or Ollama applique son propre `smart_resize` en interne sans exposer la taille effective au client. Conséquence : le code prod divise par `small_w = orig_w` (taille envoyée) au lieu de `resized_w` → coordonnées toutes shiftées vers le top-left. Bug systémique présent dans 4 occurrences de `agent_v0/server_v1/resolve_engine.py` et dans `_locate_popup_button` (L:2576). Tant que le grounding passe par Ollama, le fix ne peut être qu'une rustine (resize forcé côté serveur AVANT envoi pour matcher la convention que Ollama va appliquer ensuite — fragile, dépend de la version Ollama). Source : [HF discussion #13 sur Qwen2.5-VL-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct/discussions/13) Citation mainteneur : « The bbox_2d coordinates ... will be relative to your **resized image size** if you are resizing. » Citation discussion : « **resized dimensions parameter is not supported in OLLAMA**, which complicates coordinate translation. » ## 2. Rappel bench 8 mai 2026 Screenshot fixture : `data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792436.png` (2560×1600, boîte de dialogue OK/Cancel). | Modèle / config | Latence | Format bbox_2d | Parse regex prod | Coords cohérentes | |---|---|---|---|---| | qwen2.5vl:7b Ollama (num_predict=50, prod) | 11.0 s | `{"bbox_2d":[422,604,462,624]}` | oui | non (cx ≈ 0.17, top-left) | | qwen3-vl:8b Ollama (params prod stricts) | 8.0 s | vide (50 tokens absorbés par thinking) | non | n/a | | qwen3-vl:8b Ollama (think:false, num_predict=256) | 1.7 s | liste nue `[332,487,362,507]` | non (regex attend `"bbox_2d":[...]`) | n/a | | qwen3-vl:8b Ollama (prompt JSON explicite) | 1.8 s | `{"bbox_2d":[...]}` | oui | non (même bug d'échelle) | ## 3. Chemin technique cible vLLM ou Transformers direct, avec passage explicite de `resized_width` et `resized_height` au modèle (paramètre supporté par les deux backends), garantissant que les `bbox_2d` retournés sont dans la même résolution que celle qu'on a passée. Le service `core/grounding/server.py` (déjà en place pour InfiGUI via Unix socket, avec `_smart_resize`, `MIN_PIXELS=100*28*28`, `MAX_PIXELS=5600*28*28`) sert de référence architecturale. Modèle pressenti : `qwen3-vl:8b` (6 GB en VRAM, full GPU possible), avec : - `think:false` (désactiver le mode thinking par défaut) - `num_predict >= 128` (50 insuffisant : tokens absorbés par thinking quand activé) - prompt imposant le format JSON `[{"bbox_2d":[...],"label":"..."}]` - preprocessing image côté serveur : `_smart_resize` officiel (max_size=1280, multiples de 28, code dans la discussion HF) ## 4. Étapes de migration (5) 1. **[Setup] Choix du backend grounding** : vLLM, Transformers direct, ou réorientation de `core/grounding/server.py` vers Qwen3-VL plutôt que démarrer une 4ème stack. Décision à acter en début de chantier. 2. **[Preprocessing] Implémenter `_smart_resize` côté `resolve_engine.py`** avec la formule officielle (max_size=1280, multiples de 28). Capturer la nouvelle taille (`resized_w`, `resized_h`) et la passer au backend choisi. 3. **[Parser] Adapter les 4 occurrences `bbox_2d` parsing** (`resolve_engine.py:840-848`, 870-880, 908-917, et `_locate_popup_button` L:2576) pour diviser par `resized_w`/`resized_h` et non plus `small_w/h` (= `orig_w/h`). Centraliser dans une seule fonction utilitaire. 4. **[Prompt] Imposer le format JSON `bbox_2d`** dans le prompt envoyé (Qwen3-VL ne le sort pas spontanément, sortie liste nue par défaut). Ajouter `think:false` et `num_predict>=128` aux options. 5. **[Test bbox_2d cible] Refaire le bench du 8 mai** avec la convention contrôlée. Critère de validation : sur le screenshot heartbeat de référence, le bouton OK doit être localisé à un `cx ≈ 0.45-0.55` (mid-screen) et non plus 0.17. ## 5. Test bbox_2d à refaire (post-migration) Reproduire le test du 8 mai avec : - Même screenshot fixture : `data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792436.png` - Même cible : bouton "OK" de la boîte de dialogue - Backend nouveau (vLLM ou Transformers + `smart_resize` côté client) Sortie attendue : `bbox_2d` en pixels de l'image redimensionnée connue → division par `resized_w` → `cx` visuellement ≈ centre du bouton (vérification par overlay sur le screenshot). ## 6. Hors scope du chantier migration (à traiter après) - Nettoyage de la logique « décharger gemma4 pour qwen2.5vl » dans `agent_v0/server_v1/stream_processor.py:442,1742` — devient inutile une fois qwen2.5vl écarté. - Nettoyage des 9 mentions/commentaires `qwen2.5vl` dans le code. - Décision sur le maintien d'une compat fallback Ollama (utile en dev sans GPU) ou abandon complet. ## 7. Risques / points de vigilance - Si vLLM tourne sur le même GPU que streaming/agent-chat, vérifier que la VRAM tient : qwen3-vl 6 GB + streaming 0.8 GB + agent-chat 0.8 GB = 7.6 GB → marge OK sur 12 GB. - Le bench du 8 mai montrait des coordonnées visuellement incorrectes MÊME avec un format `bbox_2d` valide. La résolution du bug d'échelle est nécessaire mais peut-être pas suffisante. Vérifier après l'étape 5. - `_locate_popup_button` (`resolve_engine.py:2536-2585`) est plus simple à migrer que la cascade principale. Faire un POC sur cette fonction avant d'attaquer la cascade.