☰
VLLM 0.7.3源码解析:大模型推理部署与性能优化实战
2026/10/14 16:52:05 网站建设 项目流程

简介:大型语言模型推理引擎 VLLM-0.7.3 完整源码包,面向大模型推理优化开发者、算法工程师与进阶学习者,重点解决推理吞吐低、显存占用高、部署成本大等问题。源码覆盖高速令牌生成与高效内存管理两大核心模块,可配合 SKYPILOT 的工程实践,用于研究 PagedAttention 等关键机制、做性能剖析和二次开发。压缩包共 1896 个文件,约 46.33MB,以 1186 个 Python 源码文件为主,辅以 CUDA/C++ 算子(cu/cuh/hpp/cpp)、JSON/YAML 配置、Shell 脚本与 Markdown 文档,基本涵盖引擎调度、内核实现、部署配置与说明文档。已有 342 人学习浏览,适合有一定大模型基础、希望深入理解推理引擎内部实现或开展企业级部署调优的读者。下载后可直接查看完整 Python 执行链路及配套算子与配置,快速定位关键模块,梳理量化、批处理调度、显存复用等工程细节,进而提升大模型推理优化与落地能力。

1. 大模型推理为什么绕不开 VLLM:0.7.3 源码包到底能帮上什么

手里有 7B 甚至 13B 级别的开源模型,想在单卡或双卡上把服务跑起来,结果一上线就显存 OOM、并发一上来首字延迟翻倍——这是我在不少项目里反复见过的场景。VLLM-0.7.3 是目前把 LLM 大模型推理压榨得比较狠的方案之一,它在调度层用连续批处理、在显存管理上用分页式缓存,直接把吞吐推到传统朴素部署的几倍以上。这个版本源码包值得拆一遍,不仅因为现成能用,更因为你能从源码里看清算子层与调度层的边界:哪些是参数能调的,哪些必须改代码才能解。适合两类人:想让现有模型服务跑得更稳更快的工程开发者,以及想搞懂推理框架内部机制的算法同学。

2. 部署与拉起第一个推理服务:镜像选型、离线安装与启动配置

2.1 为什么我推荐官方镜像而不是直接在裸机上 pip 安装

拿到 VLLM-0.7.3 源码包之后,最常见的第一个动作是直接pip install vllm。环境干净的机器上这没问题,但如果你像我一样经常在带 CUDA 11.8、自带一堆深度学习框架依赖的机器上布服务,pip 直装很容易在依赖解析阶段翻车,最常见的报错是flash-attn编译失败、torch版本被强制升到不兼容的版本。

我一般会先去拉 0.7.3 对应的官方容器镜像,把镜像当运行底座,再在容器里挂载源码包做二次开发。这一步的好处有三个:算子层面的预编译产物已经就位,不需要现场编译;驱动只需要满足 CUDA 版本下限;因为容器文件系统是隔离的,后期换版本不会污染同一台机器上的其他项目。如果因为内网限制拉不了官方镜像,再退回裸机安装也不迟,但要注意两点:PyTorch 与 CUDA 版本必须严格匹配,flash-attn需要能看到完整的 nvcc 工具链。

# 1. 从镜像仓库拉取 VLLM-0.7.3 对应的 CUDA 12.1 运行镜像 docker pull vllm/vllm-openai:v0.7.3 # 2. 把源码包挂载进容器,注意用 --ipc=host 避免共享内存不足 docker run -it --rm \ --gpus all \ --ipc=host \ -v /data/models:/models \ -v /data/vllm-0.7.3:/workspace/vllm-src \ vllm/vllm-openai:v0.7.3 \ bash

挂载目录里模型放/models,源码放/workspace/vllm-src。很多网上教程没提--ipc=host,如果你用 dataloader 类预取或者多进程参数下发,不加这个很容易触发Bus error,表面上看是显存问题,实际上是共享内存上限不够。源码包里的docker/目录也有 Dockerfile,可以当参考,有定制需求的直接在官方 Dockerfile 顶部加层,但尽量别去改它内部的 Python 包安装顺序,那个顺序已经被压测过,改动它反而会踩依赖顺序问题。

2.2 从源码包到第一个服务:模型目录结构、入口脚本与启动参数

