当前位置: 首页 > news >正文

【VLLM篇】:原理-实现

1、VLLM

vLLM是一个建立在【PagedAttention】之上的高吞吐的【分布式服务引擎】,目标是【提高吞吐量】、【提高内存利用率】(kv-cache内存利用率高达96%),它的内存管理分配方式从【固定分配】改进为【分页管理】,类似操作系统中的分页管理,vLLM本体是一个Python编写的开源大语言模型推理框架 https://github.com/vllm-project/vllm。

常规大模型推
理主要存在【显存】与【吞吐】方面的挑战:

【显存】:

  • 【请求的输入输出长度会不同】:固定长度的预分配内存会造成较多内存浪费,同时kv-cache无法共享内存,单纯以填充的方式 会导致显存浪费。
  • 【大型kv-cache】:对llm来说,一个请求的kv-cache需求可以高达1.6G,显存本身一般只有数十G,即便不计weights占用,所有显存都给kv-cache,也不过几十个请求;
    低效内存管理会产生越来越多的内存碎片,占用更多显存,进一步减少批处理大小。

【吞吐】:

  • 【请求不会同时到达】:一般的批处理策略 会等待请求全部到达,满足一个批次的要求,再进行推理,导致较大的推理延迟;
  • 【复杂的解码算法】:greedy search、beam search、sampling等解码算法对内存管理的复杂性要求不同,需要高效兼容它们,不使解码成为推理瓶颈。

2、原理篇

KV cache

在自回归生成过程中,模型需要逐步生成每个token,而每次生成新token时,传统的Transformer架构会重新计算所有历史token的Key和Value矩阵。
这种重复计算导致计算复杂度呈二次增长(O(n 2 ⋅d)),KV Cache通过缓存已计算的Key和Value向量,仅需计算当前token的Query向量,并与缓存的K/V进行注意力计算,将复杂度降至线性(O(n⋅d)),大幅加快推理速度。

【paged attention关键一】【block table的设计】:paged attention脱胎于操作系统的【分页】设计。

  • 【分页】:程序【内存】按4k分页,每一页是一个逻辑地址,映射表为【逻辑地址,物理地址】的映射;
  • 【paged attention】:【prompt】按【block】分块,【每个block中有4个token】,每个block相当于一个4k分页,为逻辑block,
    映射表为【该映射表 逻辑block的index 在 物理block中的『index』,已填充的token的数量】。
  • paged attention 块(block)中有词元(token),并且后续需要持续增加token,导致映射表需要把index这个因素利用进来,
    同时增加【已填充的token的数量】这个信息,标记是否还能在这个block中增加token。

【pagedattention关键二】【并行采样的过程】:block内存的【共享】与【分裂】(copy on write)。

每个【物理块】会带着一个ref_count计数,表示有多少个【虚拟块】指向它,这个ref_count是共享内存的关键。

  • 【共享】:可能是不同的请求,可能是相同请求中不同的位置,它们在逻辑block中的4个token完全一致,此时它们共享一个物理block。
  • 【分裂】:两个请求的逻辑block,前1个token计算结果相同时,它们会指向一个新的物理block,
    存放第一个相同的token,如 “one”,该【物理blockA】的ref_count_A被设置为2,
    及至第二第三个token的计算结果也相同,如"two",“three”,ref_count_A仍为2;
    直到第4个token,两个请求计算出了不同的结果"ten"与"hundred",
    此时copy on write被触发,【物理blockA】被复制到【物理blockB】,
    【物理blockA】的第4个token被写入"ten",【物理blockB】的第4个token被写入"hundred",
    分别为【“one”,“two”,“three”,『“ten”』】,【“one”,“two”,“three”,『“hundred”』】,
    ref_count_A被【修改】为1,ref_count_B被【设置】为1,一轮分裂结束。

其他

  • 离散显存
    对比非paged attention的显存管理方式(分配固定大小),paged attention显存与token【在逻辑上是连续的】,【在物理地址上是离散的】,
    【多个】【不同长度】的请求可以【同时】做推理,还能【共享内存】——>【内存利用率】、【吞吐量】都得到了大量提升。

  • 调度与抢占:
    当区块大小【较小】时,【重新计算】效率更高,因为小块会导致GPU CPU之间有大量小块传输,占用带宽;
    当区块大小【较大】时,【交换】(将逐出的页面复制到磁盘上的交换空间)效率更高。

3、实现篇

