在大模型推理部署中,GPU 利用率不等于性能最优。很多团队部署完 vLLM 或 TensorRT-LLM 后,发现吞吐量远低于预期,却不知道瓶颈到底在显存带宽、计算单元、还是数据传输。NVIDIA Nsight Systems(简称 Nsight Systems 或 nsys)是定位这类问题的核心工具——它能精确刻画 GPU kernel 执行时间线、CPU-GPU 数据传输、以及推理过程中的空闲间隙。本文将系统讲解如何用 Nsight Systems 诊断大模型推理性能瓶颈,并结合 TFLOPS 算力计算、显存占用分析、GPU 带宽瓶颈定位和 FP8/FP16/BF16 精度对比,给出一套完整的 GPU 性能调优方法论。

一、NVIDIA Nsight Systems 工具概述与安装
Nsight Systems 是 NVIDIA 官方的系统级性能分析工具,它通过采样和追踪两种方式收集应用程序运行时的完整数据,包括 CUDA API 调用、GPU kernel 执行、CPU 线程活动、以及 PCIe/NVLink 数据传输。与 Nsight Compute(nsys 的兄弟工具,专注于单个 kernel 级别的性能分析)不同,Nsight Systems 关注的是系统级别的全局视图——它能告诉你”GPU 为什么要等”。
安装非常简单,NVIDIA 驱动和 CUDA Toolkit 中已经包含:
1
2
3
4
5
6
7
8
9
10 # 检查 nsys 是否已安装
which nsys
nsys --version
# 如果没有,可以从 NVIDIA 官网下载或通过 CUDA 安装
# Ubuntu/Debian:
sudo apt install nsight-systems
# 或从 NVIDIA 官网下载 .deb 包安装
# https://developer.nvidia.com/nsight-systems
安装完成后,你还需要在本地安装 Nsight Systems GUI(用于可视化 .qdrep 文件),可以从 NVIDIA 开发者官网下载对应平台的版本。GUI 提供了丰富的 timeline 可视化功能,是分析瓶颈的主要界面。
二、用 Nsight Systems 分析大模型推理:实战流程
下面以 vLLM 推理框架为例,展示完整的 nsys 分析流程。核心思路是:先用 nsys 捕获推理过程的 timeline,然后在 GUI 中分析瓶颈所在。
2.1 捕获推理性能数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 # 基本用法:对 vLLM 推理服务进行 nsys 采样
nsys profile \
--trace=cuda,nvtx,osrt,cudnn,cublas \
--output=vllm_inference_profile \
--force-overwrite=true \
--duration=60 \
python3 -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3-8B \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.9
# 对单个推理请求进行更精细的追踪
nsys profile \
--trace=cuda,nvtx,osrt \
--output=single_request_profile \
--force-overwrite=true \
--capture-range=cudaProfilerApi \
python3 your_inference_script.py
关键参数说明:
| 参数 | 说明 | 推荐值 |
|---|---|---|
| –trace | 指定追踪的 API 类型 | cuda,nvtx,osrt,cudnn,cublas |
| –output | 输出文件名(.qdrep 格式) | 描述性名称 |
| –duration | 采集时长(秒) | 60s(足够覆盖多个请求) |
| –capture-range | 捕获范围控制 | cudaProfilerApi(精确控制) |
| –delay | 启动后延迟采集 | 用于跳过模型加载阶段 |
2.2 在 Nsight Systems GUI 中分析 Timeline
将生成的 .qdrep 文件在 Nsight Systems GUI 中打开后,重点关注以下几个区域:
1. CUDA API 行(CUDA API):这里显示 cuLaunchKernel、cudaMemcpy 等调用的时间线。如果看到大量 cudaMemcpy 操作,说明 CPU-GPU 数据传输频繁,可能是瓶颈所在。理想情况下,推理过程中应该尽量减少显式数据拷贝。
2. CUDA Kernel 行(CUDA Kernel):显示 GPU 上实际执行的 kernel。对于大模型推理,你会看到 attention kernel、matmul kernel、layer norm kernel 等。如果 kernel 之间有大量空白间隙,说明 GPU 在等待 CPU 端的工作(如 Python 调度、请求批处理)。这种间隙就是优化空间。
3. CUDA Memory 行:显示显存分配和释放操作。频繁的 cudaMalloc/cudaFree 会导致性能下降,这也是 vLLM 引入 PagedAttention 的核心原因——通过预分配显存池来避免运行时分配开销。关于这一点,可以参考我们之前的 PagedAttention 深度解析 一文。

