File size: 41,168 Bytes
a1833c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: OpenMDW-1.1

"""Low-precision quantization helpers for the Cosmos3 VFM MoT network.

Quantization is applied via torchao's :func:`apply_quantization_inplace`, which
uses the ``quantize_`` path to replace each selected weight with a quantized
tensor subclass in place. Because the live parameter becomes a tensor subclass,
this only works on unsharded (plain-tensor) params and is therefore restricted
to replicated inference (``data_parallel_shard_degree == 1``); it cannot be
applied to an FSDP-sharded model whose params are ``DTensor`` shards.

ModelOpt FP8 checkpoint loading installs the exported E4M3 weights and static
scales into TorchAO tensor subclasses without calibration or re-quantization.

This is an inference-only path: the ``quantize_`` PTQ configs have no backward
support. Module selection is delegated to the filter built by
:func:`_get_filter_fn`.
"""

import gc
import json
import re
from collections.abc import Callable
from pathlib import Path

import torch
from torch import nn
from torch.distributed.tensor import DTensor, distribute_tensor
from torch.nn import functional as F

from cosmos_framework.utils import log
from cosmos_framework.configs.base.defaults.quantization import QuantizationConfig

# NOTE: ``torchao`` is imported lazily inside the functions below rather than at
# module top level. These two helpers are the only torchao consumers, but this
# module is imported transitively by the model package (e.g. during tests that
# never quantize). Keeping the imports lazy means importing this module does not
# require torchao to be installed; the imports only run — and only fail — when
# quantization is actually requested.


class _ModelOptFloat8Linear(nn.Linear):
    """Work around TorchAO 0.16 static-FP8 limitations for linear inputs."""

    # Compute dtype the FP8 weight dequantizes to. Recorded when the module is
    # swapped in on meta, where the plain E4M3 placeholder no longer carries it.
    _modelopt_high_precision_dtype: torch.dtype | None = None
    # True once real checkpoint data has been installed.
    _modelopt_weight_loaded: bool = False
    # Mixed-precision diffusion steps (see utils/generator/mixed_precision.py).
    # None until install_mixed_precision_runtime tags this module.
    _mixed_precision_runtime = None
    _mixed_precision_path: str = "generation"
    # A (N, K) dense view staged by the block provider for the current decoder
    # layer, or None. Read here, written only by the provider hooks.
    _mixed_precision_staged_weight: torch.Tensor | None = None

    def forward(self, inputs: torch.Tensor) -> torch.Tensor:
        output_shape = (*inputs.shape[:-1], self.out_features)
        # PrototypeFloat8Tensor divides each input dimension by its block size, so
        # a zero-row input fails with 0 // 0 instead of producing an empty output.
        if inputs.numel() == 0:
            return inputs.new_empty(output_shape)
        # PrototypeFloat8Tensor requires the input and its static (1, 1) activation
        # scale to have equal rank, so flatten token dimensions before dispatch.
        flat_inputs = inputs.reshape(-1, inputs.shape[-1])
        runtime = self._mixed_precision_runtime
        if runtime is not None and runtime.use_high_precision(self._mixed_precision_path):
            # W8A16: dense GEMM against the dequantized FP8 weight; the
            # activation stays in the compute dtype. The resolved weight is
            # in the runtime's configured activation_dtype, which normally
            # matches the live activations -- but defensively cast both
            # operands to the weight's dtype and cast the result back to the
            # input dtype when they differ (e.g. a fp16-activation model run
            # against a bf16-configured runtime), instead of crashing.
            weight = runtime.resolve_w8a16_weight(self)
            if weight.dtype != flat_inputs.dtype:
                output = F.linear(flat_inputs.to(weight.dtype), weight, self.bias).to(inputs.dtype)
            else:
                output = F.linear(flat_inputs, weight, self.bias)
            return output.reshape(output_shape)
        flat_outputs = F.linear(flat_inputs, self.weight, self.bias)
        return flat_outputs.reshape(output_shape)


_torchao_fsdp_support_installed = False


def _rewrap_float8(source, qdata: torch.Tensor):
    """Rebuild a static-FP8 tensor around new ``qdata``, carrying the scales over.

    Valid only because ModelOpt exports PerTensor granularity: one scalar scale
    covers the whole weight, so reshaping or slicing the data never redistributes
    it. ``block_size`` is therefore always "one block spanning everything".
    """
    return source.__class__(
        qdata,
        source.scale,
        act_quant_scale=source.act_quant_scale,
        output_act_quant_scale=source.output_act_quant_scale,
        block_size=list(qdata.shape),
        mm_config=source.mm_config,
        act_quant_kwargs=source.act_quant_kwargs,
        kernel_preference=source.kernel_preference,
        dtype=source.dtype,
        output_act_quant_kwargs=source.output_act_quant_kwargs,
    )


