Diff Coverage

Diff: origin/master...HEAD, staged and unstaged changes

Source File Diff Coverage (%) Missing Lines
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_backbone.py 6.4% 239,365-366,370,623-626,654-656,659,662,665,669-672,678-683,685-687,689-697,704-705,708-710,713-724,726-728,730-734,736,738-742,746-755,758,761,766-774
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_bwd_overhead.py 21.7% 76,120,124-132,139,150,155,161,171,176,184
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_context.py 60.0% 76,85
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_hook_manager.py 0.0% 160-162,171-173,175-178,180-185,497-498
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/estimate_v2.py 0.0% 44
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/body.py 0.0% 70,72,74,76,106,108,110,112
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/comm.py 17.9% 56-59,85-87,214-218,246-250,278-282,339-341,343,349,358-363,365,370-371,375,382,387-388,392,399,404-405,409,416,421-427,432,440-441,446-449,451-452,454-455
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/head.py 25.0% 39,61,67-68,77-78,90-91,95,99-104,108-109,113
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/tail.py 22.0% 135-139,143-145,149-152,156,159,164-168,172-174,178-181,185,188,193-200,204-207,211-214,218,228,246,262-263,267-268,272,276-277,281,285-290,353,362,371
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/utils.py 0.0% 80-81,83
hyper_parallel/auto_parallel/sapp_nd/nd/balancing_adapter.py 25.0% 41-43
hyper_parallel/auto_parallel/sapp_nd/nd/common/_cost_model_variables.py 100%  
hyper_parallel/auto_parallel/sapp_nd/nd/common/arch_hooks.py 0.0% 268
hyper_parallel/auto_parallel/sapp_nd/nd/common/cost_model_preprocess.py 12.0% 144,283-284,286-290,292,296-304,307-308,310,319-320,322-329,351-372,374,377-379,384-386,388,401-404,440
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/_cost_model_parser.py 11.1% 110-126,145-147,153-156
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_hyper.py 0.0% 379-380,499
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_hyperparallel.py 0.0% 146-150,152,172,178-179,301-303,314
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_mindformers.py 5.7% 59,171,174-175,178,181,184-185,188-194,196-198,202,209-212,214-218,220-221,302-303,308
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_mindspeed.py 0.0% 154-155,224-225,266,270-274,276-277,286
hyper_parallel/auto_parallel/sapp_nd/nd/common/hardware.py 59.3% 91-92,99,134-139,141,143
hyper_parallel/auto_parallel/sapp_nd/nd/debug.py 50.0% 66,71-72,584,614-616
hyper_parallel/auto_parallel/sapp_nd/nd/dimensions.py 22.0% 33-35,247-252,256-257,262-264,267-270,273,276,278,285,292,295-300,304-313,319-328,332-338,341-345,349,381-382
hyper_parallel/auto_parallel/sapp_nd/nd/global_config.py 23.5% 113,122,168-169,186,188,274-280
hyper_parallel/auto_parallel/sapp_nd/nd/parallelize.py 13.0% 315-322,324,327-331,340,350,441-442,472,477-489,494-502,506-507,511,523-524,529
hyper_parallel/auto_parallel/sapp_nd/perf_estimation/comm_time.py 8.4% 58-70,75-78,80-87,89,108-114,123-124,128,149-154,176,194,229,363-365,370-373,375,378,381,384-385,390-392,394-397,399,402-422,424-427,429,434-435,437-439,441-444,446-447,456-457,464,469-470,472,474-476,478-483,492-493,499,501,503,508,513-519,528-529,535,540-546,552-553,559,564-567,570-572,576,581-584,586-587,589-590,594-597,599-600,602-604,606-607,667,801,803-804,808-809,811,823-824,826-829,831-835,841,845,848,853,855,864,890-897,903-910,916-920,927-928,934-935,941-947,949-951,993-994,996,998-999,1001,1004-1005,1007,1009-1018,1020-1024,1026-1030,1035,1039,1041
hyper_parallel/auto_parallel/sapp_nd/perf_estimation/estimate.py 14.4% 59,62,67-68,73,76,86-90,117-120,131-133,137-139,145,150-162,176-178,198,201-203,207,279-281,292,396,402-403,405-412,417-418,436,450-451,534,538,546,549-550,557,561-562,567-570,574-580,583-595,599,602-604,644,646-648,650-651,653-654
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_backbone.py
235
236
237
238
239
240
241
242
243
                real_id = self._ctx.real_lay_ids[chunk_id][i][lay_id]
                name = f"Lay_{real_id}_{lay_type.name[0]}"
                idx = chunk_id * len(dyn_mem_i) + lay_id
                stat = self.mb(stat_mem_i[chunk_id][lay_id])
                dyn = self.mb(sum(dyn_mem_i[chunk_id][lay_id]))
                ax.bar(
                    0.1,
                    [dyn],
                    bottom=bottoms["dyn"],
361
362
363
364
365
366
367
368
369
370
371
372
373
374
                mt: 0 for mt in list(MemType)
            }
        for mem_type in list(MemType):
            val = self._ctx.accu_mem_type[mem_type]
            if mem_type in (MemType.AG_COMM, MemType.A2A_COMM):
                stage_logs[stage_id].accu_mem_type[mem_type] = max(
                    stage_logs[stage_id].accu_mem_type[mem_type], val
                )
            else:
                stage_logs[stage_id].accu_mem_type[mem_type] += val

    def __preprocess_layer_custom_config_list(self, stages: list) -> list:
        """flatten layer_custom_config for backbone estimation"""
        flatten = sum(
619
620
621
622
623
624
625
626
627
628
629
630
                    self._ctx.current_lay_id = lay_id
                    self._ctx.current_node = node
                    static_mem = self._inner_static_mem()
                    sm["stat"][stage_id][chunk_id][lay_id] = static_mem
                    dyn_val = self._inner_dynamic_mem()
                    sm["dyn"][stage_id][chunk_id][lay_id] = dyn_val
                    if os.environ.get("_SAPP_DEBUG_DYN") == "1":
                        _sys.stderr.write(
                            f"DEBUG_DYN stage={stage_id} chunk={chunk_id} lay={lay_id} "
                            f"node={node} active={dyn_val[0] if isinstance(dyn_val, tuple) else dyn_val} "
                            f"comm={dyn_val[1] if isinstance(dyn_val, tuple) else 0} "
                            f"mf={self._ctx.micro_factor} h={self._ccfg.h} "
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
                self.__update_stage_logs(sm["logs"], stage_id)

    def __compute_raw_dynamic(self, num_stages, stages, record_lay_types, sm, safety_buffer):
        """Compute raw dynamic memory per stage."""
        raw_dyn = []
        for stage_id in range(num_stages):
            act_sum = sum(
                sum(aval for aval, _ in cell) for cell in sm["dyn"][stage_id]
            )
            comm_peak = max(
                max(comm for _, comm in cell) for cell in sm["dyn"][stage_id]
            )
            dyn = act_sum + comm_peak
            self._ctx.init_tmp_buff()
            if not self._ccfg.freeze:
                dyn += self._overhead_obj.estimate(
                    stages, stage_id, record_lay_types
                )
                self.__update_stage_logs(sm["logs"], stage_id)
            if dyn > 0:
                dyn += safety_buffer
            raw_dyn.append(dyn)
        return raw_dyn

    @staticmethod
    def __adjust_framework_overhead(fw_oh: int, d_shard: int, d_replicate: int, tp: int,
                                     ccfg=None) -> int:
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
    @staticmethod
    def __adjust_framework_overhead(fw_oh: int, d_shard: int, d_replicate: int, tp: int,
                                     ccfg=None) -> int:
        """Adjust framework overhead based on FSDP/HSDP/TP configuration."""
        hsdp_coeff = getattr(ccfg, 'fw_oh_hsdp_shard_coeff', 0.03) if ccfg else 0.03
        fsdp_coeff = getattr(ccfg, 'fw_oh_fsdp_shard_coeff', 0.01) if ccfg else 0.01
        rep_discount = getattr(ccfg, 'fw_oh_replicate_discount', 0.025) if ccfg else 0.025
        shard_rep_interact = getattr(ccfg, 'fw_oh_shard_replicate_interact', 0.03) if ccfg else 0.03
        tp_threshold = getattr(ccfg, 'fw_oh_tp_hsdp_threshold', 8) if ccfg else 8
        tp_extra_ratio = getattr(ccfg, 'fw_oh_tp_hsdp_extra_ratio', 0.28) if ccfg else 0.28

        if fw_oh > 0 and d_shard > 1:
            if d_replicate > 1:
                fw_oh = int(fw_oh * (1.0 + hsdp_coeff * (d_shard - 1)))
            else:
                fw_oh = int(fw_oh * (1.0 + fsdp_coeff * (d_shard - 1)))
        if fw_oh > 0 and d_replicate > 1:
            fw_oh = int(fw_oh / (1.0 + rep_discount * (d_replicate - 1)))
        if fw_oh > 0 and d_shard > 1 and d_replicate > 1:
            fw_oh = int(fw_oh / (1.0 + shard_rep_interact * (d_shard - 1) * (d_replicate - 1) / d_shard))
        if fw_oh > 0 and d_replicate > 1 and tp >= tp_threshold:
            tp_hsdp_extra = int(fw_oh * tp_extra_ratio)
            fw_oh += tp_hsdp_extra
        return fw_oh

    def __build_stage_insight(
        self, stage_id, raw_dyn, total_raw, total_fw_oh,
        num_stages, sm, verbose, spec_stage_id, insights,
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
        self, stage_id, raw_dyn, total_raw, total_fw_oh,
        num_stages, sm, verbose, spec_stage_id, insights,
    ):
        """Build memory insight dict for a single stage."""
        ins = {}  # Mem Insights purpose
        ins["Static"] = sum(
            sum(mem for mem in c) for c in sm["stat"][stage_id]
        )
        ins["Dynamic"] = raw_dyn[stage_id]
        if total_raw > 0 and ins["Dynamic"] > 0:
            fw_oh_share = total_fw_oh * (
                0.5 / num_stages + 0.5 * raw_dyn[stage_id] / total_raw
            )
            ins["Dynamic"] += int(fw_oh_share)
        elif ins["Dynamic"] > 0:
            ins["Dynamic"] += total_fw_oh // num_stages
        stage_accu = sm["logs"][stage_id].accu_mem_type
        ins["ModelParameters"] = self.mb(stage_accu[MemType.MODEL_PARAM])
        ins["OptimizerStates"] = self.mb(stage_accu[MemType.OPTIM_STATE])
        ins["AccumulGradients"] = self.mb(stage_accu[MemType.ACCU_GRAD])
        ins["Attn"] = self.mb(stage_accu[MemType.ATTN_ACTIV])
        ins["FFn"] = self.mb(stage_accu[MemType.FFN_ACTIV])
        ins["Norm"] = self.mb(stage_accu[MemType.NORM_ACTIV])
        ins["AllGather Comm"] = self.mb(stage_accu[MemType.AG_COMM])
        ins["All2All Comm"] = self.mb(stage_accu[MemType.A2A_COMM])

        if self._ccfg.cp > 1:
            cp_memory = EvalBody.act_cp_layer(self._ccfg, self._ctx)
            cp_comm_buffer = EvalLayerComm.cp_comm_buffer(self._ccfg, self._ctx)

            ins["CP KV Cache"] = self.mb(cp_memory.kv_cache_memory)
            ins["CP Attn Scores"] = self.mb(cp_memory.attention_scores_memory)
            ins["CP Softmax"] = self.mb(cp_memory.softmax_outputs_memory)
            ins["CP Comm Buffer"] = self.mb(cp_comm_buffer)
            ins["CP Reduction"] = self.mb(cp_memory.total_reduction)

        ins["Node Log"] = sm["logs"][stage_id].node_compute_log
        # VERBOSE
        if verbose and spec_stage_id in (-1, stage_id):
            self.__verbose_insights(sm, stage_id, ins)
        ins["Static"] = self.mb(ins["Static"])
        ins["Dynamic"] = self.mb(ins["Dynamic"])
        insights.append(ins)

    def __postprocess_stages(self, *args):
        """Build memory insights from raw stage evaluation buffers."""
        stages, record_lay_types = args[0], args[1]
        sm = args[2]
        verbose, spec_stage_id = args[3], args[4]
        insights = args[5]
        num_stages = self._ccfg.p
        safety_buffer = int(getattr(self._ccfg, 'safety_buffer_gb', 1)) * 1024 * 1024 * 1024
        raw_dyn = self.__compute_raw_dynamic(num_stages, stages, record_lay_types, sm, safety_buffer)
        if os.environ.get("_SAPP_DEBUG_DYN") == "1":
            for sid in range(num_stages):
                act_s = sum(
                    sum(aval for aval, _ in cell) for cell in sm["dyn"][sid]
                )
                comm_p = max(
                    max(comm for _, comm in cell) for cell in sm["dyn"][sid]
                )
                _sys.stderr.write(
                    f"DEBUG_POST stage={sid} act_sum={act_s/(1024**2):.1f}MB "
                    f"comm_peak={comm_p/(1024**2):.1f}MB "
                    f"raw_dyn={raw_dyn[sid]/(1024**2):.1f}MB\n"
                )
        total_raw = sum(raw_dyn)
        fw_oh = self._ccfg.framework_overhead
        d_shard = self._ccfg.d_shard_or_d
        d_replicate = self._ccfg.d // d_shard if d_shard > 0 else 1
        tp = self._ccfg.t
        fw_oh = self.__adjust_framework_overhead(fw_oh, d_shard, d_replicate, tp, ccfg=self._ccfg)
        total_fw_oh = fw_oh * num_stages
        for stage_id in range(num_stages):
            self.__build_stage_insight(
                stage_id, raw_dyn, total_raw, total_fw_oh, num_stages,
                sm, verbose, spec_stage_id, insights,
            )
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_bwd_overhead.py
72
73
74
75
76
77
78
79
80
        """potential overhead due to imbalanced chunks"""

        def dyn(chunk_id):
            """chunk total mem"""
            return sum(sum(v) for v in dyn_mem_i[chunk_id])

        # less mem
        micro_left = self._ccfg.m - self._ccfg.p
        vpp = self._ccfg.vp
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
        return max_overhead

    @staticmethod
    def _is_full_rec(node: Any) -> bool:
        return node == LayerType.FULL_REC_LAYER

    def __bwd_dyn_mem(self, is_recomputed: bool, **kwargs: Any) -> float:
        """Compute backward overhead dynamic memory."""
        activation, comm = self._inner_dynamic_mem(**kwargs)
        if is_recomputed:
            return activation + comm
        bwd_workspace = 0.0
        current_node = getattr(self._ctx, "current_node", None)
        if current_node == LayerType.OUTPUT_LAYER:
            t = getattr(self._ccfg, "t", 1)
            if t > 1:
                bwd_workspace = (
                    (1.0 - 1.0 / t)
                    * getattr(self._ccfg, "s", 0)
                    * getattr(self._ccfg, "b", 0)
                    * getattr(self._ccfg, "v", 0)
135
136
137
138
139
140
141
142
143
                    * getattr(self._ccfg, "b", 0)
                    * getattr(self._ccfg, "v", 0)
                    * getattr(self._ccfg, "bytes_compute", 0)
                )
        return comm + bwd_workspace

    def __stage_bwd_overhead_1f1b(
        self, stages: list, stage_id: int, record_lay_types: dict
    ) -> float:
146
147
148
149
150
151
152
153
154
155
156
157
158
159
        if stages[stage_id][self._ccfg.vp - 1]:
            last_node = self._fetch_node_and_switch_env(
                stages, record_lay_types, stage_id, -1, -1
            )
            if self._is_full_rec(last_node):
                # full rec -> not rec + grad
                # not rec -> grad
                self._ctx.current_lay_id = f"rec_{self._ctx.current_lay_id}"
                self._ctx.current_node = LayerType.NOT_REC_LAYER
                res = self.__bwd_dyn_mem(
                    is_recomputed=True, default_micro_factor=1
                )
            else:
                self._ctx.current_node = last_node
157
158
159
160
161
162
163
164
165
                )
            else:
                self._ctx.current_node = last_node
                self._ctx.current_lay_id = f"G_{self._ctx.current_lay_id}"
                res = self.__bwd_dyn_mem(
                    is_recomputed=False, default_micro_factor=1
                )
                if (
                    last_node == LayerType.OUTPUT_LAYER
167
168
169
170
171
172
173
174
175
176
177
178
179
180
                ):
                    last_mtp = self._fetch_node_and_switch_env(
                        stages, record_lay_types, stage_id, -1, -2
                    )
                    if self._is_full_rec(last_mtp):
                        self._ctx.current_lay_id = (
                            f"rec_{self._ctx.current_lay_id}"
                        )
                        self._ctx.current_node = LayerType.NOT_REC_LAYER
                        res += self.__bwd_dyn_mem(
                            is_recomputed=True, default_micro_factor=1
                        )
                    else:
                        self._ctx.current_lay_id = (
180
181
182
183
184
185
186
187
                        self._ctx.current_lay_id = (
                            f"G_{self._ctx.current_lay_id}"
                        )
                        self._ctx.current_node = last_mtp
                        res += self.__bwd_dyn_mem(
                            is_recomputed=False, default_micro_factor=1
                        )
        return res
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_context.py
72
73
74
75
76
77
78
79
80
    ep_imbalanced: Any = None
    fsdp_grad: Any = None

    def __repr__(self):
        parts = [
            f"dyn.comm.dp={_qname(self.dp)}",
            f"dyn.comm.tp={_qname(self.tp)}",
            f"dyn.comm.cp={_qname(self.cp)}",
            f"dyn.comm.ep={_qname(self.ep)}",
81
82
83
84
85
86
87
88
89
            f"dyn.comm.fsdp={_qname(self.fsdp)}",
            f"dyn.comm.hsdp={_qname(self.hsdp)}",
            f"dyn.comm.fsdp_grad={_qname(self.fsdp_grad)}",
        ]
        return ", ".join(parts)


@dataclass
class NodeComputeEval:
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/_hook_manager.py
156
157
158
159
160
161
162
163
164
165
166
        dyn_cp_comm = kwargs.get("dyn_cp_comm", c_comm)
        dyn_ep_comm = kwargs.get("dyn_ep_comm", c_comm)
        dyn_ep_comm_balanced = kwargs.get("dyn_ep_comm_balanced", None)
        dyn_ep_comm_imbalanced = kwargs.get("dyn_ep_comm_imbalanced", None)
        dyn_fsdp_comm = kwargs.get("dyn_fsdp_comm", c_comm)
        dyn_hsdp_comm = kwargs.get("dyn_hsdp_comm", c_comm)
        dyn_fsdp_grad_comm = kwargs.get("dyn_fsdp_grad_comm", c_comm)
        if not self.__is_valid_eval_func(dyn_dp_comm):
            dyn_dp_comm = self._ctx.node_eval[target_node].dyn.comm.dp
        if not self.__is_valid_eval_func(dyn_tp_comm):
            dyn_tp_comm = self._ctx.node_eval[target_node].dyn.comm.tp
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
        if not self.__is_valid_eval_func(dyn_cp_comm):
            dyn_cp_comm = self._ctx.node_eval[target_node].dyn.comm.cp
        if not self.__is_valid_eval_func(dyn_ep_comm):
            dyn_ep_comm = self._ctx.node_eval[target_node].dyn.comm.ep
        if not self.__is_valid_eval_func(dyn_fsdp_comm):
            if target_node in self._ctx.node_eval:
                dyn_fsdp_comm = self._ctx.node_eval[target_node].dyn.comm.fsdp
            else:
                dyn_fsdp_comm = 0
        if not self.__is_valid_eval_func(dyn_hsdp_comm):
            if target_node in self._ctx.node_eval:
                dyn_hsdp_comm = self._ctx.node_eval[target_node].dyn.comm.hsdp
            else:
                dyn_hsdp_comm = 0
        if not self.__is_valid_eval_func(dyn_fsdp_grad_comm):
            if target_node in self._ctx.node_eval:
                dyn_fsdp_grad_comm = self._ctx.node_eval[target_node].dyn.comm.fsdp_grad
            if not self.__is_valid_eval_func(dyn_fsdp_grad_comm):
                dyn_fsdp_grad_comm = 0
        comm_cls_obj = cls_obj
        if self.is_regular_layer(target_node):
            comm_cls_obj = EvalLayerComm
        ep_balanced = (
493
494
495
496
497
498
499
500
501
502
                "dyn_ep_comm": comm_cfg.ep,
                "dyn_fsdp_comm": comm_cfg.fsdp,
                "dyn_hsdp_comm": comm_cfg.hsdp,
            }
            if hasattr(comm_cfg, "fsdp_grad"):
                comm_kwargs["dyn_fsdp_grad_comm"] = comm_cfg.fsdp_grad
            if hasattr(comm_cfg, "ep_balanced"):
                comm_kwargs["dyn_ep_comm_balanced"] = comm_cfg.ep_balanced
            if hasattr(comm_cfg, "ep_imbalanced"):
                comm_kwargs["dyn_ep_comm_imbalanced"] = comm_cfg.ep_imbalanced
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/estimate_v2.py
40
41
42
43
44
45
46
47
48

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._child_cls = self
        self.ppb = None

    def reset_config(self) -> None:
        """reset current config"""
        self.update_config(self.config_path)
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/body.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
        non_exp_p, routed_p, shared_p = ctx.eval.num_p(ccfg, ctx)
        # FSDP stores sharded parameters in compute dtype (e.g. bf16=2 bytes),
        # not param_init_type (fp32=4 bytes).  Without this, param memory is
        # overcounted by bytes_p/bytes_compute (typically 2x).
        bytes_param = ccfg.bytes_compute if getattr(ccfg, "fsdp", False) else ccfg.bytes_p
        # Routed experts: EP sharding
        routed_mem = routed_p / ccfg.ep * bytes_param / ccfg.shard_p_os_exp
        # Shared experts: partial DP sharding
        shared_mem = shared_p * bytes_param / ccfg.shard_p_os_exp_partial
        # Non expert
        non_exp_mem = non_exp_p * bytes_param / ccfg.shard_p_os_non_exp_partial
        # NOTE: FSDP all-gather buffer was previously included here but is a
        # dynamic (temporary) buffer, not static param memory.  It is already
        # accounted for in the dynamic comm path via ``fsdp_buffer_comm`` in
        # the YAML config (``comm_expr: max(fsdp + hsdp, fsdp_grad)
102
103
104
105
106
107
108
109
110
111
112
113
114
115
        """gradients"""
        non_exp_p, routed_p, shared_p = ctx.eval.num_p(ccfg, ctx)
        # FSDP stores sharded gradients in compute dtype (e.g. bf16=2 bytes),
        # not fp32 (4 bytes).  Without this, gradient memory is overcounted.
        bytes_g = ccfg.bytes_compute if getattr(ccfg, "fsdp", False) else ccfg.bytes_grad
        # Routed experts
        routed_mem = routed_p / ccfg.ep * bytes_g / ccfg.shard_grad_exp
        # Shared experts: use shard_grad_exp_partial (independent of os sharding)
        shared_mem = shared_p * bytes_g / ccfg.shard_grad_exp_partial
        # Non expert
        non_exp_mem = non_exp_p * bytes_g / ccfg.shard_grad_non_exp
        return non_exp_mem + routed_mem + shared_mem

    # No recompute and select recompute
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/comm.py
52
53
54
55
56
57
58
59
60
61
62
63
            dp_comm_non_exp += non_exp / (ccfg.cp * ccfg.t)
            dp_comm_non_exp += non_exp / ccfg.t
        # Non expert ZeRO LvL 3 / FSDP
        if ccfg.comm_d_non_exp == 3:
            dp_comm_non_exp += non_exp / (ccfg.cp * ccfg.t)
            if not ccfg.fsdp:
                dp_comm_non_exp += non_exp / ccfg.t
                dp_comm_non_exp += non_exp / ccfg.t
        return dp_comm_non_exp

    @staticmethod
    def dp_comm_exp(ccfg: CostModelConfig, ctx: Context) -> float:
81
82
83
84
85
86
87
88
89
90
91
            dp_comm_exp += exp_param_size / max(ccfg.ep, ccfg.t_exp)
        # Expert ZeRO LvL 3 / FSDP
        if ccfg.comm_d_exp == 3:
            dp_comm_exp += exp_param_size / (ccfg.cp * ccfg.t_exp * ccfg.ep)
            if not ccfg.fsdp:
                dp_comm_exp += exp_param_size / max(ccfg.ep, ccfg.t_exp)
                dp_comm_exp += exp_param_size / max(ccfg.ep, ccfg.t_exp)
        return dp_comm_exp

    @staticmethod
    def dp_comm_layer(ccfg: CostModelConfig, ctx: Context) -> float:
210
211
212
213
214
215
216
217
218
219
220
221
222
        per layer, routed through TP bandwidth.
        Result is in bytes (like TP activation comm), unlike CP/DP which are
        in element counts (parameter comm).
        """
        if ccfg.comm_ep == 0:
            return 0
        if ccfg.n_exp <= 1:
            return 0
        if ccfg.ep <= 1:
            return 0
        t_local = mb * ccfg.n_chosen_exp * ccfg.s * ccfg.b / ccfg.cp
        t_cross = t_local * (ccfg.ep - 1) / ccfg.ep
        return t_cross * ccfg.h * ccfg.bytes_compute * 2 * ccfg.comm_ep
242
243
244
245
246
247
248
249
250
251
252
253
254

        Result is in bytes (like TP activation comm), unlike CP/DP which are
        in element counts (parameter comm).
        """
        if ccfg.comm_ep == 0:
            return 0
        if ccfg.n_exp <= 1:
            return 0
        if ccfg.ep <= 1:
            return 0
        tokens = ccfg.tokens_per_expert
        if not tokens:
            return EvalLayerComm.ep_comm_layer_balanced(ccfg, ctx, mb)
274
275
276
277
278
279
280
281
282
283
284
285
286

    @staticmethod
    def ep_comm_layer(ccfg: CostModelConfig, ctx: Context, mb: int) -> float:
        """EP comm dispatcher: balanced or imbalanced based on tokens_per_expert."""
        if ccfg.comm_ep == 0:
            return 0
        if ccfg.n_exp <= 1:
            return 0
        if ccfg.ep <= 1:
            return 0
        if ccfg.tokens_per_expert is not None:
            return EvalLayerComm.ep_comm_layer_imbalanced(ccfg, ctx, mb)
        return EvalLayerComm.ep_comm_layer_balanced(ccfg, ctx, mb)
335
336
337
338
339
340
341
342
343
344
345
346
347

    @staticmethod
    def fsdp_comm_layer(ccfg: CostModelConfig, ctx: Context) -> float:
        """FSDP/HSDP communication volume estimation."""
        non_exp, routed, shared = ctx.eval.num_p(ccfg, ctx)
        exp = routed + shared
        d_shard = ccfg.d_shard_or_d

        non_exp_comm = (
            ccfg.comm_fsdp
            * non_exp / (d_shard * ccfg.cp * ccfg.t)
            * ccfg.bytes_compute
            * 2
345
346
347
348
349
350
351
352
353
            * non_exp / (d_shard * ccfg.cp * ccfg.t)
            * ccfg.bytes_compute
            * 2
        )
        exp_comm = (
            ccfg.comm_fsdp
            * exp / (d_shard * ccfg.ep * ccfg.cp * ccfg.t_exp)
            * ccfg.bytes_compute
            * 2
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
            if ccfg.n_exp > 1
            else 0
        )

        if getattr(ccfg, "comm_hsdp", 0) > 0:
            d_replicate = ccfg.d // d_shard
            sharded_non_exp = non_exp / (d_shard * ccfg.cp * ccfg.t)
            sharded_exp = exp / (d_shard * ccfg.cp * ccfg.t_exp) if ccfg.n_exp > 1 else 0
            hsdp_comm = ccfg.comm_hsdp / d_replicate * (sharded_non_exp + sharded_exp) * ccfg.bytes_compute
            non_exp_comm += hsdp_comm

        return non_exp_comm + exp_comm

    @staticmethod
    def fsdp_buffer_layer(ccfg: CostModelConfig, ctx: Context) -> float:
        """FSDP/HSDP all-gather buffer memory (bytes)."""
        non_exp, routed, shared = ctx.eval.num_p(ccfg, ctx)
        non_exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * non_exp * ccfg.bytes_p / (ccfg.cp * ccfg.t)
        )
        exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * (routed + shared) * ccfg.bytes_p
            / (ccfg.ep * ccfg.cp * ccfg.t_exp)
            if ccfg.n_exp > 1
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
            / (ccfg.ep * ccfg.cp * ccfg.t_exp)
            if ccfg.n_exp > 1
            else 0
        )
        return non_exp_buf + exp_buf

    @staticmethod
    def fsdp_buffer_comm(ccfg: CostModelConfig, ctx: Context) -> float:
        """FSDP/HSDP all-gather buffer size (bytes)."""
        non_exp, routed, shared = ctx.eval.num_p(ccfg, ctx)
        non_exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * non_exp * ccfg.bytes_compute / (ccfg.cp * ccfg.t)
        )
        exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * (routed + shared) * ccfg.bytes_compute
            / (ccfg.ep * ccfg.cp * ccfg.t_exp)
            if ccfg.n_exp > 1
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
            / (ccfg.ep * ccfg.cp * ccfg.t_exp)
            if ccfg.n_exp > 1
            else 0
        )
        return non_exp_buf + exp_buf

    @staticmethod
    def fsdp_grad_buffer_comm(ccfg: CostModelConfig, ctx: Context) -> float:
        """FSDP/HSDP gradient reduce-scatter buffer size (bytes)."""
        non_exp, routed, shared = ctx.eval.num_p(ccfg, ctx)
        non_exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * non_exp * ccfg.bytes_grad / (ccfg.cp * ccfg.t)
        )
        exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * (routed + shared) * ccfg.bytes_grad
            / (ccfg.ep * ccfg.cp * ccfg.t_exp)
            if ccfg.n_exp > 1
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
            / (ccfg.ep * ccfg.cp * ccfg.t_exp)
            if ccfg.n_exp > 1
            else 0
        )
        return non_exp_buf + exp_buf

    @staticmethod
    def hsdp_inter_buffer_comm(ccfg: CostModelConfig, ctx: Context) -> float:
        """HSDP inter-node reduce-scatter buffer size (bytes)."""
        if getattr(ccfg, "comm_hsdp", 0) <= 0:
            return 0.0
        non_exp, routed, shared = ctx.eval.num_p(ccfg, ctx)
        exp = routed + shared
        d_shard = ccfg.d_shard_or_d
        sharded_non_exp = non_exp / (d_shard * ccfg.cp * ccfg.t)
        sharded_exp = (
            exp / (d_shard * ccfg.cp * ccfg.t_exp)
            if ccfg.n_exp > 1
            else 0
        )
        return (
            ccfg.comm_hsdp
            * (sharded_non_exp + sharded_exp)
            * ccfg.bytes_compute
        )
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
        )

    @staticmethod
    def dp_buffer_comm(ccfg: CostModelConfig, ctx: Context) -> float:
        del ccfg, ctx
        return 0.0

    @staticmethod
    def tp_buffer_comm(ccfg: CostModelConfig, ctx: Context, mb: int) -> float:
        """Peak HBM buffer for TP all-gather (bytes)."""
        if ccfg.t <= 1 or ccfg.comm_t == 0:
            return 0
        rec_layer = ctx.current_node == LayerType.SEL_REC_LAYER
        rec_coeff = EvalUtils.rec_coeff(rec_layer, ccfg.rec_op.gather)

        attn_buf = ccfg.s * ccfg.b * ccfg.h * ccfg.bytes_compute
        ffn_buf = ccfg.s * ccfg.b * 2 * ccfg.hff * ccfg.bytes_compute

        peak_buf = max(attn_buf, ffn_buf)
        return rec_coeff * ccfg.comm_t * mb * peak_buf / ccfg.cp
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/head.py
35
36
37
38
39
40
41
42
43
        if ccfg.tie_emb_out:
            return 0
        param_size = ctx.eval.num_p(ccfg, ctx)
        param_size /= ccfg.shard_embed
        b_p = ccfg.bytes_compute if getattr(ccfg, "fsdp", False) else ccfg.bytes_p
        b_p /= ccfg.cp
        return param_size * b_p

    @staticmethod
