Ollama vs vLLM:并发性能深度评测
[toc]
引言
随着大语言模型(LLM)在各个领域的广泛应用,高效的推理引擎成为了部署这些模型的关键。Ollama 和 vLLM 作为当前最流行的两种推理引擎,各自有着独特的优势和适用场景。
本文将从并发性能的角度,对 Ollama 和 vLLM 进行深度对比,帮助读者更好地选择适合自己的推理框架。
一、背景介绍
1.1 Ollama
Ollama 是一个简单易用的 LLM 部署工具,以其简洁的安装和用户友好的界面而闻名。它支持多种模型架构,并提供了丰富的命令行工具和图形化界面,适合快速原型设计和小规模部署。
1.2 vLLM
vLLM(Very Large Language Model)是一个高性能的推理引擎,专注于大规模语言模型的高效推理。它通过动态批处理、显存优化和多 GPU 支持,显著提升了推理速度和资源利用率。
二、前期准备
测试环境准备
GPU型号:单块 NVIDIA GeForce RTX 4090 显卡
Ollama部署模型:同参数的 DeepSeek-R1 量化模型
vLLM部署模型: 同参数的 DeepSeek-R1-Distill-Qwen 蒸馏模型
Locust 压测工具
Locust 是一个强大的、易于使用的开源压测工具,它允许你编写 Python 脚本模拟大量并发用户进行压力测试。最棒的是,Locust 提供了一个直观的 Web UI,让你可以通过浏览器实时查看性能数据和调整测试参数。
测试报告图表说明
测试的 Charts 图表包含:
1、Total Request per Second :每秒的请求总数,横轴为时间轴,纵轴为每秒请求的数量(请求处理通过的)。
- 绿色线:每秒钟请求成功的个数
- 红色线:每秒钟请求失败的个数
2、Response Time :响应时间,横轴为时间轴,纵轴为以毫秒为单位的响应时间。需要注意的是,图表上面两根线并不是表示平均值,而是响应时间的“中位数”和“95%百分位数值”。
- 绿色线:表示中位数
- 黄色线:表示95%百分位数值
3、Number of Users :用户数,横轴为时间轴,纵轴为时间所对应的“用户数”。
———————————————— Locust性能测试之快速入门 - xyztank - 博客园
编写压测脚本
示例 python 代码:
import csv
import traceback
from locust import HttpUser, task, between
import logging
def init_loging_config():
level = logging.INFO
logging.basicConfig(
level=level,
format="%(asctime)s %(name)s %(levelname)s %(message)s ",
datefmt='%Y-%m-%d %H:%M:%S'
)
_logger = logging.getLogger(" =>")
_logger.setLevel(level)
return _logger
logger = init_loging_config()
req_type = "1"
api_key = "app-aRohuiTGaRFderfMS27EEfS2"
headers = {
"Authorization": "Bearer " + api_key,
"Content-Type": "application/json"
}
input_file = r'api-get\data\content_1.csv'
infile = open(input_file, mode='r', encoding='utf-8')
reader = csv.reader(infile)
next(reader) # 读取并跳过第一行(表头)
class QuickstartLLM(HttpUser):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def read_next_row(self):
try:
return next(reader)[1]
except StopIteration:
infile.close()
return None
def get_data(self, query: str, type: str):
return {
"inputs": {
"query": query,
"type": type
},
"response_mode": "blocking",
"user": "abc-12345"
}
def send_post_request(self):
return self.get_data(self.read_next_row(), req_type)
wait_time = between(1, 3) # 每个任务/用户之间的间隔时间(秒)
@task
def generate_text(self):
data = self.send_post_request()
try:
response = self.client.post("/v1/workflows/run", headers=headers, json=data, timeout=120)
logger.info(f"状态码: {response.status_code},输入文本: {data},响应结果: {response.text}")
except Exception as e:
logger.error(traceback.format_exc())
通过终端输入命令启动 locust:
locust -f api-get/test/locust_test.py --host http://172.10.2.122
三、性能测试
推理速度是衡量推理引擎性能的关键指标之一。以下是对 Ollama 和 vLLM 在不同模型下的推理速度测试结果:
vLLM
单线程测试
设置用户数:1,用户孵化率:1
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 7800 | 8910.67 | 5975 | 19013 | 484.35 | 0.1 | 0 |
Aggregated | 20 | 0 | 7800 | 8910.67 | 5975 | 19013 | 484.35 | 0.1 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
5个线程测试
设置用户数:5,用户孵化率:1
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 8000 | 8930.52 | 6408 | 15561 | 483.24 | 0.5 | 0 |
Aggregated | 20 | 0 | 8000 | 8930.52 | 6408 | 15561 | 483.24 | 0.5 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
10个线程测试
设置用户数:10,用户孵化率:1
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 9400 | 9453.53 | 6572 | 11864 | 489.7 | 0.9 | 0 |
Aggregated | 20 | 0 | 9400 | 9453.53 | 6572 | 11864 | 489.7 | 0.9 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
20个线程测试
设置用户数:20,用户孵化率:2
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 9900 | 10319.91 | 7264 | 15798 | 496.63 | 1 | 0 |
Aggregated | 20 | 0 | 9900 | 10319.91 | 7264 | 15798 | 496.63 | 1 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
Ollama
单线程测试
设置用户数:1,用户孵化率:1
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 6300 | 6782.01 | 3989 | 15562 | 489.15 | 0.1 | 0 |
Aggregated | 20 | 0 | 6300 | 6782.01 | 3989 | 15562 | 489.15 | 0.1 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
5个线程测试
设置用户数:5,用户孵化率:1
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 8600 | 8566.16 | 4261 | 12971 | 482.81 | 0.3 | 0 |
Aggregated | 20 | 0 | 8600 | 8566.16 | 4261 | 12971 | 482.81 | 0.3 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
10个线程测试
设置用户数:10,用户孵化率:1
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 11000 | 10935.86 | 5779 | 21193 | 484.76 | 0.7 | 0 |
Aggregated | 20 | 0 | 11000 | 10935.86 | 5779 | 21193 | 484.76 | 0.7 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
20个线程测试
设置用户数:20,用户孵化率:2
Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|
POST | /v1/workflows/run | 20 | 0 | 16000 | 16845.03 | 6214 | 26838 | 487.57 | 0.6 | 0 |
Aggregated | 20 | 0 | 16000 | 16845.03 | 6214 | 26838 | 487.57 | 0.6 | 0 |
处理请求GPU瞬时负载:
测试结果图表输出 (图表依次为:每秒请求个数RPS,请求响应时间,用户/线程数):
汇总测试结果
推理工具 | 请求并发数 | Type (请求类型) | Name(请求路径) | Requests(请求成功次数) | Fails(请求失败次数) | Median(响应时间中位数-ms) | Average (平均值-ms) | Min (最小响应时间-ms) | Max (最大响应时间-ms) | Average size (请求平均数据大小-bytes) | Current RPS(每秒处理事务数) | Current Failures/s(每秒请求失败数) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
vLLM | 1 | POST | /v1/workflows/run | 20 | 0 | 7800 | 8910.67 | 5975 | 19013 | 484.35 | 0.1 | 0 |
Aggregated | 20 | 0 | 7800 | 8910.67 | 5975 | 19013 | 484.35 | 0.1 | 0 | |||
5 | POST | /v1/workflows/run | 20 | 0 | 8000 | 8930.52 | 6408 | 15561 | 483.24 | 0.5 | 0 | |
Aggregated | 20 | 0 | 8000 | 8930.52 | 6408 | 15561 | 483.24 | 0.5 | 0 | |||
10 | POST | /v1/workflows/run | 20 | 0 | 9400 | 9453.53 | 6572 | 11864 | 489.7 | 0.9 | 0 | |
Aggregated | 20 | 0 | 9400 | 9453.53 | 6572 | 11864 | 489.7 | 0.9 | 0 | |||
20 | POST | /v1/workflows/run | 20 | 0 | 9900 | 10319.91 | 7264 | 15798 | 496.63 | 1 | 0 | |
Aggregated | 20 | 0 | 9900 | 10319.91 | 7264 | 15798 | 496.63 | 1 | 0 | |||
Ollama | 1 | POST | /v1/workflows/run | 20 | 0 | 6300 | 6782.01 | 3989 | 15562 | 489.15 | 0.1 | 0 |
Aggregated | 20 | 0 | 6300 | 6782.01 | 3989 | 15562 | 489.15 | 0.1 | 0 | |||
5 | POST | /v1/workflows/run | 20 | 0 | 8600 | 8566.16 | 4261 | 12971 | 482.81 | 0.3 | 0 | |
Aggregated | 20 | 0 | 8600 | 8566.16 | 4261 | 12971 | 482.81 | 0.3 | 0 | |||
10 | POST | /v1/workflows/run | 20 | 0 | 11000 | 10935.86 | 5779 | 21193 | 484.76 | 0.7 | 0 | |
Aggregated | 20 | 0 | 11000 | 10935.86 | 5779 | 21193 | 484.76 | 0.7 | 0 | |||
20 | POST | /v1/workflows/run | 20 | 0 | 16000 | 16845.03 | 6214 | 26838 | 487.57 | 0.6 | 0 | |
Aggregated | 20 | 0 | 16000 | 16845.03 | 6214 | 26838 | 487.57 | 0.6 | 0 |
四、并发性能结论
测试环境
-- 测试工具:Locust
-- 测试路径:/v1/workflows/run
-- 请求类型:POST
-- 测试数据:20 次请求,每次请求的大小和内容保持一致。
-- 并发数:1, 5, 10, 20
测试指标
-
Requests(请求成功次数):成功完成的请求总数。
-
Fails(请求失败次数):失败的请求总数。
-
Median(响应时间中位数-ms):所有响应时间的中位数。
-
Average(平均响应时间-ms):所有响应时间的平均值。
-
Min(最小响应时间-ms):最短响应时间。
-
Max(最大响应时间-ms):最长响应时间。
-
Average size(请求平均数据大小-bytes):每次请求的平均数据大小。
-
Current RPS(每秒处理事务数):当前的吞吐量。
-
Current Failures/s(每秒请求失败数):当前的失败率。
测试结果分析
1.请求成功率
vLLM 和 Ollama 在所有并发数下均未出现请求失败(Fails = 0
),表明两者在测试环境下均具有较高的稳定性。
2.平均响应时间
vLLM的平均响应时间随着并发数的增加而逐渐上升:
-
并发数 1:8910.67 ms
-
并发数 5:8930.52 ms
-
并发数 10:9453.53 ms
-
并发数 20:10319.91 ms
Ollama的平均响应时间同样随着并发数的增加而上升,但整体响应时间较 vLLM 更低:
-
并发数 1:6782.01 ms
-
并发数 5:8566.16 ms
-
并发数 10:10935.86 ms
-
并发数 20:16845.03 ms
结论:在低并发场景下(并发数 1 和 5),Ollama 的平均响应时间显著优于 vLLM。然而,随着并发数的增加,Ollama 的响应时间增长速度更快,尤其是在并发数 20 时,其平均响应时间几乎是 vLLM 的两倍。
3.响应时间中位数
vLLM的响应时间中位数:
-
并发数 1:7800 ms
-
并发数 5:8000 ms
-
并发数 10:9400 ms
-
并发数 20:9900 ms
Ollama的响应时间中位数:
-
并发数 1:6300 ms
-
并发数 5:8600 ms
-
并发数 10:11000 ms
-
并发数 20:16000 ms
结论:在低并发场景下,Ollama 的响应时间中位数优于 vLLM。然而,随着并发数的增加,Ollama 的响应时间中位数增长速度更快,尤其是在并发数 20 时,其响应时间中位数显著高于 vLLM。
4.吞吐量(RPS)
vLLM的吞吐量随着并发数的增加而逐渐上升:
-
并发数 1:0.1 RPS
-
并发数 5:0.5 RPS
-
并发数 10:0.9 RPS
-
并发数 20:1.0 RPS
Ollama的吞吐量同样随着并发数的增加而上升,但整体吞吐量较 vLLM 更低:
-
并发数 1:0.1 RPS
-
并发数 5:0.3 RPS
-
并发数 10:0.7 RPS
-
并发数 20:0.6 RPS
结论:vLLM 在所有并发数下的吞吐量均优于 Ollama,尤其是在高并发场景下(并发数 10 和 20),vLLM 的吞吐量显著高于 Ollama。
5.最大响应时间
vLLM的最大响应时间:
-
并发数 1:19013 ms
-
并发数 5:15561 ms
-
并发数 10:11864 ms
-
并发数 20:15798 ms
Ollama的最大响应时间:
-
并发数 1:15562 ms
-
并发数 5:12971 ms
-
并发数 10:21193 ms
-
并发数 20:26838 ms
结论:vLLM 的最大响应时间在低并发场景下略高于 Ollama,但在高并发场景下,vLLM 的最大响应时间显著低于 Ollama。这表明 vLLM 在高并发场景下具有更好的性能稳定性。
结论总结
在并发性能测试中,vLLM 和 Ollama 均表现出较高的稳定性(无失败请求),但在性能表现上存在显著差异:
1、低并发场景(并发数 1 和 5):
-
Ollama 的平均响应时间和响应时间中位数显著优于 vLLM。
-
vLLM 的吞吐量略高于 Ollama。
2、高并发场景(并发数 10 和 20):
- vLLM 的平均响应时间和响应时间中位数显著优于 Ollama。
- vLLM 的吞吐量显著高于 Ollama。
- vLLM 的最大响应时间在高并发场景下更稳定,表明其在高负载下的性能表现更优。
结论:vLLM 在高并发场景下表现更优,适合需要处理大规模并发请求的应用场景。而 Ollama 在低并发场景下具有较低的响应时间,适合对响应速度要求较高的低负载场景。
重要说明:
- 本次测试的逻辑较为简化,部分操作未考虑线程安全性,可能导致结果存在一定的偏差。
- 由于 Ollama 的 DeepSeek-R1 量化模型与 DeepSeek-R1-Distill-Qwen 蒸馏模型在架构上存在差异,其响应时间的对比并不完全具备参考性。
- 此外,测试中使用的输入文本不同也会对响应时间产生显著影响。
因此,相关测试结果仅用于大致对比,不能作为性能评估的唯一依据,实际部署和使用时应以真实环境中的表现为准。