# BentoML

**BentoML** является современным фреймворком с открытым исходным кодом для **создания, доставки и масштабирования AI-приложений**. Он устраняет разрыв между экспериментами в области машинного обучения и развёртыванием в продакшн, позволяя упаковать любую модель из любого фреймворка в готовый к производству API-сервис за считанные минуты. Запускайте BentoML в GPU-облаке Clore.ai для экономичного хостинга AI-приложений.

***

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

BentoML упрощает превращение обученной модели в масштабируемый API-сервис:

* **Независимость от фреймворка:** PyTorch, TensorFlow, JAX, scikit-learn, HuggingFace, XGBoost, LightGBM и другие
* **Bento:** Самодостаточный воспроизводимый артефакт (модель + код + зависимости)
* **Runner:** Масштабируемая единица инференса модели с автоматическим батчингом
* **Service:** Определение сервиса HTTP/gRPC, похожее на FastAPI
* **BentoCloud:** Необязательная управляемая платформа развертывания
* **Ориентированность на Docker:** Каждый Bento можно контейнеризовать одной командой

**Ключевые особенности:**

* Адаптивный микробатчинг для оптимизации пропускной способности
* Встроенная валидация входных/выходных данных с Pydantic
* Автогенерация спецификации OpenAPI
* Встроенные метрики Prometheus
* Поддержка потоковых ответов (LLM)

***

## Требования

| Требование | Минимум      | Рекомендуется   |
| ---------- | ------------ | --------------- |
| VRAM GPU   | 8 ГБ         | 16–24 ГБ        |
| GPU        | Любая NVIDIA | RTX 4090 / A100 |
| ОЗУ        | 8 ГБ         | 16 ГБ           |
| Хранилище  | 20 ГБ        | 40 ГБ           |
| Python     | 3.9+         | 3.11+           |

***

## Шаг 1 — Арендуйте GPU на Clore.ai