57
58
59
60
61
62
63
64
65
        if ccfg.tie_emb_out:
            return 0
        param_size = ctx.eval.num_p(ccfg, ctx)
        param_size /= ccfg.shard_embed
        b_grad = ccfg.bytes_compute if getattr(ccfg, "fsdp", False) else ccfg.bytes_grad
        b_grad /= ccfg.cp
        return param_size * b_grad

    @staticmethod
63
64
65
66
67
68
69
70
71
72
        return param_size * b_grad

    @staticmethod
    def dp_comm_embed(ccfg: CostModelConfig, ctx: Context) -> float:
        if getattr(ccfg, "fsdp", False):
            return 0.0
        return (
            ccfg.comm_d_non_exp
            * ctx.eval.num_p(ccfg, ctx)
            / (ccfg.shard_embed * ccfg.cp)
73
74
75
76
77
78
79
80
81
82
        )

    @staticmethod
    def tp_comm_embed(ccfg: CostModelConfig, _) -> float:
        if ccfg.t <= 1 or ccfg.comm_t == 0:
            return 0
        return (
            ccfg.comm_t
            * ccfg.s
            * ccfg.b
 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
        )

    @staticmethod
    def fsdp_comm_embed(ccfg: CostModelConfig, ctx: Context) -> float:
        param_size = ctx.eval.num_p(ccfg, ctx)
        non_exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * param_size * ccfg.bytes_compute / (ccfg.cp * ccfg.t)
        )
        return non_exp_buf

    @staticmethod
    def hsdp_comm_embed(ccfg: CostModelConfig, ctx: Context) -> float:
        if getattr(ccfg, "comm_hsdp", 0) <= 0:
            return 0.0
        param_size = ctx.eval.num_p(ccfg, ctx)
        d_shard = ccfg.d_shard_or_d
        sharded_size = param_size / (d_shard * ccfg.cp * ccfg.t)
        return ccfg.comm_hsdp * sharded_size * ccfg.bytes_compute

    @staticmethod
    def fsdp_grad_comm_embed(ccfg: CostModelConfig, ctx: Context) -> float:
        param_size = ctx.eval.num_p(ccfg, ctx)
        non_exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * param_size * ccfg.bytes_grad / (ccfg.cp * ccfg.t)
        )
        return non_exp_buf

    @staticmethod
    def activ_embed(ccfg: CostModelConfig, ctx: Context) -> float:
        """activations"""
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/tail.py
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

    @staticmethod
    def fsdp_comm_mtp(ccfg: CostModelConfig, ctx: Context) -> float:
        """FSDP/HSDP all-gather buffer size for MTP (bytes)."""
        if not ccfg.n_mtp:
            return 0
        mtp_fsdp_comm = 0.0
        param_size = EvalMTP.num_params_mtp(ccfg, ctx)
        mtp_fsdp_comm += (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * ccfg.n_mtp * param_size * ccfg.bytes_compute / (ccfg.cp * ccfg.t)
        )
        ctx.current_node = LayerType.EMBEDDING_LAYER
        param_size = ctx.eval.num_p(ccfg, ctx)
        mtp_fsdp_comm += (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * ccfg.n_mtp * param_size * ccfg.bytes_compute / (ccfg.cp * ccfg.t)
        )
        mtp_fsdp_comm += ccfg.n_mtp * ctx.eval.dyn.comm.fsdp(ccfg, ctx)
        ctx.current_node = LayerType.OUTPUT_LAYER
        param_size = ctx.eval.num_p(ccfg, ctx)
        mtp_fsdp_comm += (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * ccfg.n_mtp * param_size * ccfg.bytes_compute / (ccfg.cp * ccfg.t)
        )
        mtp_fsdp_comm += ccfg.n_mtp * EvalTailSingle.fsdp_comm_out_single(
            ccfg, ctx
        )
        return mtp_fsdp_comm

    @staticmethod
    def fsdp_grad_comm_mtp(ccfg: CostModelConfig, ctx: Context) -> float:
        """FSDP/HSDP gradient reduce-scatter buffer size for MTP (bytes)."""
        if not ccfg.n_mtp:
            return 0
        mtp_fsdp_grad_comm = 0.0
        param_size = EvalMTP.num_params_mtp(ccfg, ctx)
        mtp_fsdp_grad_comm += (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * ccfg.n_mtp * param_size * ccfg.bytes_grad / (ccfg.cp * ccfg.t)
        )
        ctx.current_node = LayerType.EMBEDDING_LAYER
        param_size = ctx.eval.num_p(ccfg, ctx)
        mtp_fsdp_grad_comm += (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * ccfg.n_mtp * param_size * ccfg.bytes_grad / (ccfg.cp * ccfg.t)
        )
        mtp_fsdp_grad_comm += ccfg.n_mtp * ctx.eval.dyn.comm.fsdp_grad(ccfg, ctx)
        ctx.current_node = LayerType.OUTPUT_LAYER
        param_size = ctx.eval.num_p(ccfg, ctx)
        mtp_fsdp_grad_comm += (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * ccfg.n_mtp * param_size * ccfg.bytes_grad / (ccfg.cp * ccfg.t)
        )
        mtp_fsdp_grad_comm += ccfg.n_mtp * EvalTailSingle.fsdp_grad_comm_out_single(
            ccfg, ctx
        )
        return mtp_fsdp_grad_comm

    @staticmethod
    def hsdp_comm_mtp(ccfg: CostModelConfig, ctx: Context) -> float:
        """HSDP inter-node reduce-scatter buffer size for MTP (bytes)."""
        if not ccfg.n_mtp or getattr(ccfg, "comm_hsdp", 0) <= 0:
            return 0
        d_shard = ccfg.d_shard_or_d
        d_replicate = ccfg.d // d_shard
        mtp_hsdp_comm = 0.0
        param_size = EvalMTP.num_params_mtp(ccfg, ctx)
        sharded_size = param_size / (d_shard * ccfg.cp * ccfg.t)
        mtp_hsdp_comm += (
            ccfg.comm_hsdp * ccfg.n_mtp * sharded_size
            * ccfg.bytes_compute / d_replicate
        )
        ctx.current_node = LayerType.EMBEDDING_LAYER
        param_size = ctx.eval.num_p(ccfg, ctx)
        sharded_size = param_size / (d_shard * ccfg.cp * ccfg.t)
        mtp_hsdp_comm += (
            ccfg.comm_hsdp * ccfg.n_mtp * sharded_size
            * ccfg.bytes_compute / d_replicate
        )
        ctx.current_node = LayerType.OUTPUT_LAYER
        param_size = ctx.eval.num_p(ccfg, ctx)
        sharded_size = param_size / (d_shard * ccfg.cp * ccfg.t)
        mtp_hsdp_comm += (
            ccfg.comm_hsdp * ccfg.n_mtp * sharded_size
            * ccfg.bytes_compute / d_replicate
        )
        return mtp_hsdp_comm


