SigLIP2¶
1 引言¶
SigLIP2 是 Google 推出的一系列视觉-语言嵌入模型。每个检查点提供独立的文本和图像编码器,用于对比嵌入(而非文本生成)。vLLM 通过 llm.embed()(离线)或 /v1/embeddings(在线)将 SigLIP2 作为池化模型运行。
支持的使用场景包括图像-文本相似度、零样本 ImageNet 分类以及多模态检索。文本和图像必须在单独的请求中嵌入;请勿在一次调用中同时传入两者。
本指南介绍如何在 Atlas 300I DUO 上使用 vLLM Ascend 部署和评估 SigLIP2。
本文档基于 2026-09-03 的 vLLM-Ascend main 分支 编写并验证。当前模型(siglip2-base-patch16-224)在该分支上完全支持文本和图像嵌入。作为池化模型,SigLIP2 用于离线 llm.embed() 和在线 /v1/embeddings 服务;PD 分离和 MTP 等功能不适用。请使用该日期的 main 分支 快照或包含 SigLIP2 支持的后续官方版本。
2 支持的特性¶
请参阅 支持的模型 获取该模型的支持特性矩阵。
3 前提条件¶
3.1 模型权重¶
| 权重版本 | 硬件要求 | 下载链接 |
|---|---|---|
siglip2-base-patch16-224 (FP16) |
1 个 Atlas 300I DUO 节点 | ModelScope | HuggingFace |
路径说明: 请将模型权重下载到您选择的目录并记录该路径。例如:
/root/.cache/modelscope/hub/models/google/siglip2-base-patch16-224。在后续命令中,请将<YOUR_MODEL_PATH>替换为您在此记录的路径(本地目录或 Hugging Face / ModelScope 模型 ID,例如google/siglip2-base-patch16-224)。
3.2 ImageNet 标签(可选,用于精度评估)¶
对于 ImageNet 验证集零样本 Top-1 评估,请准备:
- ImageNet ILSVRC 2012 验证集图像(需登录并同意条款)
- PyTorch 索引格式(0–999)的
val_label.txt,例如来自 simple-imagenet-test - 来自 yrevar gist 的
imagenet1000_clsidx_to_labels.txt
4 安装¶
硬件支持: 本教程中的 SigLIP2 仅在 Atlas 300I DUO 上得到支持和验证。请使用相应的 Docker 镜像和以下安装步骤。
4.1 Docker 镜像安装¶
您可以使用官方一体化 Docker 镜像。有关可用的镜像标签和已发布版本,请参阅 使用 Docker。
- 步骤 1:下载最新的 Docker 镜像
- 步骤 2:启动 Docker 容器
# Set the vLLM Ascend image name.
export IMAGE=quay.io/ascend/vllm-ascend:v0.23.0-310p
export NAME=siglip2-dev
# Start the container with the variables defined above.
# Atlas 300I DUO uses a single NPU device (/dev/davinci0).
docker run --rm \
--name $NAME \
--net=host \
--shm-size=1g \
--privileged=true \
--device /dev/davinci0 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /root/.cache:/root/.cache \
-it $IMAGE bash
- 步骤 3:安装验证
启动容器后,运行以下命令验证安装:
预期结果:容器以 Up 状态列出。您还可以在容器内验证 vllm-ascend 版本:
预期结果:显示版本信息,与拉取的镜像版本一致。
4.2 源码安装¶
如果您不想使用上述 Docker 镜像,也可以从源码构建所有内容:
- 从源码安装
vllm-ascend,请参阅 安装。
如果您想部署多节点环境,需要在每个节点上配置环境。
5 在线服务部署¶
5.1 单节点在线部署¶
单节点部署在单个 Atlas 300I DUO 节点上运行文本和图像嵌入,适用于开发、测试和在线 /v1/embeddings 服务。
启动命令:
#!/bin/sh
# Replace <YOUR_MODEL_PATH> with the path recorded in Section 3.1.
export MODEL_PATH=<YOUR_MODEL_PATH>
vllm serve $MODEL_PATH \
--served-model-name $MODEL_PATH \
--runner pooling \
--chat-template template_basic.jinja \
--limit-mm-per-prompt '{"image": 1}' \
--compilation-config '{"cudagraph_capture_sizes": [64,32]}' \
--additional-config '{"ascend_compilation_config": {"enable_npugraph_ex": false}}' \
--dtype float16 \
--port 8000 \
--max-model-len 64
必需参数说明:
--compilation-config对于 Atlas 300I DUO,由于硬件流数量有限,cudagraph_capture_sizes 的大小受到限制。
关键参数说明:
- SigLIP2 在线服务不支持张量并行(TP)。请勿设置
--tensor-parallel-size;请按照上述方式在单个 Atlas 300I DUO NPU 上部署。 --served-model-name必须与/v1/embeddings请求中的"model"字段匹配;请使用与MODEL_PATH相同的值。- 必须设置
--runner pooling。SigLIP2 是嵌入模型,而非生成式 LLM。 --max-model-len 64与 SigLIP2 文本分词(padding=max_length,max_length=64)匹配。- 通过
/v1/embeddings的messages发送图像时,需要设置--chat-template template_basic.jinja。 --limit-mm-per-prompt '{"image": 1}'允许每个请求包含一张图像。- 对于仅图像的 HTTP 嵌入,请在
messages中使用空文本提示,或在离线模式下使用prompt=""并配合multi_modal_data。
常见问题提示:如果遇到问题,请参阅 公共 FAQ 进行故障排除。
5.2 多节点 PD 分离部署¶
SigLIP2 是池化嵌入模型,不支持多节点 PD(Prefill-Decode)分离部署。请改用 §5.1 单节点在线部署。
6 功能验证¶
服务器启动后,您可以通过以下命令进行验证。
6.1 文本嵌入¶
export MODEL_PATH=<YOUR_MODEL_PATH> # match --served-model-name from Section 5.1
curl -X POST http://127.0.0.1:8000/v1/embeddings \
-H "Content-Type: application/json" \
-d "{
\"model\": \"${MODEL_PATH}\",
\"input\": [\"This is a photo of a dog.\"]
}"
对于零样本分类提示,请使用模板 "This is a photo of {}."。SigLIP2 在训练时对文本使用了 padding=max_length 和 max_length=64;vLLM 在使用离线 tokenization_kwargs 时会应用此设置。
6.2 图像嵌入¶
将图像编码为 base64,并通过 messages 发送:
export MODEL_PATH=<YOUR_MODEL_PATH> # match --served-model-name from Section 5.1
IMG_B64=$(base64 -w 0 /path/to/image.jpg)
curl -X POST http://127.0.0.1:8000/v1/embeddings \
-H "Content-Type: application/json" \
-d "{
\"model\": \"${MODEL_PATH}\",
\"encoding_format\": \"float\",
\"messages\": [{
\"role\": \"user\",
\"content\": [{
\"type\": \"image_url\",
\"image_url\": {\"url\": \"data:image/jpeg;base64,${IMG_B64}\"}
}]
}]
}"
预期结果:
服务返回 HTTP 200 OK,JSON 响应中包含每个请求的 embedding 字段。
更多使用示例,请参考 vLLM pooling embed 示例。
6.3 离线嵌入¶
from vllm import LLM
MODEL_PATH = "<YOUR_MODEL_PATH>" # Replace with the path recorded in Section 3.1
llm = LLM(
model=MODEL_PATH,
runner="pooling",
limit_mm_per_prompt={"image": 1},
max_model_len=64,
)
# Text
text_out = llm.embed(
["This is a photo of a dog."],
tokenization_kwargs={"padding": "max_length", "max_length": 64},
)
print(len(text_out[0].outputs.embedding))
# Image (empty prompt; field name must be multi_modal_data)
from PIL import Image
img = Image.open("/path/to/image.jpg").convert("RGB")
img_out = llm.embed(
{"prompt": "", "multi_modal_data": {"image": img}},
)
print(len(img_out[0].outputs.embedding))
7 精度评估¶
ImageNet 验证集零样本 Top-1 是 SigLIP2 常用的精度基准。
7.1 数据集与标签¶
- 下载 ImageNet ILSVRC 2012 验证集图像(需要登录)。
- 下载
val_label.txt(示例)。每行格式:ILSVRC2012_val_00000001.JPEG 65(PyTorch 类别 ID 0–999)。 - 下载
imagenet1000_clsidx_to_labels.txt以获取 1000 个类别的文本模板。
7.2 离线评估¶
分别对 1000 个类别文本和验证集图像进行嵌入,然后计算余弦相似度(L2 归一化后的点积)。示例流程:
import ast
import numpy as np
from PIL import Image
from vllm import LLM
TEXT_TEMPLATE = "This is a photo of {}."
TOKEN_KWARGS = {"padding": "max_length", "max_length": 64}
def load_classnames(path):
with open(path, encoding="utf-8") as f:
d = ast.literal_eval(f.read())
return [d[i] for i in range(1000)]
def load_val_label(path):
gt = {}
with open(path, encoding="utf-8") as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 2:
gt[parts[0].split(".")[0]] = int(parts[1])
return gt
MODEL_PATH = "<YOUR_MODEL_PATH>" # Replace with the path recorded in Section 3.1
llm = LLM(
model=MODEL_PATH,
runner="pooling",
limit_mm_per_prompt={"image": 1},
max_model_len=64,
)
classnames = load_classnames("imagenet1000_clsidx_to_labels.txt")
prompts = [TEXT_TEMPLATE.format(c) for c in classnames]
text_feats = np.asarray(
[o.outputs.embedding for o in llm.embed(prompts, tokenization_kwargs=TOKEN_KWARGS)],
dtype=np.float32,
)
text_feats /= np.linalg.norm(text_feats, axis=1, keepdims=True)
gt = load_val_label("val_label.txt")
correct = 0
total = 0
for stem, label in gt.items():
path = f"ImageNet/val/{stem}.JPEG" # or .jpeg
img = Image.open(path).convert("RGB")
feat = np.asarray(
llm.embed({"prompt": "", "multi_modal_data": {"image": img}})[0]
.outputs.embedding,
dtype=np.float32,
)
feat /= np.linalg.norm(feat)
if np.argmax(feat @ text_feats.T) == label:
correct += 1
total += 1
print(f"Top-1 accuracy: {100.0 * correct / total:.2f}%")
ImageNet 验证集上的参考 Top-1(近似值):
| Model | Top-1 |
|---|---|
siglip2-base-patch16-224 |
~69% |
8 性能评估¶
使用以下脚本通过 HTTP 对 /v1/embeddings 进行基准测试。
8.1 HTTP 服务基准测试¶
按照 §5 在线服务部署 启动服务器,然后运行:
"""Benchmark SigLIP2 /v1/embeddings serving over HTTP."""
import base64
import io
import json
import statistics
import time
import urllib.error
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
import numpy as np
from PIL import Image
BASE_URL = "http://127.0.0.1:8000"
MODEL = "<YOUR_MODEL_PATH>" # match --served-model-name from Section 5.1
NUM_REQUESTS = 200
CONCURRENCY = 8
WARMUP = 10
MODE = "both" # "text", "image", or "both"
TEXT = "This is a photo of a dog."
IMAGE_SIZE = (224, 224)
TIMEOUT_S = 120.0
def post_json(url: str, payload: dict) -> tuple[bool, float, str]:
body = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
url,
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
t0 = time.perf_counter()
try:
with urllib.request.urlopen(req, timeout=TIMEOUT_S) as resp:
resp.read()
ok = resp.status == 200
except urllib.error.HTTPError as e:
detail = e.read().decode("utf-8", errors="replace")[:200]
return False, time.perf_counter() - t0, f"HTTP {e.code}: {detail}"
except Exception as e:
return False, time.perf_counter() - t0, str(e)
return ok, time.perf_counter() - t0, ""
def random_jpeg_b64(width: int, height: int, seed: int) -> str:
arr = np.random.default_rng(seed).integers(
0, 256, size=(height, width, 3), dtype=np.uint8
)
buf = io.BytesIO()
Image.fromarray(arr, mode="RGB").save(buf, format="JPEG")
return base64.b64encode(buf.getvalue()).decode("ascii")
def build_payload(mode: str, seed: int) -> dict:
if mode == "text":
return {
"model": MODEL,
"input": [TEXT],
"encoding_format": "float",
}
w, h = IMAGE_SIZE
b64 = random_jpeg_b64(w, h, seed)
return {
"model": MODEL,
"encoding_format": "float",
"messages": [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{b64}",
},
}
],
}
],
}
def percentile(values: list[float], percent: float) -> float:
sorted_values = sorted(values)
rank = (len(sorted_values) - 1) * (percent / 100.0)
lower = int(rank)
upper = min(lower + 1, len(sorted_values) - 1)
if lower == upper:
return sorted_values[lower]
return sorted_values[lower] + (sorted_values[upper] - sorted_values[lower]) * (
rank - lower
)
def run_benchmark(mode: str) -> None:
url = f"{BASE_URL.rstrip('/')}/v1/embeddings"
total = NUM_REQUESTS + WARMUP
payloads = [build_payload(mode, i) for i in range(total)]
def send_one(index: int) -> tuple[bool, float]:
ok, latency_s, _ = post_json(url, payloads[index])
return ok, latency_s
if WARMUP:
with ThreadPoolExecutor(max_workers=min(CONCURRENCY, WARMUP)) as pool:
list(pool.map(send_one, range(WARMUP)))
t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
futures = [pool.submit(send_one, WARMUP + i) for i in range(NUM_REQUESTS)]
results = [fut.result() for fut in as_completed(futures)]
wall_s = time.perf_counter() - t0
ok_lat_ms = [lat * 1000 for ok, lat in results if ok]
failed = sum(1 for ok, _ in results if not ok)
print(f"\n=== {mode} ===")
print(f" successful: {len(ok_lat_ms)}/{NUM_REQUESTS}")
print(f" failed: {failed}")
print(f" duration: {wall_s:.2f}s")
print(f" throughput: {len(ok_lat_ms) / wall_s:.2f} req/s")
if ok_lat_ms:
print(f" mean E2EL: {statistics.mean(ok_lat_ms):.2f} ms")
print(f" median E2EL:{statistics.median(ok_lat_ms):.2f} ms")
print(f" p99 E2EL: {percentile(ok_lat_ms, 99):.2f} ms")
if __name__ == "__main__":
if MODE in ("text", "both"):
run_benchmark("text")
if MODE in ("image", "both"):
run_benchmark("image")
8.2 指标¶
该脚本会报告:
- 请求吞吐量(req/s)
- 平均 / 中位数 / p99 E2EL(端到端延迟,单位毫秒)
调整脚本顶部的 NUM_REQUESTS、CONCURRENCY 和 WARMUP。将 MODE 设置为 "text"、"image" 或 "both"。
大约几分钟后,即可获得性能评估结果。
9 性能调优¶
注意:以下配置在特定测试环境中经过验证,仅供参考。最佳配置取决于文本与图像工作负载、请求并发度、批处理大小和图像分辨率等因素。建议根据实际情况参考第 9.2 节进行调优。
9.1 推荐配置¶
以下配置已在 Atlas 300I DUO 上验证,并按使用场景分类。从 §5.1 单节点在线部署 命令开始,然后调整下方的 serve 参数。
| Scenario | Workload | Deployment | NPUs | Max Num Seqs | Max Num Batched Tokens | Max Model Len | Client Concurrency (ref.) |
|---|---|---|---|---|---|---|---|
| Text high throughput | Text /v1/embeddings |
Single node | 1 (300I DUO) | 32 | 512 | 64 | 16–32 |
| Image high throughput | Image /v1/embeddings (224×224) |
Single node | 1 (300I DUO) | 16 | 256 | 64 | 8–16 |
| Low latency | Text or image | Single node | 1 (300I DUO) | 8 | 128 | 64 | 4–8 |
注意:
--max-num-seqs和--max-num-batched-tokens在vllm serve启动时设置。§8.1 HTTP 服务基准测试 中的客户端并发度控制并行发送的 HTTP 请求数量;建议将其保持在接近--max-num-seqs的值以获得稳定的批处理效果。SigLIP2 不支持 TP 或 PD 分离。
文本高吞吐量场景的示例 serve 参数:
vllm serve $MODEL_PATH \
... \
--max-num-seqs 32 \
--max-num-batched-tokens 512 \
--max-model-len 64
9.2 调优指南¶
9.2.1 模型特定优化¶
默认启用的优化¶
以下优化已在推荐的 §5.1 配置中启用:
| Optimization Technique | Technical Principle | Performance Benefit |
|---|---|---|
| ACL graph capture | 使用 --compilation-config '{"cudagraph_capture_sizes": [64,32]}' 在 Atlas 300I DUO 上捕获固定的小批量形状 |
降低短文本和图像嵌入路径的每请求调度开销 |
| Pooling runner | 使用 --runner pooling 进行仅嵌入的前向传播 |
SigLIP2 所必需;避免生成式解码路径 |
| FP16 inference | 在 Atlas 300I DUO 上使用 --dtype float16 |
匹配 300I DUO 支持的精度和模型权重 |
需要显式启用的优化¶
| Optimization Technique | Applicable Scenarios | Enablement Method | Technical Principle | Precautions |
|---|---|---|---|---|
| Server batch tuning | 在线文本/图像服务 | 在 serve 启动时设置 --max-num-seqs 和 --max-num-batched-tokens(参见第 9.1 节) |
控制服务器上批处理的嵌入请求数量 | 如果发生 OOM,请减小这两个参数;图像嵌入通常需要比文本更小的批次 |
| Client concurrency tuning | HTTP 基准测试或生产客户端 | 增加并行的 /v1/embeddings 请求数(参见第 8.1 节) |
提高发送到服务器批处理器的负载 | 当客户端并发度超过 --max-num-seqs 后,吞吐量提升趋于平缓;请关注 p99 延迟 |
9.2.2 通用调优参考¶
通用调优方法请参考 公共性能调优文档。
详细功能描述请参考 功能矩阵。
10 常见问题¶
有关常见的环境、安装和通用参数问题,请参考 公共常见问题;本章仅涵盖模型特定问题。
问:Top-1 准确率接近 0%,但嵌入看起来是有效的。
答:请检查真实标签是否使用 PyTorch 索引(val_label.txt),而不是使用包含 yrevar 类别名称的 devkit ILSVRC2012_validation_ground_truth.txt。
问:我可以在一个请求中同时嵌入文本和图像吗?
答:不可以。SigLIP2 每个请求只接受纯文本或纯图像输入。