File size: 11,938 Bytes
fc3971e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
"""Runtime monkey-patches. Import this before launching test_infer_seedance."""

from __future__ import annotations

import os
import sys

import torch
import torch.nn.functional as F


def install_elastic_schedule(model, meta: dict) -> None:
    elastic = (meta or {}).get("elastic") or {}
    if not elastic:
        return
    from inference.model.dit.dit_module import SparseMoEMLP

    quality_steps = set(int(x) for x in elastic.get("quality_steps") or [])
    skip_layers = set(int(x) for x in elastic.get("skip_layers") or [])
    n_steps = int(elastic.get("num_steps") or 32)
    per_layer = elastic.get("layers") or {}
    fast_steps = [i for i in range(n_steps) if i not in quality_steps]
    for m in model.modules():
        if not isinstance(m, SparseMoEMLP):
            continue
        spec = per_layer.get(str(int(m.layer_idx))) or {}
        quality_width = int(spec.get("quality_width") or m.experts[0].down_proj.in_features)
        fast_width = int(spec.get("fast_width") or quality_width)
        skip_steps = set(fast_steps) if int(m.layer_idx) in skip_layers else set()
        m._elastic = {
            "quality_width": quality_width,
            "fast_width": min(fast_width, quality_width),
            "quality_steps": quality_steps,
            "skip_steps": skip_steps,
        }
        m._skip_mlp = False
        m._active_mid = quality_width
        for expert in m.experts:
            expert._active_mid = quality_width
    print(
        f"prune_pro elastic schedule: quality_steps={sorted(quality_steps)} "
        f"skip_layers={sorted(skip_layers)}"
    )


def _apply_elastic_step(module, step_index) -> None:
    from inference.model.dit.dit_module import SparseMoEMLP

    force = os.getenv("MAGI_ELASTIC_FORCE", "").strip().lower()
    skip_off = os.getenv("MAGI_ELASTIC_SKIP", "1").strip().lower() in ("0", "off", "false")
    step = 0 if step_index is None else int(step_index)
    for m in module.modules():
        if not isinstance(m, SparseMoEMLP):
            continue
        elastic = getattr(m, "_elastic", None)
        if not elastic:
            m._skip_mlp = False
            m._active_mid = None
            continue
        quality_steps = elastic["quality_steps"]
        skip_steps = set() if skip_off else elastic.get("skip_steps") or set()
        if force == "off":
            m._skip_mlp = False
            m._active_mid = None
        elif force == "quality" or (force != "fast" and step in quality_steps):
            m._skip_mlp = False
            m._active_mid = int(elastic["quality_width"])
        elif step in skip_steps:
            m._skip_mlp = True
            m._active_mid = int(elastic["fast_width"])
        else:
            m._skip_mlp = False
            m._active_mid = int(elastic["fast_width"])
        for expert in m.experts:
            expert._active_mid = None if m._skip_mlp else m._active_mid


def _patch_expert_forward(ExpertMLP) -> None:
    orig = ExpertMLP.forward_with_intermediate

    def forward_with_intermediate(self, x: torch.Tensor):
        n_mid = getattr(self, "_active_mid", None)
        full = int(self.down_proj.in_features)
        if n_mid is None or int(n_mid) >= full:
            return orig(self, x)
        n = min(full, max(1, int(n_mid)))
        x_bf = x.to(torch.bfloat16)
        projected = F.linear(x_bf, self.up_gate_proj.weight[: 2 * n]).to(torch.bfloat16)
        z = self.activation_func(projected).to(torch.bfloat16)
        y = F.linear(z, self.down_proj.weight[:, :n]).to(torch.bfloat16)
        return y, z

    ExpertMLP.forward_with_intermediate = forward_with_intermediate


def _patch_moe_forward(SparseMoEMLP) -> None:
    orig = SparseMoEMLP.forward

    def forward(self, x: torch.Tensor, modality_dispatcher=None):
        if getattr(self, "_skip_mlp", False):
            return torch.zeros_like(x)
        n_mid = getattr(self, "_active_mid", None)
        for expert in self.experts:
            expert._active_mid = n_mid
        return orig(self, x, modality_dispatcher)

    SparseMoEMLP.forward = forward