def install_torchao_float8_fsdp_support() -> None:
    """Teach TorchAO's static-FP8 tensor the ops and hooks FSDP2 needs.

    TorchAO 0.16 ships this tensor for single-device inference: it implements no
    ``fsdp_pre_all_gather`` / ``fsdp_post_all_gather``, and its shape-changing ops
    derive a scale layout from ``block_size`` assuming a fixed rank, so FSDP2's
    reshape/split/allocate path fails on a 2D linear weight. Sharding it is
    nonetheless well defined for a PerTensor scale — the scalar is identical on
    every shard, so only the FP8 bytes ever need to move.

    Registered once, globally, since the dispatch table is keyed by class.
    """
    global _torchao_fsdp_support_installed
    if _torchao_fsdp_support_installed:
        return
    from torchao.prototype.quantization.float8_static_quant.prototype_float8_tensor import (
        PrototypeFloat8Tensor,
    )
    from torchao.utils import return_and_correct_aliasing

    aten = torch.ops.aten
    implements = PrototypeFloat8Tensor.implements

    @implements([aten.view.default, aten._unsafe_view.default, aten.reshape.default])
    def _(func, types, args, kwargs):
        self, size = args[0], args[1]
        return return_and_correct_aliasing(func, args, kwargs, _rewrap_float8(self, self.qdata.reshape(*size)))

    @implements(aten.split.Tensor)
    def _(func, types, args, kwargs):
        self, size = args[0], args[1]
        dim = args[2] if len(args) > 2 else 0
        return [_rewrap_float8(self, part) for part in self.qdata.split(size, dim)]

    @implements(aten.slice.Tensor)
    def _(func, types, args, kwargs):
        self = args[0]
        return return_and_correct_aliasing(
            func, args, kwargs, _rewrap_float8(self, aten.slice.Tensor(self.qdata, *args[1:]))
        )

    @implements(aten.as_strided.default)
    def _(func, types, args, kwargs):
        self, size, stride = args[0], args[1], args[2]
        offset = args[3] if len(args) > 3 else 0
        return return_and_correct_aliasing(
            func, args, kwargs, _rewrap_float8(self, self.qdata.as_strided(size, stride, offset))
        )

    @implements(aten.new_zeros.default)
    def _(func, types, args, kwargs):
        self, size = args[0], args[1]
        return _rewrap_float8(self, self.qdata.new_zeros(size))

    @implements([aten.empty_like.default, aten.zeros_like.default])
    def _(func, types, args, kwargs):
        self = args[0]
        device = kwargs.get("device")
        qdata = func(self.qdata, **{key: value for key, value in kwargs.items() if key != "dtype"})
        # `to_empty` moves storage without data, so the scales are allocated empty
        # on the target device as well: `.to(device)` would try to copy out of a
        # meta tensor. Real scale values arrive with the checkpoint weights.
        scale = torch.empty_like(self.scale, device=device) if device is not None else self.scale
        act_quant_scale = (
            torch.empty_like(self.act_quant_scale, device=device) if device is not None else self.act_quant_scale
        )
        return self.__class__(
            qdata,
            scale,
            act_quant_scale=act_quant_scale,
            output_act_quant_scale=self.output_act_quant_scale,
            block_size=list(qdata.shape),
            mm_config=self.mm_config,
            act_quant_kwargs=self.act_quant_kwargs,
            kernel_preference=self.kernel_preference,
            dtype=self.dtype,
            output_act_quant_kwargs=self.output_act_quant_kwargs,
        )

    @implements([aten.detach.default, aten.clone.default])
    def _(func, types, args, kwargs):
        self = args[0]
        return return_and_correct_aliasing(func, args, kwargs, _rewrap_float8(self, func(self.qdata)))

    @implements(aten._to_copy.default)
    def _(func, types, args, kwargs):
        # Device moves must go through, but a dtype request must not: casting the
        # E4M3 payload to the compute dtype would silently undo the quantization.
        self = args[0]
        forwarded = {key: value for key, value in kwargs.items() if key != "dtype"}
        return return_and_correct_aliasing(func, args, kwargs, _rewrap_float8(self, func(self.qdata, **forwarded)))

    @implements(aten.copy_.default)
    def _(func, types, args, kwargs):
        destination, source = args[0], args[1]
        if isinstance(source, PrototypeFloat8Tensor):
            destination.qdata.copy_(source.qdata)
            destination.scale.copy_(source.scale)
            destination.act_quant_scale.copy_(source.act_quant_scale)
        else:
            destination.qdata.copy_(source)
        return destination

    def fsdp_pre_all_gather(self, mesh):
        # Only the FP8 bytes travel. The static scales are already identical on
        # every rank, so keeping them out of the collective saves the traffic.
        return (self.qdata,), (self.scale, self.act_quant_scale, self.dtype)

    def fsdp_post_all_gather(self, all_gather_outputs, metadata, param_dtype, *, out=None):
        (qdata,) = all_gather_outputs
        scale, act_quant_scale, dtype = metadata
        if out is not None:
            return None
        gathered = self.__class__(
            qdata,
            scale,
            act_quant_scale=act_quant_scale,
            output_act_quant_scale=self.output_act_quant_scale,
            block_size=list(qdata.shape),
            mm_config=self.mm_config,
            act_quant_kwargs=self.act_quant_kwargs,
            kernel_preference=self.kernel_preference,
            dtype=param_dtype or dtype,
            output_act_quant_kwargs=self.output_act_quant_kwargs,
        )
        return gathered, (qdata,)

    PrototypeFloat8Tensor.fsdp_pre_all_gather = fsdp_pre_all_gather
    PrototypeFloat8Tensor.fsdp_post_all_gather = fsdp_post_all_gather
    _torchao_fsdp_support_installed = True
    log.info("Installed TorchAO static-FP8 FSDP support (8 aten ops + all-gather hooks)")


