# BentoML

**BentoML** 是一个现代的开源框架，用于 **构建、交付和扩展人工智能应用**。它弥合了机器学习实验与生产部署之间的差距，让你能够在几分钟内将任何框架中的任意模型打包为可用于生产的 API 服务。在 Clore.ai 的 GPU 云上运行 BentoML，可实现具有成本效益的 AI 应用托管。

***

## 什么是 BentoML？

BentoML 让将训练好的模型变为可扩展的 API 服务变得简单：

* **与框架无关：** 支持 PyTorch、TensorFlow、JAX、scikit-learn、HuggingFace、XGBoost、LightGBM 等
* **Bento：** 一个自包含、可复现的工件（模型 + 代码 + 依赖）
* **Runner（运行单元）：** 具有自动批处理功能的可扩展模型推理单元
* **Service（服务）：** 类似 FastAPI 的 HTTP/gRPC 服务定义
* **BentoCloud：** 可选的托管部署平台
* **Docker 优先：** 每个 Bento 都可以用一条命令容器化

**主要功能：**

* 用于吞吐量优化的自适应微批处理
* 内置基于 Pydantic 的输入/输出验证
* 自动生成 OpenAPI 规范
* 内置 Prometheus 指标
* 支持流式响应（LLM）

***

## 先决条件

| 要求      | 最低要求      | 推荐配置            |
| ------- | --------- | --------------- |
| GPU 显存  | 8 GB      | 16–24 GB        |
| GPU     | 任何 NVIDIA | RTX 4090 / A100 |
| 内存（RAM） | 8 GB      | 16 GB           |
| 存储      | 20 GB     | 40 GB           |
| Python  | 3.9+      | 3.11+           |

***

## 步骤 1 — 在 Clore.ai 上租用 GPU

1. 登录到 [clore.ai](https://clore.ai).
2. 点击 **市场** 并选择具有 ≥ 16 GB 显存的 GPU 实例。
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 和常见的机器学习库
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 未在 Docker Hub 提供官方的 GPU Docker 镜像。那些 `bentoml/bento-server` 在 Docker Hub 上的镜像用于服务预打包的 Bentos，不包含 CUDA 支持。为在 Clore.ai 上启用 GPU 的部署，请从上面的 Dockerfile 构建镜像。
{% 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,
)

# 定义服务
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

# 列出已构建的 bentos
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
```

**将批量大小减小到 1**

* 检查 CUDA 可用性： `python -c "import torch; print(torch.cuda.is_available())"`
* 验证 GPU 显存： `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 GB    | \~$0.12/小时  | \~80 tok/s    | \~4 img/min | \~400 req/s  |
| RTX 4090          | 24 GB    | \~$0.70/小时  | \~140 tok/s   | \~8 img/min | \~700 req/s  |
| A100 40GB         | 40 GB    | \~$1.20/小时  | \~110 tok/s   | \~6 img/min | \~1200 req/s |
| 💡 本指南中的所有示例均可部署在 | 80 GB    | \~$2.00/小时  | \~130 tok/s   | \~7 img/min | \~1400 req/s |

**使用场景指引：**

* **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" %}
BentoML 的 **自适应微批处理** 在 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)
