Repository navigation
Expand file tree
/
Copy pathtrain_draft.py
More file actions
267 lines (216 loc) · 9.54 KB
/
Copy pathtrain_draft.py
File metadata and controls
267 lines (216 loc) · 9.54 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
"""
train_draft.py — Training script for MYDraft speculative decoding model.
Usage:
torchrun --nproc_per_node=8 train/train_draft.py \
--model_name_or_path ./HunyuanOCR \
--train_data_path ./data/train.json \
--output_dir ./output/draft \
--data_packing True \
--mtp_group 8 \
--mtp_only_labels True \
--num_draft_layers 5 \
--num_mask_tokens 64 \
--bf16 True \
--per_device_train_batch_size 1 \
--gradient_accumulation_steps 4 \
--learning_rate 1e-4 \
--num_train_epochs 3 \
--save_steps 500 \
--logging_steps 10
"""
import logging
import os
import pathlib
import sys
logger = logging.getLogger(__name__)
from pathlib import Path
from typing import Any
import torch
import transformers
from torch import nn
# Add project root to Python path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from transformers import (
AutoProcessor,
AutoTokenizer,
HunYuanVLForConditionalGeneration,
Trainer,
)
from train.argument import (
DataArguments,
DraftArguments,
ModelArguments,
TrainingArguments,
)
from train.data_processor import PackedVLDataCollator, VLDataset
from train.trainer import replace_hunyuanocr_attention_class
transformers.logging.set_verbosity_info()
# from hunyuan_vl import HunYuanVLForConditionalGeneration
# from hunyuan_vl.processing_hunyuan_vl import HunYuanVLProcessor
# from hunyuan_vl.image_processing_hunyuan_vl import HunYuanVLImageProcessor
# from hunyuan_vl.configuration_hunyuan_vl import HunYuanVLConfig
local_rank = None
logging.basicConfig(level=logging.INFO, force=True)
def rank0_print(*args):
if local_rank == 0:
print(*args)
# ============================================================================
# Custom Trainer: capture per-component scalars from MYDraft.forward and feed
# them through Trainer.log() so they get routed to ALL configured reporters
# (swanlab, tensorboard, wandb, ...) automatically via TrainerCallback.
# ============================================================================
class DraftOnlyTrainer(Trainer):
"""Trainer that surfaces ``ce_loss`` / ``distill_loss`` / ``accuracy``
(returned by ``MYDraft.forward`` as a dict in ``outputs["loss"]``) into
the standard HF logging pipeline (swanlab / tensorboard / wandb / ...).
``MYDraft.forward`` now returns a ``CausalLMOutputWithPast`` whose
``loss`` field is itself a ``dict``::
{
"loss": <main scalar used for backprop>,
"ce_loss": <cross-entropy scalar>,
"distill_loss": <distillation scalar>,
"accuracy": <token-level accuracy>,
}
We unpack that dict here, push every entry through ``self.log(...)``
so that all configured reporters (``--report_to swanlab tensorboard``)
see them, and return the main scalar to Trainer for backprop.
"""
def compute_loss(
self,
model: nn.Module,
inputs: dict[str, torch.Tensor | Any],
return_outputs: bool = False,
num_items_in_batch: torch.Tensor | None = None,
):
# Forward pass — outputs["loss"] is a dict of scalars.
outputs = model(**inputs)
loss_dict = outputs["loss"]
# Main loss used for backprop.
loss = loss_dict["loss"]
# Build a logging-only dict (detached, on CPU as python floats).
loss_dict_to_log = {
k: (v.detach().float().mean().item() if isinstance(v, torch.Tensor) else float(v))
for k, v in loss_dict.items()
}
# Push every sub-loss / accuracy into the standard HF logging
# pipeline so swanlab / tensorboard / wandb all pick them up.
self.log(loss_dict_to_log)
if return_outputs:
return loss, outputs
return loss
def safe_save_model_for_hf_trainer(trainer: transformers.Trainer, output_dir: str):
"""Save only the trainable draft parameters."""
if trainer.deepspeed:
torch.cuda.synchronize()
trainer.save_model(output_dir)
return
state_dict = trainer.model.state_dict()
if trainer.args.should_save:
# Only save draft-related parameters (exclude frozen target_model)
draft_state_dict = {
k.replace("draft_model.", ""): v for k, v in state_dict.items() if not k.startswith("target_model.")
}
cpu_state_dict = {k: v.cpu() for k, v in draft_state_dict.items()}
del state_dict
trainer._save(output_dir, state_dict=cpu_state_dict)
def train(attn_implementation="flash_attention_2"):
global local_rank
parser = transformers.HfArgumentParser((ModelArguments, DataArguments, TrainingArguments, DraftArguments))
model_args, data_args, training_args, draft_args = parser.parse_args_into_dataclasses()
local_rank = training_args.local_rank
os.makedirs(training_args.output_dir, exist_ok=True)
training_args.lr_scheduler_kwargs = {"min_lr": 2e-6}
# ── Load processor ────────────────────────────────────────────────────
rank0_print("Loading processor...")
tokenizer = AutoTokenizer.from_pretrained(
model_args.model_name_or_path,
use_fast=False,
)
processor = AutoProcessor.from_pretrained(
model_args.model_name_or_path,
)
# ── Load target model ─────────────────────────────────────────────────
rank0_print(f"Loading target model from {model_args.model_name_or_path} ...")
target_model = HunYuanVLForConditionalGeneration.from_pretrained(
model_args.model_name_or_path,
attn_implementation=attn_implementation,
dtype=torch.bfloat16 if training_args.bf16 else torch.float32,
)
# Replace attention class for packed / flatten training
replace_hunyuanocr_attention_class()
target_model.config.use_cache = False
target_model.config.block_size = draft_args.num_mask_tokens
target_model.config.num_draft_layers = draft_args.num_draft_layers
from train.dflash_draft import MYDraft
model = MYDraft(
config=target_model.config,
target_model=target_model,
use_distill=draft_args.use_distill,
loop_num=draft_args.loop_num,
sample_block_num=draft_args.sample_block_num,
)
model.config.use_cache = False
if draft_args.load_draft_path is not None:
# load safe checkpoint
from safetensors.torch import load_file as safetensors_load_file
draft_state_dict = safetensors_load_file(draft_args.load_draft_path, device="cpu")
# draft_state_dict = torch.load(model_args.load_draft_path, map_location="cpu")
draft_state_dict_new = {}
for k, v in draft_state_dict.items():
if k.startswith("draft_model."):
draft_state_dict_new[k[len("draft_model.") :]] = v
else:
draft_state_dict_new[k] = v
# del draft_state_dict[k]
model.load_state_dict(draft_state_dict_new, strict=False)
rank0_print(f"Loading draft model from {draft_args.load_draft_path} successfully")
# model.load_state_dict()
# gradient_checkpointing is handled inside MYDraft.gradient_checkpointing_enable()
# which disables checkpointing on draft_layers (monkey-patched, incompatible).
# No extra hook needed here.
if local_rank == 0:
model.print_parameter_info()
# ── Load datasets ─────────────────────────────────────────────────────
rank0_print("Loading datasets...")
train_dataset = VLDataset(
data_path=data_args.train_data_path,
image_folder=data_args.image_folder,
image_lmdb_path=data_args.image_lmdb_path,
processor=processor,
max_length=data_args.packed_max_length,
is_packed=data_args.data_flatten or data_args.data_packing,
)
eval_dataset = None
if data_args.eval_data_path:
eval_dataset = VLDataset(
data_path=data_args.eval_data_path,
image_folder=data_args.image_folder,
processor=processor,
max_length=data_args.packed_max_length,
)
# ── Data collator ─────────────────────────────────────────────────────
data_collator = PackedVLDataCollator(
processor=processor,
packed_max_length=data_args.packed_max_length,
)
# ── Trainer ───────────────────────────────────────────────────────────
trainer = DraftOnlyTrainer(
model=model,
processing_class=tokenizer,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
data_collator=data_collator,
)
if list(pathlib.Path(training_args.output_dir).glob("checkpoint-*")):
logger.info("Checkpoint found, resuming training...")
trainer.train(resume_from_checkpoint=True)
else:
trainer.train()
trainer.save_state()
model.config.use_cache = True
safe_save_model_for_hf_trainer(trainer=trainer, output_dir=training_args.output_dir)
processor.save_pretrained(training_args.output_dir)
if __name__ == "__main__":
train(attn_implementation="flash_attention_2")