class EvalTailSingle:
    """Single tail layer formulas class"""
224
225
226
227
228
229
230
231
232
    @staticmethod
    def stat_output_single_p(ccfg: CostModelConfig, ctx: Context) -> float:
        """static mem for model param (lmhead)"""
        param_size = ctx.eval.num_p(ccfg, ctx)
        b_p = ccfg.bytes_compute if getattr(ccfg, "fsdp", False) else ccfg.bytes_p
        b_p /= ccfg.shard_p_os_non_exp_partial
        return param_size * b_p

    @staticmethod
242
243
244
245
246
247
248
249
250
    @staticmethod
    def stat_output_single_grad(ccfg: CostModelConfig, ctx: Context) -> float:
        """static mem for gradient (lmhead)"""
        param_size = ctx.eval.num_p(ccfg, ctx)
        b_grad = ccfg.bytes_compute if getattr(ccfg, "fsdp", False) else ccfg.bytes_grad
        b_grad /= ccfg.shard_grad_non_exp
        return param_size * b_grad

    @staticmethod
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
        return micro_factor * activ_size

    @staticmethod
    def comm_out_single(ccfg: CostModelConfig, ctx: Context) -> float:
        del ccfg, ctx
        return 0.0

    @staticmethod
    def fsdp_comm_out_single(ccfg: CostModelConfig, ctx: Context) -> float:
        param_size = ctx.eval.num_p(ccfg, ctx)
        non_exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * param_size * ccfg.bytes_compute / (ccfg.cp * ccfg.t)
        )
        return non_exp_buf

    @staticmethod
    def fsdp_grad_comm_out_single(ccfg: CostModelConfig, ctx: Context) -> float:
        param_size = ctx.eval.num_p(ccfg, ctx)
        non_exp_buf = (
            ccfg.comm_fsdp * ccfg.fsdp_all_gather_buffer
            * param_size * ccfg.bytes_grad / (ccfg.cp * ccfg.t)
        )
        return non_exp_buf

    @staticmethod
    def hsdp_comm_out_single(ccfg: CostModelConfig, ctx: Context) -> float:
        if getattr(ccfg, "comm_hsdp", 0) <= 0:
            return 0.0
        param_size = ctx.eval.num_p(ccfg, ctx)
        d_shard = ccfg.d_shard_or_d
        sharded_size = param_size / (d_shard * ccfg.cp * ccfg.t)
        return ccfg.comm_hsdp * sharded_size * ccfg.bytes_compute


class EvalTail:
    """Single tail layer formulas class"""
349
350
351
352
353
354
355
356
357
        )

    @staticmethod
    def fsdp_comm_output(ccfg: CostModelConfig, ctx: Context) -> float:
        return sum(
            [
                EvalTailSingle.fsdp_comm_out_single(ccfg, ctx),
                EvalMTP.fsdp_comm_mtp(ccfg, ctx),
            ]
358
359
360
361
362
363
364
365
366
        )

    @staticmethod
    def hsdp_comm_output(ccfg: CostModelConfig, ctx: Context) -> float:
        return sum(
            [
                EvalTailSingle.hsdp_comm_out_single(ccfg, ctx),
                EvalMTP.hsdp_comm_mtp(ccfg, ctx),
            ]
367
368
369
370
371
372
373
374
375
        )

    @staticmethod
    def fsdp_grad_comm_output(ccfg: CostModelConfig, ctx: Context) -> float:
        return sum(
            [
                EvalTailSingle.fsdp_grad_comm_out_single(ccfg, ctx),
                EvalMTP.fsdp_grad_comm_mtp(ccfg, ctx),
            ]
hyper_parallel/auto_parallel/sapp_nd/memory_estimation/evaluators/utils.py
76
77
78
79
80
81
82
83
84
85
86
87
    @staticmethod
    def __log_ast_mem(ctx, cat, mem):
        """Save AST memory evaluation result in the current context."""
        ctx.save2log(cat, mem)
        if cat is not None and cat.name in ("AG_COMM", "A2A_COMM"):
            ctx.accu_mem_type[cat] = max(ctx.accu_mem_type[cat], mem)
        else:
            ctx.accu_mem_type[cat] += mem

    @classmethod
    def __eval_ast_name(cls, n: ast.Name, depth: int, wait: bool, **kwargs):
        """Evaluate a named memory term."""
hyper_parallel/auto_parallel/sapp_nd/nd/balancing_adapter.py
37
38
39
40
41
42
43
44
45
46
47
        """Product of chunks and stages"""
        return self.pp * self.vpp

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Pipeline):
            return NotImplemented
        return self.pp == other.pp and self.vpp == other.vpp


def infer_pp_and_vpp(offset):
    """Return a pipeline configuration inferred from an offset"""
hyper_parallel/auto_parallel/sapp_nd/nd/common/arch_hooks.py
264
265
266
267
268
269
270
271
272
        c.shard_embed = shard_embed

    for idx, f in enumerate(ccfg.layer_custom_config):

        def wrap_hook(e, f = f):
            if isinstance(e, CostModelConfig):
                e = CWrap(e)
            f[1](e)
            e.set_ccfg(custom_shard)
