Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34940c5607 | |||
| 78be1bc89e | |||
| 07be2b51cf | |||
| d6d5d80431 |
@@ -168,6 +168,12 @@ def build_parser():
|
|||||||
type=int,
|
type=int,
|
||||||
help="Maximum sequence length.",
|
help="Maximum sequence length.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--seq-step-size",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c",
|
"-c",
|
||||||
"--config",
|
"--config",
|
||||||
@@ -238,6 +244,7 @@ def train_model(
|
|||||||
adapter_file=adapter_file,
|
adapter_file=adapter_file,
|
||||||
max_seq_length=args.max_seq_length,
|
max_seq_length=args.max_seq_length,
|
||||||
grad_checkpoint=args.grad_checkpoint,
|
grad_checkpoint=args.grad_checkpoint,
|
||||||
|
seq_step_size=args.seq_step_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the selected optimizer
|
# Initialize the selected optimizer
|
||||||
|
|||||||
+74
-9
@@ -12,12 +12,21 @@ import mlx.core as mx
|
|||||||
import mlx.nn as nn
|
import mlx.nn as nn
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from mlx.nn.utils import average_gradients
|
from mlx.nn.utils import average_gradients
|
||||||
from mlx.utils import tree_flatten
|
from mlx.utils import tree_flatten, tree_map
|
||||||
from transformers import PreTrainedTokenizer
|
from transformers import PreTrainedTokenizer
|
||||||
|
|
||||||
|
from ..models.cache import KVCache, make_prompt_cache
|
||||||
from .datasets import CacheDataset
|
from .datasets import CacheDataset
|
||||||
|
|
||||||
|
|
||||||
|
def reset_prompt_cache(cache):
|
||||||
|
for e, c in enumerate(cache):
|
||||||
|
if isinstance(c, KVCache):
|
||||||
|
cache[e] = KVCache()
|
||||||
|
else:
|
||||||
|
raise ValueError("Unsupported cache")
|
||||||
|
|
||||||
|
|
||||||
def grad_checkpoint(layer):
|
def grad_checkpoint(layer):
|
||||||
"""
|
"""
|
||||||
Update all instances of type(layer) to use gradient checkpointing.
|
Update all instances of type(layer) to use gradient checkpointing.
|
||||||
@@ -65,16 +74,21 @@ class TrainingArgs:
|
|||||||
default=False,
|
default=False,
|
||||||
metadata={"help": "Use gradient checkpointing to reduce memory use."},
|
metadata={"help": "Use gradient checkpointing to reduce memory use."},
|
||||||
)
|
)
|
||||||
|
seq_step_size: Optional[int] = field(
|
||||||
|
default=None,
|
||||||
|
metadata={"help": "The examples are processsed in seq_step_size chunks."},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def default_loss(model, batch, lengths):
|
def default_loss(model, batch, lengths, cache=None):
|
||||||
inputs = batch[:, :-1]
|
inputs = batch[:, :-1]
|
||||||
targets = batch[:, 1:]
|
targets = batch[:, 1:]
|
||||||
|
|
||||||
logits = model(inputs)
|
offset = cache[0].offset if cache is not None else 0
|
||||||
|
logits = model(inputs, cache=cache)
|
||||||
logits = logits.astype(mx.float32)
|
logits = logits.astype(mx.float32)
|
||||||
|
|
||||||
steps = mx.arange(1, targets.shape[1] + 1)
|
steps = mx.arange(1, targets.shape[1] + 1) + offset
|
||||||
mask = mx.logical_and(steps >= lengths[:, 0:1], steps <= lengths[:, 1:])
|
mask = mx.logical_and(steps >= lengths[:, 0:1], steps <= lengths[:, 1:])
|
||||||
|
|
||||||
ce = nn.losses.cross_entropy(logits, targets) * mask
|
ce = nn.losses.cross_entropy(logits, targets) * mask
|
||||||
@@ -160,6 +174,7 @@ def evaluate(
|
|||||||
max_seq_length=2048,
|
max_seq_length=2048,
|
||||||
loss: callable = default_loss,
|
loss: callable = default_loss,
|
||||||
iterate_batches: callable = iterate_batches,
|
iterate_batches: callable = iterate_batches,
|
||||||
|
seq_step_size: Optional[int] = None,
|
||||||
):
|
):
|
||||||
model.eval()
|
model.eval()
|
||||||
all_losses = mx.array(0.0)
|
all_losses = mx.array(0.0)
|
||||||
@@ -167,6 +182,9 @@ def evaluate(
|
|||||||
|
|
||||||
index_iterator = iter(range(num_batches)) if num_batches != -1 else iter(int, 1)
|
index_iterator = iter(range(num_batches)) if num_batches != -1 else iter(int, 1)
|
||||||
|
|
||||||
|
seq_step_size = seq_step_size or max_seq_length
|
||||||
|
|
||||||
|
cache = make_prompt_cache(model)
|
||||||
for _, batch in zip(
|
for _, batch in zip(
|
||||||
index_iterator,
|
index_iterator,
|
||||||
iterate_batches(
|
iterate_batches(
|
||||||
@@ -176,10 +194,15 @@ def evaluate(
|
|||||||
max_seq_length=max_seq_length,
|
max_seq_length=max_seq_length,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
losses, toks = loss(model, *batch)
|
seq_length = batch[0].shape[1]
|
||||||
all_losses += losses * toks
|
for s in range(0, seq_length, seq_step_size):
|
||||||
ntokens += toks
|
local_batch = (batch[0][:, s : s + seq_step_size], batch[1])
|
||||||
mx.eval(all_losses, ntokens)
|
losses, toks = loss(model, *local_batch, cache)
|
||||||
|
all_losses += losses * toks
|
||||||
|
ntokens += toks
|
||||||
|
if s + seq_step_size >= seq_length:
|
||||||
|
reset_prompt_cache(cache)
|
||||||
|
mx.eval(all_losses, ntokens)
|
||||||
|
|
||||||
all_losses = mx.distributed.all_sum(all_losses, stream=mx.cpu)
|
all_losses = mx.distributed.all_sum(all_losses, stream=mx.cpu)
|
||||||
ntokens = mx.distributed.all_sum(ntokens, stream=mx.cpu)
|
ntokens = mx.distributed.all_sum(ntokens, stream=mx.cpu)
|
||||||
@@ -220,6 +243,8 @@ def train(
|
|||||||
if args.grad_checkpoint:
|
if args.grad_checkpoint:
|
||||||
grad_checkpoint(model.layers[0])
|
grad_checkpoint(model.layers[0])
|
||||||
|
|
||||||
|
seq_step_size = args.seq_step_size or args.max_seq_length
|
||||||
|
cache = make_prompt_cache(model)
|
||||||
state = [model.state, optimizer.state, mx.random.state]
|
state = [model.state, optimizer.state, mx.random.state]
|
||||||
|
|
||||||
@partial(mx.compile, inputs=state, outputs=state)
|
@partial(mx.compile, inputs=state, outputs=state)
|
||||||
@@ -241,6 +266,40 @@ def train(
|
|||||||
loss_value_and_grad = nn.value_and_grad(model, loss)
|
loss_value_and_grad = nn.value_and_grad(model, loss)
|
||||||
|
|
||||||
model.train()
|
model.train()
|
||||||
|
seq_step_size = args.seq_step_size or args.max_seq_length
|
||||||
|
|
||||||
|
def seq_split_step(batch):
|
||||||
|
losses = mx.array(0.0)
|
||||||
|
n_tokens = mx.array(0.0)
|
||||||
|
seq_length = batch[0].shape[1]
|
||||||
|
grad_accum = None
|
||||||
|
for s in range(0, seq_length, seq_step_size):
|
||||||
|
local_batch = (batch[0][:, s : s + seq_step_size], batch[1])
|
||||||
|
(lvalue, toks), grad = loss_value_and_grad(model, *local_batch, cache)
|
||||||
|
prev_n_tokens = n_tokens
|
||||||
|
losses += toks * lvalue
|
||||||
|
n_tokens += toks
|
||||||
|
|
||||||
|
if grad_accum is None:
|
||||||
|
grad_accum = grad
|
||||||
|
else:
|
||||||
|
scale_g = toks / n_tokens
|
||||||
|
scale_acc = prev_n_tokens / n_tokens
|
||||||
|
grad_accum = tree_map(
|
||||||
|
lambda g, acc: scale_g * g + scale_acc * acc, grad, grad_accum
|
||||||
|
)
|
||||||
|
|
||||||
|
# Let go of the prompt cache before the last eval
|
||||||
|
if s + seq_step_size >= seq_length:
|
||||||
|
reset_prompt_cache(cache)
|
||||||
|
mx.eval(grad_accum, losses, n_tokens)
|
||||||
|
|
||||||
|
grad_accum = average_gradients(grad_accum)
|
||||||
|
optimizer.update(model, grad_accum)
|
||||||
|
return losses / n_tokens, n_tokens
|
||||||
|
|
||||||
|
loss_value_and_grad = nn.value_and_grad(model, loss)
|
||||||
|
|
||||||
losses = 0
|
losses = 0
|
||||||
n_tokens = 0
|
n_tokens = 0
|
||||||
steps = 0
|
steps = 0
|
||||||
@@ -271,6 +330,7 @@ def train(
|
|||||||
num_batches=args.val_batches,
|
num_batches=args.val_batches,
|
||||||
max_seq_length=args.max_seq_length,
|
max_seq_length=args.max_seq_length,
|
||||||
iterate_batches=iterate_batches,
|
iterate_batches=iterate_batches,
|
||||||
|
seq_step_size=seq_step_size,
|
||||||
)
|
)
|
||||||
model.train()
|
model.train()
|
||||||
val_time = time.perf_counter() - tic
|
val_time = time.perf_counter() - tic
|
||||||
@@ -292,11 +352,16 @@ def train(
|
|||||||
|
|
||||||
tic = time.perf_counter()
|
tic = time.perf_counter()
|
||||||
|
|
||||||
lvalue, toks = step(batch)
|
if batch[0].shape[1] > seq_step_size:
|
||||||
|
lvalue, toks = seq_split_step(batch)
|
||||||
|
else:
|
||||||
|
lvalue, toks = step(batch)
|
||||||
|
|
||||||
losses += lvalue
|
losses += lvalue
|
||||||
n_tokens += toks
|
n_tokens += toks
|
||||||
steps += 1
|
steps += 1
|
||||||
mx.eval(state, losses, n_tokens)
|
mx.eval(state, losses, n_tokens)
|
||||||
|
|
||||||
train_time += time.perf_counter() - tic
|
train_time += time.perf_counter() - tic
|
||||||
|
|
||||||
# Report training loss if needed
|
# Report training loss if needed
|
||||||
|
|||||||
@@ -370,11 +370,10 @@ class TestScheduleConfig(unittest.TestCase):
|
|||||||
mock_iterate_batches = MagicMock()
|
mock_iterate_batches = MagicMock()
|
||||||
|
|
||||||
mock_iterate_batches.return_value = [
|
mock_iterate_batches.return_value = [
|
||||||
(MagicMock(), MagicMock()),
|
(mx.ones((2, 8), mx.int32), mx.ones((2, 2), mx.int32)),
|
||||||
(MagicMock(), MagicMock()),
|
(mx.ones((2, 8), mx.int32), mx.ones((2, 2), mx.int32)),
|
||||||
(MagicMock(), MagicMock()),
|
(mx.ones((2, 8), mx.int32), mx.ones((2, 2), mx.int32)),
|
||||||
(MagicMock(), MagicMock()),
|
(mx.ones((2, 8), mx.int32), mx.ones((2, 2), mx.int32)),
|
||||||
(MagicMock(), MagicMock()),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
mock_default_loss.side_effect = [
|
mock_default_loss.side_effect = [
|
||||||
@@ -412,9 +411,9 @@ class TestScheduleConfig(unittest.TestCase):
|
|||||||
mock_iterate_batches = MagicMock()
|
mock_iterate_batches = MagicMock()
|
||||||
|
|
||||||
mock_iterate_batches.return_value = [
|
mock_iterate_batches.return_value = [
|
||||||
(MagicMock(), MagicMock()),
|
(mx.ones((2, 8), mx.int32), mx.ones((2, 2), mx.int32)),
|
||||||
(MagicMock(), MagicMock()),
|
(mx.ones((2, 8), mx.int32), mx.ones((2, 2), mx.int32)),
|
||||||
(MagicMock(), MagicMock()),
|
(mx.ones((2, 8), mx.int32), mx.ones((2, 2), mx.int32)),
|
||||||
]
|
]
|
||||||
|
|
||||||
mock_default_loss.side_effect = [
|
mock_default_loss.side_effect = [
|
||||||
|
|||||||
Reference in New Issue
Block a user