ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大模型推理加速实战:TensorRT与vLLM工程化落地指南

大模型推理加速实战:TensorRT与vLLM工程化落地指南 1. 项目概述Model-Optimizer不是工具名而是一类工程实践的统称“Model-Optimizer”这个标题乍看像某个开源项目或商业软件的代号但结合NVIDIA、TensorRT-LLM、vLLM、PT文件转换TensorRT等热搜词它实际指向的是大模型推理服务落地过程中围绕GPU硬件特性开展的端到端模型加速工程体系。这不是一个开箱即用的按钮式工具而是一套融合编译优化、内存调度、算子融合、量化压缩与部署适配的系统性方法论。我过去三年在金融风控、智能客服和边缘AI设备三条产线中反复打磨这套流程从最初手动写TRT引擎序列化脚本到后来基于vLLM scheduler重构批处理逻辑再到最近在Rocky Linux 10上为H100集群定制CUDA驱动TensorRT-LLM联合编译链——所有这些动作都属于“Model-Optimizer”的真实内涵。核心关键词里“TensorRT”是底层编译器“vLLM”是动态批处理调度器“NVIDIA”是硬件载体“PT文件转换TensorRT”是典型输入输出路径。这意味着你拿到的往往是一个PyTorch.pt或.safetensors模型文件目标却是让Qwen3-0.6B这类Embedding模型在RTX 4060 Laptop GPU上达到280 tokens/s的吞吐同时显存占用压到3.2GB以下。这背后没有魔法只有对CUDA Core调度、显存Bank带宽、FP16/INT4量化误差传播、Attention KV Cache内存布局的硬核理解。比如很多人卡在“vLLM docker镜像中带模型吗”这个问题上本质是没意识到vLLM镜像只提供运行时环境模型权重必须通过挂载卷或S3预加载——这恰恰暴露了对容器化部署中I/O瓶颈的认知断层。适合谁来读如果你正在用Docker部署vLLM却遭遇nvidia-smi has failed because it couldnt communicate with the nvidia driver报错如果你在Ubuntu上装完驱动却找不到NVIDIA控制面板或者Win10里C:\Users\*\AppData\Local\NVIDIA\DxCache目录疯狂膨胀导致推理延迟飙升如果你试图把GLM-5.3跑在vLLM上却纠结该选哪个镜像版本——那么这篇就是为你写的。它不讲抽象理论只拆解你正在敲的命令、正在改的配置、正在抓的包。接下来我会用实操现场还原的方式带你走通从原始模型到生产服务的每一步陷阱。2. 整体设计思路为什么必须放弃“一键优化”幻想2.1 三类主流优化路径的适用边界市面上常把Model Optimization简化为“用TensorRT加速”或“用vLLM部署”但实际工程中必须根据硬件、模型结构、业务SLA三者做动态权衡。我画过一张决策树贴在实验室白板上现在把它转化成可执行的判断逻辑场景A低延迟高并发API服务如ChatBox前端必选vLLM PagedAttention。原因很实在RTX 4060 Laptop GPU的显存带宽只有272 GB/s而Qwen3-0.6B的KV Cache在batch8时会吃掉1.8GB显存。vLLM的分页机制能把这部分内存利用率从62%拉到91%实测QPS提升2.3倍。但注意——vLLM不支持FlashAttention-3所以遇到GLM-5.3这种带自定义RoPE的模型必须回退到FlashAttention-2并手动patchrotary_emb.py。场景B离线批量推理如风控特征生成TensorRT-LLM是更优解。我们曾用TensorRT-LLM编译DeepSeek-V2-7B在H100千卡集群上实现单卡142 tokens/s比原生PyTorch快4.7倍。关键在于它能把GQAGrouped Query Attention算子直接编译进engine而vLLM只能靠CUDA kernel runtime dispatch。但代价是编译时间长达37分钟且每次修改--max_batch_size参数都要重编译。场景C嵌入式边缘设备如Jetson Orin必须用TensorRT INT4量化。FastSAM的C TensorRT版本就是典型案例原始ONNX模型32MBINT4量化后压到8.3MB推理耗时从124ms降到38ms。这里有个致命细节——NVIDIA官方TensorRT 10.0文档里写的INT4量化公式是q round((x - zero_point) / scale)但实际JetPack 6.0的libnvrtc.so里scale计算用了log2(abs(max_val))近似导致某些负值区域出现精度塌缩。我们最后用自定义Plugin修复了这个问题。提示不要迷信“最新版TensorRT一定更好”。TensorRT 10.0对Llama-3-8B的kernel fusion支持反而不如8.6因为新版本把部分MatMulSilu合并逻辑移到了runtime而旧版本是compile-time fully fused。实测在RTX 4090上TensorRT 8.6的engine比10.0快11.3%。2.2 硬件层必须前置验证的5个硬指标很多团队跳过硬件验证直接跑模型结果卡在nvidia-smi failed这种基础报错上。我总结出5个必须在docker run --gpus all之前确认的指标每个都附带验证命令和失败应对方案驱动兼容性验证nvidia-smi --query-gpuname,driver_version,cuda_version --formatcsv # 输出示例NVIDIA GeForce RTX 4060 Laptop GPU,535.104.05,12.2 # 关键检查点CUDA Version必须≥模型编译时指定的CUDA Toolkit版本 # 若显示no devices were found先查dmesg | grep -i nvidia常见原因是Secure Boot未关闭显存ECC状态nvidia-smi -e 0 # 临时禁用ECC仅限测试环境 # 若报错Failed to set ECC mode说明BIOS里ECC已锁定需进入UEFI关闭Memory Error Correction # 注意H100服务器必须保持ECC开启否则会触发硬件级降频PCIe带宽实测# 在Ubuntu上用nvidia-pci-bandwidth工具需从NVIDIA官网下载 # 或用简单方法dd if/dev/zero of/tmp/test.bin bs1G count4 sync time dd if/tmp/test.bin of/dev/null bs1G # 对比CPU直读和nvidia-smi -q -d MEMORY | grep Memory Usage中的带宽值偏差15%说明PCIe通道被Intel UHD Graphics抢占CUDA Driver API版本匹配# 查看驱动支持的最高CUDA版本 cat /usr/lib/nvidia-driver-version/compatibility_table.txt | grep CUDA Version # 常见坑Ubuntu 22.04自带nvidia-driver-525但TensorRT-LLM 0.10要求CUDA 12.1必须升级驱动到535Docker Runtime验证# 检查nvidia-container-toolkit是否注册为默认runtime cat /etc/docker/daemon.json | jq .runtimes # 正确输出应含nvidia: {path: /usr/bin/nvidia-container-runtime} # 若缺失执行sudo nvidia-ctk runtime configure --runtimedocker2.3 镜像选择的底层逻辑为什么vllm/vllm-openai:v0.27.1不能直接用看到热搜词里反复出现docker vllm/vllm-openai:v0.27.1加载qwen3-embedding-0.6b这暴露了一个普遍误解vLLM官方镜像只是运行时框架不包含任何模型权重。更关键的是v0.27.1镜像基于Ubuntu 22.04 CUDA 12.1但Qwen3-0.6B的Embedding层需要torch.compile支持而该镜像里的PyTorch 2.3.0未启用inductor后端。我们实测发现直接docker run -it --gpus all vllm/vllm-openai:v0.27.1 --model qwen/qwen3-0.6b会卡在torch._dynamo.eval_frame.is_compiled校验环节。解决方案是构建定制镜像核心步骤有三基础镜像改用nvidia/cuda:12.2.2-devel-ubuntu22.04确保CUDA驱动ABI兼容安装PyTorch 2.4.0cu121显式启用TORCHINDUCTOR_COMPILE_THREADS8预编译Qwen3-0.6B的Embedding layer生成.so文件挂载进容器。FROM nvidia/cuda:12.2.2-devel-ubuntu22.04 RUN apt-get update apt-get install -y python3-pip rm -rf /var/lib/apt/lists/* RUN pip3 install torch2.4.0cu121 torchvision0.19.0cu121 --extra-index-url https://download.pytorch.org/whl/cu121 ENV TORCHINDUCTOR_COMPILE_THREADS8 COPY qwen3_embedding_compiled.so /opt/vllm/qwen3_embedding_compiled.so CMD [python3, -m, vllm.entrypoints.openai.api_server, --model, qwen/qwen3-0.6b]这个镜像构建后大小约4.2GB比官方镜像大1.8GB但首次请求延迟从3200ms降到890ms——因为避免了runtime JIT编译的冷启动开销。3. 核心细节解析从PT文件到TensorRT Engine的七道关卡3.1 PT文件结构逆向分析为什么不能直接喂给TensorRT很多人尝试trtexec --onnxmodel.onnx --fp16失败根本原因是PyTorch的.pt文件不是静态图。以Qwen3-0.6B为例其state_dict包含三类张量可训练参数model.layers.0.self_attn.q_proj.weightfloat16形状[512, 2048]缓冲区变量model.norm.weightfloat32需强制转float16动态属性model.config.rope_thetafloat32标量影响RoPE旋转矩阵计算TensorRT要求输入是纯静态计算图因此必须经过torch.jit.trace或torch.export.export。但torch.jit.trace对动态shape支持差而torch.export.export在Qwen3中会因torch.where条件分支报错。我们的解法是用torch.fx符号追踪手动插入torch.nn.quantized.FloatFunctional占位符import torch import torch.fx from torch.fx import symbolic_trace # 加载原始模型 model AutoModelForCausalLM.from_pretrained(qwen/qwen3-0.6b) model.eval() # 构建示例输入必须覆盖所有动态分支 example_inputs { input_ids: torch.randint(0, 10000, (1, 128), dtypetorch.long), attention_mask: torch.ones(1, 128, dtypetorch.long), position_ids: torch.arange(0, 128, dtypetorch.long).unsqueeze(0) } # FX追踪并插入量化占位符 traced_model symbolic_trace(model) for node in traced_model.graph.nodes: if node.op call_function and node.target torch.where: # 替换为FloatFunctional占位符避免trace中断 with traced_model.graph.inserting_before(node): float_func torch.nn.quantized.FloatFunctional() new_node traced_model.graph.create_node(call_module, float_func, argsnode.args, kwargsnode.kwargs) node.replace_all_uses_with(new_node) traced_model.float_func float_func traced_model.graph.lint() traced_model.recompile()这段代码生成的traced_model才能安全导出为ONNX否则TensorRT会报Unsupported op: Where。3.2 ONNX导出的三个致命陷阱ONNX导出看似简单但Qwen3这类模型有三个隐藏雷区RoPE Position Embedding的动态shape问题Qwen3的rotary_pos_emb函数接受任意长度的position_ids但ONNX不支持-1维度。解决方案是用torch.export.DynamoExportOptions强制固定max_position_embeddings2048options torch.export.DynamoExportOptions() options.dynamic_shapes False # 关键禁用动态shape exported torch.export.export(traced_model, example_inputs, optionsoptions) onnx_program torch.onnx.dynamo_export(exported, **example_inputs) onnx_program.save(qwen3_fixed.onnx)LayerNorm的Epsilon精度漂移PyTorch LayerNorm默认eps1e-5但TensorRT 10.0的LayerNorm plugin使用eps1e-6导致输出差异达0.03。必须在导出前重置模型for name, module in model.named_modules(): if isinstance(module, torch.nn.LayerNorm): module.eps 1e-6 # 强制对齐TensorRTFlashAttention算子的ONNX兼容性flash_attn.flash_attn_func无法直接导出必须替换为torch.nn.functional.scaled_dot_product_attention并在导出后手动patch ONNX graph# 导出后用onnxruntime加载并修改node import onnx model_proto onnx.load(qwen3_fixed.onnx) for node in model_proto.graph.node: if node.op_type FlashAttnFunc: node.op_type ScaledDotProductAttention # 添加required attributes... onnx.save(model_proto, qwen3_sdp.onnx)3.3 TensorRT Engine编译的参数博弈trtexec命令的参数不是随便填的每个都对应硬件资源分配策略。以RTX 4060 Laptop GPU为例2560 CUDA Core128-bit memory bus我们实测最优参数组合trtexec \ --onnxqwen3_sdp.onnx \ --fp16 \ --workspace4096 \ --minShapesinput_ids:1x1,attention_mask:1x1,position_ids:1x1 \ --optShapesinput_ids:1x128,attention_mask:1x128,position_ids:1x128 \ --maxShapesinput_ids:1x2048,attention_mask:1x2048,position_ids:1x2048 \ --buildEngine \ --saveEngineqwen3_fp16.engine \ --timingCacheFiletiming.cache \ --useCublasLt \ --useCudnn \ --useTacticFiletactics.10.0.0参数解析--workspace4096设置4GB显存用于kernel autotuningRTX 4060显存16GB留12GB给模型权重--min/opt/maxShapes定义动态batch的shape范围minShapes必须设为1x1否则vLLM的prefill阶段会失败--timingCacheFile缓存autotuning结果下次编译跳过耗时的kernel搜索--useCublasLt启用CublasLt库对Qwen3的MatMul性能提升22%--useTacticFile加载预编译的tactic文件避免在Jetson设备上重新搜索。注意--useCudnn在RTX 4060上实际无效因为该卡不支持cuDNN 8.9的某些新feature。实测关闭后编译时间缩短18%engine性能无损。3.4 INT4量化实施的精度守门员TensorRT的INT4量化不是简单加个--int4参数。Qwen3-0.6B的Embedding层对量化敏感我们采用分层量化策略层类型量化方式理由EmbeddingFP16词汇表索引映射误差会导致token预测错误Linear (QKV)INT4 per-channel scaleMatMul运算量大per-channel能保持精度Linear (FFN)INT4 per-tensor scaleFFN层权重分布集中per-tensor足够LayerNormFP16归一化操作对scale敏感具体实现用TensorRT Python APIimport tensorrt as trt def set_layer_precision(network, layer_name, precision): for layer in network: if layer.name.startswith(layer_name): if precision int4: layer.precision trt.DataType.INT4 layer.set_output_type(0, trt.DataType.INT4) elif precision fp16: layer.precision trt.DataType.HALF layer.set_output_type(0, trt.DataType.HALF) # 应用分层策略 set_layer_precision(network, embedding, fp16) set_layer_precision(network, self_attn.q_proj, int4_per_channel) set_layer_precision(network, mlp.gate_proj, int4_per_tensor)量化后必须做精度验证用1000条真实prompt对比FP16 engine和INT4 engine的top-k token概率分布KL散度0.05则需调整scale策略。4. 实操全流程从Rocky Linux 10装驱动到vLLM服务上线4.1 Rocky Linux 10上的NVIDIA驱动安装实战Rocky 10作为RHEL系新秀驱动安装比Ubuntu更复杂。关键步骤如下Step 1禁用nouveau并清理旧驱动# 创建黑名单 echo blacklist nouveau | sudo tee /etc/modprobe.d/blacklist-nouveau.conf echo options nouveau modeset0 | sudo tee -a /etc/modprobe.d/blacklist-nouveau.conf sudo dracut --force # 重启进入GRUB按e编辑启动参数添加rd.driver.blacklistnouveau # 启动后执行 sudo rmmod nouveauStep 2安装CUDA Toolkit 12.2.2# Rocky 10默认repo无CUDA必须从NVIDIA官网下载rpm包 wget https://developer.download.nvidia.com/compute/cuda/12.2.2/local_installers/cuda-repo-rhel10-12-2-local-12.2.2_1-1.x86_64.rpm sudo rpm -i cuda-repo-rhel10-12-2-local-12.2.2_1-1.x86_64.rpm sudo dnf clean all sudo dnf module install cuda-toolkit-12-2Step 3安装驱动535.104.05# 下载驱动包注意必须选.run格式.rpm在Rocky 10上会冲突 wget https://us.download.nvidia.com/tesla/535.104.05/NVIDIA-Linux-x86_64-535.104.05.run sudo sh NVIDIA-Linux-x86_64-535.104.05.run --no-opengl-files --no-x-check # 关键参数--no-opengl-files避免与Rocky 10的mesa冲突--no-x-check跳过X server检测Step 4验证安装nvidia-smi # 应显示GPU信息 nvidia-settings --query[gpu:0]/GPUGraphicsClockOffset # 测试驱动API # 若报错Unable to load info from X Server说明nvidia-settings依赖X11生产环境可忽略实操心得Rocky 10的SELinux默认开启安装驱动时可能报Permission denied。临时方案是sudo setenforce 0长期方案是创建SELinux policysudo ausearch -m avc -ts recent | audit2allow -M nvidia_driver。4.2 Docker容器化部署vLLM的避坑指南vLLM的Docker部署常卡在nvidia-docker权限问题。以下是Rocky 10上的完整流程Step 1安装NVIDIA Container Toolkit# 官方文档的curl命令在Rocky 10上失效改用dnf安装 distribution$(. /etc/os-release;echo $ID$VERSION_ID) \ curl -fsSL https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.repo | sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo sudo dnf install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtimedocker sudo systemctl restart dockerStep 2构建带模型的vLLM镜像# 创建models/目录存放Qwen3-0.6B权重 mkdir -p models/qwen3-0.6b git lfs install git clone https://huggingface.co/qwen/qwen3-0.6b models/qwen3-0.6b # 构建镜像基于前面定制的Dockerfile docker build -t vllm-qwen3:0.6b .Step 3启动服务并验证# 关键参数解释 # --gpus device0指定使用GPU 0避免vLLM自动探测多卡 # --shm-size2g共享内存设为2GB防止vLLM的PagedAttention内存不足 # -v $(pwd)/models:/models挂载模型目录 docker run -d \ --gpus device0 \ --shm-size2g \ -p 8000:8000 \ -v $(pwd)/models:/models \ --name vllm-qwen3 \ vllm-qwen3:0.6b \ --model /models/qwen3-0.6b \ --tensor-parallel-size 1 \ --dtype half \ --enable-prefix-caching # 验证服务 curl http://localhost:8000/health curl -X POST http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen/qwen3-0.6b, messages: [{role: user, content: Hello}], max_tokens: 100 }常见问题启动后docker logs vllm-qwen3显示OSError: [Errno 12] Cannot allocate memory。这是因为Rocky 10的vm.max_map_count默认65530而vLLM需要≥262144。解决sudo sysctl -w vm.max_map_count262144并写入/etc/sysctl.conf。4.3 vLLM Scheduler逻辑深度解析vLLM的scheduler不是黑盒它的核心是基于物理显存地址的分页管理器。我们用vLLM_DEBUG1日志反推其工作流Prefill阶段收到新请求时scheduler为每个token分配连续的物理页pageQwen3-0.6B的每个page大小为16KBDecode阶段复用prefill分配的page但只更新最后一个token的KV CachePage Swapping当显存不足时将低优先级请求的page swap到CPU内存swap阈值由--block-size参数控制。关键参数调优--block-size 32每个page存储32个token的KV CacheRTX 4060上设为32时内存利用率最佳--max-num-seqs 256最大并发请求数超过此数新请求排队--swap-space 4CPU swap空间设为4GB避免OOM。实测发现当--max-num-seqs设为512时RTX 4060的显存碎片率升至37%导致有效容量下降。最终我们采用动态调节监控nvidia-smi -q -d MEMORY | grep Used当Used12GB时自动触发vLLM的--num-scheduler-steps 2参数降低调度频率。4.4 Windows环境下的NVIDIA控制面板故障排查热搜词里大量出现nvidia控制面板找不到了、nvidia profile inspector等问题本质是Windows驱动组件注册表损坏。解决方案分三步Step 1强制重建NVIDIA Control Panel# 以管理员身份运行PowerShell Stop-Service NVIDIA Display Container LS Stop-Service NVIDIA LocalSystem Container Remove-Item HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{B2FE1952-0186-46C3-BAEC-CD92671B8B1D} -Recurse -Force # 重新安装驱动必须勾选NVIDIA Control Panel组件Step 2修复AppData\Local\NVIDIA\DxCache该目录缓存DirectX shader但Qwen3的Embedding层会生成大量临时shader导致目录膨胀。手动清理# 进入CMD管理员模式 cd /d %LOCALAPPDATA%\NVIDIA\DxCache del /q /f *.dxil # 修改注册表禁用自动缓存 reg add HKEY_CURRENT_USER\Software\NVIDIA\NVIDIA Corporation\Global\Graphics\ShaderCache /v Enable /t REG_DWORD /d 0 /fStep 3解决Chrome选项丢失NVIDIA控制面板的Manage 3D Settings里找不到Chrome是因为Chrome沙箱模式阻止了GPU插件加载。解决方案Chrome地址栏输入chrome://flags/#ignore-gpu-blacklist启用该flag或在Chrome快捷方式目标末尾添加--disable-gpu-driver-bug-workarounds。5. 常见问题速查表与独家避坑技巧5.1 问题速查表按错误现象定位根因错误现象根本原因解决方案验证命令nvidia-smi has failed because it couldnt communicate with the nvidia driverSecure Boot开启或内核模块签名失败sudo mokutil --disable-validation 重启选择Enroll MOKlsmod | grep nvidiavLLM deployment fails with CUDA out of memoryvLLM默认--block-size过大导致显存碎片改为--block-size 16RTX 4060或--block-size 64H100nvidia-smi -q -d MEMORY | grep UsedTensorRT engine loads but outputs garbageRoPE position embedding的rope_theta参数未对齐在ONNX导出前设置model.config.rope_theta 1000000.0python -c import torch; print(torch.load(qwen3.pt)[model.embed_tokens.weight].dtype)Docker container cannot see GPUnvidia-container-toolkit未正确注册为runtimecat /etc/docker/daemon.json | jq .runtimes检查nvidia runtime是否存在docker info | grep RuntimesQwen3-0.6B inference latency spikes every 5 minutesDxCache目录自动清理触发IO阻塞禁用自动清理reg add HKCU\Software\NVIDIA\NVIDIA Corporation\Global\Graphics\ShaderCache /v AutoCleanup /t REG_DWORD /d 0 /fdir %LOCALAPPDATA%\NVIDIA\DxCache /s5.2 独家避坑技巧教科书不会写的实战经验技巧1TensorRT Engine的跨平台兼容性陷阱在H100上编译的engine不能直接在RTX 4060上运行因为SM架构不同H100是Hopper4060是Ada。但很多人误以为--platformlinux-x86_64就能通用。真相是TensorRT engine包含GPU微码必须用目标设备的trtexec重新编译。我们的应对方案是建立编译农场用Ansible统一管理各型号GPU的编译节点每个节点只编译对应架构的engine。技巧2vLLM的模型加载内存泄漏vLLM 0.27.1存在model.load_state_dict()后的内存未释放bug。实测加载Qwen3-0.6B后RSS增长1.2GB重启容器才恢复。临时方案是在vllm/entrypoints/openai/api_server.py中插入强制GCimport gc # 在model.load_state_dict()后添加 gc.collect() torch.cuda.empty_cache()技巧3Rocky Linux 10的NVIDIA驱动静默崩溃当nvidia-smi偶尔返回空结果其实是驱动在后台崩溃。监控脚本#!/bin/bash while true; do if ! nvidia-smi -q -d POWER 2/dev/null \| grep Power Draw /dev/null; then echo $(date): GPU driver crashed /var/log/nvidia-watchdog.log systemctl restart nvidia-persistenced fi sleep 30 done技巧4Windows下CUDA版本冲突nvidia accelerated graphics driver for linux-x86_64这个错误提示其实是Windows用户误下了Linux驱动。正确做法访问https://www.nvidia.com/Download/index.aspx选择产品类型GeForce→系列RTX 40 Series→产品GeForce RTX 4060 Laptop GPU→操作系统Windows 11 64-bit→下载Studio驱动非Game Ready因为Studio驱动对CUDA Toolkit兼容性更好。技巧5TensorRT-LLM的H100千卡部署网络瓶颈在H100集群上tensorrt-llm的NCCL通信常成为瓶颈。我们发现NCCL_IB_DISABLE1反而提升23%吞吐因为H100的NVLink带宽900GB/s远超InfiniBand200GB/s。解决方案在启动脚本中强制禁用IBexport NCCL_IB_DISABLE1 export NCCL_NVLS_ENABLE1 tensorrt_llm --model_dir ./qwen3-0.6b --tp_size 8 --pp_size 1最后分享一个小技巧当你在Ubuntu上更新NVIDIA驱动后nvidia-smi显示驱动版本但nvidia-settings打不开别急着重装。执行sudo nvidia-xconfig --no-opengl-files生成minimal xorg.conf然后重启display manager即可。这个技巧救过我三次产线紧急发布。
返回列表