news 2026/10/15 4:53:29

vLLM源码安装实战:从环境配置到模型适配的全流程指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vLLM源码安装实战:从环境配置到模型适配的全流程指南

1. 为什么源码安装:先想清楚需求再动手

如果你的目标只是把模型快速跑起来验证效果,那我大概率建议你直接装官方预编译包。但如果你是做模型服务化部署、想把某个新模型用起来,或者需要改一点内核逻辑做实验,那么源码安装基本是绕不开的一条路。我最早接触vllm源码安装时,动机特别朴素:官方预编译包在我那台机器上总是提示CUDA版本不一致,换了几个版本又碰到依赖冲突,最后干脆走源码编译,反而一次性把环境理顺了。

这里先同步一下背景:vllm是一个面向大模型推理的高性能服务框架,核心价值在于通过PagedAttention、连续批处理和各类量化内核把GPU利用率拉到接近极限。它跟普通Python推理脚本有一个本质区别——它里面有大量C++和CUDA内核代码,打包成whl文件时绑定了编译参数和CUDA运行时。只要你的驱动、CUDA工具包、PyTorch三者的版本匹配关系和官方构建环境不完全一致,预编译包就会出现“能装上但跑不起来”的诡异情况。

源码安装的本质,是让你在自己的机器上重新完成那种编译不是问题,反而能绕开很多坑。但它的代价也很现实——编译时间长、环境容易出错、对Linux基础有一定要求。所以动手之前,先花十分钟把自己的需求和机器条件列清楚,比什么都重要。

1.1 pip安装和源码安装到底差在哪

以我自己的理解,pip安装和源码安装不是“同一个东西的两种获取方式”,而是“一份签名版唱片和一份母带”的区别。pip安装拿到的是官方提前编译好的二进制产物,里面的CUDA内核、算子实现都已经按照官方测试环境定制好了。优点是省事,缺点是你无法改动它,而且一旦底层环境有一点偏差,你可能连报错都看不懂。

源码安装拿到的是完整工程,从Python入口到底层kernel代码都摊开在你面前。你可以做三件pip安装做不到的事:

  • 修改模型实现层代码,比如换一个激活函数、改RoPE的旋转基频,然后重新编译生效。
  • 把vllm与自己的推理框架做深度集成,把调度器、缓存策略暴露成自定义接口。
  • 在编译期强制绑定某一套确定版本的工具链,让环境之间的关系变得可控。

当然,代价也很明确:源码编译需要你理解CMake、编译链接、ABI兼容这些概念,而且首次编译通常要等二三十分钟,如果中途报错,那消耗的时间还要翻倍。这也是很多人刚接触源码安装就劝退的原因,后面我会把过程中的坑尽可能摊开讲。

1.2 三种安装方式怎么选

针对是否源码安装的问题,我一般把方案分成三档。第一档是官方预编译包,适合环境干净、GPU型号常见、不需要改代码的常规使用;第二档是官方源码安装,适合需要改Python层代码或者遇到预编译包环境不匹配的情况;第三档是在源码基础上连同flash-attention一起从源码编译,适合追求极致兼容性或者在特定模型上需要深度定制算子的场景。

选型的关键标准很简单:你是否需要动到C++/CUDA层。如果只改模型配置、权重加载方式,或者只是想把vllm嵌入自己的业务代码,那么Python层的源码安装就足够了,不需要去碰底层内核。如果涉及量化、Attention实现、自定义算子,那flash-attention和vllm内核的一起编译几乎是必须的。

另一个很实际的选择依据是时间。我见过不少同学为了省事,在源码环境里硬pip一个预编译包,结果装完跑推理直接报CUDA error,花了两天排查还是回退到源码编译。如果你的机器已经存在多套CUDA、conda环境混杂的情况,我建议直接跳过预编译包,走源码安装反而更省心。

1.3 源码安装适用场景清单

总结一下,以下场景优先选择源码安装:

  • 官方预编译包在启动时报CUDA相关错误。
  • 你需要加载一个vllm尚未官方支持的新模型结构。
  • 你需要修改调度、缓存、采样层面的Python代码并立即生效。
  • 你的GPU架构比较新,预编译包来不及适配。
  • 你需要接入自定义的flash-attention版本或自定义算子。
  • 你的CUDA环境工具链版本与官方构建矩阵不一致。

