鹤啸九天 自律更自由,平凡不平庸 Less is More

大模型推理框架:vLLM

2023-09-19
阅读量

Notes(温馨提示):

  1. ★ 首次阅读建议浏览:导航指南, 或划到本页末尾, 或直接点击跳转, 查看全站导航图
  2. 右上角工具条搜索文章,右下角二维码关注微信公众号(鹤啸九天),底栏分享、赞赏、评论
  3. ★ 转载请注明文章来源,知识点积累起来不容易,水滴石穿,绳锯木断,谢谢理解
  4. ★ 如有疑问,邮件讨论,欢迎贡献优质资料


vLLM

目前最主流的大模型推理部署框架 vLLM,核心创新是 PagedAttention,借鉴操作系统虚拟内存分页思想管理 KV Cache。

  • PagedAttention:把 KV Cache 切成固定大小的“页”,按需分配,彻底消除显存碎片,并发请求共享 KV Cache 成为可能。
  • Continuous Batching(连续批处理):不等一批请求全部完成再处理下一批,而是动态插入新请求,GPU 利用率从 30% 提升到 90%+。
  • Prefix Caching:相同前缀(如 System Prompt)的 KV Cache 自动复用,多轮对话场景首 token 延迟大幅降低。
  • Speculative Decoding(投机采样):用小模型草稿 + 大模型验证,在不损失精度的前提下提升生成速度。支持 OpenAI 兼容 API,一行命令启动,已成为企业私有化部署的事实标准。

介绍

vLLM(Vectorized Large Language Model Serving System)是加州大学伯克利分校团队推出的开源推理系统,突破大模型部署中显存利用率低与推理吞吐量受限的双重挑战

Meta 前 PyTorch 团队推出 vLLM,最大特色“PagedAttention”。

两项核心机制:‌PagedAttention‌(分页注意力)与‌Continuous Batching‌(连续批处理),二者均受操作系统内存分页机制启发,重构了注意力计算与请求调度的底层逻辑,从而在不牺牲精度的前提下,大幅优化了显存管理效率与并发推理能力。

  • PagedAttention:受操作系统分页管理机制启发,将注意力机制中的键值缓存(KV Cache)以非连续方式部署于显存中。相较传统框架为每个请求强制分配连续显存块的模式,vLLM 将 KV Cache 拆分为固定尺寸的“页”,实现显存空间的动态调度与高效复用,从而彻底缓解了显存碎片化、预留冗余与并发容量受限三大核心痛点。该架构使显存利用率由传统方案的 60% 显著跃升至 95% 以上,显著增强系统对高并发请求的承载能力。
    • PagedAttention:KV 缓存被划分为块;块在内存空间中不需要连续
  • Continuous Batching‌:突破传统批量等待机制,支持新请求实时插入处理队列,实现GPU资源的零空闲运行。该机制显著降低高并发场景下的TTFT(首字出词时间),在Llama3.1-170B-FP8单H100环境下,TTFT低至123ms,优于TensorRT-LLM(194ms)与SGLang(340ms)。

多卡并行优化‌:全面兼容张量并行(Tensor Parallelism)与流水线并行(Pipeline Parallelism),依托NCCL/MPI等高性能通信框架,实现模型参数的精细化切分与高效同步,在降低显存占用的同时,显著增强整体吞吐能力。

量化优化支持‌:原生集成GPTQ、AWQ等先进量化算法,精准压缩模型参数规模,大幅提升GPU计算密度与推理效率,实现性能与资源消耗的最优平衡。

【2025-12-13】DeepSeek倒逼vLLM升级!芯片内卷、MoE横扫千模,vLLM核心维护者独家回应:如何凭PyTorch坐稳推理“铁王座”

2023 年, 加州大学伯克利分校 Sky Computing Lab 学生与研究员开源核心的 PagedAttention 技术,vLLM

短短一年多, GitHub Star 数突破 4 万,并迅速增长至如今的 6.5 万,如今已成为全球科技公司首选的推理引擎。

2024 年 11 月,红帽正式收购 Neural Magic,并将包括 vLLM 核心维护者 Michael Goin 在内的核心团队纳入旗下。

vLLM(Very Large Language Model)专为大语言模型(LLM)优化的高效推理框架

其核心:PagedAttention、动态批处理pd并行、多LoRA动态加载等

