使用TensorRT-LLM进行生产环境的部署指南

news2024/9/22 9:56:28

TensorRT-LLM是一个由Nvidia设计的开源框架,用于在生产环境中提高大型语言模型的性能。该框架是基于 TensorRT 深度学习编译框架来构建、编译并执行计算图,并借鉴了许多 FastTransformer 中高效的 Kernels 实现,并且可以利用 NCCL 完成设备之间的通讯。

虽然像vLLM和TGI这样的框架是增强推理的一个很好的起点,但它们缺乏一些优化,因此很难在生产中扩展它们。所以Nvidia在TensorRT的基础上有开发了TensorRT-LLM,像Anthropic, OpenAI, Anyscale等大公司已经在使用这个框架为数百万用户提供LLM服务。

TensorRT-LLM

与其他推理技术不同,TensorRT LLM不使用原始权重为模型服务。它会编译模型并优化内核,这样可以在Nvidia GPU上有效地服务。运行编译模型的性能优势远远大于运行原始模型。这是TensorRT LLM非常快的主要原因之一。

原始模型权重和优化选项(如量化级别、张量的并行性、管道并行性等)一起传递给编译器。然后编译器获取该信息并输出针对特定GPU优化的模型二进制文件。

但是这里整个模型编译过程必须在GPU上进行。生成的编译模型也是专门针对运行它的GPU进行优化的。例如,在A40 GPU上编译模型,则可能无法在A100 GPU上运行它。所以无论在编译过程中使用哪种GPU,都必须使用相同的GPU进行推理。

但是TensorRT LLM并不支持开箱即用所有的大型语言模型(原因是每个模型架构是不同的)。但是TensorRT所作的做深度图级优化是支持大多数流行的模型,如Mistral、Llama和Qwen等。具体支持的模型可以参考TensorRT LLM Github官方的列表

TensorRT-LLM的好处

TensorRT LLM python包允许开发人员在不了解c++或CUDA的情况下以最高性能运行LLM。

分页注意力

大型语言模型需要大量内存来存储每个令牌的键和值。随着输入序列变长,这种内存使用会变得非常大。

通常情况下,序列的键和值必须连续存储。所以即使你在序列的内存分配中释放了空间,你也不能把这个空间用于其他序列。这会导致碎片化和浪费。

分页注意力将键/值分成而不是连续的页,这样可以放在内存中的任何地方,如果您在中间释放一些分页,那么这些空间可以用于其他序列。

这可以防止碎片,并允许更高的内存利用率。在生成输出序列时,可以根据需要动态地分配和释放页面。

高效KV缓存

llm有数十亿个参数,这使得它们运行推理时速度缓慢且占用大量内存。KV缓存通过缓存LLM的层输出和激活来帮助解决这个问题,因此它们不需要为每个推理重新计算。

下面是它的工作原理:

在推理期间,当LLM执行每一层时,输出将被缓存到具有唯一键的键值存储中。当后续推断使用相同的层输入时,不是重新计算层,而是使用键检索缓存的输出。这避免了冗余计算,减少了激活内存,提高了推理速度和内存效率。

下面我们开始使用TensorRT-LLM部署一个模型

TensorRT-LLM部署教程

使用TensorRT-LLM部署模型首先就是要对模型进行编译,这里我们将使用Mistral 7B instruction v0.2。编译阶段需要GPU,所以为了方便使用我们直接在Colab上操作。

TensorRT LLM主要支持高端Nvidia gpu。所以我们在Colab上选择了A100 40GB GPU。

下载TensorRT-LLM git库。这个repo包含了编译模型所需的所有模块和脚本。

 !git clone https://github.com/NVIDIA/TensorRT-LLM.git
 %cd TensorRT-LLM/examples/llama

然后安装所需的包

 !pip install tensorrt_llm -U --pre --extra-index-url https://pypi.nvidia.com
 !pip install huggingface_hub pynvml mpi4py
 !pip install -r requirements.txt