api──engine
│   	├──scheduler──blockmanager
│	    ├──executor──worker──model runner──model loader(lora)
│	    │					                 ├──models──layer(attention)──ops(pytorch)──kernels
│	    │					                 ├──sampleout(spec_decode)
  • API: vLLM的一般有两种用法,异步的 API Server,同步的LLMEngine。
    • API Server: 异步服务端,它会为每一个request关联一个output stream,用来返回推理生成的结果。
    • LLMEngine API: 多用于测试、调试代码,它是对下层引擎的封装,并将引用层传入的参数组成engine_arges, 提供给引擎。
  • Engine: Engine层的LLMEngine具体由Scheduler与Executor两个部分组成,Scheduler负责逻辑上的虚拟调度,Executor负责实际分配显存与运行推理。
  • Scheduler:
    • SequenceGroup: SequenceGroup是为了llm推理时存在的【一个prompt - 多个output】设计的,Scheduler首先会把prompt(request)转换为SequenceGroup,这是一个list,里面的元素是若干个Sequence,它们共享相同的prompt。

    • Queue status: Scheduler维护了3个队列,其中waiting队列主要是用户刚刚输入到scheduler中的token序列;running队列是正在进行推理的token序列;swapped队列是当显存不足或者推理优先级降低时,从GPU中换出的token序列

    • BlockManager: BlockManager下属两个KVCache分配器UncachedAllocator与CachedAllocator。UncachedAllocator是正常token的KVCache分配器, 通过block_table维护分配状态;CachedAllocator主要添加了token前缀hash计算,会将相同前缀的KVCache直接复用起来,并引入了evictor等概念,被清除的KVCache会先暂时根据LRU原则转移到evictor中,如果短时间内又出现相同前缀的token,可以恢复它的前缀KVCache使用。

    • Block Table: 记录逻辑块号 到 物理块号的映射,类似页表的功能,是由虚到实的连接。

    • Evictor: 驱逐器,显存不足或新请求分配时,主动淘汰低优先级数据,如最近最少使用,提高显存利用效率,提升吞吐量。

  • Executor: 统筹模型实际运行配置config,有单卡单worker的使用,也有ray分布式框架组织的worker集群,核心类是Worker。
  • Worker:
    • ModelRunner: Worker的核心类,主要执行load_model()、capture_model(self.gpu_cache)、prepare_model_input(seq_group)、execute_model(…)的操作,其中的ModelLoader加载实际具体的llm模型结构如llama、qwen、deepseek等。
    • Attention Backend: 因为会直接和GPU或者其他计算平台打交道,这里封装了一层Attention Backend, 屏蔽这些不同平台的差异细节,如CUDA的Graph与Cache Engine,或者是别的gpu平台如Biren。
  • Backend: Backend负责从模型layers-ops-gpu kernel的调用,涉及到具体硬件算子的调用,在大语言模型transformer架构中,最核心的是attention类算子。

(以下的文件路径参考vllm 0.6.0)

3.1、api

