42e commited on
Commit
5a86450
·
verified ·
1 Parent(s): 67be6b4

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. src/mathcompose/train/train.py +98 -29
src/mathcompose/train/train.py CHANGED
@@ -26,6 +26,50 @@ from pathlib import Path
26
  DEFAULT_CONFIGS = {"v": "configs/verifier_v.yaml", "g": "configs/generator_g.yaml"}
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def build_and_train(args) -> str:
30
  import torch
31
  from datasets import load_dataset
@@ -43,13 +87,28 @@ def build_and_train(args) -> str:
43
  val_file = args.val_file or cfg["data"].get("val_path")
44
 
45
  has_cuda = torch.cuda.is_available()
46
- # bf16 needs Ampere+ (sm_80): A100/L4/H100 yes, T4 (sm_75) no -> use fp16 there.
47
  bf16_ok = has_cuda and torch.cuda.get_device_capability()[0] >= 8
48
- compute_dtype = torch.bfloat16 if bf16_ok else (torch.float16 if has_cuda else torch.float32)
 
 
 
 
 
 
 
 
 
 
 
 
49
  if has_cuda:
50
  cap = torch.cuda.get_device_capability()
51
- print(f"[gpu] {torch.cuda.get_device_name(0)} (sm_{cap[0]}{cap[1]}) -> "
52
- f"{'bf16' if bf16_ok else 'fp16'} compute")
 
 
 
 
53
  use_4bit = (not args.no_4bit) and cfg["quant"]["load_in_4bit"] and has_cuda
54
  if cfg["quant"]["load_in_4bit"] and not has_cuda and not args.no_4bit:
