ARTICLE DETAIL

资讯详情

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

HuggingFace实战指南:从模型调用到微调部署全流程

HuggingFace实战指南:从模型调用到微调部署全流程 HuggingFace 是 NLP 和大模型实战里绕不开的工具库从模型调用、数据集处理、微调训练到本地部署几乎每个环节都能用它串起来。很多人对这个生态的第一印象是“模型仓库很多”但真正上手时遇到的问题往往不在模型本身而是搞不清 transformers、datasets、tokenizers、Trainer 之间怎么配合也不知道在普通机器上怎么跑通最小流程。这篇文章会按实际落地的顺序把 HuggingFace 核心模块拆开讲清楚覆盖环境准备、模型调用、微调训练、服务化部署和常见排查思路适合刚接触大模型应用开发、想用开源模型做 NLP 任务、或者准备把模型接到自己业务里的读者。我要先说一个判断HuggingFace 真正解决的不是“把模型做出来”而是把“已经训练好的模型变成你能用的能力”这件事标准化了。你不需要自己写分词、写注意力机制、写训练循环大部分场景直接用它的模块就能完成。两小时不可能把所有细节都吃透但足够把“调用、微调、部署”这条主线跑通。下面按我的实测习惯拆开讲。1. 先搞清楚 HuggingFace 核心模块分别负责什么1.1 核心不是 Model 一个类而是一整套流程多数新手第一次接触 HuggingFace是从from transformers import pipeline开始的。一行代码调用情感分析、文本生成、命名实体识别确实很有吸引力。但真正做项目时你会发现pipeline 只是最上层封装底下还有几层东西要理解。典型流程是先从 HuggingFace Hub 下载模型权重和配置文件用 AutoTokenizer 把原始文本转成模型能读的 token id再用 AutoModel 或 AutoModelForXxx 把输入张量喂给模型得到 logits 或语义向量最后通过 post-processing 把它变成可读结果。这句流程里涉及几个模块transformers负责模型结构、预训练权重加载、tokenizer、trainer。datasets负责数据集的下载、缓存、切分、映射预处理。tokenizers负责把文本切分成 token构建 input_ids、attention_mask。accelerate负责混合精度、多卡训练、设备调度Trainer 底层会用到它。Hub/huggingface_hub负责下载模型、上传模型、管理缓存文件。实际写代码时你不一定每个模块都直接 import但它们的协作关系决定了你能不能顺利跑通。1.2 这四类人最需要 HuggingFace 工具链第一类是 NLP 算法工程师做分类、序列标注、相似度、问答这类任务需要一个统一框架来加载预训练模型和微调。第二类是后端开发或全栈工程师业务里要接入开源的文本生成、翻译、摘要模型但不想从零写推理服务需要一套标准的加载和调用方式。第三类是学生或研究者复现论文里的 baseline对照不同预训练模型的效果需要快速切换模型、数据、超参数。第四类是负责部署和运维的人要把 HuggingFace 模型导出或包装成服务观察显存、吞吐、延迟可能需要转换格式或接 vLLM 这类推理框架。不同角色关注点不一样。做算法的人更关心微调效果做工程的人更关心加载速度和并发能力。这篇文章会把两边都覆盖到。2. 环境准备先把 Python、依赖、硬件和模型下载问题解决2.1 依赖安装与版本兼容最稳的做法是创建独立虚拟环境再安装依赖。建议 Python 版本在 3.9 到 3.11 之间兼容性较好。python3 -m venv hf_env source hf_env/bin/activate pip install --upgrade pip pip install transformers datasets accelerate如果只是学习可以先不装torch装完 transformers 之后根据机器是否支持 GPU 再决定。实际使用中transformers 会自动检测可用的计算设备。# CPU 环境 pip install torch --index-url https://download.pytorch.org/whl/cpu # CUDA 环境以 CUDA 12.x 为例 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121需要确认 PyTorch 版本与 CUDA 驱动匹配。CUDA 版本不要只看系统提示要用nvidia-smi看驱动支持的 CUDA 版本再用torch.cuda.is_available()检查 PyTorch 是否真的识别到了 GPU。常见误区装完 torch 直接 import 不报错不代表能用 GPU。必须单独确认torch.cuda.is_available()返回 True。2.2 硬件门槛怎么判断模型能不能跑主要看参数规模、单条输入长度、批量大小和精度这几项。我用几个常见模型举例具体以你本机实测为准模型规模显存需求fp16/bf16显存需求int8适合场景100M 到 300M 参数如 bert-base2GB 左右1GB 左右文本分类、NER、句子向量1B 到 3B 参数如 Qwen2.5-1.5B6GB 到 10GB4GB 左右文本生成、摘要、简单对话7B 到 8B 参数如 Llama-3.1-8B16GB 到 20GB8GB 到 10GB对话、指令跟随、复杂生成13B 以上24GB 以上12GB 以上长文本、复杂推理注意这只是单卡推理的最低参考。训练或微调的显存需求通常是推理的 2 到 4 倍因为要保存梯度。如果你的显卡是 8GB 显存又想微调 7B 模型默认的 Trainer 大概率会爆显存解决办法是把per_device_train_batch_size调小、开启梯度累积或者用 LoRA 这类参数高效微调方法。CPU 环境也能跑但速度会慢很多。小模型比如 100M 到 300M 的分类模型CPU 上单条推理可能几百毫秒一条条跑还能接受。7B 生成的场景CPU 上会明显卡顿不适合交互式对话。内存方面加载 7B 模型时如果权重以 fp16 存储大约需要 14GB 内存以 int4 量化存储大约需要 5GB 到 6GB。因此内存建议至少 16GB最好 32GB 以上。磁盘方面主要注意模型缓存目录的剩余空间。HuggingFace 默认会把下载的模型缓存到用户目录下的.cache/huggingface。一个 7B 模型 fp16 权重大约 14GB多下载几个模型就容易占满磁盘。建议提前设置环境变量把缓存路径改到大盘目录。export HF_HOME/data/hf_cache2.3 国内环境下载慢或失败怎么处理很多新手卡在第一步from_pretrained时网络超时或者下载到一半失败。这通常不是代码问题而是 HuggingFace Hub 的访问链路不够稳定。几个稳妥办法按推荐顺序执行使用国内镜像服务设置环境变量export HF_ENDPOINThttps://hf-mirror.com设置之后from_pretrained下载模型会自动走镜像地址。如果项目里不方便改环境变量可以在 Python 里设置import os os.environ[HF_ENDPOINT] https://hf-mirror.com离线方式在一台能访问的机器上下载好模型目录复制到本地然后直接用from_pretrained(/本地路径)加载。通过huggingface-cli download提前下载huggingface-cli download Qwen/Qwen2.5-1.5B --local-dir ./models/qwen2.5-1.5b下载失败时不要反复重试同一个默认超时设置。可以先下载小文件确认网络稳定性再设置HF_HUB_DOWNLOAD_TIMEOUT增大超时时间。更常见的问题是一个大文件下载到一半断了HuggingFace Hub 会根据服务端返回的文件大小继续续传所以不用急着删除缓存目录。3. 模型调用先跑通 pipeline再用 AutoModel 精细控制3.1 pipeline最快验证模型能不能用的方式当你只想快速验证一个模型效果时直接使用 pipeline 最省事。它会把 tokenizer、模型、输出后处理全部封装好。from transformers import pipeline classifier pipeline(sentiment-analysis) result classifier(这个产品用起来很流畅界面也很清楚) print(result)第一次运行会自动下载一个较小的默认情感分析模型。如果是英文任务输出结果通常包含 label 和 score。pipeline 也支持指定模型classifier pipeline( sentiment-analysis, modeluer/roberta-base-finetuned-jd-binary-chinese, device0 # 使用 GPUCPU 环境可以去掉 )这里要说明device0表示使用第一块 GPU。如果你的机器显存很小建议先把device去掉让代码自动跑 CPU再观察速度和内存变化。pipeline 能处理的任务很多包括文本生成、文本分类、命名实体识别、问答、翻译、摘要、文本向量嵌入等。但要注意不是所有 pipeline 都适合服务化。比如text-generation在生成任务里会反复调用模型性能优化空间很大单纯用 pipeline 包一层然后对外提供接口在并发高时会很难看。3.2 AutoModel 和 AutoTokenizer 的精细调用pipeline 适合验证业务开发更常用 AutoModel 和 AutoTokenizer。这样能控制输入的分批、截断、padding也能读到模型的原始输出。以文本分类为例from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch model_name uer/roberta-base-finetuned-jd-binary-chinese tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name) texts [ 物流很快包装完整, 收到货发现屏幕碎了客服也不处理, ] inputs tokenizer( texts, paddingTrue, truncationTrue, max_length128, return_tensorspt ) with torch.no_grad(): outputs model(**inputs) probs torch.nn.functional.softmax(outputs.logits, dim-1) print(probs)需要理解paddingTrue和truncationTrue的作用。一个批次里的文本长度往往不同模型要求输入的张量形状一致所以短句要补齐到批次最大长度。padding就是做这件事的。但如果输入特别长token 数量会超出模型最大长度所以要用truncation截断通常限制在max_length内。这里的max_length不是越大越好。设太大单条输入占用的显存会线性增加设太小信息会丢失。一般先看模型支持的 max length再按业务文本长度设一个合理值。比如短文本分类设 128 或 256长文档摘要可以先做切片再处理。3.3 生成任务的参数temperature、top_p、max_new_tokens文本生成和分类不同分类只看 logits 的分布生成需要在此基础上逐步采样。HuggingFace 的 AutoModelForCausalLM 是生成模型常用的入口。from transformers import AutoTokenizer, AutoModelForCausalLM model_name Qwen/Qwen2.5-1.5B-Instruct tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained( model_name, torch_dtypeauto, device_mapauto ) prompt 用三句话解释什么是检索增强生成。 messages [{role: user, content: prompt}] text tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue ) inputs tokenizer(text, return_tensorspt) output_ids model.generate( inputs.input_ids.to(model.device), max_new_tokens512, temperature0.7, top_p0.9, do_sampleTrue ) response tokenizer.decode(output_ids[0][inputs.input_ids.shape[1]:], skip_special_tokensTrue) print(response)max_new_tokens决定生成多少新 token它比max_length更直观。temperature控制随机性越低越保守越高越发散。top_p是核采样只从累计概率达到 p 的候选里采样。业务场景里如果做知识库问答可以调低 temperature如果做创意写作可以适当调高。device_mapauto是让 accelerate 自动分配模型到可用设备。多卡机器上模型会被切分到多张卡没有 GPU 时模型默认放 CPU。这是很实用的参数但在推理服务里要谨慎用因为它会增加加载时间也可能导致单次响应延迟不稳定。3.4 模型调用时最容易踩的坑我见到的调用阶段报错大部分不是模型问题而是输入形态问题。输入文本是 list 时tokenizer 会返回 batch输入是单条 str 时返回的 input_ids 是二维还是一维要特别小心。处理混用数据时建议统一转成 list避免后续张量维度对不上。另一个高频问题是 tokenizer 和模型不匹配。比如用 A 模型的分词器加载 B 模型会出现 token index 越界或输出乱码。不要随意混搭。如果确实需要换 tokenizer要检查 vocab size 和模型 embedding 维度是否一致。还有模型下载到本地后目录里通常是 config.json、tokenizer.json、model.safetensors 等文件。正确做法是加载目录路径而不是加载某个 safetensors 文件model AutoModelForSequenceClassification.from_pretrained(/data/models/my_model)如果你错误传入了model.safetensors的路径transformers 通常不支持直接加载单个权重文件应该加载整个模型目录。排查时先看目录结构是否完整。4. 微调不是从零训练而是让模型适配你的数据4.1 微调前要理解 Trainer 帮你在做什么很多人会问为什么微调和训练不一样。预训练是让模型在海量文本上学习语言规律代价很高微调是拿已经学好的模型在特定任务数据上继续训练让输出适配你的格式和语义偏好。HuggingFace 的 Trainer 把训练循环、梯度更新、日志记录、checkpoint 保存都封装好了。最小微调示例可以这样写from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments ) from datasets import load_dataset dataset load_dataset(imdb, splittrain[:1000]) tokenizer AutoTokenizer.from_pretrained(bert-base-uncased) def preprocess(example): return tokenizer(example[text], truncationTrue, paddingmax_length, max_length128) dataset dataset.map(preprocess, remove_columns[text]) model AutoModelForSequenceClassification.from_pretrained(bert-base-uncased, num_labels2) training_args TrainingArguments( output_dir./results, per_device_train_batch_size8, per_device_eval_batch_size8, num_train_epochs1, evaluation_strategyepoch, save_strategyepoch, logging_steps50, fp16True, ) trainer Trainer( modelmodel, argstraining_args, train_datasetdataset, eval_datasetdataset.select(range(100)), ) trainer.train()这里最关键的不是 Trainer 本身而是数据集的格式。Trainer 要求训练集是 Dataset 对象每条样本是 dict并且包含 model 前向能接收的字段。上面例子中preprocess 函数把文本转成了 input_ids、attention_mask所以 Trainer 直接调用model(**batch)就能完成前向。4.2 数据集结构label 字段、remove_columns 和 map 的关系使用datasets模块时经常遇到的问题是字段不对。模型需要的字段是 input_ids、attention_mask、token_type_ids 等这是 tokenizer 返回的。如果你的原始数据里有 text 字段map 之后没有删掉Trainer 可能不会报错但会在 collator 阶段出现意外字段导致训练失败。稳妥做法是保留你需要的 label 字段删掉原始文本字段。不同任务 label 取值不同分类任务label 是整数对应类别索引。回归任务label 是浮点数。生成任务通常用 input_ids 和 labelslabels 是目标文本的 token id。对于生成任务比如把原文摘要成目标摘要需要自己构造 labelsdef preprocess(example): model_inputs tokenizer(example[article], truncationTrue, max_length1024) labels tokenizer(example[summary], truncationTrue, max_length256) model_inputs[labels] labels[input_ids] return model_inputs这里要防止 labels 比 input_ids 短时产生错误。实际训练时可以把 labels 的 padding 单独处理避免模型把 padding token 也当作生成目标。4.3 TrainingArguments 里的参数该怎么设置微调效果不好或者训练崩溃很多是超参设置不合适。判断标准不是“跑完就行”而是看训练集 loss 是否下降、验证集指标是否变好、显存是否出现 OOM。参数作用新手建议per_device_train_batch_size每张卡上的训练样本数从 4 或 8 开始OOM 就减半gradient_accumulation_steps梯度累积步数显存不足时设为 2 或 4learning_rate学习率分类任务常用 2e-5 到 5e-5生成任务常用 1e-5 到 3e-5num_train_epochs训练轮数小数据先训 1 轮看趋势warmup_ratio 或 warmup_steps学习率预热推荐 warmup_ratio0.1weight_decay权重衰减常用 0.01fp16 / bf16混合精度显卡支持就开启显存占用显著下降logging_steps日志打印频率50 或 100方便观察 losssave_strategycheckpoint 保存策略建议按 epoch 或固定步数保存避免存太多fp16 和 bf16 要注意。A100、H100 等新卡建议用 bf16数值稳定性更好80 系和 30 系等消费级显卡用 fp16 通常没问题。如果 loss 出现 NaN可以先关掉 fp16或检查数据集里是否有异常大数。4.4 低资源微调还不想被显存劝退就考虑 LoRA如果你的显卡只有 8GB 或 6GB直接微调 7B 模型几乎不可能。这不单是能不能加载的问题而是反向传播要保存的激活值会远超权重大小。这时可以换思路使用参数高效微调方法最常用的是 LoRA。HuggingFace 生态里LoRA 的常见实现是peft库。pip install peft使用方式不复杂先加载基础模型再用 get_peft_model 包一层。from peft import LoraConfig, get_peft_model, TaskType from transformers import AutoModelForCausalLM model AutoModelForCausalLM.from_pretrained( Qwen/Qwen2.5-1.5B-Instruct, torch_dtypeauto, device_mapauto ) lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, lora_alpha32, lora_dropout0.1, target_modules[q_proj, k_proj, v_proj, o_proj] ) model get_peft_model(model, lora_config) model.print_trainable_parameters()r是低秩矩阵的秩决定可训练参数量。lora_alpha是缩放系数数值越大LoRA 对原模型的影响越强。target_modules指定把 LoRA 加在哪些模块上不同模型结构里模块名可能不同需要先打印模型结构确认。训练完成后LoRA 权重只占很小的空间可以单独保存model.save_pretrained(./lora_weights)到部署阶段加载 LoRA 的方式是先加载原模型再用 PeftModel.from_pretrained 加载 LoRA 权重。不要只保存 LoRA 而丢了基础模型路径否则无法独立运行。5. 部署从训练产物到可调用服务5.1 保存、加载和路径规划训练完成或微调完成后第一步不是急着写服务而是确认产物完整可加载。建议单独建立一个模型目录目录下包含 config.json、tokenizer 相关文件、模型权重以及本项目的 README 或版本说明。# 保存完整模型和 tokenizer model.save_pretrained(./output_model) tokenizer.save_pretrained(./output_model)加载时from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer AutoTokenizer.from_pretrained(./output_model) model AutoModelForCausalLM.from_pretrained( ./output_model, torch_dtypeauto, device_mapauto )生产环境里我建议按“模型 ID 日期 数据集版本”来命名目录比如qwen2.5-1.5b-chat-20250101-lora-v1。这样当多个版本并存时不会出现不知道当前服务用的是哪个模型的问题。5.2 用 FastAPI 封装推理接口最直接的服务化方案是用 FastAPI 包一个 HTTP 接口。这里不涉及复杂框架只做最小可用示例。from fastapi import FastAPI from pydantic import BaseModel from transformers import AutoTokenizer, AutoModelForCausalLM app FastAPI() model_name ./output_model tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name, device_mapauto) class GenerateRequest(BaseModel): prompt: str max_new_tokens: int 256 temperature: float 0.7 app.post(/generate) def generate(req: GenerateRequest): inputs tokenizer(req.prompt, return_tensorspt) output_ids model.generate( inputs.input_ids.to(model.device), max_new_tokensreq.max_new_tokens, temperaturereq.temperature, do_sampleTrue ) response tokenizer.decode(output_ids[0], skip_special_tokensTrue) return {response: response}这个示例适合学习和内部验证但不太适合直接应对大量并发。原因有几个模型加载占用大量显存每次请求都要复用同一个模型实例不能每个请求都重新加载生成任务没有做请求排队多个请求同时进入时GPU 显存和推理延迟会变得不可控也没有超时和错误重试机制。所以接口化之后至少要观察响应时间、超时率、最大并发下显存占用这些指标。5.3 和其他推理框架配合vLLM、Ollama 的边界HuggingFace 的 transformers 只负责模型加载和推理逻辑要获得更高吞吐、更低延迟可以结合专门推理框架。vLLM 适合大模型生成场景通过 PagedAttention 管理 KV Cache多并发下吞吐量比原生 transformers 高不少。加载时可以直接读取 HuggingFace 模型目录也可以读取已经转换好的模型格式。Ollama 适合本地快速体验配置简单但自定义模型和复杂接入能力相对受限。TGIText Generation Inference适合需要生产化部署的团队支持批处理、流式输出、监控指标。不管用哪个框架迁移前都要先验证“同一个模型权重在不同框架下的输出是否一致”。你可能会遇到分词器行为、采样参数默认值不一致导致的输出差异。所以先固定一组测试用例比对框架迁移前后的结果。5.4 部署阶段的性能观察指标部署不是“能返回结果就行”至少要看四个指标单次推理延迟从请求进入到返回第一个 token 或完整结果的时间。吞吐单位时间处理的请求数或生成 token 数。显存占用模型、KV Cache、batch 尺寸决定峰值占用。成功率请求超时、报错、生成空结果的比例。如果你用的是 transformers 原生推理建议先压测单并发再逐步增加到 2、4、8 并发。不要一上来就开 32 并发否则很可能只测出显存溢出或请求超时不能反映真实性能。另一个容易被忽略的点是输出长度。max_new_tokens设得很大单请求延迟和显存占用会成倍增长。服务端最好在接口层就限制最大值比如最多生成 1024 个 token防止前端传来一个超大参数把服务拖垮。6. 遇到问题怎么排查从日志到数据再到环境6.1 先看现象再动手改参数实际跑 HuggingFace 项目时错误五花八门但有一个通用顺序先看是什么现象再判断该查哪个层面。如果是 ImportError 或 ModuleNotFoundError查依赖版本和 Python 环境。如果是下载报错或连接失败优先看网络、镜像设置、缓存目录权限。如果是显存不足 OOM看模型规模、batch size、输入长度、是否开启混精度。如果是 loss 为 NaN看学习率、fp16、数据里是否有异常值。如果是生成内容为空或乱码先查 tokenizer 是否匹配、generate 参数是否过度保守。如果是推理速度很慢先看是否误用了 CPU、是否没有开混精度、是不是批量太小或太大。6.2 常见问题排查表现象可能原因优先排查顺序from_pretrained 超时网络不稳定、默认超时过短设置镜像环境变量再尝试下载最后检查磁盘空间OOM 显存不足batch 太大、输入太长、未开启混精度减小 batch、调低 max_length、开启 fp16/bf16、加梯度累积训练 loss 不降学习率过高或过低、数据标签有误先看训练集 loss 是否下降再用小 batch 和默认学习率复现中文输出乱码tokenizer 不匹配、模型和中文能力不匹配确认使用中文预训练模型或指令模型然后检查 tokenizer 配置本地加载模型失败路径指向单个文件、目录不完整打印目录结构确认 config 文件和权重文件存在部署接口并发超时模型推理未排队、单请求耗时长限制并发、增加超时、考虑接入推理框架6.3 排查时最容易误判的内容很多问题看起来像模型能力不行实际是输入没有预处理干净。比如文本里包含大量换行、特殊符号导致 token 长度暴涨或者 label 编号从 1 开始而模型实际期望从 0 开始又或者 tokenizer 截断了关键信息导致生成结果短而空。也有一种常见情况是模型已经正常加载但由于缓存目录里的旧版本和代码不完全兼容导致行为异常。出现这种情况时可以清空当前项目的模型缓存目录重新下载后对比。如果同时跑了多个 Python 项目还要检查是不是用了不同的虚拟环境。终端里which python和pip show transformers能快速确认当前生效的路径。不要看系统里装了新版本就认为当前环境是新版本。7. “两小时吃透”的路线怎么安排更合理7.1 第一小时把调用环节跑稳我建议不要一上来就做微调先拿一个你能找到的中文或英文模型从 pipeline 开始依次完成加载、单条推理、列表批量推理、保存结果四步。目标不是理解所有源码而是熟悉“输入长什么样、输出长什么样、报错怎么看”。这一小时里至少跑通一个分类任务和一个生成任务。分类任务让你理解 tokenizer 的 padding、truncation生成任务让你理解 max_new_tokens、temperature、top_p 这些参数的影响。7.2 第二小时跑一次最小微调选一个很小的模型比如 100M 到 300M 的 bert 系列准备一个 500 到 1000 条文本的分类数据集用 Trainer 跑 1 到 2 个 epoch。这时你会看到完整的训练日志也会遇到某个参数设置不合理的情况。如果直接拿 7B 模型微调两小时很可能全花在环境和显存问题上。先在小模型上把流程跑通后面换大模型只是换模型名和数据路径的问题排错思路是一样的。7.3 后续真正需要深入的方向两小时只是起点。进一步深入时按需选择方向要把微调效果做稳定学怎么构建高质量数据集、怎么划分训练验证集、怎么设计 prompt 模板。要把服务做大并发学 vLLM 的部署参数、PagedAttention、continuous batching、流式输出。要把模型跑在低资源设备上学 GGUF 量化、ONNX 导出、ollama 集成。要把模型接到业务系统里学消息队列、异步任务、结果缓存、失败重试。这些方向不需要一次学完。当前文章这一条主线也就是“调用、微调、部署”已经覆盖了大部分项目里会用到的核心环节。真正落地时我最想强调的还是那句话先跑通最小流程再加复杂度。不要一开始就追求大模型、大批量、大并发先把输入输出和日志盯住后续每一步都能清楚地知道是环境问题、代码问题还是模型本身的问题。
返回列表