如果你一个都不属于,那源码安装对你来说确实不是最优解。强迫自己编译纯属浪费时间,工具是拿来用的,不是拿来折腾的。

2. 动手之前的软硬件前置检查

源码安装最痛苦的事情不是编译本身,而是在编译快结束的时候因为一个环境细节整体翻车。所以,动手前把软硬件前置条件逐项确认掉,能省掉后面大部分折腾。

2.1 GPU驱动与CUDA版本别想当然

我在实际排查中遇到的第一个高频坑就是“我机器上明明有显卡,为什么编译出来的内核加载不了”。根因基本都在驱动与CUDA运行时版本不匹配上。vllm源码编译时,会通过nvcc调用CUDA工具包生成可执行内核,而运行时则通过驱动自带的运行时库加载内核,两者如果版本差距过大,就会出现编译能过、运行必挂的现象。

我这边的做法是,在任务开始前先跑nvidia-smi看一眼驱动版本,再跑nvcc --version看一下工具包版本。驱动版本决定了上限的CUDA运行时版本,工具包版本决定了编译目标支持的CUDA版本。如果工具包版本比驱动支持的最高版本还高,那编译出来的东西注定跑不了。这时候要么降低工具包版本,要么先升级驱动再继续,千万别指望靠环境变量绕过兼容性检查,这个坑我踩过一次,最后只能重装驱动解决。

还要注意一点:很多机器上同时存在多个CUDA工具包,~/.bashrc里的PATH和LD_LIBRARY_PATH决定了当前实际用的是哪个版本。我建议在编译前用一个干净的shell,手动export指定工具包路径,避免把系统自带的版本和conda环境里的版本搞混。

2.2 Python与依赖包的版本矩阵

vllm对Python版本和PyTorch版本都有明确要求。它的构建脚本会在setup.py里检查Python大版本范围,如果你用了过新的Python版本,可能连依赖解析阶段都过不了。最稳妥的办法是把Python锁定在3.9到3.11之间,具体看你要编译的tag版本,因为不同vllm版本支持的Python上限不一样。

PyTorch版本的匹配更讲究。vllm编译时会去链接PyTorch暴露出来的C++扩展接口,如果PyTorch的小版本差太多,接口签名变了,那编译会直接报一堆类型不匹配的错误。我建议创建独立的conda环境,先安装对应版本的PyTorch,再在该环境内执行vllm源码安装,让构建系统自己探测当前环境的PyTorch版本。不要试图在当前环境里混装多个PyTorch版本,那会让NVIDIA库冲突问题雪上加霜。

另外,编译期依赖的setuptools和wheel版本也需要留意。很多源码安装失败源于新版setuptools改了构建行为,导致旧式setup.py执行报错。我的习惯是先把setuptools固定到一个已知稳定的版本,再执行安装,能省掉很多莫名其妙的报错。

2.3 编译工具链:GCC、CMake与磁盘体力

C++代码的编译依赖GCC或G++,CUDA代码的编译依赖nvcc,而nvcc在主机端代码生成阶段也会调用GCC。因此GCC版本不能太老,但也不能太新。太老会导致C++17标准支持不全,太新则可能导致nvcc与GCC的兼容性检查失败。通常来说,GCC 9到12区间是比较安全的。

CMake方面,源码里会通过子模块拉取一些依赖,内部目录较多,你的CMake版本太低会导致一些新语法无法识别。我建议直接装一个较新的CMake,市面上常见的3.26以上版本基本都能顺利通过配置阶段。

最后是磁盘和内存。源码编译会产生大量中间文件,磁盘空间建议预留至少30GB,否则编译到一半报No space left on device会让你非常痛苦。内存方面,编译vllm的全过程最耗内存的环节是C++和CUDA对象的并行编译,16GB内存会比较紧张,建议在make阶段限制并行任务数,比如用-j4而不是默认的核心数。当年我在一台16GB笔记本上直接全核编译,Link阶段直接OOM,后来改成-j4才顺利跑完,这个教训很深刻。

3. 源码安装完整流程参考

考虑以下假设:你已经创建好干净的conda环境,GPU驱动和CUDA工具包版本匹配,磁盘剩余空间充足。接下来就是按步骤走源码安装流程了。

