Skip to content

Commit 9eadd78

Browse files
authored
fix(training): use model config for kernel warmup (#7653)
Signed-off-by: Jiangfei Duan <jiangfeid@nvidia.com>
1 parent a54dd73 commit 9eadd78

3 files changed

Lines changed: 401 additions & 113 deletions

File tree

‎megatron/training/kernel_warmup.py‎

Lines changed: 187 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,41 @@
55
import gc
66
import logging
77
import sys
8-
from argparse import Namespace
98

109
import torch
10+
import torch.nn.functional as F
11+
12+
from megatron.core.activations import situ_glu, squared_relu, tanh_soft_clamp
13+
from megatron.core.transformer.transformer_config import TransformerConfig
14+
from megatron.core.utils import log_single_rank
1115

1216
logger = logging.getLogger(__name__)
1317

1418

15-
def _warmup_cross_entropy(args: Namespace, tp_group: torch.distributed.ProcessGroup) -> None:
19+
def _warmup_cross_entropy(
20+
config: TransformerConfig,
21+
tp_group: torch.distributed.ProcessGroup,
22+
sequence: int,
23+
micro_batch_size: int,
24+
vocab: int,
25+
logit_dtype: torch.dtype | None,
26+
) -> None:
1627
from megatron.core.fusions.fused_cross_entropy import fused_vocab_parallel_cross_entropy
1728

18-
sequence = args.seq_length // args.context_parallel_size
19-
vocab = args.padded_vocab_size
2029
if vocab <= 0:
2130
raise ValueError("CE vocabulary must be positive")
2231
if vocab % tp_group.size():
2332
raise ValueError("CE vocabulary must be divisible by tensor parallel size")
2433
for _ in range(2):
2534
logits = torch.zeros(
26-
(sequence, args.micro_batch_size, vocab // tp_group.size()),
35+
(sequence, micro_batch_size, vocab // tp_group.size()),
2736
device="cuda",
28-
dtype=args.logit_dtype or args.params_dtype,
37+
dtype=logit_dtype or config.params_dtype,
2938
requires_grad=True,
3039
)
3140
# Match the model's batch-major labels, including singleton-dimension
3241
# strides: contiguous() need not copy a transposed microbatch of one.
33-
labels = torch.zeros((args.micro_batch_size, sequence), device="cuda", dtype=torch.long)
42+
labels = torch.zeros((micro_batch_size, sequence), device="cuda", dtype=torch.long)
3443
labels = labels.transpose(0, 1).contiguous()
3544
loss = fused_vocab_parallel_cross_entropy(logits, labels, tp_group)
3645
# Match LanguageModule.compute_language_model_loss, including the [b, s]
@@ -43,69 +52,108 @@ def _warmup_cross_entropy(args: Namespace, tp_group: torch.distributed.ProcessGr
4352
del gradient, loss_gradient, loss, labels, logits
4453

4554

46-
def _warmup_activation(args: Namespace, tp_size: int, width: int, *, flatten: bool = False) -> None:
55+
def _warmup_activation(
56+
config: TransformerConfig,
57+
tp_size: int,
58+
width: int,
59+
sequence: int,
60+
micro_batch_size: int,
61+
*,
62+
kernel: str,
63+
flatten: bool = False,
64+
) -> None:
65+
"""Warm one MLP activation path at the model's FC1 output shape.
66+
67+
``kernel`` is what MLP.forward will run, selected by the caller from activation_func together
68+
with its gating and fusion settings; nothing here re-derives it from a single config field.
69+
"squared_relu", "swiglu", "geglu" and "gelu" are the fused kernels. "unfused" is the eager
70+
expression, whose jit-fused pieces (situ_glu, tanh_soft_clamp and activations such as
71+
squared_relu or quick_gelu) also compile on first use.
72+
"""
73+
from megatron.core.fusions.fused_bias_geglu import bias_geglu_impl
4774
from megatron.core.fusions.fused_bias_gelu import bias_gelu_impl
4875
from megatron.core.fusions.fused_bias_swiglu import bias_swiglu_impl
4976
from megatron.core.fusions.fused_weighted_squared_relu import weighted_squared_relu_impl
5077

51-
sequence = args.seq_length // args.context_parallel_size
52-
leading_shape = (sequence, args.micro_batch_size)
78+
leading_shape = (sequence, micro_batch_size)
5379
if flatten:
54-
leading_shape = (sequence * args.micro_batch_size,)
80+
leading_shape = (sequence * micro_batch_size,)
81+
# Gated activations consume FC1's [gate, linear] output, twice the width.
82+
gated = kernel in ("swiglu", "geglu") or (kernel == "unfused" and config.gated_linear_unit)
83+
clamp = config.activation_func_tanh_clamp_scale
5584
for _ in range(2):
5685
x = torch.zeros(
57-
(*leading_shape, width // tp_size * (2 if args.swiglu else 1)),
86+
(*leading_shape, width // tp_size * (2 if gated else 1)),
5887
device="cuda",
59-
dtype=args.params_dtype,
88+
dtype=config.params_dtype,
6089
requires_grad=True,
6190
)
91+
# The unfused path adds its bias eagerly before the activation; nothing to warm there.
6292
bias = (
6393
torch.zeros(x.shape[-1], device="cuda", dtype=x.dtype, requires_grad=True)
64-
if args.add_bias_linear and not args.squared_relu
94+
if config.add_bias_linear and kernel not in ("squared_relu", "unfused")
6595
else None
6696
)
67-
if args.squared_relu:
68-
output = weighted_squared_relu_impl(x, None, args.activation_func_tanh_clamp_scale)
69-
elif args.swiglu:
97+
if kernel == "squared_relu":
98+
output = weighted_squared_relu_impl(x, None, clamp)
99+
elif kernel == "unfused":
100+
if gated and clamp is not None:
101+
linear_clamp = config.activation_func_tanh_clamp_scale_linear
102+
output = situ_glu(x, clamp, linear_clamp, config.glu_linear_offset)
103+
elif gated:
104+
x_glu, x_linear = torch.chunk(x, 2, dim=-1)
105+
if (val := config.activation_func_clamp_value) is not None:
106+
x_glu = x_glu.clamp(min=None, max=val)
107+
x_linear = x_linear.clamp(min=-val, max=val)
108+
output = config.activation_func(x_glu) * (x_linear + config.glu_linear_offset)
109+
else:
110+
y = tanh_soft_clamp(x, clamp) if clamp is not None else x
111+
output = config.activation_func(y)
112+
elif kernel == "swiglu":
70113
output = bias_swiglu_impl(
71114
x,
72115
bias,
73-
getattr(args, "activation_func_fp8_input_store", False),
74-
clamp_value=args.activation_func_clamp_value,
75-
gate_clamp_scale=args.activation_func_tanh_clamp_scale,
76-
linear_clamp_scale=args.activation_func_tanh_clamp_scale_linear,
116+
config.activation_func_fp8_input_store,
117+
clamp_value=config.activation_func_clamp_value,
118+
gate_clamp_scale=config.activation_func_tanh_clamp_scale,
119+
linear_clamp_scale=config.activation_func_tanh_clamp_scale_linear,
77120
)
121+
elif kernel == "geglu":
122+
output = bias_geglu_impl(x, bias)
78123
else:
79124
output = bias_gelu_impl(x, bias)
80125
inputs = (x, bias) if bias is not None else (x,)
81126
gradients = torch.autograd.grad(output, inputs, torch.ones_like(output))
82127
del gradients, output, inputs, bias, x
83128

84129

85-
def _warmup_residual(args: Namespace, tp_size: int) -> None:
130+
def _warmup_residual(
131+
config: TransformerConfig, tp_size: int, sequence: int, micro_batch_size: int
132+
) -> None:
86133
from megatron.core.fusions.fused_bias_dropout import bias_dropout_add_fused_train
87134

88-
sequence = args.seq_length // args.context_parallel_size
89-
if args.sequence_parallel:
135+
if config.sequence_parallel:
90136
sequence //= tp_size
91137
for grad_enabled in (False, True):
92138
with torch.set_grad_enabled(grad_enabled):
93139
for _ in range(2):
94140
x = torch.zeros(
95-
(sequence, args.micro_batch_size, args.hidden_size),
141+
(sequence, micro_batch_size, config.hidden_size),
96142
device="cuda",
97-
dtype=args.params_dtype,
143+
dtype=config.params_dtype,
98144
requires_grad=True,
99145
)
100146
residual = torch.zeros_like(
101147
x,
102-
dtype=torch.float32 if args.fp32_residual_connection else x.dtype,
148+
dtype=torch.float32 if config.fp32_residual_connection else x.dtype,
103149
requires_grad=True,
104150
)
105151
bias = (
106-
torch.zeros_like(x[0, 0], requires_grad=True) if args.add_bias_linear else None
152+
torch.zeros_like(x[0, 0], requires_grad=True)
153+
if config.add_bias_linear
154+
else None
107155
)
108-
output = bias_dropout_add_fused_train((x, bias), residual, args.hidden_dropout)
156+
output = bias_dropout_add_fused_train((x, bias), residual, config.hidden_dropout)
109157
if grad_enabled:
110158
inputs = (x, residual, bias) if bias is not None else (x, residual)
111159
gradients = torch.autograd.grad(output, inputs, torch.ones_like(output))
@@ -125,7 +173,82 @@ def _shutdown_compile_workers() -> None:
125173
logger.warning("This PyTorch build lacks compiler-worker shutdown")
126174

127175

128-
def warmup_training_kernels(args: Namespace, tp_group: torch.distributed.ProcessGroup) -> None:
176+
def warmup_training_kernels(
177+
model_config,
178+
tp_group: torch.distributed.ProcessGroup,
179+
*,
180+
seq_length: int,
181+
micro_batch_size: int,
182+
) -> None:
183+
"""Warm up training kernels for a GPT or Hybrid model config at the training batch shape.
184+
185+
Entry points that still pass a provider function instead of a model config leave
186+
``model_config`` unset; there is no TransformerConfig to select kernels from, so nothing
187+
is warmed.
188+
"""
189+
from megatron.training.models import GPTModelConfig, HybridModelConfig
190+
from megatron.training.vocab_utils import calculate_padded_vocab_size
191+
192+
if not isinstance(model_config, (GPTModelConfig, HybridModelConfig)):
193+
log_single_rank(
194+
logger, logging.INFO, "Skipping training-kernel warmup: no GPT/Hybrid model config"
195+
)
196+
return
197+
config = model_config.transformer
198+
vocab_size = model_config.vocab_size
199+
if vocab_size is not None and model_config.should_pad_vocab:
200+
vocab_size = calculate_padded_vocab_size(
201+
vocab_size,
202+
model_config.make_vocab_size_divisible_by,
203+
config.tensor_model_parallel_size,
204+
logging_enabled=False,
205+
)
206+
_warmup_training_kernels(
207+
config,
208+
tp_group,
209+
seq_length=seq_length,
210+
micro_batch_size=micro_batch_size,
211+
padded_vocab_size=vocab_size,
212+
logit_dtype=model_config.logit_dtype,
213+
)
214+
215+
216+
def _select_activation_kernel(config: TransformerConfig) -> str | None:
217+
"""Return the activation path MLP.forward runs for fixed-shape (dense and shared) MLPs.
218+
219+
None means nothing to warm: TE activations, or fused combinations MLP.forward rejects.
220+
Quick-GeLU and the per-token-weighted kernels are fused only for routed experts, whose
221+
token counts are data-dependent, so they are left to runtime compilation.
222+
"""
223+
if config.use_te_activation_func:
224+
return None
225+
if (
226+
config.activation_func == squared_relu
227+
and not config.gated_linear_unit
228+
and config.use_fused_weighted_squared_relu
229+
and config.activation_func_tanh_clamp_scale is not None
230+
):
231+
return "squared_relu"
232+
if not config.bias_activation_fusion:
233+
return "unfused"
234+
if config.activation_func == F.silu and config.gated_linear_unit:
235+
return "swiglu"
236+
if config.activation_func == F.gelu:
237+
if config.gated_linear_unit:
238+
return "geglu"
239+
return "gelu" if config.add_bias_linear else None
240+
return None
241+
242+
243+
def _warmup_training_kernels(
244+
config: TransformerConfig,
245+
tp_group: torch.distributed.ProcessGroup,
246+
*,
247+
seq_length: int,
248+
micro_batch_size: int,
249+
padded_vocab_size: int | None,
250+
logit_dtype: torch.dtype | None = None,
251+
) -> None:
129252
"""Compile/autotune supported kernels, then release temporary startup resources.
130253
131254
Call after set_jit_fusion_options() on every tensor-parallel rank, before
@@ -134,42 +257,52 @@ def warmup_training_kernels(args: Namespace, tp_group: torch.distributed.Process
134257
shapes; no training data or parameters are needed. RNG state and compiler
135258
parallelism are preserved. Dynamic expert shapes and library-specific kernels
136259
can still compile later; shutting down workers does not disable compilation.
260+
261+
Args:
262+
config: The TransformerConfig used to build the model.
263+
tp_group: The model's tensor-parallel process group.
264+
seq_length: Actual training sequence length before context parallel partitioning.
265+
micro_batch_size: Microbatch size used by the training schedule and dataloader.
266+
padded_vocab_size: Global padded vocabulary size before tensor parallel partitioning.
267+
May be None when native fused cross-entropy is disabled.
268+
logit_dtype: Model output-projection dtype; None uses config.params_dtype for warmup.
137269
"""
138-
if args.seq_length // args.context_parallel_size <= 0 or args.micro_batch_size <= 0:
270+
sequence = seq_length // config.context_parallel_size
271+
if sequence <= 0 or micro_batch_size is None or micro_batch_size <= 0:
139272
raise ValueError("Kernel warmup dimensions must be positive")
140273
tp_size = tp_group.size()
141274
# New warmups (notably dropout) must not advance training RNG state. The
142275
# existing JIT warmup runs separately and retains its original RNG behavior.
143276
with torch.random.fork_rng(devices=[torch.cuda.current_device()]), torch.enable_grad():
144-
if args.cross_entropy_loss_fusion and args.cross_entropy_fusion_impl == "native":
145-
_warmup_cross_entropy(args, tp_group)
277+
if config.cross_entropy_loss_fusion and config.cross_entropy_fusion_impl == "native":
278+
if padded_vocab_size is None:
279+
raise ValueError("Model vocabulary must be resolved before kernel warmup")
280+
_warmup_cross_entropy(
281+
config, tp_group, sequence, micro_batch_size, padded_vocab_size, logit_dtype
282+
)
146283

147-
squared_relu = (
148-
args.squared_relu
149-
and args.use_fused_weighted_squared_relu
150-
and args.activation_func_tanh_clamp_scale is not None
151-
)
152-
swiglu = args.swiglu and args.bias_swiglu_fusion
153-
gelu = (
154-
not args.squared_relu
155-
and not args.swiglu
156-
and not args.quick_geglu
157-
and args.bias_gelu_fusion
158-
and args.add_bias_linear
159-
)
160-
if not args.use_te_activation_func and (squared_relu or swiglu or gelu):
284+
kernel = _select_activation_kernel(config)
285+
if kernel is not None:
161286
# Shared and dense MLPs have fixed shapes. Routed-expert token counts
162287
# are data-dependent and are intentionally left to runtime compilation.
163-
widths = {args.ffn_hidden_size}
164-
if args.moe_shared_expert_intermediate_size:
165-
widths.add(args.moe_shared_expert_intermediate_size)
288+
widths = {config.ffn_hidden_size}
289+
if config.moe_shared_expert_intermediate_size:
290+
widths.add(config.moe_shared_expert_intermediate_size)
166291
for width in sorted(widths):
167-
_warmup_activation(args, tp_size, width)
168292
# MLP paths also use flattened [tokens, width] activations;
169293
# Dynamo specializes on tensor rank even with identical numel.
170-
_warmup_activation(args, tp_size, width, flatten=True)
171-
if args.bias_dropout_fusion:
172-
_warmup_residual(args, tp_size)
294+
for flatten in (False, True):
295+
_warmup_activation(
296+
config,
297+
tp_size,
298+
width,
299+
sequence,
300+
micro_batch_size,
301+
kernel=kernel,
302+
flatten=flatten,
303+
)
304+
if config.bias_dropout_fusion:
305+
_warmup_residual(config, tp_size, sequence, micro_batch_size)
173306

174307
# Each helper has returned after invoking its kernels and waiting for their
175308
# compilation. Finish GPU work before releasing unused pinned blocks.

‎megatron/training/training.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,12 @@ def pretrain(
17271727
warmup_tp_group = pg_collection.tp
17281728
else:
17291729
warmup_tp_group = mpu.get_tensor_model_parallel_group()
1730-
warmup_training_kernels(args, warmup_tp_group)
1730+
warmup_training_kernels(
1731+
cfg_container.model,
1732+
warmup_tp_group,
1733+
seq_length=args.seq_length,
1734+
micro_batch_size=cfg_container.train.micro_batch_size,
1735+
)
17311736
print_rank_0("Finished training-kernel warmup.")
17321737

17331738
timestamp_after_set_jit_fusion_options = time.time()

0 commit comments

Comments
 (0)