# LLaVA

Общайтесь с изображениями с помощью LLaVA — открытой альтернативы GPT-4V.

{% hint style="success" %}
Все примеры можно запускать на GPU-серверах, арендуемых через [CLORE.AI Marketplace](https://clore.ai/marketplace).
{% endhint %}

## Аренда на CLORE.AI

1. Посетите [CLORE.AI Marketplace](https://clore.ai/marketplace)
2. Отфильтруйте по типу GPU, объему VRAM и цене
3. Выберите **On-Demand** (фиксированная ставка) или **Spot** (цена по ставке)
4. Настройте ваш заказ:
   * Выберите Docker-образ
   * Установите порты (TCP для SSH, HTTP для веб-интерфейсов)
   * Добавьте переменные окружения при необходимости
   * Введите команду запуска
5. Выберите способ оплаты: **CLORE**, **BTC**, или **USDT/USDC**
6. Создайте заказ и дождитесь развертывания

### Доступ к вашему серверу

* Найдите данные для подключения в **Моих заказах**
* Веб-интерфейсы: используйте URL HTTP-порта
* SSH: `ssh -p <port> root@<proxy-address>`

## Что такое LLaVA?

LLaVA (Large Language and Vision Assistant) может:

* Понимать и описывать изображения
* Отвечать на вопросы о визуальном содержимом
* Анализировать графики, диаграммы, скриншоты
* Оптическое распознавание текста (OCR) и понимание документов

## Варианты моделей

| Модель        | Размер | VRAM   | Качество  |
| ------------- | ------ | ------ | --------- |
| LLaVA-1.5-7B  | 7B     | 8GB    | Хорошо    |
| LLaVA-1.5-13B | 13B    | 16GB   | Лучше     |
| LLaVA-1.6-34B | 34B    | 40GB   | Лучшее    |
| LLaVA-NeXT    | 7-34B  | 8-40GB | Последняя |

## Быстрое развертывание

**Docker-образ:**

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

**Порты:**

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

**Команда:**

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

## Доступ к вашему сервису

После развертывания найдите ваш `http_pub` URL в **Моих заказах**:

1. Перейдите на **Моих заказах** страницу
2. Нажмите на ваш заказ
3. Найдите `http_pub` URL (например, `abc123.clorecloud.net`)

Используйте `https://YOUR_HTTP_PUB_URL` вместо `localhost` в примерах ниже.

## Установка

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

## Базовое использование

### Python API

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

# Простой вывод (inference)
args = type('Args', (), {
    "model_path": model_path,
    "model_base": None,
    "model_name": get_model_name_from_path(model_path),
    "query": "Опишите это изображение подробно",
    "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)
```

### Использование 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"
)

# Загрузить изображение
image = Image.open("photo.jpg")

# Создать беседу
conversation = [
    {
        "role": "user",
        "content": [
            {"type": "image"},
            {"type": "text", "text": "Что показано на этом изображении?"}
        ]
    }
]

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

## Интеграция с Ollama (рекомендуется)

Самый простой способ запустить LLaVA на CLORE.AI:

```bash
# Установите Ollama
curl -fsSL https://ollama.com/install.sh | sh

# Загрузить модель LLaVA
ollama pull llava:7b

# Запуск с изображением (CLI)
ollama run llava:7b "Опишите это изображение: /path/to/image.jpg"
```

### API LLaVA через Ollama

{% hint style="warning" %}
**Важно:** Зрение LLaVA работает **только** через `/api/generate` эндпоинт с параметром `images` параметр. `/api/chat` и совместимые с OpenAI эндпоинты **не** поддерживают изображения с LLaVA.
{% endhint %}

#### Рабочий метод: /api/generate

```bash
# Сначала кодировать изображение в base64
BASE64_IMAGE=$(base64 -i photo.jpg | tr -d '\n')

# Отправить запрос по зрению
curl https://your-http-pub.clorecloud.net/api/generate -d "{
  \"model\": \"llava:7b\",
  \"prompt\": \"Что вы видите на этом изображении? Опишите подробно.\",
  \"images\": [\"$BASE64_IMAGE\"],
  \"stream\": false
}"
```

Ответ:

```json
{
  "model": "llava:7b",
  "response": "Изображение показывает прекрасный закат над горами...",
  "done": true
}
```

#### НЕ РАБОТАЕТ: /api/chat (возвращает null для зрения)

```bash
# Это НЕ работает для запросов по зрению:
curl https://your-http-pub.clorecloud.net/api/chat -d '{
  "model": "llava:7b",
  "messages": [{"role": "user", "content": "describe", "images": ["..."]}]
}'
# Возвращает null для ответов, связанных с изображением
```

### Python с Ollama

```python
import requests
import base64

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

# Для зрения используйте /api/generate (НЕ /api/chat!)
response = requests.post(
    "https://your-http-pub.clorecloud.net/api/generate",
    json={
        "model": "llava:7b",
        "prompt": "Что вы видите на этом изображении?",
        "images": [encode_image("photo.jpg")],
        "stream": False
    }
)

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

### Полный рабочий пример

```python
import requests
import base64
import sys

def analyze_image(ollama_url, image_path, question):
    """Анализ изображения с помощью LLaVA через Ollama"""

    # Кодирование изображения
    with open(image_path, "rb") as f:
        image_base64 = base64.b64encode(f.read()).decode()

    # Используйте /api/generate (единственный рабочий эндпоинт для зрения)
    response = requests.post(
        f"{ollama_url}/api/generate",
        json={
            "model": "llava:7b",
            "prompt": question,
            "images": [image_base64],
            "stream": False
        }
    )

    return response.json()["response"]

# Пример использования
url = "https://your-http-pub.clorecloud.net"
result = analyze_image(url, "photo.jpg", "Опишите это изображение подробно")
print(result)
```

## Сценарии использования

### Описание изображения

```python
prompt = "Опишите это изображение подробно, включая цвета, объекты и атмосферу."
```

### OCR / Извлечение текста

```python
prompt = "Извлеките весь текст, видимый на этом изображении. Оформите его ясно."
```

### Анализ графиков

```python
prompt = "Проанализируйте этот график. Какие ключевые тенденции и выводы?"
```

### Код со скриншота

```python
prompt = "Извлеките код, показанный на этом скриншоте. Предоставьте только код."
```

### Обнаружение объектов

```python
prompt = "Перечислите все объекты, видимые на изображении, с их приблизительными местоположениями."
```

## Интерфейс 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)

    # Извлечь ответ ассистента
    return response.split("[/INST]")[-1].strip()

demo = gr.Interface(
    fn=analyze_image,
    inputs=[
        gr.Image(type="pil", label="Изображение"),
        gr.Textbox(label="Вопрос", value="Опишите это изображение подробно")
    ],
    outputs=gr.Textbox(label="Ответ"),
    title="Ассистент зрения LLaVA"
)

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

## 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="Опишите это изображение")
):
    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()}

