为什么LLM服务如此具有挑战性?
计算资源
由于LLM需要处理大量的参数来进行预测,这可能从70亿参数增加到3210亿,部署这样的模型可能需要大量的资源和优化,而不是使用传统的方法来部署机器学习模型。
延迟
当句子或令牌复杂时,处理结果需要几分钟时间,这可能在大规模或真实世界的业务中造成问题。例如,一家公司可能在产品Q&A聊天机器人中应用LLM,缓慢的响应可能会让用户感到沮丧。因此,应用一些方法来降低延迟是一个好的实践。
成本
在大规模系统中或系统中有多个LLM时,会消耗大量预算,因为LLMs使用大量资源进行处理,作为一个MLE,找到一种利用资源的方法将为系统带来财务效益。例如,降低每次请求的成本。
什么是vLLM?
这个项目来自于加州大学伯克利分校的学生们,他们热衷于优化服务LLMs的性能。许多系统在服务LLMs时花费了大量的资源,然而,使用简单的方法部署时响应时间却很差。因此,vLLM团队提出了一种新方法来解决这个问题,通过使用操作系统的虚拟内存设计,可提升LLM服务性能约24倍,同时相比传统方法使用的GPU内存减半。为了集成到您的系统中,vLLM提供了一个简单的接口,让机器学习工程师通过Python接口进行开发,您可以在不使用复杂包或依赖的情况下将其集成到您的系统中。
vLLM的秘密武器是什么?
为了理解vLLM如何实现降低延迟和优化系统总体性能的目标,我们应该了解vLLM的瓶颈以及如何解决这个问题。
内存使用问题
大型语言模型(LLM)本质上是一个注意力神经网络的分支,或者有些人将其称为变压器,具有基于模型的自定义解码。因此,我们需要理解一个关键概念:LLM是如何生成令牌的。
内存分片
本质上,请求的KV缓存是存储在连续的内存空间中的,因为许多深度学习框架计算都需要将张量值存储在连续的内存中。然而,根据模型架构的不同,KV缓存可能会随时间增长或缩小,这种存储格式可能会导致问题,并且对于计算来说并不实用。
如何克服这个问题
vLLM团队开发了一种新的注意力算法,称为PageAttention,其灵感来自于操作系统的虚拟内存概念。可以将块想象为页面,令牌想象为字节,请求想象为进程。
使用PageAttention的另一好处是,它允许系统共享KV缓存,因为它将令牌存储在非连续的内存中。这使得LLM在许多应用程序中得以利用。
vLLM的OpenAI兼容服务器
如果你希望快速的使用vLLM启动一个OpenAI兼容的服务器,可以如下执行:
命令行安装:
代码语言:bash复制pip3 install vllm==0.5.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
正常启动(以Qwen2-7B为例):
代码语言:bash复制python -m vllm.entrypoints.openai.api_server --model Qwen/Qwen2-7B-Instruct
其他启动参数详解:
代码语言:bash复制python3 -m vllm.entrypoints.openai.api_server -h
用法: api_server.py [-h] [--host HOST] [--port PORT] [--uvicorn-log-level {debug,info,warning,error,critical,trace}] [--allow-credentials] [--allowed-origins ALLOWED_ORIGINS] [--allowed-methods ALLOWED_METHODS] [--allowed-headers ALLOWED_HEADERS]
[--api-key API_KEY] [--lora-modules LORA_MODULES [LORA_MODULES ...]] [--chat-template CHAT_TEMPLATE] [--response-role RESPONSE_ROLE] [--ssl-keyfile SSL_KEYFILE] [--ssl-certfile SSL_CERTFILE] [--ssl-ca-certs SSL_CA_CERTS]
[--ssl-cert-reqs SSL_CERT_REQS] [--root-path ROOT_PATH] [--middleware MIDDLEWARE] [--model MODEL] [--tokenizer TOKENIZER] [--skip-tokenizer-init] [--revision REVISION] [--code-revision CODE_REVISION]
[--tokenizer-revision TOKENIZER_REVISION] [--tokenizer-mode {auto,slow}] [--trust-remote-code] [--download-dir DOWNLOAD_DIR] [--load-format {auto,pt,safetensors,npcache,dummy,tensorizer,bitsandbytes}]
[--dtype {auto,half,float16,bfloat16,float,float32}] [--kv-cache-dtype {auto,fp8,fp8_e5m2,fp8_e4m3}] [--quantization-param-path QUANTIZATION_PARAM_PATH] [--max-model-len MAX_MODEL_LEN]
[--guided-decoding-backend {outlines,lm-format-enforcer}] [--distributed-executor-backend {ray,mp}] [--worker-use-ray] [--pipeline-parallel-size PIPELINE_PARALLEL_SIZE] [--tensor-parallel-size TENSOR_PARALLEL_SIZE]
[--max-parallel-loading-workers MAX_PARALLEL_LOADING_WORKERS] [--ray-workers-use-nsight] [--block-size {8,16,32}] [--enable-prefix-caching] [--disable-sliding-window] [--use-v2-block-manager]
[--num-lookahead-slots NUM_LOOKAHEAD_SLOTS] [--seed SEED] [--swap-space SWAP_SPACE] [--gpu-memory-utilization GPU_MEMORY_UTILIZATION] [--num-gpu-blocks-override NUM_GPU_BLOCKS_OVERRIDE]
[--max-num-batched-tokens MAX_NUM_BATCHED_TOKENS] [--max-num-seqs MAX_NUM_SEQS] [--max-logprobs MAX_LOGPROBS] [--disable-log-stats]
[--quantization {aqlm,awq,deepspeedfp,fp8,marlin,gptq_marlin_24,gptq_marlin,gptq,squeezellm,compressed-tensors,bitsandbytes,None}] [--rope-scaling ROPE_SCALING] [--rope-theta ROPE_THETA] [--enforce-eager]
[--max-context-len-to-capture MAX_CONTEXT_LEN_TO_CAPTURE] [--max-seq-len-to-capture MAX_SEQ_LEN_TO_CAPTURE] [--disable-custom-all-reduce] [--tokenizer-pool-size TOKENIZER_POOL_SIZE] [--tokenizer-pool-type TOKENIZER_POOL_TYPE]
[--tokenizer-pool-extra-config TOKENIZER_POOL_EXTRA_CONFIG] [--enable-lora] [--max-loras MAX_LORAS] [--max-lora-rank MAX_LORA_RANK] [--lora-extra-vocab-size LORA_EXTRA_VOCAB_SIZE] [--lora-dtype {auto,float16,bfloat16,float32}]
[--long-lora-scaling-factors LONG_LORA_SCALING_FACTORS] [--max-cpu-loras MAX_CPU_LORAS] [--fully-sharded-loras] [--device {auto,cuda,neuron,cpu,openvino,tpu,xpu}] [--image-input-type {pixel_values,image_features}]
[--image-token-id IMAGE_TOKEN_ID] [--image-input-shape IMAGE_INPUT_SHAPE] [--image-feature-size IMAGE_FEATURE_SIZE] [--image-processor IMAGE_PROCESSOR] [--image-processor-revision IMAGE_PROCESSOR_REVISION]
[--disable-image-processor] [--scheduler-delay-factor SCHEDULER_DELAY_FACTOR] [--enable-chunked-prefill] [--speculative-model SPECULATIVE_MODEL] [--num-speculative-tokens NUM_SPECULATIVE_TOKENS]
[--speculative-draft-tensor-parallel-size SPECULATIVE_DRAFT_TENSOR_PARALLEL_SIZE] [--speculative-max-model-len SPECULATIVE_MAX_MODEL_LEN] [--speculative-disable-by-batch-size SPECULATIVE_DISABLE_BY_BATCH_SIZE]
[--ngram-prompt-lookup-max NGRAM_PROMPT_LOOKUP_MAX] [--ngram-prompt-lookup-min NGRAM_PROMPT_LOOKUP_MIN] [--model-loader-extra-config MODEL_LOADER_EXTRA_CONFIG] [--preemption-mode PREEMPTION_MODE]
[--served-model-name SERVED_MODEL_NAME [SERVED_MODEL_NAME ...]] [--qlora-adapter-name-or-path QLORA_ADAPTER_NAME_OR_PATH] [--otlp-traces-endpoint OTLP_TRACES_ENDPOINT] [--engine-use-ray] [--disable-log-requests]
[--max-log-len MAX_LOG_LEN]
vLLM与OpenAI兼容的RESTful API服务器。
可选参数:
-h, --help 显示此帮助信息并退出
--host HOST 主机名
--port PORT 端口号
--uvicorn-log-level {debug,info,warning,error,critical,trace}
uvicorn的日志等级
--allow-credentials 允许凭证
--allowed-origins ALLOWED_ORIGINS
允许的来源
--allowed-methods ALLOWED_METHODS
允许的方法
--allowed-headers ALLOWED_HEADERS
允许的头信息
--api-key API_KEY 如果提供,服务器将要求在头信息中提供此密钥。
--lora-modules LORA_MODULES [LORA_MODULES ...]
LoRA模块配置,格式为name=path。可以指定多个模块。
--chat-template CHAT_TEMPLATE
聊天模板的文件路径,或为指定模型的单行形式的模板
--response-role RESPONSE_ROLE
如果`request.add_generation_prompt=true`,则返回的角色名称。
--ssl-keyfile SSL_KEYFILE
SSL密钥文件的文件路径
--ssl-certfile SSL_CERTFILE
SSL证书文件的文件路径
--ssl-ca-certs SSL_CA_CERTS
CA证书文件
--ssl-cert-reqs SSL_CERT_REQS
是否需要客户端证书(参考标准库ssl模块)
--root-path ROOT_PATH
当应用程序位于基于路径的路由代理之后时的FastAPI root_path
--middleware MIDDLEWARE
应用于应用的附加ASGI中间件。我们接受多个--middleware参数。其值应为一个导入路径。如果提供了函数,vLLM会将其添加到服务器使用@app.middleware('http')。如果提供了类,则vLLM会使用app.add_middleware()添加它。
--model MODEL 使用的huggingface模型名称或路径。
--tokenizer TOKENIZER
使用的huggingface分词器名称或路径。如果未指定,则使用模型名称或路径。
--skip-tokenizer-init
跳过分词器和反分词器的初始化
--revision REVISION 要使用的特定模型版本。它可以是一个分支名称、一个标签名称或一个提交id。如果未指定,将使用默认版本。
--code-revision CODE_REVISION
用于模型代码在Hugging Face Hub的特定修订版本。它可以是一个分支名称、一个标签名称或一个提交id。如果未指定,将使用默认版本。
--tokenizer-revision TOKENIZER_REVISION
要使用的huggingface分词器的修订版本。它可以是一个分支名称、一个标签名称或一个提交id。如果未指定,将使用默认版本。
--tokenizer-mode {auto,slow}
分词器模式。* "auto"将使用快速分词器(如果可用)。* "slow"将总是使用慢分词器。
--trust-remote-code 信任来自huggingface的远程代码。
--download-dir DOWNLOAD_DIR
下载并加载权重的目录,默认为huggingface的默认缓存目录。
--load-format {auto,pt,safetensors,npcache,dummy,tensorizer,bitsandbytes}
用于加载模型权重的格式。* "auto"将尝试以safetensors格式加载权重,并在safetensors格式不可用时回退到pytorch bin格式。* "pt"将以pytorch bin格式加载权重。* "safetensors"将以safetensors格式加载权重。* "npcache"将以pytorch格式加载权重并存储numpy缓存以加速加载。* "dummy"将以随机值初始化权重,主要用于性能分析。* "tensorizer"将使用来自CoreWeave的tensorizer加载权重。有关更多信息,请参见示例部分中的Tensorize vLLM模型脚本。* "bitsandbytes"将使用bitsandbytes量化加载权重。
--dtype {auto,half,float16,bfloat16,float,float32}
模型权重和激活的数据类型。* "auto"将对FP32和FP16模型使用FP16精度,对BF16模型使用BF16精度。* "half"用于FP16。推荐用于AWQ量化。* "float16"与"half"相同。* "bfloat16"用于平衡精度和范围。* "float"是FP32精度的简写。* "float32"用于FP32精度。
--kv-cache-dtype {auto,fp8,fp8_e5m2,fp8_e4m3}
kv缓存存储的数据类型。如果为"auto",将使用模型数据类型。CUDA 11.8 支持fp8(=fp8_e4m3)和fp8_e5m2。ROCm(AMD GPU)支持fp8(=fp8_e4m3)
--quantization-param-path QUANTIZATION_PARAM_PATH
包含KV缓存比例因子的JSON文件路径。当KV缓存数据类型为FP8时,通常应当提供此文件。否则,KV缓存比例因子默认为1.0,可能导致准确性问题。FP8_E5M2(未缩放)仅在CUDA版本大于11.8时支持。在ROCm(AMD GPU)上,相反,支持FP8_E4M3以满足常见的推理标准。
--max-model-len MAX_MODEL_LEN
模型上下文长度。如果未指定,将从模型配置自动派生。
--guided-decoding-backend {outlines,lm-format-enforcer}
哪个引擎将默认用于指导解码(JSON架构/正则表达式等)。当前支持https://github.com/outlines-dev/outlines 和 https://github.com/noamgat/lm-format-enforcer。可通过请求中的guided_decoding_backend参数覆盖。
--distributed-executor-backend {ray,mp}
用于分布式服务的后端。当使用多于1个GPU时,如果安装了"ray"将自动设置为"ray",否则设置为"mp"(多进程)。
--worker-use-ray 已弃用,请使用--distributed-executor-backend=ray。
--pipeline-parallel-size PIPELINE_PARALLEL_SIZE, -pp PIPELINE_PARALLEL_SIZE
管道阶段的数量。
--tensor-parallel-size TENSOR_PARALLEL_SIZE, -tp TENSOR_PARALLEL_SIZE
张量并行副本的数量。
--max-parallel-loading-workers MAX_PARALLEL_LOADING_WORKERS
分多批次顺序加载模型,以避免在使用张量并行和大型模型时发生RAM OOM。
--ray-workers-use-nsight
如果指定,使用nsight对Ray工作节点进行分析。
--block-size {8,16,32}
连续token块的token块大小。
--enable-prefix-caching
启用自动前缀缓存。
--disable-sliding-window
禁用滑动窗口,设置为滑动窗口大小
--use-v2-block-manager
使用BlockSpaceMangerV2。
--num-lookahead-slots NUM_LOOKAHEAD_SLOTS
实验性调度配置,必要用于对初始解码的推测。这将被推测配置在未来替代;它目前的存在是为了直到那时启用正确性测试。
--seed SEED 操作的随机种子。
--swap-space SWAP_SPACE
每GPU的CPU交换空间大小(GiB)。
--gpu-memory-utilization GPU_MEMORY_UTILIZATION
用于模型执行器的GPU内存的使用比例,可以从0到1的范围。例如,0.5的值将意味着50%的GPU内存利用率。如果未指定,将使用0.9的默认值。
--num-gpu-blocks-override NUM_GPU_BLOCKS_OVERRIDE
如果指定,忽略GPU性能测试结果并使用这个数字的GPU块。用于测试抢占。
--max-num-batched-tokens MAX_NUM_BATCHED_TOKENS
每次迭代的最大批处理token数。
--max-num-seqs MAX_NUM_SEQS
每次迭代的最大序列数。
--max-logprobs MAX_LOGPROBS
返回logprobs的最大数量,如果在SamplingParams中指定了logprobs。
--disable-log-stats 禁用日志统计。
--quantization {aqlm,awq,deepspeedfp,fp8,marlin,gptq_marlin_24,gptq_marlin,gptq,squeezellm,compressed-tensors,bitsandbytes,None}, -q {aqlm,awq,deepspeedfp,fp8,marlin,gptq_marlin_24,gptq_marlin,gptq,squeezellm,compressed-tensors,bitsandbytes,None}
用于量化权重的方法。如果None,我们首先检查模型配置文件中的`quantization_config`属性。如果那是None,我们假设模型权重未量化,并使用`dtype`确定权重的数据类型。
--rope-scaling ROPE_SCALING
RoPE缩放配置的JSON格式。例如,{"type":"dynamic","factor":2.0}
--rope-theta ROPE_THETA
使用`rope_scaling`的RoPE theta。在某些情况下,改变RoPE theta可以提高缩放模型的性能。
--enforce-eager 总是使用PyTorch的急切模式。如果为False,将使用急切模式和CUDA图的混合以获得最大的性能和灵活性。
--max-context-len-to-capture MAX_CONTEXT_LEN_TO_CAPTURE
由CUDA图覆盍的最大上下文长度。当序列的上下文长度大于此长度时,我们将回退到急切模式。(已弃用。请改用--max-seq-len-to-capture)
--max-seq-len-to-capture MAX_SEQ_LEN_TO_CAPTURE
由CUDA图覆盖的最大序列长度。当序列的上下文长度大于此长度时,我们将回退到急切模式。
--disable-custom-all-reduce
参见ParallelConfig。
--tokenizer-pool-size TOKENIZER_POOL_SIZE
用于异步分词的分词器池的大小。如果为0,将使用同步分词。
--tokenizer-pool-type TOKENIZER_POOL_TYPE
用于异步分词的分词器池的类型。如果tokenizer_pool_size为0,则忽略。
--tokenizer-pool-extra-config TOKENIZER_POOL_EXTRA_CONFIG
分词器池额外配置。这应当是一个将被解析成字典的JSON字符串。如果tokenizer_pool_size为0,则忽略。
--enable-lora 如果为True,启用LoRA适配器的处理。
--max-loras MAX_LORAS
单个批次中LoRAs的最大数量。
--max-lora-rank MAX_LORA_RANK
LoRA秩的最大值。
--lora-extra-vocab-size LORA_EXTRA_VOCAB_SIZE
LoRA适配器中可以存在的额外词汇表的最大大小(添加到基础模型词汇表中)。
--lora-dtype {auto,float16,bfloat16,float32}
LoRA的数据类型。如果为auto,将默认为基础模型的dtype。
--long-lora-scaling-factors LONG_LORA_SCALING_FACTORS
指定多个缩放因子(可以与基础模型的缩放因子不同-参见例如Long LoRA),以允许同时使用训练有这些缩放因子的多个LoRA适配器。如果未指定,仅允许使用基础模型缩放因子训练的适配器。
--max-cpu-loras MAX_CPU_LORAS
在CPU内存中存储的LoRAs的最大数量。必须大于等于max_num_seqs。默认为max_num_seqs。
--fully-sharded-loras
默认情况下,仅使用张量并行对LoRA计算的一半进行分片。启用此选项将使用完全分片的层。在高序列长度、最大秩或张量并行大小下,这可能更快。
--device {auto,cuda,neuron,cpu,openvino,tpu,xpu}
vLLM执行的设备类型。
--image-input-type {pixel_values,image_features}
传入vLLM的图像输入类型。
--image-token-id IMAGE_TOKEN_ID
图像令牌的输入id。
--image-input-shape IMAGE_INPUT_SHAPE
给定输入类型的最大图像输入形状(对内存占用最不利)。仅用于vLLM的profile_run。
--image-feature-size IMAGE_FEATURE_SIZE
图像特征沿上下文维度的大小。
--image-processor IMAGE_PROCESSOR
要使用的huggingface图像处理器的名称或路径。如果未指定,将使用模型名称或路径。
--image-processor-revision IMAGE_PROCESSOR_REVISION
要使用的huggingface图像处理器的修订版本。它可以是一个分支名称、一个标签名称或一个提交id。如果未指定,将使用默认版本。
--disable-image-processor
即使为模型在huggingface上定义了图像处理器,也禁用图像处理器的使用。
--scheduler-delay-factor SCHEDULER_DELAY_FACTOR
在调度下一个提示之前应用延迟(以先前提示的延迟乘以延迟因子)。
--enable-chunked-prefill
如果设置,prefill请求可以根据max_num_batched_tokens进行分块。
--speculative-model SPECULATIVE_MODEL
在推测解码中使用的草案模型的名称。
--num-speculative-tokens NUM_SPECULATIVE_TOKENS
从草案模型在推测解码中采样的推测token数。
--speculative-draft-tensor-parallel-size SPECULATIVE_DRAFT_TENSOR_PARALLEL_SIZE, -spec-draft-tp SPECULATIVE_DRAFT_TENSOR_PARALLEL_SIZE
在推测解码中草案模型的张量并行副本数。
--speculative-max-model-len SPECULATIVE_MAX_MODEL_LEN
草案模型支持的最大序列长度。序列超过此长度将跳过推测。
--speculative-disable-by-batch-size SPECULATIVE_DISABLE_BY_BATCH_SIZE
如果入队请求的数量大于此值,则禁用新进入请求的推测解码。
--ngram-prompt-lookup-max NGRAM_PROMPT_LOOKUP_MAX
在推测解码中ngram提示查找的最大窗口大小。
--ngram-prompt-lookup-min NGRAM_PROMPT_LOOKUP_MIN
在推测解码中ngram提示查找的最小窗口大小。
--model-loader-extra-config MODEL_LOADER_EXTRA_CONFIG
模型加载器的额外配置。这将传递给对应于所选load_format的模型加载器。这应当是一个将被解析成字典的JSON字符串。
--preemption-mode PREEMPTION_MODE
如果为‘recompute’,引擎通过块交换执行抢占;如果为‘swap’,引擎通过块交换执行抢占。
--served-model-name SERVED_MODEL_NAME [SERVED_MODEL_NAME ...]
API中使用的模型名称。如果提供了多个名称,服务器将响应任何提供的名称。响应中的模型字段中的模型名称将是此列表中的第一个名称。如果未指定,模型名称将与`--model`参数相同。注意,此名称也将用于prometheus度量中的`model_name`标签内容,如果提供了多个名称,度量标签将采用第一个。
--qlora-adapter-name-or-path QLORA_ADAPTER_NAME
总结
vLLM展示了使用我们已经应用了十年的简单概念,就能做出令人惊叹的东西。这个框架在GPU内存使用和利用PageAttention技术的各种优势方面取得了巨大的改进。通过减少KV缓存的使用,系统能够处理更大的负载并更快地进行推理。