prompts = ["Hello, my name is","The president of the United States is","The capital of France is","The future of AI is",]
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
llm = LLM(model="facebook/opt-125m")	# 假数据的模拟推理在这里进行,得到kvcache块大小
outputs = llm.generate(prompts, sampling_params)	# 用户视角:1个批处理请求	# 系统视角:4个独立请求
for output in outputs:prompt = output.promptgenerated_text = output.outputs[0].textprint(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")

vllm的推理引擎内核(LLMEngine),实际上是【异步】工作的,batch size是根据当前【输入情况】与【显存情况】动态变化的。
【同步】的离线推理,只是把结果全部收集完毕后,再一起返回给我们,看起来是同步,实际上也是【异步】工作的。

vllm/vllm/engine/
arg_utils.py的class EngineArgs中,有所有定义的参数,部分参数说明如下:

参数名类型默认值说明
modelstr必填HuggingFace模型名称或本地路径(如"meta-llama/Llama-3-8B-Instruct"
tokenizerstrNone自定义分词器路径,未指定时使用与模型匹配的分词器
tokenizer_modestr"auto"分词器模式:"auto"(自动选择快速分词器)、"slow"(强制慢速分词器)
trust_remote_codeboolFalse是否信任远程代码(如HuggingFace自定义模型代码),存在安全风险
tensor_parallel_sizeint1张量并行GPU数量,需与模型规模匹配(如70B模型需8 GPU)
dtypestr"auto"计算数据类型:"float16""bfloat16""float32""auto"根据模型配置自动选择
devicestr"auto"硬件设备类型:"cuda""cpu"
quantizationstrNone量化方法:"awq""gptq""bitsandbytes"等,用于减少显存占用
gpu_memory_utilizationfloat0.9GPU显存利用率(0~1),值越高KV缓存越大,但可能引发OOM
swap_spaceint4每个GPU的CPU交换空间大小(GiB),用于临时存储不活跃请求的KV缓存
block_sizeint16KV缓存块大小(token数),较小值减少内存碎片,较大值提升长文本性能
max_num_batched_tokensint2048单次批处理的最大token数,影响吞吐量,高并发场景建议增大
max_num_seqsint256最大并发处理序列数,受GPU显存限制
disable_async_output_procboolFalse禁用异步输出处理(默认启用),关闭后可能降低性能
enable_prefix_cachingboolFalse启用前缀缓存,避免重复计算共享提示词,提升聊天场景性能
enable_chunked_prefillboolFalse启用分块预填充,优化长输入的内存使用,性能提升15-25%
scheduling_policystr"fcfs"调度策略:"fcfs"(先到先服务)或"priority"(优先级调度)
num_scheduler_stepsint1每次调度的前向步骤数,多步调度(如设为10)可提升吞吐量
kv_cache_dtypestr"auto"KV缓存数据类型(fp8/fp8_e4m3等),支持FP8显存优化
speculative_modelstrNone推测解码的草稿模型名称(如facebook/opt-125m
num_speculative_tokensint0推测解码时采样的token数量
enable_loraboolFalse启用LoRA适配器支持
max_lorasint1单批次中LoRA的最大数量
guided_decoding_backendstr"xgrammar"引导解码引擎(如outlines-dev/outlines
preemption_modestr"recompute"抢占模式(recompute重计算/swap交换)
spec_decoding_acceptance_methodstr"rejection_sampler"推测解码的接受方法(rejection_samplertypical_acceptance_sampler
disable_logprobs_during_spec_decodingboolFalse推测解码期间禁用log概率返回
speculative_model_quantizationstrNone草稿模型的量化方法(如awq
speculative_draft_tensor_parallel_sizeint1草稿模型的张量并行GPU数量
ngram_prompt_lookup_min/maxintNone推测解码中N-gram提示查找的窗口大小范围
multi_step_stream_outputsboolTrue多步调度时是否流式输出所有步骤结果

3.2、engine

engine中包含最关键的 【决策层Scheduler】 与 【执行层Executor】。
vllm/vllm/engine/llm_engine.py的class LLMEngine是vllm的核心类,有关键成员scheduler、executor,它们的进一步说明在下面的章节。

  • scheduler的创建
 386         self.scheduler = [387             Scheduler(388                 scheduler_config, cache_config, lora_config,389                 parallel_config.pipeline_parallel_size,390                 self.async_callbacks[v_id]391                 if model_config.use_async_output_proc else None)392             for v_id in range(parallel_config.pipeline_parallel_size)393         ]
  • executor的创建
 305         self.model_executor = executor_class(306             model_config=model_config,307             cache_config=cache_config,308             parallel_config=parallel_config,309             scheduler_config=scheduler_config,310             device_config=device_config,311             lora_config=lora_config,312             speculative_config=speculative_config,313             load_config=load_config,314             prompt_adapter_config=prompt_adapter_config,315             observability_config=self.observability_config,316         )

class LLMEngine中关键函数def add_request、def step:

  • add_request():把每一个请求包装成SequenceGroup,并加入waiting队列
  • step():执行一次推理过程,1个【prefill】算一个推理,每个【decode】各算一个推理

加载模型与预分配显存

【第一条】请求过来的时候,要【加载模型】与【预分配显存】(加载模型会加载已经搭建好的模型结构,这里注意看预分配显存)
【预分配显存】

  • 1、杜撰假数据
    max_num_seqs、max_num+batched_token:
    在【一个推理阶段】中,LLMEngine【最多能处理的【seq条数】与【token数】】,
    平均一个seq要处理max_num_batched_tokens // max_num_seqs个token,余数部分我们默认放在第一个seq中,
    假设max_num_batched_tokens=10,max_num_seqs = 3,那么我们就能杜撰出3条seq,每个seq的长度分别为4,3,3。

  • 2、用假数据做一次不使用kv-cache的前向推理
    这是为了【精确测量】所有显存占用的情况,建立【最坏】显存占用的边界:
    实际模型运行过程中,除了模型本身带来的【固定静态显存】大小之外,还会存在:
    ①动态显存:与输入形状(batch_size, seq_len)强相关的【中间activation】与【temporary tensors】
    ②系统显存占用:pytorch相关的启动显存、内存碎片等
    所以:【可以分配给KV cache的显存】 = gpu总显存 - 不使用kv-cache做一次推理所占用的显存(运行时记录)

  • 3、计算【可以分配给KV cache的【物理块数量】】
    【物理块数量】=【可以分配给KV cache的显存】/【物理块大小】
    【物理块大小】=【物理块尺寸block_size】(一个物理块可以容纳的token数量,vllm默认值是16)x【每个token在kv_cache中占用的大小】
    【每个token在kv_cache中占用的大小】:
    首先,token的本质可以是【英文子词拆分】或者【中文字符映射】:
    “transformer” → [“trans”, “former”] # 子词拆分
    “你好” → [37955] # 中文字符直接映射
    其次,需要区分两种token的存储需求【嵌入存储】与【kvcache存储】(此处以【bert large bf16】为例(1024),bert base则d_model为768):
    【嵌入存储】大小:d_model(模型核心维度) x dtype_size = 1024 x 2 = 2048
    【kvcache存储】大小:k向量+v向量,它俩均为【num_heads x head_size x num_layers 个元素】的大小,
    num_heads:注意力头数,large为16,
    head_size:单头维度,= d_model / num_heads = 1024/16 = 64,
    num_layers:transformer层数,bert large中为encoder层数24,
    故【kvcache存储】大小 = num_heads x head_size x num_layers x dtype_size x 2
    = 16x64x24x2x2 = 98, 304 Bytes = 96 K
    = 【每个token在kv_cache中占用的大小】

由此可得 【物理块大小】=【物理块尺寸block_size】x【每个token在kv_cache中占用的大小】
= 16 x 98, 304 = 1, 572, 864 Bytes = 1.5M,
【物理块数量】=【可以分配给KV cache的显存】/【物理块大小】=【可以分配给KV cache的显存】/1.5M
(上述计算是基于bert large的,如果以gpt 175B为例,物理块大小 ≈ 4.7M x 16 ≈ 72M)

  • 4、将预分配的块加载到gpu上
    按照计算好的块大小与数量,创建empty tensor,加载到gpu中,实现显存预分配,这些内存专门用于做kvcache

3.3、scheduler

./vllm/vllm/core/scheduler.py与./vllm/vllm/sequence.py比较关键的两个文件。

SequenceGroup

存在【一个prompt -> 多个output】的情况,SequenceGroup是为了这种【一对多】的情况设计的,
如decode,parallel sampling(n=3) 或者 beam search(n=3, best_of=3)(强制要求n=best_of,输出topK个output) 时,
一个SequenceGroup中,有【3个seq,且它们都共享同一个prompt】,
每个seq都有若干状态:
waiting:没做过prefill的,
running:做完prefill,已经开始做推理的,
swapped:gpu显存不够暂时被换到cpu,等待换回来继续推理的,被抢占的(最新vllm已经移除这个状态)

scheduler中,prompt变为SequenceGroup的过程:

  • 1、在LLMEngine.encode_request()中,每个prompt字符串通过tokenizer转换为对应的prompt_token_ids,比如:
    prompt_token_ids = tokenizer.encode(“Hello, my name is”) # 可能得到 [1, 150, 50, 10]
  • 2、Sequence对象​​:每个prompt_token_ids会实例化为Sequence,核心属性包括:
    prompt_token_ids: 编码后的输入token IDs。
    output_token_ids: 初始为空,用于存储生成的token。
    status: 初始为SequenceStatus.WAITING,表示待调度。
    logical_token_blocks: 根据block_size(默认为16)划分逻辑块,记录token的内存占用。
  • 3、​​SequenceGroup封装​​:所有Sequence被封装为SequenceGroup,关键属性包括:
    request_id: 请求的唯一标识。
    seqs_dict: 以seq_id为键的Sequence字典。
    sampling_params: 采样策略(如temperature、top_k等)。
    arrival_time: 请求到达时间戳。

scheduler调度

add_request()->每个prompt包装成sequenceGroup实例->放到waiting(waiting队列中的seq_group只有一个seq,即是原始的prompt)
step()->决定哪些seq_group被送去推理->model_executor执行推理->放到running
(做prefill):【prompt->SequenceGroup->list[Sequence],Sequence->list[LogicalTokenBlock]+data+status,LogicalTokenBlock->block_number(logic)+block_size(16) 】

  • ​​调度器处理​​:Scheduler将SequenceGroup加入队列,根据优先级和资源(如GPU block可用性)决定何时执行。调度时会生成SequenceGroupMetadata,包含:
    • seq_data: 各Sequence的token IDs和状态。
    • block_tables: 记录每个Sequence的物理内存块映射。
  • ​​执行阶段​​:当SequenceGroup被选中执行时:
    1、is_prompt标记为True(首次推理)。
    2、模型根据prompt_token_ids生成logits,采样得到新token(如"John")。
    3、新token追加到output_token_ids,并更新logical_token_blocks。
  • 关键中间类
    • SequenceData​​:存储序列的token IDs和累计log概率,是Sequence的核心数据成员。
    • ​​SequenceStatus​​:枚举类,管理序列的生命周期状态(如RUNNING、FINISHED)。
    • ​​LogicalTokenBlock​​:管理逻辑内存块,记录token分布和空闲槽位,与物理块通过【block_tables】映射,这是虚与实连接的纽带,类似【页表】。

3.4、block_manager

./vllm/vllm/core/block_manager_v1.py与vllm/vllm/block.py中,

  • 主要类嵌套关系:
    Scheduler->BlockSpaceManagerV1->UncachedBlockAllocator/CachedBlockAllocator
    CachedBlockAllocator通过前缀复用提升性能与显存效率​​,而​​UncachedBlockAllocator以简单性换取通用性​​。
    实际应用中可根据请求特征(如前缀重复率、显存限制)灵活选择两者,同时这两者都可以设置为self.gpu_allocator 或者self.cpu_allocator。
  • 主要函数调用堆栈
    BlockSpaceManagerV1.allocate()->BlockSpaceManagerV1.allocate_sequence->CachedBlockAllocator.allocate()->BlockSpaceManagerV1.block_tables: Dict[str, BlockTable]
  • 虚拟块号与物理块号的关联过程
    • 0、Sequence创建时会自带Counter()全局递增的seq_id
    • 1、BlockSpaceManagerV1.allocate()为SequenceGroup 分配物理块,会把waiting状态的seqs拿出来分配显存,其中的BlockSpaceManagerV1._allocate_sequence()调用CachedBlockAllocator.allocate()实际分配物理块,
      又因为一个prompt对应一个SequenceGroup,seqs们的prompt是相同的,所以只需要为一个seq分配物理块,其他seq共享此物理块。
    • 2、CachedBlockAllocator.allocate()中的allocate_block()实际申请显存,返回一个物理块PhysicalTokenBlock,其中有物理块号PhysicalTokenBlock.block_number
    • 3、BlockTable的成员是 一个【PhysicalTokenBlock的list】 + 一个【PhysicalTokenBlock对应的物理块号】的list,
      在BlockSpaceManagerV1._allocate_sequence()中,通过BlockTable.append()来增加两个list中的元素。
    • 4、prompt->SequenceGroup(Sequence自带seq_id)->BlockTable->list[PhysicalTokenBlock]:虚拟块号是list[PhysicalTokenBlock]的index,物理块号是PhysicalTokenBlock.block_number
      通过这个链路,可以找到prompt的物理块。
class Counter:def __init__(self, start: int = 0) -> None:self.counter = startdef __next__(self) -> int:i = self.counterself.counter += 1return idef reset(self) -> None:self.counter = 0self.seq_counter = Counter()
seq_id = next(self.seq_counter)
seq = Sequence(seq_id, processed_inputs, block_size, eos_token_id, lora_request, prompt_adapter_request)class BlockSpaceManagerV1(BlockSpaceManager):def allocate(self, seq_group: SequenceGroup) -> None:wait_seqs = seq_group.get_seqs(status=SequenceStatus.WAITING)seq = wait_seqs[0] # 一个prompt->SequenceGroup->若干Sequence,因此Sequence们的KV缓存物理块分配是完全相同的,# 只需要为第一个序列(wait_seqs[0])分配物理块,其他序列通过引用共享这些块即可。block_table: BlockTable = \self._allocate_sequence(seq,seq_group.num_seqs(),is_encoder_decoder)# Assign the self-attention block tables for each sequence.if len(wait_seqs) == 1:self.block_tables[seq.seq_id] = block_tableelse:for seq in wait_seqs:self.block_tables[seq.seq_id] = block_table.copy()def _allocate_sequence(self, \seq: Optional[Sequence], \ref_count: int, \is_encoder_decoder: bool = True) -> BlockTable:num_prompt_blocks = self._get_seq_num_required_blocks(seq) # seq需要的block数量block_table: BlockTable = BlockTable()assert seq is not Nonefor logical_idx in range(num_prompt_blocks):if (self.block_sliding_window is not Noneand logical_idx >= self.block_sliding_window):block = block_table[logical_idx % self.block_sliding_window]# Set the reference counts of the token blocks.block.ref_count = ref_countelse:block = self.gpu_allocator.allocate() # self.gpu_allocator: BlockAllocatorBase = CachedBlockAllocator()block.ref_count = ref_countblock_table.append(block) # 见BlockTable.append,往BlockTable中增加一个block与其物理块号return block_table # block_table中是  一个sequence所需的 所有物理块class CachedBlockAllocator(BlockAllocatorBase):def allocate(self,block_hash: Optional[int] = None,num_hashed_tokens: int = 0) -> PhysicalTokenBlock:if block_hash in self.cached_blocks:self.cache_metric_data.query(hit=True)else:self.cache_metric_data.query(hit=False)self.cached_blocks[block_hash] = self.allocate_block(block_hash, num_hashed_tokens)block = self.cached_blocks[block_hash]assert block.block_hash == block_hashblock.ref_count += 1return block # 返回新建的PhysicalTokenBlockdef allocate_block(self, block_hash: int,num_hashed_tokens: int) -> PhysicalTokenBlock:if self.current_num_blocks == self.num_blocks:block = self.evictor.evict()block.block_hash = block_hashblock.num_hashed_tokens = num_hashed_tokensreturn blockblock = PhysicalTokenBlock(device=self.device,block_number=self.current_num_blocks, # 物理块号block_size=self.block_size,block_hash=block_hash,num_hashed_tokens=num_hashed_tokens)self.current_num_blocks += 1 #物理块号 自加1return block # 返回一个新建的PhysicalTokenBlockclass PhysicalTokenBlock:def __init__(self,device: Device,block_number: int,block_size: int,block_hash: int,num_hashed_tokens: int,) -> None:self.device = deviceself.block_number = block_numberself.block_size = block_sizeself.block_hash = block_hashself.num_hashed_tokens = num_hashed_tokensself.ref_count = 0self.last_accessed = DEFAULT_LAST_ACCESSED_TIMEself.computed = Falseclass BlockTable:def __init__(self, blocks: Optional[List[PhysicalTokenBlock]] = None):self._blocks: List[PhysicalTokenBlock] = [] # PhysicalTokenBlock的listself._block_ids: List[int] = [] # PhysicalTokenBlock.block_number的list,物理块号if blocks is not None:for block in blocks:self.append(block)def append(self, block: PhysicalTokenBlock):self._blocks.append(block)self._block_ids.append(block.block_number)

3.5、executor

在vllm/executor,有若干相关联的文件与类:
./executor_base.py:class ExecutorBase(ABC)
./gpu_executor.py:class GPUExecutor(ExecutorBase)
./distributed_gpu_executor.py:class DistributedGPUExecutor(GPUExecutor)
./multiproc_gpu_executor.py:class MultiprocessingGPUExecutor(DistributedGPUExecutor)
./ray_gpu_executor.py:class RayGPUExecutor(DistributedGPUExecutor)

它们的继承关系是:
ExecutorBase(ABC)
├── GPUExecutor
│ ​​​ ​​ ​​├── DistributedGPUExecutor
│ ​​  ​​│ ​​  ​​├── MultiprocessingGPUExecutor
│ ​​  ​​│ ​​  ​​├── RayGPUExecutor

这里有两个抽象类(ExecutorBase、DistributedGPUExecutor),三个可实例化的类(GPUExecutor、MultiprocessingGPUExecutor、RayGPUExecutor),它们都包含了如下功能:模型初始化(_init_executor)、KV缓存管理(determine_num_available_blocks, initialize_cache)、模型执行(execute_model)、LoRA和Prompt Adapter管理、健康检查(check_health)。

它们的区别与联系在于:

  • ExecutorBase: 抽象基类,定义了Executor的接口,包含基本配置信息(ModelConfig, CacheConfig等),所有方法都是抽象方法,需要子类实现

  • GPUExecutor: 基础GPU执行器,单设备版本,直接与GPU设备交互,使用GPUWorker作为底层工作器,处理特定的GPU硬件设置,单进程执行模型

  • DistributedGPUExecutor: 分布式GPU执行器的抽象基类,增加了对多设备并行执行的支持,引入了parallel_worker_tasks管理并行任务,提供了_run_workers抽象方法用于在多个worker上执行操作

  • MultiprocessingGPUExecutor: 基于Python多进程的分布式GPU执行器,使用ProcessWorkerWrapper封装worker进程,通过WorkerMonitor监控worker进程状态,实现了_run_workers方法,使用多进程通信

  • RayGPUExecutor: 基于Ray的分布式GPU执行器,使用Ray进行分布式执行,支持Ray的placement group进行资源调度,可选使用Ray的编译DAG优化执行,实现了_compiled_ray_dag方法构建优化执行图,实现更复杂的worker管理和通信机制

Ray版本提供了最强大的分布式能力,而MultiprocessingGPUExecutor版本适合单机多卡场景,基础GPUExecutor则用于最简单的单卡情况。

3.6、worker

在vllm源代码vllm/vllm/worker/中 有worker.py(默认GPU)、openvino_worker.py、tpu_worker.py、xpu_worker.py等worker。
这些worker均继承自 LoraNotSupportedWorkerBase 和 LocalOrDistributedWorkerBase,提供统一的 Worker 接口(如 execute_worker、prepare_worker_input),通过 model_runner封装模型执行逻辑, 支持张量并行(Tensor Parallelism)和流水线并行(Pipeline Parallelism),通过 parallel_config 配置。

3.7、model runner

在vllm源代码vllm/vllm/worker/中 有model_runner.py、multi_step_model_runner.py、openvino_model_runner.py、tpu_model_runner.py、xpu_model_runner.py等不同硬件的model_runner。

这些model_runner的基础功能:都实现了 ModelRunnerBase 抽象基类,提供了模型运行的基本功能,包括模型加载、输入准备和执行模型推理,都使用了AttentionMetadata 和 get_attn_backend 来处理注意力机制,都实现了对输入序列的批处理逻辑,包括填充(padding)和对齐,都支持KV缓存的维护和使用,都区分了prompt(预填充)和decode(解码)两种不同的处理模式。

3.8、model loader与lora

model loader:
vllm/vllm/model_executor/model_loader/loader.py 中
主要是class BaseModelLoader(ABC)与class DefaultModelLoader(BaseModelLoader),以及若干继承了BaseModelLoader的model loader,在抽象基类BaseModelLoader中,只需要实现def load_model即可。

这些model loader的功能目标相同,均用于加载神经网络模型(返回nn.Module),支持配置模型、设备、并行策略等参数,均调_initialize_model初始化模型,并通过load_weights加载权重,输入参数也完全一致(model_config、device_config等)。

lora:

Low-Rank Adaptation低秩适配器,在model loader加载权重时参与,核心是用两个低秩矩阵相乘得到不同的高秩矩阵,在实际调用时判断使用何种不同的矩阵去修改基础权重,得到不同的效果,可支持不同的业务。
lora通过API层(如/v1/load_lora_adapter)或LLM.generate()传入LoRARequest。
Engine层​​接收LoRA请求并传递给Scheduler,Scheduler通过BlockManager请求分配显存块,封装成WorkerInput,Worker在forward时,将LoRA参数与基础模型权重结合:
W′ =W+BA,其中,B∈R^{d×r} ,A∈R^{r×k} (r≪d,k为低秩维度)。
这种做法仅需加载基础模型一次,多个轻量级适配器(<2%参数量)共享显存,资源高效,而且​支持业务场景多样化(如客服、翻译等),每个任务独立适配器,无需全量微调。

vllm/vllm/lora/ops/lora_ops.py中主要有两类算子:
bgmv (batch-gather-matrix-vector) ,批聚合矩阵向量乘,适用 多请求并行处理。
sgmv (sequence-gather-matrix-vector),序列聚合矩阵向量乘,适用 单请求的长序列优化。
实现的算子是bgmv_expand、sgmv_expand、bgmv_shrink、sgmv_shrink等.

LoRA通过​​动态参数适配​​,在vLLM架构中显著提升多任务效率,LoRA依赖Executor和Worker的协同,侧重资源复用,提升了大模型的生产力上限。

3.9、models

在vllm/vllm/model_executor/models/中添加各自大模型的组装文件,如llama.py、qwen2.py、deepseek.py等文件,使用下述attention与相关layers,使用pytorch,调用cuda后端与算子,组装完成各种目标模型,最后运行在gpu硬件上。这一步,可以视为一个正常地使用pytorch搭建模型的过程。

3.10、layers与attention

layer层的关键经典算子:
vllm/vllm/model_executor/layers/fused_moe/fused_moe.py
vllm/vllm/model_executor/layers/rotary_embedding.py
vllm/vllm/model_executor/layers/layernorm.py
vllm/vllm/model_executor/layers/quantization/gptq.py
vllm/vllm/model_executor/layers/linear.py
vllm/vllm/attention/backends/flash_attn.py
其它是一些流程化的代码,如forward、create_weights、apply、weight_loader等过程。

Attention要单独拿出来说明,原始代码中Attention相关有如下几个文件:
vllm/vllm/attention/backends/abstract.py
vllm/vllm/attention/ops/paged_attn.py
vllm/vllm/attention/selector.py
vllm/vllm/attention/layer.py

  • 抽象层 (abstract.py): 规定所有Attention实现必须提供的接口,定义AttentionBackend(后端实现的工厂接口)、AttentionImpl(定义forward与其接口)、AttentionMetadata(管理注意力计算的元数据)等抽象基类。
  • ​ ​适配器层 (layer.py): ​承上启下​​,屏蔽底层硬件的差异,同时为上层提供统一的Attention接口,将abstract.py定义的接口转换为具体后端(如pagedattn.py)可调用的形式。
  • 具体实现 (pagedattn.py): ​实现PagedAttention算法,包括KV缓存分块管理​​,维护逻辑块到物理块的映射表,以及优化非连续内存的矩阵乘。
  • 后端选择 (selector.py): ​根据硬件(优先选择硬件原生支持的后端)和模型参数(如head_size、dtype)动态选择最优后端。

3.11、ops与kernels

使用pytorch中已实现的ops,可以尝试自己写更高性能的cuda算子。

3.12、sampleout(spec_decode)

http://www.dtcms.com/a/319910.html

相关文章:

  • 【论文阅读】基于元模型的体系知识图谱构建
  • spring boot学习计划
  • 什么是AI Agents
  • 机器学习算法篇(四)决策树算法
  • XCZU19EG-2FFVB1517I FPGA Xilinx AMD ZynqUltraScale+ MPSoC
  • 如何验证Go代理是否设置成功?
  • 深入探索C++模板实现的单例模式:通用与线程安全的完美结合
  • SpringBoot的优缺点
  • MyBatis 操作数据库
  • Orange的运维学习日记--33.DHCP详解与服务部署
  • Linux 系统启动、systemd target 与 root 密码重置指南
  • vector模拟实现
  • Seelen UI:高效的设计与原型制作工具
  • 解决winform中的listbox实现拖拽时,遇到combox控件会闪烁的问题
  • APM-SigNoz可观测性系统搭建
  • TDengine IDMP 文档介绍
  • 密集场所漏检率↓78%!陌讯自适应多模态口罩识别算法实战解析
  • 【bioinfo】ncbiRefSeq数据库下载
  • 零基础-动手学深度学习-9.1. 门控循环单元(GRU)及代码实现
  • 解决 npm i node-sass@4.12.0 安装失败异常 npm i node-sass异常解决
  • 如何使用 pnpm创建Vue 3 项目
  • 玳瑁的嵌入式日记D14-0807(C语言)
  • 蓝凌EKP产品:列表查询性能优化全角度
  • C++引用专题(上):详解C++传值返回和传引用返回
  • JavaScript核心概念解析:从基础语法到对象应用
  • 部署 AddressSanitizer(ASan)定位内存泄漏、内存越界
  • Java+Vue合力开发固定资产条码管理系统,移动端+后台管理,集成资产录入、条码打印、实时盘点等功能,助力高效管理,附全量源码
  • 【保姆级喂饭教程】python基于mysql-connector-python的数据库操作通用封装类(连接池版)
  • SPI TFT全彩屏幕驱动开发及调试
  • Sentinel原理之责任链详解