【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)
  • 滑動窗口
    • 滑動窗口模型的核心思想是:每個序列只能使用固定數量的KV緩存塊(窗口大小),當序列長度超過窗口大小時,舊的KV緩存塊會被循環重用,這樣可以限制每個序列的內存使用量,而不會隨著序列長度無限增長。
    • BlockSpaceManagerV1.block_sliding_window實際窗口大小會向上取整為block_size的整數倍。
    • 當邏輯索引超過窗口大小時,會循環重用之前的block,確保每個序列使用的block數量不超過窗口大小。
    • 由于block是循環重用的,釋放時只需要釋放窗口內的block,使用set去重避免多次釋放同一個block。
    • 滑動窗口模型用于優化長序列處理的一種技術,它通過限制每個序列可以使用的KV緩存塊數量來減少內存占用。

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)

  • Speculative decoding推測解碼(也有稱投機采樣),讓小型草稿模型推理出較多的推測性回答,提升解碼速度,再讓大模型驗證是否正確,在保證準確性的情況下提升效率。

  • 通過LLM初始化時指定speculative_model和num_speculative_tokens,Executor層??的SpecDecodeWorker協調草案模型(Proposer)與主模型(Scorer)的執行,SpecDecodeWorker調用輕量級草案模型(如OPT-125M)快速生成候選token序列(如5個token),??候選序列通過RejectionSampler比較概率分布,接受連續匹配的token,若首個token被拒絕,回退到原始自回歸解碼;否則繼續驗證后續token,通過并行生成-驗證減少解碼步數,吞吐量提升30%-50%(尤其短序列)。

  • vllm/vllm/spec_decode/spec_decode_worker.py:class SpecDecodeWorker(LoraNotSupportedWorkerBase)
    vllm/vllm/spec_decode/multi_step_worker.py:class MultiStepWorker(Worker, ProposerWorkerBase)
    vllm/vllm/spec_decode/draft_model_runner.py:class TP1DraftModelRunner(ModelRunner)
    vllm/vllm/spec_decode/target_model_runner.py:class TargetModelRunner(ModelRunner)
    流程:使用draft模型生成候選token->使用target模型驗證這些token->接受或拒絕候選token。

  • SpecDecode通過并行解碼優化??,在vLLM架構中顯著提升推理速度,SpecDecode依賴Executor和Worker的協同,側重計算加速,擴展了大模型的生產力邊界,長文本生成延遲降低20%-30%。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92474.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92474.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92474.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

什么是 TcpCommunicationSpi

&#x1f9e9; 一、核心定位&#xff1a;什么是 TcpCommunicationSpi&#xff1f; /*** <tt>TcpCommunicationSpi</tt> is default communication SPI which uses* TCP/IP protocol and Java NIO to communicate with other nodes.*/翻譯&#xff1a;TcpCommunicat…

【NLP輿情分析】基于python微博輿情分析可視化系統(flask+pandas+echarts) 視頻教程 - 詞云圖-微博評論用戶詞云圖實現

大家好&#xff0c;我是java1234_小鋒老師&#xff0c;最近寫了一套【NLP輿情分析】基于python微博輿情分析可視化系統(flaskpandasecharts)視頻教程&#xff0c;持續更新中&#xff0c;計劃月底更新完&#xff0c;感謝支持。今天講解詞云圖-微博評論用戶詞云圖實現 視頻在線地…

數據結構----棧和隊列認識

目錄 棧&#xff08;后進先出&#xff09; 棧的實現 頭文件 初始化 入棧 注意&#xff1a; bool 判空 出棧----棧頂 注意 出棧頂元素&#xff0c;元素不會刪除 注意&#xff1a; 獲取棧中有效個數 銷毀棧 源文件操作 用棧實現遞歸* 隊列&#xff08;先進先出&a…

【Kafka系列】第二篇| Kafka 的核心概念、架構設計、底層原理

在大數據和分布式系統飛速發展的今天&#xff0c;消息隊列作為高效的通信工具&#xff0c;在系統解耦、異步通信、流量削峰等方面作用顯著。 而 Kafka 這款高性能、高吞吐量的分布式消息隊列&#xff0c;在日志收集、數據傳輸、實時計算等場景中應用廣泛。 接下來&#xff0c;我…

Kafka-exporter采集參數調整方案

#作者&#xff1a;張桐瑞 文章目錄1 問題概述2 修改方案2.1修改參數2.2配置示例3 消費者組均分腳本3.1使用說明3.2腳本內容3.3實現原理說明4 KAFKA-EXPORTER流程代碼4.1KAFKA-EXPORTER拉取數據流程1 問題概述 由于kafka-exporter獲取kafka指標時間過長&#xff0c;無法通過cur…

AT32的freertos下modbus TCP移植

1.準備模板 打開雅特力官網&#xff0c;也就是帶有LwIP的示例。 下載官方源碼&#xff1a;modbus 2.移植 我這里是在這里新建兩個文件夾&#xff0c;分別是modbus與port&#xff0c;這個任意&#xff0c;只需要將必要的文件加入項目即可。 將源碼中的modbus這些都移植過來&a…

Redis面試精講 Day 16:Redis性能監控與分析工具

【Redis面試精講 Day 16】Redis性能監控與分析工具 開篇 歡迎來到"Redis面試精講"系列第16天&#xff0c;今天我們將深入探討Redis性能監控與分析工具。在大型分布式系統中&#xff0c;Redis作為關鍵的數據存儲和緩存組件&#xff0c;其性能指標直接影響整個系統的…

vue3+vue-flow制作簡單可拖拽可增刪改流程圖

