调试过程
model_args
1 | ModelArguments(model_name_or_path='/root/zhangyitao/Qwen3-0_6B/', adapter_name_or_path=None, adapter_folder=None, cache_dir=None, use_fast_tokenizer=True, resize_vocab=False, split_special_tokens=False, add_tokens=None, add_special_tokens=None, new_special_tokens_config=None, init_special_tokens='noise_init', model_revision='main', low_cpu_mem_usage=True, rope_scaling=None, flash_attn='fa2', shift_attn=False, mixture_of_depths=None, use_unsloth=False, use_unsloth_gc=False, enable_liger_kernel=True, moe_aux_loss_coef=None, disable_gradient_checkpointing=False, use_reentrant_gc=True, upcast_layernorm=False, upcast_lmhead_output=False, train_from_scratch=False, infer_backend='huggingface', offload_folder='offload', use_kv_cache=True, use_v1_kernels=False, infer_dtype='auto', hf_hub_token=None, ms_hub_token=None, om_hub_token=None, print_param_status=False, trust_remote_code=True, quantization_method='bnb', quantization_bit=None, quantization_type='nf4', double_quantization=True, quantization_device_map=None, image_max_pixels=589824, image_min_pixels=1024, image_do_pan_and_scan=False, crop_to_patches=False, video_max_pixels=65536, video_min_pixels=256, video_fps=2.0, video_maxlen=128, use_audio_in_video=False, audio_sampling_rate=16000, export_dir=None, export_size=5, export_device='cpu', export_quantization_bit=None, export_quantization_dataset=None, export_quantization_nsamples=128, export_quantization_maxlen=1024, export_legacy_format=False, export_hub_model_id=None, use_kt=False, kt_optimize_rule=None, cpu_infer=32, chunk_size=8192, mode='normal', kt_maxlen=4096, kt_use_cuda_graph=True, kt_mode='normal', kt_force_think=False, vllm_maxlen=4096, vllm_gpu_util=0.7, vllm_enforce_eager=False, vllm_max_lora_rank=32, vllm_config=None, sglang_maxlen=4096, sglang_mem_fraction=0.7, sglang_tp_size=-1, sglang_config=None, sglang_lora_backend='triton', compute_dtype=torch.bfloat16, device_map={'': device(type='cuda', index=0)}, model_max_length=5000, block_diag_attn=False |
data_args
1 | DataArguments(template='qwen', dataset=['glaive_toolcall_zh_demo'], eval_dataset=None, dataset_dir='data', media_dir='data', cutoff_len=5000, train_on_prompt=False, mask_history=False, streaming=False, buffer_size=16384, mix_strategy='concat', interleave_probs=None, overwrite_cache=True, preprocessing_batch_size=1000, preprocessing_num_workers=32, max_samples=None, eval_num_beams=None, ignore_pad_token_for_loss=True, val_size=0.0, eval_on_each_dataset=False, packing=False, neat_packing=False, tool_format=None, default_system=None, enable_thinking=True, tokenized_path=None, data_shared_file_system=False |
tokenizer_module
1 | {'tokenizer': Qwen2TokenizerFast(name_or_path='/root/zhangyitao/Qwen3-0_6B/', vocab_size=151643, model_max_length=131072, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'eos_token': '<|im_end|>', 'pad_token': '<|endoftext|>', 'additional_special_tokens': ['<|im_start|>', '<|im_end|>', '<|object_ref_start|>', '<|object_ref_end|>', '<|box_start|>', '<|box_end|>', '<|quad_start|>', '<|quad_end|>', '<|vision_start|>', '<|vision_end|>', '<|vision_pad|>', '<|image_pad|>', '<|video_pad|>']}, clean_up_tokenization_spaces=False, added_tokens_decoder={ |
template (这里是在get_template_and_fix_tokenizer里面直接从字典拿的) format_observation这里把observation映射到了user!
1 | Template(format_user=StringFormatter(slots=['<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n'], tool_format=None), format_assistant=StringFormatter(slots=['{{content}}<|im_end|>\n'], tool_format=None), format_system=StringFormatter(slots=['<|im_start|>system\n{{content}}<|im_end|>\n'], tool_format=None), format_function=FunctionFormatter(slots=['{{content}}<|im_end|>\n'], tool_format='qwen'), format_observation=StringFormatter(slots=['<|im_start|>user\n<tool_response>\n{{content}}\n</tool_response><|im_end|>\n<|im_start|>assistant\n'], tool_format=None), format_tools=ToolFormatter(slots=[], tool_format='qwen'), format_prefix=EmptyFormatter(slots=[], tool_format=None), default_system='You are Qwen, created by Alibaba Cloud. You are a helpful assistant.', stop_words=['<|im_end|>'], thought_words=('<think>\n', '\n</think>\n\n'), tool_call_words=('<tool_call>', '</tool_call>'), efficient_eos=False, replace_eos=True, replace_jinja_template=False, enable_thinking=True, mm_plugin=BasePlugin(image_token=None, video_token=None, audio_token=None, expand_mm_tokens=True)) |
dataset_module
1 | {'train_dataset': Dataset({ |
dataset_module[“train_dataset”]
1 | Dataset({ |
_get_merged_dataset内部会加载数据集,真正encode是在_get_preprocessed_dataset,
example
1 | training example: |
model
1 | Qwen3ForCausalLM( |
data_collator
1 | SFTDataCollatorWith4DAttentionMask(tokenizer=Qwen2TokenizerFast(name_or_path='/root/zhangyitao/Qwen3-0_6B/', vocab_size=151643, model_max_length=131072, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'eos_token': '<|im_end|>', 'pad_token': '<|endoftext|>', 'additional_special_tokens': ['<|im_start|>', '<|im_end|>', '<|object_ref_start|>', '<|object_ref_end|>', '<|box_start|>', '<|box_end|>', '<|quad_start|>', '<|quad_end|>', '<|vision_start|>', '<|vision_end|>', '<|vision_pad|>', '<|image_pad|>', '<|video_pad|>']}, clean_up_tokenization_spaces=False, added_tokens_decoder={ |
第一个样本的输入示例
观察可以发现,observation这里被替换成了user,猜测应该是数据集map的时候,有一些特殊的替换?
1 | '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{"type": "function", "function": {"name": "generate_invoice", "description": "生成发票", "parameters": {"type": "object", "properties": {"customer_name": {"type": "string", "description": "客户名称"}, "items": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string", "description": "The item name"}, "quantity": {"type": "integer", "description": "The quantity of the item"}, "price": {"type": "number", "description": "The price per unit"}}, "required": ["name", "quantity", "price"]}}}, "required": ["customer_name", "items"]}}}\n{"type": "function", "function": {"name": "generate_password", "description": "生成随机密码", "parameters": {"type": "object", "properties": {"length": {"type": "integer", "description": "密码的长度"}}, "required": ["length"]}}}\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call><|im_end|>\n<|im_start|>user\n我需要为John Doe生成一张发票。他购买了2个苹果,每个$1,以及3根香蕉,每根$0.5。<|im_end|>\n<|im_start|>assistant\n<tool_call>\n{"name": "generate_invoice", "arguments": {"customer_name": "约翰·多伊", "items": [{"name": "苹果", "quantity": 2, "price": 1}, {"name": "香蕉", "quantity": 3, "price": 0.5}]}}\n</tool_call><|im_end|>\n<|im_start|>user\n<tool_response>\n{"invoice_id": "INV12345", "customer_name": "约翰·多伊", "items": [{"name": "苹果", "quantity": 2, "price": 1, "total": 2}, {"name": "香蕉", "quantity": 3, "price": 0.5, "total": 1.5}], "total": 3.5, "status": "生成"}\n</tool_response><|im_end|>\n<|im_start|>assistant\n发票已成功生成。发票编号为INV12345。约翰·多伊的总金额为$3.5。发票包含2个苹果,总金额为$2,以及3根香蕉,总金额为$1.5。<|im_end|>\n' |
微调完了以后部署
1 | CUDA_VISIBLE_DEVICES=0,1,2,3 /root/sunhao_11_nfs1/guorui/vllm090/bin/python -m vllm.entrypoints.openai.api_server --tensor-parallel-size 1 --dtype bfloat16 --trust-remote-code --host 0.0.0.0 --model /root/zhangyitao/output --max-model-len 8192 --served-model-name Qwen --gpu-memory-utilization 0.3 --enable-prefix-caching --tokenizer-mode auto --port 21111 --enable-auto-tool-choice --tool-call-parser hermes --chat-template /root/zhangyitao/Qwen3-0_6B/qwen3_nonthinking.jinja |
dataset_converter
1 | SharegptDatasetConverter(dataset_attr=glaive_toolcall_zh_demo.json, data_args=DataArguments(template='qwen', dataset=['glaive_toolcall_zh_demo'], eval_dataset=None, dataset_dir='data', media_dir='data', cutoff_len=5000, train_on_prompt=False, mask_history=False, streaming=False, buffer_size=16384, mix_strategy='concat', interleave_probs=None, overwrite_cache=True, preprocessing_batch_size=1000, preprocessing_num_workers=32, max_samples=None, eval_num_beams=None, ignore_pad_token_for_loss=True, val_size=0.0, eval_on_each_dataset=False, packing=False, neat_packing=False, tool_format=None, default_system=None, enable_thinking=True, tokenized_path=None, data_shared_file_system=False)) |
data_args
1 | DataArguments(template='qwen', dataset=['glaive_toolcall_zh_demo'], eval_dataset=None, dataset_dir='data', media_dir='data', cutoff_len=5000, train_on_prompt=False, mask_history=False, streaming=False, buffer_size=16384, mix_strategy='concat', interleave_probs=None, overwrite_cache=True, preprocessing_batch_size=1000, preprocessing_num_workers=32, max_samples=None, eval_num_beams=None, ignore_pad_token_for_loss=True, val_size=0.0, eval_on_each_dataset=False, packing=False, neat_packing=False, tool_format=None, default_system=None, enable_thinking=True, tokenized_path=None, data_shared_file_system=False) |
数据格式转换,最后有一个align_dataset,实际就是把dataset格式转换成指定的promt格式,map实现的
1 | 转换前: |
第二个例子
1 | '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n<|im_start|>user\n假设你有一个需要随机数的Java程序,范围在0到10之间。你可以使用什么代码片段来生成这样的数字?\n\n下面是一个示例代码片段,它使用Math.random()方法来生成一个在0到1之间的随机数,然后缩放并四舍五入结果,得到一个在0到10之间的随机数:\n\n```java\nint randomNumber = (int) (Math.random() * 11);\n```\n\n在Java中是否有更有效的方法来生成随机数?你会如何修改代码以生成具有不同范围或分布的随机数?<|im_end|>\n<|im_start|>assistant\n您提供的代码片段是Java中生成随机数的一种常见且高效的方法。然而,如果您需要对随机数生成有更多的控制,您可以使用java.util.Random类,它提供了更多生成随机数的选项。\n\n要生成一个在不同范围内的随机数,您可以修改代码以调整缩放因子。例如,要生成一个在1到10之间的随机数,您可以使用以下代码:\n\nint randomNumber = (int) (Math.random() * 10) + 1;\n\n要生成具有不同分布的随机数,您可以使用各种技术,如正态分布、指数分布或自定义分布。生成正态分布的一种方法是使用Box-Muller变换。这里有一个代码示例,它生成具有正态分布的随机数:\n\nRandom random = new Random();\ndouble u1 = random.nextDouble();\ndouble u2 = random.nextDouble();\ndouble z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\ndouble mean = 5;\ndouble stdDev = 2;\nint randomNumber = (int) (z * stdDev + mean);\n\n这段代码生成一个具有均值5和标准差2的正态分布随机数。您可以调整均值和标准差来生成具有不同分布的随机数。<|im_end|>\n<|im_start|>user\n你能解释一下算法分析中的大O记法概念吗?<|im_end|>\n<|im_start|>assistant\n是的,大O表示法是一种描述算法性能随输入数据大小增加的方法。它用于估计算法在最坏情况下的时间复杂度或空间复杂度。大O中的“O”代表“数量级”,这意味着随着输入大小的增加,算法所需的操作数以一定的速率增长。\n\n例如,如果我们有一个需要n步才能解决大小为n的问题的算法,那么时间复杂度可以表示为O(n),这意味着操作数随着输入大小线性增长。如果我们有一个需要n^2步才能解决大小为n的问题的算法,那么时间复杂度可以表示为O(n^2),这意味着操作数随着输入大小二次方增长。\n\n还有其他几种大O表示法,如O(log n)、O(n log n)、O(2^n)等,它们代表了随着输入大小增加的不同操作数增长速率。通过分析算法的大O表示法,我们可以比较不同算法的性能,并选择给定问题最有效的算法。\n\n您是否有关于大O表示法或算法分析的特定问题?<|im_end|>\n' |
数据集的处理
1 | get_dataset |
load_dataset以后数据格式为
1 | (Pdb) dataset[0] |
值得注意的是,dataset_attr是一个DatasetAttr类,直接打印只能看到看到__repr__以后打印的dataset_name,也就是数据集json文件,它的messages字段就是conversation,role_tag就是from,可以在类的定义中找到。
再看一下SharegptDatasetConverter是如何构造数据的,内部定义了一下奇偶标志位,也就决定了训练数据必须是按照human开头的顺序去执行。最后拼接的数据就是上面align_dataset后的数据。
再看一下_get_preprocessed_dataset是怎么做的,内部首先拿到SupervisedDatasetProcessor这个类
1 | (Pdb) dataset_processor |
map的时候会走到它的preprocess_dataset方法。处理每一条数据的由于传入了batch为true,和前面的align_dataset时候的map处理有一些不一样,这里加了批处理。
1 | preprocess_dataset |
encode_multiturn会构建出一个list,每个元素是个tuple,inputs_id和outputs_id,也就是输入和预测的序列。最终返回出去
_encode里面会涉及到各种formatter,对照着template的格式看,就清晰了。重点区分一下tool_formatter和function_formatter,tool_formatter是在构建system消息的时候,就会加进去,而function_formatter是在构建role function的时候用的。
如果我们需要自定义输入和输出格式,添加角色信息,主要就是需要修改_encode方法以及对应的以及对应的converter类