# LLaVA

Chatea con imágenes usando LLaVA: la alternativa de código abierto a GPT-4V.

{% hint style="success" %}
Todos los ejemplos se pueden ejecutar en servidores GPU alquilados a través de [CLORE.AI Marketplace](https://clore.ai/marketplace).
{% endhint %}

## Alquilar en CLORE.AI

1. Visita [CLORE.AI Marketplace](https://clore.ai/marketplace)
2. Filtrar por tipo de GPU, VRAM y precio
3. Elegir **Bajo demanda** (tarifa fija) o **Spot** (precio de puja)
4. Configura tu pedido:
   * Selecciona imagen Docker
   * Establece puertos (TCP para SSH, HTTP para interfaces web)
   * Agrega variables de entorno si es necesario
   * Introduce el comando de inicio
5. Selecciona pago: **CLORE**, **BTC**, o **USDT/USDC**
6. Crea el pedido y espera el despliegue

### Accede a tu servidor

* Encuentra los detalles de conexión en **Mis Pedidos**
* Interfaces web: Usa la URL del puerto HTTP
* SSH: `ssh -p <port> root@<proxy-address>`

## ¿Qué es LLaVA?

LLaVA (Large Language and Vision Assistant) puede:

* Entender y describir imágenes
* Responder preguntas sobre contenido visual
* Analizar gráficos, diagramas, capturas de pantalla
* OCR y comprensión de documentos

## Variantes de modelo

| Modelo        | Tamaño | VRAM   | Calidad |
| ------------- | ------ | ------ | ------- |
| LLaVA-1.5-7B  | 7B     | 8GB    | Bueno   |
| LLaVA-1.5-13B | 13B    | 16GB   | Mejor   |
| LLaVA-1.6-34B | 34B    | 40GB   | Mejor   |
| LLaVA-NeXT    | 7-34B  | 8-40GB | Último  |

## Despliegue rápido

**Imagen Docker:**

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

**Puertos:**

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

**Comando:**

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

## Accediendo a tu servicio

Después del despliegue, encuentra tu `http_pub` URL en **Mis Pedidos**:

1. Ir a **Mis Pedidos** página
2. Haz clic en tu pedido
3. Encuentra la `http_pub` URL (por ejemplo, `abc123.clorecloud.net`)

Usa `https://TU_HTTP_PUB_URL` en lugar de `localhost` en los ejemplos abajo.

## Instalación

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

## Uso básico

### API de 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)
)

# Inferencia simple
args = type('Args', (), {
    "model_path": model_path,
    "model_base": None,
    "model_name": get_model_name_from_path(model_path),
    "query": "Describe this image in detail",
    "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)
```

### Usando 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"
)

# Cargar imagen
image = Image.open("photo.jpg")

# Crear conversación
conversation = [
    {
        "role": "user",
        "content": [
            {"type": "image"},
            {"type": "text", "text": "What is shown in this 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)
```

## Integración con Ollama (Recomendado)

La forma más fácil de ejecutar LLaVA en CLORE.AI:

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

# Descargar el modelo LLaVA
ollama pull llava:7b

# Ejecutar con imagen (CLI)
ollama run llava:7b "Describe this image: /path/to/image.jpg"
```

### API de LLaVA vía Ollama

{% hint style="warning" %}
**Importante:** La visión de LLaVA funciona **solo** a través de la `/api/generate` endpoint con el `parámetro images` El `/api/chat` y los endpoints compatibles con OpenAI no **soportan** imágenes con LLaVA.
{% endhint %}

#### Método de trabajo: /api/generate

```bash
# Codificar la imagen a base64 primero
BASE64_IMAGE=$(base64 -i photo.jpg | tr -d '\n')

# Enviar solicitud de visión
curl https://your-http-pub.clorecloud.net/api/generate -d "{
  \"model\": \"llava:7b\",
  \"prompt\": \"What do you see in this image? Describe in detail.\",
  \"images\": [\"$BASE64_IMAGE\"],
  \"stream\": false
}"
```

Respuesta:

```json
{
  "model": "llava:7b",
  "response": "The image shows a beautiful sunset over mountains...",
  "done": true
}
```

#### NO funciona: /api/chat (devuelve null para visión)

```bash
# Esto NO funciona para consultas de visión:
curl https://your-http-pub.clorecloud.net/api/chat -d '{
  "model": "llava:7b",
  "messages": [{"role": "user", "content": "describe", "images": ["..."]}]
}'
# Devuelve null para respuestas relacionadas con imágenes
```

### Python con Ollama

```python
import requests
import base64

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

# Usa /api/generate para visión (¡NO /api/chat!)
response = requests.post(
    "https://your-http-pub.clorecloud.net/api/generate",
    json={
        "model": "llava:7b",
        "prompt": "What do you see in this image?",
        "images": [encode_image("photo.jpg")],
        "stream": False
    }
)

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

### Ejemplo completo que funciona

```python
import requests
import base64
import sys

def analyze_image(ollama_url, image_path, question):
    """Analizar una imagen usando LLaVA vía Ollama"""

    # Codificar imagen
    with open(image_path, "rb") as f:
        image_base64 = base64.b64encode(f.read()).decode()

    # Usar /api/generate (el único endpoint que funciona para visión)
    response = requests.post(
        f"{ollama_url}/api/generate",
        json={
            "model": "llava:7b",
            "prompt": question,
            "images": [image_base64],
            "stream": False
        }
    )

    return response.json()["response"]

# Uso
url = "https://your-http-pub.clorecloud.net"
result = analyze_image(url, "photo.jpg", "Describe this image in detail")
print(result)
```

## Casos de uso

### Descripción de la imagen

```python
prompt = "Describe this image in detail, including colors, objects, and atmosphere."
```

### OCR / Extracción de texto

```python
prompt = "Extract all text visible in this image. Format it clearly."
```

### Análisis de gráficos

```python
prompt = "Analyze this chart. What are the key trends and insights?"
```

### Código de una captura de pantalla

```python
prompt = "Extract the code shown in this screenshot. Provide only the code."
```

### Detección de objetos

```python
prompt = "List all objects visible in this image with their approximate locations."
```

## Interfaz 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)

    # Extraer la respuesta del asistente
    return response.split("[/INST]")[-1].strip()

demo = gr.Interface(
    fn=analyze_image,
    inputs=[
        gr.Image(type="pil", label="Image"),
        gr.Textbox(label="Question", value="Describe this image in detail")
    ],
    outputs=gr.Textbox(label="Response"),
    title="LLaVA Vision Assistant"
)

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

## Servidor 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="Describe this 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()}

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

## Procesamiento por lotes

```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()

# Procesar carpeta de imágenes
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, "Describe this image briefly")
        results.append({"file": filename, "description": description})
        print(f"{filename}: {description[:100]}...")

# Guardar resultados
import json
with open("descriptions.json", "w") as f:
    json.dump(results, f, indent=2)
```

## Optimización de memoria

### Cuantización a 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"
)
```

### Descarga a CPU (CPU Offload)

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

## Rendimiento

| Modelo        | GPU      | Tokens/seg |
| ------------- | -------- | ---------- |
| 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       |

## Solución de problemas

### Memoria insuficiente

```python