hyper_parallel/auto_parallel/sapp_nd/nd/common/cost_model_preprocess.py
140
141
142
143
144
145
146
147
148
        self.__dict__.update(state)

    @property
    def d_shard_or_d(self) -> int:
        return self.d_shard if getattr(self, "d_shard", 0) > 0 else self.d

    def fp_bytes(self, precision):
        """Return bytes size for datatype"""
        if precision and isinstance(precision, str):
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
            ("m", m),
            ("b", b),
        ):
            self.__maybe_set_int(target_ccfg, attr, value)
        if target_ccfg.use_seq_parallel:
            target_ccfg.sp = target_ccfg.t if target_ccfg.t > 1 else 1
        else:
            target_ccfg.sp = 1
        if target_ccfg.recompute_slice_activation and tp is not None:
            target_ccfg.shard_recompute_input = target_ccfg.t
            target_ccfg.shard_output_activ = target_ccfg.t
        if op and isinstance(op, int):
            target_ccfg.os_max_shard = op
            target_ccfg.has_op = True
        target_ccfg.gbs = target_ccfg.b * target_ccfg.d * target_ccfg.m

    def __apply_fsdp_strategy(self, target_ccfg, fsdp_kw, op):
        if fsdp_kw is True:
            target_ccfg.fsdp = True
            target_ccfg.has_op = True
        elif fsdp_kw is False:
            target_ccfg.fsdp = False
            if op and isinstance(op, int) and op <= 1:
                target_ccfg.has_op = False
            elif not op:
                target_ccfg.has_op = False

    def __apply_d_shard_strategy(self, target_ccfg, d_shard_kw):
        if d_shard_kw is not None and isinstance(d_shard_kw, int) and d_shard_kw > 1:
            target_ccfg.d_shard = d_shard_kw
        else:
            target_ccfg.d_shard = target_ccfg.d if target_ccfg.fsdp else 1

    def __apply_parser_config(self, target_ccfg):
        """Run the parser configuration hooks for sharding and comm flags."""
        if hasattr(target_ccfg.parser, "config_shard_emb"):
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
            target_ccfg.parser.config_shard_emb()
        if hasattr(target_ccfg.parser, "config_shard_recompute"):
            target_ccfg.parser.config_shard_recompute()
        target_ccfg.parser.config_dp_tp_exp(target_ccfg)
        if target_ccfg.fsdp:
            target_ccfg.parser.config_fsdp_shard(target_ccfg)
        else:
            target_ccfg.parser.config_optimizer_shard(target_ccfg)
            target_ccfg.shard_p_fsdp_non_exp = 0
            target_ccfg.shard_os_fsdp_non_exp = 0
            target_ccfg.shard_grad_fsdp_non_exp = 0
            target_ccfg.shard_p_fsdp_exp = 0
            target_ccfg.shard_os_fsdp_exp = 0
            target_ccfg.shard_grad_fsdp_exp = 0
            target_ccfg.fsdp_all_gather_buffer = 0
        target_ccfg.parser.config_comm_flag(target_ccfg)

    def __apply_recompute_offset(self, target_ccfg, off, fr, sr):
        if fr is not None:
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
            )

    def set_strategy(self, **kwargs):
        """overwrite parallelism"""
        model_name = kwargs.get("model_name", None)
        dp = kwargs.get("dp", None)
        tp = kwargs.get("tp", None)
        mp = kwargs.get("mp", None)
        if tp is None and mp is not None:
            tp = mp
        cp = kwargs.get("cp", None)
        ep = kwargs.get("ep", None)
        op = kwargs.get("op", None)
        etp = kwargs.get("etp", None)
        pp = kwargs.get("pp", None)
        vpp = kwargs.get("vpp", None)
        off = kwargs.get("offset", None)
        fr = kwargs.get("full_rec", None)
        sr = kwargs.get("sel_rec", None)
        m = kwargs.get("mb", None)
        b = kwargs.get("mbs", None)
        fsdp_kw = kwargs.get("fsdp", None)
        d_shard_kw = kwargs.get("hsdp", None)
        if d_shard_kw is None:
            d_shard_kw = kwargs.get("d_shard", None)
        target_ccfg = self.__strategy_target(model_name)

        self.__apply_basic_strategy(
            target_ccfg, dp, tp, ep, etp, cp, pp, vpp, m, b, op
        )
        self.__apply_fsdp_strategy(target_ccfg, fsdp_kw, op)
        self.__apply_d_shard_strategy(target_ccfg, d_shard_kw)
        is_hsdp = (
            d_shard_kw is not None
            and isinstance(d_shard_kw, int)
            and 1 < d_shard_kw < target_ccfg.d
        )
        if is_hsdp:
            target_ccfg.fsdp = True
        saved_d_shard = target_ccfg.d_shard

        logger.debug(
            "in ccfg: DP = %d, TP(MP) = %d, EP = %d, CP = %d, "
            "PP = %d, MB = %d, MBS = %d, VPP = %d, FSDP = %s",
            target_ccfg.d,
            target_ccfg.t,
397
398
399
400
401
402
403
404
405
406
407
408
            target_ccfg.b,
            target_ccfg.vp,
            target_ccfg.fsdp,
        )
        self.__apply_parser_config(target_ccfg)
        if is_hsdp:
            target_ccfg.d_shard = saved_d_shard
        self.__apply_recompute_offset(target_ccfg, off, fr, sr)
        self.__maybe_set_int(target_ccfg, "cp", cp)

    def get_strategy(self):
        """return parallelism/recompute strategies"""