0.7.3 的推理服务入口在源码包里的vllm/entrypoints/openai/api_server.py,也就是兼容 OpenAI 接口的入口。它做的事情主要有三件:解析启动参数、初始化 LLM 引擎、启动推理 worker。启动之前先确认模型目录结构,这是新手最容易忽略的一步。无论你用的是官方下载的整套权重,还是自己转过的 CKPT,目录里至少要有config.json、tokenizer.json、权重切片,并且所有文件要平铺在同一个目录下,不能把多个版本混在一个文件夹里。

# 在容器内启动,模型用 /models/my-7b-chat,服务监听 8000 端口 python -m vllm.entrypoints.openai.api_server \ --model /models/my-7b-chat \ --served-model-name my-chat \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --port 8000

--model指向本地权重目录,VLLM 0.7.3 不会自动去下载模型,如果你传一个不存在的路径,它会直接报Access to model path is denied,和网络权限没任何关系。--served-model-name是暴露给外部调用方的名字,部署多个模型时用来区分路由。--tensor-parallel-size在单卡上就是 1,多卡时用来指定用几张卡并行。--gpu-memory-utilization是显存上限比例,写成 0.85 表示预留 15% 显存给 CUDA context 和临时算子,这个值后面还有得调。--max-model-len是模型能接受的最大上下文长度,代码包里默认值通常取模型 config 里声明的最大值,如果硬件不够,调小它比强行换量化格式更直接。

启动命令跑通后,用curl测一个最简单的请求,看返回结构里usage字段的prompt_tokens和completion_tokens是否正确。很多部署问题在这个阶段用 curl 是最快的定位方式,不需要急着写压测脚本。

2.3 离线环境少了依赖怎么办:pip 换源与本地 wheel 包

生产内网环境拉不到 PyPI 的情况太常见了。0.7.3 源码包虽然能在离线状态下完成纯源码运行,但有几个 Python 依赖是在安装期就必须就位的,transformers、tokenizers、safetensors、fastapi、uvicorn这些一个都不能少。这里的一个重要技巧是:在能联网的机器上把依赖约束导出到文件,再整体搬进内网。

# 在联网机器上执行,生成 requirements.txt 和本地 wheel 缓存 pip download -r requirements.txt \ -d wheelhouse \ -i https://pypi.tuna.tsinghua.edu.cn/simple \ --only-binary=:all: # 内网机器安装,注意先装 torch 相关的本地包,再装纯 Python 依赖 pip install --no-index --find-links=./wheelhouse \ torch-2.1.2+cu121-cp310-cp310-linux_x86_64.whl

--only-binary=:all:是关键,它把带 C 扩展的包强制走预编译 wheel,避免在内网现场编译。transformers的版本要盯紧,如果预装版本高于源码包 lock 的版本,有概率在加载模型时出现KeyError,原因是新版本改了配置文件解析方式。这个坑很隐蔽,通常一次请求都不发、引擎初始化到一半就崩。建议先看 0.7.3 的requirements/common.txt里锁定的版本范围,再在联网机器上按同一份文件下载。本地 wheel 包统一放/opt/wheelhouse或者干脆放源码包根目录下,后续重装修复也方便。

3. 源码结构里的两个核心引擎:PagedAttention 与连续批处理

3.1 PagedAttention 在 0.7.3 里长什么样:分页块与显存利用率

VLLM 之所以能比原生 HF 推理快这么多,PagedAttention 是地基。传统方案给每个请求预分配一条完整的 KV 缓存连续段,长度按max-model-len算,模型一旦生成到中段,后半截缓存往往用不满,显存浪费在空置的连续块里。PagedAttention 的思路是给每个序列维护一个块表,物理显存被切成固定大小的逻辑块,KV 缓存在物理块上不必连续,块表记录虚拟逻辑页到物理页的映射。这个设计被很多人简化成“显存碎片化优化”,其实更准确地说,它把 KV 缓存的分配粒度从序列级降到了块级,块大小由--block-size控制。

# 源码包中 vllm/attention/ops/paged_attn.py 的块表参数示意 # 每个块容纳的 token 数由 block_size 控制,默认 16 block_size = 16 num_blocks = 1024 max_seq_len = 8192 # 序列 s 的逻辑页表 page_table[s] 记录物理块索引 page_table = torch.empty(num_blocks, dtype=torch.int32)