實現效果實現代碼 準備工作 安裝依賴 npm install vue-flow/core npm install vue-flow/minimap //小地圖 npm install vue-flow/controls //自帶的縮放、居中、加鎖功能我這里只用到上述三個&#xff0c;還有其余的可根據實際情況配合官方文檔使用。 npm install vue-flow/bac…

itextPdf獲取pdf文件寬高不準確

正常情況下我們通過下面方式獲取寬高PdfReader reader new PdfReader(file.getPath()); float width reader.getPageSize(1).getWidth(); float height reader.getPageSize(1).getHeight();但是這樣獲取的寬高是不準確的&#xff0c;永遠都是 寬 > 高&#xff0c;也就是橫…

NodeJs學習日志(2):windows安裝使用node.js 安裝express,suquelize,mysql,nodemon

windows安裝使用node.js 安裝express&#xff0c;suquelize&#xff0c;mysql&#xff0c;nodemon 系統是win10&#xff0c;默認已經安裝好nodejs與npm包名作用expressWeb應用框架suquelize數據庫ORMmysql數據庫nodemon代碼熱重載安裝express 添加express生成器 npm add expres…

VueCropper 圖片裁剪組件在Vue項目中的實踐應用

VueCropper 圖片裁剪組件在Vue項目中的實踐應用 1. 組件介紹 VueCropper 是一個基于 Vue.js 的圖片裁剪組件&#xff0c;它提供了豐富的圖片裁剪功能&#xff0c;包括&#xff1a; 圖片縮放、旋轉、移動固定比例裁剪高質量圖片輸出多種裁剪模式選擇 2. 安裝與引入 首先需要安裝…

給同一個wordpress網站綁定多個域名的實現方法

在WordPress網站上綁定多個域名&#xff0c;可以通過以下幾種方法實現&#xff1a; 1. 修改wp-config.php文件 在wp-config.php文件中&#xff0c;找到define(‘WP_DEBUG’, false);&#xff0c;在其下方添加以下代碼&#xff1a; define(WP_SITEURL, http:// . $_SERVER[HT…

HarmonyOS分布式開發實戰:打造跨設備協同應用

&#x1f4d6; 文章目錄 第一章&#xff1a;HarmonyOS分布式架構揭秘第二章&#xff1a;跨設備協同的核心技術第三章&#xff1a;開發環境搭建與配置第四章&#xff1a;實戰項目&#xff1a;智能家居控制系統第五章&#xff1a;數據同步與狀態管理第六章&#xff1a;性能優化與…

用 Enigma Virtual Box 把 Qt 程序壓成單文件 EXE——從編譯、收集依賴到一鍵封包

關鍵詞&#xff1a;Qt、windeployqt、Enigma Virtual Box、單文件、綠色軟件 為什么要打成單文件&#xff1f; 傳統做法&#xff1a;用 windeployqt 把依賴拷進 release 目錄&#xff0c;發給用戶一個文件夾&#xff0c;文件又多又亂。理想做法&#xff1a;把整個目錄壓成一個…

unity中實現選中人物腳下顯示圓形標識且完美貼合復雜地形(如彈坑) 的效果

要實現人物腳下圓形 完美貼合復雜地形&#xff08;如彈坑&#xff09; 的效果&#xff0c;核心思路是 「動態生成貼合地面的 Mesh」 —— 即根據地面的高度場實時計算環形頂點的 Y 坐標&#xff0c;讓每個頂點都 “貼” 在地面上。核心邏輯&#xff1a;確定環形范圍&#xff1a…

引領GameFi 2.0新范式:D.Plan攜手頂級財經媒體啟動“龍珠創意秀”

在GameFi賽道尋求新突破的今天&#xff0c;一個名為Dragonverse Plan&#xff08;D.Plan&#xff09;的項目正以其獨特的經濟模型和宏大愿景&#xff0c;吸引著整個Web3社區的目光。據悉&#xff0c;D.Plan即將聯合中文區頂級加密媒體金色財經與非小號&#xff08;Feixiaohao&a…

通信算法之307:fpga之時序圖繪制

時序圖繪制軟件 一. 序言 在FPGA設計過程中&#xff0c;經常需要編寫設計文檔&#xff0c;其中&#xff0c;不可缺少的就是波形圖的繪制&#xff0c;可以直接截取Vivado或者Modelsim平臺實際仿真波形&#xff0c;但是往往由于信號雜亂無法凸顯重點。因此&#xff0c;通過相應軟…

計網學習筆記第3章 數據鏈路層(灰灰題庫)

題目 11 單選題 下列說法正確的是______。 A. 路由器具有路由選擇功能&#xff0c;交換機沒有路由選擇功能 B. 三層交換機具有路由選擇功能&#xff0c;二層交換機沒有路由選擇功能 C. 三層交換機適合異構網絡&#xff0c;二層交換機不適合異構網絡 D. 路由器適合異構網絡&…

SQL的LEFT JOIN優化

原sql&#xff0c;一個base表a,LEFT JOIN三個表抽數 SELECT ccu.*, ctr.*, om.*, of.* FROM ods.a ccu LEFT JOIN ods.b ctr ON ccu.coupon_code ctr.coupon_code AND ctr.is_deleted 0 LEFT JOIN ods.c om ON ctr.bill_code om.order_id AND om.deleted 0 LEFT JOIN ods.…

Redis 核心概念、命令詳解與應用實踐:從基礎到分布式集成

目錄 1. 認識 Redis 2. Redis 特性 2.1 操作內存 2.2 速度快 2.3 豐富的功能 2.4 簡單穩定 2.5 客戶端語言多 2.6 持久化 2.7 主從復制 2.8 高可用 和 分布式 2.9 單線程架構 2.9.1 引出單線程模型 2.9.2 單線程快的原因 2.10 Redis 和 MySQL 的特性對比 2.11 R…