资讯动态

基于Mixtral-8x7B的中文MoE模型优化:从词表扩充到QLoRA增量训练

发布时间:2026/8/4 17:03:39 来源:尧图企业网站定制
1. 项目概述与核心价值最近在开源社区里Mixtral-8x7B 这个模型火得一塌糊涂。它由 Mistral AI 发布凭借其独特的“专家混合”架构在保持与 Llama 2 70B 相近性能的同时推理时只激活约 130 亿参数效率和性能的平衡做得相当出色。但作为一个主要面向中文场景的开发者我拿到原版模型的第一反应是它的中文处理能力尤其是分词效率能行吗原版 Mixtral-8x7B 沿用了 Llama 的词表对中文的编码效率确实不高。一个常见的中文句子它可能会切成一大堆零碎的 token这不仅拖慢推理速度更关键的是会严重占用宝贵的上下文窗口。在需要长上下文理解或复杂推理链的场景下这简直是致命的。于是我们团队决定动手做一个专门为中文优化的 Mixtral-8x7B也就是这个Chinese-Mixtral-8x7B项目。这个项目的核心目标很明确在不显著损害模型原有英文能力的前提下大幅提升其中文编解码效率和中文理解生成能力。我们不是从头训练一个模型那成本太高了。我们采用的是“增量预训练”的技术路线具体来说就是两步走第一步为模型扩充一个包含大量中文字符和词汇的新词表第二步使用大规模高质量的中英文语料对这个“换装”后的模型进行继续训练让它适应新的词汇并学习中文的语言模式。最终开源的模型在多项中英文基准测试上都表现出了竞争力尤其是在英文能力上得益于原版 Mixtral 的强劲底子它甚至超过了同规模的其他中文优化模型。对于任何想要在中文任务上尝试 MoE 模型或者希望有一个中英文能力均衡且高效的基础模型的同行来说这个项目都提供了一个非常不错的起点。2. 核心思路与技术方案选型做中文大模型优化摆在面前的路其实有好几条。最常见的是“中文持续预训练”即在原版模型的基础上直接用中文数据继续训练。这种方法简单直接但问题在于原版词表对中文不友好训练和推理效率低下属于“带着镣铐跳舞”。另一种极端是“从头预训练”这能获得最彻底的中文优化但计算成本和时间成本是天文数字非一般团队所能承受。我们选择了中间路线扩词表增量预训练。这个方案的精妙之处在于它试图在“效率”和“效果”之间找到一个最佳平衡点。2.1 为什么选择扩词表首先直接使用原词表进行中文训练编码效率低是硬伤。一个汉字可能被拆成多个 byte-level 的 token这直接导致序列长度膨胀同样的中文内容需要的 token 数量可能是优化后的 2-3 倍。这直接挤占了本可用于指令或思维链的上下文空间。计算开销增加Transformer 的计算复杂度与序列长度的平方成正比。更长的序列意味着更慢的推理速度和更高的显存占用。潜在的信息损失过于细碎的切分可能破坏词汇或概念的完整性不利于模型学习语义单元。因此扩充词表加入足够多的中文字符和常见词汇是提升中文处理效率的治本之策。2.2 为什么选择 Mixtral-8x7B 作为基座在众多开源基座模型中选中 Mixtral-8x7B我们是经过深思熟虑的先进的 MoE 架构MoE 模型是当前大模型 scaling 的一个重要方向。它通过稀疏激活在总参数量巨大的情况下让每次推理的计算量保持在一个可控范围。Mixtral-8x7B 是首个将 MoE 架构成功推向主流的开源模型研究和使用价值极高。强大的原生性能其在 MMLU、HellaSwag 等权威英文基准上的表现证明了其作为基座模型的优秀素质。一个好的“底子”是后续优化成功的前提。活跃的生态支持Mixtral 发布后迅速得到了vLLM,Flash Attention 2,bitsandbytes等主流优化库和框架的支持部署和应用的生态已经初步形成降低了我们的使用和二次开发成本。2.3 为什么采用 QLoRA 进行训练Mixtral-8x7B 有 467 亿参数即使只训练一部分全参数微调对显存的需求也是恐怖的。QLoRA 技术是我们的救星。4-bit 量化将模型权重压缩到 4-bit 精度NF4 格式显存占用降至约 1/4。低秩适配器我们不直接更新庞大的原始权重而是训练一系列小的、低秩的适配器矩阵。训练时只有这些适配器和我们新增的词嵌入层参数被更新。性能保持研究表明QLoRA 训练的性能可以接近全参数微调是资源受限情况下的最佳实践。这个技术选型决定了我们整个项目的可行性。它让我们能够在有限的算力下我们主要使用 8 卡 A800 80G 集群完成对这样一个大模型的、非 trivial 的改造。实操心得基座模型与训练策略的耦合性选择训练方法时必须考虑基座模型的特点。Mixtral 的 MoE 结构让它的参数分布和密集模型不同。我们实验发现对模型中所有的 Linear 层包括每个专家内部的 FFN都添加 LoRA 适配器效果比只加在注意力层要好。这可能是 MoE 模型中专家前馈网络承担了更核心的运算所致。3. 词表扩充寻找最佳中文词汇边界词表扩充听起来只是“加一些新词”但要做好里面门道很多。加多少词加哪些词怎么初始化新加的词向量每一个问题都直接影响最终模型的性能。3.1 词表构建与评估指标我们使用sentencepiece工具在约 14G 的中文语料知乎、悟道上训练 BPE 词表。关键不在于训练过程而在于如何从成千上万个候选词表中选出最优的一个。我们面临一个权衡词表太小中文压缩率低效率提升有限词表太大embedding 层会变得稀疏可能增加模型学习的难度并导致 embedding 矩阵占用过多显存。为了科学地评估我们引入了ALP这个指标。ALP 的核心思想是一个好的词表应该能够用较少的、有意义的 token 来表示一种语言。它通过计算文本的平均分词长度并对低频子词进行惩罚来量化词表对特定语言的“词汇能力”。ALP 值越高通常意味着该词表对这种语言的编码效率越高。我们固定了新增中文 token 的总数例如 25000 个但调整其中“单字” token 的数量如 4451, 5435, 6414, 7434 个生成了多组词表。然后在独立的书籍和百科语料上计算它们的 ALP 值。从上图可以清晰看到随着总词表大小的增加ALP 值先快速上升后趋于平缓。我们选择 ALP 曲线“拐点”对应的词表大小此时增加词表带来的收益开始递减。最终我们选择了在新增 25000 个中文 token 的前提下ALP 值最高的那组配置即包含6414 个中文单字的词表。最终词表大小为 57000。3.2 新词向量初始化策略词表确定了接下来要把新 token 对应的 embedding 向量塞进模型的 embedding 层和语言模型头中。怎么初始化这些新向量至关重要。一种朴素的做法是直接用随机正态分布初始化。但我们认为新加入的中文字符和词汇在语义空间上应该与原有词表中功能相近的 token 靠近。因此我们采用了更聪明的策略对于每个新 token我们计算它在原词表中所有子词subwordembedding 的平均值作为其初始值。例如新词“人工智能”可能被原词表切分为“人”、“工”、“智”、“能”四个子词。我们就取这四个子词对应向量的平均值作为“人工智能”这个词的初始向量。对于新增的单字也采用类似方法。注意事项初始化的一致性必须确保tokenizer新增词汇的顺序与模型embedding层新增行向量的顺序完全一致。通常的做法是先得到扩充后的 tokenizer然后按照tokenizer.get_vocab()提供的映射关系按顺序扩展模型的 embedding 矩阵。顺序错乱会导致模型完全无法工作。4. 增量预训练全流程实操解析有了新词表下一步就是用数据让模型“学会”使用它。这个过程就是增量预训练。4.1 环境搭建与依赖安装稳定可复现的环境是训练的基础。我们推荐以下配置# 基础 PyTorch 和 CUDA conda create -n chinese-mixtral python3.10 conda activate chinese-mixtral pip install torch2.0.1 torchvision0.15.2 torchaudio2.0.2 --index-url https://download.pytorch.org/whl/cu118 # 核心机器学习库 pip install transformers4.36.2 datasets accelerate peft trl # 训练优化与工具 pip install bitsandbytes # 用于4-bit量化 pip install flash-attn --no-build-isolation # Flash Attention 2 加速 pip install deepspeed # 如需使用DeepSpeed需从源码编译注意与CUDA版本匹配 pip install tensorboard scikit-learn pandas matplotlib nltk rouge # 数据处理 pip install sentencepiece fire这里有几个关键点PyTorch 2.0.1我们开发时基于此版本高版本可能存在 API 变动。Flash Attention 2务必添加--no-build-isolation参数否则可能因环境问题编译失败。安装成功后在加载模型时指定attn_implementation”flash_attention_2″即可启用能显著提升训练和推理速度。bitsandbytesQLoRA 依赖的 4-bit 量化库。Linux 系统安装一般没问题Windows 可能需要额外折腾。4.2 数据准备与预处理我们使用了约 42B Token 的数据进行训练混合了中文和英文语料以保持模型的多语言能力。中文数据SkyPile-150B 数据集中 2022 和 2023 年的部分约 30B Token。我们过滤了低质量数据并进行了去重。英文数据SlimPajama-6B 数据集我们重复了 2 个 Epoch约 12B Token。数据处理流程如下下载与合并使用datasets库下载数据或将本地jsonl文件合并。一个实用的脚本是使用split命令按行数划分训练集和验证集如 999:1。# 假设所有分片在 data/ 目录下 cat data/*.jsonl data/all.jsonl total_lines$(wc -l data/all.jsonl) train_lines$((total_lines * 999 / 1000)) split -l $train_lines data/all.jsonl data/part- mv data/part-aa data/train.jsonl mv data/part-ab data/valid.jsonl注册数据集在data/datasets.toml中配置数据集信息让训练脚本知道去哪里找数据。[SkyPile-2023] splits [train, valid] root /path/to/your/data doc sky-{split} # 对应 sky-train.jsonl, sky-valid.jsonl encoded encoded-sky-{split}数据预处理关键步骤这是最耗时的步骤之一但至关重要。我们需要用新的 tokenizer 将所有文本数据预先编码成 token ID并保存起来这样训练时就直接读取 ID省去了实时分词的开销。python data/preprocess_datasets.py \ --ds_name SkyPile-2023 \ --tokenizer_name_or_path ./chinese-mixtral-tokenizer \ # 你扩充后的tokenizer路径 --max_length 2048 # 根据你的上下文长度设定预处理脚本会生成encoded-*的二进制文件。你可以用data/utils.py来检查每个数据集的总 token 数确保数据量符合预期。4.3 QLoRA 训练配置详解训练脚本的核心是torchrun命令它启动了分布式训练。以下是关键参数解析torchrun --nnodes1 --nproc_per_node8 \ # 使用8张GPU train.py \ --model_name_or_path mistralai/Mixtral-8x7B-v0.1 \ # 基座模型 --tokenizer_name ./chinese-mixtral-tokenizer \ # 新tokenizer --use_peft \ # 启用PEFT (QLoRA) --lora_r 64 \ # LoRA的秩越大能力越强参数量越多 --lora_alpha 128 \ # LoRA的缩放因子通常设为r的2倍 --lora_dropout 0.05 \ # Dropout防止过拟合 --lora_target_modules “all-linear” \ # 对所有Linear层加LoRA --quantization 4bit \ # 4-bit量化 --bf16 \ # 使用bfloat16混合精度训练 --output_dir ./output \ # 输出目录 --num_train_epochs 1 \ # 训练轮数 --per_device_train_batch_size 1 \ # 每GPU批大小受限于显存 --gradient_accumulation_steps 16 \ # 梯度累积步数有效批大小 batch_size * steps * GPU数 --learning_rate 2e-4 \ # 学习率 --lr_scheduler_type cosine \ # 余弦退火学习率调度 --warmup_ratio 0.03 \ # 预热步数比例 --logging_steps 10 \ --save_strategy “steps” \ --save_steps 500 \ --save_total_limit 3 \ --data_config ./data/datasets.toml \ # 数据集配置 --data_weights “1.0:SkyPile-2022,0.1:SkyPile-2023,1.0:SlimPajama” # 数据集混合权重参数选择背后的考量lora_r64对于 70B 量级的模型秩太小如 8可能不足以学习足够的知识我们实验发现 64 是一个较好的平衡点。per_device_train_batch_size1由于模型巨大即使量化后单卡也几乎无法放下大于 1 的 batch。通过gradient_accumulation_steps16和 8 卡并行我们将有效批大小提升到了 128保证了训练的稳定性。learning_rate2e-4对于 LoRA 训练学习率通常比全参数微调设得稍高一些。我们参考了相关论文和社区实践这个值在多次实验中表现稳定。数据混合权重我们让模型更多地接触中文数据SkyPile但对 2023 年的数据只采样 10%以平衡数据的新鲜度和多样性。英文数据SlimPajama则全量使用以稳固其英文能力。4.4 模型合并与导出QLoRA 训练完成后我们得到的是一个小巧的适配器文件adapter而不是完整的模型权重。要用于推理需要将适配器与原始模型合并。from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer # 加载原模型和tokenizer base_model AutoModelForCausalLM.from_pretrained( “mistralai/Mixtral-8x7B-v0.1”, torch_dtypetorch.bfloat16, device_map“auto” ) tokenizer AutoTokenizer.from_pretrained(“./chinese-mixtral-tokenizer”) # 加载QLoRA适配器 model PeftModel.from_pretrained(base_model, “./output/checkpoint-xxxx”) # 合并模型并保存 merged_model model.merge_and_unload() merged_model.save_pretrained(“./chinese-mixtral-8x7b-merged”) tokenizer.save_pretrained(“./chinese-mixtral-8x7b-merged”)合并后的模型就是一个标准的 Transformers 模型可以直接用from_pretrained加载使用。5. 模型推理与性能实测模型训练好了是骡子是马得拉出来溜溜。我们重点关注三个方面基础能力评测、生成效果观察和推理效率。5.1 评测基准结果分析我们选取了 C-Eval 和 CMMLU 作为中文知识理解评测集MMLU 和 HellaSwag 作为英文能力评测集。对比模型选择了其他几个知名的、参数量在 13B 级别的中文优化开源模型。模型名称增量训练语料C-Eval (5-shot)CMMLU (5-shot)MMLU (5-shot)HellaSwag (0-shot)Ziya2-13B-Base650B59.2960.9359.8658.90TigerBot-13B-Base-v3500B50.5251.6553.4659.16Chinese-Mixtral-8x7B (Ours)42B52.0851.0869.8065.69从结果可以看出中文能力我们的模型在训练数据量42B Token远少于 Ziya2 (650B) 和 TigerBot (500B) 的情况下取得了与 TigerBot 相当的中文评测分数。这证明了我们扩词表增量训练路线的有效性模型能够高效地利用有限的中文数据学习。英文能力这是最大的亮点。我们的模型在 MMLU 和 HellaSwag 上大幅领先其他对比模型甚至超过了数据量更大的 Ziya2。这充分说明了 Mixtral-8x7B 基座模型的强大以及我们的训练方法成功保留了其原有的英文能力。对于需要中英文混合任务的应用场景这是一个巨大优势。5.2 生成效果与编解码效率评测分数是冰冷的生成效果是直观的。我们对比了不同模型在相同提示词下的生成结果。Chinese-Mixtral-8x7B 在中文生成上流畅、自然在需要知识推理的问题上也能给出结构清晰的回答比如解释概念、列举步骤等。更重要的提升在于编解码效率。我们使用相同的约 1.4GB 中文文本进行测试模型词表大小编码后 Token 数量相对原版提升Mixtral-8x7B-v0.1 (原版)32,000606M-Chinese-Mixtral-8x7B (Ours)57,000355M41.5%TigerBot-13B-Base-v365,112342M43.6%我们的模型将中文编码效率提升了41.5%。这意味着推理速度更快处理的 token 数减少直接降低计算量。上下文窗口更“长”在固定的最大序列长度限制下如 4096你可以输入更多字数的中文内容或者留出更多空间给模型生成。成本更低对于按 token 计费的云 API 服务这能直接省钱。5.3 推理代码示例与优化加载并使用合并后的模型非常简单和任何 Hugging Face 模型一样import torch from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline model_id “./chinese-mixtral-8x7b-merged” # 或 “HIT-SCIR/Chinese-Mixtral-8x7B” tokenizer AutoTokenizer.from_pretrained(model_id) # 方式1使用 Flash Attention 2 加速推荐需安装 flash-attn model AutoModelForCausalLM.from_pretrained( model_id, attn_implementation“flash_attention_2”, # 启用加速 torch_dtypetorch.bfloat16, device_map“auto” ) # 方式2使用 4-bit 量化节省显存 model AutoModelForCausalLM.from_pretrained( model_id, load_in_4bitTrue, # 4-bit量化加载 device_map“auto” ) # 构造提示词 prompt “请用中文解释一下什么是机器学习。” messages [{“role”: “user”, “content”: prompt}] text tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptTrue) # 生成 inputs tokenizer(text, return_tensors“pt”).to(model.device) outputs model.generate(**inputs, max_new_tokens512, temperature0.7, do_sampleTrue) print(tokenizer.decode(outputs[0][len(inputs[0]):], skip_special_tokensTrue))重要提示本项目开源的是基座模型它没有经过指令微调。因此它的“对话”或“遵循指令”的能力比较弱。它更擅长完成续写、补全等任务。如果你需要让它能对话必须进行后续的指令微调。6. 指令微调实战指南要让 Chinese-Mixtral-8x7B 变成一个能对话的助手指令微调是关键一步。我们提供了完整的微调代码。6.1 指令数据准备指令数据需要被处理成特定的格式。通常每条数据包含一个指令instruction、可选的输入input以及期望的输出output。我们需要将它们拼接成一个文本序列。例如使用常见的[INST]格式[INST] SYS 你是一个乐于助人的AI助手。 /SYS 请写一首关于春天的诗。 [/INST] 当然这是一首关于春天的诗 [春天来了万物复苏...]在预处理时我们将所有对话历史如果有和当前指令按照选定的模板如 ChatML、Alpaca 等拼接成一个长字符串并赋值给”text”字段。最终的数据文件是每行一个 JSON 对象的.jsonl文件。{“text”: “[INST] SYS\\n你是一个助手。\\n/SYS\\n\\n请写一首诗。 [/INST] 当然这是一首诗\\n[诗歌内容]”}6.2 指令微调训练指令微调的脚本scripts/train-sft.sh与预训练脚本类似但有以下关键区别数据集指向你准备好的指令数据集。学习率通常比继续预训练的学习率更低例如5e-5因为指令微调是在已有知识上进行“对齐”不需要大力度的参数更新。训练轮数指令数据量通常远小于预训练数据可能需要 3-5 个 epoch 以防止过拟合。损失函数通常只计算模型输出部分的损失忽略指令部分的损失。这可以通过在数据处理时设置”labels”来实现将指令部分的 token 对应的 label 设为-100被忽略。启动指令微调torchrun --nnodes1 --nproc_per_node8 train_sft.py \ --model_name_or_path ./chinese-mixtral-8x7b-merged \ --tokenizer_name ./chinese-mixtral-tokenizer \ --use_peft \ --lora_r 64 \ --lora_alpha 128 \ --lora_dropout 0.1 \ # SFT时dropout可以稍高以防过拟合 --quantization 4bit \ --bf16 \ --dataset_path ./data/instruction_data.jsonl \ --output_dir ./sft_output \ --num_train_epochs 3 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 16 \ --learning_rate 5e-5 \ --lr_scheduler_type cosine \ --warmup_ratio 0.03 \ --logging_steps 10 \ --save_strategy “epoch”6.3 微调后效果对比经过指令微调后模型的变化是显而易见的格式遵循能够严格按照指令要求的格式如 JSON、列表、邮件进行输出。拒绝能力对于不恰当或有害的请求能够给出安全的拒绝回应。对话流畅性多轮对话的上下文理解能力和回复连贯性大幅提升。任务泛化对未见过的指令类型也能做出合理的尝试。我们基于 Chinese-Mixtral-8x7B 微调出了“活字 3.0”模型它在保持强大中英文能力的同时具备了优秀的指令遵循和对话能力证明了这条技术路径的可行性。7. 常见问题、避坑指南与扩展思考在实际操作中我们遇到了不少坑也总结出一些经验。7.1 训练不稳定与 Loss 震荡问题在训练初期Loss 可能出现剧烈震荡或突然上升NaN。排查与解决梯度爆炸这是最常见的原因。首先检查gradient_accumulation_steps设置是否合理确保有效批大小不是特别大或特别小通常 128-512 是一个安全范围。可以尝试启用梯度裁剪(–max_grad_norm 0.3)。学习率过高对于 QLoRA2e-4是一个常用起点但如果你的数据分布非常特殊或批大小很小可能需要调低。可以尝试1e-4或5e-5。精度问题确保使用了bf16或fp16混合精度训练。对于 A100/A800 等支持 bf16 的卡优先使用bf16它的数值范围更广更不容易溢出。如果只有 fp16可以尝试减小批大小或使用动态损失缩放。数据问题检查预处理后的数据中是否有大量空行或异常字符。确保max_length设置正确过长的序列被正确截断或丢弃。7.2 模型合并后性能下降问题合并 LoRA 权重后模型生成质量似乎不如训练时加载适配器进行推理。排查与解决合并脚本问题确保使用model.merge_and_unload()方法进行合并这是 PEFT 库推荐的方式。手动合并权重容易出错。精度丢失合并操作可能在默认的fp32下进行而你的模型原本是bf16。确保在合并和保存时指定正确的 dtypemerged_model.to(dtypetorch.bfloat16)和merged_model.save_pretrained(…, torch_dtypetorch.bfloat16)。Adapter 权重未完全加载检查训练保存的 checkpoint 是否包含完整的适配器权重。有时只有最后一次保存的 checkpoint 是完整的。7.3 推理速度慢或显存不足问题即使使用了 4-bit 量化推理速度仍然不理想或者处理长文本时爆显存。优化方案启用 Flash Attention 2这是提升推理速度最有效的方法之一如前文代码所示。使用 vLLM 部署对于生产环境 API 服务强烈推荐使用vLLM。它实现了 PagedAttention 等优化吞吐量远超原生 Transformers。pip install vllm from vllm import LLM, SamplingParams llm LLM(model“./chinese-mixtral-8x7b-merged”, tensor_parallel_size8) # 8卡张量并行 outputs llm.generate(prompts, sampling_params)调整生成参数减少max_new_tokens使用do_sampleFalse贪婪解码或降低top_p/top_k值都能加快生成速度。更激进的量化可以尝试使用bitsandbytes的load_in_4bit配合bnb_4bit_quant_type“nf4”和bnb_4bit_compute_dtypetorch.bfloat16。对于特别大的上下文可以考虑load_in_8bit虽然模型体积大但推理时计算更快。7.4 中文生成出现乱码或重复问题模型生成的中文包含乱码、无意义重复或中途停止。排查与解决Tokenizer 不匹配这是最可能的原因务必确保推理时使用的 tokenizer 与训练时完全一致。如果你是从 Hugging Face 加载我们的模型它会自动匹配。如果是本地合并的模型请务必使用我们项目提供的、与模型配套的 tokenizer 文件而不是原版 Mixtral 的 tokenizer。生成参数不当过高的temperature(如 1.0) 或过低的top_p(如 0.5) 可能导致输出随机、混乱。对于中文任务temperature0.7-0.9,top_p0.9-0.95是常见的稳定区间。重复惩罚repetition_penalty可以设置在 1.1 到 1.2 之间来抑制重复。模型未收敛如果是在自己微调的模型上出现可能是训练不充分或过拟合。检查训练 loss 曲线确保其在验证集上也是下降并趋于平稳的。7.5 未来扩展方向Chinese-Mixtral-8x7B 是一个强大的基础你可以在此基础上做很多事领域微调使用法律、医疗、金融、代码等垂直领域数据进行继续预训练或指令微调打造专业模型。更长上下文原模型支持 32K 上下文但训练时我们可能只用了 2K 或 4K。可以尝试使用 NTK-aware 插值、YaRN 等位置编码外推方法或直接使用更长序列数据进行微调来激活其长上下文能力。多模态扩展结合视觉编码器如 CLIP尝试构建中文多模态大模型。推理优化深入集成vLLM,TGI等推理框架探索 MoE 模型特有的专家并行、负载均衡等优化策略追求极致的推理性价比。这个项目最大的价值在于它提供了一个经过验证的、将顶尖英文 MoE 模型高效适配到中文场景的完整 pipeline。从词表设计、数据准备、QLoRA 训练到评测部署其中的每一个环节的思考和踩过的坑对于想要定制自己领域大模型的团队来说都是宝贵的经验。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价