Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7c38e30
udoate datasets.py
Goekdeniz-Guelmez Mar 14, 2025
a6a5b2b
update lora.py
Goekdeniz-Guelmez Mar 14, 2025
61e1504
adding orpo_trainer.py + datastes.py fix
Goekdeniz-Guelmez Mar 14, 2025
2b0b8de
udpate LORA.md + lora_config.yaml
Goekdeniz-Guelmez Mar 14, 2025
84a9535
nits + update Acnowledgements.md
Goekdeniz-Guelmez Mar 14, 2025
5d75c25
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez Mar 17, 2025
03c623e
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez Mar 18, 2025
e01c768
Merge branch 'main' into adding-orpo-training
Goekdeniz-Guelmez Mar 18, 2025
6cc3567
formatting
Goekdeniz-Guelmez Mar 18, 2025
aa144f4
Merge branch 'main' into adding-orpo-training
Goekdeniz-Guelmez Mar 19, 2025
74d55e9
making key names customizable
Goekdeniz-Guelmez Mar 19, 2025
c892278
remove reference model arg
Goekdeniz-Guelmez Mar 20, 2025
4991c90
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez Mar 24, 2025
f02dc81
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez Mar 25, 2025
866a99f
remove matal in clear cache + type definition
Goekdeniz-Guelmez Mar 25, 2025
289cff0
nits
Goekdeniz-Guelmez Mar 25, 2025
f04ae96
nits
Goekdeniz-Guelmez Mar 25, 2025
8685da6
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez Mar 27, 2025
6510eb5
Merge branch 'main' into adding-orpo-training
Goekdeniz-Guelmez Mar 27, 2025
037796f
fix
Goekdeniz-Guelmez Mar 27, 2025
ce99c75
Merge branch 'main' into adding-orpo-training
Goekdeniz-Guelmez Mar 31, 2025
9caed7f
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez Apr 17, 2025
61cce75
Merge branch 'main' into adding-orpo-training
Goekdeniz-Guelmez Apr 21, 2025
54d979d
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez Apr 28, 2025
ee06d6f
Merge branch 'main' into adding-orpo-training
Goekdeniz-Guelmez May 8, 2025
3c124e1
Refactor orpo_loss function to improve log probability calculations a…
Goekdeniz-Guelmez May 8, 2025
dbc7cd4
fix evaluation
Goekdeniz-Guelmez May 8, 2025
fe08092
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez May 9, 2025
c9031d1
Merge branch 'main' into adding-orpo-training
Goekdeniz-Guelmez May 10, 2025
5713919
use CacheDataset()
Goekdeniz-Guelmez May 10, 2025
c143a64
nits
Goekdeniz-Guelmez May 10, 2025
e272098
Merge branch 'ml-explore:main' into adding-orpo-training
Goekdeniz-Guelmez May 15, 2025
a8e264f
fix
Goekdeniz-Guelmez May 15, 2025
bea9bd8
format
Goekdeniz-Guelmez May 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ACKNOWLEDGMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ MLX LM was developed with contributions from the following individuals:

- Shunta Saito: Added support for PLaMo models.
- Prince Canuma: Helped add support for `Starcoder2` models.
- Gökdeniz Gülmez: Added support for the following architectures: OpenBMB's `MiniCPM` and `MiniCPM3`, Kyutai's `Helium`, State-Space's`Mamba v1`, Z.ai & THUKEG's `GLM4`, and Allenai's `OLMoE`; Added support for the following training algorithms: `full-fine-tuning`; Added support for the following other features: `Multiple Optimizers to choose for training`, and `reporting training metrics to WandB (Weights & Biases)`.
- Gökdeniz Gülmez: Added support for the following architectures: OpenBMB's `MiniCPM` and `MiniCPM3`, Kyutai's `Helium`, State-Space's`Mamba v1`, Z.ai & THUKEG's `GLM4`, and Allenai's `OLMoE`; Added support for the following training algorithms: `full-fine-tuning`, and `Odds Ratio Preference Optimization (ORPO)`; Added support for the following other features: `Multiple Optimizers to choose for training`, and `reporting training metrics to WandB (Weights & Biases)`.
54 changes: 53 additions & 1 deletion mlx_lm/LORA.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ LoRA (QLoRA).[^qlora] LoRA fine-tuning works with the following model families:

