# LLaMA-Factory Function Call训练完整指南 ## 1. 同步上游和本地更新代码 ### 1.1 同步上游代码 ```bash # 进入LLaMA-Factory目录 cd LLaMA-Factory # 添加上游仓库(如果还没有添加) git remote add upstream https://github.com/hiyouga/LLaMA-Factory.git # 获取上游最新代码 git fetch upstream # 合并上游代码到本地分支 git merge upstream/main ``` ### 1.2 处理冲突 如果出现冲突,需要手动解决冲突后提交: ```bash # 解决冲突后 git add . git commit -m "Merge upstream changes" ``` ## 2. Dataset使用说明 ### 2.1 数据组织结构 自定义数据全部放在 `LLaMA-Factory/data/dataset` 目录下,按照训练日期分割: ``` LLaMA-Factory/data/dataset/ ├── 8_29/ # 8月29日的数据 ├── 8_30/ # 8月30日的数据 ├── 9_10/ # 9月10日的数据 │ └── function_call_data/ # function call数据目录 ├── 9_14/ # 9月14日的数据 └── preprocess_dara/ # 数据预处理脚本目录 ``` ### 2.2 Function Call数据拼接 分散的function_call数据拼接成为多轮对话格式: **数据位置**:`LLaMA-Factory/data/dataset/9_10/function_call_data/` 中的json数据 **拼接脚本**:`LLaMA-Factory/data/dataset/preprocess_dara/convert_to_function_call_format.py` **重要提示**: - 拼接的对话格式中,**偶数轮的信息才能被学习到,奇数轮的信息不能被学习到** - 在构造hardmatch数据时需要特别注意这个规则 **使用示例**: ```bash cd /home/ziqiang/LLaMA-Factory/data/dataset/preprocess_dara python convert_to_function_call_format.py \ --input_dir ../9_10/raw_data \ --output_dir ../9_10/function_call_data \ --output_file function_call_train.json ``` ### 2.3 数据合并和格式转换 #### 2.3.1 混合训练数据合并 由于MCP function call使用多轮对话训练集结构,而价格服务部分使用单轮instruction input output格式,混训时需要统一格式。 **脚本**:`LLaMA-Factory/data/dataset/preprocess_dara/merge_and_convert_data.py` **功能**: - 将不同格式的数据转换为统一的sharegpt格式 - 自动在dataset_info.json中创建相应条目 - 支持多种数据格式的混合训练 **使用示例**: ```bash python merge_and_convert_data.py \ --function_call_data ../9_10/function_call_data/function_call_train.json \ --price_service_data ../9_10/price_service_data.json \ --output_file ../9_14/mixed_training_data.json \ --dataset_name mixed_training_data ``` #### 2.3.2 简单JSON文件合并 如果只需要合并不同的JSON文件: **脚本**:`LLaMA-Factory/data/dataset/preprocess_dara/merge_json_files.py` **使用示例**: ```bash python merge_json_files.py \ --input_files file1.json file2.json file3.json \ --output_file merged_data.json ``` ### 2.4 创建dataset_info.json条目 每次新建dataset进行训练时,需要在 `LLaMA-Factory/data/dataset_info.json` 中创建相应条目。 **自动创建**:`merge_and_convert_data.py` 脚本目前支持自动添加条目 **手动创建示例**: ```json { "mixed_training_data": { "file_name": "/home/ziqiang/LLaMA-Factory/data/dataset/9_14/mixed_training_data.json", "formatting": "sharegpt", "columns": { "messages": "conversations", "system": "system", "tools": "tools" } } } ``` ## 3. 训练和评估命令 ### 3.1 训练命令 #### 3.1.1 基础训练命令 ```bash CUDA_VISIBLE_DEVICES=6 llamafactory-cli train \ --stage sft \ --do_train True \ --model_name_or_path /data/models/Qwen3-8B \ --preprocessing_num_workers 16 \ --finetuning_type lora \ --template qwen3 \ --flash_attn auto \ --dataset_dir data \ --dataset mixed_training_data_09_17 \ --cutoff_len 8192 \ --learning_rate 5e-05 \ --num_train_epochs 5 \ --max_samples 100000 \ --per_device_train_batch_size 2 \ --gradient_accumulation_steps 8 \ --lr_scheduler_type cosine \ --max_grad_norm 1.0 \ --logging_steps 5 \ --save_steps 100 \ --warmup_steps 0 \ --packing False \ --enable_thinking False \ --output_dir /home/ziqiang/LLaMA-Factory/saves/Qwen3-8B/lora/train_$(date +%Y-%m-%d-%H-%M) \ --bf16 True \ --plot_loss True \ --trust_remote_code True \ --ddp_timeout 180000000 \ --include_num_input_tokens_seen True \ --optim adamw_torch \ --lora_rank 8 \ --lora_alpha 16 \ --lora_dropout 0 \ --lora_target all ``` #### 3.1.2 带调试日志的训练命令 ```bash # 将训练日志同时输出到控制台和文件 CUDA_VISIBLE_DEVICES=6,7 llamafactory-cli train \ --stage sft \ --do_train True \ --model_name_or_path /data/models/Qwen3-8B \ --preprocessing_num_workers 16 \ --finetuning_type lora \ --template qwen3 \ --flash_attn auto \ --dataset_dir data \ --dataset mixed_training_data_09_17 \ --cutoff_len 8192 \ --learning_rate 5e-05 \ --num_train_epochs 5 \ --max_samples 100000 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 16 \ --lr_scheduler_type cosine \ --max_grad_norm 1.0 \ --logging_steps 5 \ --save_steps 100 \ --warmup_steps 0 \ --packing False \ --enable_thinking False \ --output_dir /home/ziqiang/LLaMA-Factory/saves/Qwen3-8B/lora/train_$(date +%Y-%m-%d-%H-%M) \ --bf16 True \ --plot_loss True \ --trust_remote_code True \ --ddp_timeout 180000000 \ --include_num_input_tokens_seen True \ --optim adamw_torch \ --lora_rank 8 \ --lora_alpha 16 \ --lora_dropout 0.1 \ --lora_target all \ --gradient_checkpointing True \ 2>&1 | tee token_debug_current.log CUDA_VISIBLE_DEVICES=6 llamafactory-cli train \ --stage sft \ --do_train True \ --model_name_or_path /data/models/Qwen3-8B \ --preprocessing_num_workers 16 \ --finetuning_type lora \ --template qwen3 \ --flash_attn auto \ --dataset_dir data \ --dataset mixed_training_data_09_17 \ --cutoff_len 8192 \ --learning_rate 5e-05 \ --num_train_epochs 1 \ --max_samples 100000 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 16 \ --lr_scheduler_type cosine \ --max_grad_norm 1.0 \ --logging_steps 5 \ --save_steps 100 \ --warmup_steps 0 \ --packing False \ --enable_thinking False \ --output_dir /home/ziqiang/LLaMA-Factory/saves/Qwen3-8B/lora/train_$(date +%Y-%m-%d-%H-%M) \ --bf16 True \ --plot_loss True \ --trust_remote_code True \ --ddp_timeout 180000000 \ --include_num_input_tokens_seen True \ --optim adamw_torch \ --lora_rank 4 \ --lora_alpha 8 \ --lora_dropout 0.1 \ --lora_target all \ --gradient_checkpointing True \ 2>&1 | tee token_debug_current.log ``` #### 3.1.3 调试Token限制问题的训练命令 ```bash # 使用较小的cutoff_len来观察截断情况 CUDA_VISIBLE_DEVICES=6 llamafactory-cli train \ --stage sft \ --do_train True \ --model_name_or_path /data/models/Qwen3-8B \ --preprocessing_num_workers 16 \ --finetuning_type lora \ --template qwen3 \ --flash_attn auto \ --dataset_dir data \ --dataset mixed_training_data_09_17 \ --cutoff_len 8192 \ --learning_rate 5e-05 \ --num_train_epochs 5 \ --max_samples 1000 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 1 \ --lr_scheduler_type cosine \ --max_grad_norm 1.0 \ --logging_steps 1 \ --save_steps 100 \ --warmup_steps 0 \ --packing False \ --enable_thinking False \ --output_dir /home/ziqiang/LLaMA-Factory/saves/Qwen3-8B/lora/debug_$(date +%Y-%m-%d-%H-%M) \ --bf16 True \ --plot_loss True \ --trust_remote_code True \ --ddp_timeout 180000000 \ --include_num_input_tokens_seen True \ --optim adamw_torch \ --lora_rank 8 \ --lora_alpha 16 \ --lora_dropout 0 \ --lora_target all ``` **重要参数说明**: - `--dataset`:指定在dataset_info.json中注册的条目名称 - `--output_dir`:LoRA模型保存位置 - `--enable_thinking`:是否训练模型的think模型 - `--cutoff_len`:Token长度限制,调试时建议使用2048观察截断情况 - `--max_samples`:调试时建议使用较小值(如1000)快速验证 - `2>&1 | tee`:将标准输出和错误输出同时显示在控制台和保存到文件 ### 3.2 评估命令 ```bash CUDA_VISIBLE_DEVICES=6 llamafactory-cli train \ --stage sft \ --do_predict True \ --model_name_or_path /data/models/Qwen3-8B \ --adapter_name_or_path /home/ziqiang/LLaMA-Factory/saves/Qwen3-8B/lora/train_2025-09-14-10-48-context/checkpoint-430 \ --preprocessing_num_workers 8 \ --finetuning_type lora \ --template qwen3 \ --flash_attn auto \ --dataset_dir data \ --eval_dataset test_data \ --cutoff_len 2048 \ --per_device_eval_batch_size 8 \ --predict_with_generate True \ --max_new_tokens 1024 \ --do_sample False \ --temperature 0.0 \ --top_p 1.0 \ --bf16 True \ --trust_remote_code True \ --output_dir Qwen3-8B/eval_results/9_14 ``` **重要参数说明**: - `--eval_dataset`:指定在dataset_info.json中注册的测试数据集名称 - `--output_dir`:评估结果保存位置 ### 3.3 评估结果分析 评估后会生成 `generated_predictions.jsonl` 文件,包含每条数据的label和predict。 **价格服务评估脚本**:`LLaMA-Factory/data/dataset/preprocess_dara/eval_by_field.py` **使用示例**: ```bash python eval_by_field.py \ --predictions_file Qwen3-8B/eval_results/9_14/generated_predictions.jsonl \ --output_file eval_results_by_field.json ``` ## 4. Function Call训练数据的Loss Mask机制详解 ### 4.1 数据转换过程 #### 原始对话数据: ```json { "conversations": [ {"from": "system", "value": "你是一个智能助手..."}, {"from": "human", "value": "报表编号H20250611的收入类型分布情况能分析一下吗?"}, {"from": "function_call", "value": "{\"name\": \"analyze_revenue_by_type\", \"arguments\": {...}}"}, {"from": "observation", "value": "工具返回的结果..."}, {"from": "gpt", "value": "根据分析结果..."} ] } ``` #### 转换为Token序列: ``` <|im_start|>system 你是一个智能助手... <|im_end|> <|im_start|>user 报表编号H20250611的收入类型分布情况能分析一下吗? <|im_end|> <|im_start|>assistant {"name": "analyze_revenue_by_type", "arguments": {...}} <|im_end|> <|im_start|>user 工具返回的结果... <|im_end|> <|im_start|>assistant 根据分析结果... <|im_end|> ``` ### 4.2 Loss Mask应用 #### Input IDs (完整序列): ``` [系统消息tokens] [用户查询tokens] [工具调用tokens] [工具结果tokens] [最终回答tokens] ``` #### Labels (用于计算loss): ``` [ IGNORE_INDEX ] [ IGNORE_INDEX ] [工具调用tokens] [ IGNORE_INDEX ] [最终回答tokens] (不训练) (不训练) (训练) (不训练) (训练) ``` ### 4.3 训练目标 模型学习的目标是: 1. **根据用户查询生成正确的工具调用** - 从用户输入预测function_call部分 2. **根据工具结果生成合适的回答** - 从工具返回结果预测最终答案 模型**不会**学习: 1. 生成工具返回结果(observation部分被mask) 2. 重复用户输入或系统提示 ### 4.4 关键代码实现 #### Template中的格式定义: ```python # Qwen3模板 format_observation=StringFormatter( slots=["<|im_start|>user\n\n{{content}}\n<|im_end|>\n<|im_start|>assistant\n"] ) ``` #### Loss计算时的mask: ```python # 在supervised.py中 if self.data_args.train_on_prompt: source_label = source_ids else: source_label = [IGNORE_INDEX] * source_len # 输入部分被mask # observation部分会被自动识别并mask为IGNORE_INDEX ``` ## 5. Token调试日志系统 ### 5.1 概述 为了帮助开发者更好地理解和调试ShareGPT格式训练中的token限制问题,我们在关键代码位置添加了详细的调试日志系统。 ### 5.2 调试日志类型 #### 5.2.1 [TEMPLATE_DEBUG] - 模板编码阶段 **位置**: `src/llamafactory/data/template.py` 的 `encode_multiturn` 函数 **记录内容**: - 输入messages数量 - 编码后messages数量 - 生成的pairs数量和每个pair的token长度 **示例输出**: ``` [TEMPLATE_DEBUG] encode_multiturn开始 [TEMPLATE_DEBUG] 输入messages数量: 6 [TEMPLATE_DEBUG] 编码后messages数量: 6 [TEMPLATE_DEBUG] 生成的pairs数量: 3 [TEMPLATE_DEBUG] Pair 1: source=120 tokens, target=80 tokens [TEMPLATE_DEBUG] Pair 2: source=1300 tokens, target=50 tokens [TEMPLATE_DEBUG] Pair 3: source=400 tokens, target=200 tokens ``` #### 5.2.2 [TOKEN_DEBUG] - 数据编码阶段 **位置**: `src/llamafactory/data/processor/supervised.py` 的 `_encode_data_example` 函数 **记录内容**: - 原始conversations长度 - 编码后的pairs数量 - 每个pair的原始长度和截断后长度 - 剩余预算和累计长度 - 最终结果和使用率 **示例输出**: ``` [TOKEN_DEBUG] 开始处理数据样本 [TOKEN_DEBUG] 原始conversations长度: 6 条消息 [TOKEN_DEBUG] 编码后的pairs数量: 3 [TOKEN_DEBUG] 初始total_length: 0 [TOKEN_DEBUG] cutoff_len: 2048 [TOKEN_DEBUG] === Pair 1 === [TOKEN_DEBUG] 原始长度: source=120, target=80 [TOKEN_DEBUG] 剩余预算: 2048 [TOKEN_DEBUG] 截断后长度: source=120->120, target=80->80 [TOKEN_DEBUG] 当前累计长度: 200/2048 [TOKEN_DEBUG] === Pair 2 === [TOKEN_DEBUG] 原始长度: source=1300, target=50 [TOKEN_DEBUG] 剩余预算: 1848 [TOKEN_DEBUG] 截断后长度: source=1300->1300, target=50->50 [TOKEN_DEBUG] 当前累计长度: 1550/2048 [TOKEN_DEBUG] === Pair 3 === [TOKEN_DEBUG] 原始长度: source=400, target=200 [TOKEN_DEBUG] 剩余预算: 498 [TOKEN_DEBUG] 截断后长度: source=400->298, target=200->200 [TOKEN_DEBUG] ⚠️ source被截断: 102 tokens [TOKEN_DEBUG] 当前累计长度: 2048/2048 [TOKEN_DEBUG] === 最终结果 === [TOKEN_DEBUG] 最终input_ids长度: 2048 [TOKEN_DEBUG] 最终labels长度: 2048 [TOKEN_DEBUG] 最终total_length: 2048 [TOKEN_DEBUG] 使用率: 2048/2048 (100.0%) [TOKEN_DEBUG] 处理完成 ``` #### 5.2.3 [INFER_SEQLEN] - 截断策略阶段 **位置**: `src/llamafactory/data/processor/processor_utils.py` 的 `infer_seqlen` 函数 **记录内容**: - 截断策略的选择过程 - 输入输出参数 - 具体的截断逻辑 **示例输出**: ``` [INFER_SEQLEN] 输入: source_len=400, target_len=200, cutoff_len=498 [INFER_SEQLEN] 条件1: target_len*2 < cutoff_len (200*2=400 < 498) [INFER_SEQLEN] 策略1: target完全保留,截断source [INFER_SEQLEN] 输出: source_len=400->298, target_len=200->200 ``` ### 5.3 日志查看方法 #### 5.3.1 运行训练并记录日志 ```bash CUDA_VISIBLE_DEVICES=0,1 llamafactory-cli train \ --stage sft \ --do_train True \ --model_name_or_path /data/models/Qwen3-8B \ --dataset your_dataset \ --cutoff_len 2048 \ --output_dir ./debug_output \ 2>&1 | tee debug_train.log ``` #### 5.3.2 过滤特定日志 ```bash # 查看所有token调试日志 grep 'TOKEN_DEBUG' debug_train.log # 查看截断策略日志 grep 'INFER_SEQLEN' debug_train.log # 查看模板编码日志 grep 'TEMPLATE_DEBUG' debug_train.log # 查看截断事件 grep '⚠️' debug_train.log # 查看使用率统计 grep '使用率:' debug_train.log ``` #### 5.3.3 使用分析脚本 ```bash # 分析日志文件 python analyze_token_logs.py debug_train.log ``` ### 5.4 关键指标解读 #### 5.4.1 使用率 (Usage Rate) - **100%**: 完全使用cutoff_len,可能有截断 - **<100%**: 未完全使用,数据较短 - **>100%**: 不可能出现,检查日志 #### 5.4.2 截断事件 - **source被截断**: 通常是observation内容过长 - **target被截断**: 通常是assistant回复被截断 - **预算耗尽**: 后续pairs被完全丢弃 #### 5.4.3 截断策略 - **策略1**: target完全保留,截断source (target_len * 2 < cutoff_len) - **策略2**: source完全保留,截断target (source_len * 2 < cutoff_len) - **策略3**: 按比例截断source和target ### 5.5 优化建议 基于日志分析结果: 1. **如果observation经常被截断**: - 增加cutoff_len到4096或8192 - 压缩observation内容长度 2. **如果assistant回复被截断**: - 这是最严重的问题,必须解决 - 优先增加cutoff_len 3. **如果使用率很低**: - 考虑减少cutoff_len以提高训练效率 4. **如果经常预算耗尽**: - 数据过长,需要预处理压缩 ### 5.6 日志文件位置 训练过程中的日志会输出到以下位置: 1. **统一调试日志文件**: `token_debug_YYYYMMDD_HHMMSS.log`(自动生成时间戳) - 包含所有 `[TOKEN_DEBUG]`、`[TEMPLATE_DEBUG]`、`[INFER_SEQLEN]` 日志 - 自动轮转:文件大小达到50MB时自动创建新文件 - 自动清理:保留3天的历史日志 - 异步写入:不影响训练性能 2. **控制台输出**: 直接显示在终端(彩色格式) 3. **重定向文件**: 如果使用 `tee` 命令,会保存到指定文件 4. **训练日志**: 在 `output_dir` 目录下的 `trainer_log.jsonl` 文件 5. **系统日志**: 根据系统配置,可能输出到 `/var/log/` 或其他位置 **推荐做法**: ```bash # 将日志同时输出到控制台和文件 CUDA_VISIBLE_DEVICES=0,1 llamafactory-cli train [参数] 2>&1 | tee training_$(date +%Y%m%d_%H%M%S).log # 或者只保存到文件 CUDA_VISIBLE_DEVICES=0,1 llamafactory-cli train [参数] > training.log 2>&1 ``` **查看统一调试日志**: ```bash # 实时查看调试日志 tail -f token_debug_*.log # 过滤特定类型的调试信息 grep "TOKEN_DEBUG" token_debug_*.log grep "TEMPLATE_DEBUG" token_debug_*.log grep "INFER_SEQLEN" token_debug_*.log # 查看截断事件 grep "截断" token_debug_*.log # 统计使用率 grep "使用率" token_debug_*.log | tail -10 ``` ### 5.7 注意事项 1. **性能影响**: 调试日志会增加训练时间,建议只在调试时使用 2. **日志量**: 日志量较大,建议重定向到文件 3. **生产环境**: 生产环境建议移除或注释掉调试日志 4. **存储空间**: 长时间训练会产生大量日志,注意磁盘空间 ## 6. 快速参考 ### 6.1 常用调试命令 ```bash # 快速调试token截断问题 CUDA_VISIBLE_DEVICES=6 llamafactory-cli train \ --stage sft --do_train True \ --model_name_or_path /data/models/Qwen3-8B \ --dataset your_dataset --cutoff_len 2048 \ --max_samples 100 --num_train_epochs 1 \ --output_dir ./debug_output \ 2>&1 | tee debug.log # 查看截断事件 grep '⚠️' debug.log # 查看使用率统计 grep '使用率:' debug.log # 分析日志 python analyze_token_logs.py debug.log ``` ### 6.2 日志文件位置总结 | 日志类型 | 位置 | 说明 | |---------|------|------| | **统一调试日志** | `token_debug_*.log` | 包含所有`[TOKEN_DEBUG]`等标记的调试信息 | | **控制台输出** | 终端 | 实时显示,训练时可见 | | **重定向文件** | `training_*.log` | 使用`tee`命令保存的完整日志 | | **训练日志** | `output_dir/trainer_log.jsonl` | LLaMA-Factory生成的训练日志 | ### 6.3 关键文件路径 ``` LLaMA-Factory/ ├── src/llamafactory/data/ │ ├── processor/supervised.py # TOKEN_DEBUG日志 │ ├── processor/processor_utils.py # INFER_SEQLEN日志 │ └── template.py # TEMPLATE_DEBUG日志 ├── configure_token_logs.py # 日志配置脚本 ├── token_debug_*.log # 统一调试日志文件 ├── test_token_debug.py # 测试脚本 ├── analyze_token_logs.py # 日志分析脚本 └── TOKEN_DEBUG_README.md # 快速使用指南 ``` ### 6.4 故障排除 | 问题 | 可能原因 | 解决方案 | |------|----------|----------| | 日志不显示 | 日志级别设置 | 检查logging配置 | | 截断频繁 | cutoff_len太小 | 增加cutoff_len到4096或8192 | | 使用率低 | 数据过短 | 考虑减少cutoff_len | | 训练效果差 | assistant被截断 | 优先解决截断问题 |