PagedAttention 本质是灵活管理 KV Cache(注意力缓存)的方式。

  • 传统 transformer 推理时,每个 token 都带上历史上下文,缓存爆炸。
  • vLLM 通过类似虚拟内存分页方式,优化性能。像会打麻将的高效服务员,记牌记得巨快

好处

  • API 设计接近 OpenAI 标准接口,部署友好,尤其对做“OpenAI替代”的场景特别合适。
  • 高并发时不掉链子,响应速度几乎线性增长。

前提:GPU A100 起步。

vllm vs sglang

【2025-09-27】vllm:pd并行、多LoRA动态加载

vllm和sglang 部署分别:

  • vLLM 在高并发和低延迟场景下表现优异,尤其擅长快速生成第一个词(TTFT低)。
  • SGLang 在多轮对话这类前缀复用率高的场景中,吞吐量优势明显,测试显示其在Llama-7B上的吞吐量可比vLLM高5倍。

问题

vllm 默认占满显存

【2026-3-26】案例

  • vllm 加载 qwen-7b 模型,显存占到40G左右,但关掉vllm时占用17G
  • vllm 加载 qwen3-4b 模型,A6000,显存占41G!

显存计算公式:

  • 显存占用 ≈ KV Cache + 模型参数 + 激活值 + 临时buffer

$ [ \text{显存占用} \approx \text{KV Cache} + \text{模型参数} + \text{激活值} + \text{临时 buffer}] $

详见站内专题:GPU显存分析

gpu_memory_utilization 默认值为 0.9,GPU 内存的使用率为 90%。如果遇到显存不足的问题,可以降低该值。

  • KV 缓存:在自回归解码过程中,LLM 的所有输入标记会生成注意键和值张量,并保存在 GPU 内存中,这些缓存可能会占用大量显存。
  • vLLM 引入 PagedAttention 来有效管理 KV 缓存,这种机制允许在不连续内存空间中存储连续的键和值。

通过合理调整 gpu_memory_utilization 参数,可以在保证模型性能的同时,优化显存的使用。

调用代码

import vllm

model = vllm.LLM(
  model_path="/data/models/Meta-Llama-3-8B-Instruct",
  tensor_parallel_size=2,
  gpu_memory_utilization=0.15, # 设置显存利用率为 15%
  temperature=0.2,
  top_p=0.95,
  max_tokens=100
)

vllm 不支持 embedding的lora

【2025-2-4】vllm 不支持 embedding的lora功能!

It’s not supported yet according to the compatibility matrix. Let me update the supported models page to avoid this confusion. cc @jeejeelee

14953

undefined symbol

【2026-4-7】错误信息

import vllm._C  # noqa
    ^^^^^^^^^^^^^^
ImportError: /ofs/ese-llm-ssd/users/wangqiwen/envs/py312/lib/python3.12/site-packages/vllm/_C.abi3.so: undefined symbol: _ZN3c104cuda29c10_cuda_check_implementationEiPKcS2_ib

分析

  • 豆包:vllm 与 PyTorch/CUDA 版本不兼容

解法

  • 查看 pytorch、cuda和vllm版本
  • 对照 vllm 官方要求(关键):
    • vllm 0.4.x+ 需 PyTorch ≥ 2.0.0,CUDA ≥ 11.8
    • vllm 0.3.x 需 PyTorch ≥ 1.13.0,CUDA ≥ 11.7
  • 升级对应版本
# 查看当前 PyTorch 版本:
python -c "import torch; print(torch.__version__); print(torch.version.cuda)"
# 查看当前 vllm 版本:
uv pip list | grep vllm 
# 升级vllm: 0.16 -> 0.19
uv pip install vllm --upgrade

vLLM 框架

核心架构是LLMEngine:包含调度器(Scheduler)和推理工作器(Worker)

  • 调度器(Scheduler):
    • 调度策略(policy):
      • 请求的连续批处理(Continuous Batching)
      • 请求队列维护:waiting(待处理)、running(正在处理)、swapped(因显存不足被挂起)
    • 空间“调度指挥官”(BlockSpaceManager):负责逻辑资源规划与分配策略;
      • 计算kv cache需要的块数量
      • 判断是否需要为新token分配新的空间(如需则通知cacheengine)
      • 内存共享:例如在并行采样时,多个输出序列可共享同一prompt的KV缓存块
  • 推理工作器(Worker):
    • 模型运行:包括多GPU分布式计算(张量并行、流水线并行)
    • 空间“后勤执行者”(CacheEngine):负责物理显存的具体操作与数据存取。
  • 性能效果:
    • 吞吐:提升高达24倍
    • 耗时:提升1.2倍。

