From 8b1611c26c4752ba4adcc9ebf950b3d52ecabe87 Mon Sep 17 00:00:00 2001 From: danielhanchen Date: Sun, 31 May 2026 03:02:03 +0000 Subject: [PATCH] Fix full finetuning precision on V100 / no-bf16 GPUs Float16 full finetuning upcasts weights to float32, so the model dtype is float32, not bfloat16. The SFTTrainer precision template treated 'not float16' as 'bfloat16', which broke full finetuning on V100/T4: the dtype guard rejected float32 + fp16, and the auto mixed precision branch forced bf16 on hardware without it. FORCE_FLOAT32 models (Gemma3, gpt_oss, gemma3n, qwen3_5) were also only kept in float32 for LoRA, so full finetuning fell through to fp16 and produced NaNs. Distinguish bfloat16 from float32 in the guard, pick bf16 in the auto branch only when the GPU supports it (else fp16), and apply force_float32 for FORCE_FLOAT32 models in full finetuning too. bf16 GPUs are unchanged. --- unsloth/models/rl.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/unsloth/models/rl.py b/unsloth/models/rl.py index 359716fda4..2f806c59cd 100644 --- a/unsloth/models/rl.py +++ b/unsloth/models/rl.py @@ -1027,8 +1027,9 @@ def _patch_trl_rl_trainers_impl(trainer_file = "grpo_trainer"): "use_fp16 = getattr(args, 'fp16', False)\n" "if type(use_fp16) is not bool: use_fp16 = False\n" "force_float32 = False\n" - "full_finetuning = os.environ.get('UNSLOTH_ENABLE_FULL_FINETUNING', '0') == '1'\n" - "if not full_finetuning and (os.environ.get('UNSLOTH_FORCE_FLOAT32', '0') == '1'):\n" + # FORCE_FLOAT32 models (Gemma3, gpt_oss, ...) cannot use float16; keep + # them in float32 even for full finetuning so V100/T4 never autocast to fp16. + "if os.environ.get('UNSLOTH_FORCE_FLOAT32', '0') == '1':\n" " print('Unsloth: Switching to float32 training since model cannot work with float16')\n" " force_float32 = True\n" "mixed_precision_dtype = os.environ.get('UNSLOTH_MIXED_PRECISION', 'float32')\n" @@ -1037,8 +1038,9 @@ def _patch_trl_rl_trainers_impl(trainer_file = "grpo_trainer"): "from unsloth_zoo.utils import _get_dtype\n" "dtype = _get_dtype(dtype)\n" "float16 = dtype == torch.float16\n" + "bfloat16 = dtype == torch.bfloat16\n" "if not force_float32 and (float16 and use_bf16): raise TypeError('Unsloth: Model is in float16 precision but you want to use bfloat16 precision. Set fp16 to `True` and bf16 to `False`')\n" - "if not force_float32 and (not float16 and use_fp16): raise TypeError('Unsloth: Model is in bfloat16 precision but you want to use float16 precision. Set fp16 to `False` and bf16 to `True`')\n" + "if not force_float32 and (bfloat16 and use_fp16): raise TypeError('Unsloth: Model is in bfloat16 precision but you want to use float16 precision. Set fp16 to `False` and bf16 to `True`')\n" "if force_float32:\n" " # Forced float32 training\n" " args.fp16 = False\n" @@ -1047,11 +1049,12 @@ def _patch_trl_rl_trainers_impl(trainer_file = "grpo_trainer"): " if hasattr(args, 'mixed_precision'): args.mixed_precision = 'no'\n" " # args.mixed_precision is a new argument which needs to be set now\n" "elif (not use_bf16 and not use_fp16) and mixed_precision_dtype == 'float32':\n" - " # Mixed precision training\n" - " args.fp16 = float16\n" - " args.bf16 = not float16\n" - " os.environ['ACCELERATE_MIXED_PRECISION'] = 'fp16' if float16 else 'bf16'\n" - " if hasattr(args, 'mixed_precision'): args.mixed_precision = 'fp16' if float16 else 'bf16'\n" + " # Mixed precision training. bf16 only if the GPU supports it; V100/T4 use fp16.\n" + " use_bf16_amp = (not float16) and torch.cuda.is_bf16_supported()\n" + " args.fp16 = not use_bf16_amp\n" + " args.bf16 = use_bf16_amp\n" + " os.environ['ACCELERATE_MIXED_PRECISION'] = 'bf16' if use_bf16_amp else 'fp16'\n" + " if hasattr(args, 'mixed_precision'): args.mixed_precision = 'bf16' if use_bf16_amp else 'fp16'\n" " # args.mixed_precision is a new argument which needs to be set now\n" "elif mixed_precision_dtype == 'bfloat16':\n" " # Both False since bfloat16 full finetuning doesn't do any autocasting.\n"