代码的逻辑其实可以拆成三块看:一个序列在逻辑空间上是连续的 token 流,但在物理显存里是离散的若干块;当生成新 token 时,只需要分配一个新块,而不是调整整段连续缓存;GQA 多查询头共享同一个块表,显著减少重复的显存寻址开销。块太大,短序列浪费空间;块太小,块表本身和寻址开销上升。单卡场景用默认 16 就够了,长上下文压测时调到 32 能降低块表查询次数,但首 token 延迟会有轻微上升,这是取舍项,不是纯优化项。

3.2 Continuous Batching 的调度逻辑:请求如何排队与抢占

VLLM 0.7.3 的调度器在vllm/core/scheduler.py里,核心逻辑是 Continuous Batching 的变体。传统静态批处理是把一批请求集合起来,等批里最慢的序列完成才释放整批资源;Continuous Batching 在 token 粒度上动态插入和删除序列,某个序列生成完了,当前 step 立刻把空出来的显存和计算资源交给等待中的新序列。

调度器里有几个值得读的状态:waiting队列是还没拿到显存的请求,running队列是正在生成中的序列。每一步调度开始时,先把 running 里已结束(到达max_tokens或触发 stop 条件)的序列逐出,回收物理块;再把 waiting 里的请求按到达顺序尝试放进 running 队列,直到显存块不够为止。这个调度时机不是每步生成都触发,而是由参数决定。

# vllm/config/schedule.py 中相关调度配置 max_num_batched_tokens = 4096 # 一个 step 里最多处理的 token 总数 max_num_seqs = 256 # 一个 step 里最多并发的序列数

这两个参数在源码里是有默认值的,但实际压测里我一般会按显存动态改。max_num_batched_tokens影响单步计算图规模,太大会导致单步延迟波动,太小则 GPU 利用率上不去;max_num_seqs限制并发序列数,它和gpu-memory-utilization、模型大小共同决定实际并发上限。调度器还会做抢占,新请求要进入而显存不够时,它会选择把 running 队列尾部某些序列的 KV 块 swap 到 CPU,而不是直接拒绝请求。源码里对应的行为是preempt_mode的recompute与swap两种,默认走 recompute,即释放该序列全部显存块,后续重新计算的代价就是重复一轮 prefill,如果这个状态频繁被触发,你会看到服务延迟突然高一大截。

3.3 从源码到实践:改哪些参数能直接感受到推理变快

很多人在跑 VLLM 时只改模型路径和端口,其他全默认,这其实浪费了框架一半的潜力。这里给出三个参数改动建议:单卡 7B 场景,--max-model-len 4096能省下一大块 KV 缓存预分配空间;--max-num-batched-tokens 2048会让调度更灵敏,小请求占坑的时间变短;--enable-prefix-caching在 API 场景里开启后,prompt 前缀相同的请求可以复用 KV 缓存,比如多轮对话的 system prompt 部分。

另外有个和源码强相关的细节:0.7.3 支持了多模态输入的一些预处理路径,但如果你只是跑纯文本模型,不要在前端请求体里塞image_url字段,多模态检查器会在解析阶段报错。源码包里model_executor目录下的输入处理器会检查 modalities 字段,空值没填到处理器指定的 key 上,会直接抛KeyError,看起来像模型加载失败,实际是入参格式问题。

4. 性能调优:TP 尺寸、显存比例、块大小与量化格式的取舍

4.1 六个关键启动参数与建议范围

推理服务跑起来只是第一步,想榨干卡上的算力,下面这张表是 0.7.3 启动参数里最值得细调的六项。每项都来自源码包里的config模块,不是拍脑袋出来的。

参数名默认值建议范围说明与适用场景
gpu-memory-utilization0.90.72-0.90显存占用比例,需预留算子临时显存,多模型共存时调低
max-model-len模型内置值2048-8192越长 KV 缓存占用越大,按实际业务长度裁剪
tensor-parallel-size11-卡数超过物理卡数直接启动失败
block-size1616-32长上下文低并发可调大,短请求高并发维持 16
max-num-batched-tokens81922048-8192单 step token 上限,大值吞吐高,小值延迟稳
enable-prefix-cachingFalse按需开启有公共前缀的多轮对话场景收益明显