多 LoRA 动态加载

  • 无延迟切换:单基础模型(如Llama 3-8B)同时挂载多个LoRA适配器(如聊天/函数调用),请求时通过lora_request参数指定,切换延迟低于1ms。
  • 资源复用:基础模型参数仅加载一次,适配器共享显存,支持5+适配器并行服务。

与stable diffusion类似,vllm 也支持在请求调用时指定调用某个lora;但有两点差异:

  • vllm中的lora是一开始全部就加载进模型的,(sd是请求时才加载)
  • vllm中每次请求只能指定一个lora
# 同时加载两个适配器
vllm serve meta-llama/Meta-Llama-3-8B \
  --enable-lora \
  --lora-modules \
    oasst=/path/to/oasst_adapter \  # 名称 oasst
    xlam=/path/to/xlam_adapter     # 名称 xlam

PagedAttention 动态分页机制

分页注意力机制(PagedAttention)是什么?

  • 分页注意力机制借鉴了计算机操作系统中的内存分页管理,通过动态分配和复用显存空间,显著提升大模型推理的效率和吞吐量。

image

传统大模型推理中,注意力机制(Transformer的自注意力层)为每个请求序列分配连续的显存块,存储以下数据:

  • (1)键值缓存(Key-Value Cache,KV Cache):存储历史token的键值对,用于生成后续token。
  • (2)中间激活值:计算注意力权重时的中间结果。

vLLM基于PyTorch构建,创新性地引入了PagedAttention技术。借鉴操作系统的虚拟内存分页机制,将注意力键值对(KV Cache)存储在非连续显存空间,显著提高了显存利用率。

PagedAttention 通过分块管理显存动态按需分配跨请求共享内存,解决了传统方法中显存碎片化预留浪费并发限制三大瓶颈。

image

特点

  • 分块管理KV缓存:将注意力键值(Key-Value Cache)划分为固定大小的块(如4-16 tokens/块),按需动态分配GPU显存,避免传统连续分配导致的碎片问题。
  • 显存复用与共享:短序列推理时仅占用必要块,释放空间供其他请求使用;支持跨请求的缓存共享(如相同前缀提示词),显存利用率提升最高达25%。
  • 写时复制(Copy-on-Write):共享块标记为只读,修改时创建新副本,减少重复计算

原文

vllm 部署

安装

# 安装基础版本
pip install vllm

# 安装支持CUDA的版本
# pip install vllm-nightly # 无效,报错
pip install -U vllm \
    --torch-backend=auto \
    --extra-index-url https://wheels.vllm.ai/nightly

vllm 两种模型部署方式:

  • 在线服务形式(Online Serving)
    • 在线服务: 通过指令启动vllm服务,将模型以服务的形式完成部署,之后可以通过openai格式的api来访问模型
  • 离线推理形式 (Offline Inference)
    • 离线推理: 通过类方式初始化一个模型,之后传入提示词即可访问模型

vllm 使用

# 示例代码:生成文本
from vllm import LLM, SamplingParams

prompts = ["什么是vLLM?", "vLLM的优势是什么?"]
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
llm = LLM(model="meta-llama/Llama-2-7b-chat-hf")  # 可限制最大并发:max_num_seqs=2,使用FP16:dtype="float16"

outputs = llm.generate(prompts, sampling_params)  # 批量推理

