Mini-SGLang 源码解析(五):注意力机制系统
学习文件:attention/base.py (66 行), attention/fa.py (187 行), attention/fi.py (279 行)
1. Attention Backend 的核心职责
Attention Backend 是整个推理系统的计算核心,负责执行 Attention 计算:
1.1 职责定位
输入:Q (Query)、K (Key)、V (Value) 三个矩阵
输出:Attention 输出(加权求和后的结果)
核心:实现高效的 Attention 计算
1.2 为什么需要抽象基类?
Mini-SGLang 支持多种 Attention 实现:
FlashAttention:适合 Prefill 阶段(计算密集)
FlashInfer:适合 Decode 阶段(访存密集)
但 Engine 只需要调用 attn_backend.forward(),不关心具体实现。
123456789# engine.pyclass Engine: def __init__(self, config): self.attn_ba ...
Mini-SGLang 源码解析(四):GPU 计算引擎系统
学习文件:engine/engine.py (209 行), engine/graph.py (145 行), engine/sample.py (76 行), models/base.py (15 行), models/llama.py (86 行)
1. Engine 的核心职责
Engine 是整个推理系统的数据平面,负责实际执行 GPU 计算:
1.1 职责定位
Scheduler:控制平面,决定"做什么"(调度哪些请求)
Engine:数据平面,执行"怎么做"(前向传播 + 采样)
1.2 核心功能
模型加载和权重管理
KV Cache 内存分配
前向传播执行
Token 采样
CUDA Graph 优化
跨 Stream 异步同步
2. Engine 初始化流程
2.1 初始化的 6 个步骤
12345678910111213141516171819202122232425262728def __init__(self, config: EngineConfig): # 1. 设置设备和通信 self.device = torch.devi ...
Mini-SGLang 源码解析(三):调度系统详细实现
学习目标
深入理解 Mini-SGLang 的调度系统,包括:
Scheduler 主调度器的核心逻辑
PrefillManager 的 Chunked Prefill 实现
DecodeManager 的请求管理
重叠调度 (Overlap Scheduling) 的原理
1. 调度系统整体架构
1.1 核心组件
调度系统由 5 个核心组件组成:
123456Scheduler (主调度器) ├─→ PrefillManager (Prefill 阶段调度) ├─→ DecodeManager (Decode 阶段调度) ├─→ CacheManager (缓存管理) ├─→ TableManager (页表管理) └─→ Engine (GPU 计算引擎)
1.2 数据流概览
123456789101112131415用户请求 (UserMsg) ↓PrefillManager.pending_list (待处理队列) ↓Scheduler._schedule_next_batch() (调度批次) ↓Scheduler._prepare_batch( ...
Mini-SGLang 源码解析(二):推理流程与多进程架构
本文深入分析 Mini-SGLang 的完整推理流程,探讨多进程架构、ZMQ 消息传递、流式反分词和重叠调度等核心技术。
环境:Mini-SGLang v0.1.0 | Python 3.10+ | PyTorch 2.0+
源码位置:python/minisgl/server/, python/minisgl/scheduler/, python/minisgl/tokenizer/
1. 背景:推理系统的架构挑战
LLM 推理系统需要平衡三个关键目标:
低延迟:用户体验要求快速响应(首 token 延迟 < 100ms)
高吞吐:服务端需要同时处理大量请求(> 1000 QPS)
资源利用:GPU 利用率需要保持在 80% 以上
Mini-SGLang 通过多进程架构 + 异步消息传递实现了这些目标。
2. 系统架构:3 进程模型
2.1 架构全景
1234567┌─────────────┐ ZMQ ┌─────────────┐ ZMQ ┌─────────────┐│ Frontend │ ────→ │ Tokenizer ...
Mini-SGLang 源码解析(一):核心数据结构与设计模式
本文深入分析 Mini-SGLang 的核心数据结构设计,探讨 LLM 推理系统的关键优化技术。
环境:Mini-SGLang v0.1.0 | Python 3.10+ | PyTorch 2.0+
源码位置:python/minisgl/core.py
1. 背景:LLM 推理的性能瓶颈
大语言模型推理面临两个核心挑战:
计算密集:Transformer 的自注意力机制复杂度为 O(n²)
内存密集:KV Cache 占用大量显存,成为推理瓶颈
Mini-SGLang 通过精心设计的数据结构和调度策略,在保持代码简洁(~5000 行)的同时,实现了接近 vLLM 的性能。
2. KV Cache:从 O(n²) 到 O(n) 的优化
2.1 问题定义
在自回归生成中,每生成一个新 token,都需要计算注意力:
123456# 标准 Transformer 注意力Q = x @ W_q # Query: [batch, seq_len, d_model]K = x @ W_k # Key: [batch, seq_len, d_model]V = x @ W_ ...
Mini-Infer (35): 插件架构实战 — 从旧架构到新架构的迁移
Mini-Infer (35): 插件架构实战 — 从旧架构到新架构的迁移
1. 迁移策略概述
从旧的 Operator + Kernel 架构迁移到新的 Plugin 架构,我们采用以下策略:
A. 保留底层原语
底层计算原语(im2col、gemm、bias、transpose)保持不变,它们是设备无关的数学操作:
1234567保留:├── kernels/cpu/gemm.cpp├── kernels/cpu/im2col.cpp├── kernels/cpu/bias.cpp├── kernels/cuda/gemm.cu├── kernels/cuda/im2col.cu└── kernels/cuda/bias.cu
B. 删除旧 Kernel 实现
旧的算子级 Kernel(如 Conv2DKernel、ReLUKernel)被删除,其逻辑移入 Plugin:
12345删除:├── kernels/cpu/conv2d_kernel.cpp → 移入 Conv2DCPUPlugin├── kernels/cpu/relu_kernel.cpp → 移入 ...
Mini-Infer (34): 插件架构 (下) — PluginRegistry 与自动注册
Mini-Infer (34): 插件架构 (下) — PluginRegistry 与自动注册
1. 问题背景:双层注册的痛点
在旧架构中,我们有两个独立的注册表:
1234// 旧架构OperatorFactory::register_operator("Conv2D", Conv2DOperatorCreator);KernelRegistry::register_kernel("Conv2D", CPU, Conv2DCPUKernel);KernelRegistry::register_kernel("Conv2D", CUDA, Conv2DCUDAKernel);
问题:
维护成本高:添加新算子需要修改两个地方。
一致性问题:Operator 和 Kernel 的注册可能不同步。
查找开销:执行时需要先查 Operator,再查 Kernel。
新架构:统一的 PluginRegistry,一次注册,直接使用。
2. PluginKey 设计
A. 二元组 Key
12345678910// mini_infer/operators/plugin_regis ...
Mini-Infer (33): 插件架构 (中) — CRTP 基类与静态多态
Mini-Infer (33): 插件架构 (中) — CRTP 基类与静态多态
1. CRTP 模式回顾
CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)是 C++ 中一种强大的设计模式,用于实现静态多态。
A. 基本形式
1234567891011121314template <typename Derived>class Base {public: void interface() { static_cast<Derived*>(this)->implementation(); }};class Derived : public Base<Derived> {public: void implementation() { // 具体实现 }};
B. 静态多态 vs 动态多态
特性
动态多态 (virtual)
静态多态 (CRTP)
绑定时机
运行时
编译时
虚函数表
需要
不需要
性能开销
有(间接调 ...
Mini-Infer (32): 插件架构 (上) — IPlugin 接口设计
Mini-Infer (32): 插件架构 (上) — IPlugin 接口设计
1. 问题背景:为什么需要插件架构?
在之前的 Mini-Infer 实现中,我们使用了 Operator + Kernel 双层抽象:
Operator:定义算子的元数据(输入/输出数量、形状推理)。
Kernel:实现具体的计算逻辑(CPU/CUDA)。
这种设计在早期工作良好,但随着功能增加,问题逐渐暴露:
A. 双层抽象的问题
12345Operator (元数据) ↓KernelRegistry (查找) ↓Kernel (计算)
维护成本高:添加新算子需要修改两个地方。
一致性问题:Operator 和 Kernel 的参数可能不同步。
查找开销:每次执行都需要从 Registry 查找 Kernel。
B. 多后端支持的复杂性
123// 旧架构:需要为每个后端注册 KernelREGISTER_KERNEL(Conv2D, CPU, Conv2DCPUKernel);REGISTER_KERNEL(Conv2D, CUDA, Conv2DCUDAKernel); ...
Mini-Infer (31): CUDA 后端支持 (下) — TensorRT 风格权重预加载
Mini-Infer (31): CUDA 后端支持 (下) — TensorRT 风格权重预加载
1. 问题背景:权重拷贝的性能瓶颈
在 GPU 推理中,一个常见的性能问题是权重拷贝开销。
A. PCIe 带宽限制
CPU 和 GPU 之间通过 PCIe 总线通信。即使是 PCIe 4.0 x16,理论带宽也只有约 32 GB/s,远低于 GPU 显存带宽(如 A100 的 2 TB/s)。
123CPU Memory ──PCIe──► GPU Memory ↑ 瓶颈所在
B. 首次推理延迟
如果每次推理都从 CPU 拷贝权重到 GPU:
123推理请求 → 拷贝权重 → 执行计算 → 返回结果 ↑ 额外延迟
对于一个 100MB 的模型,PCIe 拷贝可能需要 3-5ms,而实际计算可能只需要 1ms。
C. TensorRT 的解决方案
TensorRT 在 Build-Time 将权重加载到 GPU,Run-Time 直接使用:
12Build-Time: 解析模型 → 优化图 → ...
Mini-Infer (30): CUDA 后端支持 (中) — CUDADeviceContext 与异构执行环境
Mini-Infer (30): CUDA 后端支持 (中) — CUDADeviceContext 与异构执行环境
1. DeviceContext 抽象回顾
在 Mini-Infer 的架构中,DeviceContext 是执行环境的抽象基类:
12345678910111213141516// mini_infer/backends/device_context.hclass DeviceContext {public: virtual ~DeviceContext() = default; /** * @brief 获取设备类型 */ virtual core::DeviceType device_type() const = 0; /** * @brief 同步设备(等待所有操作完成) */ virtual void synchronize() = 0;};
对于 CPU,CPUDeviceContext 的实现非常简单(几乎是空的)。但对于 CUDA,我们需要管理更多资源。
2. CUDADeviceCo ...
Mini-Infer (29): CUDA 后端支持 (上) — CUDAAllocator 与显存管理
Mini-Infer (29): CUDA 后端支持 (上) — CUDAAllocator 与显存管理
1. 问题背景:CPU 与 GPU 内存管理的差异
在之前的实现中,Mini-Infer 只支持 CPU 推理。现在我们要添加 CUDA 后端支持,首先需要解决的就是 GPU 显存管理。
CPU 和 GPU 内存管理有几个关键差异:
特性
CPU 内存
GPU 显存
分配函数
malloc / aligned_alloc
cudaMalloc
释放函数
free
cudaFree
分配开销
较低
较高(需要驱动调用)
默认对齐
通常 16 字节
256 字节
访问方式
直接访问
需要 Kernel 或 cudaMemcpy
错误处理
返回 nullptr
返回 cudaError_t
为了统一管理,我们需要一个抽象的 Allocator 接口,以及针对 CUDA 的具体实现。
2. Allocator 抽象接口回顾
12345678910111213141516171819202122232425// mini_infer/core/ ...
Mini-Infer (28): Core 数据结构优化 — Storage 与 Tensor 分离
Mini-Infer (28): Core 数据结构优化 — Storage 与 Tensor 分离
1. 问题背景:为什么 Tensor 需要与 Storage 分离?
在早期的 Mini-Infer 实现中,Tensor 类直接持有数据指针和分配器。这种设计在简单场景下工作良好,但随着功能的增加,问题逐渐暴露:
A. 内存池复用的需求
静态内存规划(Blog 23)要求多个 Tensor 共享同一块预分配的内存。如果 Tensor 直接持有数据指针,就无法优雅地实现这种共享。
123456┌─────────────────────────────────────────────────────┐│ Shared Memory Pool │├─────────┬─────────┬─────────┬─────────┬─────────────┤│ Tensor A│ Tensor B│ Tensor C│ Tensor D│ ... ││ offset=0│offset=1K│offset= ...
Mini-Infer (27): 运行时架构重构 (下) — ExecutionContext 与零拷贝执行
Mini-Infer (27): 运行时架构重构 (下) — ExecutionContext 与零拷贝执行
1. ExecutionContext 的职责定位
在上一篇中,我们介绍了 InferencePlan 作为不可变的构建产物。本篇我们来看它的"运行时伙伴"——ExecutionContext。
ExecutionContext 是每次推理请求的可变状态容器,它负责:
内存池管理:持有实际的内存缓冲区。
中间张量存储:存储每个节点的输出激活值。
设备上下文:管理 CPU/CUDA 执行环境。
动态形状推理:在输入形状变化时重新推导。
核心设计原则:
InferencePlan 是共享的,多个 Context 可以引用同一个 Plan。
ExecutionContext 是独占的,每个推理请求一个 Context。
这种分离使得并发推理成为可能。
2. 初始化流程 (initialize)
A. 类定义
123456789101112131415161718192021222324// mini_infer/runtime/execution_context.h ...
Mini-Infer (26): 运行时架构重构 (上) — InferencePlan 与 Build-Time 优化
Mini-Infer (26): 运行时架构重构 (上) — InferencePlan 与 Build-Time 优化
1. 为什么需要分离 Build-Time 和 Run-Time?
在之前的架构中,Engine 类承担了太多职责:图的构建、优化、内存规划、执行上下文管理、推理执行……这种"大一统"的设计在简单场景下工作良好,但随着功能的增加,问题逐渐暴露:
线程安全问题:多个推理请求共享同一个 Engine 实例时,状态管理变得复杂。
资源浪费:每次推理都需要重新准备某些"不变"的数据结构。
扩展困难:想要支持多 Context 并发推理时,现有架构难以适应。
TensorRT 的解决方案是将推理过程分为两个阶段:
Build-Time (构建期):解析模型、优化图、规划内存、预加载权重。产物是一个不可变的 ICudaEngine。
Run-Time (运行期):基于 Engine 创建 IExecutionContext,每个 Context 持有自己的中间张量和状态。
这种分离带来的好处是:
Engine 可以被多个 Context 共享,节省内存。
Cont ...
Mini-Infer (25): 动态形状的基石 — `OptimizationProfile` 设计与实现
Mini-Infer (25): 动态形状的基石 — OptimizationProfile 设计与实现
1. 核心理念:Min, Opt, Max 三位一体
对于任何一个动态输入(比如 input_0),我们不再只给它一个模糊的 -1,而是要求用户提供三个形状:
Min Shape: 允许的最小尺寸。
作用:边界检查。小于此尺寸的输入将被拒绝。
Opt Shape (Optimal): 最常用的尺寸。
作用:核心优化的依据。引擎会针对这个尺寸选择最优的算法(Kernel Selection)和执行调度策略。
Max Shape: 允许的最大尺寸。
作用:内存分配的依据。MemoryPlanner 会根据 Max Shape 来计算网络中所有中间 Tensor 的最大可能尺寸,并据此分配显存。这样,在运行期间只要输入不超过 Max,就永远不需要重新分配显存。
2. 代码实现:ShapeRange 与 OptimizationProfile
A. 形状范围 (ShapeRange)
这是 Profile 的基本单元。它封装了三个形状,并提供了校验逻辑。
1 ...