gpu-memory-utilization是最需要现场试的一项。它控制的是 KV 缓存池的上限,不是模型权重占用。模型权重在初始化时占多少就是多少,比例设置只影响 KV 缓存能申请多少显存。如果你在跑 7B 模型、显存 24G、比例调到 0.9,实际 KV 缓存能用的空间约为总显存减去 CUDA context 减去权重,这个逻辑很容易被误解成“VLLM 吃掉了 90% 显存”,其实它是把可用的部分尽量留给 KV 缓存。

4.2 Tensor Parallel 与数据并行:多卡场景怎么切分

多卡部署时,0.7.3 的--tensor-parallel-size做的是张量并行:把模型权重按层内切分到多张卡上,每张卡只负责一部分计算,forward 过程中需要 all-reduce 同步。这是显存和算力一起扩展的方案。与之相对的数据并行是每个 worker 持有一份完整权重,各自处理不同请求。

张量并行不是免费的。卡间通信用 NCCL,通信开销在 7B 模型、单卡能放下全量权重时通常是负收益,因为计算量太小、通信占比太高。我测过 7B 模型 1 卡与 2 卡对比,p50 延迟几乎一样,吞吐略有提升但远没到翻倍。真正的收益出现在 13B/30B 甚至更大模型上,单卡放不下、必须切成多卡时,TP 就是必选项。

# 4 卡启动 13B 模型,TP=4 python -m vllm.entrypoints.openai.api_server \ --model /models/my-13b \ --tensor-parallel-size 4 \ --gpu-memory-utilization 0.84 \ --dtype bfloat16

TP=4 时,0.7.3 会把模型权重切到 4 张卡上,每张卡的显存占用约等于总权重的 1/4 加 KV 缓存均值。dtype务必显式指定为bfloat16或float16,不要交给自动检测,因为有些权重文件同时存在 fp16 和 fp32 版本,自动检测会选择 config 里的torch_dtype,但如果你从源码包目录结构里加载的路径里权重文件不全是同一 dtype,初始化和推理时会出现隐式转换,拖慢速度。NCCL 通信失败时,先看/usr/local/nvidia下的驱动和容器内 CUDA 是否匹配,再看多卡之间的 P2P 是否被 PCIe switch 限制成走 host memory。这两点是 TP 部署最常见的失败来源。

4.3 AWQ、GPTQ 与 AutoRound:0.7.3 内置量化路径怎么走

0.7.3 源码包里有model_executor/layers/quantization目录,里面按量化方案分成了几个子模块。加载量化模型的关键是--quantization参数要和权重文件实际使用的方案一致。AWQ 的权重入口一般带awq标志,GPTQ 的权重要配合model.config.quantization_config,两者不能混用。

# 启动 AWQ 量化模型 python -m vllm.entrypoints.openai.api_server \ --model /models/my-7b-awq \ --quantization awq \ --max-model-len 4096 # 启动 GPTQ 量化模型,注意 group-size 需要用 128 的默认值 python -m vllm.entrypoints.openai.api_server \ --model /models/my-7b-gptq \ --quantization gptq \ --gpu-memory-utilization 0.80

量化格式的选择有明确边界:AWQ 在 0.7.3 里的算子融合度更高,尤其对gemm层有一定速度优势;GPTQ 的权重来源更广泛,HF 仓库里能找到更多现成权重;AutoRound 是相对新一些的方案,校准流程更简单,但当前版本里算子覆盖度不如前两者。显存不够时,量化 4bit 通常能把 7B 塞进 8G 卡,但要注意max-model-len也要同步调小,否则 KV 缓存又会把省下来的显存吃掉。选择优先级我一般是这样:能上 bf16 就不要量化;显存卡在边界时优先 AWQ;手头只有 GPTQ 权重就老老实实跑 GPTQ,模型权重自带的质量差异比框架支持度的影响更大。

5. 避坑手记:部署与压测中常见的七个翻车现场

5.1 启动时报显存不足,但 nvidia-smi 明明显示显存还剩不少

现象:启动日志里出现torch.cuda.OutOfMemoryError或CUDA OOM,但用nvidia-smi看显存只占了一小半。

原因:VLLM 的显存分配走的是 PyTorch 的缓存分配器,它启动时一次性把gpu-memory-utilization对应的显存池申请到手,后续 token 生成时再在池内细分。nvidia-smi看到的实际占用会低于预估值,因为 PyTorch 可能尚未把全部预留空间映射到物理显存,但保留的虚拟区间是存在的。如果模型重 + KV 池加起来超过物理显存,初始化的某一步就会触发 OOM。