for output in outputs:
    print(f"输入:{output.prompt}\n输出:{output.outputs[0].text}\n"2

服务开启和各参数作用

vllm 服务启动指令如下

vllm serve deepseek-ai/deepseek-vl2-tiny  \
 --hf_overrides '{"architectures": ["DeepseekVLV2ForCausalLM"]}' \
 --dtype float16   --trust_remote_code \
 --host 0.0.0.0 --port 8080 \
 --chat_template template_deepseek_vl2.jinja \
 --gpu-memory-utilization 0.7 \
 --limit-mm-per-prompt "image=8"

详见地址

bert 模型

除了生成式任务,但分类任务同样可以通过指定合适的任务类型实现。

【2025-5-1】vLLM项目中部署BERT

关键启动参数包括:

python -m vllm.entrypoints.api_server \
    --model path/to/bert_model \
    --tensor-parallel-size 1 \
    --dtype float16 \
    --max-model-len 512 \
    --served-model-name bert-cls

注意:

  • 最大序列长度应设置为BERT标准配置(通常512)
  • 半精度(float16)可显著提升推理速度
  • 服务名称用于后续API调用标识

分类任务可以采用以下调用方式:

import openai

client = openai.Client(base_url="http://localhost:8000/v1")
response = client.completions.create(
    model="bert-cls",
    prompt="这是一条需要分类的文本",
    max_tokens=1,  # 分类任务通常只需要1个token输出
    temperature=0  # 确保确定性输出
)

性能优化建议

  • 批处理优化:通过增大--max-batch-size参数提高吞吐量
  • 量化部署:尝试使用--quantization bitsandbytes进行8bit量化
  • 请求合并:客户端实现请求队列合并,减少小包传输
  • 监控集成:添加Prometheus监控指标采集

vLLM 分布式

资料

更多分布式计算知识,见站内专题:分布式训练理论

vLLM 并行方式

vLLM 并行方式:

  • Tensor Parallelism(TP,张量并行):每个gpu只存权重的1/n, 计算完成后都要AllReduce同步
  • Data Parallelism(DP,数据并行)
  • Pipeline Parallelism(PP,流水线并行)
  • Expert Parallelism(EP,专家并行,MoE 专用开关)

vLLM 允许设置 PP 和 DP

问题:

  • TP 如何切分权重、为何需要 AllReduce
  • DP+EP 在 MoE 场景下如何配合 AllToAll 做请求级并行
  • 为什么所谓的“DP Attention” 和传统数据并行完全不是一回事
  • 什么时候要开--enable-expert-parallel,什么时候反而是徒增开销

「交叉点」:

  • TP+EP:低并发、交互式场景下延迟更优
  • DP+EP:高并发、大吞吐场景下扩展性更好

专家激活密度(每个 token 实际激活多少 expert)会直接决定 EP 是加速还是减速;而像 DeepSeek 这种 MLA/MQA 模型,在 KV cache 管理上又需要特别对待。

DP

命令

vllm serve model-name --data-parallel-size 4

TP

vllm 部署 Qwen 2.5 14B 模型

下载模型

huggingface-cli download Qwen/Qwen2.5-14B –resume-download –local-dir /data/models/qwen2.5-14b

查看模型的参数:

cat /data/models/qwen2.5-14b/config.json

vllm 部署模型时要设置 tensor-parallel-sizepipeline-parallel-size 两个参数,和模型结构、GPU设备数量都有关系

  • 参数涉及到模型并行的两种方式 Tensor Parallelism (TP,张量并行)和Pipeline Parallelism (PP,流水线并行)

只采用 Tensor Parallelism,不采用 Pipeline Parallelism。

从config.json中 可以知道注意力头的数量 “num_attention_heads”是 40

  • num_attention_heads 必须是 tensor-parallel-size 张量并行度的整数倍。

vllm 部署命令:

vllm serve model-name --tensor-parallel-size 4

关闭 NCCL,使用 ray

export NCCL_IB_DISABLE=1
export NCCL_SOCKET_IFNAME=eth0
export NCCL_DEBUG=INFO
python -m vllm.entrypoints.openai.api_server \
  --model /data/models/qwen2.5-14b \
  --tensor-parallel-size 4 \
  --pipeline-parallel-size 1 \
  --trust-remote-code \
  --gpu-memory-utilization 0.75 \
  --max-num-seqs 64 \
  --max-model-len 4096 \
  --host 0.0.0.0 \
  --port 8000 \
  --distributed-executor-backend=ray  

测试验证

另起终端界面,运行以下命令,测试模型是否可用

curl http://localhost:8000/v1/completions   \
-H "Content-Type: application/json"   \
-d '{"model": "/data/models/qwen2.5-14b","max_tokens":"256" ,"temperature": "0.7","prompt": "请介绍一下你自己?"}' | jq

PP

把模型的不同层拆给不同 GPU / 节点,每块 GPU 负责一段「流水线阶段」,数据像装配线一样一级一级往后传

vLLM 不会傻傻地让流水线一次只跑一个请求,而是同时塞进多条请求:

  • GPU 0 处理请求 B 的第 1 段
  • GPU 1 同时处理请求 A 的第 2 段
  • GPU 2 可能在处理更早一条请求的第 3 段

这样流水线各阶段都能被填满,大幅减少 pipeline bubble。

示例

# Pure PP: 4 GPUs as 4 pipeline stages
vllm serve model-name --pipeline-parallel-size 4
# TP within nodes + PP across nodes (2 pipeline stages with each pipeline has 4 GPUs)
vllm serve model-name --tensor-parallel-size 4 --pipeline-parallel-size 2

llm = LLM(
    model=str(model_dir),
    dtype='bfloat16',
    gpu_memory_utilization=0.4,
    tensor_parallel_size=2, # 张量并行
    pipeline_parallel_size = 2, # 流水线并行
    data_parallel_size = 2 
)

TP 和 PP 很好理解,而DP 的意思应该是,例如你有4张卡,设置 TP = 2 , PP = 1 ,此时 vLLM 内部会启动“两份模型”, 进行并行的数据处理

MoE

部署 DeepSeek-R1 这类超大 Mixture-of-Experts(MoE)模型时,问题不只是“有多少块 GPU”,而是这些 GPU 怎么配合工作。

  • 选错并行策略→ KV cache 被复制 8 倍、显存直接爆掉,或者通信开销占了一半时间、吞吐被腰斩。
  • 选对并行策略→ 同样硬件上,针对业务场景,性能明显提升。

Expert Parallelism(EP,专家并行)是专为 MoE 模型设计的“修饰开关“,需要和 TP 或 DP 组合使用

概念:Sharded Experts vs Split Experts

维度 Sharded Experts Split Experts
定义 每块GPU持有全部专家,单个专家的权重张量在多GPU间分片存储 专家整体分发至不同GPU,每块GPU仅持有部分完整专家
使用 不开 –enable-expert-parallel 开启 –enable-expert-parallel
示例
(256个专家,TP=8)
每块GPU均有256个专家,单个专家权重仅存1/8 TP=8+EP, 每块GPU仅存32个完整专家(GPU0:0-31、GPU1:32-63…)
并行组合 仅搭配张量并行(TP) 可搭配张量并行(TP)+专家并行(EP),还可结合数据并行(DP)
通信方式 通过AllReduce聚合分片计算结果 1. 与DP结合:用AllToAll完成token与专家路由
2. 仅与TP结合:用AllReduce
存储特点 单专家权重分片,无完整专家独占GPU 单GPU存储完整专家,专家整体分布式部署

EP 开关控制什么?

  • 不开 EP:每块 GPU 上都有全部专家,权重张量被切片(shard),使用AllReduce 聚合;
  • 开EP:专家在 GPU 之间做分布(split),当 DP > 1 时:使用 AllToAll(DP Attention);当DP = 1、只有 TP 时:仍然是 AllReduce。

MoE 并行常见误区

  • 误区1:“Expert Parallelism 是一种单独的并行方式” ———— 不是,而是开关 --enable-expert-parallel,改写 MoE 层通信与映射方式,只能和 TP 或 DP 搭配使用
    • 注意:只有当 TP_SIZE × DP_SIZE > 1 时,EP 才会真正生效,否则会被直接忽略
  • 误区2:“DP Attention 就是普通的数据并行” ———— MoE 模型上,vLLM 用特殊的 “DP Attention”,和 Data Parallelism 完全不同。
  • 误区3:“所有 experts 在 MoE 中都是一样的”———— 不对,MoE 模型中两类 expert行为截然不同。
  • 误区4:“TP+EP 通信和 DP+EP 一样都是 AllToAll” ———— TP+EP 只用 AllReduce,完全不会走 AllToAll。vLLM 源码中,AllToAll kernel 的启用条件是:dp_size > 1

DP vs DP Attention

名词区分

传统 DP(Data Parallelism)

  • 每块 GPU(或一个 TP 组)上都有完整一份模型副本
  • 各副本处理不同请求,互不通信(推理阶段)

DP Attention

  • 仍用 --data-parallel-size N 配置,但语义:在一个逻辑「大模型副本」内部做请求级并行
  • Attention 层是复制的,而 MoE 层的行为取决于是否开 EP
  • 推理过程中需要 AllGather / AllToAll / slice 等操作,在 GPU 间反复重排 batch
  • 只能在:
    • ■ 搭配 EP:形成「DP + EP」
    • ■ 不搭 EP:退化为传统 DP,不再具备 KV cache 分区收益

TP 对 MLA/MQA 的问题

Multi-Latent Attention(MLA)与 Multi-Query Attention(MQA)只有一个 KV 头:

  • TP 可以按 head 维把 Q/K/V 的线性层切片
  • 但KV cache 只有一个 head,没法再按head 维度切

结果:

  • KV cache 在所有 TP rank 上完整复制

例子:TP=32 时:

  • 线性层计算:每块 GPU 只算 1/32 的 head ✅
  • KV cache:每块 GPU 都有 完整副本❌
  • 显存浪费极其严重

DP Attention 不再是「多个独立模型副本」,而是一个逻辑模型内部的分工:

  • 每块 GPU 上都有完整的非 MoE 层(attention、dense)
  • 但 KV cache 按请求 / token 维度切分:
  • 每块 GPU 只存自己负责那一部分请求的 KV cache
  • 推理时通过 AllToAll 在 GPU 之间互相转发 token

对比传统 DP:

  • 传统 DP:每块 GPU 处理自己的 batch,KV cache 完整复制在每个副本中
  • DP Attention:一个「逻辑 batch」被拆散,KV cache 被分布到多个 GPU

示例:TP=4,DP=8(共 32 块 GPU)

  • 32 块 GPU = 8 个 DP 组 × 每组 4 块 GPU 做 TP
  • 非 MoE 计算:每块 GPU 算 1/4 的 head(组内 TP)
  • KV cache:每个 DP 组只持有自己请求的 KV,组内 4 块再细分
  • AllReduce:只在每个 4-GPU TP 组内做,不跨 32 块全局
  • 不同 DP 组之间处理的是完全不同的一批请求

可用性

  • MoE + DP Attention 在 vLLM v0.9.0+ 的 V1 engine 中可用,
  • 是为 DeepSeek-V2/V3/R1 这类 MLA/MQA 架构专门设计的。

MoE Expert 种类

MoE 模型中通常有两类 expert,行为截然不同。

Routed Experts

  • 通过路由机制按需激活
  • 每个token 只会激活一小部分 expert(例如 256 中激活 8 个)
  • EP 开启时:通过 determine_expert_map 把 routed expert 分布到不同 GPU
  • EP 关闭时:所有 routed expert 在每块 GPU 上都存在,但权重会按 flatten_tp_across_dp做切片
  • DeepSeek-R1:有 256 个 routed experts

Shared Experts

  • 每个 token 都会激活(没有路由)
  • 更像一层普通的 dense MLP
  • 默认情况权重也是切片存储,只有在以下条件同时满足时才会复制

vLLM 源码解析

【2024-4-12】图解大模型计算加速系列:vLLM源码解析2,调度器策略(Scheduler)

  • 一、入口函数
  • 二、SequenceGroup
    • 2.1 原生请求输入
    • 2.2 SequenceGroup的作用
    • 2.3 SequenceGroup的结构
  • 三、add_request: 预处理请求
  • 四、step:调度器策略
    • 4.1 调度器结构
    • 4.2 整体调度流程
    • 4.3 _passed_delay:waiting队列调度时间阈值判断
    • 4.4 can_allocate:能否为seq_group分配物理块(prefill)
    • 4.5 can_append_slot: 能否为seq_group分配物理块(decode)
    • 4.6 allocate与append_slot:为seq_group实际分配物理块
    • 4.7 preempt:抢占策略
    • 4.8 调度器整体代码解读

案例

‘qwen-vllm - 通义千问VLLM推理部署DEMO’

结束


支付宝打赏 微信打赏

~ 海内存知已,天涯若比邻 ~

Share

Related Posts

标题:大模型投机采样

摘要:大模型如何加速推理过程?方法之一:投机采样,整理系列解法

标题:大模型推理框架 LLM Inference Tool

摘要:如何提升LLM推理效率?

站内可视化导航

文章可视化导读:鼠标划过图形块时,如果出现蓝色光环, 点击即可跳转到对应主题

Comments

--disqus--

    Content
    My Moment ( 微信公众号 )
    欢迎关注鹤啸九天