# LLaVA

Discutez avec des images en utilisant LLaVA - l'alternative open-source à GPT-4V.

{% hint style="success" %}
Tous les exemples peuvent être exécutés sur des serveurs GPU loués via [CLORE.AI Marketplace](https://clore.ai/marketplace).
{% endhint %}

## Location sur CLORE.AI

1. Visitez [CLORE.AI Marketplace](https://clore.ai/marketplace)
2. Filtrer par type de GPU, VRAM et prix
3. Choisir **À la demande** (tarif fixe) ou **Spot** (prix d'enchère)
4. Configurez votre commande :
   * Sélectionnez l'image Docker
   * Définissez les ports (TCP pour SSH, HTTP pour les interfaces web)
   * Ajoutez des variables d'environnement si nécessaire
   * Entrez la commande de démarrage
5. Sélectionnez le paiement : **CLORE**, **BTC**, ou **USDT/USDC**
6. Créez la commande et attendez le déploiement

### Accédez à votre serveur

* Trouvez les détails de connexion dans **Mes commandes**
* Interfaces Web : utilisez l'URL du port HTTP
* SSH : `ssh -p <port> root@<adresse-proxy>`

## Qu'est-ce que LLaVA ?

LLaVA (Large Language and Vision Assistant) peut :

* Comprendre et décrire des images
* Répondre aux questions sur le contenu visuel
* Analyser des graphiques, diagrammes, captures d'écran
* OCR et compréhension de documents

## Variantes de modèle

| Modèle        | Taille | VRAM   | Qualité  |
| ------------- | ------ | ------ | -------- |
| LLaVA-1.5-7B  | 7B     | 8 Go   | Bon      |
| LLaVA-1.5-13B | 13B    | 16Go   | Meilleur |
| LLaVA-1.6-34B | 34B    | 40Go   | Meilleur |
| LLaVA-NeXT    | 7-34B  | 8-40GB | Dernier  |

## Déploiement rapide

**Image Docker :**

```
pytorch/pytorch:2.5.1-cuda12.4-cudnn9-devel
```

**Ports :**

```
22/tcp
8000/http
```

**Commande :**

```bash
pip install llava torch transformers accelerate gradio && \
python -m llava.serve.cli --model-path liuhaotian/llava-v1.5-7b --load-4bit
```

## Accéder à votre service

Après le déploiement, trouvez votre `http_pub` URL dans **Mes commandes**:

1. Aller à la **Mes commandes** page
2. Cliquez sur votre commande
3. Trouvez l' `http_pub` URL (par ex., `abc123.clorecloud.net`)

Utilisez `https://VOTRE_HTTP_PUB_URL` au lieu de `localhost` dans les exemples ci-dessous.

## Installation

```bash
git clone https://github.com/haotian-liu/LLaVA.git
cd LLaVA
pip install -e .
pip install flash-attn --no-build-isolation
```

## Utilisation de base

### API Python

```python
from llava.model.builder import load_pretrained_model
from llava.mm_utils import get_model_name_from_path
from llava.eval.run_llava import eval_model
from PIL import Image

model_path = "liuhaotian/llava-v1.5-7b"
tokenizer, model, image_processor, context_len = load_pretrained_model(
    model_path=model_path,
    model_base=None,
    model_name=get_model_name_from_path(model_path)
)

# Inférence simple
args = type('Args', (), {
    "model_path": model_path,
    "model_base": None,
    "model_name": get_model_name_from_path(model_path),
    "query": "Décris cette image en détail",
    "conv_mode": None,
    "image_file": "photo.jpg",
    "sep": ",",
    "temperature": 0.2,
    "top_p": None,
    "num_beams": 1,
    "max_new_tokens": 512
})()

output = eval_model(args)
print(output)
```

### Utilisation de Transformers

```python
from transformers import LlavaNextProcessor, LlavaNextForConditionalGeneration
import torch
from PIL import Image

processor = LlavaNextProcessor.from_pretrained("llava-hf/llava-v1.6-mistral-7b-hf")
model = LlavaNextForConditionalGeneration.from_pretrained(
    "llava-hf/llava-v1.6-mistral-7b-hf",
    torch_dtype=torch.float16,
    device_map="auto"
)

# Charger l'image
image = Image.open("photo.jpg")

# Créer la conversation
conversation = [
    {
        "role": "user",
        "content": [
            {"type": "image"},
            {"type": "text", "text": "Que montre cette image ?"}
        ]
    }
]

prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
inputs = processor(prompt, image, return_tensors="pt").to("cuda")

output = model.generate(**inputs, max_new_tokens=200)
response = processor.decode(output[0], skip_special_tokens=True)
print(response)
```

## Intégration Ollama (recommandée)

La façon la plus simple d'exécuter LLaVA sur CLORE.AI :

```bash
# Installer Ollama
curl -fsSL https://ollama.com/install.sh | sh

# Récupérer le modèle LLaVA
ollama pull llava:7b

# Exécuter avec une image (CLI)
ollama run llava:7b "Décris cette image : /chemin/vers/image.jpg"
```

### API LLaVA via Ollama

{% hint style="warning" %}
**Important :** La vision LLaVA fonctionne **uniquement** via le `/api/generate` endpoint avec le `paramètre images` Le `/api/chat` et les endpoints compatibles OpenAI **ne** prennent pas en charge les images avec LLaVA.
{% endhint %}

#### Mode de fonctionnement : /api/generate

```bash
# Encoder l'image en base64 d'abord
BASE64_IMAGE=$(base64 -i photo.jpg | tr -d '\n')

# Envoyer la requête vision
curl https://your-http-pub.clorecloud.net/api/generate -d "{
  \"model\": \"llava:7b\",
  \"prompt\": \"Que voyez-vous dans cette image ? Décrivez en détail.\",
  \"images\": [\"$BASE64_IMAGE\"],
  \"stream\": false
}"
```

Réponse :

```json
{
  "model": "llava:7b",
  "response": "L'image montre un magnifique coucher de soleil sur des montagnes...",
  "done": true
}
```

#### Ne fonctionne PAS : /api/chat (retourne null pour la vision)

```bash
# Ceci NE fonctionne PAS pour les requêtes vision :
curl https://your-http-pub.clorecloud.net/api/chat -d '{
  "model": "llava:7b",
  "messages": [{"role": "user", "content": "describe", "images": ["..."]}]
}'
# Retourne null pour les réponses liées aux images
```

### Python avec Ollama

```python
import requests
import base64

def encode_image(image_path):
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode()

# Utilisez /api/generate pour la vision (PAS /api/chat !)
response = requests.post(
    "https://your-http-pub.clorecloud.net/api/generate",
    json={
        "model": "llava:7b",
        "prompt": "Que voyez-vous dans cette image ?",
        "images": [encode_image("photo.jpg")],
        "stream": False
    }
)

print(response.json()["response"])
```

### Exemple complet fonctionnel

```python
import requests
import base64
import sys

def analyze_image(ollama_url, image_path, question):
    """Analyser une image en utilisant LLaVA via Ollama"""

    # Encoder l'image
    with open(image_path, "rb") as f:
        image_base64 = base64.b64encode(f.read()).decode()

    # Utilisez /api/generate (le seul endpoint fonctionnel pour la vision)
    response = requests.post(
        f"{ollama_url}/api/generate",
        json={
            "model": "llava:7b",
            "prompt": question,
            "images": [image_base64],
            "stream": False
        }
    )

    return response.json()["response"]

# Utilisation
url = "https://your-http-pub.clorecloud.net"
result = analyze_image(url, "photo.jpg", "Décris cette image en détail")
print(result)
```

## Cas d'utilisation

### Description de l'image

```python
prompt = "Décris cette image en détail, y compris les couleurs, les objets et l'atmosphère."
```

### OCR / Extraction de texte

```python
prompt = "Extraire tout le texte visible dans cette image. Formatez-le clairement."
```

### Analyse de graphiques

```python
prompt = "Analysez ce graphique. Quelles sont les tendances et les idées clés ?"
```

### Code à partir d'une capture d'écran

```python
prompt = "Extrayez le code affiché dans cette capture d'écran. Fournissez uniquement le code."
```

### Détection d'objets

```python
prompt = "Listez tous les objets visibles dans cette image avec leurs emplacements approximatifs."
```

## Interface Gradio

```python
import gradio as gr
from transformers import LlavaNextProcessor, LlavaNextForConditionalGeneration
import torch

processor = LlavaNextProcessor.from_pretrained("llava-hf/llava-v1.6-mistral-7b-hf")
model = LlavaNextForConditionalGeneration.from_pretrained(
    "llava-hf/llava-v1.6-mistral-7b-hf",
    torch_dtype=torch.float16,
    device_map="auto"
)

def analyze_image(image, question):
    conversation = [
        {
            "role": "user",
            "content": [
                {"type": "image"},
                {"type": "text", "text": question}
            ]
        }
    ]

    prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
    inputs = processor(prompt, image, return_tensors="pt").to("cuda")

    output = model.generate(**inputs, max_new_tokens=500)
    response = processor.decode(output[0], skip_special_tokens=True)

    # Extraire la réponse de l'assistant
    return response.split("[/INST]")[-1].strip()

demo = gr.Interface(
    fn=analyze_image,
    inputs=[
        gr.Image(type="pil", label="Image"),
        gr.Textbox(label="Question", value="Décris cette image en détail")
    ],
    outputs=gr.Textbox(label="Réponse"),
    title="Assistant de vision LLaVA"
)

demo.launch(server_name="0.0.0.0", server_port=8000)
```

## Serveur API

```python
from fastapi import FastAPI, UploadFile, File, Form
from transformers import LlavaNextProcessor, LlavaNextForConditionalGeneration
import torch
from PIL import Image
import io

app = FastAPI()

processor = LlavaNextProcessor.from_pretrained("llava-hf/llava-v1.6-mistral-7b-hf")
model = LlavaNextForConditionalGeneration.from_pretrained(
    "llava-hf/llava-v1.6-mistral-7b-hf",
    torch_dtype=torch.float16,
    device_map="auto"
)

@app.post("/analyze")
async def analyze(
    image: UploadFile = File(...),
    question: str = Form(default="Décris cette image")
):
    img = Image.open(io.BytesIO(await image.read()))

    conversation = [
        {
            "role": "user",
            "content": [
                {"type": "image"},
                {"type": "text", "text": question}
            ]
        }
    ]

    prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
    inputs = processor(prompt, img, return_tensors="pt").to("cuda")

    output = model.generate(**inputs, max_new_tokens=500)
    response = processor.decode(output[0], skip_special_tokens=True)

    return {"response": response.split("[/INST]")[-1].strip()}

# Lancer : uvicorn server:app --host 0.0.0.0 --port 8000
```

## Traitement par lots

```python
from transformers import LlavaNextProcessor, LlavaNextForConditionalGeneration
import torch
from PIL import Image
import os

processor = LlavaNextProcessor.from_pretrained("llava-hf/llava-v1.6-mistral-7b-hf")
model = LlavaNextForConditionalGeneration.from_pretrained(
    "llava-hf/llava-v1.6-mistral-7b-hf",
    torch_dtype=torch.float16,
    device_map="auto"
)

def analyze_image(image_path, question):
    image = Image.open(image_path)

    conversation = [
        {"role": "user", "content": [
            {"type": "image"},
            {"type": "text", "text": question}
        ]}
    ]

    prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
    inputs = processor(prompt, image, return_tensors="pt").to("cuda")

    output = model.generate(**inputs, max_new_tokens=300)
    return processor.decode(output[0], skip_special_tokens=True).split("[/INST]")[-1].strip()

# Traiter un dossier d'images
image_folder = "./images"
results = []

for filename in os.listdir(image_folder):
    if filename.endswith(('.jpg', '.png', '.jpeg')):
        path = os.path.join(image_folder, filename)
        description = analyze_image(path, "Décris brièvement cette image")
        results.append({"file": filename, "description": description})
        print(f"{filename}: {description[:100]}...")

# Enregistrer les résultats
import json
with open("descriptions.json", "w") as f:
    json.dump(results, f, indent=2)
```

## Optimisation de la mémoire

### Quantification 4 bits

```python
from transformers import BitsAndBytesConfig

quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.float16
)

model = LlavaNextForConditionalGeneration.from_pretrained(
    "llava-hf/llava-v1.6-mistral-7b-hf",
    quantization_config=quantization_config,
    device_map="auto"
)
```

### Déchargement CPU

```python
model = LlavaNextForConditionalGeneration.from_pretrained(
    "llava-hf/llava-v1.6-mistral-7b-hf",
    torch_dtype=torch.float16,
    device_map="auto",
    offload_folder="offload"
)
```

## Performances

| Modèle        | GPU      | Tokens/sec |
| ------------- | -------- | ---------- |
| LLaVA-1.5-7B  | RTX 3090 | \~30       |
| LLaVA-1.5-7B  | RTX 4090 | \~45       |
| LLaVA-1.6-7B  | RTX 4090 | \~40       |
| LLaVA-1.5-13B | A100     | \~35       |

## Dépannage

### Mémoire insuffisante

```python

# Utiliser la quantification 4 bits

# Ou utiliser un modèle plus petit (7B au lieu de 13B)

# Ou traiter des images plus petites
image = image.resize((336, 336))
```

### Génération lente

* Utiliser l'attention flash
* Réduire max\_new\_tokens
* Utiliser un modèle quantifié

### Mauvaise qualité

* Utiliser un modèle plus grand
* Meilleurs prompts avec contexte
* Images de résolution plus élevée

## Estimation des coûts

Tarifs typiques du marché CLORE.AI (à partir de 2024) :

| GPU       | Tarif horaire | Tarif journalier | Session de 4 heures |
| --------- | ------------- | ---------------- | ------------------- |
| RTX 3060  | \~$0.03       | \~$0.70          | \~$0.12             |
| RTX 3090  | \~$0.06       | \~$1.50          | \~$0.25             |
| RTX 4090  | \~$0.10       | \~$2.30          | \~$0.40             |
| A100 40GB | \~$0.17       | \~$4.00          | \~$0.70             |
| A100 80GB | \~$0.25       | \~$6.00          | \~$1.00             |

*Les prix varient selon le fournisseur et la demande. Vérifiez* [*CLORE.AI Marketplace*](https://clore.ai/marketplace) *pour les tarifs actuels.*

**Économisez de l'argent :**

* Utilisez **Spot** market pour les charges de travail flexibles (souvent 30-50 % moins cher)
* Payer avec **CLORE** jetons
* Comparer les prix entre différents fournisseurs

## Prochaines étapes

* LLMs Ollama - Exécuter LLaVA avec Ollama
* RAG + LangChain - Vision + RAG
* vLLM Inference - Mise en production