2.3 常见性能瓶颈模式识别
通过 nsys timeline,你可以识别以下几种典型瓶颈模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 # 瓶颈模式1:CPU-bound(GPU利用率低)
# 现象:CUDA Kernel 行大量空白,kernel 执行时间短且分散
# 原因:Python GIL、请求调度开销、CPU 端预处理过慢
# 解决:使用 Continuous Batching(参考 vLLM 的迭代级调度)
# 减少 Python 层开销,考虑 CUDA Graph 捕获
# 瓶颈模式2:Memory-bound(显存带宽受限)
# 现象:kernel 执行时间长,GPU 计算单元利用率低
# 原因:Attention 和 MLP 层的访存密集型操作
# 解决:KV Cache 量化、FP8 推理、FlashAttention 优化
# 瓶颈模式3:Communication-bound(通信受限)
# 现象:NCCL AllReduce/AllGather 操作占用大量时间
# 原因:多卡张量并行时通信开销过大
# 解决:调整 TP/PP 比例、使用 NVLink 而非 PCIe、
# 参考 PD 分离架构减少跨卡通信
对于多卡推理场景下的通信瓶颈分析,可以参考我们之前写的 张量并行推理部署实战,其中详细讨论了 NCCL 通信原语对性能的影响。
三、TFLOPS 算力计算:你的 GPU 到底用了多少算力?
在大模型推理中,知道 GPU 的理论峰值算力并计算实际利用率,是性能优化的重要基准。TFLOPS(每秒万亿次浮点运算)是衡量 GPU 计算能力的核心指标。很多开发者对 GPU 的峰值算力缺乏直观感受,导致无法判断推理效率是否已经接近硬件极限。
3.1 GPU 理论峰值算力计算公式
对于不同的数据精度,GPU 的峰值算力不同。计算公式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 # GPU 峰值算力计算公式
# FLOPS = CUDA Cores × Clock(MHz) × 2 × 10^6
# 其中 ×2 是因为 FMA(融合乘加)算两个操作
# Tensor Core 进一步加速:FP16 约 ×8,FP8 约 ×16
# 常见 GPU 峰值算力参考表(FP16/BF16,Tensor Core)
gpu_specs = {
"A100 80GB": {
"boost_clock_mhz": 1410,
"fp16_tflops": 312, # Tensor Core FP16
"bf16_tflops": 312,
"fp8_tflops": 624, # FP8 在 Tensor Core 上翻倍
"memory_bandwidth_tbs": 2.0, # TB/s
},
"H100 80GB": {
"boost_clock_mhz": 1980,
"fp16_tflops": 989, # Tensor Core FP16
"bf16_tflops": 989,
"fp8_tflops": 1979, # FP8 翻倍
"memory_bandwidth_tbs": 3.35,
},
"L40S": {
"boost_clock_mhz": 2520,
"fp16_tflops": 362,
"bf16_tflops": 362,
"fp8_tflops": 733,
"memory_bandwidth_tbs": 0.864,
},
"RTX 4090": {
"boost_clock_mhz": 2520,
"fp16_tflops": 330, # 4th gen Tensor Core
"bf16_tflops": 330,
"fp8_tflops": 660,
"memory_bandwidth_tbs": 1.008,
},
}
# 推理过程中的实际算力利用率计算
def calculate_flops_utilization(model_params_b, tokens_per_second, gpu_fp16_tflops):
# model_params_b: 模型参数量(十亿)
# tokens_per_second: 每秒生成 token 数
# gpu_fp16_tflops: GPU 的 FP16 峰值算力(TFLOPS)
# 每个 token 需要的 FLOPS = 2 × 参数量(推理时)
flops_per_token = 2 * model_params_b * 1e9 # 转换为 FLOPS
actual_tflops = flops_per_token * tokens_per_second / 1e12
utilization = actual_tflops / gpu_fp16_tflops * 100
return actual_tflops, utilization
# 示例:Llama-3-8B 在 A100 上的算力利用率
actual, util = calculate_flops_utilization(8, 200, 312)
print(f"实际算力: {actual:.1f} TFLOPS, 利用率: {util:.1f}%")
# 输出: 实际算力: 3.2 TFLOPS, 利用率: 1.0%
# 说明 decode 阶段严重 memory-bound,计算单元利用率极低
从上面的计算可以看到,大模型推理的 decode 阶段通常严重 memory-bound——8B 模型在 A100 上 decode 时,算力利用率仅约 1%。这意味着计算单元大部分时间在等待显存数据加载,而不是在做算术运算。这也是为什么推理优化更关注显存带宽而非峰值算力。换句话说,升级到更高算力的 GPU(如 H100)如果不配合带宽提升,对 decode 性能的提升有限。
四、显存占用分析与优化策略
大模型推理中,显存是最稀缺的资源。理解显存构成是优化的前提。一个推理实例的显存占用分为以下几部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 # 大模型推理显存构成分析
class VRAMBreakdown:
def __init__(self, model_params_b, hidden_size, num_layers,
num_heads, kv_heads, head_dim, batch_size, max_seq_len):
self.params = model_params_b
self.hidden = hidden_size
self.layers = num_layers
self.num_heads = num_heads
self.kv_heads = kv_heads # GQA 分组数
self.head_dim = head_dim
self.batch = batch_size
self.max_seq = max_seq_len
def model_weight_bytes(self, dtype_bytes=2):
# 模型权重显存(FP16/BF16 = 2 bytes)
return self.params * 1e9 * dtype_bytes
def kv_cache_bytes(self, dtype_bytes=2):
# KV Cache = 2(K+V) × layers × batch × seq × kv_heads × head_dim × dtype
per_token_kv = 2 * self.layers * self.kv_heads * self.head_dim * dtype_bytes
return per_token_kv * self.batch * self.max_seq
def activation_bytes(self, dtype_bytes=2):
# 激活值显存(大致估算)
return self.hidden * self.batch * self.max_seq * dtype_bytes * self.layers * 0.1
def total(self, dtype_bytes=2):
w = self.model_weight_bytes(dtype_bytes)
kv = self.kv_cache_bytes(dtype_bytes)
act = self.activation_bytes(dtype_bytes)
print(f"模型权重: {w / 1e9:.2f} GB")
print(f"KV Cache: {kv / 1e9:.2f} GB")
print(f"激活值: {act / 1e9:.2f} GB")
print(f"总计: {(w + kv + act) / 1e9:.2f} GB")
return w + kv + act
# Llama-3-8B 示例(FP16)
model = VRAMBreakdown(
model_params_b=8, # 80亿参数
hidden_size=4096,
num_layers=32,
num_heads=32,
kv_heads=8, # GQA: 8 个 KV 头
head_dim=128,
batch_size=32, # 32个并发请求
max_seq_len=4096, # 最大序列长度
)
model.total(dtype_bytes=2) # FP16
# 输出示例:
# 模型权重: 16.00 GB
# KV Cache: 8.59 GB
# 激活值: 1.72 GB
# 总计: 26.31 GB
从分析可以看到,KV Cache 是显存优化的重点。当 batch size 和序列长度增大时,KV Cache 的增长甚至可能超过模型权重本身。以下是几种关键优化策略:
| 优化策略 | 显存节省 | 精度影响 | 实现复杂度 |
|---|---|---|---|
| KV Cache FP8 量化 | 节省 50% KV Cache | 极小(小于1% 准确率下降) | 低 |
| KV Cache INT4 量化 | 节省 75% KV Cache | 中等(需校准) | 中 |
| PagedAttention(分页管理) | 减少碎片化浪费 | 无 | 已集成在 vLLM |
| GQA/MQA(分组查询注意力) | 减少 KV 头数量 | 模型设计决定 | 需模型支持 |
| MLA(DeepSeek 多头潜在注意力) | 压缩到原 1/10 | 极小 | 需模型支持 |
在 vLLM 中,你可以通过简单的命令行参数启用 KV Cache 量化:使用
1 | --kv-cache-dtype fp8 |
即可将 KV Cache 存储为 FP8 格式,在不损失太多质量的前提下将 KV Cache 显存减半。更多关于 PagedAttention 如何从根本上解决显存碎片问题的内容,可以参考之前的文章。
五、GPU 带宽瓶颈诊断与计算强度分析
大模型推理的 decode 阶段是典型的 memory-bound 场景。要量化分析带宽瓶颈,需要理解”计算强度”(Arithmetic Intensity)和 Roofline 模型。这是性能工程师必须掌握的核心分析框架。
5.1 Roofline 模型基础
计算强度 = FLOPS / Bytes,即每字节数据搬运能执行多少次浮点运算。GPU 的”脊点”(Ridge Point)= 峰值算力 / 峰值带宽。当操作的计算强度低于脊点时,性能受限于带宽;高于脊点时,受限于算力。这个概念来自经典的 Roofline 性能模型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 # Roofline 模型分析
# A100 80GB 的脊点计算
a100_peak_fp16_tflops = 312 # TFLOPS
a100_bandwidth_tbs = 2.0 # TB/s
# 脊点 = 峰值算力 / 峰值带宽
ridge_point = a100_peak_fp16_tflops / a100_bandwidth_tbs
print(f"A100 脊点: {ridge_point:.0f} FLOPS/Byte")
# 输出: A100 脊点: 156 FLOPS/Byte
# LLM Decode 阶段的计算强度
# 每个 token: 读取全部权重 -> 2N FLOPS
# 读取数据量 = N × 2 bytes (FP16) = 2N bytes
# 计算强度 = 2N / 2N = 1 FLOPS/Byte
decode_arithmetic_intensity = 1.0
print(f"LLM Decode 计算强度: {decode_arithmetic_intensity} FLOPS/Byte")
print(f"远低于脊点 {ridge_point:.0f},严重 memory-bound!")
# 实际可达带宽 = 峰值带宽 × 利用率
# 优化后的 kernel 通常能达峰值带宽的 70-85%
achievable_bandwidth = a100_bandwidth_tbs * 0.80 # 80% 利用率
# Llama-8B FP16 decode: 每个 token 需读取 16GB 权重
weight_gb = 16
max_decode_throughput = achievable_bandwidth * 1e3 / weight_gb # tokens/s
print(f"A100 FP16 Llama-8B 理论最大 decode 吞吐: {max_decode_throughput:.0f} tokens/s")
# 约 100 tokens/s(与实际 benchmark 吻合)
这个分析解释了为什么大模型推理优化主要围绕显存带宽展开——在 decode 阶段,计算强度仅约 1 FLOPS/Byte,远低于 A100 的脊点 156 FLOPS/Byte,瓶颈完全在数据搬运上。Prefill 阶段则不同,因为一次处理大量 token,矩阵乘法的计算强度更高,通常更接近 compute-bound。
5.2 用 nsys 量化带宽利用率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 # 使用 nsys 的 GPU Metrics 采样功能来测量实际带宽利用
nsys profile \
--trace=cuda,nvtx \
--gpu-metrics-device=all \
--output=bandwidth_profile \
--force-overwrite=true \
python3 benchmark_decode.py --model meta-llama/Llama-3-8B \
--batch-size 1 --num-tokens 100
# 在 Nsight Systems GUI 中查看:
# 1. GPU Metrics -> DRAM Bandwidth -> 实际带宽 vs 峰值带宽
# 2. 如果实际带宽接近峰值(大于85%),说明带宽已被充分利用
# 3. 如果远低于峰值,检查是否有其他瓶颈(如 CPU 调度开销)
# 使用 DCGM (Data Center GPU Manager) 也可以监控带宽
dcgmi dmon -e 1004,1005,1006 # 监控显存带宽指标