3.1 获取源码与选择分支

第一步是把源代码拉到本地。这里强烈建议直接用git clone而不是从网页下载zip包,因为vllm构建过程中可能需要子模块,git方式能把子模块一并拉全。

git clone <仓库地址> vllm cd vllm git checkout <你想安装的版本标签> git submodule update --init --recursive

我习惯先拉一个稳定的release标签来对应生产环境,只有需要体验新功能时才切主分支。源码版本一旦定了,后面所有依赖版本围绕它来配,否则编译报错会非常难排查。

拿到源码后,先读取里面自带的说明文档,重点看两个文件:环境依赖列表和版本对应矩阵。这两个文件能帮你避开很多“官方也没解释清楚”的环境坑。至少把里面的Python版本、CUDA版本、PyTorch版本对照你自己的机器过一遍,对得上了再继续。

3.2 处理flash-attention的两个选择

flash-attention是vllm做Attention加速的核心依赖。源码安装vllm时,它有两种衔接方式:一种是直接使用预编译好的flash-attention库,另一种是连同flash-attention一起从源码编译。如果你的目标GPU架构在官方flash-attention的预编译范围内,直接装预编译版本是最快的。如果不在,那就必须源码编译,而这往往是整个安装流程中最容易出问题的一环。

这里给一个实操建议:先装PyTorch,再装flash-attention,最后装vllm主体。这个顺序的意义在于,vllm构建脚本会动态探测flash-attention是否存在以及版本是否符合要求,先装好可以让它自动带上合适的编译选项。

flash-attention源码编译本身也要注意两点:一是它会自动检测GPU架构并生成对应代码,如果你的显卡太新,需要检查构建日志里是否识别到正确架构号;二是编译耗时较长,耐心等就好,如果中途报CUDA版本不匹配,先回头检查工具包版本。

3.3 首次编译:参数配置与执行

依赖就绪后,安装命令本身并不复杂,但有几个环境变量需要先定义好,否则可能编译出没有针对你的GPU优化的内核。

export MAX_JOBS=4 export CMAKE_BUILD_TYPE=Release pip install -e . --no-build-isolation

MAX_JOBS是控制编译并行人数的环境变量,我通常根据内存和核心数来设。如果你的机器内存不满16GB,建议设为2到4,别贪多。CMAKE_BUILD_TYPE=Release很重要,因为默认调试模式生成的二进制体积更大、运行效率也低。

--no-build-isolation这个参数的意义是让pip沿用当前环境已有的编译依赖,而不是重新创建一个隔离环境再下载一遍依赖。这样可以大幅减少重复下载,也能避免隔离环境里缺少GPU相关库导致编译失败。如果你不熟悉构建隔离的机制,直接用这个参数能多省点心。

首次编译完成后,会看到pip提示成功安装到当前环境。先别急着跑大模型,我们需要做一次安装自检。

3.4 编译期的常见挫败体验

如果你在编译过程中报错,先不要慌。绝大多数错误可以分成几类:一类是NVIDIA相关库路径找不到,这类要检查LD_LIBRARY_PATH和CUDA_HOME是否指向了工具包的真实路径;一类是C++编译时头文件缺失,这类通常表示系统依赖没装全,根据报错信息用系统包管理器补上即可;还有一类是nvcc和GCC版本兼容性报错,这类需要调整GCC版本或升级nvcc。

编译中途报错后再重新执行安装命令,构建系统一般会从失败点附近继续,但你如果改了CMake选项,最好先清理一下构建缓存再编译。我的经验是“编译失败一次后,宁可多花几分钟清缓存重来,也不要抱着侥幸心理反复重试”。

4. 从“装好”到“跑通”:桥接你的模型结构

源码安装成功只是第一步,很多项目的真正目标是让一个vllm尚未原生支持的新模型跑起来。这一步涉及在源码里做模型结构适配。别怕,这一层主要是Python代码,不需要重写CUDA内核。

4.1 模型入口与config映射关系

vllm通过一个全局注册表来决定模型类如何加载。你在构造离线推理引擎时传入的model路径会被解析,然后去注册表里找对应的模型实现类。如果你想接入一个新模型,需要做的事情是先看这个模型的config文件里写了什么架构名,然后在vllm源码里找到对应的模型实现模块,确认它是否已经存在。

