陈兴振同城机器学习平台资源使用率优化实践
2021年10月18~20日,由IT168联合旗下ITPUB、ChinaUnix两大技术社区主办的第12届中国数据库技术大会(DTCC2021)在北京国际会议中心召开,大会以“数造未来”为主题,围绕数据架构、人工智能与大数据应用等内容展开分享和探讨。58同城TEG AI Lab高级架构师陈兴振在人工智能与大数据应用专题下分享了《58同城机器学习平台资源使用率优化实践》。本文根据分享实录整理,欢迎大家阅读分享。
01 背 景
AI正在驱动行业变革,为加速58同城AI应用的落地,我们自2017年9月开始构建机器学习平台WPAI(Wuba Platform of AI),支持机器学习算法一站式开发,以提高AI工程师的研发效率。
WPAI包括基础计算平台、算法应用平台两部分:
- 基础计算平台集中管理GPU、CPU、NPU硬件资源,集成TensorFlow、PyTorch、Caffe、PaddlePaddle等机器学习框架,支持机器学习模型离线训练和在线推理,在线推理服务支持自动扩缩容(弹性推理服务),离线训练作业和在线推理服务支持混合部署(离在线混布)。开发者可以向平台申请离线、在线资源,使用机器学习框架开发模型,打造自己的机器学习服务。
- 为了进一步提高AI工程效率,我们在基础计算平台之上继续打造了算法应用平台,包括NLP算法平台WubaNLP、图像算法平台凤凰(由58技术委员会AI分会基于WPAI基础计算平台协同共建)、排序学习平台(应用于搜索、推荐、广告系统)等。算法应用平台直接集成了相应领域下的常用模型,以Web的方式提供应用,开发者只需要在Web界面做相应配置即可完成训练和推理,大大提高开发效率。
- 在AI平台之外,我们还构建了AI周围子系统,如向量检索平台vSearch、AB实验平台日晷,以进一步提高AI工程效率。
WPAI机器学习平台是AI应用的底座,我们基于WPAI打造了灵犀智能语音语义平台、MAI智能营销引擎、智能写稿,具体请参见 58同城 AI Lab 产品能力介绍。
WPAI上在线推理服务请求的“高峰低谷”现象导致部分时段资源利用率偏低,训练集群存在部门之间资源争抢、任务资源配置不合理等问题导致集群吞吐量下降,模型推理计算量大占用计算资源多。购置和维护GPU、CPU等计算资源带来了昂贵的开销,平台规模越来越大,如何改善机器学习平台的资源利用效率成为了亟需解决的问题。针对以上问题,WPAI从训练任务资源调度、推理服务弹性扩缩容、离在线资源混部、模型推理加速四个方面进行了优化,本文将详细阐述各个部分的优化细节。
02 训练任务资源调度
各业务部门在WPAI上提交训练任务,平台统一进行资源分配调度。部门资源由集团根据各业务部门需求统一进行采购,另外每个部门在平台上还可以申请一定数量的借用资源,借用资源指当部门资源不足时,可以临时借用其他部门的空闲资源,部门采购的资源和申请的借用资源分别录入到训练K8S集群资源配额。部门训练任务提交后,若部门资源配额充足则提交到部门资源上,否则再判断其借用资源是否充足,当借用资源充足且集群整体资源有空闲时,任务会提交到部门的借用资源进行训练。平台早期这种训练任务资源分配及调度策略,保证了部门采购资源有空闲时能被其他部门进行充分利用,但随着平台规模的扩大,逐渐暴露出一些问题:
(1)部门资源借用的超售机制导致使用部门资源训练任务和使用借用资源训练任务发生资源争抢,这样会出现用户在平台上部门资源充足而训练任务提交后却要进行排队的现象。
(2)用户进行资源申请时往往申请的资源量大于实际需要的资源量,训练任务资源使用率低,导致资源浪费。
(3)平台训练集群GPU卡型号众多,训练时由用户选择卡型号,导致不同型号的GPU卡使用不均衡,一部分型号GPU显卡用满而另一部分型号GPU显卡比较空闲。
针对训练集群出现的上述问题,平台对训练任务资源调度进行了优化,实现了一套离线训练资源调度系统。
离线训练资源调度系统整体架构如下图所求,包括任务画像、训练任务资源调度器、K8S操作部署服务、K8S集群,核心模块为任务画像和训练任务资源调度器。任务画像是整个调度系统的基础,为调度系统提供历史和实时的数据支持;训练任务资源调度器基于任务画像数据实现资源调度控制策略,主要包括任务配置资源自动调整策略、基于优先级抢占调度策略、被抢占任务重调度策略、异构GPU资源调度策略;K8S操作部署服务负责与K8S集群进行交互,操作K8S集群启动pod。下面将重点对任务画像和训练任务资源调度器进行介绍。
任务画像主要采集任务资源数据和任务训练调度情况数据,任务资源数据包括使用的资源类型、GPU卡具体型号、GPU/CPU/显存/内存配额、训练时各个资源的平均使用率和峰值使用率,任务训练调度情况数据包括等待时长、训练时长、训练状态。如一个任务历史训练多次我们会计算出这个任务GPU、CPU、显存、内存的平均使用率、峰值使用率、平均训练时长、平均等待时长等数据。训练任务资源使用数据由Prometheus进行监控,为了便于进行任务画像数据构建,如下图所求,我们将Prometheus数据实时写入kafka,使用Flink进行实时计算分析后存入MySQL形成实时画像,同时kafka数据会配置落盘到HDFS,每天凌晨会定时启动Spark程序对前一天历史数据进行分析形成历史画像。
训练任务资源调度器是整个系统的核心,利用任务画像数据进行资源调度策略控制:
(1)任务配置资源自动调整策略:针对用户申请资源往往大于实际需要资源导致任务资源利用率低问题,我们在进行任务调度时根据任务画像数据为任务自动分配合适的资源量,如GPU配额取最近3次GPU使用率均值和显存使用率峰值较大者除以90%,CPU配额limit取最近3次的CPU峰值除以50%,CPU配额request取最近3次CPU使用均值除以50%。
(2)基于优先级抢占调度及重调度策略:我们按策略为任务配置权重分值,分值越大优先级越高,在集群资源不足时高优先级任务会抢占低优先级任务。根据任务使用资源的类型为每个任务配置一个基础分值,使用部门资源训练任务基础分值为100万,使用借用资源训练任务基础分值为1000,这样确保部门资源充足的任务优先级高于借用资源任务,在基础权重分值基础上任务资源使用率均值达标则增加20分,反之则减少20分,任务等待时长超过10分钟后,开始每10分钟增加1~5分,增加量随增加次数递增,单次增加量封顶为5分。低优先级训练任务被抢占后会立即再加入到调度队列当中等待空闲资源再启动训练。
(3)异构GPU资源调度策略:平台对TensorFlow、PyTorch、Caffe等框架不同版本在P40、A100、RTX3090、T4等不同型号的GPU卡上进行了适配,任务调度时会优先调度到用户选择的GPU卡型号,当用户选择型号GPU卡资源不足时,调度系统根据平台当前各型号GPU卡空闲情况为其调度到其他型号GPU卡上。
离线训练资源调度系统通过任务配置资源自动调整有效对用户申请的冗余资源进行了回收,通过优先级抢占调度策略确保了使用部门资源训练任务和资源利用率高任务能优先得到训练,通过异构GPU资源调度策略解决了不同型号GPU卡利用率不均衡问题。优化后离线训练集群GPU使用率相对提升51%,CPU使用率相对提升38%。
03 推理服务弹性扩缩容
深度学习模型推理服务具有一些典型特征:
(1)业务在进行模型推理部署时往往倾向于申请比实际需求更多的资源以确保服务的稳定性。
(2)模型推理服务资源申请时需要以峰值为基准才能保证服务峰值阶段的稳定性。
(3)模型推理请求量会因业务需求变化存在突增或下降情况。
(4)在58业务场景下,模型推理服务每天的资源使用率存在明显的潮汐特征,白天为流量高峰资源使用率高,夜间为流量低谷资源使用率较低,“高峰低谷”现象导致推理资源部分时段资源利用率偏低。
推理服务的这些特征会带来两个问题,一是如何快速应对流量的突然上升,二是如何回收波谷资源和业务方多申请的资源以提高资源使用率。为此WPAI构建了一套自动弹性扩缩容系统,提供以下能力:
(1)智能扩容:模型推理服务的扩容需要开发人员主动触发,当流量暴增时,业务有损时间取决于开发人员对报警的响应速度,智能扩容基于模型推理节点实时资源使用情况和模型预测自动调整节点数量,流量突增时无需人工干预。
(2)自动缩容:主动回收推理服务低谷空闲资源和业务方多申请的冗余资源,提升平台资源使用率;长时间无流量未及时下线模型或间断有流量模型通过1->0&0->1自动伸缩来节省资源。
自动弹性扩缩容的基本思想是根据各种指标来配置扩缩容策略,一期上线的主要是资源使用率指标,包括GPU、CPU、内存、显存,当前正在加入QPS、平均耗时、TP99耗时、异常量、限流量等指标。针对资源使用率指标,每个推理模型可配置扩容阈值maxRate、缩容阈值minRate和期望阈值expectRate,假设当前实际的资源使用率是y,当 y > maxRate 时服务进行扩容,增加节点数量,当y < minRate 时服务缩容,减少节点数量,具体扩容和缩容的节点数量以扩缩后资源使用率y达到期望阈值expectRate为目标,计算公式如下:
扩容计算公式:
Ceil(y/expectRate∗NodeNum)
缩容计算公式:
floor(y/expectRate∗NodeNum)
自动扩容除了根据实时的资源使用率进行计算还会基于模型进行预测提前扩容,保证服务流量上涨时服务的稳定性,整个自动弹性扩缩容系统架构设计如下图所求,包括监控数据接入模块、模型预测模块、Flink实时处理模块、弹性扩缩策略服务。
推理服务弹性扩缩容系统接入线上资源监控数据和流量监控数据,线上推理服务资源监控Prometheus数据实时写入Kafka后接入Flink实时分析,同时落地HDFS,推理服务线上请求量、耗时、请求状态等流量数据通过Rsyslog对Istio gateway日志进行实时采集写入Kafka后接入Flink实时分析,同时落地Elasticsearch和HDFS。
模型预测模块基于WPAI排序学习平台完成,对HDFS上最近一个月推理服务资源监控数据和流量监控数据先进行特征工程处理,生成Libsvm格式的特征数据,然后再进行XGBoost模型训练,最后在WPAI排序学习平台进行上线,为弹性扩缩系统提供在线预测服务。我们训练了10个XGBoost模型,分别用来预测未来1分钟、2分钟、3分钟、4分钟、5分钟推理服务的GPU使用率、CPU使用率。在做特征工程时,我们选择推理服务模型ID、当天第几分钟、星期几、是否节假日、节点CPU使用率、节点GPU使用率、节点个数、推理请求qps、推理请求异常量、推理平均耗时等字段为特征。模型预测为推理服务弹性扩缩策略提供参考,在推理服务流量高峰来临时提前进行扩容,防止扩容不及时导致线上流量损失。
Flink实时处理模块,实时计算各个推理服务最近5分钟的资源使用数据,以推理服务为维度对不同模型节点资源使用进行汇总,得出每个服务每分钟的GPU/CPU平均使用率,并调用XGBoost资源使用率预测模型得到服务未来5分钟的GPU/CPU平均使用率。在Flink处理的filter阶段对单个pod的资源监控数据、流量监控数据指标进行过滤提取所需要的指标,然后在keyBy阶段以推理服务任务ID维度进行聚合。最后将推理服务最近五分钟汇总计算的数据和模型预测的未来五分钟数据发给策略服务,为扩缩策略提供实时数据。
弹性扩缩策略服务受数据驱动提供多重计算策略,包括实时扩缩计算策略、预测扩缩计算策略、过滤策略、系统策略、兜底策略、告警策略等。实时扩缩计算策略接受推理服务最近5分钟的CPU、GPU资源使用数据,计算出当前资源使用率,对CPU和GPU使用率分别进行判断,当最近2分钟使用率都超过扩容阈值时则按扩容公式进行增大副本数,当最近5分钟使用率都小于缩容阈值时则按缩容公式进行减小副本数。预测扩缩计算策略跟实时扩缩计算策略流程相似,取的是调用模型预测计算得到的未来5分钟的GPU、CPU使用率数据,当未来5分钟中任意2分钟超过扩容阈值时则按扩容公式进行增大副本数。过滤策略则会过滤各种异常状态,如Kafka实时数据消费过慢超过2分钟之前的数据过滤不处理,正在进行扩缩容操作的推理服务不进行重复扩缩处理,已下线及未开启扩缩容功能的推理服务不进行处理,正在进行手动部署的模型不进行处理,判断推理服务最近3分钟的节点数是不是一个稳定状态等。系统策略指系统定义的基本规则,如每个推理服务最少节点数为2个,白天流量高峰时段不进行缩容等。兜底策略指自动扩缩容系统监控接入数据异常的处理策略,比如某个推理服凌晨进行了缩容,白天流量高峰时策略服务获取不到推理服务实时的指标数据不能进行扩容导致线上请求异常,针对这种极端情况系统对已经开启自动扩缩容的推理服务进行实时监控,连续5分钟没有实时指标数据时则将服务节点数恢复至昨天的最大节点数。告警策略对服务扩容失败等异常情况进行及时告警。
推理服务弹性扩缩容除实现节点的水平自动扩缩外,还实现了节点数量1->0&0->1自动扩容、定时扩缩容、垂直扩缩容等功能,未来扩容缩计算指标除了GPU/CPU使用率还将加入推理请求QPS、耗时、异常量、限流量等指标。
推理服务弹性扩缩容上线后线上应用效果明显,自动扩容能力对推理服务线上突增流量能及时甚至提前进行响应,保证模型推理服务的高可用。如下图所求,某推理模型应用自动扩缩容功能后,在两次流量高峰来临时及时进行了扩容,线上推理请求没有因为流量突增而出现请求异常或耗时增加现象。
推理服务弹性扩缩容能有效回收夜间低谷资源,将回收的资源通过离在线混部出让给训练作业使用,通过自动扩缩容让推理服务节点资源使用率维持在期望值附近。1->0&0->1扩缩功能及时回收无流量推理服务资源,有效提升了平台资源利用率。
04 离在线资源混部
WPAI上任务分为在线推理服务和离线训练作业两种类型,在线推理服务为线上业务,业务方通过HTTP或RPC方式接入,对延时敏感,有毫秒级SLO要求,白天流量高而夜间流量低,离线训练作业没有毫秒级SLO要求,业务方只关心模型能否在要求的时间内训练完,不同模型训练时间从几十分钟到几小时不等,只要训练任务一启动机器负载就很高。如下图所示在线推理服务的GPU负载随着流量变化呈明显的潮汐特征,离线训练GPU负载在训练期间基本上都比较高。GPU等硬件计算资源成本昂贵,通过离在线混部能将推理服务流量低谷时的空闲资源进行充分利用,节省成本,同时当在线服务出现流量高峰时,能通过混部对训练资源进行拆借以应对在线资源不足。
离在线混部方案大体分为两种,第一种是离线和在线业务部署在同一台物理机上,这种方案需要控制物理机上CPU、内存、带宽、磁盘IO、L3 Cache等资源的完全隔离,尽可能降低混部后离线业务的高负载对在线业务的影响,同时需要考虑当资源不足时在线业务如何及时抢占离线业务,整体开发工作量比较大;第二种是通过整机出让实现离线和在线的动态混部,一台物理机在一个时间段内跑在线业务另外一个时间段内跑离线业务,根据在线业务和离线业务负载情况进行动态调整。我们采取的是第二种方案,通过整机出让实现训练和推理资源的动态调配,白天推理流量高峰时将离线训练资源出让给在线用于支撑流量高峰,夜间推理服务自动缩容空闲出的资源整机出让用于离线训练。
在整机出让的离在线混部方案中,核心是如何实现物理节点状态在离线和在线之间动态转换。我们首先对物理节点按资源类型进行分组,分为CPU、P40、P40 vGPU、T4、T4 vGPU等资源组,以资源组为单位,在资源组内根据在线和离线负载情况进行物理节点状态动态转化,当推理服务流量低资源空闲时进行OnlineToOffline转换(物理节点状态由在线转换为离线,需迁移节点上在线推理服务pod),而当推理服务流量高峰来临资源紧缺时进行OfflineToOnline转换(物理节点状态由离线转换为在线,需清理节点上离线训练作业pod)。OnlineToOffline开始的前提是在线资源组的quota使用率低于设定的阈值下限minRate,为了减少在线推理服务pod不必要的迁移,当离线训练作业出现pending时我们才触发OnlineToOffline转换,每次转换的节点数量根据当前在线资源组quota使用率计算得到,使得转换一定数量的节点在线资源组quota使用率达到期望的阈值excpectRate。当在线资源组qutota使用率超过设定的阈值上限maxRate或在线资源组有pod生pending时,会触发OfflineToOnline转换。
为了保证物理节点OnlineToOffline、OfflineToOnline整机出让过程的平稳,我们引入了unnormal中间状态,正常物理节点用于离线训练作业任务调度时状态为offline normal,用于在线推理服务部署时状态为online normal。节点为normal状态时才允许往上面调度新的任务,而当节点为unnormal状态时禁止向节点上调度任务。OnlineToOffline开始时先将状态置为offline unnormal,这时pod不会往上面调度,然后开始对节点上面已经调度的在线推理服务pod进行清理,清理完毕后将节点状态置为offline normal,开始向上面调度离线训练作业。OfflineToOnline也是类似处理过程。
OnlineToOffline关键是如何确保转换过程中在线推理服务的稳定性,转换时我们进行步长控制,设定每次并行转换的节点数量为3,让在线推理资源平滑出让给离线训练作业,同时为了减少在线推理服务pod驱逐,选择物理节点时会优先选择pod数量最少的节点。在对节点上在线推理pod进行迁移时,我们采用先创建再删除策略,避免在线推理服务由于pod数量减少而导致耗时上升,先将要删除的pod从现有ReplicaSet中进行隔离,这时k8s会在其他节点上创建pod,待新的pod创建好后再将隔离的pod进行删除。
OfflineToOnline则需要在推理资源不足时进行快速响应,当在线推理资源组quota使用率低于阈值下限时或在线pod出现pending时会触发OfflineToOnline转换,一次转换的节点数量为pending pod需要的资源量或在线推理资源组quota使用率达到期望值所需要的资源量。在选择离线节点时优先选择训练任务少且训练任务开始时间短的节点。
为了让离线训练作业更好地使用混部资源,我们对离线训练作业调度策略进行了优化。整个集群资源每个资源组分配少量资源到训练固定资源池,剩余的全部划分到混部资源池,根据历史训练时长对离线训练任务进行画像,长时训练任务调度到训练固定资源池,短时训练任务优先调度到混部资源池。系统对长时任务的定义采用动态调整策略,初始设定训练时长超过12小时为长时任务,上午8:0011:00为离线节点向在线节点转换高峰,18:0022:00为在线节点向离线节点转换高峰,任务调度时间为18:00到次日8:00区间时,长时任务定义时长动态变化为次日8:00减去当前时间加上1小时,其他时间段长时任务定义为超过12小时的任务。调度到混部资源上的训练作业被在线推理服务抢占kill后会立即重新调度到训练固定资源池。
当前平台平均每天65%的离线训练作业使用在线推理出让的混部资源训练完成,使用混部资源训练的任务kill率为1.5%。未来我们将持续优化离在线混部效果并实验同一物理机上同时混部离线和在线任务。
05 模型推理加速
深度学习模型推理对时延和算力有很高的要求,若将训练好的模型直接进行部署,可能会出现推理时间较长或算力不足等问题。常见的模型推理加速方法包括模型网络层优化、模型剪枝加速、降低参数精度、针对硬件特性优化等。在推理加速工具上,NVIDIA有TensorRT,Intel也推出了OpenVINO,下面将对GPU、CPU上推理加速分别进行介绍。
TensorRT是NVIDIA推出的一款基于CUDA和cuDNN的神经网络推理加速引擎,能从以下几个方面来提升模型在GPU上推理性能:
(1)层与张量融合:TensorRT可以做计算图优化,通过kernel融合,减少数据拷贝等手段,生成网络的优化计算图。
(2)内核自动调整:TensorRT可以自动选取最优kernel。同样是矩阵乘法,在不同GPU架构上以及不同矩阵大小,最优的GPU kernel的实现方式不同,TensorRT可以把它优选出来。
(3)权重与激活精度校准:支持将模型量化为FP16/INT8精度,提高吞吐量的同时保持高准确度。
(4)动态张量显存:更大限度减少显存占用,高效地为张量重复利用显存。
(5)多流执行:并行处理多个输入流的可扩展设计。
同时NVIDIA提供了Triton Inference Server(以下简称TIS)框架支持多种框架模型的推理服务部署。TIS是NVIDIA针对旗下GPU推出的高性能在线推理解决方案,可通过HTTP或gRPC端点提供服务,为模型部署方案提供更多灵活性的同时可以充分发挥GPU的并行计算能力。
针对部署在GPU上的深度学习模型,我们采用TensorRT+TIS加速方案,TensorFlow构建的SavedModel格式模型、PyTorch构建的pth格式模型、Keras构建的H5格式模型等先统一转换为ONNX格式,然后通过TensorRT的ONNX Parser模块进行加载解析为TensorRT内部的网络图,最后利用TensorRT进行图层融合等方式进行优化,序列化为Optimized Plans文件。而对于含有TensorRT不支持算子的模型,平台建立自定义算子库,利用TensorRT Plugin插件机制进行实现,将不支持的算子替换为用户自定义的计算逻辑以获得TensorRT优化支持。优化后的Optimized Plans文件通过TIS进行加载提供线上推理服务。
此外平台还提供了TensorRT INT8量化功能,用户训练好的TensorFlow和PyTorch模型在平台上进行TensorRT+TIS加速部署时,只需要打开INT8量化选项并配置好校验数据集,平台通过实现TensorRT量化接口构建插件,在模型优化过程中直接量化,并基于实验的迭代搜索阀值 ,通过配置校验数据集,在模型的每一网络层首先运行标准的FP32推理,收集到激活值的直方图,然后基于不同的INT8量化scale阈值进行推理产生不同的量化分布,最后计算每个分布与原分布的相对熵,选择熵最少的一个,也就是跟原分布最像的一个,即精度损失最小的scale值。通过TensorRT+TIS加速后模型推理性能大幅提升,比如在T4型号显卡上部署RestNet50模型推理,相比TensorFlow Serving直接部署推理,在不降低推理精度情况下,TensorRT+TIS加速后QPS提升80%耗时降低18%,在使用INT8量化后QPS提升6.6倍耗时降低67%。
OpenVINO是Intel基于自身现有的硬件平台开发的,一种可以加快高性能计算机视觉和深度学习视觉应用开发速度的工具套件,包括Model Optimizer和Model Server两个核心组件。Model Optimizer是一个跨平台命令行工具,可将TensorFlow、ONNX、Caffe等框架格式模型转换为与nGraph兼容的开源中间表示IR格式,然后再通过Model Server进行加载提供推理服务能力。Model Optimizer在执行优化时,尽可能删除过多的层和群运算,以更简单、更快速地形成图表,主要优化项包括分组卷积融合、裁剪无效算子、线性运算融合等。
平台基于OpenVINO Model Optimizer和Model Server实现CPU上推理加速,封装了模型转换工具集,将TensorFlow训练的SavedMoel格式模型、PyTorch训练的pth格式模型结合Model Optimizer自动优化为IR格式,然后通过Model Server加载对外提供gRPC/HTTP调用。我们在应用OpenVINO工具时也发现一些问题,如OpenVINO虽然支持大多数主流的OP操作,但也有少量OP暂时还没有支持,如果模型中含有这类OP,则这个模型的优化转换流程执行就会失败,另外OpenVINO并不支持动态输入模型,对于一些输入无法确定的OP和模型无法进行优化。平台从自定义OP扩展、相似OP替换、模型重塑三个方面提供解决方案。
1、自定义OP扩展: 平台提供调试环境,支持用户编写不支持OP的实现代码,编译成so文件保存,推理时平台会将该so文件动态链接到模型优化和模型部署过程中,利用Model Server的pipeline机制,通过编写config文件定义模型的执行流程,定义模型和自定义OP的执行顺利,辅助完成模型优化加速。
2、相似OP替换: 我们在应用OpenVINO加速时发现通常不支持的OP会出现在模型的最后几个节点中,是对已经计算好的数据进行最后的排序和筛选,这时可以通过OP替换的方式进行模型的微调而不影响模型效果。如可以通过ArgMax实现CTCGreedyDecoder的部分功能,剩下功能放入模型�
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek/post/%E4%BA%92%E8%81%94%E7%BD%91/%E9%99%88%E5%85%B4%E6%8C%AF%E5%90%8C%E5%9F%8E%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B9%B3%E5%8F%B0%E8%B5%84%E6%BA%90%E4%BD%BF%E7%94%A8%E7%8E%87%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com