-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Expand file tree
/
Copy pathsft_trainer.py
More file actions
1863 lines (1676 loc) · 102 KB
/
Copy pathsft_trainer.py
File metadata and controls
1863 lines (1676 loc) · 102 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2020-2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import inspect
import json
import os
import types
import warnings
from collections import defaultdict
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import torch
import torch.nn as nn
import torch.nn.functional as F
import transformers
from accelerate import PartialState
from accelerate.logging import get_logger
from accelerate.utils import is_peft_model
from datasets import Dataset, DatasetDict, IterableDataset, IterableDatasetDict
from packaging.version import Version
from transformers import (
AutoProcessor,
BitsAndBytesConfig,
DataCollator,
PreTrainedModel,
PreTrainedTokenizerBase,
ProcessorMixin,
TrainerCallback,
TrainingArguments,
)
from transformers.data.data_collator import DataCollatorMixin
from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
from transformers.trainer_utils import EvalPrediction
from transformers.utils import is_peft_available
from ..chat_template_utils import (
clone_chat_template,
get_training_chat_template,
has_generation_markers,
is_chat_template_stop_token_trained,
)
from ..data_utils import (
_tokenize,
apply_chat_template,
get_dataset_column_names,
is_conversational,
is_conversational_from_value,
maybe_convert_to_chatml,
pack_dataset,
prepare_multimodal_messages,
)
from ..models import get_act_offloading_ctx_manager
from .base_trainer import _BaseTrainer
from .sft_config import SFTConfig
from .utils import (
create_model_from_path,
entropy_from_logits,
flush_left,
get_config_model_id,
maybe_gather_lm_head_ctx,
pad,
selective_log_softmax,
)
if is_peft_available():
import peft
from peft import PeftConfig, PeftModel, PeftType, get_peft_model
_CHUNKED_LM_HEAD_CHUNK_SIZE = 256
@dataclass
class _ChunkedCELMHeadOutput(CausalLMOutputWithPast):
"""`CausalLMOutputWithPast` with extra fields populated by the chunked-CE path."""
num_correct_tokens: torch.Tensor | None = None
entropy_sum: torch.Tensor | None = None
num_valid_tokens: torch.Tensor | None = None
aux_loss: torch.Tensor | None = None
def _chunk(h, w, b, lbl, logit_scale, final_logit_softcapping):
with maybe_gather_lm_head_ctx(w, b):
logits = h.float() @ w.float().t()
if b is not None:
logits = logits + b.float()
if logit_scale != 1.0:
logits = logits * logit_scale
if final_logit_softcapping is not None:
logits = final_logit_softcapping * torch.tanh(logits / final_logit_softcapping)
log_p = F.log_softmax(logits, dim=-1)
# A chunk's tail may be `-100` padding: `ignore_index` zeroes their loss; `valid` does the same for accuracy/entropy.
chunk_loss = F.nll_loss(log_p, lbl, ignore_index=-100, reduction="sum")
valid = lbl != -100
chunk_correct = ((logits.argmax(dim=-1) == lbl) & valid).sum().float()
chunk_entropy = (-(log_p.exp() * log_p).sum(dim=-1) * valid).sum()
return chunk_loss, chunk_correct, chunk_entropy
def _chunked_cross_entropy_loss(
hidden_states: torch.Tensor,
lm_head_weight: torch.Tensor,
chunk_size: int,
labels: torch.Tensor | None = None,
shift_labels: torch.Tensor | None = None,
num_items_in_batch: torch.Tensor | int | None = None,
logit_scale: float = 1.0,
final_logit_softcapping: float | None = None,
lm_head_bias: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Memory-efficient next-token cross-entropy over hidden states and an `lm_head` weight.
The full `lm_head` projection is never materialized. Valid (non-`-100`) tokens are packed to the front (via
`argsort` on the label mask, a static-shape op) and processed in chunks of `chunk_size`, rounding the count up to a
whole chunk so masked positions land in a skippable tail. Each chunk's `[chunk_size, vocab_size]` logits are kept
alive only during its own forward/backward via gradient checkpointing, so peak logits memory is `chunk_size *
vocab_size` instead of `batch_size * seq_len * vocab_size`. Quantizing the chunk count to a multiple of
`chunk_size` keeps this XLA/Neuron-safe (at most `total / chunk_size` distinct traced shapes, not one per
valid-token count) while still dropping fully-masked chunks on GPU.
At least one of `labels` or `shift_labels` must be provided. `labels` triggers the internal `labels[..., 1:]` /
`hidden_states[..., :-1, :]` shift; `shift_labels` skips it, assuming the caller already aligned labels with hidden
states (the contract under context / sequence parallelism). If both are given, `shift_labels` wins (matching
[`~transformers.loss.ForCausalLMLoss`]).
Args:
hidden_states (`torch.Tensor`):
Base decoder output of shape `(B, S, H)`, i.e. before the `lm_head` projection.
lm_head_weight (`torch.Tensor`):
Weight of the `lm_head` linear layer, shape `(V, H)`.
chunk_size (`int`):
Number of valid tokens processed per chunk. Peak memory scales linearly with this.
labels (`torch.Tensor`, *optional*):
Labels of shape `(B, S)`. Positions equal to `-100` are excluded from both the `lm_head` matmul and the
loss. Mutually exclusive with `shift_labels`.
shift_labels (`torch.Tensor`, *optional*):
Pre-shifted labels of shape `(B, S)`, aligned with `hidden_states` (position `i` predicts
`shift_labels[i]`). Mutually exclusive with `labels`.
num_items_in_batch (`torch.Tensor`, `int` or `None`, *optional*):
Total number of valid tokens across the global batch, as plumbed by [`~transformers.Trainer`]. When
provided, the loss is reduced as `sum / num_items_in_batch`, matching the gradient-accumulation-correct
behavior of HF's default cross-entropy. When `None`, reduction is `mean` over local valid tokens.
logit_scale (`float`, *optional*, defaults to `1.0`):
Multiplier applied to each chunk's logits before the cross-entropy, matching the `logit_scale` behavior of
Cohere-style models.
final_logit_softcapping (`float`, *optional*):
If set, applies `softcap * tanh(logits / softcap)` to each chunk's logits before the cross-entropy,
matching the `final_logit_softcapping` behavior of Gemma-style models. Applied after `logit_scale`.
lm_head_bias (`torch.Tensor`, *optional*):
Bias of the `lm_head` linear layer, shape `(V,)`. Added to each chunk's logits when provided.
Returns:
`tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]`: scalar loss, number of correctly-predicted
tokens (count), sum of per-token Shannon entropy (in nats), and number of valid (non-`-100`) target tokens —
all over the local batch. Raw sums are returned so callers can reduce correctly across ranks.
"""
if labels is None and shift_labels is None:
raise ValueError("At least one of `labels` or `shift_labels` must be provided.")
if shift_labels is not None:
hidden = hidden_states.reshape(-1, hidden_states.size(-1))
labels = shift_labels.reshape(-1)
else:
hidden = hidden_states[..., :-1, :].reshape(-1, hidden_states.size(-1))
labels = labels[..., 1:].reshape(-1)
valid = labels != -100
n_valid_tensor = valid.sum()
correct = hidden.new_zeros((), dtype=torch.float32)
entropy_sum = hidden.new_zeros((), dtype=torch.float32)
if n_valid_tensor == 0:
# Whole micro-batch masked (e.g. completion-only loss + truncation). Keep the loss connected
# to the autograd graph through every trainable parameter so `.backward()` succeeds and DDP /
# FSDP gradient sync doesn't hang on a missing param.
with maybe_gather_lm_head_ctx(lm_head_weight, lm_head_bias):
loss = (hidden_states.float().sum() + lm_head_weight.float().sum()) * 0.0
if lm_head_bias is not None:
loss = loss + lm_head_bias.float().sum() * 0.0
return loss, correct, entropy_sum, n_valid_tensor
# Pack valid tokens to the front so masked positions form whole trailing chunks. `argsort` on the boolean mask is
# a static-shape op (unlike `hidden[valid]`, whose output shape is data-dependent and poisons XLA compilation).
order = valid.to(torch.int8).argsort(descending=True, stable=True)
hidden = hidden[order]
labels = labels[order]
# Process only the whole chunks covering the valid prefix: bounds XLA recompiles and drops fully-masked chunks on GPU.
n_padded = (n_valid_tensor / chunk_size).ceil().to(torch.int64) * chunk_size
loss = hidden.new_zeros((), dtype=torch.float32)
for start in range(0, n_padded, chunk_size):
h_chunk = hidden[start : start + chunk_size]
lbl_chunk = labels[start : start + chunk_size]
chunk_loss, chunk_correct, chunk_entropy = torch.utils.checkpoint.checkpoint(
_chunk,
h_chunk,
lm_head_weight,
lm_head_bias,
lbl_chunk,
logit_scale,
final_logit_softcapping,
use_reentrant=False,
)
loss = loss + chunk_loss
correct = correct + chunk_correct
entropy_sum = entropy_sum + chunk_entropy
if num_items_in_batch is None:
loss = loss / n_valid_tensor
else:
if isinstance(num_items_in_batch, torch.Tensor):
num_items_in_batch = num_items_in_batch.to(loss.device)
loss = loss / num_items_in_batch
return loss, correct, entropy_sum, n_valid_tensor
def _patch_chunked_ce_lm_head(model: torch.nn.Module, chunk_size: int, is_vlm: bool = False) -> None:
"""
Patch `model.forward` to compute the LM loss via [`_chunked_cross_entropy_loss`].
When `labels` (or pre-shifted `shift_labels`, for CP/SP) are provided, the patched forward runs the decoder up to
`last_hidden_state` (skipping the `lm_head` matmul), drops `labels == -100` positions, and computes the
cross-entropy in chunks of `chunk_size` valid tokens. Returns a [`_ChunkedCELMHeadOutput`] with `loss` set,
`logits=None`, and `num_correct_tokens` / `entropy_sum` / `num_valid_tokens` over non-ignored tokens. For MoE
models (`output_router_logits=True`), the load-balancing aux loss is added with the same coefficient and formula as
the model's reference forward.
Without labels, the original forward runs unchanged — generation and labels-free eval preserve any per-model logits
post-processing (`logit_scale`, `final_logit_softcapping`, `logits_to_keep` slicing).
Args:
model (`torch.nn.Module`):
Model to patch. For PEFT, pass `peft_model.get_base_model()` rather than the `PeftModel` wrapper, so
prompt-learning variants (PromptTuning, PrefixTuning, PTuning) keep their virtual-token injection in
`PeftModel.forward` before delegating into the patched forward.
chunk_size (`int`):
Number of valid tokens processed per CE chunk.
is_vlm (`bool`):
Set to `True` for VLMs. Only used to read `logit_scale` / `final_logit_softcapping` /
`output_router_logits` from `model.config.text_config` instead of the top-level config.
"""
# VLM scaling configs (`logit_scale`, `final_logit_softcapping`, MoE `output_router_logits`) live on `text_config`;
# text-only models keep them on the top-level config.
text_config = model.config.text_config if is_vlm else model.config
final_logit_softcapping = getattr(text_config, "final_logit_softcapping", None)
logit_scale = getattr(text_config, "logit_scale", 1.0)
original_forward = model.forward
lm_head = model.get_output_embeddings()
def _chunked_ce_forward(
self: torch.nn.Module,
input_ids: torch.Tensor | None = None,
attention_mask: torch.Tensor | None = None,
labels: torch.Tensor | None = None,
num_items_in_batch: torch.Tensor | int | None = None,
shift_labels: torch.Tensor | None = None,
output_router_logits: bool | None = None,
**kwargs,
) -> CausalLMOutputWithPast:
# Without labels, fall back to the original forward so generation and labels-free evaluation
# preserve any per-model logits post-processing (e.g. Cohere `logit_scale`, Gemma
# `final_logit_softcapping`, `logits_to_keep` slicing).
if labels is None and shift_labels is None:
# MoE models: request router logits so the model returns `outputs.aux_loss`. VLM wrappers honor this only
# as a forward kwarg (not from the model config), so it must be passed here.
if output_router_logits is not None:
kwargs["output_router_logits"] = output_router_logits
return original_forward(input_ids=input_ids, attention_mask=attention_mask, **kwargs)
if output_router_logits is None:
output_router_logits = getattr(text_config, "output_router_logits", False)
kwargs.pop("use_cache", None)
decoder_kwargs = {}
# MoE models: request router logits so the model returns `outputs.aux_loss`. VLM wrappers honor this only
# as a forward kwarg (not from the model config), so it must be passed here.
if output_router_logits:
decoder_kwargs["output_router_logits"] = True
# `base_model` gives the backbone model (skipping `lm_head`) — text decoder for LMs, multimodal wrapper
# for VLMs (so vision-token injection runs before the text decoder). `get_decoder()` won't do: on VLMs it
# returns just the text stack and feeds image-placeholder IDs through it.
# Pre-5.0 transformers VLMs set `base_model_prefix = ""` so `self.base_model is self` (re-runs `lm_head`).
# Fall back to `self.model` there.
if is_vlm and Version(transformers.__version__) < Version("5.0.0"):
backbone = self.model
else:
backbone = self.base_model
outputs: BaseModelOutputWithPast = backbone(
input_ids=input_ids, attention_mask=attention_mask, use_cache=False, **decoder_kwargs, **kwargs
)
hidden_states = outputs.last_hidden_state
lm_head_weight = lm_head.weight
lm_head_bias = lm_head.bias
# Under FSDP2, lm_head.weight is a DTensor (Shard(0) or Replicate). Passing it directly
# into the gradient-checkpointed chunk loop causes FSDP2 to re-gather it once per chunk
# during backward recomputation. full_tensor() converts it to a plain tensor once; all
# chunks reference that tensor, so only one all-gather occurs (in full_tensor()'s backward).
if isinstance(lm_head_weight, torch.distributed.tensor.DTensor):
lm_head_weight = lm_head_weight.full_tensor()
if lm_head_bias is not None:
lm_head_bias = lm_head_bias.full_tensor()
loss, num_correct_tokens, entropy_sum, num_valid_tokens = _chunked_cross_entropy_loss(
hidden_states,
lm_head_weight,
chunk_size,
labels=labels,
shift_labels=shift_labels,
num_items_in_batch=num_items_in_batch,
logit_scale=logit_scale,
final_logit_softcapping=final_logit_softcapping,
lm_head_bias=lm_head_bias,
)
aux_loss = None
if output_router_logits:
# Mirror the per-family MoE forward: add `router_aux_loss_coef * load_balancing_loss_func(...)` to
# the main loss. Mixtral is the source of truth — every MoE family (Qwen3Moe, GptOss, OLMoE,
# Qwen2Moe, DBRX, JetMoE, PhiMoE, …) pulls this function from mixtral via the modular system, so a
# single import keeps us in lockstep with upstream for every family we test.
from transformers.models.mixtral.modeling_mixtral import load_balancing_loss_func
if Version(transformers.__version__) < Version("5.0.0") and not is_vlm:
num_experts = self.num_experts
num_experts_per_tok = self.num_experts_per_tok
router_aux_loss_coef = self.router_aux_loss_coef
else:
# Upstream bug AttributeError: 'GptOssConfig' object has no attribute 'num_experts'; see #5754
if text_config.model_type == "gpt_oss" and Version("5.0.0") <= Version(
transformers.__version__
) < Version("5.6.0"):
num_experts = self.num_experts
else:
num_experts = text_config.num_experts
num_experts_per_tok = text_config.num_experts_per_tok
router_aux_loss_coef = text_config.router_aux_loss_coef
aux_loss = load_balancing_loss_func(
outputs.router_logits, num_experts, num_experts_per_tok, attention_mask
)
loss = loss + router_aux_loss_coef * aux_loss.to(loss.device)
return _ChunkedCELMHeadOutput(
loss=loss,
logits=None,
past_key_values=outputs.past_key_values,
hidden_states=outputs.hidden_states,
attentions=outputs.attentions,
num_correct_tokens=num_correct_tokens,
entropy_sum=entropy_sum,
num_valid_tokens=num_valid_tokens,
aux_loss=aux_loss,
)
# Keep the original forward signature so `generate`'s `_validate_model_kwargs` still sees the
# model's real inputs (e.g. VLM `pixel_values`, `spatial_shapes`) and doesn't reject them. The
# unbound `__func__` signature makes `MethodType`'s `self`-stripping land correctly.
_chunked_ce_forward.__signature__ = inspect.signature(original_forward.__func__)
model.forward = types.MethodType(_chunked_ce_forward, model)
logger = get_logger(__name__)
FLASH_ATTENTION_VARIANTS = {
"flash_attention_2",
"flash_attention_3",
"kernels-community/flash-attn2",
"kernels-community/flash-attn3",
"kernels-community/vllm-flash-attn3",
}
@dataclass
class DataCollatorForLanguageModeling(DataCollatorMixin):
"""
Data collator used for language modeling data. Inputs are dynamically padded to the maximum length of a batch.
This collator expects each example in the input list to be a dictionary containing at least the `"input_ids"` key.
If the input contains `"labels"`, they are used as is (padded like the input IDs); otherwise the labels default to
the input IDs. Tokens that shouldn't contribute to the loss are expected to be already set to `-100` in the labels;
the [`SFTTrainer`] takes care of this during dataset preparation. The collator returns a dictionary containing the
following keys:
- `"input_ids"`: Tensor of input IDs, padded to the maximum length of the batch.
- `"labels"`: Tensor of labels, padded with `-100` to the maximum length of the batch. If `padding_free` is set
to `False`, the following key is also returned:
- `"attention_mask"`: Tensor of attention masks, padded to the maximum length of the batch.
If `padding_free` is set to `True`, the following key is also returned:
- `"position_ids"`: Tensor of position IDs, padded to the maximum length of the batch.
Args:
pad_token_id (`int`):
Token ID to use for padding.
padding_free (`bool`, *optional*, defaults to `False`):
If set to `True`, the sequences will be flattened into a single sequence, and the position IDs will be
generated accordingly and returned instead of the attention mask.
pad_to_multiple_of (`int`, *optional*):
If set, the sequences will be padded to a multiple of this value.
return_tensors (`str`, *optional*, defaults to `"pt"`):
Type of Tensor to return. Only `"pt"` is currently supported.
Examples:
```python
>>> from trl.trainer.sft_trainer import DataCollatorForLanguageModeling
>>> collator = DataCollatorForLanguageModeling(pad_token_id=0)
>>> examples = [{"input_ids": [1, 2, 3]}, {"input_ids": [4, 5]}]
>>> collator(examples)
{'input_ids': tensor([[ 1, 2, 3],
[ 4, 5, 0]]),
'attention_mask': tensor([[ 1, 1, 1],
[ 1, 1, 0]]),
'labels': tensor([[ 1, 2, 3],
[ 4, 5, -100]])}
>>> # With prebuilt labels
>>> examples = [
... {"input_ids": [1, 2, 3], "labels": [-100, 2, 3]},
... {"input_ids": [4, 5], "labels": [-100, 5]},
... ]
>>> collator(examples)
{'input_ids': tensor([[ 1, 2, 3],
[ 4, 5, 0]]),
'attention_mask': tensor([[ 1, 1, 1],
[ 1, 1, 0]]),
'labels': tensor([[-100, 2, 3],
[-100, 5, -100]])}
>>> # With padding_free
>>> collator = DataCollatorForLanguageModeling(pad_token_id=0, padding_free=True)
>>> collator(examples)
{'input_ids': tensor([[ 1, 2, 3, 4, 5]]),
'position_ids': tensor([[0, 1, 2, 0, 1]]),
'labels': tensor([[-100, 2, 3, -100, 5]])}
```
"""
pad_token_id: int
padding_free: bool = False
pad_to_multiple_of: int | None = None
return_tensors: str = "pt"
def torch_call(self, examples: list[dict[str, Any]]) -> dict[str, Any]:
input_ids = [example["input_ids"] for example in examples]
batch_seq_lengths = [example["seq_lengths"] for example in examples] if "seq_lengths" in examples[0] else None
labels = [example.get("labels", example["input_ids"]) for example in examples]
# Convert to tensor
input_ids = [torch.tensor(ids) for ids in input_ids]
labels = [torch.tensor(lbl) for lbl in labels]
# For padding-free, we should NOT create attention_mask as it causes FlashAttention to ignore position_ids and
# compute wrong cu_seq_lens from the all-1s mask
if self.padding_free:
if batch_seq_lengths is not None:
position_ids = self.get_position_ids_from_packed_seq_lengths(batch_seq_lengths)
else:
position_ids = [torch.arange(len(ids)) for ids in input_ids]
else:
attention_mask = [torch.ones_like(ids) for ids in input_ids]
# If padding_free, flatten everything into a single sequence
output = {}
if self.padding_free:
input_ids = [torch.cat(input_ids, dim=0)]
labels = [torch.cat(labels, dim=0)]
position_ids = [torch.cat(position_ids, dim=0)]
# Pad
output["input_ids"] = pad(
input_ids,
padding_value=self.pad_token_id,
padding_side="right",
pad_to_multiple_of=self.pad_to_multiple_of,
)
output["labels"] = pad(
labels, padding_value=-100, padding_side="right", pad_to_multiple_of=self.pad_to_multiple_of
)
if self.padding_free:
output["position_ids"] = pad(
position_ids, padding_value=0, padding_side="right", pad_to_multiple_of=self.pad_to_multiple_of
)
output["labels"][output["position_ids"] == 0] = -100
else:
output["attention_mask"] = pad(
attention_mask, padding_value=0, padding_side="right", pad_to_multiple_of=self.pad_to_multiple_of
)
return output
@staticmethod
def get_position_ids_from_packed_seq_lengths(batch_seq_lengths: list[list[int]]) -> list[torch.Tensor]:
"""
Get position IDs for packed sequences.
Args:
batch_seq_lengths (`list[list[int]]`):
A list of lists containing the lengths of each individual document in the packed batch.
Return:
`list[torch.Tensor]`:
A list of tensors containing the position IDs for each packed sequence.
"""
# Get lengths per row
example_lengths = [sum(seq_lengths) for seq_lengths in batch_seq_lengths]
# Flat list of lengths
batch_seq_lengths = torch.tensor(
[seq_length for seq_lengths in batch_seq_lengths for seq_length in seq_lengths]
)
position_ids = torch.ones(sum(example_lengths), dtype=batch_seq_lengths.dtype)
position_ids[0] = 0
# Reset position ids to 0 at the start of each sequence
position_ids[batch_seq_lengths[:-1].cumsum(0)] = -(batch_seq_lengths[:-1] - 1)
position_ids = position_ids.cumsum(0)
# Split back into one tensor per example
return list(position_ids.split(example_lengths))
@dataclass
class DataCollatorForVisionLanguageModeling(DataCollatorMixin):
"""
Data collator for vision-language modeling tasks.
Unlike text-only datasets, where the collator typically receives pre-tokenized inputs ready for batching,
vision-language data processing involves converting images into pixel values. This conversion is disk-intensive,
making upfront preprocessing of the entire dataset impractical. Therefore, this collator performs tokenization and
image processing on-the-fly to efficiently prepare batches.
Each input example should be a dictionary containing at least:
- An `"images"` key holding a list of images, or an `"image"` key holding a single image.
- [language modeling](#language-modeling) type: either a `"messages"` key for conversational inputs or a `"text"`
key for standard text inputs.
- [prompt-completion](#prompt-completion) type: keys `"prompt"` and `"completion"` for the prompt and completion.
The collator outputs a dictionary including:
- `"input_ids"`: Tensor of token IDs.
- `"attention_mask"`: Tensor indicating attention mask.
- `"pixel_values"`: Tensor representing image pixel values.
- `"labels"`: Tensor for training labels.
Additional keys may be present depending on the processor, such as `"image_grid_thw"` or `"image_position_ids"`.
Args:
processor ([`~transformers.ProcessorMixin`]):
The processor used to tokenize text and process images. It must be a subclass of
[`~transformers.ProcessorMixin`] and include a `tokenizer` with a defined `pad_token_id`.
max_length (`int`, *optional*):
Maximum sequence length. Sequences longer than `max_length` are truncated to `max_length`. If `None`, no
truncation is applied.
completion_only_loss (`bool`, *optional*, defaults to `False`):
Whether to compute loss only on the completion part of the sequence. When `True`, the labels for the prompt
part are set to -100. It requires the dataset type to be prompt-completion.
pad_to_multiple_of (`int`, *optional*):
If set, the sequences will be padded to a multiple of this value.
dataset_text_field (`str`, *optional*, defaults to `"text"`):
Name of the column that contains text data in the dataset. This parameter is only relevant for [standard
datasets format](dataset_formats#standard).
return_tensors (`str`, *optional*, defaults to `"pt"`):
Type of Tensor to return. Only `"pt"` is currently supported.
Example:
```python
>>> from trl.trainer.sft_trainer import DataCollatorForVisionLanguageModeling
>>> from transformers import AutoProcessor
>>> processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct")
>>> collator = DataCollatorForVisionLanguageModeling(processor)
>>> examples = [
... {"images": [Image.open("image_0.png")], "messages": [{"role": "user", "content": "What is this?"}]},
... {"images": [Image.open("image_1.png")], "messages": [{"role": "user", "content": "Describe this image."}]},
... ]
>>> collator(examples)
{'input_ids': tensor([[151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198,
151644, 872, 198, 151652, 151655, 151655, 151655, 151655, 151653, 3838, 374,
419, 30, 151645, 198],
[151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198,
151644, 872, 198, 151652, 151655, 151655, 151655, 151655, 151653, 74785, 419,
2168, 13, 151645, 198]]),
'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]),
'pixel_values': tensor([[-0.9893, 0.1785, 1.5362, ..., -0.0582, 0.8661, -0.2431],
[-0.2302, 0.9522, -1.1061, ..., 0.0555, 1.3354, -0.6412],
[ 1.2150, 0.9084, 0.7041, ..., 0.2404, -0.8403, -0.5133],
...,
[ 0.6895, 0.2807, 0.2515, ..., -0.2004, -1.2100, 0.0555],
[ 0.8209, -0.9748, 1.5654, ..., 1.6055, -0.4706, 0.5817],
[-1.0915, 0.4559, 0.9230, ..., 0.5106, 0.0982, -0.1720]]),
'image_grid_thw': tensor([[1, 4, 4],
[1, 4, 4]]),
'labels': tensor([[151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198,
151644, 872, 198, 151652, 151655, 151655, 151655, 151655, 151653, 3838, 374,
419, 30, 151645, 198],
[151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198,
151644, 872, 198, 151652, 151655, 151655, 151655, 151655, 151653, 74785, 419,
2168, 13, 151645, 198]])}
```
"""
processor: ProcessorMixin
max_length: int | None = None
completion_only_loss: bool = False # default not used in practice; SFTTrainer always passes the relevant value
pad_to_multiple_of: int | None = None
dataset_text_field: str = "text"
return_tensors: str = "pt"
def torch_call(self, examples: list[dict[str, Any]]) -> dict[str, Any]:
if "messages" in examples[0] or self.dataset_text_field in examples[0]:
if self.completion_only_loss:
raise ValueError(
"The `completion_only_loss` argument is not supported for language modeling datasets."
)
return self._collate_language_modeling(examples)
elif "prompt" in examples[0] and "completion" in examples[0]:
return self._collate_prompt_completion(examples)
else:
raise KeyError(f"Unexpected input keys in examples: {list(examples[0].keys())}.")
def _collate_language_modeling(self, examples: list[dict[str, Any]]) -> dict[str, Any]:
if "image" in examples[0]:
for example in examples:
example["images"] = [example.pop("image")]
images = [example["images"] for example in examples]
# Transformers requires at least one image in the batch, otherwise it throws an error
if all(img_list == [] for img_list in images):
images = None
if "messages" in examples[0]: # conversational case
messages = [
prepare_multimodal_messages(example["messages"], images=example["images"]) for example in examples
]
texts = self.processor.apply_chat_template(messages)
elif self.dataset_text_field in examples[0]: # standard case
texts = [example[self.dataset_text_field] for example in examples]
else:
raise KeyError(
"The input examples must contain either 'messages' for conversational data or 'text' for standard "
"data."
)
output = self.processor(
images=images,
text=texts,
padding=True,
padding_side="right",
pad_to_multiple_of=self.pad_to_multiple_of,
truncation=self.max_length is not None,
max_length=self.max_length,
return_tensors=self.return_tensors,
add_special_tokens=False, # to avoid adding the BOS twice, see https://huggingface.co/blog/qgallouedec/gotchas-in-tokenizer-behavior#7-chat-template-and-tokenization-dont-compose-due-to-special-tokens
)
labels = output["input_ids"].clone()
labels[output["attention_mask"] == 0] = -100
# We mask only padding tokens (-100) in the labels. Vision tokens are left unchanged because their handling in
# loss computation has to be done by the model, and masking them here would be infeasible in practice as vision
# token definitions vary across architectures.
output["labels"] = labels
return output
def _collate_prompt_completion(self, examples: list[dict[str, Any]]) -> dict[str, Any]:
if self.pad_to_multiple_of is not None:
raise NotImplementedError(
"Padding to a multiple of a value is not yet implemented for vision-language modeling and "
"prompt-completion data."
)
if "image" in examples[0]:
for example in examples:
example["images"] = [example.pop("image")]
images = [example["images"] for example in examples]
# Transformers requires at least one image in the batch, otherwise it throws an error
if all(img_list == [] for img_list in images):
images = None
if is_conversational(examples[0]): # conversational case
for example in examples:
example["prompt"] = prepare_multimodal_messages(example["prompt"], images=example["images"])
example["completion"] = prepare_multimodal_messages(example["completion"])
examples = [apply_chat_template(example, self.processor) for example in examples]
prompts = [example["prompt"] for example in examples]
completions = [example["completion"] for example in examples]
processed_prompts = self.processor(
images=images,
text=prompts,
padding=True,
padding_side="left",
return_tensors=self.return_tensors,
add_special_tokens=False, # to avoid adding the BOS twice, see https://huggingface.co/blog/qgallouedec/gotchas-in-tokenizer-behavior#7-chat-template-and-tokenization-dont-compose-due-to-special-tokens
)
processed_completions = self.processor(
text=completions,
padding=True,
padding_side="right",
return_tensors=self.return_tensors,
add_special_tokens=False, # to avoid adding the BOS twice, see https://huggingface.co/blog/qgallouedec/gotchas-in-tokenizer-behavior#7-chat-template-and-tokenization-dont-compose-due-to-special-tokens
)
# Concatenate prompts and completions
prompt_ids, prompt_mask = processed_prompts["input_ids"], processed_prompts["attention_mask"]
completion_ids, completion_mask = processed_completions["input_ids"], processed_completions["attention_mask"]
input_ids = torch.cat((prompt_ids, completion_ids), dim=1)
attention_mask = torch.cat((prompt_mask, completion_mask), dim=1)
completion_mask = torch.cat((torch.zeros_like(prompt_mask), completion_mask), dim=1)
if "token_type_ids" in processed_prompts: # special case for Gemma
prompt_token_type_ids = processed_prompts["token_type_ids"]
completion_token_type_ids = processed_completions["token_type_ids"]
token_type_ids = torch.cat((prompt_token_type_ids, completion_token_type_ids), dim=1)
if "mm_token_type_ids" in processed_prompts: # special case for Qwen2.5-VL
prompt_mm_token_type_ids = processed_prompts["mm_token_type_ids"]
mm_token_type_ids = torch.cat((prompt_mm_token_type_ids, torch.zeros_like(completion_ids)), dim=1)
# Flush left to reduce padding
if "token_type_ids" in processed_prompts and "mm_token_type_ids" in processed_prompts:
attention_mask, input_ids, completion_mask, token_type_ids, mm_token_type_ids = flush_left(
attention_mask, input_ids, completion_mask, token_type_ids, mm_token_type_ids
)
elif "token_type_ids" in processed_prompts:
attention_mask, input_ids, completion_mask, token_type_ids = flush_left(
attention_mask, input_ids, completion_mask, token_type_ids
)
elif "mm_token_type_ids" in processed_prompts:
attention_mask, input_ids, completion_mask, mm_token_type_ids = flush_left(
attention_mask, input_ids, completion_mask, mm_token_type_ids
)
else:
attention_mask, input_ids, completion_mask = flush_left(attention_mask, input_ids, completion_mask)
# Truncate if necessary
if self.max_length is not None:
input_ids = input_ids[:, : self.max_length]
attention_mask = attention_mask[:, : self.max_length]
completion_mask = completion_mask[:, : self.max_length]
if "token_type_ids" in processed_prompts:
token_type_ids = token_type_ids[:, : self.max_length]
if "mm_token_type_ids" in processed_prompts:
mm_token_type_ids = mm_token_type_ids[:, : self.max_length]
# Create labels and mask padding tokens
labels = input_ids.clone()
labels[attention_mask == 0] = -100
if self.completion_only_loss:
labels[completion_mask == 0] = -100
# Build the output dictionary
output = processed_prompts # we take processed_prompts because it contains the images
output["input_ids"] = input_ids
output["attention_mask"] = attention_mask
output["labels"] = labels
if "token_type_ids" in processed_prompts:
output["token_type_ids"] = token_type_ids
if "mm_token_type_ids" in processed_prompts:
output["mm_token_type_ids"] = mm_token_type_ids
return output
def dft_loss(outputs, labels, num_items_in_batch=None):
"""
DFT loss function, as presented in [On the Generalization of SFT: A Reinforcement Learning Perspective with Reward
Rectification](https://huggingface.co/papers/2508.05629)
"""
labels = nn.functional.pad(labels, (0, 1), value=-100)
shift_labels = labels[..., 1:]
loss_mask = shift_labels != -100
shift_labels[~loss_mask] = 0
logprobs = selective_log_softmax(outputs.logits, shift_labels)
per_token_loss = -logprobs.exp().detach() * logprobs
if num_items_in_batch is None:
num_items_in_batch = loss_mask.sum()
loss = (per_token_loss * loss_mask).sum() / num_items_in_batch
return loss
class SFTTrainer(_BaseTrainer):
"""
Trainer for Supervised Fine-Tuning (SFT) method.
This class is a wrapper around the [`~transformers.Trainer`] class and inherits all of its attributes and methods.
Example:
```python
>>> from trl import SFTTrainer
>>> from datasets import load_dataset
>>> dataset = load_dataset("roneneldan/TinyStories", split="train[:1%]")
>>> trainer = SFTTrainer(
... model="Qwen/Qwen2.5-0.5B-Instruct",
... train_dataset=dataset,
... )
>>> trainer.train()
```
Args:
model (`str` or [`~transformers.PreTrainedModel`] or [`~peft.PeftModel`]):
Model to be trained. Can be either:
- A string, being the *model id* of a pretrained model hosted inside a model repo on huggingface.co, or a
path to a *directory* containing model weights saved using
[`~transformers.PreTrainedModel.save_pretrained`], e.g., `'./my_model_directory/'`. The model is loaded
using `<ModelArchitecture>.from_pretrained` (where `<ModelArchitecture>` is derived from the model
config) with the keyword arguments in `args.model_init_kwargs`. If `dtype` is not specified in
`args.model_init_kwargs`, it defaults to `float32`. This differs from
[`~transformers.PreTrainedModel.from_pretrained`], where (since Transformers v5) the dtype is inferred
from the model config.
- A [`~transformers.PreTrainedModel`] object. Only causal language models are supported.
- A [`~peft.PeftModel`] object. Only causal language models are supported.
args ([`SFTConfig`], *optional*):
Configuration for this trainer. If `None`, a default configuration is used.
data_collator ([`~transformers.DataCollator`], *optional*):
Function to use to form a batch from a list of elements of the processed `train_dataset` or `eval_dataset`.
Will default to [`~trainer.sft_trainer.DataCollatorForLanguageModeling`] if the model is a language model
and [`~trainer.sft_trainer.DataCollatorForVisionLanguageModeling`] if the model is a vision-language model.
train_dataset ([`~datasets.Dataset`] or [`~datasets.IterableDataset`]):
Dataset to use for training. This trainer supports both [language modeling](#language-modeling) type and
[prompt-completion](#prompt-completion) type. The format of the samples can be either:
- [Standard](dataset_formats#standard): Each sample contains plain text.
- [Conversational](dataset_formats#conversational): Each sample contains structured messages (e.g., role
and content).
The trainer also supports pre-tokenized datasets, recognized by a required `input_ids` column. An optional
`labels` column (`-100` on tokens excluded from the loss) is used as is if present; otherwise labels are
built from the optional `assistant_masks` / `completion_mask` columns (which are folded in then dropped,
`completion_mask` only when `completion_only_loss=True`), or default to a copy of `input_ids`. Sequences
are truncated to `max_length` during preparation. With `skip_prepare_dataset=True`, preparation is skipped
and the collator is expected to handle the dataset as is.
When `train_dataset` is an [`~datasets.IterableDataset`] (e.g. a streaming dataset), `max_steps` must be
set in the training arguments, since its length cannot be inferred and the total number of training steps
is required to bound the training loop and configure the learning rate scheduler.
eval_dataset ([`~datasets.Dataset`], [`~datasets.IterableDataset`], [`~datasets.DatasetDict`], [`~datasets.IterableDatasetDict`] or `dict[str, Dataset | IterableDataset]`):
Dataset to use for evaluation. It must meet the same requirements as `train_dataset`.
processing_class ([`~transformers.PreTrainedTokenizerBase`], [`~transformers.ProcessorMixin`], *optional*):
Processing class used to process the data. If `None`, the processing class is loaded from the model's name
with [`~transformers.AutoProcessor.from_pretrained`]. A padding token, `tokenizer.pad_token`, must be set.
If the processing class has not set a padding token, `tokenizer.eos_token` will be used as the default.
compute_loss_func (`Callable`, *optional*):
A function that accepts the raw model outputs, labels, and the number of items in the entire accumulated
batch (batch_size * gradient_accumulation_steps) and returns the loss. For example, see the default [loss
function](https://github.com/huggingface/transformers/blob/052e652d6d53c2b26ffde87e039b723949a53493/src/transformers/trainer.py#L3618)
used by [`Trainer`].
compute_metrics (`Callable[[EvalPrediction], dict]`, *optional*):
The function that will be used to compute metrics at evaluation. Must take a
[`~transformers.EvalPrediction`] and return a dictionary string to metric values. When passing
[`SFTConfig`] with `batch_eval_metrics` set to `True`, your `compute_metrics` function must take a boolean
`compute_result` argument. This will be triggered after the last eval batch to signal that the function
needs to calculate and return the global summary statistics rather than accumulating the batch-level
statistics.
callbacks (list of [`~transformers.TrainerCallback`], *optional*):
List of callbacks to customize the training loop. Will add those to the list of default callbacks detailed
in [here](https://huggingface.co/docs/transformers/main_classes/callback).
If you want to remove one of the default callbacks used, use the [`~transformers.Trainer.remove_callback`]
method.
optimizers (`tuple[torch.optim.Optimizer | None, torch.optim.lr_scheduler.LambdaLR | None]`, *optional*, defaults to `(None, None)`):
A tuple containing the optimizer and the scheduler to use. Will default to an instance of `AdamW` on your
model and a scheduler given by [`~transformers.get_linear_schedule_with_warmup`] controlled by `args`.
optimizer_cls_and_kwargs (`tuple[Type[torch.optim.Optimizer], Dict[str, Any]]`, *optional*):
A tuple containing the optimizer class and keyword arguments to use. Overrides `optim` and `optim_args` in
`args`. Incompatible with the `optimizers` argument.
Unlike `optimizers`, this argument avoids the need to place model parameters on the correct devices before
initializing the Trainer.
preprocess_logits_for_metrics (`Callable[[torch.Tensor, torch.Tensor], torch.Tensor]`, *optional*):
A function that preprocess the logits right before caching them at each evaluation step. Must take two
tensors, the logits and the labels, and return the logits once processed as desired. The modifications made
by this function will be reflected in the predictions received by `compute_metrics`.
Note that the labels (second parameter) will be `None` if the dataset does not have them.
quantization_config ([`~transformers.BitsAndBytesConfig`], *optional*):
Quantization configuration used when loading the model from a model identifier. Combine with `peft_config`
for QLoRA training. Ignored if the model is already instantiated.
peft_config ([`~peft.PeftConfig`], *optional*):
PEFT configuration used to wrap the model. If `None`, the model is not wrapped.
formatting_func (`Callable`, *optional*):
Formatting function applied to the dataset before tokenization. Applying the formatting function explicitly
converts the dataset into a [language modeling](#language-modeling) type.
"""
_tag_names = ["trl", "sft"]
_name = "SFT"
def __init__(
self,
model: "str | PreTrainedModel | PeftModel",
args: SFTConfig | TrainingArguments | None = None,
data_collator: DataCollator | None = None,
train_dataset: Dataset | IterableDataset | None = None,
eval_dataset: Dataset
| IterableDataset
| DatasetDict
| IterableDatasetDict
| dict[str, Dataset | IterableDataset]
| None = None,
processing_class: PreTrainedTokenizerBase | ProcessorMixin | None = None,
compute_loss_func: Callable | None = None,
compute_metrics: Callable[[EvalPrediction], dict] | None = None,
callbacks: list[TrainerCallback] | None = None,
optimizers: tuple[torch.optim.Optimizer | None, torch.optim.lr_scheduler.LambdaLR | None] = (None, None),
optimizer_cls_and_kwargs: tuple[type[torch.optim.Optimizer], dict[str, Any]] | None = None,
preprocess_logits_for_metrics: Callable[[torch.Tensor, torch.Tensor], torch.Tensor] | None = None,
quantization_config: "BitsAndBytesConfig | None" = None,
peft_config: "PeftConfig | None" = None,
formatting_func: Callable[[dict], str] | None = None,
):
# Args
if args is None:
model_name = model if isinstance(model, str) else get_config_model_id(model.config)
model_name = model_name.split("/")[-1]
args = SFTConfig(f"{model_name}-SFT")
elif isinstance(args, TrainingArguments) and not isinstance(args, SFTConfig):
dict_args = args.to_dict()
dict_args["hub_token"] = args.hub_token # to_dict hides the hub_token
if Version(transformers.__version__) < Version("5.0.0"):
dict_args.pop("push_to_hub_token")
args = SFTConfig(**dict_args)
if train_dataset is None:
raise ValueError("`train_dataset` is required")
elif isinstance(train_dataset, IterableDataset):
# IterableDataset requires dispatch_batches=False because Accelerate's dispatch mode may try to concatenate
# batches from multiple processes, leading to mismatch errors.
if args.accelerator_config.dispatch_batches is True:
logger.warning(
"You are using an `IterableDataset` for training with `dispatch_batches=True`. `dispatch_batches` "
"is forced to `False` when using an `IterableDataset`. To remove this warning, unset "
"`dispatch_batches` in `SFTConfig` or set it to `False`."
)
args.accelerator_config.dispatch_batches = False
# Model
if isinstance(model, str):
model_init_kwargs = dict(args.model_init_kwargs or {}) # copy to avoid mutating model_init_kwargs
if quantization_config is not None:
if "quantization_config" in model_init_kwargs:
raise ValueError(
"You set `quantization_config` both as a trainer argument and in `args.model_init_kwargs`. "
"Please set it in only one place, preferably as a trainer argument."
)
model_init_kwargs["quantization_config"] = quantization_config
# Distributed training requires device_map=None ("auto" fails)
if args.distributed_state.distributed_type in ["MULTI_GPU", "DEEPSPEED"]:
model_init_kwargs["device_map"] = None
model_init_kwargs.setdefault("trust_remote_code", args.trust_remote_code)
model = create_model_from_path(model, **model_init_kwargs)
else:
if args.model_init_kwargs is not None:
logger.warning(
"You passed `model_init_kwargs` to the `SFTConfig`, but your model is already instantiated. "
"The `model_init_kwargs` will be ignored."
)
if quantization_config is not None:
logger.warning(
"You passed `quantization_config` to the trainer, but your model is already instantiated. The "
"`quantization_config` will be ignored."
)
# Non-quantized models do not have the `is_loaded_in_{8,4}bit` attributes, whereas quantized models do
_is_quantized_model = getattr(model, "is_loaded_in_4bit", False) or getattr(model, "is_loaded_in_8bit", False)
# Processing class
if processing_class is None:
processing_class = AutoProcessor.from_pretrained(
get_config_model_id(model.config), trust_remote_code=args.trust_remote_code
)
# Handle pad token for processors or tokenizers
if isinstance(processing_class, ProcessorMixin):
self._tokenizer = processing_class.tokenizer
self._is_vlm = True
elif isinstance(processing_class, PreTrainedTokenizerBase):
self._tokenizer = processing_class
self._is_vlm = False
else:
raise TypeError("The `processing_class` must be either a `PreTrainedTokenizerBase` or a `ProcessorMixin`")
if args.eos_token is not None:
if args.eos_token not in self._tokenizer.get_vocab():
raise ValueError(
f"The specified `eos_token` ('{args.eos_token}') is not found in the vocabulary of the given "
f"`processing_class` ({processing_class.__class__.__name__}). Ensure that the `eos_token` exists "
"in the vocabulary before using it as an EOS token."
)