436
437
438
439
440
441
442
443
444
        """
        config_ref = weakref.ref(self)
        for idx, f in enumerate(self.layer_custom_config):

            def wrap(e, hook = f[1]):
                hook(e)
                if isinstance(e, CostModelConfig):
                    config = config_ref()
                    if config is None:
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/_cost_model_parser.py
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
        return hff

    def config_fsdp_shard(self, ccfg: Any) -> None:
        """Configure FSDP/HSDP shard variables."""
        d_shard = ccfg.d_shard_or_d
        d_replicate = ccfg.d // d_shard
        ccfg.shard_p_fsdp_non_exp = d_shard * ccfg.cp * ccfg.t
        ccfg.shard_os_fsdp_non_exp = d_shard * ccfg.cp * ccfg.t
        ccfg.shard_grad_fsdp_non_exp = d_shard * ccfg.cp * ccfg.t
        ccfg.shard_p_fsdp_exp = d_shard * ccfg.cp * ccfg.t
        ccfg.shard_os_fsdp_exp = d_shard * ccfg.cp * ccfg.t
        ccfg.shard_grad_fsdp_exp = d_shard * ccfg.cp * ccfg.t
        ccfg.fsdp_all_gather_buffer = 1.0
        ccfg.has_grad_shard = True
        ccfg.shard_p_os_non_exp_partial = ccfg.shard_p_fsdp_non_exp
        ccfg.shard_p_os_non_exp = ccfg.shard_p_fsdp_non_exp
        ccfg.shard_grad_non_exp = ccfg.shard_grad_fsdp_non_exp
        ccfg.shard_p_os_exp_partial = ccfg.shard_p_fsdp_exp
        ccfg.shard_p_os_exp = ccfg.shard_p_fsdp_exp
        ccfg.shard_grad_exp = ccfg.shard_grad_fsdp_exp
        ccfg.d_replicate = d_replicate

    def config_comm_flag(self, ccfg):
        """comm flag variables"""
        ccfg.comm_d_non_exp = (
141
142
143
144
145
146
147
148
149
150
151
        ccfg.comm_ep = float(
            ccfg.ep > 1 or ccfg.n_exp > 1
        )  # expert parallel comm factor
        ccfg.comm_cp = float(ccfg.cp > 1)  # context parallel comm factor
        ccfg.comm_fsdp = float(ccfg.fsdp and ccfg.d > 1)
        is_hsdp = ccfg.fsdp and getattr(ccfg, "d_shard", 0) > 1 and ccfg.d_shard < ccfg.d
        ccfg.comm_hsdp = float(is_hsdp)
        ccfg.comm_dp_overlap = 0.9  # transitional overlap, see _cost_model_variables.py
        ccfg.comm_tp_overlap = 0.5  # transitional overlap, see _cost_model_variables.py

    @staticmethod
149
150
151
152
153
154
155
156
157
158
159
160
        ccfg.comm_tp_overlap = 0.5  # transitional overlap, see _cost_model_variables.py

    @staticmethod
    def is_fsdp(ccfg: Any) -> bool:
        if not (ccfg.has_op and ccfg.has_grad_shard):
            return False
        op_weight_shard = getattr(ccfg, "op_weight_shard", 0)
        return op_weight_shard == ccfg.d * ccfg.t and op_weight_shard > 0

    def config_dp_tp_exp(self, ccfg):
        """MoE strategy variables"""
        if ccfg.etp > 1:
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_hyper.py
375
376
377
378
379
380
381
382
383
        self.ccfg.vp = max(1, int(
            self._get_cfg_attr(accel, "pp_interleave_num", 1) or 1
        ))
        use_sp = bool(self._get_cfg_attr(accel, "use_seq_parallel", False))
        self.ccfg.use_seq_parallel = use_sp
        self.ccfg.sp = self.ccfg.t if use_sp else 1  # Sequence parallel factor
        self.ccfg.pp_sched = str(
            self._get_cfg_attr(accel, "pipeline_scheduler", "1f1b")
        )
495
496
497
498
499
500
501
502
503
            self._get_cfg_attr(model_raw, "softmax_compute_type", "float32"))
        self.ccfg.bytes_grad = 4
        self.ccfg.bytes_os = 4
        self.ccfg.bytes_norm = 4
        self.ccfg.framework_overhead = getattr(
            self.ccfg, "framework_overhead", 0
        )

    def _init_moe_strategy(self):
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_hyperparallel.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
        self.__parse_feature_flag()
        self.__parse_hyperparam()
        self.__parse_strat()
        self.__parse_moe()
        self.ccfg.fsdp = self.is_fsdp(self.ccfg)
        d_shard = max(1, self.config.parallelism.data_parallel_shard_degree)
        self.ccfg.d_shard = d_shard if self.ccfg.fsdp else 1
        if self.ccfg.fsdp:
            self.config_fsdp_shard(self.ccfg)
        else:
            self.config_optimizer_shard(self.ccfg)  # need to adapt FSDP
        self.config_comm_flag(self.ccfg)
        self.__parse_batch()
        self.__init_shard()
        self.__init_bytes()
168
169
170
171
172
173
174
175
176
        self.ccfg.t = max(1, self.config.parallelism.tensor_parallel_degree)
        self.ccfg.p = max(1, self.config.parallelism.pipeline_parallel_degree)
        self.ccfg.cp = max(1, self.config.parallelism.context_parallel_degree)
        self.ccfg.ep = max(1, self.config.parallelism.expert_parallel_degree)
        self.ccfg.use_seq_parallel = True
        self.ccfg.sp = self.ccfg.t
        self.ccfg.vp = 1
        self.ccfg.op_weight_shard = (
            self.config.parallelism.data_parallel_shard_degree * self.ccfg.t
174
175
176
177
178
179
180
181
182
183
        self.ccfg.vp = 1
        self.ccfg.op_weight_shard = (
            self.config.parallelism.data_parallel_shard_degree * self.ccfg.t
        )
        self.ccfg.has_op = self.ccfg.op_weight_shard > self.ccfg.t  # Assuming
        self.ccfg.has_grad_shard = self.ccfg.has_op  # Assuming FSDP grad shard
        self.ccfg.os_max_shard = (
            self.ccfg.op_weight_shard if self.ccfg.op_weight_shard >= 1
            else self.ccfg.d * self.ccfg.t
        )  # need correction
297
298
299
300
301
302
303
304
305
306
307

    def __init_shard(self):
        """sharding vars"""
        self.ccfg.shard_embed = self.ccfg.t
        self.ccfg.shard_output_activ = self.ccfg.t
        self.ccfg.recompute_slice_activation = True
        self.ccfg.shard_recompute_input = self.ccfg.t
        self.ccfg.is_shard_mtp_param = True

    def __init_bytes(self):
        """fp bytes vars"""
310
311
312
313
314
315
316
        self.ccfg.bytes_softmax = 4
        self.ccfg.bytes_grad = 4
        self.ccfg.bytes_os = 4
        self.ccfg.bytes_norm = 4
        self.ccfg.framework_overhead = getattr(
            self.ccfg, "framework_overhead", 0
        )
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_mindformers.py
55
56
57
58
59
60
61
62
63
        )  # Context parallel
        self.ccfg.ep = max(
            1, self.config.parallel_config.expert_parallel
        )  # Expert parallel
        self.ccfg.use_seq_parallel = self.config.parallel_config.use_seq_parallel
        self.ccfg.sp = (
            self.ccfg.t if self.ccfg.use_seq_parallel else 1
        )  # Sequence parallel factor
        if self.ccfg.cp > 1 and self.ccfg.sp > 1:
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
            self.ccfg.gmm = cfg.moe_grouped_gemm

    def __config_parse_yaml_fp_bytes(self):
        """Parse MindFormer format for FP byte storages and framework overhead."""
        self.ccfg.bytes_p = self.ccfg.fp_bytes(
            self.config.model.model_config.param_init_type,
        )  # parameters
        if not self.ccfg.bytes_p:
            self.ccfg.bytes_p = self.ccfg.fp_bytes(
                self.config.model.model_config.params_dtype
            )
        self.ccfg.bytes_compute = self.ccfg.fp_bytes(
            self.config.model.model_config.compute_dtype
        )  # activations
        self.ccfg.bytes_softmax = self.ccfg.fp_bytes(
            self.config.model.model_config.softmax_compute_type
        )  # softmax output
        if not self.ccfg.bytes_softmax:
            self.ccfg.bytes_softmax = self.ccfg.fp_bytes(
                self.config.model.model_config.softmax_compute_dtype
            )
        if not self.ccfg.bytes_p:
            raise AttributeError("bytes_p not positive")
        if not self.ccfg.bytes_compute:
            raise AttributeError("bytes_compute not positive")
        self.ccfg.bytes_grad = 4
        self.ccfg.bytes_os = 4
        self.ccfg.bytes_norm = 4

        if not getattr(self.ccfg, "framework_overhead", 0):
            cap_gb = self.ccfg.device_capacity.to_gb().size
            self.ccfg.framework_overhead = int(
                10.5 * 1024 ** 3 * (cap_gb / 32.0) ** 1.74
            )
        else:
            self.ccfg.framework_overhead = getattr(
                self.ccfg, "framework_overhead", 0
            )

    def __config_parse_yaml_optimizer_shard(self):
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

    def __config_parse_yaml_optimizer_shard(self):
        """Parse MindFormer format for optimizer parallel and FSDP shard factors."""
        # Optimizer parallel factors
        if self.ccfg.op_weight_shard:
            self.ccfg.os_max_shard = self.ccfg.op_weight_shard
        elif self.ccfg.has_op:
            self.ccfg.os_max_shard = self.ccfg.d * self.ccfg.t
        else:
            self.ccfg.os_max_shard = 1
        self.ccfg.fsdp = self.is_fsdp(self.ccfg)
        if self.ccfg.fsdp:
            self.ccfg.d_shard = self.ccfg.d
            self.config_fsdp_shard(self.ccfg)
        else:
            self.ccfg.d_shard = 1
            self.config_optimizer_shard(self.ccfg)

    def __config_parse_yaml_op_recompute(self):
        """MindFormer format for select recompute"""
        # [HYPOTHESIS]
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
        )
        self.__config_parse_yaml_parallelism()
        self.__config_parse_yaml_hyperparameters()
        self.__config_parse_yaml_moe()
        self.__config_parse_yaml_fp_bytes()
        self.__config_parse_yaml_optimizer_shard()

        # Other factors
        self.config_shard_emb()
        self.ccfg.shard_output_activ = 1
        self.ccfg.recompute_slice_activation = (
            self.config.recompute_config.recompute_slice_activation
        )
        self.ccfg.shard_recompute_input = (
            self.ccfg.t
hyper_parallel/auto_parallel/sapp_nd/nd/common/framework_parsers/cost_model_parser_mindspeed.py
150
151
152
153
154
155
156
157
158
159
        cc.p = max(cc.pipeline_model_parallel_size, self.config.tmp.pp)
        cc.cp = self.config.tmp.cp
        cc.d = self.config.tmp.dp
        cc.ep = max(cc.expert_model_parallel_size, self.config.tmp.ep)
        cc.use_seq_parallel = bool(mod.sequence_parallel)
        cc.sp = cc.t if cc.use_seq_parallel else 1
        if cc.cp > 1 and cc.sp > 1:
            logger.warning(
                "sequence parallelism and context parallelism are both enabled"
            )
220
221
222
223
224
225
226
227
228
229
        cc.config_format = "json"
        cc.model_name = mod.model_id
        cc.freeze = mod.freeze  # for later
        cc.has_fa = True
        cc.has_op = bool(getattr(mod, "use_distributed_optimizer", False))
        cc.has_grad_shard = cc.has_op
        # cc.vp_less_mem = False
        cc.has_clip = False
        cc.cp_algo = "colossalai_cp"
        cc.gmm = mod.moe_grouped_gemm
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
        )  # softmax output
        cc.bytes_grad = 4
        cc.bytes_os = 4
        cc.bytes_norm = 4
        cc.framework_overhead = getattr(cc, "framework_overhead", 0)

        # Optimizer parallel factors
        cc.os_max_shard = cc.d * cc.t
        cc.op_weight_shard = cc.os_max_shard if cc.has_op else 0
        cc.fsdp = self.is_fsdp(cc)
        if cc.fsdp:
            cc.d_shard = cc.d
            self.config_fsdp_shard(cc)
        else:
            cc.d_shard = 1
            self.config_optimizer_shard(cc)

        # Other factors
        cc.shard_embed = cc.t * cc.d
        cc.shard_output_activ = 1
282
283
284
285
286
287
288
289
290
        cc.shard_recompute_input = 1
        cc.s_fa = (
            cc.s if not cc.has_fa else cc.s / cc.a
        )  # flash attention factor [HYPOTHESIS]
        self.config_comm_flag(cc)
        cc.gbs = cc.b * cc.d * cc.m
        cc.n_mtp = mod.mtp_num_layers
        # Recomputation
        self.__config_parse_json_op_recompute(cc)
hyper_parallel/auto_parallel/sapp_nd/nd/common/hardware.py
87
88
89
90
91
92
93
94
95
96
        # EP borrows from DP, not counted in total devices; kept for
        # topology tracking only — callers should NOT pass ep > 1
        # unless they also account for EP-in-DP convention.
        device_number = dp * tp * cp * pp * ep
        dp_original = dp
        logger.debug("DP = %d, TP = %d, EP = %d, CP = %d, PP = %d, d_shard = %d", dp, tp, ep, cp, pp, d_shard)
        assignment = {}
        assignment[Dim.TP] = []
        assignment[Dim.EP] = []
        assignment[Dim.CP] = []
 95
 96
 97
 98
 99
100
101
102
103
        assignment[Dim.EP] = []
        assignment[Dim.CP] = []
        assignment[Dim.DP] = []
        assignment[Dim.PP] = []
        assignment[Dim.HSDP] = []
        for level in range(self.levels):
            bound = self.level_bound_number[level]
            if bound:
                level_device_number = min(device_number, bound)
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
            assignment[Dim.PP].append(pp_level)
            pp = pp // pp_level
            remaining_devices = remaining_devices // pp_level

            if d_shard > 1 and dp > 0:
                d_replicate = dp_original // d_shard
                if level == 0:
                    assignment[Dim.HSDP].append(1)
                elif level == 1 and d_replicate > 1:
                    assignment[Dim.HSDP].append(d_replicate)
                else:
                    assignment[Dim.HSDP].append(1)
            else:
                assignment[Dim.HSDP].append(0)

        return assignment

hyper_parallel/auto_parallel/sapp_nd/nd/debug.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
            name = "Rec"
        elif self == self.DP_COMM:
            name = "DP"
        elif self == self.MP_COMM:
            name = "TP(MP)"
        elif self == self.EP_COMM:
            name = "EP"
        elif self == self.CP_COMM:
            name = "CP"
        elif self == self.FSDP_COMM:
            name = "FSDP"
        elif self == self.PP_COMM:
            name = "P2P"
        elif self == self.BUBBLE:
            name = "BBL"
580
581
582
583
584
585
586
587
588
    )
    estimations_in_real_components[RealParts.CP_WAIT].append(
        estimations[PerfParts.CP_COMM.value - 1]
    )
    estimations_in_real_components[RealParts.FSDP_WAIT].append(
        estimations[PerfParts.FSDP_COMM.value - 1]
    )
    estimations_in_real_components[RealParts.EP_WAIT].append(
        estimations[PerfParts.EP_COMM.value - 1]
610
611
612
613
614
615
616
617
618
619
620
    op = "op_wait"
    if op in real.keys():
        parts[RealParts.DP_WAIT][-1] += real["op_wait"]

    tp = "tp_wait"
    if tp in real.keys():
        parts[RealParts.MP_WAIT][-1] += real["tp_wait"]

    sp = "sp_wait"
    if sp in real.keys():
        parts[RealParts.MP_WAIT][-1] += real["sp_wait"]
hyper_parallel/auto_parallel/sapp_nd/nd/dimensions.py
29
30
31
32
33
34
35
36
37
38
39
    from hyper_parallel.auto_parallel.sapp_nd.nd.common._cost_model_variables import _CostModVar


def from_str_bool(value: float) -> bool:
    if isinstance(value, str):
        return value.strip().lower() not in ("false", "0", "no", "off", "")
    return bool(value)


class Dimension:
    """Output dimension"""
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
        return d in self.dims_val

    @staticmethod
    def _check_mbn_pp(dims_val, all_dims):
        if MBN in dims_val and PP in all_dims:
            valid = dims_val[MBN] >= dims_val[PP]
            valid = valid and not (dims_val[PP] == 1 and dims_val[MBN] > 1)
            if not valid:
                return False
        return True

    @staticmethod
    def _check_power_of_two(dim, value):
        if not (value & (value - 1)) == 0:
            logger.warning("%s must be a power of 2", str(dim))
            return False
        return True

    def _validate_pp_mbn(self):
        if MBN in self.dims_val and PP in self.all_dims:
            valid = self.dims_val[MBN] >= self.dims_val[PP]
            valid = valid and not (
                self.dims_val[PP] == 1 and self.dims_val[MBN] > 1
            )
            if not valid:
                logger.warning("PP and MBN were deemed not suitable")
                return False
        return True

    def _validate_tp_power_of_two(self):
        if TP in self.all_dims and not (
            (self.dims_val[TP] & (self.dims_val[TP] - 1)) == 0
        ):
            logger.warning("%s must be a power of 2", str(TP))
            return False
        return True

    def _validate_each_dimension(self):
        for d in self.dims_val:
            if not d.is_valid(self.dims_val[d]):
281
282
283
284
285
286
287
288
289
        for d in self.dims_val:
            if not d.is_valid(self.dims_val[d]):
                logger.warning("Dimension %d is not valid", d)
                return False
        return True

    def _validate_sp_cp_coexistence(self):
        if SP in self.all_dims and CP in self.all_dims:
            if self.dims_val[SP] and self.dims_val[CP] > 1:
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
        if SP in self.all_dims and CP in self.all_dims:
            if self.dims_val[SP] and self.dims_val[CP] > 1:
                logger.warning("SP & CP cannot coexist")
                return False
        return True

    def _validate_op_power_of_two(self):
        if OP in self.all_dims:
            op = self.dims_val[OP]
            if not (op & (op - 1)) == 0:
                logger.warning("OP %d must be a power of 2", op)
                return False
        return True

    def _validate_fsdp_constraints(self):
        """Validate FSDP coexistence with OP, EP and the DP lower bound."""
        if not (self.has_dim(FSDP) and self.dims_val[FSDP]):
            return True
        if self.has_dim(OP) and self.dims_val[OP] > 1:
            logger.warning("FSDP and OP cannot coexist (OP > 1)")
            return False
        if self.has_dim(EP) and self.dims_val[EP] > 1:
            logger.warning("FSDP and EP cannot coexist")
            return False
        if self.has_dim(DP) and self.dims_val[DP] < 2:
            logger.warning("FSDP requires DP >= 2")
            return False
        return True

    def _validate_hsdp_fsdp_coexistence(self, d_shard):
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
        return True

    def _validate_hsdp_fsdp_coexistence(self, d_shard):
        """Validate HSDP constraints that depend on FSDP being present."""
        if d_shard > 1 and not self.dims_val[FSDP]:
            logger.warning("HSDP d_shard > 1 requires FSDP enabled")
            return False
        if d_shard > 1 and self.dims_val[EP] > 1:
            logger.warning("HSDP and EP cannot coexist")
            return False
        if d_shard > 1 and self.dims_val[OP] > 1:
            logger.warning("HSDP and OP cannot coexist (OP > 1)")
            return False
        return True

    def _validate_hsdp_constraints(self):
        """Validate HSDP-related dimension constraints."""
        if not self.has_dim(HSDP):
            return True
        d_shard = self.dims_val[HSDP]
        if self.has_dim(DP) and d_shard > self.dims_val[DP]:
            logger.warning("HSDP d_shard cannot exceed DP")
            return False
        if self.has_dim(FSDP) and not self._validate_hsdp_fsdp_coexistence(
            d_shard
        ):
            return False
        if d_shard > 1 and not (d_shard & (d_shard - 1)) == 0:
            logger.warning("HSDP d_shard %d must be a power of 2", d_shard)
            return False
        return True

    def is_valid(self):
        """Check if all dimensions values are valid"""
        return (
            self._validate_pp_mbn()
            and self._validate_tp_power_of_two()
            and self._validate_each_dimension()
            and self._validate_sp_cp_coexistence()
377
378
379
380
381
382
383
384
385
386
    dname = str(acronym).upper()
    for d in ALL_DIMS:
        if d.name == dname:
            return d
    if dname == _MP_ALIAS:
        return TP
    raise ValueError(f"Dimension {dname} does NOT exist")


def get_dims(dims):
hyper_parallel/auto_parallel/sapp_nd/nd/global_config.py
109
110
111
112
113
114
115
116
117
        expert_num = self.ccfg.n_exp
        if expert_num > 1:
            ep = self.dim_val(Dim.EP, parallel_config)
            dp = self.dim_val(Dim.DP, parallel_config)
            tp = self.dim_val(Dim.TP, parallel_config)
            logger.debug(
                "moe valid ? EP %d <= E %d & EP %d <= DP %d * TP(MP) %d",
                ep,
                expert_num,
118
119
120
121
122
123
124
125
126
                ep,
                dp,
                tp,
            )
            return ep <= min(expert_num, dp * tp)
        return True

    def ep_constraints_valid(self, parallel_config):
        """Check EP-specific divisibility constraints (C1, C2).
164
165
166
167
168
169
170
171
172
173
    def make_parallel_config_args(self, **kwargs):
        """Create a parallel config from parallel values"""
        logger.debug("dimensions considered: %s", str(self.dimensions))

        if kwargs.get("tp") is None and kwargs.get("mp") is not None:
            kwargs["tp"] = kwargs.pop("mp")
        dims = []
        # dims.append((Dim.DP, dp))
        for dim in self.dimensions:
            dims.append((dim, kwargs.get(dim.lname())))