def is_modelopt_fp8_checkpoint(checkpoint_path: str | Path) -> bool:
    """Return whether a local checkpoint declares ModelOpt FP8 quantization."""
    quant_config_path = Path(checkpoint_path) / "hf_quant_config.json"
    if not quant_config_path.is_file():
        return False
    try:
        quant_config = json.loads(quant_config_path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as error:
        raise ValueError(f"Unable to read a valid checkpoint quantization config: {quant_config_path}") from error
    if (
        not isinstance(quant_config, dict)
        or quant_config.get("quant_method") != "modelopt"
        or quant_config.get("quant_algo") != "FP8"
    ):
        raise ValueError(f"Unsupported checkpoint quantization configuration: {quant_config_path}")
    return True


def collect_modelopt_fp8_params(model: nn.Module) -> set[nn.Parameter]:
    """Return the FP8 weights of every ModelOpt linear currently in ``model``.

    Collected by module type immediately before use rather than captured earlier,
    because parallelization rebuilds parts of the network (notably the
    ``language_model`` subtree) and hands back fresh parameter objects, so any
    set gathered ahead of that no longer matches by identity.
    """
    return {module.weight for module in model.modules() if isinstance(module, _ModelOptFloat8Linear)}


def plan_modelopt_fp8_targets(
    checkpoint_path: str | Path,
    key_mapper: Callable[[str, str], str | None],
    *,
    weight_map: dict[str, str],
) -> list[str]:
    """Return the network-relative FQNs a ModelOpt FP8 checkpoint quantizes.

    Derived from the checkpoint index alone — no model instance is required — so
    the result can be threaded through the model config and consumed by
    ``build_net`` before the network exists. Only the E4M3 tensors are reported;
    weights ModelOpt intentionally left in high precision (``proj_in``,
    ``lm_head``, the vision tower, ...) are skipped.

    Args:
        checkpoint_path: Local root of the ModelOpt diffusers checkpoint.
        key_mapper: Maps a diffusers source key and shard path to a model state key.
        weight_map: Merged checkpoint weight map.

    Returns:
        Sorted target module FQNs, relative to the VFM network.
    """
    from safetensors import safe_open

    checkpoint_path = Path(checkpoint_path)
    weights_by_shard: dict[str, list[str]] = {}
    for source_key, shard_path in weight_map.items():
        if source_key.endswith(".weight"):
            weights_by_shard.setdefault(shard_path, []).append(source_key)

    target_fqns: set[str] = set()
    for shard_path, source_weight_keys in sorted(weights_by_shard.items()):
        with safe_open(checkpoint_path / shard_path, framework="pt", device="cpu") as shard:
            shard_keys = set(shard.keys())
            for source_weight_key in sorted(source_weight_keys):
                if source_weight_key not in shard_keys:
                    continue
                if shard.get_slice(source_weight_key).get_dtype() != "F8_E4M3":
                    continue
                target_weight_key = key_mapper(source_weight_key, shard_path)
                if target_weight_key is None or not target_weight_key.endswith(".weight"):
                    continue
                target_fqns.add(target_weight_key.removesuffix(".weight"))
    return sorted(target_fqns)


def swap_modelopt_fp8_linears_on_meta(model: nn.Module, target_fqns: list[str]) -> list[str]:
    """Swap the given linears to FP8 modules while the network is still on meta.

    This must run *before* the network is parallelized and materialized. FSDP2
    wraps the whole network into a single parameter group, so a linear replaced
    after ``fully_shard`` would leave the group holding a stale parameter; and
    ``to_empty`` on the original bf16 shapes is what sets peak memory, which for
    a Super-class model exceeds a single 80 GB device. Swapping here means FSDP
    shards — and ``to_empty`` materializes — one byte per element instead of two.

    The weight is created as a real (meta) TorchAO static-FP8 tensor rather than a
    plain E4M3 placeholder, because FSDP decides at wrap time whether a parameter
    carries the ``fsdp_pre_all_gather`` extension. A plain tensor swapped to a
    subclass afterwards is already registered as an ordinary parameter and fails
    on the first all-gather. Actual weight bytes and scales arrive later, in
    :func:`apply_modelopt_fp8_checkpoint_inplace`.

    Args:
        model: Meta-device VFM network.
        target_fqns: Module FQNs to convert, as planned from the checkpoint.

    Returns:
        Sorted FQNs actually swapped; targets absent from this model variant are
        skipped, matching the loader's tolerance for task-specialized exports.
    """
    install_torchao_float8_fsdp_support()
    from torchao.float8.inference import Float8MMConfig
    from torchao.prototype.quantization.float8_static_quant.prototype_float8_tensor import (
        PrototypeFloat8Tensor,
    )
    from torchao.quantization import PerTensor
    from torchao.quantization.quantize_.workflows import QuantizeTensorToFloat8Kwargs

    mm_config = Float8MMConfig(use_fast_accum=True)
    activation_quant_kwargs = QuantizeTensorToFloat8Kwargs(
        float8_dtype=torch.float8_e4m3fn,
        granularity=PerTensor(),
        mm_config=mm_config,
    )
    swapped_fqns: list[str] = []
    for target_module_fqn in sorted(target_fqns):
        try:
            module = model.get_submodule(target_module_fqn)
        except AttributeError:
            continue
        if not isinstance(module, nn.Linear):
            raise KeyError(f"ModelOpt FP8 target {target_module_fqn!r} is not an nn.Linear module")

        replacement = _ModelOptFloat8Linear(
            module.in_features,
            module.out_features,
            bias=module.bias is not None,
            device="meta",
            dtype=module.weight.dtype,
        )
        replacement._modelopt_high_precision_dtype = module.weight.dtype
        quantized_shape = tuple(module.weight.shape)
        replacement.weight = nn.Parameter(
            PrototypeFloat8Tensor(
                torch.empty(quantized_shape, dtype=torch.float8_e4m3fn, device="meta"),
                torch.empty(1, 1, dtype=torch.float32, device="meta"),
                act_quant_scale=torch.empty(1, 1, dtype=torch.float32, device="meta"),
                block_size=list(quantized_shape),
                mm_config=mm_config,
                act_quant_kwargs=activation_quant_kwargs,
                dtype=module.weight.dtype,
            ),
            requires_grad=False,
        )
        if module.bias is not None:
            replacement.bias = module.bias
        replacement.train(module.training)

        parent_fqn, _, child_name = target_module_fqn.rpartition(".")
        parent = model.get_submodule(parent_fqn) if parent_fqn else model
        setattr(parent, child_name, replacement)
        swapped_fqns.append(target_module_fqn)

    log.info(f"Swapped {len(swapped_fqns)} linears to meta-device ModelOpt FP8 modules")
    return swapped_fqns


def apply_modelopt_fp8_checkpoint_inplace(
    model: nn.Module,
    checkpoint_path: str | Path,
    key_mapper: Callable[[str, str], str | None],
    *,
    weight_map: dict[str, str],
) -> list[str]:
    """Install ModelOpt static-FP8 checkpoint tensors as TorchAO weights.

    ModelOpt's HF export stores the already-quantized E4M3 bytes under ``weight``
    and its per-tensor dequantization scales under ``weight_scale`` and
    ``input_scale``. This adapter streams those tensors from the checkpoint and
    constructs TorchAO ``PrototypeFloat8Tensor`` weights directly, avoiding any
    dequantization, recalibration, or re-quantization.

    Args:
        model: Unsharded Cosmos3 network whose linears will be converted in place.
        checkpoint_path: Local root of the ModelOpt diffusers checkpoint.
        key_mapper: Maps a diffusers source key and shard path to a model state key.
        weight_map: Merged checkpoint weight map.

    Returns:
        Sorted fully qualified names of converted linear modules.
    """
    checkpoint_path = Path(checkpoint_path)
    if not is_modelopt_fp8_checkpoint(checkpoint_path):
        raise ValueError(f"Not a ModelOpt FP8/FP8 checkpoint: {checkpoint_path}")

    from safetensors import safe_open
    from torchao.float8.inference import Float8MMConfig
    from torchao.prototype.quantization.float8_static_quant.prototype_float8_tensor import (
        PrototypeFloat8Tensor,
    )
    from torchao.quantization import PerTensor
    from torchao.quantization.quantize_.workflows import QuantizeTensorToFloat8Kwargs

    source_weights_by_shard: dict[str, list[str]] = {}
    for source_key, shard_path in weight_map.items():
        if source_key.endswith(".weight"):
            source_weights_by_shard.setdefault(shard_path, []).append(source_key)

    mm_config = Float8MMConfig(use_fast_accum=True)
    activation_quant_kwargs = QuantizeTensorToFloat8Kwargs(
        float8_dtype=torch.float8_e4m3fn,
        granularity=PerTensor(),
        mm_config=mm_config,
    )
    # Target module FQNs successfully replaced with static-FP8 linear modules.
    converted_fqns: list[str] = []
    # Source FP8 weights mapped to linear modules present in this model variant.
    applicable_source_weight_keys: set[str] = set()
    # Source FP8 weights actually installed; compared with the applicable set
    # after conversion to detect incomplete plans.
    converted_source_weight_keys: set[str] = set()
    # Weight shard -> validated (source weight key, target module FQN) conversions.
    conversion_plan_by_shard: dict[str, list[tuple[str, str]]] = {}
    # Scale shard -> scale keys to preload; scales may be separate from their weights.
    source_scales_by_shard: dict[str, list[str]] = {}
    # Target module FQNs reserved by the plan, used to reject duplicate mappings.
    planned_target_fqns: set[str] = set()

    # Identify and validate every applicable FP8 conversion before replacing any
    # modules. The checkpoint may contain intentionally ignored weights or
    # optional heads absent from this model variant; those must be filtered
    # before requiring their scales.
    for shard_path, source_weight_keys in sorted(source_weights_by_shard.items()):
        full_shard_path = checkpoint_path / shard_path
        with safe_open(full_shard_path, framework="pt", device="cpu") as shard:
            shard_keys = set(shard.keys())
            missing_weight_keys = set(source_weight_keys) - shard_keys
            if missing_weight_keys:
                raise KeyError(
                    f"ModelOpt weight tensor(s) indexed in {full_shard_path} are missing: {sorted(missing_weight_keys)}"
                )
            for source_weight_key in sorted(source_weight_keys):
                tensor_slice = shard.get_slice(source_weight_key)
                if tensor_slice.get_dtype() != "F8_E4M3":
                    continue

                target_weight_key = key_mapper(source_weight_key, shard_path)
                if target_weight_key is None:
                    continue
                if not target_weight_key.endswith(".weight"):
                    raise KeyError(f"ModelOpt FP8 key {source_weight_key!r} has no Cosmos3 weight mapping")
                target_module_fqn = target_weight_key.removesuffix(".weight")
                try:
                    module = model.get_submodule(target_module_fqn)
                except AttributeError:
                    continue
                if not isinstance(module, nn.Linear):
                    raise KeyError(
                        f"ModelOpt FP8 key {source_weight_key!r} mapped to {target_module_fqn!r}, "
                        "which is not an nn.Linear module"
                    )
                if type(module.weight) is not nn.Parameter and not isinstance(module, _ModelOptFloat8Linear):
                    # Distinguish the two ways a weight stops being a plain
                    # parameter: FSDP replaced it with a shard, or something
                    # already quantized it. Reporting the former as the latter
                    # sends debugging in entirely the wrong direction. A module
                    # already swapped by `swap_modelopt_fp8_linears_on_meta` is
                    # neither — its weight may legitimately be a DTensor shard.
                    if type(module.weight).__name__ == "DTensor":
                        raise ValueError(
                            f"ModelOpt FP8 target is an FSDP shard, not a plain parameter: {target_module_fqn}. "
                            "The FP8 swap must run before the network is parallelized."
                        )
                    raise ValueError(f"ModelOpt FP8 target is already quantized: {target_module_fqn}")
                checkpoint_shape = tuple(tensor_slice.get_shape())
                if checkpoint_shape != tuple(module.weight.shape):
                    raise ValueError(
                        f"Shape mismatch for {target_module_fqn}: checkpoint has {checkpoint_shape}, "
                        f"model expects {tuple(module.weight.shape)}"
                    )
                if target_module_fqn in planned_target_fqns:
                    raise ValueError(f"Multiple ModelOpt FP8 weights map to target: {target_module_fqn}")

                source_module_key = source_weight_key.removesuffix(".weight")
                source_scale_keys = (
                    f"{source_module_key}.weight_scale",
                    f"{source_module_key}.input_scale",
                )
                missing_scale_keys = set(source_scale_keys) - weight_map.keys()
                if missing_scale_keys:
                    raise KeyError(
                        f"ModelOpt FP8 tensor {source_weight_key!r} is missing scale tensor(s) "
                        f"from the checkpoint index: {sorted(missing_scale_keys)}"
                    )
                for source_scale_key in source_scale_keys:
                    source_scales_by_shard.setdefault(weight_map[source_scale_key], []).append(source_scale_key)

                applicable_source_weight_keys.add(source_weight_key)
                planned_target_fqns.add(target_module_fqn)
                conversion_plan_by_shard.setdefault(shard_path, []).append((source_weight_key, target_module_fqn))

    if not applicable_source_weight_keys:
        raise ValueError(f"No ModelOpt FP8 linear weights were found in {checkpoint_path}")

    # ModelOpt's consolidated HF export can place the scalar scales in a
    # different shard from the corresponding FP8 weight. Read only those tiny
    # tensors up front, following the root index rather than assuming locality.
    exported_scales: dict[str, torch.Tensor] = {}
    for shard_path, source_scale_keys in sorted(source_scales_by_shard.items()):
        full_shard_path = checkpoint_path / shard_path
        with safe_open(full_shard_path, framework="pt", device="cpu") as shard:
            shard_keys = set(shard.keys())
            missing_scale_keys = set(source_scale_keys) - shard_keys
            if missing_scale_keys:
                raise KeyError(
                    f"ModelOpt scale tensor(s) indexed in {full_shard_path} are missing: {sorted(missing_scale_keys)}"
                )
            for source_scale_key in source_scale_keys:
                scale = shard.get_tensor(source_scale_key)
                if scale.numel() != 1:
                    raise ValueError(f"ModelOpt scale tensor {source_scale_key!r} must contain exactly one value")
                exported_scales[source_scale_key] = scale

    for shard_path, conversions in sorted(conversion_plan_by_shard.items()):
        full_shard_path = checkpoint_path / shard_path
        with safe_open(full_shard_path, framework="pt", device="cpu") as shard:
            for source_weight_key, target_module_fqn in conversions:
                source_module_key = source_weight_key.removesuffix(".weight")
                source_weight_scale_key = f"{source_module_key}.weight_scale"
                source_input_scale_key = f"{source_module_key}.input_scale"
                module = model.get_submodule(target_module_fqn)
                device = module.weight.device

                if isinstance(module, _ModelOptFloat8Linear):
                    # Already swapped on meta before parallelization; the weight
                    # is an E4M3 placeholder, so the compute dtype comes from the
                    # dtype recorded at swap time rather than from the parameter.
                    replacement = module
                    high_precision_dtype = module._modelopt_high_precision_dtype
                    if high_precision_dtype is None:
                        raise ValueError(f"ModelOpt FP8 module has no recorded compute dtype: {target_module_fqn}")
                else:
                    high_precision_dtype = module.weight.dtype
                    replacement = _ModelOptFloat8Linear(
                        module.in_features,
                        module.out_features,
                        bias=False,
                        device="meta",
                        dtype=high_precision_dtype,
                    )
                    replacement.bias = module.bias
                    replacement.train(module.training)

                    parent_fqn, _, child_name = target_module_fqn.rpartition(".")
                    parent = model.get_submodule(parent_fqn) if parent_fqn else model
                    setattr(parent, child_name, replacement)
                    del module

                quantized_data = shard.get_tensor(source_weight_key)
                weight_scale = exported_scales[source_weight_scale_key].reshape(1, 1)
                input_scale = exported_scales[source_input_scale_key].reshape(1, 1)

                existing_param = replacement.weight
                if isinstance(existing_param, DTensor):
                    # FSDP already sharded the placeholder, so only this rank's
                    # slice of the checkpoint tensor is written. The scales are
                    # per-tensor and therefore identical on every rank.
                    local_weight = existing_param.to_local()
                    quantized_data = distribute_tensor(
                        quantized_data.to(device=device),
                        existing_param.device_mesh,
                        existing_param.placements,
                    ).to_local()
                elif isinstance(existing_param.data, PrototypeFloat8Tensor):
                    local_weight = existing_param.data
                else:
                    local_weight = None

                if local_weight is not None:
                    # Fill the parameter that FSDP is already tracking; rebinding
                    # `replacement.weight` here would detach it from the parameter
                    # group and strand the all-gather on a stale tensor.
                    if tuple(local_weight.qdata.shape) != tuple(quantized_data.shape):
                        raise ValueError(
                            f"Shard mismatch for {target_module_fqn}: model shard "
                            f"{tuple(local_weight.qdata.shape)}, checkpoint slice {tuple(quantized_data.shape)}"
                        )
                    local_weight.qdata.copy_(quantized_data)
                    local_weight.scale.copy_(weight_scale)
                    local_weight.act_quant_scale.copy_(input_scale)
                else:
                    # Unswapped legacy path: the module still holds a plain bf16
                    # weight, so build the quantized tensor and bind it directly.
                    replacement.weight = nn.Parameter(
                        PrototypeFloat8Tensor(
                            quantized_data.to(device=device),
                            weight_scale.to(device=device),
                            act_quant_scale=input_scale.to(device=device),
                            block_size=list(quantized_data.shape),
                            mm_config=mm_config,
                            act_quant_kwargs=activation_quant_kwargs,
                            dtype=high_precision_dtype,
                        ),
                        requires_grad=False,
                    )
                replacement._modelopt_weight_loaded = True
                converted_fqns.append(target_module_fqn)
                converted_source_weight_keys.add(source_weight_key)

    missing_conversions = applicable_source_weight_keys - converted_source_weight_keys
    if missing_conversions:
        raise ValueError(f"ModelOpt FP8 weights were not converted: {sorted(missing_conversions)}")
    converted_fqns.sort()
    sharded_count = sum(1 for fqn in converted_fqns if isinstance(model.get_submodule(fqn).weight.data, DTensor))
    log.info(
        f"Loaded {len(converted_fqns)} calibrated ModelOpt FP8 weights into TorchAO "
        f"({sharded_count} sharded / {len(converted_fqns) - sharded_count} replicated)"
    )
    log.debug(f"ModelOpt FP8 matched_fqns={converted_fqns}")
    return converted_fqns


def _get_filter_fn(quantization_config: QuantizationConfig) -> Callable[[nn.Module, str], bool]:
    """Build a module-selection predicate from the quantization config.

    The returned closure captures ``include_regex`` / ``exclude_regex`` and
    implements the selection policy documented on :class:`QuantizationConfig`.
    Each key is treated as a regular expression and matched against a module's
    FQN with :func:`re.search` (a plain substring remains a valid pattern, so
    existing substring-style keys keep working). It is passed to torchao as
    ``filter_fn`` (for ``quantize_``), which expects a
    ``(module, fqn) -> bool`` signature.

    Args:
        quantization_config: Config carrying the include/exclude key lists.

    Returns:
        A predicate suitable for torchao's ``filter_fn`` / ``module_filter_fn``.
    """

    include_patterns: list[re.Pattern[str]] = []
    for pattern in quantization_config.include_regex:
        try:
            include_patterns.append(re.compile(pattern))
        except re.error as error:
            raise ValueError(f"Invalid include_regex pattern {pattern!r}: {error}") from error

    exclude_patterns: list[re.Pattern[str]] = []
    for pattern in quantization_config.exclude_regex:
        try:
            exclude_patterns.append(re.compile(pattern))
        except re.error as error:
            raise ValueError(f"Invalid exclude_regex pattern {pattern!r}: {error}") from error

    def _filter_fn(mod: nn.Module, name: str) -> bool:
        """Decide whether a single module should be quantized.

        Used by preflight and torchao as each walks the model recursively. A
        module is selected only when ALL of the following hold:

        1. It is an ``nn.Linear`` (the only layer type these recipes support).
        2. ``include_regex`` is empty (include everything) OR the module's FQN
           matches at least one include pattern.
        3. The module's FQN matches none of the ``exclude_regex`` patterns.

        Each include/exclude key is treated as a regular expression and matched
        against the FQN with :func:`re.search`, so the pattern can match anywhere
        in the name (a plain substring is still a valid regex, preserving the
        previous substring-match behavior, while enabling anchors like ``^``/``$``,
        alternation, character classes, etc.).

        Note the parenthesization around the include check: ``and`` binds tighter
        than ``or`` in Python, so without it the ``nn.Linear`` and exclude
        checks would not apply across both include branches.

        Args:
            mod (torch.nn.Module): The module that is being processed.
            name (str): A fully qualified name of the module that is being processed.

        Return:
            True if the module should be quantized, False otherwise.
        """
        # torch.compile inserts `_orig_mod` into FQNs; hide it from user-facing regex matching.
        canonical_name = ".".join(part for part in name.split(".") if part != "_orig_mod")
        return (
            isinstance(mod, nn.Linear)
            and (not include_patterns or any(pattern.search(canonical_name) for pattern in include_patterns))
            and not any(pattern.search(canonical_name) for pattern in exclude_patterns)
        )

    return _filter_fn


def _get_validated_quantization_fqns(model: nn.Module, filter_fn: Callable[[nn.Module, str], bool]) -> list[str]:
    """Validate the selected modules and return their sorted FQNs."""
    matched_modules = sorted(
        ((name, module) for name, module in model.named_modules() if filter_fn(module, name)),
        key=lambda item: item[0],
    )
    if not matched_modules:
        raise ValueError("No nn.Linear modules matched the quantization selection")
    matched_fqns = [name for name, _ in matched_modules]
    already_quantized_fqns = [name for name, module in matched_modules if type(module.weight) is not nn.Parameter]
    if already_quantized_fqns:
        raise ValueError(f"Quantization targets are already quantized: {', '.join(already_quantized_fqns)}")
    return matched_fqns


def apply_quantization_inplace(model: nn.Module, quantization_config: QuantizationConfig) -> list[str]:
    """Apply quantization in place via ``quantize_`` (replaces weights with quantized tensors).

    This is the replication path. ``quantize_`` replaces each weight with a
    quantized tensor subclass as the live parameter, which only works when the
    parameters are plain tensors. It therefore cannot be applied to an already
    FSDP-sharded model (the params are ``DTensor`` shards), so it is restricted
    to replicated inference (``data_parallel_shard_degree == 1``).

    These configs (``MXDynamicActivationMXWeightConfig`` /
    ``NVFP4DynamicActivationNVFP4WeightConfig`` /
    ``Float8DynamicActivationFloat8WeightConfig``) are inference-only (PTQ) and
    have no backward support. For the sharded case use ``apply_quantization``
    (the module-swap path) instead; both functions are currently inference
    paths, selected by whether FSDP is sharding the model.

    Returns:
        Sorted fully qualified names of the matched modules.
    """
    # No-op when quantization is disabled.
    if quantization_config.method is None:
        return []

    filter_fn = _get_filter_fn(quantization_config)
    matched_fqns = _get_validated_quantization_fqns(model, filter_fn)

    from torchao.prototype.mx_formats import (
        MXDynamicActivationMXWeightConfig,
        NVFP4DynamicActivationNVFP4WeightConfig,
    )
    from torchao.quantization import (
        Float8DynamicActivationFloat8WeightConfig,
        PerRow,
        PerTensor,
        quantize_,
    )

    def _reclaim_and_log_gpu_memory(tag: str) -> None:
        # synchronize() + gc.collect() + empty_cache() are load-bearing, not passive
        # observability: torch.mem_get_info reports memory free at the CUDA-driver level.
        # synchronize() first drains any in-flight device work (e.g. async copies from the
        # checkpoint load path) so their allocations retire before we measure; otherwise the
        # baseline could over-report free memory. PyTorch's caching allocator then holds
        # freed blocks in its own pool without returning them to the driver, so empty_cache()
        # flushes that pool back to the driver.
        torch.cuda.synchronize()
        gc.collect()
        torch.cuda.empty_cache()
        free, total = torch.cuda.mem_get_info(torch.device("cuda", torch.cuda.current_device()))
        log.info(f"GPU memory ({tag}): {free / 2**30:.2f} GiB free / {total / 2**30:.2f} GiB total")

    _reclaim_and_log_gpu_memory("before quantization")

    if quantization_config.method == "mxfp8":
        quantize_(
            model,
            config=MXDynamicActivationMXWeightConfig(),
            filter_fn=filter_fn,
        )

    elif quantization_config.method == "nvfp4":
        # use_triton_kernel=False avoids torchao's fused NVFP4 Triton kernel, which
        # requires the external `mslk` package. Prebuilt mslk wheels are linked
        # against upstream torch and fail to load against NVIDIA's NGC custom torch
        # builds (ABI mismatch on `torch::Library::_def`), so we use torchao's
        # built-in NVFP4 path instead.
        quantize_(
            model,
            config=NVFP4DynamicActivationNVFP4WeightConfig(use_triton_kernel=False),
            filter_fn=filter_fn,
        )

    elif quantization_config.method == "fp8":
        # Hopper-compatible FP8. Unlike mxfp8 / nvfp4 (block-scaled MX / NVFP4
        # formats whose accelerated kernels require Blackwell sm_100 tensor
        # cores), this is plain e4m3 dynamic-activation + fp8-weight quantization
        # executed via torch._scaled_mm, which is supported on Hopper (sm_90) and
        # Ada (sm_89). Scaling granularity is user-selectable: PerRow (rowwise,
        # better accuracy) or PerTensor (single scale, slightly faster); both are
        # supported on Hopper.
        granularity = PerRow() if quantization_config.fp8_granularity == "per_row" else PerTensor()
        quantize_(
            model,
            config=Float8DynamicActivationFloat8WeightConfig(granularity=granularity),
            filter_fn=filter_fn,
        )

    else:
        raise ValueError(f"Unsupported quantization method: {quantization_config.method}")

    _reclaim_and_log_gpu_memory("after quantization")
    log.info(f"Applied runtime PTQ method={quantization_config.method}, matched_count={len(matched_fqns)}")
    log.debug(f"Runtime PTQ matched_fqns={matched_fqns}")
    return matched_fqns