File size: 18,137 Bytes
3e936b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from pipeline.streaming_training import StreamingTrainingPipeline
from typing import List, Optional, Tuple
import torch
import torch.distributed as dist

class StreamingSwitchTrainingPipeline(StreamingTrainingPipeline):

    def __init__(self, *args, **kwargs):
        _apr_enabled = kwargs.pop('apr_enabled', False)
        _apr_alpha_max = kwargs.pop('apr_alpha_max', 0.8)
        _apr_d_window = kwargs.pop('apr_d_window', None)
        _apr_blend_sink = kwargs.pop('apr_blend_sink', False)
        _global_sink = kwargs.pop('global_sink', False)
        super().__init__(*args, **kwargs)
        self.global_sink = _global_sink
        self.apr_enabled = _apr_enabled
        self.apr_alpha_max = float(max(0.0, min(0.8, _apr_alpha_max)))
        self.apr_d_window = _apr_d_window
        self.apr_blend_sink = _apr_blend_sink
        if not dist.is_initialized() or dist.get_rank() == 0:
            print(f'[StreamingSwitchTrainingPipeline] global_sink={self.global_sink} (config-driven, Opt 14 kwargs.pop fix active)')

    def generate_chunk_with_cache(self, noise: torch.Tensor, conditional_dict: dict, *, current_start_frame: int=0, requires_grad: bool=True, switch_frame_index: Optional[int]=None, switch_conditional_dict: Optional[dict]=None, switch_recache_frames: Optional[torch.Tensor]=None, return_sim_step: bool=False) -> Tuple[torch.Tensor, Optional[int], Optional[int]]:
        if switch_conditional_dict is None or switch_frame_index is None:
            return super().generate_chunk_with_cache(noise=noise, conditional_dict=conditional_dict, current_start_frame=current_start_frame, requires_grad=requires_grad, return_sim_step=return_sim_step)
        batch_size, chunk_frames, num_channels, height, width = noise.shape
        assert chunk_frames % self.num_frame_per_block == 0
        num_blocks = chunk_frames // self.num_frame_per_block
        all_num_frames = [self.num_frame_per_block] * num_blocks
        output = torch.zeros_like(noise)
        num_denoising_steps = len(self.denoising_step_list)
        exit_flags = self.generate_and_sync_list(len(all_num_frames), num_denoising_steps, device=noise.device)
        if not requires_grad:
            start_gradient_frame_index = chunk_frames
        else:
            start_gradient_frame_index = switch_frame_index
        local_start_frame = 0
        self.generator.model.local_attn_size = int(self.local_attn_size)
        self._set_all_modules_max_attention_size(int(self.local_attn_size))
        using_second = False
        cond_in_use = conditional_dict
        for block_index, current_num_frames in enumerate(all_num_frames):
            if not using_second and local_start_frame >= switch_frame_index:
                self._recache_after_switch(output[:, :local_start_frame, ...], current_start_frame + local_start_frame, switch_conditional_dict, local_start_frame, switch_recache_frames)
                cond_in_use = switch_conditional_dict
                using_second = True
            noisy_input = noise[:, local_start_frame:local_start_frame + current_num_frames]
            for step_idx, current_timestep in enumerate(self.denoising_step_list):
                exit_flag = step_idx == exit_flags[0] if self.same_step_across_blocks else step_idx == exit_flags[block_index]
                timestep = torch.ones([batch_size, current_num_frames], device=noise.device, dtype=torch.int64) * current_timestep
                if not exit_flag:
                    with torch.no_grad():
                        _, denoised_pred = self.generator(noisy_image_or_video=noisy_input, conditional_dict=cond_in_use, timestep=timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=(current_start_frame + local_start_frame) * self.frame_seq_length)
                        if step_idx < len(self.denoising_step_list) - 1:
                            next_timestep = self.denoising_step_list[step_idx + 1]
                            noisy_input = self.scheduler.add_noise(denoised_pred.flatten(0, 1), torch.randn_like(denoised_pred.flatten(0, 1)), next_timestep * torch.ones([batch_size * current_num_frames], device=noise.device, dtype=torch.long)).unflatten(0, denoised_pred.shape[:2])
                else:
                    enable_grad = local_start_frame >= start_gradient_frame_index
                    context_manager = torch.enable_grad() if enable_grad else torch.no_grad()
                    with context_manager:
                        _, denoised_pred = self.generator(noisy_image_or_video=noisy_input, conditional_dict=cond_in_use, timestep=timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=(current_start_frame + local_start_frame) * self.frame_seq_length)
                    break
            output[:, local_start_frame:local_start_frame + current_num_frames] = denoised_pred
            context_timestep = torch.ones_like(timestep) * self.context_noise
            context_noisy = self.scheduler.add_noise(denoised_pred.flatten(0, 1), torch.randn_like(denoised_pred.flatten(0, 1)), context_timestep.flatten(0, 1)).unflatten(0, denoised_pred.shape[:2])
            with torch.no_grad():
                self.generator(noisy_image_or_video=context_noisy, conditional_dict=cond_in_use, timestep=context_timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=(current_start_frame + local_start_frame) * self.frame_seq_length)
            local_start_frame += current_num_frames
        if not self.same_step_across_blocks:
            denoised_timestep_from, denoised_timestep_to = (None, None)
        elif exit_flags[0] == len(self.denoising_step_list) - 1:
            denoised_timestep_to = 0
            denoised_timestep_from = 1000 - torch.argmin((self.scheduler.timesteps.cuda() - self.denoising_step_list[exit_flags[0]].cuda()).abs(), dim=0).item()
        else:
            denoised_timestep_to = 1000 - torch.argmin((self.scheduler.timesteps.cuda() - self.denoising_step_list[exit_flags[0] + 1].cuda()).abs(), dim=0).item()
            denoised_timestep_from = 1000 - torch.argmin((self.scheduler.timesteps.cuda() - self.denoising_step_list[exit_flags[0]].cuda()).abs(), dim=0).item()
        if return_sim_step:
            return (output, denoised_timestep_from, denoised_timestep_to, exit_flags[0] + 1)
        return (output, denoised_timestep_from, denoised_timestep_to)

    def _recache_after_switch(self, output, current_start_frame, new_conditional_dict, local_start_frame=None, switch_recache_frames=None):
        sink_tok_size = None
        sink_backup = None
        sink_norm_before = None
        if self.global_sink and current_start_frame > 0:
            gen = self.generator
            from torch.distributed.fsdp import FullyShardedDataParallel as _FSDP
            if hasattr(gen, '_fsdp_wrapped_module'):
                w = gen._fsdp_wrapped_module
                m = w.model
                _inner_tmp = m._fsdp_wrapped_module if isinstance(m, _FSDP) else m
                if hasattr(_inner_tmp, 'base_model') and hasattr(_inner_tmp.base_model, 'model'):
                    _inner_tmp = _inner_tmp.base_model.model
            elif hasattr(gen, 'model'):
                _inner_tmp = gen.model
            else:
                _inner_tmp = None
            if _inner_tmp is not None:
                sink_tok_size = _inner_tmp.blocks[0].self_attn.sink_size * self.frame_seq_length
                sink_backup = [{'k': self.kv_cache1[i]['k'][:, :sink_tok_size].clone(), 'v': self.kv_cache1[i]['v'][:, :sink_tok_size].clone()} for i in range(self.num_transformer_blocks)]
                sink_norm_before = torch.norm(self.kv_cache1[0]['k'][:, :sink_tok_size]).float().item()
                if not dist.is_initialized() or dist.get_rank() == 0:
                    print(f'[Recache-Train] switch@frame={current_start_frame}, global_sink=True, sink_tok={sink_tok_size}, sink_norm(block0) BEFORE = {sink_norm_before:.4f}')
        elif not self.global_sink:
            if not dist.is_initialized() or dist.get_rank() == 0:
                print(f'[Recache-Train] switch@frame={current_start_frame}, global_sink=False (sink will be recached with new prompt)')
        apr_old_cache = None
        if self.apr_enabled and current_start_frame > 0:
            apr_old_cache = [{'k': self.kv_cache1[i]['k'].clone(), 'v': self.kv_cache1[i]['v'].clone()} for i in range(self.num_transformer_blocks)]
        if not self.global_sink:
            for block_idx in range(self.num_transformer_blocks):
                cache = self.kv_cache1[block_idx]
                cache['k'].zero_()
                cache['v'].zero_()
        for blk in self.crossattn_cache:
            blk['k'].zero_()
            blk['v'].zero_()
            blk['is_init'] = False
        gen = self.generator
        from torch.distributed.fsdp import FullyShardedDataParallel as _FSDP
        if hasattr(gen, '_fsdp_wrapped_module'):
            w = gen._fsdp_wrapped_module
            m = w.model
            inner = m._fsdp_wrapped_module if isinstance(m, _FSDP) else m
            if hasattr(inner, 'base_model') and hasattr(inner.base_model, 'model'):
                inner = inner.base_model.model
        elif hasattr(gen, 'model'):
            inner = gen.model
        else:
            inner = None
        if current_start_frame == 0:
            return
        if switch_recache_frames is not None:
            frames_to_recache = torch.cat([switch_recache_frames, output], dim=1)[:, -21:, ...]
            num_recache_frames = frames_to_recache.shape[1]
        elif local_start_frame is not None:
            num_recache_frames = min(local_start_frame, 21)
            frames_to_recache = output[:, -num_recache_frames:]
        else:
            num_recache_frames = min(current_start_frame, 21)
            frames_to_recache = output[:, -num_recache_frames:]
        batch_size, num_recache_frames, c, h, w = frames_to_recache.shape
        device = frames_to_recache.device
        block_mask = self.generator.model._prepare_blockwise_causal_attn_mask(device=device, num_frames=num_recache_frames, frame_seqlen=self.frame_seq_length, num_frame_per_block=self.num_frame_per_block, local_attn_size=21)
        context_timestep = torch.ones([batch_size, num_recache_frames], device=device, dtype=torch.int64) * self.context_noise
        self.generator.model.block_mask = block_mask
        _has_memory = inner is not None and (getattr(inner, 'query_memory_encoder', None) is not None or getattr(inner, 'sink_memory', None) is not None)
        if _has_memory:
            frame_seqlen = self.frame_seq_length
            sink_frames = inner.blocks[0].self_attn.sink_size
            max_attn = inner.blocks[0].self_attn.max_attention_size
            recent_window_frames = (max_attn - sink_frames * frame_seqlen) // frame_seqlen
            inner._ei_prev_window_start = max(sink_frames, current_start_frame - recent_window_frames)
        with torch.no_grad():
            self.generator(noisy_image_or_video=frames_to_recache, conditional_dict=new_conditional_dict, timestep=context_timestep, kv_cache=self.kv_cache1, crossattn_cache=self.crossattn_cache, current_start=(current_start_frame - num_recache_frames) * self.frame_seq_length)
        if sink_backup is not None:
            for i in range(self.num_transformer_blocks):
                self.kv_cache1[i]['k'][:, :sink_tok_size] = sink_backup[i]['k']
                self.kv_cache1[i]['v'][:, :sink_tok_size] = sink_backup[i]['v']
            sink_norm_after = torch.norm(self.kv_cache1[0]['k'][:, :sink_tok_size]).float().item()
            if not dist.is_initialized() or dist.get_rank() == 0:
                delta = abs(sink_norm_after - sink_norm_before)
                status = 'PRESERVED ✓' if delta < 0.001 else f'MISMATCH ✗ (delta={delta:.4e})'
                print(f'[Recache-Train] global_sink=True: sink_norm AFTER = {sink_norm_after:.4f} [{status}]')
                try:
                    import model.streaming_training as _st_mod
                    setattr(_st_mod, '_last_recache_sink_delta', delta)
                except Exception:
                    pass
            del sink_backup
        for blk in self.crossattn_cache:
            blk['k'].zero_()
            blk['v'].zero_()
            blk['is_init'] = False
        if self.apr_enabled and apr_old_cache is not None:
            self._apply_apr_blend(old_cache=apr_old_cache, recache_start_frame=current_start_frame - num_recache_frames, current_start_frame=current_start_frame, num_recache_frames=num_recache_frames, inner=inner)
            del apr_old_cache
        if inner is not None and getattr(inner, 'query_memory_encoder', None) is not None:
            enc = inner.query_memory_encoder
            if getattr(enc, 'memory_recache', False):
                enc.reset(batch_size=frames_to_recache.shape[0], device=frames_to_recache.device, dtype=torch.bfloat16)
                last_blk = self.num_transformer_blocks - 1
                cache = self.kv_cache1[last_blk]
                frame_seqlen = self.frame_seq_length
                sink_tok = inner.blocks[0].self_attn.sink_size * frame_seqlen
                local_end = cache['local_end_index'].item()
                if local_end > sink_tok:
                    recache_k = cache['k'][:, sink_tok:local_end].clone()
                    recache_v = cache['v'][:, sink_tok:local_end].clone()
                    sink_k = cache['k'][:, :sink_tok].clone() if sink_tok > 0 else None
                    sink_v = cache['v'][:, :sink_tok].clone() if sink_tok > 0 else None
                    enc.update(recache_k, recache_v, sink_k, sink_v)
        if inner is not None and getattr(inner, 'sink_memory', None) is not None:
            sm = inner.sink_memory
            sm.reset()
            sink_frames_count = inner.blocks[0].self_attn.sink_size
            sink_output = output[:, :sink_frames_count] if output.shape[1] >= sink_frames_count else None
            if sink_output is None:
                if switch_recache_frames is not None and switch_recache_frames.shape[1] >= sink_frames_count:
                    sink_output = switch_recache_frames[:, :sink_frames_count]
            if sink_output is not None:
                device = sink_output.device
                batch_size_local = sink_output.shape[0]
                sink_ts = torch.ones([batch_size_local, sink_frames_count], device=device, dtype=torch.int64) * self.context_noise
                n_heads = inner.blocks[0].self_attn.num_heads
                head_dim = inner.blocks[0].self_attn.head_dim
                sink_cache_size = sink_frames_count * self.frame_seq_length
                temp_kv = [{'k': torch.zeros([batch_size_local, sink_cache_size, n_heads, head_dim], device=device, dtype=torch.bfloat16), 'v': torch.zeros([batch_size_local, sink_cache_size, n_heads, head_dim], device=device, dtype=torch.bfloat16), 'global_end_index': torch.zeros(1, dtype=torch.long, device=device), 'local_end_index': torch.zeros(1, dtype=torch.long, device=device)} for _ in range(self.num_transformer_blocks)]
                temp_crossattn = [{'k': torch.zeros_like(self.crossattn_cache[0]['k']), 'v': torch.zeros_like(self.crossattn_cache[0]['v']), 'is_init': False} for _ in range(self.num_transformer_blocks)]
                with torch.no_grad():
                    self.generator(noisy_image_or_video=sink_output, conditional_dict=new_conditional_dict, timestep=sink_ts, kv_cache=temp_kv, crossattn_cache=temp_crossattn, current_start=0)
                frame_seqlen_local = self.frame_seq_length
                sink_frames_local = inner.blocks[0].self_attn.sink_size
                max_attn_local = inner.blocks[0].self_attn.max_attention_size
                recent_win = (max_attn_local - sink_frames_local * frame_seqlen_local) // frame_seqlen_local
                inner._ei_prev_window_start = max(sink_frames_local, current_start_frame - recent_win)
            if not dist.is_initialized() or dist.get_rank() == 0:
                print(f'[SinkMem] reset + re-captured on prompt switch at frame {current_start_frame}')

    def _apply_apr_blend(self, *, old_cache, recache_start_frame, current_start_frame, num_recache_frames, inner):
        frame_seqlen = self.frame_seq_length
        sink_size = inner.blocks[0].self_attn.sink_size
        sink_tok = sink_size * frame_seqlen
        D = self.apr_d_window if self.apr_d_window is not None else num_recache_frames
        D = max(1, int(D))
        alpha_max = self.apr_alpha_max
        n_frames = num_recache_frames
        with torch.no_grad():
            for blk_idx in range(self.num_transformer_blocks):
                new_k = self.kv_cache1[blk_idx]['k']
                new_v = self.kv_cache1[blk_idx]['v']
                old_k = old_cache[blk_idx]['k']
                old_v = old_cache[blk_idx]['v']
                if self.apr_blend_sink and (not self.global_sink):
                    for f in range(sink_size):
                        d_t = current_start_frame - f
                        alpha = min(alpha_max, max(0.0, 1.0 - d_t / D))
                        s0, s1 = (f * frame_seqlen, (f + 1) * frame_seqlen)
                        new_k[:, s0:s1] = (1 - alpha) * old_k[:, s0:s1] + alpha * new_k[:, s0:s1]
                        new_v[:, s0:s1] = (1 - alpha) * old_v[:, s0:s1] + alpha * new_v[:, s0:s1]
                base = sink_tok
                for f in range(n_frames):
                    absolute_frame = recache_start_frame + f
                    d_t = current_start_frame - absolute_frame
                    alpha = min(alpha_max, max(0.0, 1.0 - d_t / D))
                    s0, s1 = (base + f * frame_seqlen, base + (f + 1) * frame_seqlen)
                    new_k[:, s0:s1] = (1 - alpha) * old_k[:, s0:s1] + alpha * new_k[:, s0:s1]
                    new_v[:, s0:s1] = (1 - alpha) * old_v[:, s0:s1] + alpha * new_v[:, s0:s1]
        if not dist.is_initialized() or dist.get_rank() == 0:
            print(f'[APR] switch@{current_start_frame}: blended {n_frames} frames (α_max={alpha_max}, D_window={D})')