Skip to content

Commit 14188de

Browse files
committed
Clean up PTQ buffer naming.
Signed-off-by: Cory Ye <cye@nvidia.com>
1 parent da15e63 commit 14188de

4 files changed

Lines changed: 71 additions & 42 deletions

File tree

‎megatron/core/extensions/transformer_engine.py‎

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,19 @@ def _get_global_layer_fqn(module_name: str, named_modules: Dict[str, torch.nn.Mo
310310
return module_name
311311

312312

313+
def _parse_ptq_calibration_buffer_name(buffer_name: str) -> Tuple[str, str, str]:
314+
"""Parse a TransformerEngine PTQ calibration buffer name."""
315+
match = re.fullmatch(
316+
r"(?P<tensor_name>.+)_"
317+
r"(?P<metadata_name>amax(?:_rowwise)?|scale_inv)_"
318+
r"(?P<recipe_name>.+)_te_ptq_calibrated",
319+
buffer_name,
320+
)
321+
if match is None:
322+
raise ValueError(f"Malformed TransformerEngine PTQ calibration buffer {buffer_name!r}")
323+
return (match.group("tensor_name"), match.group("metadata_name"), match.group("recipe_name"))
324+
325+
313326
def add_ptq_calibration_metadata_to_state_dict(
314327
state_dict: Dict[str, Any], model: List[torch.nn.Module]
315328
) -> None:
@@ -341,22 +354,21 @@ def add_ptq_calibration_metadata_to_state_dict(
341354
continue
342355
processed_buffers = []
343356
for key, data in sorted(calibration_buffers):
357+
tensor_name, metadata_name, recipe_name = _parse_ptq_calibration_buffer_name(key)
344358
metadata = data.detach()
345359
if metadata.numel() > 1:
346360
# Only global FP32 per-tensor scales are checkpointed.
347361
metadata = metadata.amax().reshape(1)
348362

349-
if "_scale_inv_" in key:
363+
if metadata_name == "scale_inv":
350364
# Inverted scaling factor, i.e. absmax / quantmax.
351-
scale_inv = metadata
365+
scale_inv = metadata.clone()
352366
else:
353367
# Convert absmax to a dequantization scaling factor.
354-
if "nvfp4" in key:
368+
if recipe_name == "nvfp4":
355369
# E2M1_MAX * E4M3_MAX
356370
quant_max = 6.0 * 448.0
357-
elif any(
358-
recipe in key for recipe in ("fp8_delayed_scaling", "fp8_current_scaling")
359-
):
371+
elif recipe_name == "fp8_delayed_scaling":
360372
quant_max = 448.0
361373
else:
362374
raise ValueError(f"Unsupported amax calibration buffer {key!r}")
@@ -370,25 +382,22 @@ def add_ptq_calibration_metadata_to_state_dict(
370382
scale_inv, op=torch.distributed.ReduceOp.MAX, group=reduction_group
371383
)
372384

373-
tensor_name = re.split(
374-
r"_tensor_(?:amax(?:_rowwise)?|scale_inv)_", key, maxsplit=1
375-
)[0]
376385
gemm_match = re.fullmatch(r"(input|weight)_gemm(\d+)", tensor_name)
377-
processed_buffers.append((key, scale_inv, gemm_match))
386+
processed_buffers.append((tensor_name, recipe_name, scale_inv, gemm_match))
378387

379388
expert_buffers = [
380389
entry
381390
for entry in processed_buffers
382-
if hasattr(module, "_pg_collection") and entry[2] is not None
391+
if hasattr(module, "_pg_collection") and entry[3] is not None
383392
]
384393
non_expert_buffers = [
385394
entry
386395
for entry in processed_buffers
387-
if not (hasattr(module, "_pg_collection") and entry[2] is not None)
396+
if not (hasattr(module, "_pg_collection") and entry[3] is not None)
388397
]
389398
if expert_buffers:
390399
# Gather every local GEMM's input and weight scales in one EP collective.
391-
local_scales = torch.cat([scale for _, scale, _ in expert_buffers])
400+
local_scales = torch.cat([scale for _, _, scale, _ in expert_buffers])
392401
ep_group = module._pg_collection.ep
393402
ep_size = get_pg_size(ep_group)
394403
num_local_scales = local_scales.numel()
@@ -402,24 +411,27 @@ def add_ptq_calibration_metadata_to_state_dict(
402411
gathered_scales = gathered_scales.cpu()
403412

404413
for ep_rank in range(ep_size):
405-
for scale_idx, (key, _, gemm_match) in enumerate(expert_buffers):
414+
for scale_idx, (_, recipe_name, _, gemm_match) in enumerate(expert_buffers):
406415
tensor_kind = gemm_match.group(1)
407416
local_gemm_idx = int(gemm_match.group(2))
408417
global_expert_idx = ep_rank * module.num_gemms + local_gemm_idx
409418
expert_fqn = _get_grouped_linear_expert_fqn(export_name, global_expert_idx)
410419
# Rename dense keys to match vLLM, TensorRT-LLM, and other PTQ formats.
411420
# NOTE(@cspades): I don't like this but these are the conventional names.
412-
scale_suffix = "_2" if tensor_kind == "weight" and "nvfp4" in key else ""
421+
scale_suffix = (
422+
"_2" if tensor_kind == "weight" and recipe_name == "nvfp4" else ""
423+
)
413424
local_calibration_state[
414425
f"{expert_fqn}.{tensor_kind}_scale{scale_suffix}"
415426
] = gathered_scales[ep_rank * num_local_scales + scale_idx]
416427

417-
for key, scale_inv, _ in non_expert_buffers:
428+
for tensor_name, recipe_name, scale_inv, _ in non_expert_buffers:
418429
# Rename dense keys to match vLLM, TensorRT-LLM, and other PTQ formats.
419430
# NOTE(@cspades): I don't like this but these are the conventional names.
420-
scale_prefix = "input_" if "input" in key else "weight_"
421-
scale_suffix = "_2" if "weight" in key and "nvfp4" in key else ""
422-
local_calibration_state[f"{export_name}.{scale_prefix}scale{scale_suffix}"] = (
431+
scale_suffix = (
432+
"_2" if tensor_name.endswith("weight") and recipe_name == "nvfp4" else ""
433+
)
434+
local_calibration_state[f"{export_name}.{tensor_name}_scale{scale_suffix}"] = (
423435
scale_inv.cpu()
424436
)
425437

‎megatron/core/fp8_utils.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ def _make_te_calibration_config(transformer_engine_calibration_decay: float = 0.
107107
"""Construct an immutable TE calibration config once per decay value."""
108108
if not HAVE_TE:
109109
raise RuntimeError(
110-
"ckpt_transformer_engine_calibration requires Transformer Engine "
111-
"to be installed"
110+
"ckpt_transformer_engine_calibration requires Transformer Engine to be installed"
112111
)
113112
calibration_config_cls = getattr(
114113
transformer_engine.pytorch, "QuantizationCalibrationConfig", None

‎tests/unit_tests/test_checkpointing.py‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -683,12 +683,15 @@ def test_save_checkpoint(
683683

684684
# Text-only checkpoint saves still bypass dataloader state, while passing CP through.
685685
cp_group = mock.sentinel.cp_group
686-
with mock.patch(
687-
"megatron.training.checkpointing.maybe_save_dataloader_state",
688-
wraps=maybe_save_dataloader_state,
689-
) as save_dataloader_state, mock.patch(
690-
"megatron.training.checkpointing.add_ptq_calibration_metadata_to_state_dict"
691-
) as export_calibration_metadata:
686+
with (
687+
mock.patch(
688+
"megatron.training.checkpointing.maybe_save_dataloader_state",
689+
wraps=maybe_save_dataloader_state,
690+
) as save_dataloader_state,
691+
mock.patch(
692+
"megatron.training.checkpointing.add_ptq_calibration_metadata_to_state_dict"
693+
) as export_calibration_metadata,
694+
):
692695
save_checkpoint(
693696
iteration,
694697
[model],

‎tests/unit_tests/transformer/test_ptq_calibration_metadata_buffering.py‎

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,15 @@ def test_calibration_options_default_to_disabled():
3030

3131
def test_disabled_megatron_option_returns_no_te_calibration_config():
3232
config = SimpleNamespace(
33-
ckpt_transformer_engine_calibration=False,
34-
transformer_engine_calibration_decay=0.5,
33+
ckpt_transformer_engine_calibration=False, transformer_engine_calibration_decay=0.5
3534
)
3635

3736
assert fp8_utils.get_te_calibration_config(config) is None
3837

3938

4039
def test_calibration_option_requires_te_config_support(monkeypatch):
4140
config = SimpleNamespace(
42-
ckpt_transformer_engine_calibration=True,
43-
transformer_engine_calibration_decay=0.123,
41+
ckpt_transformer_engine_calibration=True, transformer_engine_calibration_decay=0.123
4442
)
4543
monkeypatch.setattr(fp8_utils.transformer_engine.pytorch, "QuantizationCalibrationConfig", None)
4644
fp8_utils._make_te_calibration_config.cache_clear()
@@ -51,8 +49,7 @@ def test_calibration_option_requires_te_config_support(monkeypatch):
5149

5250
def test_megatron_options_create_te_calibration_config():
5351
config = SimpleNamespace(
54-
ckpt_transformer_engine_calibration=True,
55-
transformer_engine_calibration_decay=0.5,
52+
ckpt_transformer_engine_calibration=True, transformer_engine_calibration_decay=0.5
5653
)
5754

5855
calibration_config = fp8_utils.get_te_calibration_config(config)
@@ -109,8 +106,7 @@ def legacy_fp8_autocast(*, enabled, fp8_recipe, fp8_group):
109106
monkeypatch.setattr(fp8_utils.transformer_engine.pytorch, "fp8_autocast", legacy_fp8_autocast)
110107

111108
with pytest.raises(
112-
RuntimeError,
113-
match="--ckpt-transformer-engine-calibration.*accepts calibration_config",
109+
RuntimeError, match="--ckpt-transformer-engine-calibration.*accepts calibration_config"
114110
):
115111
fp8_utils.get_fp8_context(config)
116112

@@ -233,10 +229,10 @@ def test_calibration_amax_conversion_and_scale_inv_passthrough(monkeypatch):
233229
_disable_scale_collectives(monkeypatch)
234230
linear = torch.nn.Module()
235231
linear.register_buffer(
236-
"input_tensor_amax_fp8_delayed_scaling_te_ptq_calibrated", torch.tensor([896.0])
232+
"input_amax_fp8_delayed_scaling_te_ptq_calibrated", torch.tensor([896.0])
237233
)
238234
linear.register_buffer(
239-
"weight_tensor_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
235+
"weight_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
240236
)
241237
model = torch.nn.Module()
242238
model.add_module("linear", linear)
@@ -253,7 +249,7 @@ def test_calibration_amax_conversion_and_scale_inv_passthrough(monkeypatch):
253249
def test_nvfp4_weight_amax_exports_secondary_weight_scale(monkeypatch):
254250
_disable_scale_collectives(monkeypatch)
255251
linear = torch.nn.Module()
256-
linear.register_buffer("weight_tensor_amax_nvfp4_te_ptq_calibrated", torch.tensor([2688.0]))
252+
linear.register_buffer("weight_amax_nvfp4_te_ptq_calibrated", torch.tensor([2688.0]))
257253
model = torch.nn.Module()
258254
model.add_module("linear", linear)
259255
state_dict = {"model": {}}
@@ -263,6 +259,25 @@ def test_nvfp4_weight_amax_exports_secondary_weight_scale(monkeypatch):
263259
torch.testing.assert_close(state_dict["model"]["linear.weight_scale_2"], torch.ones(1))
264260

265261

262+
def test_fused_module_tensor_names_export_distinct_scales(monkeypatch):
263+
_disable_scale_collectives(monkeypatch)
264+
mlp = torch.nn.Module()
265+
mlp.register_buffer(
266+
"fc1_input_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
267+
)
268+
mlp.register_buffer(
269+
"fc2_input_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.5])
270+
)
271+
model = torch.nn.Module()
272+
model.add_module("mlp", mlp)
273+
state_dict = {"model": {}}
274+
275+
te_ext.add_ptq_calibration_metadata_to_state_dict(state_dict, [model])
276+
277+
torch.testing.assert_close(state_dict["model"]["mlp.fc1_input_scale"], torch.tensor([0.25]))
278+
torch.testing.assert_close(state_dict["model"]["mlp.fc2_input_scale"], torch.tensor([0.5]))
279+
280+
266281
def test_global_layer_fqn_uses_layer_number():
267282
model = torch.nn.Module()
268283
model.layers = torch.nn.ModuleList([torch.nn.Module()])
@@ -284,10 +299,10 @@ def test_expert_parallel_calibration_scales_are_gathered(monkeypatch):
284299
grouped_linear.num_gemms = 2
285300
grouped_linear._pg_collection = SimpleNamespace(ep=ep_group)
286301
grouped_linear.register_buffer(
287-
"input_gemm0_tensor_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
302+
"input_gemm0_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
288303
)
289304
grouped_linear.register_buffer(
290-
"input_gemm1_tensor_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.5])
305+
"input_gemm1_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.5])
291306
)
292307
mlp = torch.nn.Module()
293308
mlp.add_module("linear_fc1", grouped_linear)
@@ -321,7 +336,7 @@ def test_pipeline_parallel_calibration_states_are_merged(monkeypatch):
321336
model.pg_collection = SimpleNamespace(pp=pp_group)
322337
linear = torch.nn.Module()
323338
linear.register_buffer(
324-
"input_tensor_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
339+
"input_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
325340
)
326341
model.add_module("linear", linear)
327342

@@ -352,7 +367,7 @@ def test_pipeline_parallel_conflicting_calibration_states_are_rejected(monkeypat
352367
model.pg_collection = SimpleNamespace(pp=pp_group)
353368
linear = torch.nn.Module()
354369
linear.register_buffer(
355-
"input_tensor_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
370+
"input_scale_inv_fp8_current_scaling_te_ptq_calibrated", torch.tensor([0.25])
356371
)
357372
model.add_module("linear", linear)
358373

0 commit comments

Comments
 (0)