fix: support completion_only_loss=True with prompt/completion dataset columns

When completion_only_loss=True, TRL rejects formatting_func but Unsloth's
patched _prepare_dataset/_prepare_non_packed_dataloader assumed either
formatting_func or dataset_text_field was always set, causing a catch-22.

Now handles prompt/completion columns as a third case for BOS token
detection, with a safe None fallback for all other cases.
This commit is contained in:
Ayush Kushwaha 2026-03-21 23:22:20 +05:30
commit 978f78c6f1
2 changed files with 20 additions and 6 deletions

View file

@ -153,11 +153,18 @@ def sft_trainer_prepare_dataset(function_name, function):
"if 'tokenizer' not in locals(): tokenizer = processing_class\n"
"if 'formatting_func' not in locals(): raise RuntimeError('Unsloth: Please file a bug report - `formatting_func` does not exist!')\n"
"if 'dataset_text_field' not in locals() and 'args' in locals(): dataset_text_field = args.dataset_text_field\n"
"if 'dataset_text_field' not in locals(): raise RuntimeError('Unsloth: Please file a bug report - `dataset_text_field` does not exist!')\n"
"test_text = dataset[0][dataset_text_field] if (formatting_func is None and dataset_text_field is not None) else formatting_func(dataset[0])[0]\n"
"if 'dataset_text_field' not in locals(): dataset_text_field = None\n"
"if formatting_func is None and dataset_text_field is None and 'prompt' in dataset[0] and 'completion' in dataset[0]:\n"
" test_text = dataset[0]['prompt'] + dataset[0]['completion']\n"
"elif formatting_func is None and dataset_text_field is not None:\n"
" test_text = dataset[0][dataset_text_field]\n"
"elif formatting_func is not None:\n"
" test_text = formatting_func(dataset[0])[0]\n"
"else:\n"
" test_text = None\n"
"chat_template = getattr(tokenizer, 'chat_template', None)\n"
"chat_template = '' if chat_template is None else chat_template\n"
"has_bos_token_already = (test_text.startswith(tokenizer.bos_token) or tokenizer.bos_token in chat_template) "
"has_bos_token_already = (test_text is not None and (test_text.startswith(tokenizer.bos_token) or tokenizer.bos_token in chat_template)) "
"if getattr(tokenizer, 'bos_token', None) is not None else False\n"
"if 'add_special_tokens' not in locals() and has_bos_token_already:\n"
" from functools import partial\n"

View file

@ -974,11 +974,18 @@ def patch_sft_trainer_tokenizer():
"if 'tokenizer' not in locals(): tokenizer = processing_class\n"
"if 'formatting_func' not in locals(): raise RuntimeError('Unsloth: Please file a bug report - `formatting_func` does not exist!')\n"
"if 'dataset_text_field' not in locals() and 'args' in locals(): dataset_text_field = args.dataset_text_field\n"
"if 'dataset_text_field' not in locals(): raise RuntimeError('Unsloth: Please file a bug report - `dataset_text_field` does not exist!')\n"
"test_text = dataset[0][dataset_text_field] if (formatting_func is None and dataset_text_field is not None) else formatting_func(dataset[0])[0]\n"
"if 'dataset_text_field' not in locals(): dataset_text_field = None\n"
"if formatting_func is None and dataset_text_field is None and 'prompt' in dataset[0] and 'completion' in dataset[0]:\n"
" test_text = dataset[0]['prompt'] + dataset[0]['completion']\n"
"elif formatting_func is None and dataset_text_field is not None:\n"
" test_text = dataset[0][dataset_text_field]\n"
"elif formatting_func is not None:\n"
" test_text = formatting_func(dataset[0])[0]\n"
"else:\n"
" test_text = None\n"
"chat_template = getattr(tokenizer, 'chat_template', None)\n"
"chat_template = '' if chat_template is None else chat_template\n"
"has_bos_token_already = (test_text.startswith(tokenizer.bos_token) or tokenizer.bos_token in chat_template) "
"has_bos_token_already = (test_text is not None and (test_text.startswith(tokenizer.bos_token) or tokenizer.bos_token in chat_template)) "
"if getattr(tokenizer, 'bos_token', None) is not None else False\n"
"if 'add_special_tokens' not in locals() and has_bos_token_already:\n"
" from functools import partial\n"