1. Войдите в [clore.ai](https://clore.ai).
2. Нажмите **Маркетплейс** и выберите GPU-инстанс с ≥ 16 ГБ VRAM.
3. Установите Docker-образ: мы будем использовать пользовательскую сборку (см. Шаг 2).
4. Установите открытые порты: `22` (SSH) и `3000` (сервис BentoML).
5. Нажмите **Арендовать**.

***

## Шаг 2 — Dockerfile

У BentoML нет официального GPU Docker-образа, поэтому мы собираем его сами:

```dockerfile
FROM pytorch/pytorch:2.1.2-cuda12.1-cudnn8-runtime

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    git wget curl \
    openssh-server \
    libgl1 libglib2.0-0 \
    && rm -rf /var/lib/apt/lists/*

# Настроить SSH
RUN mkdir /var/run/sshd && \
    echo 'root:clore123' | chpasswd && \
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# Установите BentoML и распространённые ML-библиотеки
RUN pip install --upgrade pip && \
    pip install \
        bentoml \
        transformers \
        accelerate \
        diffusers \
        Pillow \
        numpy \
        scipy \
        tritonclient[all]

WORKDIR /workspace

EXPOSE 22 3000

CMD service ssh start && tail -f /dev/null
```

### Сборка и отправка

Соберите образ и отправьте его в ваш аккаунт Docker Hub (замените `YOUR_DOCKERHUB_USERNAME` на ваш фактический логин):

```bash
docker build -t YOUR_DOCKERHUB_USERNAME/bentoml-gpu:latest .
docker push YOUR_DOCKERHUB_USERNAME/bentoml-gpu:latest
```

{% hint style="info" %}
BentoML не предоставляет официальный GPU Docker-образ на Docker Hub. Образы `bentoml/bento-server` на Docker Hub предназначены для раздачи предупакованных Bento и не включают поддержку CUDA. Соберите образ из приведённого выше Dockerfile для развертываний с поддержкой GPU на Clore.ai.
{% endhint %}

***

## Шаг 3 — Подключение по SSH

```bash
ssh root@<clore-host> -p <assigned-ssh-port>
```

Проверьте BentoML:

```bash
bentoml --version
# Ожидаемо: bentoml, version 1.x.x
```

***

## Шаг 4 — Ваш первый сервис BentoML

### Простой текстовый классификатор

Создайте файл сервиса:

```bash
mkdir -p /workspace/my-service
cat > /workspace/my-service/service.py << 'EOF'
import bentoml
from bentoml.io import JSON, Text
import numpy as np

# Определите Runner (единицу модели)
class TextClassifierRunnable(bentoml.Runnable):
    SUPPORTED_RESOURCES = ("gpu", "cpu")
    SUPPORTS_CPU_MULTI_THREADING = True
    
    def __init__(self):
        import torch
        from transformers import pipeline
        
        self.classifier = pipeline(
            "text-classification",
            model="distilbert-base-uncased-finetuned-sst-2-english",
            device=0 if torch.cuda.is_available() else -1,
        )
    
    @bentoml.Runnable.method(batchable=True, batch_dim=0)
    def classify(self, texts: list[str]) -> list[dict]:
        results = self.classifier(texts)
        return results

# Создать Runner
classifier_runner = bentoml.Runner(
    TextClassifierRunnable,
    name="text_classifier",
    max_batch_size=32,
    max_latency_ms=100,
)

# Определить Service
svc = bentoml.Service(
    name="text_classifier_service",
    runners=[classifier_runner],
)

@svc.api(input=Text(), output=JSON())
async def classify(text: str) -> dict:
    """Классифицировать тональность входного текста."""
    results = await classifier_runner.classify.async_run([text])
    return results[0]
EOF
```

### Запуск сервиса

```bash
cd /workspace/my-service

bentoml serve service:svc \
    --host 0.0.0.0 \
    --port 3000 \
    --reload
```

{% hint style="info" %}
Параметр `--reload` флаг включает горячую перезагрузку во время разработки. Уберите его в продакшне для стабильности.
{% endhint %}

***

## Шаг 5 — Доступ к сервису

Откройте автоматически сгенерированный Swagger UI:

```
http://<clore-host>:<public-port-3000>
```

Или протестируйте с помощью `curl`:

```bash
curl -X POST http://<clore-host>:<public-port-3000>/classify \
    -H "Content-Type: text/plain" \
    -d "This GPU cloud service is amazing!"
```

Ожидаемый ответ:

```json
{"label": "POSITIVE", "score": 0.9986}
```

***

## Шаг 6 — Сервис классификации изображений

### Сервис для моделей компьютерного зрения

```python
# /workspace/vision-service/service.py
import bentoml
from bentoml.io import Image, JSON
from PIL import Image as PILImage
import numpy as np

class ImageClassifierRunnable(bentoml.Runnable):
    SUPPORTED_RESOURCES = ("gpu",)
    SUPPORTS_CPU_MULTI_THREADING = False
    
    def __init__(self):
        import torch
        import torchvision.transforms as transforms
        from torchvision.models import resnet50, ResNet50_Weights
        
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        weights = ResNet50_Weights.DEFAULT
        self.model = resnet50(weights=weights).to(self.device)
        self.model.eval()
        self.preprocess = weights.transforms()
        self.categories = weights.meta["categories"]
    
    @bentoml.Runnable.method(batchable=True, batch_dim=0)
    def predict(self, images: list) -> list[dict]:
        import torch
        
        batch = torch.stack([self.preprocess(img) for img in images]).to(self.device)
        
        with torch.no_grad():
            predictions = self.model(batch).softmax(dim=1)
        
        results = []
        for pred in predictions:
            top5 = pred.topk(5)
            results.append({
                "predictions": [
                    {"label": self.categories[idx], "score": round(score.item(), 4)}
                    for score, idx in zip(top5.values, top5.indices)
                ]
            })
        return results


image_runner = bentoml.Runner(
    ImageClassifierRunnable,
    name="image_classifier",
    max_batch_size=16,
)

svc = bentoml.Service(
    name="image_classifier_service",
    runners=[image_runner],
)

@svc.api(input=Image(), output=JSON())
async def classify(image: PILImage.Image) -> dict:
    """Классифицировать изображение с помощью ResNet50."""
    results = await image_runner.predict.async_run([image])
    return results[0]
```

```bash
bentoml serve service:svc --host 0.0.0.0 --port 3000
```

Проверка с изображением:

```bash
curl -X POST http://<clore-host>:<public-port-3000>/classify \
    -H "Content-Type: image/jpeg" \
    --data-binary @/path/to/image.jpg
```

***

## Шаг 7 — Потоковый сервис LLM

Для языковых моделей с потоковыми ответами:

```python
# /workspace/llm-service/service.py
import bentoml
from bentoml.io import JSON, Text
from typing import AsyncGenerator

class LLMRunnable(bentoml.Runnable):
    SUPPORTED_RESOURCES = ("gpu",)
    SUPPORTS_CPU_MULTI_THREADING = False
    
    def __init__(self):
        from transformers import AutoModelForCausalLM, AutoTokenizer
        import torch
        
        model_name = "microsoft/phi-2"
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForCausalLM.from_pretrained(
            model_name,
            torch_dtype=torch.float16,
            device_map="auto"
        )
    
    @bentoml.Runnable.method(batchable=False)
    def generate(self, prompt: str, max_tokens: int = 200) -> str:
        import torch
        
        inputs = self.tokenizer(prompt, return_tensors="pt").to("cuda")
        
        with torch.no_grad():
            outputs = self.model.generate(
                **inputs,
                max_new_tokens=max_tokens,
                do_sample=True,
                temperature=0.7,
                pad_token_id=self.tokenizer.eos_token_id,
            )
        
        return self.tokenizer.decode(outputs[0], skip_special_tokens=True)


llm_runner = bentoml.Runner(LLMRunnable, name="llm")

svc = bentoml.Service("llm_service", runners=[llm_runner])

@svc.api(input=JSON(), output=Text())
async def generate(body: dict) -> str:
    prompt = body.get("prompt", "")
    max_tokens = body.get("max_tokens", 200)
    return await llm_runner.generate.async_run(prompt, max_tokens)
```

***

## Шаг 8 — Сохранение и сборка Bento

Файл **Bento** это упакованный воспроизводимый артефакт:

```python
# /workspace/build_bento.py
import bentoml

# Сохранить модель в хранилище моделей BentoML
import torch
from torchvision.models import resnet50, ResNet50_Weights

model = resnet50(weights=ResNet50_Weights.DEFAULT)
model.eval()

saved_model = bentoml.pytorch.save_model(
    name="resnet50",
    model=model,
    labels={"framework": "pytorch", "task": "image-classification"},
    metadata={"accuracy": 0.80, "dataset": "ImageNet"}
)
print(f"Model saved: {saved_model.tag}")
```

```bash
python /workspace/build_bento.py

# Список сохранённых моделей
bentoml models list

# Собрать Bento (требуется bentofile.yaml)
bentoml build
```

### bentofile.yaml

```yaml
service: "service:svc"
labels:
  owner: "ml-team"
  stage: "production"
include:
  - "*.py"
python:
  packages:
    - torch
    - torchvision
    - transformers
    - Pillow
    - numpy
docker:
  python_version: "3.11"
  cuda_version: "12.1"
  system_packages:
    - libgl1
```

```bash
bentoml build

# Список собранных bento
bentoml list

# Контейнеризация
bentoml containerize image_classifier_service:latest \
    --image-tag YOUR_DOCKERHUB_USERNAME/my-bento:latest
```

***

## Мониторинг и метрики

BentoML предоставляет метрики Prometheus по адресу `/metrics`:

```bash
curl http://<clore-host>:<public-port-3000>/metrics
```

Ключевые метрики:

```
# Частота запросов
bentoml_service_request_total{endpoint="classify", http_status_code="200"}
# Задержка
bentoml_service_request_duration_seconds{endpoint="classify"}
# Пропускная способность Runner  
bentoml_runner_request_total{runner_name="image_classifier"}
```

***

## Конфигурация адаптивного батчинга

```python
# Тонкая настройка поведения батчинга
image_runner = bentoml.Runner(
    ImageClassifierRunnable,
    name="image_classifier",
    max_batch_size=64,          # Максимум запросов в батче
    max_latency_ms=50,          # Максимальное ожидание перед отправкой
)
```

***

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

### Сервис не запускается

```
ERROR - Не удалось инициализировать runner
```

**Решения:**

* Проверьте доступность CUDA: `python -c "import torch; print(torch.cuda.is_available())"`
* Проверьте объём видеопамяти GPU (VRAM): `nvidia-smi`
* Проверьте, что загрузка модели завершена (ищите прогресс загрузки в логах)

### Порт 3000 недоступен

```bash
# Убедитесь, что сервис привязывается к 0.0.0.0 (не к localhost)
bentoml serve service:svc --host 0.0.0.0 --port 3000
```

### Высокая задержка при первом запросе

Это нормально — первый запрос триггерит загрузку модели (прогрев). Все последующие запросы будут быстрыми. Добавьте вызов прогрева после старта:

```bash
# Прогрев после запуска
sleep 10 && curl -s -o /dev/null http://localhost:3000/healthz
```

### Ошибки импорта

```
ModuleNotFoundError: No module named 'transformers'
```

**Решение:**

```bash
pip install transformers accelerate
```

***

## Рекомендации Clore.ai по GPU

BentoML — это фреймворк для сервинга — требования к GPU зависят полностью от модели, которую вы разворачиваете. Вот чего ожидать для распространённых нагрузок:

| GPU       | VRAM  | Цена Clore.ai | Пропускная способность LLM (7B Q4) | Диффузионные модели (SDXL) | Компьютерное зрение (ResNet50) |
| --------- | ----- | ------------- | ---------------------------------- | -------------------------- | ------------------------------ |
| RTX 3090  | 24 ГБ | \~$0.12/ч     | \~80 ток/с                         | \~4 изображ./мин           | \~400 запросов/с               |
| RTX 4090  | 24 ГБ | \~$0.70/ч     | \~140 ток/с                        | \~8 изображ./мин           | \~700 запросов/с               |
| A100 40GB | 40 ГБ | \~$1.20/ч     | \~110 ток/с                        | \~6 изображ./мин           | \~1200 запросов/с              |
| A100 80GB | 80 ГБ | \~$2.00/ч     | \~130 ток/с                        | \~7 изображ./мин           | \~1400 запросов/с              |

**Рекомендации по выбору оборудования:**

* **Сервинг LLM API (7B–13B):** RTX 3090 (\~$0.12/час) — оптимальное соотношение цена/производительность
* **API для генерации изображений:** RTX 3090 или RTX 4090 в зависимости от требований по пропускной способности
* **Крупные модели (34B–70B Q4):** A100 40GB (\~$1.20/час) — комфортно помещается
* **Производственный сервинг нескольких моделей:** A100 80GB для запасов памяти

{% hint style="info" %}
Адаптивный **микробатчинг** особенно эффективен на A100 — аппаратный планировщик эффективно обрабатывает батчи, обеспечивая большую пропускную способность на доллар по сравнению с наивным обслуживанием одиночных запросов. Для высоконагруженных API A100 40GB часто даёт лучшую окупаемость, чем две RTX 4090.
{% endhint %}

***

## Полезные ресурсы

* [Официальная документация BentoML](https://docs.bentoml.com)
* [BentoML на GitHub](https://github.com/bentoml/BentoML)
* [Примеры BentoML](https://github.com/bentoml/BentoML/tree/main/examples)
* [Сообщество BentoML в Discord](https://l.bentoml.com/join-slack-space)
* [Галерея BentoML](https://www.bentoml.com/gallery)
* [Быстрый старт: обслуживание LLM](https://docs.bentoml.com/en/latest/get-started/quickstart.html)


---

# 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-ru/mlops-i-razvyortyvanie/bentoml.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.
