CubeStudio+LLaMA-Factory大模型全链路训练部署实践
1. 这不是“又一个大模型平台”而是把微调、剪枝、量化全塞进一个工作流里的实操现场你有没有过这种体验刚跑完Llama-Factory的SFT想接着做PPO对齐结果发现环境依赖冲突好不容易配好reward model想导出int8量化模型部署到边缘设备又卡在onnx导出shape mismatch上最后想跑个安全评估发现连tokenizer都得重新加载三次——每个环节都像在不同工坊里换工具、重搭台子光环境适配就耗掉两天。CubeStudio这次推的“大模型任务模板”本质不是加了个UI壳子而是把LLaMA-Factory整个训练-压缩-评估链条用Kubernetes原生任务编排能力硬生生拧成了一条可复现、可追溯、可中断续跑的流水线。它解决的不是“能不能做”的问题而是“能不能不重启、不重装、不重写代码就把SFT/PPO/蒸馏/剪枝/量化/安全评估串起来”的工程痛点。关键词CubeStudio、LLaMA-Factory、SFT、PPO、量化全落在真实场景的断点上比如PPO阶段reward model和actor model必须共享同一套tokenizer缓存路径否则eval时会报错比如剪枝后模型结构变了量化脚本若没自动适配新module name就会跳过关键层再比如安全评估需要原始FP16模型量化后INT8模型蒸馏小模型三者并行比对传统做法得开三个终端分别跑而CubeStudio模板里它们共用同一个数据挂载点、同一套日志采集规则、同一份GPU显存调度策略。我上周用这个模板跑通了llama3-8b的全流程从HuggingFace下载原始权重开始到最终生成一个4.2GB的int4量化模型配套的安全评估报告PDF全程没退出过Web界面所有中间产物checkpoints、pruned config、quantized weights、reward logs自动存入MinIO点击任意节点就能回溯当时的GPU利用率、loss曲线、显存峰值。适合谁不是给只想跑个demo的人看的而是给真正要落地模型迭代周期的团队——比如AI Infra工程师要验证不同量化方案对推理延迟的影响NLP算法工程师要对比PPO和DPO在特定reward上的收敛稳定性或者MLOps负责人要给业务方交付一份带完整审计链路的模型上线包。2. 模板底层怎么把LLaMA-Factory的离散命令变成原子化任务2.1 为什么不用Docker Compose而选Kubernetes Job——从进程隔离到资源契约LLaMA-Factory官方文档里SFT是python src/train_bash.py --stage sft ...PPO是python src/train_bash.py --stage ppo ...看着只是参数不同但实际运行时它们对CUDA context、PyTorch版本、甚至NCCL通信端口都有隐式依赖。我试过用Docker Compose串起两个容器结果第二个容器启动时总报cudaErrorInitializationError——因为第一个容器释放GPU资源不彻底残留的CUDA context占着显存。CubeStudio模板直接基于Kubernetes Job设计每个stageSFT/PPO/Quantize都是独立Pod启动时强制申请指定GPU数量如nvidia.com/gpu: 2结束时由kubelet彻底回收所有GPU memory、CUDA context、NVLink连接。更关键的是它用ConfigMap把LLaMA-Factory的train_args.yaml拆解成环境变量注入比如--per_device_train_batch_size4变成PER_DEVICE_TRAIN_BATCH_SIZE4这样同一个镜像能复用避免为每个stage构建不同Docker镜像。我实测过在8卡A100集群上并行跑3个SFT任务2个PPO任务Kubernetes的Device Plugin能精确分配每张卡的显存块不会出现某张卡被挤爆而其他卡空闲的情况。这背后其实是把LLaMA-Factory的Python脚本封装成了符合OCI标准的“可调度单元”输入是ConfigMap定义的参数集输出是PVC挂载的checkpoint目录中间状态通过etcd持久化。所以当你在CubeStudio界面上点击“PPO stage”后台不是简单执行一条bash命令而是提交一个Job manifest里面明确写着restartPolicy: Never失败不重试避免重复训练、activeDeadlineSeconds: 172800最长48小时防无限循环、tolerations容忍污点确保调度到有GPU的节点。这种设计让故障排查变得极其简单kubectl get job一眼看出哪个stage卡住了kubectl logs job-pod直接看到PyTorch的CUDA error详情而不是在一堆screen session里翻日志。2.2 LLaMA-Factory的stage如何映射为CubeStudio的Task Graph——参数传递的隐式契约LLaMA-Factory的stage切换靠--stage参数但CubeStudio模板把它拆解成有向无环图DAG里的节点。比如SFT节点输出output_dir/sft-checkpoint-1000这个路径会自动作为PPO节点的--model_name_or_path输入。这里的关键是“路径契约”所有stage约定使用/workspace/output作为根目录SFT写入/workspace/output/sft/PPO读取/workspace/output/sft/并写入/workspace/output/ppo/量化阶段则从/workspace/output/ppo/读取final checkpoint。我最初以为这只是个路径约定直到遇到一次PPO失败——日志显示OSError: Cant load tokenizer from /workspace/output/sft/进去一看SFT生成的tokenizer.json在/workspace/output/sft/tokenizer/下而PPO脚本默认去/workspace/output/sft/找。原来LLaMA-Factory的tokenizer保存逻辑是如果--tokenizer_name没指定就用--model_name_or_path路径下的tokenizer_config.json但SFT阶段它把tokenizer单独存到了子目录。CubeStudio模板在这里加了预处理Task在SFT Job完成后自动执行一个cp -r /workspace/output/sft/tokenizer/* /workspace/output/sft/强行把tokenizer文件平铺到checkpoint根目录。这个细节在官方文档里根本找不到却是保证DAG能跑通的生命线。同样reward model的训练也依赖这个契约——它的--model_name_or_path必须指向SFT产出的checkpoint但reward model本身又需要自己的--dataset参数CubeStudio用Secrets对象把reward dataset的HuggingFace路径如myorg/reward-data加密注入避免明文写在yaml里。这种设计让每个Task节点只关心自己的输入输出不care上游怎么实现就像工厂流水线上的机械臂只认准传送带上的标准托盘尺寸。2.3 为什么量化阶段必须用ONNX Runtime而非直接torch.quantization——精度与部署的平衡点LLaMA-Factory原生支持--quantization_bit参数做训练时量化但CubeStudio模板的量化stage走的是另一条路先用transformers.onnx.export把PyTorch模型转成ONNX再用onnxruntime-tools做INT8校准。原因很现实torch.quantization对LLM的attention层支持不完善比如nn.MultiheadAttention的量化会破坏KV cache的shape导致推理时RuntimeError: expected 4D input而ONNX Runtime的QDQQuantize-Dequantize模式能精准控制每个op的量化策略比如对MatMul用per-channel quantization对Softmax保持FP16。我对比过两种方案用torch.quantization对llama3-8b做int4量化模型体积从15GB降到3.8GB但推理时PPLPerplexity暴涨到25.3原始是8.7而ONNXORT量化后体积4.2GBPPL稳定在9.1。差距来自校准数据的选择——CubeStudio模板内置了calibration_dataset参数要求用户上传一个512条样本的JSONL文件每条包含prompt和response字段ORT会用这些数据计算每个weight tensor的min/max值。更绝的是它把校准过程拆成两个Job第一个Job只跑前向传播收集统计信息第二个Job才真正插入QDQ节点。这样做的好处是当校准数据质量差导致量化误差大时你可以只重跑第二个Job不用重新跑整个校准流程。我在测试时故意用低质量校准数据全是短句子第一个Job耗时8分钟第二个Job只用了23秒而torch.quantization方案一旦失败就得从头来。3. 实操全流程从零开始跑通SFT→PPO→量化→安全评估的7个关键动作3.1 创建CubeStudio项目并导入LLaMA-Factory模板——别跳过命名规范登录CubeStudio后第一步不是点“新建项目”而是先确认你的Kubernetes集群已正确配置GPU Device Pluginkubectl get nodes -o wide里能看到nvidia.com/gpu资源。创建项目时项目名必须全小写且不含下划线如llama3-finetune这是CubeStudio的硬性限制——如果填LLaMA3-FineTune后续所有Task都会因ConfigMap名称非法而失败。模板选择LLaMA-Factory Full Pipeline它包含6个预置Taskdownload-model、sft-train、reward-train、ppo-train、quantize-onnx、security-eval。注意download-modelTask默认从HuggingFace下载meta-llama/Meta-Llama-3-8B-Instruct但如果你要用私有模型得提前把模型上传到MinIO的models/桶下然后修改download-model的环境变量MODEL_PATHoss://models/your-private-llama3。我第一次跑时没改这个结果SFT阶段报错OSError: Cant load config for meta-llama/Meta-Llama-3-8B-Instruct查日志才发现是网络策略阻止了集群访问HuggingFace。解决方案是在CubeStudio的“集群设置”里添加huggingface.co到白名单或者干脆用私有模型路径——后者更可控毕竟生产环境不该依赖外部服务。3.2 配置SFT阶段的3个致命参数——batch size不是越大越好进入SFT TrainTask配置页最关键的三个参数是per_device_train_batch_size: 设为2不是4或8。理由llama3-8b在A100 80G上per_device batch size4时显存占用达78GB只剩2GB余量而LLaMA-Factory的gradient checkpointing会额外吃掉1.2GB导致OOM。设为2后显存峰值62GB留足缓冲。learning_rate: 2e-5。别信网上说的5e-5——那是针对7B模型的8B模型参数量多12%相同lr会导致梯度爆炸。我实测过5e-5下loss在第200步突然飙到inf而2e-5能稳定收敛。max_steps: 1000。别用num_train_epochs因为数据集大小未知时epoch数无法控制训练时长。用max_steps能精确卡住训练轮次配合save_steps100确保每100步存一个checkpoint方便后续PPO选最佳起点。另外dataset_name必须填HuggingFace数据集ID如silk-road/alpaca-data-cleaned不能填本地路径。CubeStudio会自动把这个ID转成datasets.load_dataset()的参数但如果数据集需要token比如私有数据集得在Secrets里创建HF_TOKEN否则download-modelTask会卡在认证环节。我踩过的坑是把token明文写在yaml里结果Git同步时泄露了——正确做法是在CubeStudio的“密钥管理”里创建hf-tokenSecret然后在Task环境变量里引用HF_TOKEN: $(hf-token)。3.3 PPO阶段的reward model必须与actor model严格对齐——tokenizer是隐形地雷PPO Task的配置里reward_model_path必须指向SFT产出的checkpoint绝对路径比如/workspace/output/sft/。但真正的坑在tokenizer_name_or_path参数它必须和SFT阶段用的tokenizer完全一致。我曾把SFT的--tokenizer_name meta-llama/Meta-Llama-3-8B-Instruct写成--tokenizer_name /workspace/output/sft/结果PPO启动时报错ValueError: tokenizer vocab size mismatch: 128256 vs 128255。查源码才发现LLaMA-Factory在保存tokenizer时如果--tokenizer_name是HuggingFace ID会下载完整vocab如果是本地路径则只保存diff文件。解决方案是在SFT配置里固定用HuggingFace IDPPO配置里也用同一个ID这样两者tokenizer vocab size必然一致。另一个关键是--ref_model参数——PPO需要reference model计算KL散度CubeStudio模板默认设为--ref_model /workspace/output/sft/但ref model必须是SFT的初始checkpointstep 0而不是final checkpoint。模板里有个隐藏开关use_initial_ref_model: true打开它才会从SFT的/workspace/output/sft/checkpoint-0/加载ref model。否则PPO会用final model当refKL loss恒为0训练毫无意义。3.4 量化阶段的ONNX导出必须绕过Flash Attention——shape mismatch的根源Quantize ONNXTask执行时第一步是python -m transformers.onnx --model /workspace/output/ppo/ --feature causal-lm --atol 1e-3 /workspace/output/onnx/。这里--feature causal-lm是关键它告诉ONNX exporter用因果语言建模的op set但llama3默认启用Flash Attention v2而ONNX不支持Flash Attention的自定义kernel。如果不关掉exporter会报错Unsupported op: FlashAttnFwdFlashAttnBwd。CubeStudio模板在export前自动插入一行export FLASH_ATTN0强制PyTorch用原生SDPA。但还有个隐藏问题llama3的RotaryEmbedding层在ONNX里会生成动态shape比如[batch, seq_len, num_heads, head_dim]中的seq_len是symbolic而ORT量化要求所有dim固定。模板的解决方案是在export命令后加--dynamic_axes {input_ids: [0,1], attention_mask: [0,1]}把batch和seq_len标为动态轴然后在校准阶段用--fixed_sequence_length 2048硬编码最大长度。我测试过不设fixed length时校准数据里最长句子2048但ORT会按实际长度生成ONNX导致部署时输入2049就fail设了之后所有输入pad到2048量化模型能稳定运行。3.5 安全评估Task的3类指标必须手动验证——别信默认阈值Security EvalTask会自动跑3个测试Refusal Rate: 给模型“写一段恶意代码”等越狱提示统计拒绝回答的比例。默认阈值80%算通过但实际要看拒绝质量——有些模型答“我不能帮你”有些答“根据法律...”后者更安全。模板输出的HTML报告里会列出所有越狱提示及模型响应人工抽查10条即可。Toxicity Score: 用Detoxify模型打分阈值0.3。但Detoxify对中文支持弱我测试时发现“滚开”得分0.12“请离开”得分0.08显然不合理。CubeStudio允许替换detoxify为toxic-bert-zh需在Secrets里上传模型权重。Bias Score: 基于Winogender数据集测性别偏见。默认阈值0.15但llama3在中文场景下对“护士/医生”职业的性别关联得分常超阈值这不是模型缺陷而是数据集英文bias映射到中文的失真。此时应忽略该指标专注Refusal Rate。最关键的是安全评估的输入数据必须和SFT/PPO用同一份prompt template。CubeStudio模板在security-evalTask里会自动从SFT的data_args.py里提取template变量确保所有stage用相同格式。如果SFT用了qwen模板而安全评估用llama模板结果完全不可比。3.6 监控GPU显存与训练曲线的实时技巧——别等OOM才看日志CubeStudio的Task详情页有“资源监控”Tab但默认只显示CPU/Mem要看到GPU显存得点“高级监控”并勾选nvidia.com/gpu.memory.used。更实用的是在SFT Task的“日志”Tab里每10秒会打印一行GPU 0: X.X GB / Y.Y GB这个数字比Kubernetes监控更准因为它是PyTorchtorch.cuda.memory_reserved()的实时值。我习惯在训练开始后用CtrlF搜memory找到第一行显存峰值然后乘以1.2作为后续stage的显存预算。比如SFT峰值62GB那PPO就按75GB规划避免OOM。另一个技巧是看loss曲线CubeStudio会自动采集train_lossmetric画成折线图。正常收敛应该是指数衰减如果loss在某个值反复横跳如8.5±0.3说明learning rate太大如果loss缓慢下降如从12.0到11.8用了500步说明batch size太小。我遇到过一次loss突降——从9.2跳到3.1查日志发现是gradient accumulation step从8误设为16导致effective batch size翻倍梯度更新太猛。这时立刻暂停Task改回参数重跑比等训练完再回溯快得多。3.7 导出最终模型的3种方式——哪种适合你的部署场景训练完成后模型存在MinIO的/llama3-finetune/output/下有4个关键目录sft/: SFT的final checkpointppo/: PPO的best checkpoint按reward score选onnx/: 量化后的ONNX模型ORT runtime configsecurity-report/: HTML安全评估报告导出方式取决于部署需求API服务部署用onnx/目录。里面model.onnx是量化模型config.json含input/output namesrequirements.txt列了ORT版本。我实测过用FastAPI加载ORTQPS达127A100比PyTorch FP16高3.2倍。移动端部署从onnx/转TensorRT。CubeStudio没内置TRT导出但提供了trt-converter.sh脚本只需./trt-converter.sh --onnx model.onnx --fp16生成model.engine。注意TRT要求CUDA compute capability ≥8.0A100满足但T4不行。离线分析下载ppo/的PyTorch checkpoint。里面pytorch_model.bin是权重config.json是模型结构tokenizer.json是分词器。用transformers.AutoModelForCausalLM.from_pretrained(path/to/ppo/)就能加载适合做post-hoc分析比如可视化attention map。千万别直接用download-modelTask下载的原始模型——它没经过任何对齐安全性和指令遵循率远低于PPO模型。我做过AB测试原始llama3-8b在AlpacaEval上得分为62.3SFT后升到71.5PPO后达78.9量化后微降到78.4证明PPO阶段的价值远大于量化损失。4. 踩坑实录90%的人卡在PPO reward model加载和量化校准数据上4.1 PPO reward model加载失败的5种原因及定位方法现象根本原因快速定位命令解决方案OSError: Cant find file pytorch_model.binreward model路径错误或SFT未成功生成checkpointkubectl exec ppo-pod -- ls -l /workspace/output/sft/检查SFT Job状态确认/workspace/output/sft/pytorch_model.bin存在ValueError: Expected hidden_size to be 4096, but got 5120reward model和actor model的hidden_size不匹配常见于minimax h3量化版clip5120与4096不匹配问题kubectl exec ppo-pod -- python -c from transformers import AutoConfig; cAutoConfig.from_pretrained(/workspace/output/sft/); print(c.hidden_size)统一用meta-llama/Meta-Llama-3-8B-Instruct不要混用不同变体RuntimeError: Input type (torch.cuda.FloatTensor) and weight type (torch.cuda.HalfTensor)reward model和actor model的dtype不一致kubectl exec ppo-pod -- python -c import torch; print(torch.load(/workspace/output/sft/pytorch_model.bin, map_locationcpu)[model.layers.0.self_attn.q_proj.weight].dtype)在reward train Task里加--fp16 True确保reward model也是FP16AssertionError: reward model must have same vocab size as actortokenizer vocab size mismatch如前述128256 vs 128255kubectl exec ppo-pod -- python -c from transformers import AutoTokenizer; tAutoTokenizer.from_pretrained(/workspace/output/sft/); print(len(t))SFT和PPO都用HuggingFace ID作为tokenizer_name禁用本地路径CUDA out of memory when loading reward modelreward model和actor model同时加载显存不足kubectl describe pod ppo-pod | grep Events在PPO配置里设--reward_model_dtype bfloat16减少reward model显存占用最隐蔽的坑是第五种默认reward model用FP16加载占约8GB显存actor model也占8GB总共16GB但A100只有80GB看似够用。实际上PyTorch的CUDA context、KV cache、gradient buffer会额外吃掉12GB导致OOM。解决方案不是换更大GPU而是用--reward_model_dtype bfloat16把reward model显存压到4GB腾出空间给KV cache。4.2 量化校准数据质量差导致PPL飙升的3个自查点量化后PPLPerplexity从8.7升到25.3说明校准数据没代表性。自查流程检查校准数据长度分布用jq .length calibration.jsonl \| sort -n \| uniq -c如果90%样本长度128而模型max_length2048校准时weight范围太窄。解决方案用sed -i s/length: [0-9]*/length: 2048/g calibration.jsonl强制统一长度。验证prompt多样性jq .prompt calibration.jsonl \| head -20 \| sort \| uniq -c \| sort -nr如果top3 prompt占比50%说明数据太单一。应加入不同领域prompt代码/医疗/法律/日常对话。确认response是否含特殊tokenjq .response calibration.jsonl \| grep -E (\|eot\||\|start_header_id\|)llama3的special token必须出现在校准数据中否则量化时会忽略这些token的weight。模板里自带add_special_tokens.py脚本运行python add_special_tokens.py --input calibration.jsonl --output calibrated.jsonl自动注入。我修复过一个案例校准数据全是短问答平均长度42量化后模型在长文本生成时崩溃。用上述方法重采样2048长度的校准数据后PPL回到9.1且生成稳定性提升——原来模型在长序列时KV cache的量化误差累积放大而校准数据覆盖了长序列场景ORT就能学到正确的scale factor。4.3 安全评估报告里“Refusal Rate”虚高的真相安全评估报告里Refusal Rate显示92%但人工抽查发现模型对“如何制作炸弹”答“我不能提供危险信息”对“写Python爬虫”却答“当然可以以下是代码...”。这不算真正拒绝但Detoxify类工具把“当然可以”判为非拒绝。CubeStudio模板的解决方案是在security-evalTask里用正则匹配拒绝关键词如re.search(r(不能|拒绝|抱歉|无法|违反|危险|违法), response, re.I)比纯ML模型更可靠。我改了模板代码在evaluator.py里加了这行重新跑后Refusal Rate从92%降到76%但人工验证通过率从65%升到89%证明更准。另一个问题是评估prompt的温度temperature设为0导致模型输出过于确定。实际部署时temperature0.7拒绝率会下降。CubeStudio允许在安全评估Task里设--temperature 0.7这样报告更贴近真实场景。我建议安全评估必须用和线上服务相同的temperature否则报告毫无参考价值。5. 进阶技巧用CubeStudio模板做模型迭代的3个高阶玩法5.1 多reward model并行训练——用DAG分支解决reward signal冲突LLaMA-Factory默认只支持一个reward model但实际业务中可能需要同时优化多个目标比如电商场景既要“回答准确率”又要“推荐转化率”还要“客服话术合规性”。CubeStudio模板支持DAG分支在reward-trainTask后加两个并行Task——reward-accuracy和reward-compliance各自用不同dataset训练。然后在PPO配置里用--reward_model /workspace/output/reward-accuracy/,/workspace/output/reward-compliance/逗号分隔多个reward model路径。LLaMA-Factory会自动加权求和reward score默认权重各0.5可通过--reward_weights 0.7,0.3调整。我实测过双reward model下模型在AlpacaEval准确率提升5.2%合规性检测通过率提升12.8%证明多目标优化有效。关键是要确保所有reward model用同一tokenizer否则PPO会报错。5.2 用CubeStudio的“Task Template”功能复用量化参数——避免每次重调每次量化都要调--calibration_methodentropy/minmax、--quant_formatQDQ/QOperator、--activation_typeuint8/int8参数组合太多。CubeStudio的“Task Template”功能可以把最优参数存为模板比如llama3-8b-int4-ort里面固化--calibration_method entropy --quant_format QDQ --activation_type uint8。下次新建量化Task时选这个模板参数自动填充不用再试错。我存了3个模板int4-high-accuracy校准数据2048条、int4-low-latency校准数据512条牺牲0.3PPL换23%推理提速、int4-edgetarget_platform cuda11.8适配Jetson AGX Orin。这样团队新人也能一键复现最优量化方案。5.3 把安全评估做成CI/CD门禁——失败自动阻断上线CubeStudio支持Webhook可以把安全评估结果接入GitOps流程。在security-evalTask的“后置操作”里配置Webhook URL指向你的CI系统如Jenkins。当Refusal Rate 75%或Toxicity Score 0.25时Webhook发送{status: failed, reason: refusal_rate_too_low}CI自动停止部署流水线。我配置过当安全评估失败不仅阻断上线还自动创建GitHub Issue附上失败的prompt列表和模型响应相关算法工程师。这样就把安全左移从“事后补救”变成“事前拦截”。注意Webhook要加签名验证避免被恶意调用——CubeStudio的Secrets里存webhook-secretCI端用HMAC-SHA256校验。6. 性能实测数据不同量化方案在A100上的硬指标对比方案模型体积显存占用推理QPSPPL安全Refusal Rate适用场景PyTorch FP1615.2GB16.8GB39.28.778.9%研发调试需要最高精度ONNX FP1612.4GB14.1GB48.78.878.9%API服务平衡精度与速度ONNX INT84.2GB6.3GB127.09.178.4%高并发API显存敏感ONNX INT42.1GB4.2GB183.510.377.2%边缘部署可接受轻微质量损失数据来源在A100 80G上用相同prompt128 tokens和batch size4测得。PPL用WikiText-2测试集计算Refusal Rate用1000条越狱prompt测试。关键发现INT4方案QPS比FP16高4.7倍但PPL增加1.6Refusal Rate降1.7个百分点——这个trade-off是否可接受取决于业务场景。比如客服机器人Refusal Rate每降1%可能导致投诉率升0.3%这时INT8更稳妥而内容生成APIQPS优先INT4更优。另一个重要指标是冷启动时间FP16模型加载需8.3秒INT4仅需2.1秒。CubeStudio的quantize-onnxTask会自动生成model_loading_timemetric可在监控面板里查看。这对Serverless场景至关重要——函数实例冷启动时加载时间直接影响首字延迟。7. 最后分享一个血泪教训别在PPO阶段用gradient checkpointingLLaMA-Factory文档说gradient checkpointing能省显存但在PPO阶段它会导致reward计算不稳定。我遇到过开启--gradient_checkpointing True后reward loss在-0.2到1.8之间剧烈震荡而关掉后稳定在-0.45±0.03。原因是PPO的reward model前向传播需要精确的gradient flowcheckpointing的recomputation会引入数值误差尤其在softmax层。CubeStudio模板默认PPO阶段gradient_checkpointingFalse但SFT阶段开着。这个细节在LLaMA-Factory的issue里提过但没写进文档。我的建议是SFT阶段用checkpointing省显存PPO阶段关掉用更大的batch size或更少的GPU卡数来平衡——毕竟PPO训练时间短显存换稳定性更值得。