下载模型

 from huggingface_hub import snapshot_download
 from google.colab import userdata
 
 
 snapshot_download(
     "mistralai/Mistral-7B-Instruct-v0.2",
     local_dir="tmp/hf_models/mistral-7b-instruct-v0.2",
     max_workers=4
 )

这一步可以查看Colab的tmp/hf_models目录,在那里可以看到模型权重。

然后是加载模型,并转换成特定的tensorRT LLM格式

 !python convert_checkpoint.py --model_dir ./tmp/hf_models/mistral-7b-instruct-v0.2 \
                              --output_dir ./tmp/trt_engines/1-gpu/ \
                              --dtype float16

下一步就是使用trtllm-build命令编译模型。如果需要量化和其他优化,可以在这里指定参数。为了简单起见,我没有使用任何额外的优化。

 !trtllm-build --checkpoint_dir ./tmp/trt_engines/1-gpu/ \
             --output_dir ./tmp/trt_engines/compiled-model/ \
             --gpt_attention_plugin float16 \
             --gemm_plugin float16 \
             --max_input_len 32256

Mistral 7B instruction v0.2支持32K上下文长度。所以这里在max_input_length标志设置了上下文长度。

编译模型需要15-30分钟

模型编译完成后就可以直接使用了。我们这里还要介绍一下模型部署的方法,有很多方法可以部署这个编译后的模型比如像FastAPI这样的简单工具,或者像triton推理服务器这样更复杂的工具。

当使用像FastAPI这样的工具时,开发人员必须设置API服务器,编写Dockerfile,并正确配置CUDA,这里面包含了很多服务器后端的工作,有时候我们并不熟悉,所以这里我们介绍一个简单的开源工具Truss。

Truss允许开发人员使用GPU打包他们的模型,并在任何云环境中运行它们。它有很多很棒的功能,使集成模型变得轻而易举。使用Truss的主要好处是,可以轻松地将具有GPU支持的模型容器化,并将其部署到任何云环境中。

安装:

 pip install --upgrade truss

如果从头开始创建Truss项目,你可以运行下面的命令:

 truss init mistral-7b-tensort-llm

mistral-7b-tensort-llm是我们项目的名称,可以随便编写。运行上面的命令会自动生成部署Truss所需的文件。

下面是mistral-7b- tensort -llm-truss的目录结构:

 ├── mistral-7b-tensorrt-llm-truss
 │   ├── config.yaml
 │   ├── model
 │   │   ├── __init__.py
 │   │   └── model.py
 |   |   └── utils.py
 |   ├── requirements.txt

以下是上述文件的快速介绍:

1、config.yaml用于为模型设置各种配置,包括其资源、依赖项、环境变量等。在这里,我们可以指定模型名称、要安装的Python依赖项以及要安装的系统包。

2、model/model.py是Truss的核心。它包含将在Truss服务器上执行的Python代码。在model.py中有两个主要方法:load()和predict()。

load方法是我们从hugs face下载编译模型并初始化TensorRT LLM的地方;predict方法接收HTTP请求并调用模型。

3、model/utils.py包含model.py文件的一些辅助函数。utils.py文件不是我们自己编写的,可以直接从TensorRT LLM存储库中获取的。

4、含运行编译模型所需的Python依赖项,truss会使用它来初始化我们的环境。