很多模型其实只是改了层数、维度、头数等超参,架构名仍然是常用的那几种,那么你根本不需要改代码,直接实现加载权重就能用。只有架构名不在注册表里时,才需要新增实现类。这里最省力的做法是:找一个结构最接近的已有模型类,复制一份出来,改点名和关键计算逻辑。别从零写。

4.2 激活函数与位置编码的改造

模型差异化最大的两块通常是激活函数和位置编码。激活函数一般改动范围小,找到模型中对应的激活函数调用点,改成目标形式或者换成自定义函数即可。改完之后,只要逻辑正确且输出形状保持一致,不需要重新写底层算子。

位置编码同样如此。比如有的模型用RoPE,有的用ALiBi,有的用动态NTK缩放。vllm内部已经实现了好几种常见位置编码,并且通过配置参数来选择。你需要做的通常是找到位置编码相关类,调整旋转基频、缩放系数等参数。如果目标模型用了全新的位置编码方式,那就需要自己实现一个位置编码函数并接入attention模块的调用链。

这里有个实操经验:每次改完位置编码,先用小批量数据做一次前向传播,对比参考实现输出。只有输出形状和数值范围对得上,才继续下一步。不要一口气改很多再整体调试,那样定位错误非常痛苦。

4.3 QKV布局与GQA的实现细节

另一个常见改造点是Attention的QKV布局。有的模型权重文件里存的是三份独立的Q、K、V矩阵,有的存的是拼接后的一整块,还有的用分组查询注意力(GQA)。如果你加载权重时报shape不匹配,大概率是QKV布局和模型实现类里的拆分逻辑不一致。

处理办法通常有两种:一种是在权重加载阶段把拼接权重拆成三份,另一种是修改模型实现里处理QKV的逻辑。我比较推荐前者,因为权重文件布局是被训练框架决定的,属于数据侧问题,在vllm侧适配权重文件的布局,能保持模型实现代码的通用性。

GQA的适配重点在于头数映射。GQA模型通常是K和V的头数比Q少,需要在attention实现里做expand或者分组映射。vllm的底层内核已经支持GQA,你只需要在模型实现层把索引逻辑写对,把K和V头部映射到对应的Q分组上。

4.4 权重不匹配的排查方法

如果加载权重后跑起来出了问题,最常见的现象是几乎所有输出都是NaN或者重复同一个token。这种问题99%来自权重shape不匹配或者参数映射错误。我的排查步骤固定是:

  • 打印加载过程中的shape日志,确认每个张量的shape与模型定义一致。
  • 对比参考实现的第一层输出,逐步定位某个位置开始出现数值偏差。
  • 检查是否需要对权重做缩放或transpose操作,比如有些权重需要转发置,有些需要额外维度处理。

这一步会消耗不少时间,但几乎是模型适配的必经之路。只要数值走对了,后面调优解码参数就是水磨工夫。

5. 编译结果的验证与性能摸底

源码安装完成、模型适配也跑通了,很多人会直接扔进生产环境。我建议还是先做一轮验证,总比线上出事故强。

5.1 冒烟测试的三步走

第一步是环境冒烟测试,不加模型参数,直接看引擎能否正常加载。第二步是小模型生成测试,用一个很小的模型,输入几句简单文本,确认能返回非空输出。第三步是目标模型生成测试,确认目标模型在vllm框架下的首token延迟和生成速度符合预期。

我习惯把这三步写成一个可以反复执行的脚本,每次改动源码后都跑一遍。因为改Python代码并不总能被构建系统识别,有时候你以为改了代码,但实际上没重新编译,导致测试的仍是旧版本。通过冒烟脚本可以在每次改动后快速确认构建是否生效。

5.2 输出一致性对比怎么对症

如果只是验证推理正确性,建议用一个与参考实现完全一致的输入和生成参数,比如相同的种子、相同的采样温度、相同的max_tokens,然后对比生成结果。这里要特别注意一点:不同推理框架在采样算法上可能存在细微差异,直接对比token序列不一定能对齐,更可靠的方式是比较logits分布或困惑度。

logits对比偏移在1e-4以内基本可以断定推理正确,超过这个量级就要检查数值精度或内核路径。我通常把输出差一个数量级以上的情况视为实现bug,而不是采样随机性导致的差别。

