找回密码
 立即注册
首页 业界区 业界 基于 nano-vLLM 学习大模型推理关键功能

基于 nano-vLLM 学习大模型推理关键功能

栓汨渎 2 小时前
注:本文已于2025.12.31 发表于知乎和公众号
1. 背景

如果要向一位完全不了解大模型推理技术的开发者介绍这个领域,我应该从哪里讲起?大模型推理的最简流程可以概括为:输入一串文本 → 文本通过词典映射表转换成一串数字序号 → 序号再经过 embedding 层的计算,变成一组能代表语义的浮点数向量 → 这组向量送入推理系统,经过层层的矩阵乘法、加法和各类专用函数的运算,得到新的输出向量 → 对输出向量做概率筛选,选出概率最高的那个数值对应的序号 → 最后再通过词典映射表 “翻译” 回文字,得到最终输出的一个词。
1.jpeg
图 1 这是对大模型推理最朴素的理解,上述流程看似简单,但背后的推理计算环节对普通开发者而言仍是一个 “黑盒”。如果想更进一步拆解推理引擎的底层加速原理,nano-vllm 会是一个极佳的入门切入点。2. 简介

nano-vLLM 代码量仅约 1200 行,却实现了生产级推理框架的核心技术原型,具体包括:

  • 连续批处理(Continuous Batching)
  • KV 缓存(Prefix KV Cache / Paged KV Cache)
  • 高性能编译与执行优化(Torch Compilation、Triton、CUDA Graph)
  • 张量并行(Tensor Parallelism)
该框架极具入门学习价值,本文将先介绍 nano-vLLM 的基本组成架构,再对部分核心技术要点展开深入解析。3. 系统架构

nano-vLLM 的架构非常有层次感。3.1. 整体架构概览

2.jpeg
图 2, 来自:https://deepwiki.com/GeeeekExplorer/nano-vllm三层结构

  • 接口层:User Interface Layer
  • 推理引擎中控层:Inference Engine Layer
  • 显存管理和模型执行层:Memory Management & Model Execution Layer
3.2. 类层面架构

从类设计层面观察 nano-vLLM 的架构。
3.jpeg
  图 3上图中四种颜色代表系统的四个组成部分

  • 浅蓝色,入口和推理引擎中控层
  • 浅绿色,模型推理
  • 浅红色,KV Cache 管理
  • 浅紫色,权重加载和矩阵计算的封装
3.3. 码层面划分

源码规划上也较为简洁。目录结构如下:
  1. nanovllm/
  2. ├── engine
  3. ├── layers
  4. ├── models
  5. └── utils
复制代码

  • engine,引擎的入口、中控,同时 KV Cache 比较简单,代码也放在这个目录下。
  • layers,模型推理的通用组件,内部包括:linear、layernorm、rotary_embedding、attention、activation 等基础功能的封装,可以被不同模型使用。
  • models,模型的实现,依赖 layers 的组件实现不同模型的推理。
  • utils,不同层都可能会用到的工具函数。
4. 连续批处理

4.1. 概念理解

(1)定义连续批处理 (Continuous Batching):是一种迭代级(Iteration-level)的调度策略。它以“Token 生成步骤”为调度粒度。通过动态地在每一轮迭代中替换已完成的任务,消除了由于生成长度不一导致的 GPU 计算气泡,极大地提升了系统的吞吐量。(2)朴素理解一个请求需要执行多轮,不同请求需要执行的轮数不同,系统一轮最多只能同时执行一批 N 个请求,当一个批次里的请求参差不齐的完成时,每完成一个请求就将其用新请求替代掉。对比传统批处理和连续批处理:

  • 传统批处理 (Static Batching):必须等待 Batch 中生成序列最长的那个请求完成,整个 Batch 才会释放。在此期间,生成序列短请求完成后槽位会空转。
  • 连续批处理 (Continuous Batching):请求完成即退出,新请求立即补位,槽位始终满载。
4.2. 最基础的连续批处理

最简单的连续批处理,不考虑 prefill 和 decode 的差异,示例代码:[code]import timeimport threadingimport queueimport random# 1. 初始化线程安全的等待队列waiting_queue = queue.Queue()MAX_BATCH_SIZE = 3# --- 模拟用户请求线程 (生产者) ---def user_request_producer():    request_id = 1    while True:        # 模拟用户随机到达:每 1~2 秒来一个新请求        time.sleep(random.uniform(1, 2))                # 每个请求需要的 Token 长度随机(3到8之间)        req = {"id": f"REQ-{request_id}", "remain": random.randint(3, 8)}        waiting_queue.put(req)                print(f"\n[用户端] 送入新请求: {req['id']} (预计长度: {req['remain']})")        request_id += 1        if request_id > 5:          break# --- 核心推理循环 (消费者/执行器) ---def inference_loop():    running_batch = []        print("--- 推理引擎已启动 ---")    iteration = 0    while True:        # A. 补位逻辑:只要 Batch 没满且队列里有货,就拉进来        while len(running_batch) < MAX_BATCH_SIZE:            try:                # 使用 block=False,如果队列空了直接报错进 except,不阻塞推理逻辑                new_req = waiting_queue.get(block=False)                running_batch.append(new_req)                print(f"  >>> [调度] {new_req['id']} 进入 Batch")            except queue.Empty:                break        # B. 推理逻辑:如果当前 Batch 有任务,就执行一次 Step        if running_batch:            iteration += 1            print("="*20 + f"{iteration=}" + "="*20)            # 模拟 GPU 推理耗时 (Step 耗时)            time.sleep(1.2)                         # 当前 Batch 状态展示            active_ids = [f"{r['id']}(剩{r['remain']-1})" for r in running_batch]            print(f"[GPU推理] 处理中: {active_ids}")                        # 每一个请求的剩余长度减 1            finished_this_step = []            for req in running_batch:                req["remain"] -= 1                if req["remain"]

相关推荐

您需要登录后才可以回帖 登录 | 立即注册