model.py包含执行的主代码,让我们首先看一下load函数。

 import subprocess
 subprocess.run(["pip", "install", "tensorrt_llm", "-U", "--pre", "--extra-index-url", "https://pypi.nvidia.com"])
 
 import torch
 from model.utils import (DEFAULT_HF_MODEL_DIRS, DEFAULT_PROMPT_TEMPLATES,
                    load_tokenizer, read_model_name, throttle_generator)
 
 import tensorrt_llm
 import tensorrt_llm.profiler
 from tensorrt_llm.runtime import ModelRunnerCpp, ModelRunner
 from huggingface_hub import snapshot_download
 
 STOP_WORDS_LIST = None
 BAD_WORDS_LIST = None
 PROMPT_TEMPLATE = None
 
 class Model:
     def __init__(self, **kwargs):
         self.model = None
         self.tokenizer = None
         self.pad_id = None
         self.end_id = None
         self.runtime_rank = None
         self._data_dir = kwargs["data_dir"]
 
     def load(self):
         snapshot_download(
             "htrivedi99/mistral-7b-v0.2-trtllm",
             local_dir=self._data_dir,
             max_workers=4,
         )
 
         self.runtime_rank = tensorrt_llm.mpi_rank()
 
         model_name, model_version = read_model_name(f"{self._data_dir}/compiled-model")
         tokenizer_dir = "mistralai/Mistral-7B-Instruct-v0.2"
 
         self.tokenizer, self.pad_id, self.end_id = load_tokenizer(
             tokenizer_dir=tokenizer_dir,
             vocab_file=None,
             model_name=model_name,
             model_version=model_version,
             tokenizer_type="llama",
         )
 
 
         runner_cls = ModelRunner
         runner_kwargs = dict(engine_dir=f"{self._data_dir}/compiled-model",
                              lora_dir=None,
                              rank=self.runtime_rank,
                              debug_mode=False,
                              lora_ckpt_source="hf",
                             )
 
         self.model = runner_cls.from_dir(**runner_kwargs)

在文件的顶部,我们导入了必要的模块,特别是tensorrt_llm;然后在load函数中,我们使用snapshot_download函数下载编译后的模型;然后使用model/utils.py附带的load_tokenizer函数下载模型的标记器;最后使用TensorRT LLM使用ModelRunner类加载编译后的模型。

下面就是predict函数

 def predict(self, request: dict):
 
         prompt = request.pop("prompt")
         max_new_tokens = request.pop("max_new_tokens", 2048)
         temperature = request.pop("temperature", 0.9)
         top_k = request.pop("top_k",1)
         top_p = request.pop("top_p", 0)
         streaming = request.pop("streaming", False)
         streaming_interval = request.pop("streaming_interval", 3)
 
         batch_input_ids = self.parse_input(tokenizer=self.tokenizer,
                                       input_text=[prompt],
                                       prompt_template=None,
                                       input_file=None,
                                       add_special_tokens=None,
                                       max_input_length=1028,
                                       pad_id=self.pad_id,
                                       )
         input_lengths = [x.size(0) for x in batch_input_ids]
 
         outputs = self.model.generate(
             batch_input_ids,
             max_new_tokens=max_new_tokens,
             max_attention_window_size=None,
             sink_token_length=None,
             end_id=self.end_id,
             pad_id=self.pad_id,
             temperature=temperature,
             top_k=top_k,
             top_p=top_p,
             num_beams=1,
             length_penalty=1,
             repetition_penalty=1,
             presence_penalty=0,
             frequency_penalty=0,
             stop_words_list=STOP_WORDS_LIST,
             bad_words_list=BAD_WORDS_LIST,
             lora_uids=None,
             streaming=streaming,
             output_sequence_lengths=True,
             return_dict=True)
 
         if streaming:
             streamer = throttle_generator(outputs, streaming_interval)
 
             def generator():
                 total_output = ""
                 for curr_outputs in streamer:
                     if self.runtime_rank == 0:
                         output_ids = curr_outputs['output_ids']
                         sequence_lengths = curr_outputs['sequence_lengths']
                         batch_size, num_beams, _ = output_ids.size()
                         for batch_idx in range(batch_size):
                             for beam in range(num_beams):
                                 output_begin = input_lengths[batch_idx]
                                 output_end = sequence_lengths[batch_idx][beam]
                                 outputs = output_ids[batch_idx][beam][
                                           output_begin:output_end].tolist()
                                 output_text = self.tokenizer.decode(outputs)
 
                                 current_length = len(total_output)
                                 total_output = output_text
                                 yield total_output[current_length:]
             return generator()
         else:
             if self.runtime_rank == 0:
                 output_ids = outputs['output_ids']
                 sequence_lengths = outputs['sequence_lengths']
                 batch_size, num_beams, _ = output_ids.size()
                 for batch_idx in range(batch_size):
                     for beam in range(num_beams):
                         output_begin = input_lengths[batch_idx]
                         output_end = sequence_lengths[batch_idx][beam]
                         outputs = output_ids[batch_idx][beam][
                                   output_begin:output_end].tolist()
                         output_text = self.tokenizer.decode(outputs)
                         return {"output": output_text}

