meepsquared 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. meepsquared/__init__.py +39 -0
  2. meepsquared/__main__.py +10 -0
  3. meepsquared/_allocator.py +91 -0
  4. meepsquared/_banner.py +51 -0
  5. meepsquared/_batches.py +364 -0
  6. meepsquared/_checkpointing.py +605 -0
  7. meepsquared/_compile.py +165 -0
  8. meepsquared/_data_checks.py +211 -0
  9. meepsquared/_fp8.py +289 -0
  10. meepsquared/_fused.py +243 -0
  11. meepsquared/_geglu_tanh.py +93 -0
  12. meepsquared/_grouped.py +444 -0
  13. meepsquared/_liger.py +61 -0
  14. meepsquared/_linear_attention.py +66 -0
  15. meepsquared/_moe.py +228 -0
  16. meepsquared/_save.py +278 -0
  17. meepsquared/_selective.py +238 -0
  18. meepsquared/_sessions.py +453 -0
  19. meepsquared/_split.py +83 -0
  20. meepsquared/_trl.py +305 -0
  21. meepsquared/_unified_memory.py +112 -0
  22. meepsquared/_varlen.py +241 -0
  23. meepsquared/_vendor/__init__.py +0 -0
  24. meepsquared/_vendor/axolotl/LICENSE +202 -0
  25. meepsquared/_vendor/axolotl/README.md +19 -0
  26. meepsquared/_vendor/axolotl/__init__.py +0 -0
  27. meepsquared/_vendor/axolotl/geglu.py +176 -0
  28. meepsquared/_vendor/axolotl/lora.py +2450 -0
  29. meepsquared/_vendor/axolotl/op_registry.py +66 -0
  30. meepsquared/_vendor/axolotl/quantize.py +263 -0
  31. meepsquared/_vendor/axolotl/swiglu.py +179 -0
  32. meepsquared/_vendor/axolotl/utils.py +12 -0
  33. meepsquared/_vllm.py +152 -0
  34. meepsquared/chat_templates.py +224 -0
  35. meepsquared/cli.py +93 -0
  36. meepsquared/job.py +1933 -0
  37. meepsquared/models.py +510 -0
  38. meepsquared/multi_gpu.py +35 -0
  39. meepsquared-0.1.0.dist-info/METADATA +273 -0
  40. meepsquared-0.1.0.dist-info/RECORD +44 -0
  41. meepsquared-0.1.0.dist-info/WHEEL +4 -0
  42. meepsquared-0.1.0.dist-info/entry_points.txt +2 -0
  43. meepsquared-0.1.0.dist-info/licenses/LICENSE +201 -0
  44. meepsquared-0.1.0.dist-info/licenses/NOTICE +20 -0