def _patch_grouped(SparseMoEMLP) -> None:
    orig = SparseMoEMLP._experts_grouped

    def _experts_grouped(self, x_norm, topk_idx, topk_vals, mode):
        n_mid = getattr(self, "_active_mid", None)
        e0 = self.experts[0]
        full = int(e0.down_proj.in_features)
        if n_mid is None or int(n_mid) >= full or mode != "bmm":
            return orig(self, x_norm, topk_idx, topk_vals, mode)
        n = min(full, max(1, int(n_mid)))
        ids = topk_idx[:, 0]
        g = topk_vals[:, 0]
        n_tok = x_norm.shape[0]
        ecount = self.num_experts
        dtype = x_norm.dtype
        order = ids.argsort(stable=True)
        ids_s = ids[order]
        x_s = x_norm[order]
        g_s = g.to(x_norm.dtype)[order]
        counts = torch.bincount(ids_s, minlength=ecount)
        offsets = torch.cumsum(counts, 0) - counts
        col = torch.arange(n_tok, device=x_norm.device) - offsets[ids_s]
        m_max = int(counts.max().item())
        if m_max == 0:
            return torch.zeros_like(x_norm)
        x_pad = x_norm.new_zeros(ecount, m_max, x_norm.shape[1])
        g_pad = x_norm.new_zeros(ecount, m_max)
        x_pad[ids_s, col] = x_s
        g_pad[ids_s, col] = g_s
        w_up = torch.stack([e.up_gate_proj.weight.to(dtype)[: 2 * n] for e in self.experts], dim=0)
        z = torch.bmm(x_pad, w_up.transpose(-2, -1)).to(dtype)
        z = e0.activation_func(z)
        if z.dtype != dtype:
            z = z.to(dtype)
        w_dn = torch.stack([e.down_proj.weight.to(dtype)[:, :n] for e in self.experts], dim=0)
        y = torch.bmm(z, w_dn.transpose(-2, -1)).to(dtype)
        y = y * g_pad.unsqueeze(-1)
        y_s = y[ids_s, col]
        y_orig = y_s.new_empty(n_tok, y_s.shape[1])
        y_orig[order] = y_s
        return y_orig

    SparseMoEMLP._experts_grouped = _experts_grouped


def install_runtime_patches() -> None:
    """Patch inference classes in-process. Call before importing test_infer_seedance."""
    from inference.model.dit import dit_module as dm
    from inference.pruning import checkpoint as ckpt

    if getattr(dm, "_prune_pro_elastic_patched", False):
        return

    _patch_expert_forward(dm.ExpertMLP)
    _patch_moe_forward(dm.SparseMoEMLP)
    _patch_grouped(dm.SparseMoEMLP)

    orig_set = dm.set_sparse_moe_diffusion_timestep

    def set_sparse_moe_diffusion_timestep(module, timestep, step_index=None, num_steps=None):
        orig_set(module, timestep, step_index=step_index, num_steps=num_steps)
        _apply_elastic_step(module, step_index)

    dm.set_sparse_moe_diffusion_timestep = set_sparse_moe_diffusion_timestep
    dit_pkg = sys.modules.get("inference.model.dit")
    if dit_pkg is not None:
        dit_pkg.set_sparse_moe_diffusion_timestep = set_sparse_moe_diffusion_timestep
    vg = sys.modules.get("inference.pipeline.video_generate")
    if vg is not None:
        vg.set_sparse_moe_diffusion_timestep = set_sparse_moe_diffusion_timestep

    orig_rebuild = ckpt.rebuild_mid_shapes

    def rebuild_mid_shapes(model, meta):
        orig_rebuild(model, meta)
        if meta.get("elastic"):
            install_elastic_schedule(model, meta)

    ckpt.rebuild_mid_shapes = rebuild_mid_shapes
    dm._prune_pro_elastic_patched = True
    print("prune_pro: installed elastic runtime patches")


def _dit_bench_enabled() -> bool:
    return os.getenv("MAGI_DIT_BENCH", "").strip().lower() in ("1", "true", "yes", "on")