predict函数接受一些模型输入,如提示、max_new_tokens、温度等。我们使用请求在函数的顶部提取所有这些值。调用LLM模型来使用self.model.generate函数生成输出。generate函数接受各种参数,帮助控制LLM的输出。

为了在云中运行我们的模型,还需要将其容器化。Truss会负责为我们创建Dockerfile并打包所有内容,所以我们不需要做太多事情。

在mistral-7b- tensort -llm-truss目录之外创建一个名为main.py的文件。将以下代码粘贴到其中:

 import truss
 from pathlib import Path
 
 tr = truss.load("./mistral-7b-tensorrt-llm-truss")
 command = tr.docker_build_setup(build_dir=Path("./mistral-7b-tensorrt-llm-truss"))
 print(command)

运行main.py文件并查看mistral-7b- tensort -llm-truss目录。应该会看到自动生成的一堆文件。下面就可以使用docker构建容器。依次运行以下命令:

 docker build mistral-7b-tensorrt-llm-truss -t mistral-7b-tensorrt-llm-truss:latest
 docker tag mistral-7b-tensorrt-llm-truss <docker_user_id>/mistral-7b-tensorrt-llm-truss
 docker push <docker_user_id>/mistral-7b-tensorrt-llm-truss

这些docker的配置文件就是truss为我们自动生成好的,我们下面简单的介绍一下看k8s的部署,我不会深入讨论如何设置GKE集群,因为这不在本文的讨论范围之内。

创建以下kubernetes部署:

 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: mistral-7b-v2-trt
   namespace: default
 spec:
   replicas: 1
   selector:
     matchLabels:
       component: mistral-7b-v2-trt-layer
   template:
     metadata:
       labels:
         component: mistral-7b-v2-trt-layer
     spec:
       containers:
       - name: mistral-container
         image: htrivedi05/mistral-7b-v0.2-trt:latest
         ports:
           - containerPort: 8080
         resources:
           limits:
             nvidia.com/gpu: 1
       nodeSelector:
         cloud.google.com/gke-accelerator: nvidia-tesla-a100
 ---
 apiVersion: v1
 kind: Service
 metadata:
   name: mistral-7b-v2-trt-service
   namespace: default
 spec:
   type: ClusterIP
   selector:
     component: mistral-7b-v2-trt-layer
   ports:
   - port: 8080
     protocol: TCP
     targetPort: 8080

这是一个标准的kubernetes部署,它运行一个映像为htrivedi05/mistral-7b-v0.2-trt:latest的容器。

可以通过运行命令创建部署:

 kubectl create -f mistral-deployment.yaml

分配kubernetes pod需要几分钟的时间。一旦pod开始运行,我们之前编写的load函数就会被执行。

一旦加载了模型后就可以在pod日志中看到类似Completed model.load()的执行时间为449234毫秒。要通过HTTP向模型发送请求,我们需要对服务进行端口转发。你可以使用下面的命令:

 kubectl port-forward svc/mistral-7b-v2-trt-service 8080

打开任意Python脚本并运行以下代码:

 import requests
 
 data = {"prompt": "What is a mistral?"}
 res = requests.post("http://127.0.0.1:8080/v1/models/model:predict", json=data)
 res = res.json()
 print(res)

将看到如下输出:

 {"output": "A Mistral is a strong, cold wind that originates in the Rhone Valley in France. It is named after the Mistral wind system, which is associated with the northern Mediterranean region. The Mistral is known for its consistency and strength, often blowing steadily for days at a time. It can reach speeds of up to 130 kilometers per hour (80 miles per hour), making it one of the strongest winds in Europe. The Mistral is also known for its clear, dry air and its role in shaping the landscape and climate of the Rhone Valley."}

这样我们的推理服务就部署成功了

性能基准测试