# Запуск: uvicorn server:app --host 0.0.0.0 --port 8000
```

## Пакетная обработка

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

# Обработка папки с изображениями
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, "Кратко опишите это изображение")
        results.append({"file": filename, "description": description})
        print(f"{filename}: {description[:100]}...")

# Сохранить результаты
import json
with open("descriptions.json", "w") as f:
    json.dump(results, f, indent=2)
```

## Оптимизация памяти

### Квантование 4-бит

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

### Выгрузка на 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"
)
```

## Производительность

| Модель        | GPU      | Токенов/с |
| ------------- | -------- | --------- |
| 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      |

## Устранение неполадок

### Недостаточно памяти

```python

# Использовать 4-битную квантизацию

# Или использовать меньшую модель (7B вместо 13B)

# Или обрабатывать меньшие изображения
image = image.resize((336, 336))
```

### Медленная генерация

* Использовать flash attention
* Уменьшить max\_new\_tokens
* Использовать квантизированную модель

### Плохое качество

* Используйте модель побольше
* Лучшие подсказки с контекстом
* Изображения более высокого разрешения

## Оценка стоимости

Типичные ставки на маркетплейсе CLORE.AI (по состоянию на 2024):

| GPU       | Почасовая ставка | Дневная ставка | Сессия 4 часа |
| --------- | ---------------- | -------------- | ------------- |
| 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       |

*Цены варьируются в зависимости от провайдера и спроса. Проверьте* [*CLORE.AI Marketplace*](https://clore.ai/marketplace) *для текущих тарифов.*

**Экономьте деньги:**

* Используйте **Spot** рынок для гибких рабочих нагрузок (часто на 30–50% дешевле)
* Платите с помощью **CLORE** токенов
* Сравнивайте цены у разных провайдеров

## Дальнейшие шаги

* LLMы Ollama — Запуск LLaVA с Ollama
* RAG + LangChain — Зрение + RAG
* vLLM Inference - Продакшен-сервинг