5.3 吞吐基准测试的注意事项

跑性能基准测试时,最要注意的是不要把首token延迟和生成吞吐混为一谈。首token延迟反映的是prefill阶段的效率,生成吞吐反映的是decode阶段的效率。两个指标通常对应不同瓶颈,一个偏计算密集,一个偏访存密集。

我常用的测试方式是:准备一个固定长度的输入集,固定输出长度,用服务端压测方式发多路并发请求。压测一段时间后观察平均吞吐和P95延迟。你会发现并发数提升后,吞吐先上升后趋于平缓甚至下降,那个拐点基本就是当前批量大小和显存容量下的极限值。

还有一点很值得提醒:不要把Benchmark结果当作生产性能承诺。真实业务里的输入长度分布、缓存命中率、会话数量都会影响最终指标。最多把它当作参考基线,用来验证改动有没有带来收益。

6. 高频问题的排查记录

这一节把我在源码安装和适配过程中遇到过的经典问题整理成速查表,方便你遇到类似情况时快速定位。

现象常见原因处理建议
编译时报NVIDIA库找不到LD_LIBRARY_PATH没包含工具包路径重新export路径后再编译
nvcc编译报错,指向GCC版本GCC版本与nvcc不兼容安装并切换到兼容GCC版本
运行时提示CUDA driver版本不足驱动太老,低于运行时要求升级驱动,或降级工具包版本
加载权重shape不匹配QKV布局假设不同在权重加载阶段做拆分或合并
输出全部NaN参数映射错位或缩放问题逐层对比参考实现输出定位
编译中途OOM并行编译任务过多降低MAX_JOBS数量再试
修改Python代码不生效没有重新构建或缓存未清理清理构建缓存后重新安装
flash-attention版本冲突环境中存在多个flash-attention统一到指定版本并重建环境

6.1 编译链接报错的通用排查顺序

遇到编译链接报错时,我的排查顺序是固定的。第一,先看完整报错信息里出现的第一个error,通常根因在那里。第二,确认当前shell的CUDA_HOME、LD_LIBRARY_PATH是否指向正确。第三,检查是否存在多个Python环境干扰,确保当前激活的是准备装vllm的那个环境。

如果这些都没问题,再看依赖版本是否与vllm要求一致。很多时候编译报错看起来是缺某个系统库,实际是PyTorch版本太老或太新导致链接的符号不存在。这时候用源码中标注的支持版本去重新安装依赖,通常能解决大部分问题。

6.2 运行时CUDA mismatch的应对策略

运行时出现CUDA mismatch,有时候并不是真的版本不匹配,而是进程加载了多个不同版本的CUDA库。这种情况常见于系统里同时装了多个工具包,或者某个依赖库自己带了CUDA运行时副本。

我的建议是:用ldd命令检查最终加载的动态库来源,看看CUDA运行时到底来自哪里。如果动态库来自某个conda包而不是工具包目录,那就要在环境变量里调整搜索顺序。也有一种情况是vllm编译时用了工具包A的库,运行时却被环境变量优先指向工具包B,那就要统一两者的路径。

6.3 显存不足与缓存策略调整

显存不足几乎是跑大模型必然要面对的问题。vllm的显存分配策略里有几个关键参数可以调,比如gpu_memory_utilization控制显存使用上限,max_num_batched_tokens控制批量token上限,max_num_seqs控制并发序列数。

我实际测试下来,当显存不足时优先降低gpu_memory_utilization,从0.9降到0.8甚至0.7,往往比盲目调小批量更有效。因为vllm的PagedAttention会预留一部分显存做KV cache,预留过大会挤占模型权重空间,过小则无法容纳足够多的批处理序列。找出那个平衡点,是调优的关键。

如果改完这些参数仍然OOM,那就需要考虑模型切分或者量化。源码安装的好处在这里也很明显,你可以更自由地接各种后端,试多种量化方案,不需要等官方发布新包。

6.4 其他经典坑

还有一个我在实际运行中踩过很多次的坑:同机多卡跑分布式推理时,每张卡的显存总量和显存分配参数可能会被错误地乘以卡数。要注意vllm的显存参数在分布式环境下是基于单卡的,不是全局。你如果不仔细看文档,很容易把全局显存当单卡显存去设置,结果每张卡都分配了过大的空间,直接OOM。