我运行了一些自定义基准测试,得到了以下结果:


可以看到TensorRT-LLM的加速推理还是很明显的

总结

在这篇文章中,我们演示了如何使用TensorRT LLM实现模型加速推理,文章内容涵盖了从编译LLM到在生产中部署模型的所有内容。

虽然TensorRT LLM比其他推理优化器更复杂,但性能提高也是非常明显。虽然该框架仍处于早期阶段,但是可以提供目前最先进的LLM优化。并且它是完全开源的可以商业化,我相信TensorRT LLM以后还会有更大的发展,因为毕竟是NVIDIA自己的产品.

https://avoid.overfit.cn/post/22b19ff044984de69da655a67721cff3

作者:Het Trivedi

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1488805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入理解nginx的https alpn机制

目录 1. 概述2. alpn协议的简要理解2.1 ssl的握手过程2.2 通过抓包看一下alpn的细节3. nginx源码分析3.1 给ssl上下文设置alpn回调3.2 连接初始化3.3 处理alpn协议回调3.4 握手完成,启用http协议4.4 总结阅读姊妹篇:深入理解nginx的https alpn机制 1. 概述 应用层协议协商(…

大地测量学课堂笔记:1、绪论

慕课网址&#xff1a;https://www.icourse163.org/course/WHU-1464124180?fromsearchPage&outVendorzw_mooc_pcssjg_https://www.icourse163.org/course/WHU-1464124180?fromsearchPage&outVendorzw_mooc_pcssjg_ 1. 大地测量学的定义 大地测量学是专门研究精确测量…

【数据结构】复杂度详解

目录 &#xff08;一&#xff09;算法的复杂度 &#xff08;二&#xff09;时间复杂度 &#xff08;1&#xff09;练笔解释&#xff1a; i&#xff0c;示例1 ii&#xff0c;示例2 iii&#xff0c;二分查找 iv&#xff0c;斐波那契 &#xff08;三&#xff09;空间复杂度…

java中的set

Set Set集合概述和特点 不可以存储重复元素 没有索引,不能使用普通for循环遍历 哈希值 哈希值简介 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值 如何获取哈希值 Object类中的public int hashCode()&#xff1a;返回对象的哈希码值。 哈希值的特点 同一个…

【ARM Trace32(劳特巴赫) 高级篇 21 -- SystemTrace ITM 使用介绍】

文章目录 SystemTrace ITMSystemTrace ITM 常用命令Trace Data AnalysisSystemTrace ITM CoreSight ITM (Instrumentation Trace Macrocell) provides the following information: Address, data value and instruction address for selected data cyclesInterrupt event info…

C++基于多设计模式下的同步异步日志系统day5

C基于多设计模式下的同步&异步日志系统day5 &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&am…

技术总结: PPT绘图

目录 写在前面参考文档技巧总结PPT中元素的连接立方体调整厚度调整图形中的文本3D 图片调整渐变中的颜色 写在前面 能绘制好一个好看的示意图非常重要, 在科研和工作中好的示意图能精准表达出自己的想法, 减少沟通的成本, 可视化的呈现也可以加强自身对系统的理解, 时间很久后…

Unity 协程(Coroutine)到底是什么?

参考链接&#xff1a;Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 为啥在Unity中一般不考虑多线程 因为在Unity中&#xff0c;只能在主线程中获取物体的组件、方法、对象&#xff0c;如果脱离这些&#xff0c;Unity的很多功能无法实现&#xff0c;那么多线程…

python lambda表达式(匿名函数)

lambda 表达式 在Python中&#xff0c;匿名函数&#xff08;也称为lambda函数&#xff09;是一种简洁的方式来定义小函数&#xff0c;这些函数可以在需要的地方直接定义和使用&#xff0c;而不需要使用def关键字来定义一个具有名称的函数。 lambda 函数是一种小型、匿名的、内…

vue+element ui上传图片到七牛云服务器

本来打算做一个全部都是前端完成的资源上传到七牛云的demo&#xff0c;但是需要获取token&#xff0c;经历了九九八十一难&#xff0c;最终还是选择放弃&#xff0c;token从后端获取&#xff08;springboot&#xff09;。如果你们有前端直接能解决的麻烦记得私我哦&#xff01;…

学习网络编程No.12【传输层协议之TCP】

引言&#xff1a; 北京时间&#xff1a;2024/2/27/14:12&#xff0c;不知过了多久终于在今天上午更新了新的文章。促使好久没有登录CSDN的我回关了几个近期关注我的人&#xff0c;然后过了没多久有人就通过二维码加了我的微信&#xff0c;他问了我一个问题&#xff0c;如何学好…

【S32DS报错】-7-程序进入HardFault_Handler,无法正常运行

【S32K3_MCAL从入门到精通】合集&#xff1a; S32K3_MCAL从入门到精通https://blog.csdn.net/qfmzhu/category_12519033.html 问题背景&#xff1a; 在S32DS IDE中使用PEmicro&#xff08;Multilink ACP&#xff0c;Multilink Universal&#xff0c;Multilink FX&#xff09…

3分钟,学会一个测试员必懂 Lambda 小知识!

今天再来给大家介绍下函数式接口和方法引用。 函数式接口 问&#xff1a;Lambda 表达式的类型是什么&#xff1f; 答&#xff1a;函数式接口 问&#xff1a;函数式接口是什么&#xff1f; 答&#xff1a;只包含一个抽象方法的接口&#xff0c;称为函数式接口 &#xff08;…

【图像版权】论文阅读:CRMW 图像隐写术+压缩算法

不可见水印 前言背景介绍ai大模型水印生成产物不可见水印CRMW 在保护深度神经网络模型知识产权方面与现有防御机制有何不同&#xff1f;使用图像隐写术和压缩算法为神经网络模型生成水印数据集有哪些优势&#xff1f;特征一致性训练如何发挥作用&#xff0c;将水印数据集嵌入到…

MSCKF5讲:后端代码分析

MSCKF5讲&#xff1a;后端代码分析 文章目录 MSCKF5讲&#xff1a;后端代码分析1 初始化initialize()1.1 加载参数1.2 初始化IMU连续噪声协方差矩阵1.3 卡方检验1.4 接收与订阅话题createRosIO() 2 IMU静止初始化3 重置resetCallback()4 featureCallback4.1 IMU初始化判断4.2 I…

YOLOv9独家改进|动态蛇形卷积Dynamic Snake Convolution与RepNCSPELAN4融合

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、改进点介绍 Dynamic Snake Convolution是一种针对细长微弱的局部结构特征与复杂多变的全局形态特征设计的卷积模块。 RepNCSPELAN4是YOLOv9中的特…

【C语言】动态内存管理------常见错误,以及经典笔试题分析,柔性数组【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】动态内存管理------常见错误&#xff0c;以及经典笔试题分析&#xff0c;柔性数组【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 在了解完内存操作中最关键的一节---动…

怎样裁剪视频上下多余的部分?分享3个裁剪的工具!

在数字时代&#xff0c;视频已成为我们生活中不可或缺的一部分。无论是观看电影、制作个人vlog&#xff0c;还是进行专业的视频编辑&#xff0c;我们时常会遇到需要裁剪视频上下多余部分的情况。那么&#xff0c;如何进行视频裁剪呢&#xff1f;本文将为您详细介绍几种常用的视…

day46_Servlet

今日内容 0 复习昨日 1 Servlet基础 1.1 Servlet介绍 1.2 第一个Servlet 1.3 流程分析 1.4 使用细节 1.5 映射细节 1.6 生命周期 2 HttpServlet 2.1 HTTP请求、响应、状态码 2.2 GET和POST的区别 2.3 HttpServlet 0 复习昨日 1 maven创建-java项目结构 2 maven创建-javaweb项目…

16.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-设计数据发送结构实现更复杂的数据发送

上一个内容&#xff1a;15.发送通信数据包至分析工具 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;f691a6a12ab49a711713f8ccdc8dd712c05826e9 代码下载地址&#xff0c;在 titan 目录下&…