- [Run](#Run)
- [Fine-tune](#Fine-tune)
- [ORPO-Training](#ORPO-Training)
- [Evaluate](#Evaluate)
- [Generate](#Generate)
- [Fuse](#Fuse)
Expand Down Expand Up @@ -87,7 +88,58 @@ The default training computes a loss for every token in the sample. You can
ignore the prompt and compute loss for just the completion by passing
`--mask-prompt`. Note this is only supported for `chat` and `completion`
datasets. For `chat` datasets the final message in the message list is
considered the completion. See the [dataset section](#Data) for more details.
considered the completion. See the [dataset section](#Data) for more details.

### ORPO-Training

Odds Ratio Preference Optimization (ORPO) training fine-tunes models using human preference data. Usage:

```shell
mlx_lm.lora \
--model <path_to_model> \
--train \
--training-mode orpo \
--data <path_to_data> \
--beta 0.1
```

Parameters:

- `--beta`: Temperature for logistic function (default: 0.1)

Data format (JSONL):

```jsonl
# Basic format with string responses
{"prompt": "User prompt", "chosen": "Preferred response", "rejected": "Less preferred response"}

# With custom preference score
{"prompt": "User prompt", "chosen": "Preferred response", "rejected": "Less preferred response", "preference_score": 8.0}

# With system message
{"prompt": "User prompt", "chosen": "Preferred response", "rejected": "Less preferred response", "system": "System instruction"}

# With full conversation objects
{
"prompt": "User prompt",
"chosen": {
"messages": [
{"role": "system", "content": "System instruction"},
{"role": "user", "content": "User message"},
{"role": "assistant", "content": "Assistant response"}
]
},
"rejected": {
"messages": [
{"role": "system", "content": "System instruction"},
{"role": "user", "content": "User message"},
{"role": "assistant", "content": "Assistant response"}
]
}
}
```

The trainer assigns binary rewards (1.0 chosen, 0.0 rejected) if no explicit rewards provided via `preference_score`.

### Evaluate

Expand Down
10 changes: 9 additions & 1 deletion mlx_lm/examples/lora_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ train: true
# The fine-tuning method: "lora", "dora", or "full".
fine_tune_type: lora

# The training-mode: "normal", or "dpo"
training_mode: normal

# The Optimizer with its possible inputs
optimizer: adamw
# optimizer_config:
Expand Down Expand Up @@ -89,4 +92,9 @@ lora_parameters:
# valid_split: "train[-100:]"
# prompt_feature: "text"
# completion_feature: "summary"

# For ORPO training
# prompt_feature: "prompt"
# system_feature: "system"
# chosen_feature: "chosen"
# rejected_feature: "rejected"
# preference_score_feature: "preference_score"
129 changes: 99 additions & 30 deletions mlx_lm/lora.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from .tuner.callbacks import WandBCallback
from .tuner.datasets import CacheDataset, load_dataset
from .tuner.orpo_trainer import ORPOTrainingArgs, evaluate_orpo, train_orpo
from .tuner.trainer import TrainingArgs, TrainingCallback, evaluate, train
from .tuner.utils import (
build_schedule,
Expand Down Expand Up @@ -42,6 +43,7 @@
"model": "mlx_model",
"train": False,
"fine_tune_type": "lora",
"training_mode": "normal",
"optimizer": "adam",
"optimizer_config": {
"adam": {},
Expand All @@ -68,6 +70,9 @@
"lora_parameters": {"rank": 8, "dropout": 0.0, "scale": 10.0},
"mask_prompt": False,
"wandb": None,
# ORPO args
"beta": 0.1,
"reward_scaling": 1.0,
}


Expand Down Expand Up @@ -100,6 +105,12 @@ def build_parser():
choices=["lora", "dora", "full"],
help="Type of fine-tuning to perform: lora, dora, or full.",
)
parser.add_argument(
"--training-mode",
type=str,
choices=["normal", "dpo", "orpo"],
help="Training mode: normal, DPO or ORPO.",
)
parser.add_argument(
"--optimizer",
type=str,
Expand Down Expand Up @@ -186,6 +197,20 @@ def build_parser():
help="WandB project name to report training metrics. Disabled if None.",
)
parser.add_argument("--seed", type=int, help="The PRNG seed")

# ORPO args
parser.add_argument(
"--beta",
type=float,
help="Temperature parameter for ORPO training.",
default=0.1,
)
parser.add_argument(
"--reward-scaling",
type=float,
help="Reward scaling factor for ORPO training, not implemented.",
default=1.0,
)
return parser


Expand Down Expand Up @@ -231,18 +256,7 @@ def train_model(
adapter_file = adapter_path / "adapters.safetensors"
save_config(vars(args), adapter_path / "adapter_config.json")

# init training args
training_args = TrainingArgs(
batch_size=args.batch_size,
iters=args.iters,
val_batches=args.val_batches,
steps_per_report=args.steps_per_report,
steps_per_eval=args.steps_per_eval,
steps_per_save=args.save_every,
adapter_file=adapter_file,
max_seq_length=args.max_seq_length,
grad_checkpoint=args.grad_checkpoint,
)
model.train()

# Initialize the selected optimizer
lr = build_schedule(args.lr_schedule) if args.lr_schedule else args.learning_rate
Expand All @@ -259,29 +273,84 @@ def train_model(

opt = opt_class(learning_rate=lr, **optimizer_config)

# Train model
train(
model=model,
args=training_args,
optimizer=opt,
train_dataset=CacheDataset(train_set),
val_dataset=CacheDataset(valid_set),
training_callback=training_callback,
)
if args.training_mode == "orpo":
training_args = ORPOTrainingArgs(
batch_size=args.batch_size,
iters=args.iters,
val_batches=args.val_batches,
steps_per_report=args.steps_per_report,
steps_per_eval=args.steps_per_eval,
steps_per_save=args.save_every,
adapter_file=adapter_file,
max_seq_length=args.max_seq_length,
grad_checkpoint=args.grad_checkpoint,
beta=args.beta,
reward_scaling=args.reward_scaling,
)

train_orpo(
model=model,
optimizer=opt,
train_dataset=CacheDataset(train_set),
val_dataset=CacheDataset(valid_set),
args=training_args,
training_callback=training_callback,
)
else:
training_args = TrainingArgs(
batch_size=args.batch_size,
iters=args.iters,
val_batches=args.val_batches,
steps_per_report=args.steps_per_report,
steps_per_eval=args.steps_per_eval,
steps_per_save=args.save_every,
adapter_file=adapter_file,
max_seq_length=args.max_seq_length,
grad_checkpoint=args.grad_checkpoint,
)

train(
model=model,
args=training_args,
optimizer=opt,
train_dataset=CacheDataset(train_set),
val_dataset=CacheDataset(valid_set),
training_callback=training_callback,
)


def evaluate_model(args, model: nn.Module, test_set):
test_loss = evaluate(
model=model,
dataset=CacheDataset(test_set),
batch_size=args.batch_size,
num_batches=args.test_batches,
max_seq_length=args.max_seq_length,
)
model.eval()

if args.training_mode == "orpo":
test_loss, test_rewards, _, test_metrics = evaluate_orpo(
model=model,
dataset=test_set,
batch_size=args.batch_size,
num_batches=args.test_batches,
max_seq_length=args.max_seq_length,
beta=args.beta,
)
test_ppl = math.exp(test_loss)
print(
f"Test loss {test_loss:.3f}, Test ppl {test_ppl:.3f}, Rewards: {test_rewards[0]:.3f}, {test_rewards[1]:.3f}"
)

print("ORPO Test Metrics:")
for metric_name, metric_value in test_metrics.items():
print(f" {metric_name}: {float(metric_value):.3f}")
else:
test_loss = evaluate(
model=model,
dataset=test_set,
batch_size=args.batch_size,
num_batches=args.test_batches,
max_seq_length=args.max_seq_length,
)

test_ppl = math.exp(test_loss)
test_ppl = math.exp(test_loss)

print(f"Test loss {test_loss:.3f}, Test ppl {test_ppl:.3f}.")
print(f"Test loss {test_loss:.3f}, Test ppl {test_ppl:.3f}.")


def run(args, training_callback: TrainingCallback = None):
Expand Down
Loading