182
183
184
185
186
187
188
189
190
191
192

    def make_parallel_config(self, dtpc_p, mbsn, evos_p):
        """Create a parallel config from parallel values"""
        logger.debug("dimensions considered: %s", str(self.dimensions))
        (dp, tp, pp, cp) = dtpc_p
        (mbs, mbn) = mbsn
        (ep, vpp, op, sp, fsdp, d_shard) = evos_p
        return self.make_parallel_config_args(
            dp=dp,
            tp=tp,
            pp=pp,
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
            return [False, True]
        return [dim.from_config(self.ccfg)]

    def hsdp_space(self, dp: int) -> List[Any]:
        if Dim.HSDP not in self.dimensions:
            return [dp]
        power_of_2_divisors = Hard.all_divisors(dp)
        valid = [s for s in power_of_2_divisors if 2 <= s < dp]
        if not valid:
            return [dp]
        return [dp] + valid

    def max_op(self, dp, tp, ep):
        """Compute bound for dimension OP"""
        if (
hyper_parallel/auto_parallel/sapp_nd/nd/parallelize.py
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
        return space

    def _parallel_loops_fsdop(self, space, pool, dtpc_p, mbsn, ep, vpp, dp, tp):
        """Iterate over FSDP/OP/d_shard/SP sub-space for one (ep, vpp)."""
        for fsdp in self.config.bool_space(Dim.FSDP):
            if fsdp and dp < 2:
                continue
            if fsdp and ep > 1:
                continue
            if fsdp:
                op_space = [1]
                d_shard_space = self.config.hsdp_space(dp)
            else:
                op_space = self.config.space(
                    Dim.OP, self.config.max_op(dp, tp, ep)
                )
                d_shard_space = [1]
            for op in op_space:
                for d_shard in d_shard_space:
                    for sp in self.config.bool_space(Dim.SP):
                        space = self.inside_loop_nest(
                            space,
                            pool,
                            (
                                dtpc_p,
336
337
338
339
340
341
342
343
344
                                mbsn,
                                (ep, vpp, op, sp, fsdp, d_shard),
                            ),
                        )
        return space

    def parallel_loops(self, space, pool, dims):
        """Exploration loop nest level 2: dimensions dependent on others"""
        dtpc_p, mbsn = dims
346
347
348
349
350
351
352
353
        for ep in self.config.space(Dim.EP, dp * tp):
            for vpp in self.config.range_space(
                Dim.VPP, min(4, pp, self.config.total_layer_num() // pp)
            ):
                space = self._parallel_loops_fsdop(
                    space, pool, dtpc_p, mbsn, ep, vpp, dp, tp
                )
        return space
437
438
439
440
441
442
443
444
445
446
                            memory=mem,
                            cache_file=cache_file,
                        )
                        debugger.write()
                        debugger.info.pop(Debug.PerfParts.TOTAL, None)
                        debugger.info.pop(Debug.PerfParts.MEMORY, None)
                        debug_parts = list(debugger.info.keys())
                        values = list(debugger.info.values())
                    else:
                        score = estimate_performance(
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
        return (sorted(new_scored_space, key=lambda x: x[2]), debug_parts)

    def _score_single_config(self, config, ccfg, stage_focused=None):
        """Score a single parallel config: estimate memory and performance."""
        debugger = Debug.Debug(
            config, info_type=Debug.PerfParts, enable=self.enable_debug
        )
        # If MB (microbatch number) is not in the config dimensions,
        # inject it so set_strategy can compute m from gbs/d/b.
        injected_mbn = False
        if Dim.MBN not in config.dims_val and Dim.MBS in config.dims_val and Dim.DP in config.dims_val:
            dp_val = config.dims_val[Dim.DP]
            mbs_val = config.dims_val[Dim.MBS]
            if isinstance(dp_val, int) and isinstance(mbs_val, int) and dp_val > 0 and mbs_val > 0:
                mbn = self.global_batch_size // dp_val // mbs_val
                config.dims_val[Dim.MBN] = mbn
                injected_mbn = True
        self.config.set_parallel_config(config)
        if injected_mbn:
            del config.dims_val[Dim.MBN]
        peak_mem = self.memory_estim()
        est_kwargs = {
            "debugger": debugger,
            "device_type": self.machine.device,
            "ccfg": ccfg,
        }
        if stage_focused is not None:
            est_kwargs["stage_focused"] = stage_focused
        score = estimate_performance(self.config.ccfg, **est_kwargs)
        debugger.write()
        debugger.info.pop(Debug.PerfParts.TOTAL, None)
        debugger.info.pop(Debug.PerfParts.MEMORY, None)
        debug_parts = list(debugger.info.keys())
        values = list(debugger.info.values())
        return peak_mem, score, debug_parts, values

    def order_space_test_comm_classified(self, space, order_by = 2, ccfg = None):
        """Order the given space with performance estimation"""
        if ccfg is None:
            ccfg = CustomConfig()
        scored_space = []
        debug_parts = []
        for config, real_time, real_comm_wait in space:
            peak_mem, score, debug_parts, values = self._score_single_config(
                config, ccfg, stage_focused=0,
            )
            scored_space.append(
                (config, peak_mem, real_time, score, values, real_comm_wait)
519
520
521
522
523
524
525
526
527
528
529
530
531
532
        return (sorted(scored_space, key=lambda x: x[order_by]), debug_parts)

    def order_space_test(self, space, order_by = 2, ccfg = None):
        """Order the given space with performance estimation"""
        if ccfg is None:
            ccfg = CustomConfig()
        scored_space = []
        debug_parts = []
        for config, real_time in space:
            logger.info("Test config %s", str(config))
            peak_mem, score, debug_parts, values = self._score_single_config(
                config, ccfg,
            )
            scored_space.append((config, peak_mem, real_time, score, values))
hyper_parallel/auto_parallel/sapp_nd/perf_estimation/comm_time.py
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


def _get_flop_coeffs(device_type, dimension, sub_key):
    """Get FLOP-mode regression coefficients from device_type."""
    if device_type is not None and hasattr(device_type, 'flop_coeffs'):
        dim_coeffs = device_type.flop_coeffs.get(dimension, {})
        if dim_coeffs:
            result = dim_coeffs.get(sub_key, {})
            if result:
                return result
    fallback = Hard.device_map.get("V4")
    if fallback and fallback.flop_coeffs:
        dim_coeffs = fallback.flop_coeffs.get(dimension, {})
        result = dim_coeffs.get(sub_key, {})
        if result:
            return result
    return {}


def _compute_hsdp_features(cfg, d_shard_val, device_type, mb=1):
    """Compute shared derived features for HSDP/FSDP FLOP-mode models."""
    d_val = cfg.d if getattr(cfg, "d", 1) > 0 else 1
    tp_val = max(cfg.t if getattr(cfg, "t", 1) > 0 else 1, 1)
    cp_val = max(cfg.cp if getattr(cfg, "cp", 1) > 0 else 1, 1)
    d_replicate = max(d_val // d_shard_val, 1) if d_shard_val > 0 else 1

    sg = d_shard_val * cp_val * tp_val
    default_dev = Hard.device_map.get("V4", Hard.Device_A2)
    dev_per_node = device_type.intra_node_num() if device_type else default_dev.intra_node_num()
    cross = 1.0 if sg * d_replicate > dev_per_node else 0.0
    inv_sg = 1.0 / sg if sg > 0 else 0.0
    inv_tp = 1.0 / tp_val if tp_val > 0 else 0.0
    ag_vol = 1.0 - 1.0 / d_shard_val if d_shard_val > 0 else 0.0
    mb_val = max(mb, 1)

    return {
        "tp": tp_val, "cp": cp_val, "d": d_val,
        "d_shard": d_shard_val, "d_replicate": d_replicate,
        "sg": sg, "inv_sg": inv_sg, "inv_tp": inv_tp,
        "ag_vol": ag_vol, "cross": cross,
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118


def _flop_mode_comp_comm(cfg, d_shard_val, device_type=None, mb=1):
    """Estimate COMP cost for FLOP mode when d_replicate > 1 (HSDP)."""
    f = _compute_hsdp_features(cfg, d_shard_val, device_type, mb=mb)
    if f["d_replicate"] <= 1:
        return 0.0
    c = _get_flop_coeffs(device_type, "comp", "hsdp")
    if not c:
        return 0.0
    total = (
        c.get("INTERCEPT", 0)
        + c.get("TP", 0) * f["tp"]
        + c.get("INV_TP", 0) * f["inv_tp"]
        + c.get("DP", 0) * f["d"]
119
120
121
122
123
124
125
126
127
128
129
130
131
132
        + c.get("AG_VOL", 0) * f["ag_vol"]
        + c.get("CROSS_INV_TP", 0) * f["cross_inv_tp"]
        + c.get("LOG2_M", 0) * f["log2_m"]
    )
    score = total / f["m"]
    logger.info(
        "FLOP_COMP_HSDP: d=%d tp=%d d_shard=%d d_rep=%d m=%d score=%.2f",
        f["d"], f["tp"], f["d_shard"], f["d_replicate"], f["m"], score,
    )
    return score


def _msg_size_efficient_bw(msg_bytes: float, peak_bw_gbps: float,
                            small_eff: float = 0.5,
145
146
147
148
149
150
151
152
153
154
155
156
157
158

    Returns:
        Effective bandwidth in bytes/s.
    """
    msg_mb = msg_bytes / 1e6
    if msg_mb <= 0:
        return peak_bw_gbps * 1e9 * small_eff
    t = max(0.0, min(1.0, (log10(max(msg_mb, 1e-6)) - _LOG10_MB_FLOOR) / _LOG10_MB_RANGE))
    eff = small_eff + (large_eff - small_eff) * t
    return peak_bw_gbps * 1e9 * eff


def _cp_resolve_topology(cp, device_per_node, bw_intra, bw_inter):
    """Resolve CP topology and effective bandwidth.
172
173
174
175
176
177
178
179
180


def _cp_comm_zero(ccfg, device_type=None):
    """Return a zero CPCommunicationCost for cp <= 1."""
    overlap_ratio = device_type.cp_overlap_ratio if device_type else 0.5
    return CPCommunicationCost(
        kv_volume_per_step=0.0, total_kv_volume=0.0, comm_volume=0.0,
        ring_steps=0, ring_directions=0,
        total_comm_time=0.0, exposed_comm_time=0.0,
190
191
192
193
194
195
196
197
198
                          ring_steps, ring_directions, cp, s, b,
                          attention_type, kv_dim, cp_algo, topology,
                          effective_bandwidth, device_type=None):
    """Build CPCommunicationCost with standard time calculation."""
    overlap_ratio = device_type.cp_overlap_ratio if device_type else 0.5
    total_comm_time = (total_kv_volume / (effective_bandwidth * 1e9)) * 1e3
    exposed_comm_time = total_comm_time * (1 - overlap_ratio)
    return CPCommunicationCost(
        kv_volume_per_step=volume_per_step,
225
226
227
228
229
230
231
232
233
        Per-All2All volume = s * b * (a/t) * bytes * (cp-1)/cp (head dims).
        Total volume = 2 * per-All2All volume.
    """
    if ccfg.cp <= 1:
        return _cp_comm_zero(ccfg, device_type=device_type)

    s, b = ccfg.s, ccfg.b
    cp = ccfg.cp
    t = max(1, ccfg.t)
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


def comm_embed_ouput(cfg):
    """ "formula"""
    comm_embed = cfg.bytes_compute * cfg.h * cfg.v / cfg.shard_embed
    comm_output = cfg.h * cfg.v / cfg.t
    return comm_embed, comm_output


def prepare_context():
    """context object"""
    ctx = Context()
    ctx.attn_num_p = EvalAttn.num_params_attn
    ctx.ffn_num_p = EvalFFn.num_params_ffn
    ctx.norm_num_p = EvalNorm.num_params_norm

    ctx.node_eval[LayerType.EMBEDDING_LAYER] = NodeEval(
        EvalHead.num_params_embed, None, None
    )
    ctx.node_eval[LayerType.OUTPUT_LAYER] = NodeEval(
        EvalTail.num_params_output, None, None
    )
    ctx.node_eval[LayerType.NOT_REC_LAYER] = NodeEval(
        EvalBody.num_params_layer, None, None
    )
    ctx.enable_accu_log = False
    return ctx


def _accumulate_layer_comm(comm, param):
    """Accumulate per-layer communication volumes for DP, FSDP, TP, EP, CP."""
    cfg = param["cfg"]
    ctx = param["ctx"]
    layer = ctx.current_node

    fsdp_intra_vol = 0.0
    hsdp_inter_vol = 0.0
    tp_layer_count = 0
    fsdp_layer_count = 0

    is_fsdp_layer = (
        layer not in [LayerType.EMBEDDING_LAYER, LayerType.OUTPUT_LAYER]
    )
    if is_fsdp_layer and param["flatten"]:
        custom_fun = param["flatten"].pop(0)
        if custom_fun:
            custom_fun(cfg)
        logger.info("is layer moe ? %s", cfg.n_exp > 1)
        ctx.current_node = LayerType.NOT_REC_LAYER
        logger.info("param ctx %s", ctx)
        comm[Dim.DP] += EvalLayerComm.dp_comm_layer(cfg, ctx)
        comm[Dim.FSDP] += EvalLayerComm.fsdp_comm_layer(cfg, ctx)
        non_exp, routed, shared = ctx.eval.num_p(cfg, ctx)
        exp = routed + shared
        bc = cfg.bytes_compute
        fsdp_intra_vol += non_exp * bc * 2
        if cfg.n_exp > 1:
            fsdp_intra_vol += exp * bc * 2
        d_shard_local = cfg.d_shard_or_d
        if getattr(cfg, "comm_hsdp", 0) > 0 and d_shard_local < cfg.d:
            sharded_non_exp = non_exp / (d_shard_local * cfg.cp * cfg.t)
            sharded_exp = exp / (d_shard_local * cfg.cp * cfg.t_exp) if cfg.n_exp > 1 else 0
            hsdp_inter_vol += (sharded_non_exp + sharded_exp) * bc
    fsdp_layer_count = 1 if is_fsdp_layer else 0

    comm[Dim.TP] += EvalLayerComm.tp_comm_layer(cfg, ctx, 1)
    tp_layer_count += 1
    comm[Dim.EP] += EvalLayerComm.ep_comm_layer(cfg, ctx, 1)
    comm[Dim.CP] += cp_comm_layer_detailed(cfg, ctx, device_type=param.get("device_type")).comm_volume

    return fsdp_intra_vol, hsdp_inter_vol, tp_layer_count, fsdp_layer_count


def _flop_mode_fsdp_comm(cfg, fsdp_layer_count, d_shard_val, device_type, pp=1, mb=1):
    """Estimate FSDP/HSDP communication cost for FLOP mode."""
    if fsdp_layer_count <= 0 or d_shard_val <= 0:
        return 0.0

    f = _compute_hsdp_features(cfg, d_shard_val, device_type, mb=mb)
    f["pp"] = max(pp, 1)
    is_hsdp = f["d_replicate"] > 1

    sub_key = "hsdp" if is_hsdp else "fsdp"
    c = _get_flop_coeffs(device_type, "shard", sub_key)
    if not c:
        return 0.0

    if is_hsdp:
        total = (
            c.get("INTERCEPT", 0)
            + c.get("TP", 0) * f["tp"]
            + c.get("SG", 0) * f["sg"]
            + c.get("CROSS_AG_VOL", 0) * f["cross_ag_vol"]
452
453
454
455
456
457
458
459
460
461
            + c.get("CROSS_INV_TP", 0) * f["cross_inv_tp"]
            + c.get("INV_SG_D_REP", 0) * f["inv_sg_d_rep"]
            + c.get("AG_VOL_D_REP", 0) * f["ag_vol_d_rep"]
        )
        return total / f["pp"] / f["m"]
    per_layer = (
        c.get("INTERCEPT", 0)
        + c.get("INV_SG", 0) * f["inv_sg"]
        + c.get("CROSS_INV_TP", 0) * f["cross_inv_tp"]
        + c.get("D_SHARD", 0) * f["d_shard"]
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
        + c.get("CROSS_INV_TP", 0) * f["cross_inv_tp"]
        + c.get("D_SHARD", 0) * f["d_shard"]
        + c.get("TP", 0) * f["tp"]
    )
    return per_layer * 2 * fsdp_layer_count / f["pp"] / f["m"]


def _flop_mode_dp_comm(comm_dp_raw, cfg, d_shard_val, device_type, mb=1):
    """Estimate DP communication cost for FLOP mode."""
    if comm_dp_raw <= 0:
        return 0.0

    f = _compute_hsdp_features(cfg, d_shard_val, device_type, mb=mb)

    if (cfg.fsdp or d_shard_val > 1) and d_shard_val > 0:
        if f["sg"] <= 1:
            return 0.0

        if f["d_replicate"] > 1:
            c = _get_flop_coeffs(device_type, "dp", "hsdp")
            if not c:
                return 0.0
            dp_val = f["d_replicate"] * f["d_shard"]
            total = (
                c.get("INTERCEPT", 0)
                + c.get("TP", 0) * f["tp"]
                + c.get("DP", 0) * dp_val
                + c.get("SG", 0) * f["sg"]
488
489
490
491
492
493
494
495
496
497
                + c.get("AG_VOL", 0) * f["ag_vol"]
                + c.get("D_REP", 0) * f["d_replicate"]
                + c.get("CROSS_AG_VOL", 0) * f["cross_ag_vol"]
            )
            score = total / f["m"]
            logger.info(
                "FLOP_DP_HSDP: d=%d tp=%d d_shard=%d sg=%d d_rep=%d "
                "cross=%.1f score=%.2f mb=%d raw=%.4f",
                f["d"], f["tp"], f["d_shard"], f["sg"], f["d_replicate"],
                f["cross"], score, f["m"], comm_dp_raw,
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
                "cross=%.1f score=%.2f mb=%d raw=%.4f",
                f["d"], f["tp"], f["d_shard"], f["sg"], f["d_replicate"],
                f["cross"], score, f["m"], comm_dp_raw,
            )
            return score

        return 0.0

    logger.info(
        "FLOP_DP_FALLBACK: d=%d tp=%d fsdp=%s d_shard=%d raw=%.4f result=0.0",
        f["d"], f["tp"], cfg.fsdp, f["d_shard"],
        comm_dp_raw,
    )
    return 0.0


def _flop_mode_tp_comm(cfg, d_shard_val, device_type, mb=1):
    """Estimate TP communication cost for FLOP mode when d_replicate > 1."""
    f = _compute_hsdp_features(cfg, d_shard_val, device_type, mb=mb)
    if f["d_replicate"] <= 1:
        return 0.0
    c = _get_flop_coeffs(device_type, "tp", "hsdp")
    if not c:
        return 0.0
    total = (
        c.get("INTERCEPT", 0)
        + c.get("INV_TP", 0) * f["inv_tp"]
        + c.get("INV_SG", 0) * f["inv_sg"]
        + c.get("LOG2_D_REP", 0) * f["log2_d_rep"]
524
525
526
527
528
529
530
531
532
533
        + c.get("CROSS_D_REP", 0) * f["cross_d_rep"]
        + c.get("CROSS_SG", 0) * f["cross_sg"]
        + c.get("M", 0) * f["m"]
    )
    score = total / f["m"]
    logger.info(
        "FLOP_TP_HSDP: d=%d tp=%d d_shard=%d sg=%d d_rep=%d "
        "cross=%.1f m=%d score=%.2f",
        f["d"], f["tp"], f["d_shard"], f["sg"], f["d_replicate"],
        f["cross"], f["m"], score,
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
        "cross=%.1f m=%d score=%.2f",
        f["d"], f["tp"], f["d_shard"], f["sg"], f["d_replicate"],
        f["cross"], f["m"], score,
    )
    return score


def _flop_mode_pp_total_comm(cfg, d_shard_val, device_type, mb=1):
    """Estimate PP_TOTAL cost for FLOP mode when d_replicate > 1 (HSDP)."""
    f = _compute_hsdp_features(cfg, d_shard_val, device_type, mb=mb)
    if f["d_replicate"] <= 1:
        return 0.0
    c = _get_flop_coeffs(device_type, "pp", "hsdp")
    if not c:
        return 0.0
    total = (
        c.get("INTERCEPT", 0)
        + c.get("AG_VOL_D_REP", 0) * f["ag_vol_d_rep"]
        + c.get("CROSS_SG", 0) * f["cross_sg"]
        + c.get("M", 0) * f["m"]
548
549
550
551
552
553
554
555
556
557
        + c.get("AG_VOL_D_REP", 0) * f["ag_vol_d_rep"]
        + c.get("CROSS_SG", 0) * f["cross_sg"]
        + c.get("M", 0) * f["m"]
    )
    score = total / f["m"]
    logger.info(
        "FLOP_PP_TOTAL_HSDP: d=%d tp=%d d_shard=%d sg=%d d_rep=%d "
        "cross=%.1f m=%d score=%.2f",
        f["d"], f["tp"], f["d_shard"], f["sg"], f["d_replicate"],
        f["cross"], f["m"], score,
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
        "cross=%.1f m=%d score=%.2f",
        f["d"], f["tp"], f["d_shard"], f["sg"], f["d_replicate"],
        f["cross"], f["m"], score,
    )
    return score


def compute_hsdp_flop_total(cfg, d_shard_val, device_type, fsdp_layer_count, mb=1, pp=1):
    """Estimate performance using HSDP FLOP model."""
    comp_score = _flop_mode_comp_comm(cfg, d_shard_val, device_type=device_type, mb=mb)
    tp_score = _flop_mode_tp_comm(cfg, d_shard_val, device_type=device_type, mb=mb)
    dp_score = _flop_mode_dp_comm(1.0, cfg, d_shard_val, device_type, mb=mb)
    shard_score = _flop_mode_fsdp_comm(
        cfg, fsdp_layer_count, d_shard_val, device_type, pp=pp, mb=mb,
    )
    pp_score = _flop_mode_pp_total_comm(cfg, d_shard_val, device_type, mb=mb)
    total_score = comp_score + tp_score + shard_score + dp_score + pp_score
    logger.info(
        "HSDP_FLOP_TOTAL: comp=%.2f tp=%.2f shard=%.2f dp=%.2f pp=%.2f total=%.2f",
        comp_score, tp_score, shard_score, dp_score, pp_score, total_score,
    )
    return total_score


def _apply_flop_mode(comm, param, fsdp_layer_count):
    """Apply FLOP-mode estimation to comm_time calculation."""
    cfg = param["cfg"]
    d_shard_val = cfg.d_shard_or_d
    mb = cfg.m if hasattr(cfg, "m") and cfg.m > 0 else 1
    pp = cfg.p if hasattr(cfg, "p") and cfg.p > 0 else 1

    f = _compute_hsdp_features(cfg, d_shard_val, param["device_type"], mb=mb)
    dev_per_node = f["dev_per_node"]

    if f["d_replicate"] > 1:
        comm[Dim.TP] = _flop_mode_tp_comm(
            cfg, d_shard_val, param["device_type"], mb=mb,
        )
    else:
        c = _get_flop_coeffs(param["device_type"], "tp", "fsdp")
        if c:
            tp_scaling = (c.get("A", 0) + c.get("B", 0) * f["tp"] + c.get("C", 0) / f["tp"]) / max(f["d"], 1)
            comm[Dim.TP] *= max(1, f["tp"] // dev_per_node) * tp_scaling

    comm[Dim.EP] *= max(1, param["cfg"].ep // dev_per_node)
    comm[Dim.CP] *= max(1, param["cfg"].cp // dev_per_node)

    dp_result = _flop_mode_dp_comm(comm[Dim.DP], cfg, d_shard_val, param["device_type"], mb=mb)
    if dp_result > 0:
        comm[Dim.DP] = dp_result

    if (cfg.fsdp or d_shard_val > 1) and fsdp_layer_count > 0:
        comm[Dim.FSDP] = _flop_mode_fsdp_comm(
            cfg, fsdp_layer_count, d_shard_val, param["device_type"],
            pp=pp, mb=mb,
        )
663
664
665
666
667
668
669
670
671
    logger.info(lccfgs)
    param["layer_count"] = 0
    param["idx_lccfg"] = 0
    # ignores comm recomp, to improve
    comms = {Dim.DP: [], Dim.TP: [], Dim.EP: []}
    for stage in param["stages"]:
        comm = {Dim.DP: 0.0, Dim.TP: 0.0, Dim.EP: 0.0}
        for chunk in stage:
            for layer in chunk:
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
    # For layer type
    param["flatten"] = sum(
        [[f[1]] * f[0] for f in param["cfg"].layer_custom_config], []
    )
    comms = {Dim.DP: [], Dim.TP: [], Dim.EP: [], Dim.CP: [], Dim.FSDP: []}
    for stage in param["stages"]:
        comm = {Dim.DP: 0.0, Dim.TP: 0.0, Dim.EP: 0.0, Dim.CP: 0.0, Dim.FSDP: 0.0}
        stage_fsdp_count = 0
        for chunk in stage:
            for layer in chunk:
                param["ctx"].current_node = layer
                _, _, _, fc = _accumulate_layer_comm(comm, param)
                stage_fsdp_count += fc

        _apply_flop_mode(comm, param, stage_fsdp_count)

        if param["ccfg"].ttype == PerformanceType.TIME:
            for dim, ov in zip([Dim.DP, Dim.TP, Dim.CP], [0.0, 0.0, 0.0]):
                comm[dim] = estimate_comm_score(
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
                    overlap=ov,
                    device=param["device_type"],
                )

        comm[Dim.TP] *= param["cfg"].comm_t
        comm[Dim.EP] *= param["cfg"].comm_ep

        d_shard_val = param["cfg"].d_shard_or_d
        if not (param["cfg"].fsdp or d_shard_val > 1):
            comm[Dim.DP] *= (1 - param["cfg"].comm_dp_overlap)
            comm[Dim.TP] *= (1 - param["cfg"].comm_tp_overlap)

        scale = param["device_type"].comm_scale_factor
        if scale != 1.0:
            logger.info("comm scale factor: %.2f", scale)
            for dim in (Dim.DP, Dim.TP, Dim.EP, Dim.CP, Dim.FSDP):
                comm[dim] *= scale

        comms[Dim.DP].append(comm[Dim.DP])
        comms[Dim.TP].append(comm[Dim.TP])
        comms[Dim.EP].append(comm[Dim.EP])
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
        comms[Dim.DP].append(comm[Dim.DP])
        comms[Dim.TP].append(comm[Dim.TP])
        comms[Dim.EP].append(comm[Dim.EP])
        comms[Dim.CP].append(comm[Dim.CP])
        comms[Dim.FSDP].append(comm[Dim.FSDP])

    if param["debugger"] and param["debugger"].is_enabled():
        logger.info("DP_COMM = %s", comms[Dim.DP])
        logger.info("TP(MP)_COMM = %s", comms[Dim.TP])
        logger.info("EP_COMM = %s", comms[Dim.EP])
        logger.info("CP_COMM = %s", comms[Dim.CP])
        logger.info("FSDP_COMM = %s", comms[Dim.FSDP])
        param["debugger"].info[PerfParts.DP_COMM] = comms[Dim.DP]
        param["debugger"].info[PerfParts.MP_COMM] = comms[Dim.TP]
        param["debugger"].info[PerfParts.EP_COMM] = comms[Dim.EP]
        param["debugger"].info[PerfParts.CP_COMM] = comms[Dim.CP]
        param["debugger"].info[PerfParts.FSDP_COMM] = comms[Dim.FSDP]
        if param["cfg"].cp > 1:
            cp_comm_details = cp_comm_layer_detailed(param["cfg"], param["ctx"],
                                                      device_type=param.get("device_type"))
            param["debugger"].info["CP_KV_VOLUME"] = cp_comm_details.total_kv_volume
            param["debugger"].info["CP_EXPOSED_TIME"] = cp_comm_details.exposed_comm_time
            param["debugger"].info["CP_TOPOLOGY"] = cp_comm_details.topology
860
861
862
863
864
865
866
867
            param["debugger"].info["CP_BANDWIDTH"] = cp_comm_details.effective_bandwidth

    res = []
    for i, c in enumerate(comms[Dim.TP]):
        res += [c + comms[Dim.DP][i] + comms[Dim.EP][i] + comms[Dim.CP][i] + comms[Dim.FSDP][i]]

    return res

886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
    )


def level_efficiency(level, device = None):
    if device is not None:
        idx = level.value - 1 if isinstance(level, NetworkLevel) else level - 1
        p2p_eff = getattr(device, 'p2p_efficiency', None)
        if p2p_eff is not None and 0 <= idx < len(p2p_eff):
            return p2p_eff[idx]
        if 0 <= idx < len(device.level_efficiency):
            return device.level_efficiency[idx]
    raise ValueError(
        f"No efficiency for level {level}; device required"
    )

899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
    )


def level_bandwidth(level, device = None):
    if device is not None:
        idx = level.value - 1 if isinstance(level, NetworkLevel) else level - 1
        p2p = getattr(device, 'p2p_bandwidth', None)
        if p2p is not None and idx < len(p2p):
            return p2p[idx]
        if 0 <= idx < len(device.level_bandwidth):
            return device.level_bandwidth[idx]
    raise ValueError(
        f"No P2P bandwidth for level {level}; device required"
    )

912
913
914
915
916
917
918
919
920
921
922
923
    )


def level_latency(level, device = None):
    if device is not None:
        idx = level.value - 1 if isinstance(level, NetworkLevel) else level - 1
        if 0 <= idx < len(device.level_latency):
            return device.level_latency[idx]
    raise ValueError(
        f"No latency for level {level}; device required"
    )

923
924
925
926
927
928
929
930
931
932


def comm_throughput(level, device = None):
    """formula"""
    eff = level_efficiency(level, device=device)
    bw = level_bandwidth(level, device=device)
    return bw * eff


def estimate_comm_size_time(_, comm_size, level, device = None):
930
931
932
933
934
935
936
937
938
939


def estimate_comm_size_time(_, comm_size, level, device = None):
    """formula"""
    th = comm_throughput(level, device=device)
    lat = level_latency(level, device=device)
    return lat + comm_size / th


def _shard_group_levels(device, shard_size):
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955


def _shard_group_levels(device, shard_size):
    """Compute message-size-dependent effective bandwidth."""
    remaining = shard_size
    levels = []
    for level in range(device.levels):
        bound = device.level_bound_number[level]
        if bound:
            n = min(remaining, bound)
            remaining = remaining // n if n > 0 else remaining
        else:
            n = remaining
        levels.append(n)
    return levels


def estimate_comm_score(
    cfg,
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
    per_rank_msg: if True, comm_volume is already the per-rank message
    size (not total).  Skip the /n_level division.  Use for FSDP/HSDP
    where the volume is pre-computed as a per-rank shard.
    """
    if comm_volume <= 0:
        return 0

    d_shard = cfg.d_shard_or_d

    if shard_group_size > 0:
        n_levels = _shard_group_levels(device, shard_group_size)
    else:
        assignment = device.level_assign(
            dp=cfg.d, tp=cfg.t, cp=cfg.cp, pp=cfg.p, d_shard=d_shard
        )
        lookup_dim = dim if dim in assignment else Dim.DP
        n_levels = assignment[lookup_dim]

    time_s = 0
    for level in range(device.levels):
        n_level = n_levels[level]
        if n_level <= 1:
            continue
        if per_rank_msg:
            msg_per_rank = comm_volume
        elif shard_group_size > 0:
            prod_from_l = 1
            for k in range(level, len(n_levels)):
                prod_from_l *= n_levels[k]
            msg_per_rank = comm_volume / prod_from_l
        else:
            msg_per_rank = comm_volume / n_level
        if isinstance(a2a_efficiency, dict):
            eff = a2a_efficiency.get(level, level_efficiency(NetworkLevel(level + 1), device=device))
        elif a2a_efficiency is not None:
            eff = a2a_efficiency
        else:
            eff = level_efficiency(NetworkLevel(level + 1), device=device)
        bw_bps = device.level_bandwidth[level] * 1e9 * eff
        lat = level_latency(NetworkLevel(level + 1), device=device)
        if per_rank_msg:
            collective_time = (
                (n_level - 1) * msg_per_rank / bw_bps
                + lat * 2 * (n_level - 1)
            )
        else:
            collective_time = (
                2 * (n_level - 1) / n_level * msg_per_rank / bw_bps
                + lat * 2 * (n_level - 1)
            )
        time_s += collective_time

    return time_s * (1 - overlap)
hyper_parallel/auto_parallel/sapp_nd/perf_estimation/estimate.py
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


def efficiency(x):
    """obtained via extrapolation"""
    eff = min(
        1.0, max(0.1, 0.00004694 * x**3 + 0.0014 * x**2 - 0.0336 * x + 0.1)
    )
    return eff


def throughput(precision_bytes, flop):
    """assumes matrix"""
    eff = efficiency(flop / (10.0**12))
    return precision_bytes**2 * (10.0**12) * eff


def estimate_comp_flop_time(cfg, flop, is_softmax=False):
    """flop from throughput"""
    th = throughput(
        cfg.bytes_softmax if is_softmax else cfg.bytes_compute, flop
    )
    return flop / th


def op_table(cfg):
    """OP compute load formulas."""
82
83
84
85
86
87
88
89
90
91
92
93
94
    table["n_attMM"] = (
        3 * (1 + cfg.n_kv / cfg.a) * cfg.b * cfg.s * cfg.h * cfg.h
    )
    table["n_ffMM"] = 6 * cfg.b * cfg.s * cfg.h * cfg.hff
    table["n_attBMM"] = 6 * cfg.b * cfg.s * cfg.s * cfg.h / cfg.cp
    table["n_ffBMM"] = 6 * cfg.b * cfg.s * cfg.s * cfg.hff / cfg.cp
    table["n_softmax"] = 13 * cfg.a * cfg.b * cfg.s * cfg.s / cfg.cp
    table["n_headCast"] = 3 * cfg.a * cfg.b * cfg.s * cfg.s / cfg.cp
    table["n_gather"] = 0  # comm metric, not compute — handled by fill_tp_table/comm_time
    table["n_ffAct"] = 21 * cfg.b * cfg.hff

    table["n_normOp"] = 30 * cfg.b * cfg.s * cfg.h * cfg.t / cfg.sp
    table["n_dropout"] = (
113
114
115
116
117
118
119
120
121
122
123
124

# Evaluation functions
def _bulk_comp_layer_flop(cfg, lccfgs, table, table_exp, layer, layer_count, idx_lccfg, with_recomp=False):
    """Compute FLOP for a single layer in bulk compute estimation."""
    if layer == LayerType.EMBEDDING_LAYER:
        return 0, layer_count, idx_lccfg, True
    if layer == LayerType.OUTPUT_LAYER:
        flop = (1 if cfg.dc_kv == 0 else cfg.n_mtp) * (
            1
            / 16
            * 6
            * cfg.b
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
            * cfg.s
            * cfg.bytes_p
            / cfg.t
        )
        return flop, layer_count, idx_lccfg, True
    layer_count += 1
    if (
        idx_lccfg + 1 < len(lccfgs)
        and lccfgs[idx_lccfg][1] <= layer_count
    ):
        layer_count = 0
        idx_lccfg += 1
    flop = get_table_quantity(
        lccfgs[idx_lccfg][0],
        table_exp if (lccfgs[idx_lccfg][0].n_exp > 1) else table,
        layer,
        with_recomp,
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
        table_exp if (lccfgs[idx_lccfg][0].n_exp > 1) else table,
        layer,
        with_recomp,
    )
    return flop, layer_count, idx_lccfg, False


def _apply_bulk_comp_flop_scale(flops, cfg, ccfg, device_type):
    """Apply FSDP/HSDP/TP scaling to bulk compute FLOPs."""
    tp_val = max(cfg.t if hasattr(cfg, 't') and cfg.t > 0 else 1, 1)
    d_val = max(cfg.d if hasattr(cfg, 'd') and cfg.d > 0 else 1, 1)
    d_shard_val = cfg.d_shard_or_d
    d_replicate = max(d_val // d_shard_val, 1) if d_shard_val > 0 else 1
    if ccfg.ttype != PerformanceType.TIME:
        if d_replicate > 1:
            mb = cfg.m if hasattr(cfg, 'm') and cfg.m > 0 else 1
            comp_score = _flop_mode_comp_comm(cfg, d_shard_val, device_type=device_type, mb=mb)
            return [comp_score for _ in flops]
        comp_coeffs = _get_flop_coeffs(device_type, "comp", "fsdp")
        comp_tp_scale = (comp_coeffs["A"] + comp_coeffs["B"] / tp_val) * tp_val / d_val
        return [f * comp_tp_scale for f in flops]
    return flops


def estimate_op_bulk_comp(
    cfg,
172
173
174
175
176
177
178
179
180
181
182
    device_type = None,
):
    """FW + BW"""
    _ = debugger
    if ccfg is None:
        ccfg = CustomConfig()
    _ = device_name
    table = op_table(cfg)

    table_exp = deepcopy(table)  # Verify this with MF MoEV2
    table_exp["n_ffMM"] *= (
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    for stage in stages:
        flops += [0]
        for chunk in stage:
            for layer in chunk:
                flop, layer_count, idx_lccfg, is_skip = _bulk_comp_layer_flop(
                    cfg, lccfgs, table, table_exp, layer, layer_count, idx_lccfg, with_recomp
                )
                if is_skip and layer == LayerType.EMBEDDING_LAYER:
                    continue
                if ccfg.ttype == PerformanceType.TIME and not is_skip:
                    flop = estimate_comp_flop_time(lccfgs[idx_lccfg][0], flop)
                flops[-1] += flop

    return _apply_bulk_comp_flop_scale(flops, cfg, ccfg, device_type)


def estimate_comp(
    cfg,
275
276
277
278
279
280
281
282
283
284
285
        for i in range(len(compute_perfs))
    ]

    if debugger and debugger.is_enabled():
        for p in [PerfParts.DP_COMM, PerfParts.MP_COMM, PerfParts.EP_COMM, PerfParts.CP_COMM, PerfParts.FSDP_COMM]:
            if p not in debugger.info or not isinstance(debugger.info[p], list):
                debugger.info[p] = [0] * len(compute_perfs)
            debugger.info[p] = [
                comm_w * c for c in debugger.info[p]
            ]
        debugger.info[PerfParts.FW_COMPUTE] = [
288
289
290
291
292
293
294
295
296
        debugger.info[PerfParts.BW_COMPUTE] = [
            fw * BACKWARD_RATIO for fw in debugger.info[PerfParts.FW_COMPUTE]
        ]
        debugger.info[PerfParts.RECOMPUTE] = re_perf
        debugger.info["COMM_RATIO"] = comm_w

    return [perf[i] + re_perf[i] for i in range(len(perf))]
    #penalty_fn(stage)
    #return stage
392
393
394
395
396
397
398
399
400
            pipeline_perf,
            time_sum,
        )
        debugger.info[PerfParts.BUBBLE] = bubble
        debugger.info["MB_COUNT"] = cfg.m
    return pipeline_perf


def _count_p2p_messages(cfg):
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421


def _count_p2p_messages(cfg):
    """Count P2P send/recv operations for pipeline parallelism."""
    if cfg.p <= 1:
        return 0
    if cfg.vp == 1:
        if cfg.p == 2:
            return 4 * cfg.m
        return 4 * cfg.p * cfg.m + 4 * cfg.p * cfg.p - 14 * cfg.p
    if cfg.p == 2:
        return 8 * cfg.m * cfg.vp - 4 * cfg.m
    if cfg.p == 4:
        return 16 * cfg.m * cfg.vp + 12
    return 4 * cfg.p * cfg.m * cfg.vp + 4 * cfg.p * cfg.p - 13 * cfg.p


def estimate_p2p_comm(cfg, straggler, device_type = None, debugger = None):
    """pipeline comm"""
    ratio = device_type.p2p_ratio if device_type else 0.002
    nb_send_recv = _count_p2p_messages(cfg)
    pp_comm = ratio * nb_send_recv / cfg.p * straggler / cfg.sp
    if debugger and debugger.is_enabled():
        debugger.info[PerfParts.PP_COMM] = pp_comm
432
433
434
435
436
437
438
439
    """wrapper"""
    if ccfg.ptype != P2PCommType.MANUAL:
        p2p = 0
    else:
        p2p = estimate_p2p_comm(cfg, max(stage_perfs), device_type=device_type, debugger=debugger)
    if debugger and debugger.is_enabled():
        debugger.info[PerfParts.PP_COMM] = p2p
    return p2p
446
447
448
449
450
451
452
453
454
455
    compute_ratio = coeffs.get("COMPUTE")
    for part, raw in list(debugger.info.items()):
        if part in (PerfParts.TOTAL, PerfParts.MEMORY):
            continue
        if not isinstance(part, PerfParts):
            continue
        if part in (PerfParts.FW_COMPUTE,
                   PerfParts.BW_COMPUTE,
                   PerfParts.RECOMPUTE):
            ratio = compute_ratio
530
531
532
533
534
535
536
537
538
539
540
541
542


def _compute_stage_perfs(cfg, ccfg, stages, debugger, device_type):
    """Compute per-stage performance including compute, comm and recompute."""
    compute_perfs = estimate_comp(
        cfg, ccfg, stages, with_recomp=False, debugger=debugger,
        device_name=device_type.name, device_type=device_type
    )
    recompute_perfs = (
        [0] * cfg.p
        if ccfg.retype not in {RecType.COMPUTE_ONLY, RecType.WITH}
        else estimate_comp(
            cfg, ccfg, stages, with_recomp=True, debugger=debugger,
542
543
544
545
546
547
548
549
550
551
552
553
554
            cfg, ccfg, stages, with_recomp=True, debugger=debugger,
            device_name=device_type.name, device_type=device_type
        )
    )
    comm_perfs = estimate_comm(
        cfg, ccfg, stages, device_type, with_recomp=False, debugger=debugger
    )
    logger.info("PerfEst: comm_perfs %s", comm_perfs)
    recomm_perfs = (
        [0] * cfg.p
        if ccfg.retype not in {RecType.COMM_ONLY, RecType.WITH}
        else estimate_comm(
            cfg, ccfg, stages, device_type, with_recomp=True, debugger=debugger
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
        else estimate_comm(
            cfg, ccfg, stages, device_type, with_recomp=True, debugger=debugger
        )
    )
    stage_perfs = estimate_stage(
        cfg, ccfg, compute_perfs, comm_perfs,
        recompute_perfs, recomm_perfs, debugger=debugger,
    )
    logger.info("PerfEst: stage_perfs %s", stage_perfs)
    return stage_perfs


def _estimate_hsdp_perf(cfg, d_shard_val, device_type, debugger):
    """Estimate performance using the standard stage-based model."""
    mb = cfg.m if hasattr(cfg, "m") and cfg.m > 0 else 1
    pp = cfg.p if hasattr(cfg, "p") and cfg.p > 0 else 1
    n_lay = int(getattr(cfg, "n_lay", 60)) or 60
    perf = compute_hsdp_flop_total(
        cfg, d_shard_val, device_type,
        fsdp_layer_count=n_lay, mb=mb, pp=pp,
    )
    perf *= mb
    if debugger and debugger.is_enabled():
        debugger.info.clear()
        comp_total = _flop_mode_comp_comm(cfg, d_shard_val, device_type=device_type, mb=mb) * mb
        tp_total = _flop_mode_tp_comm(cfg, d_shard_val, device_type=device_type, mb=mb) * mb
        dp_total = _flop_mode_dp_comm(1.0, cfg, d_shard_val, device_type, mb=mb) * mb
        shard_total = _flop_mode_fsdp_comm(
            cfg, n_lay, d_shard_val, device_type, pp=pp, mb=mb,
        ) * mb * pp
        pp_total = _flop_mode_pp_total_comm(cfg, d_shard_val, device_type=device_type, mb=mb) * mb
        debugger.info[PerfParts.FW_COMPUTE] = comp_total / (1 + BACKWARD_RATIO)
        debugger.info[PerfParts.BW_COMPUTE] = comp_total * BACKWARD_RATIO / (1 + BACKWARD_RATIO)
        debugger.info[PerfParts.RECOMPUTE] = 0.0
        debugger.info[PerfParts.DP_COMM] = dp_total
        debugger.info[PerfParts.MP_COMM] = tp_total
        debugger.info[PerfParts.EP_COMM] = 0.0
        debugger.info[PerfParts.CP_COMM] = 0.0
        debugger.info[PerfParts.FSDP_COMM] = shard_total
        debugger.info[PerfParts.PP_COMM] = 0.0
        debugger.info[PerfParts.BUBBLE] = pp_total
    logger.info("PerfEst: HSDP_FLOP total perf %s", perf)
    return perf


def _estimate_non_hsdp_perf(cfg, ccfg, stage_perfs, debugger, device_type, stage_focused):
    perf = estimate_perf(
        cfg, ccfg, stage_perfs, stage_focused=stage_focused, debugger=debugger
    )
    perf += estimate_p2p(cfg, ccfg, stage_perfs, debugger=debugger, device_type=device_type)
    logger.info("PerfEst: perf %s", perf)
    return perf


# performance estimation
def estimate_performance(*args, **kwargs):
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
    logger.info(str(cfg))
    logger.info(stages)
    logger.info(ccfg)

    stage_perfs = _compute_stage_perfs(cfg, ccfg, stages, debugger, device_type)

    d_shard_val = cfg.d_shard_or_d
    d_replicate = max(cfg.d // d_shard_val, 1) if d_shard_val > 0 else 1
    is_hsdp = d_replicate > 1 and d_shard_val > 0

    if is_hsdp:
        perf = _estimate_hsdp_perf(cfg, d_shard_val, device_type, debugger)
    else:
        stage_focused = kwargs.get("stage_focused", None)
        perf = _estimate_non_hsdp_perf(
            cfg, ccfg, stage_perfs, debugger, device_type, stage_focused
        )

    cache_file = kwargs.get("cache_file")