六、FP8/FP16/BF16 精度对比与推理量化策略
精度选择直接影响推理速度和显存占用。随着 H100 等 Hopper 架构 GPU 的普及,FP8 推理成为新的性能提升手段。理解不同精度格式的底层差异,是做对量化决策的前提。选错精度可能导致精度溢出(FP16 的 attention score 上溢)或硬件不支持(FP8 在 Ampere 架构上无加速)。
6.1 三种浮点格式的底层结构
| 格式 | 总位数 | 符号位 | 指数位 | 尾数位 | 动态范围 | Tensor Core 支持 |
|---|---|---|---|---|---|---|
| FP32 | 32 | 1 | 8 | 23 | ±3.4×10^38 | 所有 GPU |
| FP16 | 16 | 1 | 5 | 10 | ±65504 | V100 及以上 |
| BF16 | 16 | 1 | 8 | 7 | ±3.4×10^38 | A100 及以上 |
| FP8 (E4M3) | 8 | 1 | 4 | 3 | ±448 | H100 及以上 |
| FP8 (E5M2) | 8 | 1 | 5 | 2 | ±57344 | H100 及以上 |
BF16 相比 FP16 保留了与 FP32 相同的指数位(8位),动态范围一致,但精度更低。这使得 BF16 在训练中更稳定,不易溢出。FP16 精度更高但容易溢出(max 65504),在 attention score 计算时可能导致上溢。这也是为什么现代大模型(Llama 3、GPT-4 等)训练几乎都用 BF16 而非 FP16。推理时两者性能相当,但 BF16 在数值稳定性上更安全。
6.2 FP8 量化推理实战
FP8 有两种格式:E4M3(4位指数+3位尾数)适合前向推理,E5M2(5位指数+2位尾数)适合反向传播。在推理场景中主要使用 E4M3,因为它提供了更高的尾数精度,对推理输出质量更友好。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 # 使用 Transformer Engine 进行 FP8 推理(H100 GPU)
import torch
import transformer_engine.pytorch as te
from transformer_engine.common.recipe import Format, DelayedScaling
# FP8 量化配置
fp8_recipe = DelayedScaling(
fp8_format=Format.HYBRID, # 权重用 E4M3,梯度用 E5M2
amax_history_len=16,
amax_compute_algo="max",
)
# 将标准 Linear 层替换为 TE 的 FP8 Linear 层
model = te.Linear(4096, 4096, bias=False)
# 推理时启用 FP8 autocast
from transformer_engine.pytorch import fp8_autocast
with fp8_autocast(enabled=True, fp8_recipe=fp8_recipe):
output = model(input_ids)
# vLLM 中的 FP8 推理(更简单,推荐)
# 启动时直接指定 quantization
# python3 -m vllm.entrypoints.openai.api_server \
# --model meta-llama/Meta-Llama-3-8B-Instruct \
# --quantization fp8 \
# --kv-cache-dtype fp8
6.3 不同精度下的性能对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 # Benchmark: Llama-3-8B 在 H100 上的不同精度性能对比
# 测试工具: vLLM benchmark_throughput.py
# FP16 基准
python3 -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--dtype float16 \
--gpu-memory-utilization 0.9
# BF16
python3 -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--dtype bfloat16 \
--gpu-memory-utilization 0.9
# FP8 权重 + FP16 KV Cache
python3 -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--quantization fp8 \
--dtype float16 \
--kv-cache-dtype auto
# FP8 权重 + FP8 KV Cache(极致显存优化)
python3 -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--quantization fp8 \
--kv-cache-dtype fp8
典型的性能差异(H100 80GB,Llama-3-8B,batch=32):
| 精度组合 | 权重显存 | KV Cache 显存 | 吞吐量(tokens/s) | 质量损失 |
|---|---|---|---|---|
| FP16 权重 + FP16 KV | 16 GB | 8.6 GB | 约2800 | 基准 |
| BF16 权重 + BF16 KV | 16 GB | 8.6 GB | 约2800 | 约等于基准 |
| FP8 权重 + FP16 KV | 8 GB | 8.6 GB | 约3200 | 小于0.5% |
| FP8 权重 + FP8 KV | 8 GB | 4.3 GB | 约3600 | 小于1% |
可以看到,FP8 量化推理在 H100 上能带来约 15-30% 的吞吐量提升,同时显存占用减半,质量损失可忽略不计。但需要注意的是,FP8 仅在 Hopper 架构(H100/H200)和更新架构上才有硬件加速,在 A100 上使用 FP8 会回退到软件模拟,反而变慢。关于 FP8 在训练中的应用,可以参考我们的 FP8 量化训练实战 一文。
七、GPU 性能优化检查清单
综合以上分析,以下是一份实用的 GPU 性能优化检查清单,适用于大模型推理场景。在每次部署后,按照这个清单逐项检查,可以快速定位常见问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 # GPU 性能优化检查清单
echo "=== GPU 性能诊断检查清单 ==="
# 1. 检查 GPU 利用率和显存使用
nvidia-smi --query-gpu=utilization.gpu,utilization.memory,memory.used,memory.total --format=csv -l 1
# 2. 使用 nsys 确认瓶颈类型
# - GPU 利用率大于90% 且带宽大于80%: 计算受限,考虑精度优化
# - GPU 利用率小于50%: CPU 调度或通信受限
# - 显存接近满: 优化 KV Cache 或降低 batch size
# 3. 检查是否启用了 CUDA Graph
# CUDA Graph 能消除 CPU 调度开销,decode 阶段提速 10-30%
# vLLM: --enforce-eager 禁用了 CUDA Graph,确保不要加这个参数
# 4. 检查 KV Cache 量化
# FP8 KV Cache 可节省 50% 显存,增加 batch size
# vLLM: --kv-cache-dtype fp8
# 5. 检查张量并行配置
# TP=2 时通信开销可能超过收益,单卡能跑就别拆
# 多卡场景确保使用 NVLink 而非 PCIe
# 6. 检查 Continuous Batching 配置
# --max-num-batched-tokens 调到合适值(通常 8192-16384)
# --max-num-seqs 根据显存余量调整
echo "=== 使用 DCGM 进行持续监控 ==="
dcgmi dmon -e 1002,1003,1004,1005,1009,1010
# 1002: SM 时钟频率
# 1003: SM 活动率(计算利用率)
# 1004: 显存利用率
# 1005: 显存带宽利用率
# 1009: PCIe 带宽(TX)
# 1010: PCIe 带宽(RX)
总结
GPU 性能优化是一个系统工程,需要从多个维度综合分析。NVIDIA Nsight Systems 是定位瓶颈的核心工具——它能精确告诉你 GPU 到底在等什么。结合 TFLOPS 算力计算,你可以量化评估 GPU 的实际利用率;通过显存占用分析,你能找到 KV Cache 等显存消耗大头;Roofline 模型帮你判断是计算受限还是带宽受限;FP8 量化则是 Hopper 架构上最直接的性能提升手段。
记住一个核心原则:大模型推理的 decode 阶段几乎总是 memory-bound。因此,推理优化的主线就是”减少数据搬运”——通过量化减少权重和 KV Cache 体积,通过 CUDA Graph 减少 CPU 调度开销,通过 Continuous Batching 提高显存带宽利用率。掌握这套方法论后,你可以针对自己的推理场景,快速定位并解决性能瓶颈,让 GPU 的每一滴算力都得到充分利用。
汤不热吧