ARTICLE DETAIL

资讯详情

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

Atlas 300V 24G推理加速卡部署YOLO全流程:转换、编程与性能优化

Atlas 300V 24G推理加速卡部署YOLO全流程:转换、编程与性能优化 1. 先搞明白“atlas 300V 24G”到底是不是运算加速卡最近后台和评论区一直被同一个问题刷屏atlas 300V 24G到底算不算运算加速卡这问题问得挺有代表性我刚开始接触atlas系列的时候也被命名绕晕过。先说结论atlas 300V 24G是华为Atlas系列里面向边缘推理场景的加速卡它确实是运算加速卡但它不能像训练卡那样去跑完整的模型训练流程它的主职是“推理”。为了讲清楚这件事我先用生活里的例子打个比方。训练模型就像是“写一本菜谱”你需要不断试菜、调整配料比例这个过程计算量大、耗时长用的是训练卡。而推理则是“照着菜谱做菜”模型结构已经固定只是拿到输入数据后快速给出结果这个过程追求的是低延迟、高吞吐。Atlas 300V 24G干的就是“照着菜谱做菜”的活而且它一次能同时做很多道菜也就是批量推理。Atlas 300V这个产品系列有几个常见规格我做了个表方便大家对照型号显存容量典型场景是否支持训练Atlas 300V Pro24GB视频分析、目标检测、OCR、语义分割否主要面向推理Atlas 300V Lite8GB轻量级边缘盒子、小型摄像头接入否推理Atlas 300I Pro16GB通用推理、多路视频流处理否推理Atlas 800/900系列32GB以上训练、联合推理是那“24G”这个参数重要在哪它直接决定了你单卡能塞多大多复杂的模型、能同时处理多少路数据。比如你要在边缘侧跑一个YOLOv8m做实时目标检测输入分辨率如果去到1280x1280批量大小设为4那8GB显存基本就吃紧了24GB则能比较从容地跑起来还能再叠加一些前后处理算子。所以选型的时候显存不是越大越好而是要和你的模型尺寸、输入分辨率、并发路数匹配起来省下来的钱能干很多别的事。简单总结就是atlas 300V 24G是一块推理加速卡不是训练卡。我在实际项目中用它跑过YOLO、百度的PaddleOCR、还有几个自研的小模型整体感受是只要你把环境折腾明白推理性能和稳定性都相当能打尤其在视频流并发推送上比纯CPU方案强太多。2. 部署前必须弄懂的硬件与软件关系2.1 Atlas加速卡的核心部件与系统架构要顺利部署YOLO你不能只知道它是一块卡得知道它里面到底是什么结构。Atlas 300V 24G的核心是昇腾AI处理器里面集成了AI Core、ARM核、DVPP数字视觉预处理模块、以及片上的缓存和调度单元。AI Core负责跑神经网络算子ARM核负责处理一些轻量级控制逻辑和辅助计算DVPP则专门做图像解码、缩放、抠图等预处理。理解这个分工特别重要因为你在部署YOLO的时候能不能发挥出硬件性能很大程度上取决于你是否合理地用了各个模块。举个例子YOLO的预处理通常包含图像缩放、归一化、通道变换这些操作如果硬写在Python里跑在CPU上1000张图可能要花掉好几秒但如果你把预处理算子放到DVPP上做基本就是毫秒级别。很多新手部署完发现还没纯CPU跑得快多半就是没把硬件的这些部件用起来。接着说系统层面Atlas 300V 24G一般以PCIe插卡形式插在x86服务器或Atlas服务器上通过PCIe总线和CPU通信。安装好驱动后系统里会出现一个名为/dev/davinci0的设备文件这就是你的加速卡设备节点。你用AscendCL昇腾计算语言写推理程序时就通过这个设备节点去和硬件交互。2.2 CANN、AscendCL、MindSpore这些名词到底是啥很多人在这一步就被劝退了因为名词实在太多。我用人话捋一遍CANN昇腾AI处理器的软件栈总称包含驱动、运行时、算子库、图编译引擎。你可以理解成Atlas卡的“操作系统补丁包”装上它之后上层工具才能调用硬件。AscendCL编程接口类似CUDA对NVIDIA卡的作用提供统一API让你在代码里做模型加载、输入输出处理、推理执行。ATC模型转换工具把TensorFlow、PyTorch、ONNX等格式的模型转换成昇腾专用的.om格式。MindSpore昇腾亲儿子深度学习框架但不是唯一选择你完全可以继续用PyTorch训练模型然后转成ONNX再部署到atlas上。这里我想重点说明一个常见的认知误区很多人以为用了Atlas就必须用MindSpore重新写模型代码其实不是。昇腾工具链对PyTorch的兼容性已经很成熟了尤其是ONNX中转路径基本能做到训练代码零改动只需要在导出模型时稍加注意。我自己平时训练YOLO就是用PyTorch导出ONNX再转成om格式整个过程半小时内能搞定。2.3 软硬件匹配驱动、固件、CANN版本不对其他都白搭这是我觉得整个部署过程中最坑、也最值得单独说的一块。Atlas系列的驱动、固件、CANN三个东西是有版本匹配关系的。打个比方驱动是钥匙固件是锁芯CANN是你拿钥匙开锁后进房子干活用的工具箱三者只要有一个版本不对门就开不了或者进去了也干不了活。我们踩过这样一个坑新买来的Atlas 300V卡出厂固件比较老我们直接装了最新版的CANN结果跑ATC模型转换时报错说算子库版本不匹配后来又把驱动升级到配套版本结果反过来要求固件升级升级固件又得进入维护模式一来一回折腾了两个晚上。所以真心建议拿到卡或者新装环境时第一步就是去官方文档找到“驱动固件CANN版本配套表”照着配套版本一个个装不要自作主张上最新版。我这里给一套经过实测的推荐组合基于CentOS 7.6和Ubuntu 20.04两个系统都验证过组件版本说明驱动22.0.3与固件配套固件22.0.3和驱动同一发布包CANN5.1.RC2对应驱动固件Python3.7-3.9推荐3.8兼容性最好操作系统Ubuntu 20.04 x86_64我用的最多坑最少装的过程有个小细节先装驱动和固件重启然后再装CANN。驱动包装完会提示你重启别偷懒直接装下一步不重启后续各种设备节点识别错误会让你怀疑人生。3. YOLO模型部署的完整路径PyTorch到OM格式3.1 为什么不能直接把PyTorch模型丢给Atlas跑很多人拿到Atlas卡后的第一反应是我训练好的best.pt能不能直接加载答案是不能。原因在于Atlas加速卡上跑的算子指令集和NVIDIA GPU不同PyTorch的.pt权重文件底层绑定的是CUDA生态昇腾硬件读不懂。这就需要一条“翻译”链路先把PyTorch模型导出为ONNX通用格式再通过ATC工具转换成昇腾专用.om格式。这个过程你可以类比成电影蓝光碟转成手机能看的MP4.pt是蓝光原盘ONNX是中间母版.om是压缩打包好的手机版本。中间可能有轻微的画质损失但只要参数得当损失可以控制在忽略不计的范围对于YOLO这种目标检测模型来说影响很小。那为什么一定要有ONNX这一步因为ONNX已经成了深度学习框架之间的“普通话”PyTorch、TensorFlow、PaddlePaddle都能导出ONNX昇腾的ATC工具也优先支持读ONNX。所以无论你用什么框架训练的模型只要会转ONNX就能上Atlas。这也是我强烈建议团队里不管用什么框架都保留一个ONNX导出脚本的原因以后换硬件、换部署平台都方便。3.2 ATC转换的详细参数与实操命令把模型转成om格式核心工具是ATC。我这里用YOLOv5s的导出举例因为YOLOv5s的网络结构和导出流程最有代表性其他版本的YOLO套路完全一致。训练好后按下面几步操作第一步把PyTorch模型导出为ONNX。在YOLOv5项目目录下执行python export.py --weights best.pt --include onnx --opset 11 --simplify这里有个容易踩的坑--opset版本不要选太高。我们实测过opset 12、13在ATC转换时偶尔会报算子不支持opset 11是最稳的。--simplify参数建议加上它会用onnx-simplifier做计算图简化去掉一些冗余算子让生成的ONNX结构更清爽ATC转换成功率更高。第二步用ATC转om格式。命令长这样atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_16 \ --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --loginfo逐行解释一下这些参数--framework55表示输入是ONNX格式。--soc_versionAscend310P3这里要特别强调不同型号的Atlas卡对应的soc_version是不同的。Atlas 300V 24G对应的是Ascend310P3但如果你买的是Atlas 300I Pro那对应的可能是Ascend310P4具体型号在官方文档里查填错了转换会直接报错。--input_shapeimages:1,3,640,640固定输入尺寸。如果部署时输入的图片尺寸不固定就得用动态shape后面细说。--loginfo日志级别建议第一次调试时用info级别能看到具体的算子映射过程。转换完成后当前目录下会生成一个yolov5s_16.om文件这个就是Atlas能识别的模型文件了。我很建议把每次转换的ATC参数记下来因为后续换输入分辨率、换batch size都要重新转换参数共享能省很多时间。第三步关于动态shape的选择。YOLO部署最常遇到的需求是不同尺寸的输入图片。如果每次都要重转模型很麻烦。ATC支持动态shape配置atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_dynamic \ --soc_versionAscend310P3 \ --input_shapeimages:-1,3,-1,-1 \ --dynamic_dims640,640;1280,1280;1920,1080 \ --loginfo这里-1表示这个维度可变dynamic_dims指定了可选的组合。但要注意动态shape会比固定shape损失一些性能因为硬件无法提前做最优的内存规划。我个人的建议是如果应用场景输入分辨率相对固定比如摄像头采集的1080P视频那就按固定分辨率转稳且快。3.3 YOLO后面的输出层怎么解析anchor和输出Tensor的关系模型转换只是一半工作另一半是怎么解析模型的输出。YOLO的输出不像分类网络那样直接给个概率向量它输出的是多个feature map上的坐标偏移、置信度和类别概率。很多新手在部署时卡在这一步因为训练框架里的后处理代码跑得好好的换到Atlas上就懵了。原因在于ONNX导出时默认把模型的原始输出原封不动导出YOLOv5的原始输出是三个shape为[1, 3, 80, 80, 7]、[1, 3, 40, 40, 7]、[1, 3, 20, 20, 7]的张量7代表(x, y, w, h, objectness, 2个类别概率)。这里3是anchor的数量80x80/40x40/20x20是不同尺度的特征图分辨率。后处理要做四件事从输出张量中解析出边界框坐标注意YOLOv5的输出坐标是相对于输入图片尺寸的归一化值需要乘以原始图片的宽高还原。用sigmoid函数把objectness和类别概率映射到0-1区间。按置信度阈值过滤掉低置信度的框。做NMS非极大值抑制去掉重叠框。我写过一个简单的参考代码不依赖框架直接用NumPy处理om模型的输出import numpy as np def post_process(outputs, img_shape, conf_thres0.25, iou_thres0.45): # outputs 是om模型的三个输出tensor组成的列表 boxes, confs, clses [], [], [] for output in outputs: batch output[0] # shape [3, H, W, 7] anchor_num batch.shape[0] for a in range(anchor_num): feat_map batch[a] h, w feat_map.shape[:2] for i in range(h): for j in range(w): row feat_map[i, j] obj_conf row[4] if obj_conf conf_thres: continue cls_scores row[5:] cls_id int(np.argmax(cls_scores)) cls_conf cls_scores[cls_id] * obj_conf if cls_conf conf_thres: continue x_center, y_center, bw, bh row[:4] x1 (x_center - bw / 2) * img_shape[1] y1 (y_center - bh / 2) * img_shape[0] x2 (x_center bw / 2) * img_shape[1] y2 (y_center bh / 2) * img_shape[0] boxes.append([x1, y1, x2, y2]) confs.append(cls_conf) clses.append(cls_id) # 接着做NMS keep nms(boxes, confs, iou_thres) return [(boxes[i], confs[i], clses[i]) for i in keep]这里我刻意没用向量化写法图个直观。实际上在生产环境里用纯Python循环跑80x80x3的feature map一帧大概要几十毫秒也就是能给YOLO本身的推理加不少负担。更好的做法是把这个后处理过程用C重写或者借助Python的numba、Cython加速特别是处理视频流的时候每帧省1毫秒长时间跑下来差距巨大。4. 基于AscendCL的完整推理流程与代码实现4.1 AscendCL推理程序的骨架环境配好了、模型转换好了接下来就是要写推理程序。Atlas 300V支持用Python调用AscendCL官方提供的python-acllite封装让代码简洁不少但如果你想精细控制性能还是得了解原生接口的调用方式。我直接用原生的方式讲理解原理之后用任何封装都容易上手。整个推理流程分这么几步初始化acl.init()然后设置设备。加载模型从om文件读入模型创建模型ID。创建输入输出Dataset定义输入Tensor的内存、形状和格式申请输出Tensor的内存。执行推理调用acl.mdl.execute。解析输出把输出Tensor数据拷贝到CPU内存做后处理。资源释放释放内存、unload模型、finalize设备。其中最关键、也最容易出错的是第3步。因为AscendCL对输入数据的内存有对齐要求比如每通道的宽高对齐到16否则可能报错或者结果不对。从OpenCV读入的图片是HWC格式YOLO模型要求的是CHW所以处理顺序是读图 - 缩放 - BGR转RGB - HWC转CHW - 归一化 - 拷贝到设备内存。这些操作你可以自己写Python做也可以利用DVPP硬件加速。下面是一段核心代码框架注释里写了我认为重要的点import acl import numpy as np class AtlasYOLO: def __init__(self, model_path, device_id0): self.device_id device_id self.model_path model_path self.context None self.stream None self.model_id None self.input_dataset None self.output_dataset None self._init_resource() def _init_resource(self): acl.init() ret acl.rt.set_device(self.device_id) self.context, ret acl.rt.create_context(self.device_id) self.stream, ret acl.rt.create_stream() self.model_id, ret acl.mdl.load_from_file(self.model_path) self._prepare_input_output() def _prepare_input_output(self): # 创建输入输出数据集 self.input_dataset acl.mdl.create_dataset() self.output_dataset acl.mdl.create_dataset() # 根据模型描述信息分配内存 self.input_desc acl.mdl.create_tensor_desc() self.output_desc acl.mdl.create_tensor_desc() # 具体内存分配略实际项目中按模型输入shape创建numpy数组并对齐 def infer(self, input_data): # 把预处理好的numpy数组拷贝到设备内存 # 调用acl.mdl.execute执行推理 # 读取输出并返回 pass def release(self): acl.mdl.unload(self.model_id) acl.rt.destroy_stream(self.stream) acl.rt.destroy_context(self.context) acl.rt.reset_device(self.device_id) acl.finalize()这个类我只是展示框架实际项目中你还需要在_prepare_input_output里根据acl.mdl.get_desc返回的模型描述信息动态获取输入输出的维度、数据类型然后分配内存。如果直接用写死的shape换一个模型就得改代码不灵活。4.2 数据预处理为什么GPU上的resize和Atlas上的不一样YOLO训练时通常会做mosaic增强、随机缩放但部署时的预处理相对固定。在Atlas上有两种做法我强烈建议先掌握普通做法性能优化时再上DVPP。普通做法是用OpenCV和NumPy在CPU上完成import cv2 def letterbox(img, new_shape(640, 640)): shape img.shape[:2] ratio min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad (int(round(shape[1] * ratio)), int(round(shape[0] * ratio))) dw (new_shape[1] - new_unpad[0]) / 2 dh (new_shape[0] - new_unpad[1]) / 2 img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) top, bottom int(round(dh - 0.1)), int(round(dh 0.1)) left, right int(round(dw - 0.1)), int(round(dw 0.1)) img cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value(114, 114, 114)) return img img cv2.imread(test.jpg) img letterbox(img, (640, 640)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) # HWC - CHW input_data np.ascontiguousarray(img)这里面有个细节值得多说一句letterbox的填充色一定要用114这是YOLO训练时默认的颜色填充值。如果你随手填0推理结果会出现不少误检因为模型没见过训练时带黑色边框的图边界框的置信度会整体偏低。要是用DVPP做预处理代码会复杂一些但优势明显。DVPP能直接对JPEG解码、缩放、抠图全程不占用CPU和AI Core。实测用DVPP做视频流解码缩放到640x640单路1080P视频的整体处理速度能提升20%左右。这个优化我建议放在项目稳定运行后再做一开始追求的是“跑通”不是“跑快”。4.3 推理与后处理批量推理如何不影响实时性视频流场景下单帧推理往往不是瓶颈瓶颈在解码和后处理。比如用Python的opencv-python读一个1080P视频流解码一帧可能要8-10毫秒YOLOv5s推理一帧在Atlas 300V上差不多5-6毫秒后处理纯Python循环解析目标框筛选又要15-20毫秒加起来就过了30毫秒也就是帧率跑不到30FPS。要提升整体吞吐我建议从三个方向入手第一解码与推理分离。用多线程或进程池一个线程专门读视频流并做缩放一个线程专门跑推理另一个线程做后处理形成流水线。不要同步地做“读一帧、处理一帧”。第二控制NMS的实现方式。PyTorch框架里的torchvision.ops.nms在Atlas上用不了要用NumPy或者写C扩展。一个可行的方案是把NMS封装成.so动态库用Python调用或者直接上opencv的cv2.dnn.NMSBoxes实测性能还不错只是需要把坐标格式转成标准框列表。第三批量推理。如果同时接入多路视频流可以把多帧拼成一个batch一起推理。Atlas 300V 24G在batch size为4时单帧推理时间增加的很少但吞吐量翻了好几倍。比如单帧推理5毫秒batch4时总耗时可能也就8毫秒平均每帧2毫秒这利用率提升非常明显。5. 常见报错与排查流程我从废寝忘食里总结的经验5.1 ATC转换失败算子不支持这是出现频率最高的问题。ATC报错信息一般是Unsupported op type XXX或者TE Build failed。碰到这种情况我一般是按下面的顺序排查先把ONNX模型用Netron打开看一下报错的算子是什么类型。如果是自定义算子比如YOLOv5的Focus模块换成普通卷积、或者某些版本使用了自定义的SiLU激活先尝试用onnx-simplifier做简化把自定义结构打散成基础算子。如果还不行用atc --logdebug重新转换日志里会给出具体是哪个算子、在ONNX图的哪一层出的问题然后去昇腾社区查这个算子是否被支持。实在不支持的话换一种实现方式重写该部分这在转YOLO模型时偶尔会发生。我之前遇到过一个典型案例YOLOv5s导出ONNX时使用了opset 13结果ATC插件里没有对应的ReduceMean实现报错换了三次版本都不行。后来把opset降回11一次就过了。所以记住能不用高版本opset就不用。5.2 推理结果全为0或偏差很大预处理不一致推理能跑通但结果很奇怪比如所有目标都检测不到、或者一堆乱框。这个问题90%出在预处理不一致。训练时如果输入是RGB、归一化到0-1部署时你喂了BGR、没归一化那结果肯定离谱。排查技巧把模型的输入Tensor打印出来看数值范围是否在0-1之间。然后单独找一个训练集的图依次对比输入和输出的预期结果。因为YOLO模型的权重完全依赖输入分布一旦输入分布变了精度立刻崩。另外还要注意图片是否做了等比例缩放。如果你直接把原图resize到640x640没有做letterbox那么宽高比全变了小目标检测精度会大幅下降。这个问题在文档里不会写但实际部署时特别常见。5.3 运行时报错设备内存不足或Open Device Failed这类报错多数是资源管理和版本匹配的问题。出现Open Device Failed时先跑一下npu-smi info确认卡是否被正确识别然后用ls /dev/davinci*检查设备节点是否存在。如果设备节点都没了八成是驱动没装好或者驱动和固件不匹配重新按配套表装一遍。内存不足的话用npu-smi info看看卡上内存占用情况然后检查代码里有没有及时释放tensor数据。AscendCL里申请了设备内存但没有手动释放短时间内不会出问题长时间跑就会报内存不足。我早期写的推理服务挂了三天报内存泄漏排查了半天才发现是每次推理后没做acl.rt.free。我见过最诡异的一个报错是程序在容器里跑不起来报Device Open Failed但宿主机上一切正常。后来发现是容器启动时没有把所有设备映射进去需要在docker run时加上--device/dev/davinci0和--device/dev/davinci_manager同时挂载/usr/local/Ascend/driver/lib64等驱动库目录。容器化部署时一定要把这个检查清单记好。5.4 性能优化把25毫秒压到10毫秒以内性能优化是部署完成后一定会面临的问题。Atlas 300V跑YOLOv5s理论推理时间在单帧5-8毫秒左右但实际项目里很多人跑出来是20多毫秒性能都被预处理和后处理吃掉了。针对预处理推荐把图像缩放从cv2.resize换到DVPP硬件加速裁剪和缩放由硬件完成CPU完全解放。针对后处理先把纯Python的循环改成NumPy矩阵操作再考虑用C写个扩展。我实测过一组对比数据方案单帧耗时Python循环后处理18msNumPy向量化后处理7msC扩展后处理2ms整个流程未优化32ms整个流程DVPPNumPyC9ms从32毫秒压到9毫秒关键是找到瓶颈在哪儿。用cProfile跑一遍程序看时间分布别凭感觉瞎优化这是我一贯的原则。6. 部署完成后的运行维护与多路视频流扩展6.1 AI服务常驻内存模型复用与进程守护模型加载这个操作开销不小所以生产环境里模型必须在常驻进程里加载一次然后循环接收推理请求。不要每次请求都重新加载模型更不要每次请求都重新初始化AscendCL环境否则延迟会高到不可接受。我自己做了一个简单的推理服务框架启动时加载模型并初始化然后把推理封装成一个函数用FastAPI或者gRPC对外提供服务。内部维护一个队列推理请求进队列后台线程池消费。这里要提一个坑AscendCL的context是线程绑定的多线程推理时要确保每个线程都有独立的context不能在多个线程里共用同一个context否则会随机报错。如果你做的是视频流分析服务推荐思路是多个视频流共用一个模型实例通过batch方式喂给加速卡。比如接入8路1080P视频每路取一帧拼成batch8一次推理全部完成。这样单卡就能很轻松处理十几路视频流CPU基本处于低负载状态。6.2 性能监控npu-smi和日志定位Atlas卡自带了npu-smi工具类似NVIDIA的nvidia-smi。部署完一定要学会看它的输出重点看AI Core的利用率、内存占用和温度这三项。AI Core利用率如果长期低于50%说明程序没有充分利用硬件可能是预处理太慢导致模型在空等。利用率长期接近100%时要注意推理请求是否过多观察推理队列积压情况。内存占用比较直观24G显卡如果只跑一个YOLOv5s模型占用可能不到4G那么多出来的显存可以用来加载多个模型比如YOLO目标检测加PaddleOCR一起部署。如果内存持续上涨但模型数量没变那就是内存泄漏了检查代码中的资源释放。日志定位上AscendCL运行时报错信息通常比较含糊我的习惯是在每个关键API调用后打印返回值不为0就是出错。别嫌代码丑调试阶段多打日志能帮你省下至少一天的排查时间。等稳定了再统一去掉。6.3 边缘场景的特殊问题掉电、断网和长期稳定Atlas 300V经常用于边缘机房或者现场的AI盒子这种环境比服务器机房恶劣得多。我们遇到过现场突然断电、重启后驱动没正常加载、系统起不来的情况。解决办法是给设备配置开机自启脚本检查/dev/davinci0是否存在不存在就重新加载驱动模块。长期稳定运行还有一个容易忽略的点散热。Atlas 300V虽然是推理卡功耗比训练卡低不少但满负荷运行时发热依然可观。边缘盒子里如果散热不良温度超过80度后性能会降频推理时间明显增加。建议定期检查设备温度特别是夏天。网络断连这块我通常会在推理服务里加一个简单的自动重连逻辑。比如视频流读取失败时等待几秒自动重新初始化视频流而不是让整个进程崩溃。这些稳定性细节在演示时看不出来但现场跑一个月项目靠不靠谱就靠这些兜底逻辑了。如果你是从零开始接触Atlas我建议不要一上来就追求最优性能先把整个流程串起来跑通一遍理解每个环节在干什么然后再一步步优化。这条路我走了不少弯路现在把能避的坑都提前标记出来了希望你能走得比我顺得多。
返回列表