Memory Efficient GRPO (#1773)

* Update __init__.py

* Update loader.py

* Update rl.py

* Update rl.py

* Update _utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Better TRL handling

* Update rl.py

* Update tokenizer_utils.py

* Auto patching

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update rl.py

* Update tokenizer_utils.py

* Update rl.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update tokenizer_utils.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update tokenizer_utils.py

* Update rl.py

* Update rl.py

* Update rl.py

* max seq length

* Update rl.py

* Update rl.py

* Patching

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* NEFTune

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Extra replacements

* Update rl_replacements.py

* Update rl.py

* extra RL replacements

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update llama.py

* Update rl_replacements.py

* Update _utils.py

* Update loader_utils.py

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* autocast

* Update rl_replacements.py

* Update llama.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update llama.py

* Update rl_replacements.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update rl_replacements.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update pyproject.toml

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update llama.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update llama.py

* Update _utils.py

* Update llama.py

* Update _utils.py

* Update rl_replacements.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update rl_replacements.py

* Update llama.py

* Update llama.py

* Update llama.py

* Update llama.py

* GRPO optimized

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Selective Log softmax

* Fix GRPO bsz

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Fix TRL

* Metrics GRPO

* Update rl_replacements.py

* Update rl_replacements.py

* No compile

* Update rl.py

* Remove docs

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl_replacements.py

* Update rl.py

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* llama-quantize on WINDOWS WSL error fix - edit save.py (gguf saving breaks) (#1649)

* edit save.py to fix gguf saving breaks.

* add check for .exe or not exe file extension for linux and windows

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update llama.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update llama.py

* Update llama.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl.py

* Update rl.py

* Update rl_replacements.py

* Update rl.py

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* unsloth_num_chunks

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl_replacements.py

* Update rl_replacements.py

* Update rl_replacements.py (#1754)

Fix typo in comment: know -> now.

This was printed when running the Llama3.1_(8B)-GRPO.ipynb example notebook, so I'd expect others to run into it as well.

* Optional logits

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl_replacements.py

* Update rl.py

* Update rl.py

* Update rl.py

* Update rl.py

* fix an import error (#1767)

* fix an import error

* Delete .gitignore

* Update loader.py

* Update save.py

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>

* SamplingParams

* Convert mask to float (#1762)

* [Windows Support] Add latest `xformers` wheels to pyproject.toml (#1753)

* Add latest xformers

* Add a couple of lines to docs

* vLLMSamplingParams

* Update __init__.py

* default num_chunks == -1

* Versioning

---------

Co-authored-by: Gennadii Manzhos <105049664+everythingisc00l@users.noreply.github.com>
Co-authored-by: Seth Weidman <seth@sethweidman.com>
Co-authored-by: Nino Risteski <95188570+NinoRisteski@users.noreply.github.com>
Co-authored-by: Edd <68678137+Erland366@users.noreply.github.com>
Co-authored-by: Ben <6579034+versipellis@users.noreply.github.com>
This commit is contained in:
Daniel Han 2025-02-20 04:23:28 -08:00 committed by GitHub
commit 7b6cd79c2f
11 changed files with 206 additions and 101 deletions

View file

@ -193,7 +193,7 @@ print(f'pip install --upgrade pip && pip install "unsloth[{x}] @ git+https://git
### Windows Installation
To run Unsloth directly on Windows:
- Install Triton from this Windows fork and follow the instructions: https://github.com/woct0rdho/triton-windows
- Install Triton from this Windows fork and follow the instructions: https://github.com/woct0rdho/triton-windows (be aware that the Windows fork requires PyTorch >= 2.4 and CUDA 12)
- In the SFTTrainer, set `dataset_num_proc=1` to avoid a crashing issue:
```python
trainer = SFTTrainer(
@ -202,12 +202,15 @@ trainer = SFTTrainer(
)
```
### Advanced/Troubleshooting
For **advanced installation instructions** or if you see weird errors during installations:
1. Install `torch` and `triton`. Go to https://pytorch.org to install it. For example `pip install torch torchvision torchaudio triton`
2. Confirm if CUDA is installated correctly. Try `nvcc`. If that fails, you need to install `cudatoolkit` or CUDA drivers.
3. Install `xformers` manually. You can try installing `vllm` and seeing if `vllm` succeeds. Check if `xformers` succeeded with `python -m xformers.info` Go to https://github.com/facebookresearch/xformers. Another option is to install `flash-attn` for Ampere GPUs.
4. Finally, install `bitsandbytes` and check it with `python -m bitsandbytes`
4. Double check that your versions of Python, CUDA, CUDNN, `torch`, `triton`, and `xformers` are compatible with one another. The [PyTorch Compatibility Matrix](https://github.com/pytorch/pytorch/blob/main/RELEASE.md#release-compatibility-matrix) may be useful.
5. Finally, install `bitsandbytes` and check it with `python -m bitsandbytes`
## 📜 [Documentation](https://docs.unsloth.ai)
- Go to our official [Documentation](https://docs.unsloth.ai) for saving to GGUF, checkpointing, evaluation and more!

View file

@ -39,7 +39,7 @@ triton = [
"triton @ https://github.com/woct0rdho/triton-windows/releases/download/v3.1.0-windows.post5/triton-3.1.0-cp312-cp312-win_amd64.whl ; python_version=='3.12' and platform_system == 'Windows'",
]
huggingface = [
"unsloth_zoo>=2025.2.5",
"unsloth_zoo>=2025.2.6",
"packaging",
"tyro",
"transformers>=4.46.1,!=4.47.0",
@ -196,6 +196,10 @@ cu126onlytorch260 = [
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and platform_system == 'Linux'",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and platform_system == 'Linux'",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and platform_system == 'Linux'",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp39-cp39-win_amd64.whl ; python_version=='3.9' and platform_system == 'Windows'",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp310-cp310-win_amd64.whl ; python_version=='3.10' and platform_system == 'Windows'",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp311-cp311-win_amd64.whl ; python_version=='3.11' and platform_system == 'Windows'",
"xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp312-cp312-win_amd64.whl ; python_version=='3.12' and platform_system == 'Windows'",
]
cu118 = [
"unsloth[huggingface]",
@ -344,7 +348,7 @@ colab-ampere-torch220 = [
"flash-attn>=2.6.3",
]
colab-new = [
"unsloth_zoo>=2025.2.5",
"unsloth_zoo>=2025.2.6",
"packaging",
"tyro",
"transformers>=4.46.1,!=4.47.0",

View file

@ -196,7 +196,7 @@ pass
# Check for unsloth_zoo
try:
unsloth_zoo_version = importlib_version("unsloth_zoo")
if Version(unsloth_zoo_version) < Version("2025.2.4"):
if Version(unsloth_zoo_version) < Version("2025.2.6"):
try:
os.system("pip install --upgrade --no-cache-dir --no-deps unsloth_zoo")
except:

View file

@ -20,4 +20,4 @@ from .mistral import FastMistralModel
from .qwen2 import FastQwen2Model
from .dpo import PatchDPOTrainer, PatchKTOTrainer
from ._utils import is_bfloat16_supported
from .rl import PatchFastRL
from .rl import PatchFastRL, vLLMSamplingParams

View file

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "2025.2.12"
__version__ = "2025.2.13"
__all__ = [
"SUPPORTS_BFLOAT16",

View file

@ -700,6 +700,7 @@ def LlamaModel_fast_forward(
elif inputs_requires_grad:
inputs_embeds.requires_grad_(False)
pass
attention_mask = attention_mask[:,:self.max_seq_length] # Must resize!
inputs_embeds *= attention_mask.unsqueeze(0).transpose(0, 1).transpose(1, 2)
if inputs_requires_grad: inputs_embeds.requires_grad_(True)
pass
@ -774,9 +775,12 @@ def LlamaModel_fast_forward(
self.SWA_mask = True
self.GA_mask = False
elif attention_mask is not None:
# Fixes https://github.com/unslothai/unsloth/issues/853
# Unsloth needs a 2D mask, not a [2, 1, n, n] mask!
# https://github.com/pytorch/pytorch/issues/103749
# Need to convert to float and not using bool
attention_mask = (1.0 - attention_mask.float()) * torch.finfo(inputs_embeds.dtype).min
dynamic_SWA_mask = _prepare_4d_causal_attention_mask_for_sdpa(
attention_mask,
(batch_size, seq_length),
@ -1030,6 +1034,7 @@ def CausalLM_fast_forward(fast_forward_inference):
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
num_logits_to_keep: Optional[int] = 0,
logits_to_keep: Optional[int] = 0,
*args, **kwargs,
) -> Union[Tuple, CausalLMOutputWithPast]:
@ -1053,16 +1058,16 @@ def CausalLM_fast_forward(fast_forward_inference):
# decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn)
self.model._has_no_labels = labels is None
outputs = self.model(
input_ids=input_ids,
causal_mask=causal_mask,
attention_mask=attention_mask,
position_ids=position_ids,
past_key_values=past_key_values,
inputs_embeds=inputs_embeds,
use_cache=use_cache,
output_attentions=output_attentions,
output_hidden_states=output_hidden_states,
return_dict=return_dict,
input_ids = input_ids,
causal_mask = causal_mask,
attention_mask = attention_mask,
position_ids = position_ids,
past_key_values = past_key_values,
inputs_embeds = inputs_embeds,
use_cache = use_cache,
output_attentions = output_attentions,
output_hidden_states = output_hidden_states,
return_dict = return_dict,
)
pass
hidden_states = outputs[0]
@ -1072,6 +1077,20 @@ def CausalLM_fast_forward(fast_forward_inference):
logit_softcapping = getattr(self.config, "final_logit_softcapping", 0)
logit_scaling = getattr(self.config, "logit_scale", 0)
dtype = lm_head.dtype
num_logits_to_keep = max(num_logits_to_keep, logits_to_keep)
# Output last hidden states without logits if asked
if os.environ.get("UNSLOTH_RETURN_HIDDEN_STATES", "0") == "1":
if num_logits_to_keep != 0:
hidden_states = hidden_states[:, -num_logits_to_keep:, :]
return CausalLMOutputWithPast(
loss = None,
logits = hidden_states,
past_key_values = outputs.past_key_values,
hidden_states = outputs.hidden_states,
attentions= outputs.attentions,
)
pass
if bsz == 1 and q_len == 1:
logits = torch.mv(lm_head, hidden_states.ravel().to(dtype))
@ -1166,11 +1185,11 @@ def CausalLM_fast_forward(fast_forward_inference):
return (loss,) + output if loss is not None else output
return CausalLMOutputWithPast(
loss=loss,
logits=logits,
past_key_values=outputs.past_key_values,
hidden_states=outputs.hidden_states,
attentions=outputs.attentions,
loss = loss,
logits = logits,
past_key_values = outputs.past_key_values,
hidden_states = outputs.hidden_states,
attentions= outputs.attentions,
)
pass
return _CausalLM_fast_forward
@ -1180,28 +1199,30 @@ pass
@torch._disable_dynamo
def PeftModelForCausalLM_fast_forward(
self,
input_ids=None,
causal_mask=None,
attention_mask=None,
inputs_embeds=None,
labels=None,
output_attentions=None,
output_hidden_states=None,
return_dict=None,
task_ids=None,
num_logits_to_keep=0,
input_ids = None,
causal_mask = None,
attention_mask = None,
inputs_embeds = None,
labels = None,
output_attentions = None,
output_hidden_states = None,
return_dict = None,
task_ids = None,
num_logits_to_keep = 0,
logits_to_keep = 0,
**kwargs,
):
return self.base_model(
input_ids=input_ids,
causal_mask=causal_mask,
attention_mask=attention_mask,
inputs_embeds=inputs_embeds,
labels=labels,
output_attentions=output_attentions,
output_hidden_states=output_hidden_states,
return_dict=return_dict,
num_logits_to_keep=num_logits_to_keep,
input_ids = input_ids,
causal_mask = causal_mask,
attention_mask = attention_mask,
inputs_embeds = inputs_embeds,
labels = labels,
output_attentions = output_attentions,
output_hidden_states = output_hidden_states,
return_dict = return_dict,
num_logits_to_keep = num_logits_to_keep,
logits_to_keep = logits_to_keep,
**kwargs,
)
pass
@ -1694,9 +1715,9 @@ class FastLlamaModel:
elif dtype == torch.bfloat16 and not SUPPORTS_BFLOAT16:
logger.warning_once("Device does not support bfloat16. Will change to float16.")
dtype = torch.float16
elif dtype == torch.float16 and SUPPORTS_BFLOAT16:
logger.warning_once("Device supports bfloat16 but you selected float16. Will change to bfloat16.")
dtype = torch.bfloat16
# elif dtype == torch.float16 and SUPPORTS_BFLOAT16:
# logger.warning_once("Device supports bfloat16 but you selected float16. Will change to bfloat16.")
# dtype = torch.bfloat16
assert(dtype == torch.float16 or dtype == torch.bfloat16 or dtype == torch.float32)

View file

@ -24,10 +24,14 @@ from peft import PeftConfig, PeftModel
from .loader_utils import get_model_name
import os, contextlib, sys
try:
from huggingface_hub.utils import get_token
from huggingface_hub import get_token
except:
# Old HF Hub versions <= 0.0.25
from huggingface_hub.utils._token import get_token
try:
from huggingface_hub.utils import get_token
except:
# For older versions of huggingface_hub
from huggingface_hub.utils._token import get_token
pass
pass
from huggingface_hub import HfFileSystem
import importlib.util

View file

@ -601,11 +601,6 @@ __INT_TO_FLOAT_MAPPER = \
"Qwen/Qwen2.5-VL-72B-Instruct",
"unsloth/Qwen2.5-VL-72B-Instruct-bnb-4bit",
),
"unsloth/DeepHermes-3-Llama-3-8B-Preview-unsloth-bnb-4bit" : (
"unsloth/DeepHermes-3-Llama-3-8B-Preview",
"NousResearch/DeepHermes-3-Llama-3-8B-Preview",
"unsloth/DeepHermes-3-Llama-3-8B-Preview-bnb-4bit",
),
"unsloth/DeepScaleR-1.5B-Preview-unsloth-bnb-4bit" : (
"unsloth/DeepHermes-3-Llama-3-8B-Preview",
"agentica-org/DeepScaleR-1.5B-Preview",

View file

@ -14,6 +14,7 @@
__all__ = [
"PatchFastRL",
"vLLMSamplingParams",
]
import torch
@ -36,12 +37,20 @@ selective_log_softmax = RL_REPLACEMENTS["selective_log_softmax"]
torch_compile_options = {
"epilogue_fusion" : True,
"max_autotune" : True,
"max_autotune" : False, # Disable Triton mm kernels
"shape_padding" : True,
"trace.enabled" : False,
"triton.cudagraphs" : False,
}
def vLLMSamplingParams(**kwargs):
from vllm import SamplingParams
sampling_params = SamplingParams(**kwargs)
sampling_params._set_kwargs = kwargs
return sampling_params
pass
def PatchRL(FastLanguageModel):
from trl.models.utils import unwrap_model_for_generation
@ -94,11 +103,12 @@ from typing import *
from dataclasses import dataclass, field
from packaging.version import Version
import torch
import numpy as np
from contextlib import nullcontext
from torch.nn import functional as F
torch_compile_options = {{
"epilogue_fusion" : True,
"max_autotune" : True,
"max_autotune" : False,
"shape_padding" : True,
"trace.enabled" : False,
"triton.cudagraphs" : False,
@ -112,16 +122,24 @@ class Unsloth{RLConfig_name}({RLConfig_name}):
"""
{__RLConfig_doc__}
"""
sampling_params: Optional[Any] = field(
vllm_sampling_params: Optional[Any] = field(
default = None,
metadata = {{'help': 'vLLM SamplingParams'}},
)
unsloth_num_chunks : Optional[int] = field(
default = -1,
metadata = {{'help': 'Chunk size to reduce memory usage. -1 is most efficient.'}},
)
def __init__({RLConfig_arguments},
sampling_params = None,
vllm_sampling_params = None,
unsloth_num_chunks = -1,
**kwargs,
):
{RLConfig_extra_args}
super().__init__({RLConfig_call_args}{RLConfig_kwargs})
assert(hasattr(vllm_sampling_params, '_set_kwargs'))
self.vllm_sampling_params = vllm_sampling_params
self.unsloth_num_chunks = unsloth_num_chunks
pass
{RLTrainer_extras}
@ -422,7 +440,9 @@ def _patch_trl_rl_trainers(trainer_file = "grpo_trainer"):
# Create full module
exec(f"from trl.trainer import ({RLTrainer_name}, {RLConfig_name},)")
__RLTrainer_doc__ = eval(f"trl.trainer.{RLTrainer_name}").__doc__
if __RLTrainer_doc__ is None: __RLTrainer_doc__ = ""
__RLConfig_doc__ = eval(f"trl.trainer.{RLConfig_name}") .__doc__
if __RLConfig_doc__ is None: __RLConfig_doc__ = ""
# Get all pre-modules
if trainer_file in RL_PRE_ITEMS:
@ -431,6 +451,11 @@ def _patch_trl_rl_trainers(trainer_file = "grpo_trainer"):
RL_pre = ""
pass
# Check if SamplingParams is in there
if "SamplingParams" in old_RLTrainer_source:
RL_pre = RL_pre + "\n" + inspect.getsource(vLLMSamplingParams)
pass
# Selective log softmax
selective_log_softmax_code = inspect.getsource(selective_log_softmax)
@ -457,6 +482,14 @@ def _patch_trl_rl_trainers(trainer_file = "grpo_trainer"):
selective_log_softmax_code = selective_log_softmax_code,
)
# Remove multiple doc strings
if __RLConfig_doc__ != "" and RLTrainer_source.count(__RLTrainer_doc__) == 2:
RLTrainer_source = RLTrainer_source.replace(__RLTrainer_doc__, "", 1)
pass
# Remove multiple newlines
RLTrainer_source = re.sub(r"[\n]{3,}", "\n", RLTrainer_source)
# Create new function
created_module = create_new_function(
f"Unsloth{RLTrainer_name}",
@ -501,7 +534,7 @@ def patch_functions(RLTrainer, trainer_file, RLTrainer_name, all_imports, import
replacer = replacer[0]
vllm_setter = "\n" + " "*8 + \
"if hasattr(model, 'vllm_engine') and "\
"getattr(args, 'use_vllm') and getattr(args, 'use_vllm', False): "\
"hasattr(args, 'use_vllm') and (getattr(args, 'use_vllm', False) == False): "\
"args.use_vllm = True\n"
init = init.replace(replacer, replacer + vllm_setter)
pass
@ -529,14 +562,31 @@ def patch_functions(RLTrainer, trainer_file, RLTrainer_name, all_imports, import
)
if len(sampling_params) == 1:
sampling_params = sampling_params[0]
# Fix guided_decoding
sampling_params = sampling_params.replace(
"guided_decoding=guided_decoding,",
'guided_decoding='\
'GuidedDecodingParams(backend="outlines", regex=args.vllm_guided_decoding_regex) '\
'if getattr(args, "vllm_guided_decoding_regex", None) is not None else None,',
)
# Replace with our vLLM engine
sampling_params = \
" "*12 + "self.llm = model.vllm_engine; self._last_loaded_step = 0; " + \
sampling_params # Add spaces
# Add extra arguments to SamplingParams
extra = "**getattr(getattr(args, 'vllm_sampling_params', vLLMSamplingParams()), '_set_kwargs', {})"
# Backwards replace
to_replace = "," + extra + "," + ")"
sampling_params = to_replace.join(sampling_params.rsplit(")", 1))
# Strip multiple commas
sampling_params = re.sub(r"[\,][\s]{0,}\,", ",", sampling_params)
new_vllm_part = \
f"\n{' '*8}if {args}.use_vllm:\n{sampling_params} "\
f"if getattr(args, 'sampling_params', None) is None else "\
f"getattr(args, 'sampling_params', None)\n{' '*8}else:\n"
f"\n{' '*8}if {args}.use_vllm:\n{sampling_params}"\
f"\n{' '*8}else:\n"
init = init.replace(vllm_part, new_vllm_part)
pass
pass
@ -607,6 +657,7 @@ def patch_functions(RLTrainer, trainer_file, RLTrainer_name, all_imports, import
old, new = changed[function]
RLTrainer_source = RLTrainer_source.replace(old, new)
pass
RLTrainer_source = RLTrainer_source.replace(
f"class {RLTrainer_name}", f"class _Unsloth{RLTrainer_name}", 1
)

View file

@ -40,7 +40,7 @@ torch_compile_options = {
}
# Check untrained tokens
def sft_trainer_fix_untraiend_tokens(call_args, extra_args):
def sft_trainer_fix_untrained_tokens(call_args, extra_args):
if "model" in call_args and "train_dataset" in call_args:
fix_tokenizer = \
"IGNORED_TOKENIZER_NAMES = os.environ.get('UNSLOTH_IGNORED_TOKENIZER_NAMES', '').split('\\n')\n"\
@ -52,7 +52,7 @@ def sft_trainer_fix_untraiend_tokens(call_args, extra_args):
return fix_tokenizer
return ""
pass
RL_EXTRA_ARGS["sft_trainer"].append(sft_trainer_fix_untraiend_tokens)
RL_EXTRA_ARGS["sft_trainer"].append(sft_trainer_fix_untrained_tokens)
# Remove DPO columns which might randomnly be tokenized
@ -177,6 +177,7 @@ def grpo_trainer__get_per_token_logps(function_name, function):
if function_name != "_get_per_token_logps": return function
def _get_per_token_logps(self, model, input_ids, attention_mask, logits_to_keep):
return None # Unsloth efficient GRPO
if not hasattr(self, '_autocast_dtype'):
self._autocast_dtype = torch.float16 if os.environ.get('ACCELERATE_MIXED_PRECISION', 'fp16') == 'fp16' else torch.bfloat16
with torch.amp.autocast(device_type = 'cuda', dtype = self._autocast_dtype):
@ -198,8 +199,12 @@ def grpo_trainer__get_per_token_logps(function_name, function):
pass
RL_FUNCTIONS["grpo_trainer"].append(grpo_trainer__get_per_token_logps)
grpo_compute_loss = RL_REPLACEMENTS["grpo_compute_loss"]
grpo_compute_loss = RL_REPLACEMENTS["grpo_compute_loss"]
UnslothEfficientGRPO = RL_REPLACEMENTS["UnslothEfficientGRPO"]
grpo_accumulated_loss = RL_REPLACEMENTS["grpo_accumulated_loss"]
RL_PRE_ITEMS["grpo_trainer"].append(inspect.getsource(grpo_compute_loss))
RL_PRE_ITEMS["grpo_trainer"].append(inspect.getsource(UnslothEfficientGRPO))
RL_PRE_ITEMS["grpo_trainer"].append(inspect.getsource(grpo_accumulated_loss))
# Edit _get_per_token_logps to handle mixed precision
def grpo_trainer_compute_loss(function_name, function):
@ -213,10 +218,12 @@ def grpo_trainer_compute_loss(function_name, function):
prompt_ids, prompt_mask = inputs["prompt_ids"], inputs["prompt_mask"]
completion_ids, completion_mask = inputs["completion_ids"], inputs["completion_mask"]
input_ids = torch.cat([prompt_ids, completion_ids], dim=1)
bsz, qlen = input_ids.shape
# attention_mask = torch.cat([prompt_mask, completion_mask], dim=1)
attention_mask = None
logits_to_keep = completion_ids.size(1) # we only need to compute the logits for the completion tokens
_input_ids = input_ids
_logits_to_keep = logits_to_keep
per_token_logps = self._get_per_token_logps(model, input_ids, attention_mask, logits_to_keep)
# Compute the KL divergence between the model and the reference model
@ -229,9 +236,16 @@ def grpo_trainer_compute_loss(function_name, function):
# per_token_loss = -(per_token_loss - self.beta * per_token_kl)
# loss = ((per_token_loss * completion_mask).sum(dim=1) / completion_mask.sum(dim=1)).mean()
input_ids = input_ids[:, -logits_to_keep:]
loss, completion_length, mean_kl = grpo_compute_loss(
ref_per_token_logps, per_token_logps, input_ids, completion_mask, self.beta, advantages,
)
if False:#per_token_logps is not None:
loss, completion_length, mean_kl = grpo_compute_loss(
ref_per_token_logps, per_token_logps, input_ids, completion_mask, self.beta, advantages,
)
else:
loss, completion_length, mean_kl = grpo_accumulated_loss(
self, _input_ids, logits_to_keep, completion_mask, advantages,
n_chunks = self.args.unsloth_num_chunks,
)
# Log the metrics
# completion_length = self.accelerator.gather_for_metrics(completion_mask.sum(1)).float().mean().item()
self._metrics["completion_length"].append(completion_length.item())
@ -256,7 +270,7 @@ def grpo_trainer_fix_batch_size(RLTrainer_source, RLConfig_source):
check_batch_size = \
"div = per_device_train_batch_size // num_generations\n"\
"if div * num_generations != per_device_train_batch_size:\n"\
" print('Unsloth: We know expect `per_device_train_batch_size` to be a multiple of `num_generations`.\\n"\
" print('Unsloth: We now expect `per_device_train_batch_size` to be a multiple of `num_generations`.\\n"\
"We will change the batch size of ' + str(per_device_train_batch_size) + ' to the `num_generations` of ' + str(num_generations))\n"\
" per_device_train_batch_size = num_generations\n"
return check_batch_size

View file

@ -31,10 +31,14 @@ from transformers.models.llama.modeling_llama import logger
from .tokenizer_utils import fix_sentencepiece_gguf
from huggingface_hub import HfApi
try:
from huggingface_hub.utils import get_token
from huggingface_hub import get_token
except:
# Old HF Hub versions <= 0.0.25
from huggingface_hub.utils._token import get_token
try:
from huggingface_hub.utils import get_token
except:
# For older versions of huggingface_hub
from huggingface_hub.utils._token import get_token
pass
pass
from pathlib import Path
@ -254,7 +258,7 @@ def unsloth_save_model(
# First check for a token!
if push_to_hub:
from huggingface_hub import whoami
try:
try:
username = whoami(token = token)["name"]
except:
raise RuntimeError(
@ -385,7 +389,7 @@ def unsloth_save_model(
else:
internal_model = model
pass
# Cannot be converted properly!
if (save_method == "merged_4bit") or (save_method == "lora") or (
not hasattr(model, "model") or \
@ -481,7 +485,7 @@ def unsloth_save_model(
gb_found = re.match("([0-9]{1,})[\s]{0,}GB", max_shard_size, flags = re.IGNORECASE)
mb_found = re.match("([0-9]{1,})[\s]{0,}MB", max_shard_size, flags = re.IGNORECASE)
if gb_found: sharded_ram_usage = int(gb_found.group(1)) * 1024 * 1024 * 1024
elif mb_found: sharded_ram_usage = int(mb_found.group(1)) * 1024 * 1024
elif mb_found: sharded_ram_usage = int(mb_found.group(1)) * 1024 * 1024
elif type(max_shard_size) is int:
sharded_ram_usage = sharded_ram_usage
pass
@ -612,7 +616,7 @@ def unsloth_save_model(
# Edit save_pretrained_settings
# [TODO] _create_repo has errors due to **kwargs getting accepted
save_pretrained_settings["state_dict"] = state_dict
# commit_description does not seem to work?
what_to_delete = ("use_temp_dir", "commit_message", "create_pr", "revision", "commit_description", "tags",) \
if not push_to_hub else \
@ -665,7 +669,7 @@ def unsloth_save_model(
# Revert back padding side
tokenizer.padding_side = old_padding_side
print(" Done.")
else:
print()
@ -877,10 +881,15 @@ def install_llama_cpp_old(version = -10):
pass
# Check if successful
if not os.path.exists("llama.cpp/quantize") and not os.path.exists("llama.cpp/llama-quantize"):
if not (
os.path.exists("llama.cpp/llama-quantize.exe") or
os.path.exists("llama.cpp/llama-quantize") or
os.path.exists("llama.cpp/quantize.exe") or
os.path.exists("llama.cpp/quantize")
):
raise RuntimeError(
"Unsloth: The file 'llama.cpp/llama-quantize' or `llama.cpp/quantize` does not exist.\n"\
"But we expect this file to exist! Maybe the llama.cpp developers changed the name?"
"But we expect this file to exist! Maybe the llama.cpp developers changed the name or check extension of the llama-quantize file."
)
pass
pass
@ -957,7 +966,7 @@ def save_to_gguf(
else:
raise TypeError("Unsloth: quantization_method can only be a string or a list of strings")
pass
# Check if bfloat16 is supported
if model_dtype == "bf16" and not torch.cuda.is_bf16_supported():
logger.warning(
@ -973,7 +982,7 @@ def save_to_gguf(
pass
# Check I quants
for quant_method in quantization_method:
for quant_method in quantization_method:
if quant_method.startswith("iq2"):
raise RuntimeError("Unsloth: Currently iq2 type quantizations aren't supported yet - sorry!")
pass
@ -1026,9 +1035,9 @@ def save_to_gguf(
pass
# Determine whether the system already has llama.cpp installed and the scripts are executable
quantize_location = get_executable(["llama-quantize", "quantize"])
quantize_location = get_executable(["llama-quantize", "quantize", "llama-quantize.exe", "quantize.exe"])
convert_location = get_executable(["convert-hf-to-gguf.py", "convert_hf_to_gguf.py"])
error = 0
if quantize_location is not None and convert_location is not None:
print("Unsloth: llama.cpp found in the system. We shall skip installation.")
@ -1062,14 +1071,18 @@ def save_to_gguf(
# and llama.cpp/main changed to llama.cpp/llama-cli
# See https://github.com/ggerganov/llama.cpp/pull/7809
quantize_location = None
if os.path.exists("llama.cpp/quantize"):
if os.path.exists("llama.cpp/quantize.exe"):
quantize_location = "llama.cpp/quantize.exe"
elif os.path.exists("llama.cpp/quantize"):
quantize_location = "llama.cpp/quantize"
elif os.path.exists("llama.cpp/llama-quantize.exe"):
quantize_location = "llama.cpp/llama-quantize.exe"
elif os.path.exists("llama.cpp/llama-quantize"):
quantize_location = "llama.cpp/llama-quantize"
else:
raise RuntimeError(
"Unsloth: The file 'llama.cpp/llama-quantize' or 'llama.cpp/quantize' does not exist.\n"\
"But we expect this file to exist! Maybe the llama.cpp developers changed the name?"
"Unsloth: The file ('llama.cpp/llama-quantize' or 'llama.cpp/llama-quantize.exe' if you are on Windows WSL) or 'llama.cpp/quantize' does not exist.\n"\
"But we expect this file to exist! Maybe the llama.cpp developers changed the name or check extension of the llama-quantize file."
)
pass
@ -1150,7 +1163,7 @@ def save_to_gguf(
# Concurrency from https://rentry.org/llama-cpp-conversions#merging-loras-into-a-model
final_location = str((Path(model_directory) / f"unsloth.{first_conversion.upper()}.gguf").absolute())
print(f"Unsloth: [1] Converting model at {model_directory} into {first_conversion} GGUF format.\n"\
f"The output location will be {final_location}\n"\
"This might take 3 minutes...")
@ -1217,7 +1230,7 @@ def save_to_gguf(
command = f"./{quantize_location} {full_precision_location} "\
f"{final_location} {quant_method} {n_cpus}"
try_execute([command,], force_complete = True)
# Check if quantization succeeded!
@ -1378,7 +1391,7 @@ def _determine_username(save_directory, old_username, token):
save_directory = save_directory.lstrip("./")
if "/" not in save_directory:
from huggingface_hub import whoami
try:
try:
username = whoami(token = token)["name"]
if type(old_username) is str and username != old_username:
username = old_username
@ -1412,7 +1425,7 @@ def create_huggingface_repo(
repo_type = "model",
exist_ok = False,
private = private,
)
)
# Create model card
from huggingface_hub import ModelCard
@ -1453,7 +1466,7 @@ def upload_to_huggingface(
repo_type = "model",
exist_ok = False,
private = private,
)
)
# Create model card
from huggingface_hub import ModelCard
@ -1527,7 +1540,7 @@ def fix_tokenizer_bos_token(tokenizer):
# Check if BOS added already, then warn
fix_bos_token = False
chat_template = getattr(tokenizer, "chat_template", None)
if (tokenizer("A").input_ids[0] == getattr(tokenizer, "bos_token_id", None)):
if chat_template is not None and \
(
@ -1546,7 +1559,7 @@ def fix_tokenizer_bos_token(tokenizer):
new_chat_template = re.sub(r"\{[\s]{0,}\{[\s]{0,}bos\_token[\s]{0,}\}[\s]{0,}\}", "", chat_template)
# Remove {{bos_token +
new_chat_template = re.sub(r"\{[\s]{0,}\{[\s]{0,}bos\_token[\s]{0,}\+[\s]{0,}", "", new_chat_template)
tokenizer.chat_template = new_chat_template
pass
@ -1580,7 +1593,7 @@ def create_ollama_modelfile(tokenizer, gguf_location):
modelfile = modelfile\
.replace(FILE_LOCATION_REPLACER, "{__FILE_LOCATION__}")\
.replace(EOS_TOKEN_REPLACER, "{__EOS_TOKEN__}")
if "__EOS_TOKEN__" in modelfile:
modelfile = modelfile.format(
__FILE_LOCATION__ = gguf_location,
@ -1591,7 +1604,7 @@ def create_ollama_modelfile(tokenizer, gguf_location):
__FILE_LOCATION__ = gguf_location,
)
pass
modelfile = modelfile\
.replace("⚫@✅#🦥", "{")\
.replace("⚡@🦥#⛵", "}")\
@ -1733,7 +1746,7 @@ def unsloth_save_pretrained_gguf(
# Save to GGUF
all_file_locations, want_full_precision = save_to_gguf(
model_type, model_dtype, is_sentencepiece_model,
model_type, model_dtype, is_sentencepiece_model,
new_save_directory, quantization_method, first_conversion, makefile,
)
@ -1911,7 +1924,7 @@ def unsloth_push_to_hub_gguf(
# Save to GGUF
all_file_locations, want_full_precision = save_to_gguf(
model_type, model_dtype, is_sentencepiece_model,
model_type, model_dtype, is_sentencepiece_model,
new_save_directory, quantization_method, first_conversion, makefile,
)
@ -1928,7 +1941,7 @@ def unsloth_push_to_hub_gguf(
# If not needing full precision, skip the first
if not want_full_precision: all_file_locations = all_file_locations[1:]
for file_location in all_file_locations:
print("Unsloth: Uploading GGUF to Huggingface Hub...")
username = upload_to_huggingface(
@ -2044,8 +2057,8 @@ def unsloth_convert_lora_to_ggml_and_push_to_hub(
def unsloth_convert_lora_to_ggml_and_save_locally(
self,
save_directory: str, # Added parameter for the folder name
tokenizer,
save_directory: str, # Added parameter for the folder name
tokenizer,
temporary_location: str = "_unsloth_temporary_saved_buffers",
maximum_memory_usage: float = 0.85,
):
@ -2162,7 +2175,7 @@ def unsloth_generic_save_pretrained_merged(
tags : List[str] = None,
temporary_location : str = "_unsloth_temporary_saved_buffers",
maximum_memory_usage : float = 0.75,
):
):
"""
Same as .push_to_hub(...) except 4bit weights are auto
converted to float16 with as few overhead as possible.