def install_dit_bench_patches() -> None:
    """Skip VAE load/decode/save; time DiT evaluate_with_latent only.

    Call after importing test_infer_seedance so MagiEvaluator exists.
    """
    if not _dit_bench_enabled():
        return
    import time

    import inference.pipeline.pipeline as pl
    import inference.pipeline.video_generate as vg

    if getattr(vg, "_prune_pro_dit_bench", False):
        return

    class _SkipVAE(torch.nn.Module):
        def __init__(self, *args, **kwargs):
            super().__init__()
            # Defaults match the published MoE denoise bench (sr=44100, C_a=128).
            # Native MagiHuman uses C_a=64 and hop=2048; set the env overrides for that run.
            self.sample_rate = int(os.getenv("MAGI_DIT_BENCH_SAMPLE_RATE", "44100"))
            self.latent_dim = int(os.getenv("MAGI_DIT_BENCH_LATENT_DIM", "128"))
            hop = os.getenv("MAGI_DIT_BENCH_HOP", "").strip()
            if hop:
                self.hop = int(hop)
                self.downsampling_ratio = int(hop)

    orig_init = vg.MagiEvaluator.__init__

    def _init(self, *args, **kwargs):
        saved = (
            vg.get_vae2_2,
            vg.get_turbo_vaed,
            vg.OmniAudioFeatureExtractor,
            vg.SAAudioFeatureExtractor,
        )
        vg.get_vae2_2 = lambda *a, **k: _SkipVAE()
        vg.get_turbo_vaed = lambda *a, **k: _SkipVAE()
        vg.OmniAudioFeatureExtractor = _SkipVAE
        vg.SAAudioFeatureExtractor = _SkipVAE
        try:
            orig_init(self, *args, **kwargs)
        finally:
            (
                vg.get_vae2_2,
                vg.get_turbo_vaed,
                vg.OmniAudioFeatureExtractor,
                vg.SAAudioFeatureExtractor,
            ) = saved
        print("prune_pro dit_bench: skipped video/audio VAE load", flush=True)

    vg.MagiEvaluator.__init__ = _init

    orig_ewl = vg.MagiEvaluator.evaluate_with_latent

    def _ewl(self, *args, **kwargs):
        torch.cuda.synchronize()
        t0 = time.perf_counter()
        out = orig_ewl(self, *args, **kwargs)
        torch.cuda.synchronize()
        print(f"dit_denoise_s={time.perf_counter() - t0:.6f}", flush=True)
        return out

    vg.MagiEvaluator.evaluate_with_latent = _ewl

    orig_eval = vg.MagiEvaluator.evaluate

    def _eval(self, *args, **kwargs):
        orig_pp = self.post_process

        def _skip_pp(*a, **k):
            from inference.utils import event_path_timer, print_rank_last

            event_path_timer().synced_record(
                "Step7: Decode Audio skipped", print_fn=print_rank_last
            )
            event_path_timer().synced_record(
                "Step8: Post Process skipped", print_fn=print_rank_last
            )
            return None, None

        self.post_process = _skip_pp
        try:
            return orig_eval(self, *args, **kwargs)
        finally:
            self.post_process = orig_pp

    vg.MagiEvaluator.evaluate = _eval

    def _run(
        self,
        prompt,
        image,
        audio,
        save_path_prefix,
        seed=42,
        seconds=4.0,
        br_width=480,
        br_height=272,
        sr_width=None,
        sr_height=None,
        output_width=None,
        output_height=None,
        upsample_mode=None,
    ):
        self._validate_offline_request(prompt=prompt, save_path_prefix=save_path_prefix)
        save_path = f"{save_path_prefix}_{seconds}s_{br_width}x{br_height}.dit_bench"
        with torch.random.fork_rng(devices=[torch.cuda.current_device()]):
            torch.random.manual_seed(seed)
            self.evaluator.evaluate(
                prompt,
                image,
                audio,
                seconds=seconds,
                br_width=br_width,
                br_height=br_height,
                sr_width=sr_width,
                sr_height=sr_height,
                br_num_inference_steps=self.evaluation_config.num_inference_steps,
                sr_num_inference_steps=self.evaluation_config.sr_num_inference_steps,
            )
            self.evaluator.dump_moe_clip(save_path_prefix)
        print(f"dit_bench skip_video {save_path}", flush=True)
        if torch.distributed.is_initialized():
            torch.distributed.barrier()
        return save_path

    pl.MagiPipeline.run_offline = _run
    vg._prune_pro_dit_bench = True
    print("prune_pro: installed DiT-denoise bench (skip VAE decode/save)", flush=True)