@@ -0,0 +1,39 @@
1
+ # Copyright Alejandro Martínez Corriá and the Thinkube contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Fast LoRA and QLoRA fine-tuning on Transformers, PEFT and TRL.
5
+
6
+ import meepsquared as m2
7
+ job = m2.TrainingJob(model="org/model", data="org/dataset", method="lora", steps=1000)
8
+ print(job.plan())
9
+ job.train().save("out")
10
+
11
+ The calls code coming from Unsloth uses stay (see meepsquared.models):
12
+
13
+ model, tokenizer = m2.MeepLanguageModel.from_pretrained("org/model", load_in_4bit=True)
14
+ model = m2.MeepLanguageModel.get_peft_model(model, r=16)
15
+
16
+ Importing meepsquared makes TRL's SFTTrainer and GRPOTrainer take their settings
17
+ from a meepsquared model, unless their config sets them (see meepsquared._trl), and
18
+ makes GRPO's weight updates reach vLLM under their checkpoint names (see meepsquared._vllm).
19
+ It also sets PyTorch's CUDA allocator to expandable segments, unless the
20
+ allocator is already set (see meepsquared._allocator), and registers the attention
21
+ implementation "meepsquared_varlen" with Transformers (see meepsquared._varlen).
22
+ """
23
+
24
+ from meepsquared import _allocator
25
+
26
+ _allocator.apply() # before anything below can start CUDA
27
+
28
+ from meepsquared import _trl, _varlen, _vllm # noqa: E402
29
+ from meepsquared.job import Overrides, Plan, Recipe, Result, TrainingJob # noqa: E402
30
+ from meepsquared.models import MeepLanguageModel, MeepModel # noqa: E402
31
+ from meepsquared.multi_gpu import fsdp_config # noqa: E402
32
+
33
+ __version__ = "0.1.0"
34
+ __all__ = ["TrainingJob", "Recipe", "Overrides", "Plan", "Result", "MeepLanguageModel", "MeepModel",
35
+ "fsdp_config", "__version__"]
36
+
37
+ _trl.patch_trainers()
38
+ _varlen.register()
39
+ _vllm.patch_vllm_generation()
@@ -0,0 +1,10 @@
1
+ # Copyright Alejandro Martínez Corriá and the Thinkube contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """python -m meepsquared: the meepsquared command (see meepsquared.cli)."""
5
+
6
+ import sys
7
+
8
+ from meepsquared.cli import main
9
+
10
+ sys.exit(main())
@@ -0,0 +1,91 @@
1
+ # Copyright Alejandro Martínez Corriá and the Thinkube contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """PyTorch's CUDA allocator with expandable segments.
5
+
6
+ PyTorch keeps freed GPU memory in fixed-size segments for reuse. A training
7
+ whose tensors change shape from step to step (padding-free batches of
8
+ changing length, the routed rows of a mixture of experts) leaves those
9
+ segments in pieces that the next request does not fit, so memory that is free
10
+ cannot be used. With expandable segments a segment grows in place instead,
11
+ which keeps the reserved memory close to what is in use; auto checkpointing
12
+ counts reserved but unused memory as free, so it also reads truer.
13
+
14
+ meepsquared turns it on when it is imported, unless something else decided:
15
+ - before CUDA starts, through PyTorch's own setting, PYTORCH_ALLOC_CONF;
16
+ - once CUDA has started (importing TRL or bitsandbytes can start it), through
17
+ torch.cuda.memory._set_allocator_settings, which changes the setting for the
18
+ segments the allocator creates from then on (vLLM switches expandable
19
+ segments with it);
20
+ - not when PYTORCH_ALLOC_CONF or PYTORCH_CUDA_ALLOC_CONF is already set: the
21
+ user's setting stands;
22
+ - not on Windows or under WSL, where PyTorch does not support it, nor on an
23
+ NVIDIA Jetson board, whose driver refuses the virtual-memory calls it is
24
+ built on. The DGX Spark's GB10 supports them.
25
+ The decision and its reason are kept in DECISION and printed in the banner.
26
+ """
27
+
28
+ import os
29
+ import sys
30
+
31
+ VARIABLES = ("PYTORCH_ALLOC_CONF", "PYTORCH_CUDA_ALLOC_CONF")
32
+ SETTING = "expandable_segments:True"
33
+ JETSON_RELEASE_FILE = "/etc/nv_tegra_release"
34
+
35
+ DECISION = "not decided"
36
+
37
+
38
+ def _cuda_started() -> bool:
39
+ torch = sys.modules.get("torch")
40
+ if torch is None:
41
+ return False
42
+ try:
43
+ return bool(torch.cuda.is_initialized())
44
+ except AttributeError:
45
+ return False
46
+
47
+
48
+ def _unsupported_platform() -> str:
49
+ """Why this platform cannot use expandable segments, or "" when it can."""
50
+ if os.name == "nt":
51
+ return "Windows"
52
+ try:
53
+ if "microsoft" in os.uname().release.lower():
54
+ return "WSL"
55
+ except AttributeError:
56
+ pass
57
+ if os.path.exists(JETSON_RELEASE_FILE):
58
+ return "a Jetson board"
59
+ return ""
60
+
61
+
62
+ def _set_running_allocator(setting: str) -> None:
63
+ import torch
64
+
65
+ torch.cuda.memory._set_allocator_settings(setting)
66
+
67
+
68
+ def decide(environ=None, cuda_started=None, unsupported=None, set_running=None) -> str:
69
+ """Turn expandable segments on where that is right; returns what was decided, and why.
70
+
71
+ The keyword arguments are for tests; by default the process's environment,
72
+ CUDA state and platform are read, and a running allocator is changed in place.
73
+ """
74
+ environ = os.environ if environ is None else environ
75
+ cuda_started = _cuda_started() if cuda_started is None else cuda_started
76
+ unsupported = _unsupported_platform() if unsupported is None else unsupported
77
+ set_running = _set_running_allocator if set_running is None else set_running
78
+ given = [name for name in VARIABLES if name in environ]
79
+ if given:
80
+ return f"as set in {given[0]}"
81
+ if unsupported:
82
+ return f"off (not supported on {unsupported})"
83
+ environ["PYTORCH_ALLOC_CONF"] = SETTING
84
+ if cuda_started:
85
+ set_running(SETTING)
86
+ return "expandable segments"
87
+
88
+
89
+ def apply() -> None:
90
+ global DECISION
91
+ DECISION = decide()
meepsquared/_banner.py ADDED
@@ -0,0 +1,51 @@
1
+ # Copyright Alejandro Martínez Corriá and the Thinkube contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """The banner from_pretrained prints once a model is loaded: what meepsquared will
5
+ train it with. MEEPSQUARED_QUIET=1 in the environment turns it off."""
6
+
7
+ import os
8
+
9
+ MARK = ["╭────────────╮ ", "│ meep meep! │ ", "╰──┬─────────╯ ", " ╵ ∘ ○ · · ·"]
10
+
11
+
12
+ def _gpu() -> str:
13
+ import torch
14
+
15
+ if not torch.cuda.is_available():
16
+ return "none"
17
+ major, minor = torch.cuda.get_device_capability(0)
18
+ return f"{torch.cuda.get_device_name(0)} (sm_{major}{minor})"
19
+
20
+
21
+ def _allocator() -> str:
22
+ from meepsquared import _allocator as allocator
23
+
24
+ return allocator.DECISION
25
+
26
+
27
+ def _versions() -> str:
28
+ import torch
29
+ import transformers
30
+ import trl
31
+
32
+ return f"PyTorch {torch.__version__.split('+')[0]}. Transformers {transformers.__version__}. TRL {trl.__version__}"
33
+
34
+
35
+ def text(version: str, model_type: str, weights: str, liger: bool, padding_free: bool, padding_free_reason: str) -> str:
36
+ padding = "on" if padding_free else f"off ({padding_free_reason})"
37
+ lines = [
38
+ f"Meep² {version}: fast LoRA and QLoRA on Transformers, PEFT and TRL",
39
+ f"GPU: {_gpu()}, allocator: {_allocator()}. {_versions()}",
40
+ f"Model: {model_type}, {weights}. Liger: {'on' if liger else 'off'}. Padding-free: {padding}",
41
+ "Apache-2.0: https://github.com/thinkube/meepsquared",
42
+ ]
43
+ return "\n".join(f"{mark} {line}" for mark, line in zip(MARK, lines))
44
+
45
+
46
+ def show(**kwargs) -> None:
47
+ if os.environ.get("MEEPSQUARED_QUIET") == "1":
48
+ return
49
+ from meepsquared import __version__
50
+
51
+ print(text(__version__, **kwargs), flush=True)
@@ -0,0 +1,364 @@
1
+ # Copyright Alejandro Martínez Corriá and the Thinkube contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """The order of SFT's training data, laid out ahead as the micro-batches the training runs.
5
+
6
+ meepsquared owns the order of the examples, so that the memory is measured on a
7
+ batch the training runs, and no batch it runs is larger (see
8
+ meepsquared._checkpointing.largest_batch). Each epoch shuffles the examples with
9
+ a generator seeded from the trainer's seed plus the epoch (args.data_seed
10
+ when set, else args.seed, as Transformers' TrainingArguments define them),
11
+ then cuts them into micro-batches in that order, one of two ways:
12
+
13
+ - a number of examples per micro-batch: consecutive examples `micro` at a
14
+ time, the last micro-batch shorter, or dropped with dataloader_drop_last,
15
+ as PyTorch's BatchSampler cuts them;
16
+ - a token budget per micro-batch: consecutive examples grouped until the
17
+ next would take the group over the budget (see groups). A conversation is
18
+ never split, so a group holds at least one, even one longer than the
19
+ budget.
20
+
21
+ The cost of a group is the tokens the GPU computes:
22
+
23
+ - packed (padding-free): the sum of the lengths, as the collator lays the
24
+ conversations end to end in one sequence;
25
+ - padded: the count times the longest, rounded up to the padding multiple,
26
+ as every conversation is padded to the longest one.
27
+
28
+ Under several processes, Accelerate's BatchSamplerShard gives each process
29
+ its share of the micro-batches: one in turn to each process, and with
30
+ even_batches, the end evened out with micro-batches from the start of the
31
+ epoch. meepsquared counts each process's share with that same class.
32
+
33
+ The Trainer counts the micro-batches of an epoch once, from len() before the
34
+ first epoch: ceil(count / accumulation) updates an epoch, `accumulation`
35
+ micro-batches each and the last the rest, until max_steps updates; with
36
+ epochs, max_steps is ceil(epochs x updates an epoch). trained_by() follows
37
+ that count. With a token budget, an epoch's group count can differ from the
38
+ first epoch's by a few: a shorter epoch ends early, a longer one leaves its
39
+ last groups unseen, and trained_by() leaves them out too.
40
+
41
+ install() gives the trainer the layout: its get_train_dataloader is replaced
42
+ by one that passes the layout as the DataLoader's batch sampler. TRL's
43
+ SFTTrainer, on TRL 0.29 and 1.x alike, builds its DataLoader with the
44
+ Trainer's get_train_dataloader.
45
+
46
+ A training resumed from a checkpoint continues at the next micro-batch laid
47
+ out: resume_at() starts the batch sampler in the epoch the training stopped
48
+ in, and the Trainer skips the micro-batches of the steps trained in it, on
49
+ each process its own share.
50
+ """
51
+
52
+ import dataclasses
53
+ import functools
54
+ import itertools
55
+ import math
56
+ import types
57
+ from typing import Optional
58
+
59
+ from torch.utils.data import DataLoader, Sampler
60
+
61
+
62
+ def lengths(dataset, max_length: int) -> list:
63
+ """The tokens of each example of a tokenized datasets.Dataset, cut to max_length as the collator cuts them."""
64
+ import datasets
65
+ import pyarrow.compute as pc
66
+
67
+ if not isinstance(dataset, datasets.Dataset):
68
+ raise TypeError(f"meepsquared lays out the batches of a datasets.Dataset with tokenized examples, not "
69
+ f"{type(dataset).__name__}")
70
+ if "input_ids" not in dataset.column_names:
71
+ raise ValueError(f"meepsquared lays out the batches from the tokenized examples ('input_ids'); the dataset has "
72
+ f"{dataset.column_names}")
73
+ counted = pc.list_value_length(dataset.with_format("arrow")["input_ids"]).to_pylist()
74
+ return [min(n, max_length) for n in counted]
75
+
76
+
77
+ def groups(lengths: list, order, budget: int, padded: bool, multiple: int = 1) -> list:
78
+ """Consecutive examples of `order`, grouped while their cost stays within `budget`; one at least per group."""
79
+ out, group, longest, total = [], [], 0, 0
80
+ for index in order:
81
+ length = lengths[index]
82
+ if group:
83
+ if padded:
84
+ cost = (len(group) + 1) * math.ceil(max(longest, length) / multiple) * multiple
85
+ else:
86
+ cost = total + length
87
+ if cost > budget:
88
+ out.append(group)
89
+ group, longest, total = [], 0, 0
90
+ group.append(index)
91
+ longest, total = max(longest, length), total + length
92
+ if group:
93
+ out.append(group)
94
+ return out
95
+
96
+
97
+ def shuffled(count: int, seed: int, epoch: int) -> list:
98
+ """The examples 0 to count - 1 in the order of `epoch`: a permutation drawn by a generator seeded seed + epoch."""
99
+ import torch
100
+
101
+ return torch.randperm(count, generator=torch.Generator().manual_seed(seed + epoch)).tolist()
102
+
103
+
104
+ def held_out(count: int, share: float, seed: int) -> tuple:
105
+ """(trained, held out): the examples 0 to count - 1 split for evaluation, each part in the data's order.
106
+
107
+ The held out are the first round(share x count) of a permutation drawn by
108
+ a generator seeded with `seed`, at least one; at least one is trained.
109
+ """
110
+ import torch
111
+
112
+ held = max(1, round(share * count))
113
+ if held >= count:
114
+ raise ValueError(f"eval_share {share:g} of {count} examples holds out {held}, and leaves none to train on")
115
+ chosen = set(torch.randperm(count, generator=torch.Generator().manual_seed(seed))[:held].tolist())
116
+ return [i for i in range(count) if i not in chosen], sorted(chosen)
117
+
118
+
119
+ def cut(order: list, micro: int, drop_last: bool) -> list:
120
+ """Consecutive examples of `order`, `micro` at a time; a shorter last one kept unless drop_last."""
121
+ batches = [order[start:start + micro] for start in range(0, len(order), micro)]
122
+ if drop_last and batches and len(batches[-1]) < micro:
123
+ batches.pop()
124
+ return batches
125
+
126
+
127
+ def padding(args) -> tuple:
128
+ """(padded, multiple) from TRL's SFTConfig: whether batches are padded, and to what multiple of tokens (1: none)."""
129
+ padded = not args.padding_free
130
+ return padded, args.pad_to_multiple_of if padded and args.pad_to_multiple_of is not None else 1
131
+
132
+
133
+ @dataclasses.dataclass(frozen=True)
134
+ class Loading:
135
+ """What decides the order of a trainer's micro-batches besides the batch: its seed, how it pads and drops, and how
136
+ Accelerate shares the micro-batches among its processes."""
137
+
138
+ seed: int
139
+ drop_last: bool
140
+ padded: bool
141
+ multiple: int
142
+ processes: int
143
+ split_batches: bool
144
+ even_batches: bool
145
+
146
+
147
+ def loading(trainer) -> Loading:
148
+ """The Loading of a built SFT trainer."""
149
+ args, accelerator = trainer.args, trainer.accelerator
150
+ if accelerator.dispatch_batches:
151
+ raise ValueError("accelerator_config dispatch_batches=True: the main process would load every batch and "
152
+ "split it among the processes; meepsquared lays out each process's micro-batches, leave it unset")
153
+ if accelerator.num_processes > 1 and not accelerator.even_batches:
154
+ raise ValueError("accelerator_config even_batches=False: the processes would train different numbers of "
155
+ "micro-batches; meepsquared lays out even shares, leave it unset")
156
+ padded, multiple = padding(args)
157
+ # TrainingArguments: data_seed, "if not set, random generators for data sampling will use the same seed as seed"
158
+ seed = args.data_seed if args.data_seed is not None else args.seed
159
+ return Loading(seed, bool(args.dataloader_drop_last), padded, multiple, accelerator.num_processes,
160
+ bool(accelerator.split_batches), bool(accelerator.even_batches))
161
+
162
+
163
+ class _Epoch:
164
+ """One epoch's micro-batches, as a batch sampler for Accelerate's BatchSamplerShard."""
165
+
166
+ def __init__(self, batches: list, batch_size: Optional[int], drop_last: bool):
167
+ self.batches, self.batch_size, self.drop_last = batches, batch_size, drop_last
168
+
169
+ def __len__(self) -> int:
170
+ return len(self.batches)
171
+
172
+ def __iter__(self):
173
+ return iter(self.batches)
174
+
175
+
176
+ def share(batches: list, batch_size: Optional[int], drop_last: bool, loading: Loading, process: int) -> tuple:
177
+ """(count, micro-batches): what one process trains of an epoch's micro-batches, as Accelerate's
178
+ BatchSamplerShard gives them; the count is its len(), which the Trainer counts by."""
179
+ epoch = _Epoch(batches, batch_size, drop_last)
180
+ if loading.processes == 1:
181
+ return len(epoch), list(epoch)
182
+ from accelerate.data_loader import BatchSamplerShard
183
+
184
+ shard = BatchSamplerShard(epoch, num_processes=loading.processes, process_index=process,
185
+ split_batches=loading.split_batches, even_batches=loading.even_batches)
186
+ return len(shard), list(shard)
187
+
188
+
189
+ @dataclasses.dataclass(frozen=True)
190
+ class Layout:
191
+ """The micro-batches of every epoch a training runs, and what it trains of them.
192
+
193
+ epochs: each epoch's micro-batches before Accelerate shares them out, lists
194
+ of example indices. batch_size: the examples of a micro-batch, None with a
195
+ token budget. per_epoch: the micro-batches of an epoch on each process, as
196
+ the Trainer counts them. steps: the updates the training makes. trained:
197
+ the micro-batches the training runs, on every process.
198
+ """
199
+
200
+ epochs: tuple
201
+ batch_size: Optional[int]
202
+ loading: Loading
203
+ examples: int
204
+ per_epoch: int
205
+ steps: int
206
+ trained: tuple
207
+
208
+
209
+ def lay_out(lengths: list, loading: Loading, accumulation: int, steps: Optional[int], epochs: Optional[float],
210
+ micro: Optional[int] = None, budget: Optional[int] = None, kinds: Optional[list] = None) -> Layout:
211
+ """The micro-batches of a training of `steps` updates, or `epochs` (one when neither), `accumulation`
212
+ micro-batches an update; each micro-batch `micro` examples, or as many as fit `budget` tokens.
213
+
214
+ With `kinds` (each example's), each micro-batch holds examples of one
215
+ kind: the epoch's order is cut kind by kind, and its micro-batches are
216
+ then shuffled by the same generator, so that the kinds mix in the order.
217
+ """
218
+ if (micro is None) == (budget is None):
219
+ raise ValueError("a layout takes micro (examples per micro-batch) or budget (tokens per micro-batch), "
220
+ "one of them")
221
+ if not lengths:
222
+ raise ValueError("the training data has no examples")
223
+
224
+ def batches_of(order: list) -> list:
225
+ if micro is not None:
226
+ return cut(order, micro, loading.drop_last)
227
+ return groups(lengths, order, budget, loading.padded, loading.multiple)
228
+
229
+ def epoch(index: int) -> list:
230
+ if kinds is None:
231
+ return batches_of(shuffled(len(lengths), loading.seed, index))
232
+ import torch
233
+
234
+ generator = torch.Generator().manual_seed(loading.seed + index)
235
+ order = torch.randperm(len(lengths), generator=generator).tolist()
236
+ made = []
237
+ for kind in sorted(set(kinds)):
238
+ made += batches_of([i for i in order if kinds[i] == kind])
239
+ return [made[i] for i in torch.randperm(len(made), generator=generator).tolist()]
240
+
241
+ drop_last = loading.drop_last if micro is not None else False
242
+ first = epoch(0)
243
+ per_epoch = share(first, micro, drop_last, loading, 0)[0]
244
+ if per_epoch == 0:
245
+ raise ValueError(f"the training data's {len(lengths)} examples make no micro-batch on each of "
246
+ f"{loading.processes} processes")
247
+ updates = max(math.ceil(per_epoch / accumulation), 1)
248
+ max_steps = steps if steps is not None else math.ceil((epochs if epochs is not None else 1) * updates)
249
+ count = math.ceil(max_steps / updates) if steps is not None else math.ceil(epochs if epochs is not None else 1)
250
+ laid = [first] + [epoch(index) for index in range(1, count)]
251
+ trained, made = [], None
252
+ for process in range(loading.processes):
253
+ taken, made = trained_by([share(batches, micro, drop_last, loading, process)[1] for batches in laid],
254
+ per_epoch, accumulation, max_steps)
255
+ trained += taken
256
+ return Layout(tuple(laid), micro, loading, len(lengths), per_epoch, made, tuple(trained))
257
+
258
+
259
+ def trained_by(shares: list, per_epoch: int, accumulation: int, max_steps: int) -> tuple:
260
+ """(micro-batches, updates): what one process trains of its share of each epoch, as the Trainer takes them.
261
+
262
+ An epoch makes ceil(per_epoch / accumulation) updates, of `accumulation`
263
+ micro-batches each and the last of the rest; an epoch whose share runs
264
+ out first ends there, and the training ends after max_steps updates.
265
+ """
266
+ updates = max(math.ceil(per_epoch / accumulation), 1)
267
+ last = per_epoch % accumulation or accumulation
268
+ out, step = [], 0
269
+ for batches in shares:
270
+ taken = iter(batches)
271
+ for update in range(updates):
272
+ pulled = list(itertools.islice(taken, accumulation if update != updates - 1 else last))
273
+ if not pulled:
274
+ break
275
+ out += pulled
276
+ step += 1
277
+ if step >= max_steps:
278
+ return out, step
279
+ return out, step
280
+
281
+
282
+ class LaidOutBatchSampler(Sampler):
283
+ """The micro-batches of a Layout, one epoch per pass: the epoch set_epoch names, else the one after the last
284
+ pass, starting at `epoch`.
285
+
286
+ len() is the first epoch's count, which the Trainer counts every epoch by.
287
+ batch_size None (a token budget): Accelerate's BatchSamplerShard then
288
+ takes the micro-batches as of varying size.
289
+ """
290
+
291
+ def __init__(self, layout: Layout, epoch: int = 0):
292
+ self.layout = layout
293
+ self.batch_size = layout.batch_size
294
+ self.drop_last = layout.loading.drop_last if layout.batch_size is not None else False
295
+ self.epoch = epoch
296
+
297
+ def set_epoch(self, epoch: int) -> None:
298
+ self.epoch = epoch
299
+
300
+ def __len__(self) -> int:
301
+ return len(self.layout.epochs[0])
302
+
303
+ def __iter__(self):
304
+ epoch = self.epoch
305
+ if epoch >= len(self.layout.epochs):
306
+ raise RuntimeError(f"the trainer started epoch {epoch + 1}, and meepsquared laid out the micro-batches of "
307
+ f"{len(self.layout.epochs)} epochs for {self.layout.steps} updates; the trainer's "
308
+ "max_steps or num_train_epochs changed after the plan")
309
+ self.epoch = epoch + 1
310
+ return iter(self.layout.epochs[epoch])
311
+
312
+
313
+ def _get_train_dataloader(trainer, layout: Layout) -> DataLoader:
314
+ """The Trainer's training DataLoader, iterating the micro-batches of `layout`."""
315
+ from transformers.trainer_utils import seed_worker
316
+
317
+ from meepsquared import _data_checks
318
+
319
+ args = trainer.args
320
+ dataset = trainer._remove_unused_columns(trainer.train_dataset, description="Training")
321
+ if len(dataset) != layout.examples:
322
+ raise ValueError(f"the trainer's dataset has {len(dataset)} examples, and the micro-batches were laid out "
323
+ f"for {layout.examples}")
324
+ dataset, collator = _data_checks.weighted_loader(trainer, dataset)
325
+ loader = DataLoader(dataset, batch_sampler=LaidOutBatchSampler(layout, trainer._meepsquared_first_epoch),
326
+ collate_fn=collator,
327
+ num_workers=args.dataloader_num_workers, pin_memory=args.dataloader_pin_memory,
328
+ persistent_workers=args.dataloader_persistent_workers,
329
+ prefetch_factor=args.dataloader_prefetch_factor,
330
+ worker_init_fn=functools.partial(seed_worker, num_workers=args.dataloader_num_workers,
331
+ rank=args.process_index))
332
+ return trainer.accelerator.prepare(loader)
333
+
334
+
335
+ def install(trainer, layout: Layout) -> None:
336
+ """Make the trainer train the micro-batches of `layout`, kept as its _meepsquared_layout; refused when the trainer
337
+ would load them otherwise."""
338
+ if loading(trainer) != layout.loading:
339
+ raise ValueError(f"the micro-batches were laid out for {layout.loading}, and the trainer loads them as "
340
+ f"{loading(trainer)}")
341
+ trainer.get_train_dataloader = types.MethodType(functools.partial(_get_train_dataloader, layout=layout), trainer)
342
+ trainer._meepsquared_layout = layout
343
+ trainer._meepsquared_first_epoch = 0
344
+ trainer._meepsquared_weights = None
345
+
346
+
347
+ def resume_at(trainer, step: int) -> int:
348
+ """Make the trainer's micro-batches start in the epoch a training resumed after `step` updates continues;
349
+ returns that epoch.
350
+
351
+ The Trainer counts the epochs trained as step // ceil(micro-batches an
352
+ epoch / accumulation), and skips the micro-batches of the steps trained
353
+ in that epoch. The batch sampler starts in that epoch itself, as
354
+ Accelerate's set_epoch does not reach it through the skipping sampler
355
+ under several processes.
356
+ """
357
+ layout = trainer._meepsquared_layout
358
+ updates = max(math.ceil(layout.per_epoch / trainer.args.gradient_accumulation_steps), 1)
359
+ epoch = step // updates
360
+ if epoch >= len(layout.epochs):
361
+ raise ValueError(f"resume after {step} updates: epoch {epoch + 1}, and meepsquared laid out the micro-batches of "
362
+ f"{len(layout.epochs)} epochs for {layout.steps} updates")
363
+ trainer._meepsquared_first_epoch = epoch
364
+ return epoch