解决:降低--gpu-memory-utilization到 0.72 附近,再看max-model-len是否设置过大。也可以先跑一个极短上下文的请求验证模型本身能加载,确认是 KV 缓存池问题而不是权重加载问题。还有个容易忽略的点:容器里如果还跑了其他服务,它们占用的显存也在总量内,--gpus all会让 VLLM 以为整卡都归自己,但实际另外进程已经占了 2G 到 3G。

5.2 tensor-parallel-size 设置大于实际卡数导致启动崩溃

现象:python -m vllm.entrypoints.openai.api_server启动后不久,进程直接段错误退出,日志里出现initialize_worker或NCCL相关报错。

原因:VLLM 0.7.3 初始化张量并行组时,会把 TP 组映射到实际可见的 GPU 列表。如果你写了--tensor-parallel-size 4,但机器只有 2 张卡,NCCL 在创建通信组时无法找到第 3 和第 4 张卡,底层直接触发 abort。日志往往只显示NCCL error,不会提示“卡数不够”这种直白文案。

解决:启动前先跑nvidia-smi -L确认卡数,然后检查环境变量CUDA_VISIBLE_DEVICES是否把可见卡数限制了。如果只想用某两张卡,应该写成CUDA_VISIBLE_DEVICES=2,3,而不是写0,1,2,3然后希望 TP 只取前两张。

5.3 模型输出乱码但服务状态正常,一次请求都不报错

现象:服务能正常返回响应,status 都是 200,但生成文本是乱码或者空字符串,日志没有任何异常。

原因:这类问题大概率在 tokenizer 与模型权重不匹配。比如权重文件用的是新版 tokenizer,但模型目录里被塞了一份旧版tokenizer.json,decode 出来的内容对不上。

解决:直接对比模型目录里tokenizer_config.json中声明的词汇表大小和权重加载日志里实际加载的vocab_size是否一致。我碰到过一次某个模型目录里混入了同家族另一规格的 tokenizer 文件,原因是从网盘下载资源时多复制了一个缓存文件,清理掉冗余文件后重启就正常了。如果对比后不一致,去权重来源重新下载整套配置文件,尽量不要手动改vocab_size强行对齐,那只会有更隐蔽的错位。

5.4 用 API 客户端调用时首字延迟高得离谱并伴随偶发超时

现象:curl 单条请求正常,但用 Python 的openai客户端并发 20 条请求时,部分请求超时,首字延迟从 300ms 飙到 5s 以上。

原因:并发上来后,running 序列增多,KV 缓存池耗尽,调度器开始对部分序列做抢占重算。每次重算都要重新 prefill,相当于把已经生成的部分 token 丢掉、从头计算,计算量直接翻倍,延迟就被拉长了。默认的抢占行为没有在日志里明确提示,只能从指标曲线里看出来。

解决:调大gpu-memory-utilization或调小max-model-len;同时把--max-num-batched-tokens降低到 2048,让调度器每次 step 只处理少量 token,降低重算时的损失。业务上如果 prompt 前缀长度固定,打开--enable-prefix-caching能有效减少这类重算。

5.5 日志里不打印关键告警导致问题被忽略

现象:服务启动时无任何 warning,压测一段时间后发现显存缓慢爬升,最后 OOM 崩溃,检查日志却没有留下显存分配的相关记录。

原因:VLLM 0.7.3 的默认日志级别是 INFO,很多属于 DEBUG 级别的调度细节、块回收细节不会输出,它们恰恰是定位显存缓慢增长和请求堆积的关键信息。

解决:启动命令加--log-level debug,同时在外部做监控,用nvidia-smi dmon -i 0 -s mem每两秒记录显存占用。不要依赖框架内部日志来做容量告警,把显存监控放到服务外部的定时任务里更可靠。

5.6 通过源码包自带的单元测试,快速验证环境是否正常

现象:部署新环境时,总担心某些算子没编译成功,但又不想立刻加载大模型验证。

解决:0.7.3 源码包里有tests/目录,运行pytest tests/test_basic_batching.py -x -q这类基础用例,能验证核心的调度、采样、缓存是否正常。这一步通常只要一两分钟,比加载完整模型快得多,可以作为环境变更后的回归检查。