55
  print("[warn] 4-bit requested but no CUDA — falling back to full-precision "
@@ -62,19 +121,18 @@ def build_and_train(args) -> str:
62
  model_kwargs["quantization_config"] = build_bnb_config(cfg, compute_dtype=compute_dtype)
63
  model = AutoModelForCausalLM.from_pretrained(base_id, **model_kwargs)
64
 
65
- if compute_dtype == torch.float16:
66
- # On T4 the fp16 GradScaler can't unscale bf16 grads. transformers may load
67
- # the non-quantized weights in the model's default bf16 regardless of dtype=,
68
- # so LoRA inherits bf16. Force any remaining bf16 params to fp16 and pin the
69
- # config dtype so PEFT doesn't recreate bf16 adapters from the stale config.
70
- model.config.torch_dtype = torch.float16
71
  cast = 0
72
  for p in model.parameters():
73
  if p.dtype == torch.bfloat16:
74
- p.data = p.data.to(torch.float16)
75
  cast += 1
76
  if cast:
77
- print(f"[dtype] cast {cast} bf16 param tensors -> fp16 (T4 AMP compatibility)")
78
 
79
  data_files = {"train": train_file}
80
  if val_file and Path(val_file).exists():
@@ -84,42 +142,51 @@ def build_and_train(args) -> str:
84
  eval_ds = ds.get("validation")
85
 
86
  t = cfg["train"]
87
- # optional CLI overrides (getattr keeps programmatic callers/tests working)
88
- if getattr(args, "epochs", None):
89
- t["epochs"] = args.epochs
90
- if getattr(args, "batch_size", None):
91
- t["per_device_train_batch_size"] = args.batch_size
92
- if getattr(args, "grad_accum", None):
93
- t["gradient_accumulation_steps"] = args.grad_accum
94
- if getattr(args, "max_length", None):
95
- t["max_length"] = args.max_length
96
-
97
- gc_mode = getattr(args, "grad_ckpt", "auto")
 
 
 
 
 
 
98
  if gc_mode == "on":
99
  grad_ckpt = True
100
  elif gc_mode == "off":
101
  grad_ckpt = False
102
  else:
103
  grad_ckpt = bool(t.get("gradient_checkpointing", True)) and use_4bit
 
104
  optim = t.get("optim", "paged_adamw_8bit") if use_4bit else "adamw_torch"
 
 
105
 
106
  sft_args = SFTConfig(
107
  output_dir=output_dir,
108
- num_train_epochs=t.get("epochs", 3),
109
- per_device_train_batch_size=t.get("per_device_train_batch_size", 2),
110
- gradient_accumulation_steps=t.get("gradient_accumulation_steps", 8),
111
  learning_rate=float(t.get("learning_rate", 2e-4)),
112
  lr_scheduler_type=t.get("lr_scheduler_type", "cosine"),
113
  warmup_ratio=t.get("warmup_ratio", 0.03),
114
  logging_steps=t.get("logging_steps", 10),
115
  save_steps=t.get("save_steps", 50),
116
- max_length=t.get("max_length", cfg["model"].get("max_context", 2048)),
117
  completion_only_loss=t.get("completion_only_loss", True),
118
  packing=t.get("packing", False),
119
  gradient_checkpointing=grad_ckpt,
120
  optim=optim,
121
- bf16=bf16_ok and not getattr(args, "no_amp", False),
122
- fp16=has_cuda and not bf16_ok and not getattr(args, "no_amp", False),
123
  seed=t.get("seed", 0),
124
  report_to=(["wandb"] if args.wandb else "none"),
125
  max_steps=args.max_steps if args.max_steps else -1,
@@ -164,6 +231,8 @@ def main() -> None:
164
  ap.add_argument("--grad-accum", type=int, default=None)
165
  ap.add_argument("--max-length", type=int, default=None, help="sequence length (<=4096)")
166
  ap.add_argument("--grad-ckpt", choices=["auto", "on", "off"], default="auto")
 
 
167
  ap.add_argument("--no-amp", action="store_true",
168
  help="disable fp16/bf16 mixed precision (T4 fallback if the scaler errors)")
169
  ap.add_argument("--wandb", action="store_true")
 
26
  DEFAULT_CONFIGS = {"v": "configs/verifier_v.yaml", "g": "configs/generator_g.yaml"}
27
 
28
 
29
+ def _presets_for(vram_gb: float) -> dict:
30
+ """Per-GPU QLoRA defaults for a ~1.5B model. Explicit CLI flags override these;
31
+ these override the yaml config."""
32
+ big = vram_gb >= 70 # 80GB A100/H100 vs 40GB
33
+ return {
34
+ # T4 has no bf16 and its fp16 GradScaler has been unreliable -> amp off (fp32).
35
+ "t4": dict(batch_size=1, grad_accum=16, max_length=2048, grad_ckpt="on", amp="off"),
36
+ "l4": dict(batch_size=4, grad_accum=4, max_length=4096, grad_ckpt="on", amp="auto"),
37
+ "a100": dict(batch_size=16 if big else 8, grad_accum=1 if big else 2,
38
+ max_length=4096, grad_ckpt="off", amp="auto"),
39
+ "h100": dict(batch_size=16, grad_accum=1, max_length=4096, grad_ckpt="off", amp="auto"),
40
+ "generic_big": dict(batch_size=8, grad_accum=2, max_length=4096, grad_ckpt="off", amp="auto"),
41
+ "generic_mid": dict(batch_size=4, grad_accum=4, max_length=3072, grad_ckpt="on", amp="auto"),
42
+ "generic_small": dict(batch_size=1, grad_accum=16, max_length=2048, grad_ckpt="on", amp="auto"),
43
+ }
44
+
45
+
46
+ def detect_gpu_preset(force: str = "auto") -> tuple[str, dict]:
47
+ """Return (label, preset dict). ``force`` in {auto,none,t4,l4,a100,h100}."""
48
+ import torch
49
+
50
+ if force == "none":
51
+ return "none", {}
52
+ if not torch.cuda.is_available():
53
+ return "cpu", {}
54
+ vram = torch.cuda.get_device_properties(0).total_memory / 1e9
55
+ presets = _presets_for(vram)
56
+ if force in ("t4", "l4", "a100", "h100"):
57
+ return force, presets[force]
58
+ name = torch.cuda.get_device_name(0).lower()
59
+ cap = torch.cuda.get_device_capability()
60
+ if "a100" in name:
61
+ key = "a100"
62
+ elif "h100" in name or "h200" in name:
63
+ key = "h100"
64
+ elif "l4" in name:
65
+ key = "l4"
66
+ elif "t4" in name or cap[0] < 8:
67
+ key = "t4"
68
+ else:
69
+ key = "generic_big" if vram >= 70 else ("generic_mid" if vram >= 20 else "generic_small")
70
+ return key, presets[key]
71
+
72
+
73
  def build_and_train(args) -> str:
74
  import torch
75
  from datasets import load_dataset
 
87
  val_file = args.val_file or cfg["data"].get("val_path")
88
 
89
  has_cuda = torch.cuda.is_available()
 
90
  bf16_ok = has_cuda and torch.cuda.get_device_capability()[0] >= 8
91
+
92
+ label, preset = detect_gpu_preset(getattr(args, "gpu_preset", "auto"))
93
+ amp_off = getattr(args, "no_amp", False) or preset.get("amp") == "off"
94
+
95
+ # Precision: bf16 on Ampere+ (A100/L4/H100); fp16 AMP on T4; fp32 when AMP is
96
+ # disabled on a non-bf16 GPU (avoids fp16 gradient underflow without a scaler).
97
+ if bf16_ok:
98
+ compute_dtype = torch.bfloat16
99
+ elif has_cuda and not amp_off:
100
+ compute_dtype = torch.float16
101
+ else:
102
+ compute_dtype = torch.float32
103
+
104
  if has_cuda:
105
  cap = torch.cuda.get_device_capability()
106
+ prec = "bf16" if bf16_ok else ("fp32(no-amp)" if amp_off else "fp16")
107
+ print(f"[gpu] {torch.cuda.get_device_name(0)} (sm_{cap[0]}{cap[1]}) | "
108
+ f"preset={label} | compute={prec}")
109
+ if preset:
110
+ print(f"[preset] {label} -> {preset}")
111
+
112
  use_4bit = (not args.no_4bit) and cfg["quant"]["load_in_4bit"] and has_cuda
113
  if cfg["quant"]["load_in_4bit"] and not has_cuda and not args.no_4bit:
114
  print("[warn] 4-bit requested but no CUDA — falling back to full-precision "
 
121
  model_kwargs["quantization_config"] = build_bnb_config(cfg, compute_dtype=compute_dtype)
122
  model = AutoModelForCausalLM.from_pretrained(base_id, **model_kwargs)
123
 
124
+ if has_cuda and compute_dtype in (torch.float16, torch.float32):
125
+ # transformers may keep non-quantized weights in the model's default bf16
126
+ # even with dtype=; a bf16 grad breaks the fp16 GradScaler and mixes dtypes.
127
+ # Force any bf16 params to the chosen compute dtype and pin the config.
128
+ model.config.torch_dtype = compute_dtype
 
129
  cast = 0
130
  for p in model.parameters():
131
  if p.dtype == torch.bfloat16:
132
+ p.data = p.data.to(compute_dtype)
133
  cast += 1
134
  if cast:
135
+ print(f"[dtype] cast {cast} bf16 param tensors -> {compute_dtype}")
136
 
137
  data_files = {"train": train_file}
138
  if val_file and Path(val_file).exists():
 
142
  eval_ds = ds.get("validation")
143
 
144
  t = cfg["train"]
145
+
146
+ def pick(cli_val, key, cfg_default):
147
+ """Precedence: explicit CLI flag > GPU preset > yaml config."""
148
+ if cli_val is not None:
149
+ return cli_val
150
+ if key in preset:
151
+ return preset[key]
152
+ return cfg_default
153
+
154
+ max_ctx = cfg["model"].get("max_context", 4096)
155
+ epochs = getattr(args, "epochs", None) or t.get("epochs", 3)
156
+ batch = pick(getattr(args, "batch_size", None), "batch_size", t.get("per_device_train_batch_size", 2))
157
+ accum = pick(getattr(args, "grad_accum", None), "grad_accum", t.get("gradient_accumulation_steps", 8))
158
+ maxlen = min(pick(getattr(args, "max_length", None), "max_length", t.get("max_length", max_ctx)), max_ctx)
159
+
160
+ gc_cli = getattr(args, "grad_ckpt", "auto")
161
+ gc_mode = gc_cli if gc_cli != "auto" else preset.get("grad_ckpt", "auto")
162
  if gc_mode == "on":
163
  grad_ckpt = True
164
  elif gc_mode == "off":
165
  grad_ckpt = False
166
  else:
167
  grad_ckpt = bool(t.get("gradient_checkpointing", True)) and use_4bit
168
+
169
  optim = t.get("optim", "paged_adamw_8bit") if use_4bit else "adamw_torch"
170
+ print(f"[train] epochs={epochs} batch={batch} accum={accum} (eff={batch * accum}) "
171
+ f"max_length={maxlen} grad_ckpt={grad_ckpt}")
172
 
173
  sft_args = SFTConfig(
174
  output_dir=output_dir,
175
+ num_train_epochs=epochs,
176
+ per_device_train_batch_size=batch,
177
+ gradient_accumulation_steps=accum,
178
  learning_rate=float(t.get("learning_rate", 2e-4)),
179
  lr_scheduler_type=t.get("lr_scheduler_type", "cosine"),
180
  warmup_ratio=t.get("warmup_ratio", 0.03),
181
  logging_steps=t.get("logging_steps", 10),
182
  save_steps=t.get("save_steps", 50),
183
+ max_length=maxlen,
184
  completion_only_loss=t.get("completion_only_loss", True),
185
  packing=t.get("packing", False),
186
  gradient_checkpointing=grad_ckpt,
187
  optim=optim,
188
+ bf16=bf16_ok and not amp_off,
189
+ fp16=has_cuda and not bf16_ok and not amp_off,
190
  seed=t.get("seed", 0),
191
  report_to=(["wandb"] if args.wandb else "none"),
192
  max_steps=args.max_steps if args.max_steps else -1,
 
231
  ap.add_argument("--grad-accum", type=int, default=None)
232
  ap.add_argument("--max-length", type=int, default=None, help="sequence length (<=4096)")
233
  ap.add_argument("--grad-ckpt", choices=["auto", "on", "off"], default="auto")
234
+ ap.add_argument("--gpu-preset", choices=["auto", "none", "t4", "l4", "a100", "h100"],
235
+ default="auto", help="GPU-tuned defaults (auto-detects by default)")
236
  ap.add_argument("--no-amp", action="store_true",
237
  help="disable fp16/bf16 mixed precision (T4 fallback if the scaler errors)")
238
  ap.add_argument("--wandb", action="store_true")