另外,日志级别也不容忽视。vllm日志很多,默认会打印大量加载细节,调试时很有用,但线上环境建议调低日志级别,避免日志写入影响推理性能。在源码环境里改日志级别非常方便,这是我认为源码安装带来的额外好处。

个人经验收尾

最后分享一个我的心得体会。很多人一提到源码安装就觉得是硬核玩家才该做的事,实际上在我看来,vllm源码安装更多是“一次投资,长期收益”的选择。第一次编译确实耗时,但只要你把环境整理干净、把依赖版本锁定,后续每次迭代都是增量式的,修改模型实现后重编的速度远比你想象中快。而且在这个过程中,你会慢慢理解vllm的调度器、缓存管理、算子分发是怎么串起来的,这种理解是翻多少篇博客都换不来的。

如果你正准备给一个官方尚未支持的模型做推理服务化,我的建议是别怕麻烦,直接按上面流程走一遍源码安装。过程中把每一步遇到的报错记录下来,一次性把环境做成一个可复用的模板。等到再跑第二个模型时,你会发现自己已经可以很从容地改模型实现、调显存参数、做压测对比了。这种掌控力,才是源码安装带给你的真正价值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 4:53:27

CTF解题实战:系统性思维与工业级安全分析方法论

1. 项目概述&#xff1a;这不是“打游戏”&#xff0c;而是一场系统性思维的实战拉练“CTF解题小记——心得记录1”这个标题乍看平平无奇&#xff0c;像极了某位刚入门的选手随手记下的学习笔记。但如果你在一线带过至少三届CTF校队、陪跑过五场省级以上赛事、亲手调试过二十台…

作者头像 李华
网站建设 2026/10/15 4:53:07

光伏储能毕设忙到秃头?这些论文工具可以这样搭着用 ☀️[特殊字符]

先把场景说清楚&#xff1a;新能源科学与工程属于工学下的动力工程及工程热物理类&#xff0c;很多同学到了毕业设计会遇到一类很典型的任务——“分布式光伏储能系统容量优化配置及经济性分析”。 你可能要完成的成果包括&#xff1a; 整理当地辐照度、温度、负荷或电价数据…

作者头像 李华
网站建设 2026/10/15 4:51:59

Linux服务器Java环境配置全指南:JDK安装与环境变量实战

一台刚从机房上架、装好操作系统的裸服务器&#xff0c;摆在我面前的第一件事永远是配Java环境。这事看着简单&#xff0c;无非就是装个JDK设个变量&#xff0c;但实际动手的人都知道&#xff0c;里头的细节坑多到能写一本书。版本选错、环境变量不生效、Maven找不到JDK、应用一…

作者头像 李华
网站建设 2026/10/15 4:51:38

静态路由实现内外网同时访问:默认路由冲突原理与配置指南

前些天有位同事抱着笔记本过来找我说&#xff0c;插上公司内网网线之后浏览器里的外网页面就全部打不开了&#xff0c;拔掉网线又恢复正常。他一边摇头一边问&#xff1a;“我就想在同一台笔记本电脑上同时访问外网和内网&#xff0c;怎么这么费劲&#xff1f;”这个场景我相信…

作者头像 李华
网站建设 2026/10/15 4:51:35

Blender G键移动背后:transform_modal源码核心逻辑拆解

很多做Blender插件开发或者对Blender内部机制感兴趣的朋友&#xff0c;都会在某个阶段产生这样的疑惑&#xff1a;按下G键之后&#xff0c;鼠标在3D视图里滑动&#xff0c;物体跟着动&#xff0c;这个过程到底是哪个函数在驱动&#xff1f;网上能看到的大多数资料&#xff0c;讲…

作者头像 李华
网站建设 2026/10/15 4:50:39

Django Forms钩子函数实战:从校验到安全的全链路解析

1. 为什么Forms组件不是“写个HTML表单”就完事了&#xff1f;——从三个真实翻车现场说起你有没有遇到过这样的情况&#xff1a;用户提交一个注册表单&#xff0c;后端收到的却是空字符串&#xff0c;但前端明明显示填了内容&#xff1b;或者密码两次输入不一致&#xff0c;页…

作者头像 李华