6. 压测脚本与日志解读:让服务在崩溃前暴露问题

6.1 用一段轻量级 Python 脚本做并发压测与指标统计

压测不需要一上来就上重型工具。VLLM 0.7.3 的服务协议兼容 OpenAI 接口,所以用requests写一个几十行的脚本就能看清吞吐和延迟基线。我习惯在源码包目录下创建一个bench/文件夹,把压测脚本和结果放一起,方便对比不同启动参数的效果。

import json import time import threading import requests from statistics import mean, median API_URL = "http://127.0.0.1:8000/v1/completions" PAYLOAD = { "model": "my-chat", "prompt": "请用三句话解释大模型推理中的 KV 缓存机制。", "max_tokens": 256, "temperature": 0.7 } results = [] def send_one(): start = time.time() r = requests.post(API_URL, json=PAYLOAD, timeout=120) cost = time.time() - start if r.status_code == 200: data = r.json() ttft = data.get("metrics", {}).get("time_to_first_token", None) results.append((cost, ttft)) threads = [threading.Thread(target=send_one) for _ in range(20)] t_start = time.time() for t in threads: t.start() for t in threads: t.join() total = time.time() - t_start print("total_seconds:", round(total, 2)) print("requests:", len(results)) print("throughput: %.2f req/s" % (len(results) / total)) print("p50_latency: %.2fs" % median([r[0] for r in results]))

这个脚本把 20 个请求一次性发出,统计总耗时、吞吐、p50 延迟。metrics.time_to_first_token字段在部分版本里不一定返回,如果没有就根据响应内容大小和采样耗时反推。脚本本身只是骨架,真实的压测还应该覆盖不同max_tokens、不同并发数、重复请求次数取多次平均。我一般会把并发数按 1、4、8、16 跑四轮,再对比参数调整前后的曲线。

6.2 看懂 VLLM 0.7.3 的核心日志字段与显存画像

VLLM 启动日志里藏着大量有效信息,问题是太多人直接划过去。重点关注三处:初始化时打印的显存使用估算表,它会把 KV 缓存池大小、最大并发序列数估算值列出来;调度器在运行期间如果出现Preempted字样,说明抢占正在发生;引擎结束时的统计行会给出本次服务的 token 总数等累计值。

# 日志中典型的显存估算表 # GPU KV cache size: 18.42 GB # Maximum concurrency: 320 sequences

把GPU KV cache size和Maximum concurrency记下来,再和你的压测结果对比,能看出当前服务距离显存上限还有多远。如果并发数到不了这个估算值就开始超时,问题要么在 CPU 侧(调度线程瓶颈)、要么在max-num-batched-tokens上。显存画像方面,用nvidia-smi dmon持续采样,观察mem列是否稳步上升,如果长时间运行后显存不断增长而不回落,大概率是某个 API 调用路径没释放缓存池,这时候就要把请求特征记录下来反馈给源码层排查了。

6.3 一个定位 OOM 的实用技巧:先关前缀缓存,再逐步加并发

遇到 OOM 时,不要一上来就调低显存比例,那样会连带降低吞吐。我常用的定位思路是先关掉--enable-prefix-caching,因为前缀缓存在某些版本里额外维护一张 hash 表,占用一部分显存;关掉之后如果 OOM 消失,说明是前缀缓存和 KV 池竞争资源。接着用二分法加并发数,找到稳定的临界并发值,再回来微调显存比例和max-model-len。

# 关闭前缀缓存,并将 KV 池上限设定为 0.75 python -m vllm.entrypoints.openai.api_server \ --model /models/my-7b-chat \ --gpu-memory-utilization 0.75 \ --max-model-len 4096 \ --disable-prefix-caching # 观察显存占用稳定后,逐步增加并发压测 nvidia-smi dmon -i 0 -s mem

那次在某个 7B 模型上碰到 OOM,关了前缀缓存后问题立刻消失,后来一查是 prompt 前缀千奇百怪,hash 表膨胀得厉害,远没有命中缓存的价值。从那以后我每次压测前都先按这个顺序排除变量:先关缓存、再降 token、最后才动显存比例。这个习惯帮我省下了不少排查时间,也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询