# Usar cuantización de 4 bits

# O usar un modelo más pequeño (7B en lugar de 13B)

# O procesar imágenes más pequeñas
image = image.resize((336, 336))
```

### Generación lenta

* Usar flash attention
* Reducir max\_new\_tokens
* Usar modelo cuantizado

### Calidad pobre

* Usa un modelo más grande
* Mejores prompts con contexto
* Imágenes de mayor resolución

## Estimación de costos

Tarifas típicas del marketplace de CLORE.AI (a fecha de 2024):

| GPU       | Tarifa por hora | Tarifa diaria | Sesión de 4 horas |
| --------- | --------------- | ------------- | ----------------- |
| 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           |

*Los precios varían según el proveedor y la demanda. Consulta* [*CLORE.AI Marketplace*](https://clore.ai/marketplace) *para las tarifas actuales.*

**Ahorra dinero:**

* Usa **Spot** market para cargas de trabajo flexibles (a menudo 30-50% más barato)
* Paga con **CLORE** tokens
* Compara precios entre diferentes proveedores

## Próximos pasos

* LLMs de Ollama - Ejecutar LLaVA con Ollama
* RAG + LangChain - Visión + RAG
* Inferencia vLLM - Servicio en producción


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-es/modelos-de-vision/llava-vision-language.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
