victor HF Staff commited on
Commit
b2fe92c
·
verified ·
1 Parent(s): 2cb441e

perf: optional First-Block-Cache "Faster" toggle (~1.4x, A/B-verified near-clean); default off

Browse files
app.py CHANGED
@@ -55,6 +55,10 @@ REWRITER_MODEL = os.environ.get("REWRITER_MODEL", "Qwen/Qwen3.6-27B:deepinfra")
55
  # forward per step on the low-noise tail (~15% fewer transformer forwards).
56
  # 1.0 disables the optimization (standard CFG on every step).
57
  CFG_STOP = float(os.environ.get("LINGBOT_CFG_STOP", "0.7"))
 
 
 
 
58
 
59
  # (height, width), multiples of 16 — official 480p buckets.
60
  VIDEO_SIZES = {
@@ -292,9 +296,10 @@ def _run_pipe(target, **call_kwargs):
292
 
293
  @spaces.GPU(duration=_video_gpu_duration, size="xlarge")
294
  def _gpu_generate_video(caption, height, width, num_frames, steps, guidance_scale, shift,
295
- negative_prompt, seed, image=None):
296
  target = pipe_i2v if image is not None else pipe
297
  kwargs = {"image": image} if image is not None else {}
 
298
  t0 = time.perf_counter()
299
  output, cfg_mode = _run_pipe(
300
  target,
@@ -307,6 +312,9 @@ def _gpu_generate_video(caption, height, width, num_frames, steps, guidance_scal
307
  guidance_scale=float(guidance_scale),
308
  shift=float(shift),
309
  guidance_stop=CFG_STOP,
 
 
 
310
  generator=torch.Generator().manual_seed(seed),
311
  **kwargs,
312
  )
@@ -325,7 +333,9 @@ def _image_gpu_duration(caption, height, width, steps, *args, **kwargs) -> int:
325
 
326
 
327
  @spaces.GPU(duration=_image_gpu_duration, size="xlarge")
328
- def _gpu_generate_image(caption, height, width, steps, guidance_scale, shift, negative_prompt, seed):
 
 
329
  t0 = time.perf_counter()
330
  output, cfg_mode = _run_pipe(
331
  pipe,
@@ -338,6 +348,9 @@ def _gpu_generate_image(caption, height, width, steps, guidance_scale, shift, ne
338
  guidance_scale=float(guidance_scale),
339
  shift=float(shift),
340
  guidance_stop=CFG_STOP,
 
 
 
341
  generator=torch.Generator().manual_seed(seed),
342
  )
343
  torch.cuda.synchronize()
@@ -354,6 +367,7 @@ def generate_t2v(
354
  steps,
355
  guidance_scale,
356
  shift,
 
357
  negative_prompt,
358
  seed,
359
  randomize_seed,
@@ -367,7 +381,8 @@ def generate_t2v(
367
  seed = _resolve_seed(seed, randomize_seed)
368
  caption = _prepare_caption(prompt, "t2v", duration_s, enhance)
369
  video = _gpu_generate_video(
370
- caption, height, width, num_frames, steps, guidance_scale, shift, negative_prompt, seed
 
371
  )
372
  return video, seed, caption
373
 
@@ -380,6 +395,7 @@ def generate_i2v(
380
  steps,
381
  guidance_scale,
382
  shift,
 
383
  negative_prompt,
384
  seed,
385
  randomize_seed,
@@ -396,7 +412,7 @@ def generate_i2v(
396
  caption = _prepare_caption(prompt, "ti2v", duration_s, enhance, image=image)
397
  video = _gpu_generate_video(
398
  caption, height, width, num_frames, steps, guidance_scale, shift, negative_prompt, seed,
399
- image=image,
400
  )
401
  return video, seed, caption
402
 
@@ -407,6 +423,7 @@ def generate_t2i(
407
  steps,
408
  guidance_scale,
409
  shift,
 
410
  negative_prompt,
411
  seed,
412
  randomize_seed,
@@ -417,7 +434,8 @@ def generate_t2i(
417
  seed = _resolve_seed(seed, randomize_seed)
418
  caption = _prepare_caption(prompt, "t2i", None, enhance)
419
  image = _gpu_generate_image(
420
- caption, height, width, steps, guidance_scale, shift, negative_prompt, seed
 
421
  )
422
  return image, seed, caption
423
 
@@ -535,26 +553,30 @@ def _advanced(negative_default: str, steps_value: int):
535
  steps = gr.Slider(label="Inference steps", minimum=4, maximum=40, step=1, value=steps_value)
536
  guidance = gr.Slider(label="Guidance scale", minimum=1.0, maximum=10.0, step=0.5, value=3.0)
537
  shift = gr.Slider(label="Flow shift", minimum=1.0, maximum=9.0, step=0.5, value=3.0)
 
 
 
 
538
  negative = gr.Textbox(label="Negative prompt", value=negative_default, lines=3)
539
  seed, randomize = _seed_row()
540
- return steps, guidance, shift, negative, seed, randomize
541
 
542
 
543
  # Example runners: examples carry only the natural-language prompt (and first
544
  # frame for I2V) — enhancement is on and every other control keeps its default,
545
  # so the examples table stays to one or two clean columns instead of ten.
546
  def _example_t2v(prompt):
547
- return generate_t2v(prompt, "832 × 480 (16:9)", 2.0, 24, 3.0, 3.0,
548
  DEFAULT_NEGATIVE_PROMPT, 42, False, True)
549
 
550
 
551
  def _example_i2v(image, prompt):
552
- return generate_i2v(image, prompt, "832 × 480 (16:9)", 2.0, 24, 3.0, 3.0,
553
  DEFAULT_NEGATIVE_PROMPT, 42, False, True)
554
 
555
 
556
  def _example_t2i(prompt):
557
- return generate_t2i(prompt, "1280 × 736 (16:9, 720p)", 30, 3.0, 3.0,
558
  DEFAULT_NEGATIVE_PROMPT_IMAGE, 42, False, True)
559
 
560
 
@@ -609,7 +631,7 @@ with gr.Blocks(title="LingBot-Video MoE 30B-A3B") as demo:
609
  t2v_enhance = gr.Checkbox(
610
  label="Enhance prompt (official rewriter — required for plain prompts)", value=True
611
  )
612
- t2v_steps, t2v_guidance, t2v_shift, t2v_negative, t2v_seed, t2v_rand = _advanced(
613
  DEFAULT_NEGATIVE_PROMPT, 24
614
  )
615
  t2v_btn = gr.Button("Generate video", variant="primary")
@@ -620,7 +642,7 @@ with gr.Blocks(title="LingBot-Video MoE 30B-A3B") as demo:
620
  t2v_caption_out = gr.Textbox(label="Caption", lines=4)
621
 
622
  t2v_inputs = [
623
- t2v_prompt, t2v_size, t2v_dur, t2v_steps, t2v_guidance, t2v_shift,
624
  t2v_negative, t2v_seed, t2v_rand, t2v_enhance,
625
  ]
626
  t2v_outputs = [t2v_out, t2v_seed_out, t2v_caption_out]
@@ -657,7 +679,7 @@ with gr.Blocks(title="LingBot-Video MoE 30B-A3B") as demo:
657
  i2v_enhance = gr.Checkbox(
658
  label="Enhance prompt (official rewriter — required for plain prompts)", value=True
659
  )
660
- i2v_steps, i2v_guidance, i2v_shift, i2v_negative, i2v_seed, i2v_rand = _advanced(
661
  DEFAULT_NEGATIVE_PROMPT, 24
662
  )
663
  i2v_btn = gr.Button("Generate video", variant="primary")
@@ -668,7 +690,7 @@ with gr.Blocks(title="LingBot-Video MoE 30B-A3B") as demo:
668
  i2v_caption_out = gr.Textbox(label="Caption", lines=4)
669
 
670
  i2v_inputs = [
671
- i2v_image, i2v_prompt, i2v_size, i2v_dur, i2v_steps, i2v_guidance, i2v_shift,
672
  i2v_negative, i2v_seed, i2v_rand, i2v_enhance,
673
  ]
674
  i2v_outputs = [i2v_out, i2v_seed_out, i2v_caption_out]
@@ -700,7 +722,7 @@ with gr.Blocks(title="LingBot-Video MoE 30B-A3B") as demo:
700
  t2i_enhance = gr.Checkbox(
701
  label="Enhance prompt (official rewriter — required for plain prompts)", value=True
702
  )
703
- t2i_steps, t2i_guidance, t2i_shift, t2i_negative, t2i_seed, t2i_rand = _advanced(
704
  DEFAULT_NEGATIVE_PROMPT_IMAGE, 30
705
  )
706
  t2i_btn = gr.Button("Generate image", variant="primary")
@@ -711,7 +733,7 @@ with gr.Blocks(title="LingBot-Video MoE 30B-A3B") as demo:
711
  t2i_caption_out = gr.Textbox(label="Caption", lines=4)
712
 
713
  t2i_inputs = [
714
- t2i_prompt, t2i_size, t2i_steps, t2i_guidance, t2i_shift,
715
  t2i_negative, t2i_seed, t2i_rand, t2i_enhance,
716
  ]
717
  t2i_outputs = [t2i_out, t2i_seed_out, t2i_caption_out]
 
55
  # forward per step on the low-noise tail (~15% fewer transformer forwards).
56
  # 1.0 disables the optimization (standard CFG on every step).
57
  CFG_STOP = float(os.environ.get("LINGBOT_CFG_STOP", "0.7"))
58
+ # "Faster" mode: First-Block-Cache (reuse the block-loop residual on steps whose
59
+ # first-block residual barely changes). (threshold, warmup, max_consecutive).
60
+ # A/B-verified at ~1.4x with near-identical quality and no temporal flicker.
61
+ FAST_CACHE = (0.03, 4, 1)
62
 
63
  # (height, width), multiples of 16 — official 480p buckets.
64
  VIDEO_SIZES = {
 
296
 
297
  @spaces.GPU(duration=_video_gpu_duration, size="xlarge")
298
  def _gpu_generate_video(caption, height, width, num_frames, steps, guidance_scale, shift,
299
+ negative_prompt, seed, cache=None, image=None):
300
  target = pipe_i2v if image is not None else pipe
301
  kwargs = {"image": image} if image is not None else {}
302
+ thr, warmup, maxc = cache if cache else (None, 4, 1)
303
  t0 = time.perf_counter()
304
  output, cfg_mode = _run_pipe(
305
  target,
 
312
  guidance_scale=float(guidance_scale),
313
  shift=float(shift),
314
  guidance_stop=CFG_STOP,
315
+ fbcache_threshold=thr,
316
+ fbcache_warmup=warmup,
317
+ fbcache_max_consecutive=maxc,
318
  generator=torch.Generator().manual_seed(seed),
319
  **kwargs,
320
  )
 
333
 
334
 
335
  @spaces.GPU(duration=_image_gpu_duration, size="xlarge")
336
+ def _gpu_generate_image(caption, height, width, steps, guidance_scale, shift, negative_prompt,
337
+ seed, cache=None):
338
+ thr, warmup, maxc = cache if cache else (None, 4, 1)
339
  t0 = time.perf_counter()
340
  output, cfg_mode = _run_pipe(
341
  pipe,
 
348
  guidance_scale=float(guidance_scale),
349
  shift=float(shift),
350
  guidance_stop=CFG_STOP,
351
+ fbcache_threshold=thr,
352
+ fbcache_warmup=warmup,
353
+ fbcache_max_consecutive=maxc,
354
  generator=torch.Generator().manual_seed(seed),
355
  )
356
  torch.cuda.synchronize()
 
367
  steps,
368
  guidance_scale,
369
  shift,
370
+ fast,
371
  negative_prompt,
372
  seed,
373
  randomize_seed,
 
381
  seed = _resolve_seed(seed, randomize_seed)
382
  caption = _prepare_caption(prompt, "t2v", duration_s, enhance)
383
  video = _gpu_generate_video(
384
+ caption, height, width, num_frames, steps, guidance_scale, shift, negative_prompt, seed,
385
+ cache=FAST_CACHE if fast else None,
386
  )
387
  return video, seed, caption
388
 
 
395
  steps,
396
  guidance_scale,
397
  shift,
398
+ fast,
399
  negative_prompt,
400
  seed,
401
  randomize_seed,
 
412
  caption = _prepare_caption(prompt, "ti2v", duration_s, enhance, image=image)
413
  video = _gpu_generate_video(
414
  caption, height, width, num_frames, steps, guidance_scale, shift, negative_prompt, seed,
415
+ cache=FAST_CACHE if fast else None, image=image,
416
  )
417
  return video, seed, caption
418
 
 
423
  steps,
424
  guidance_scale,
425
  shift,
426
+ fast,
427
  negative_prompt,
428
  seed,
429
  randomize_seed,
 
434
  seed = _resolve_seed(seed, randomize_seed)
435
  caption = _prepare_caption(prompt, "t2i", None, enhance)
436
  image = _gpu_generate_image(
437
+ caption, height, width, steps, guidance_scale, shift, negative_prompt, seed,
438
+ cache=FAST_CACHE if fast else None,
439
  )
440
  return image, seed, caption
441
 
 
553
  steps = gr.Slider(label="Inference steps", minimum=4, maximum=40, step=1, value=steps_value)
554
  guidance = gr.Slider(label="Guidance scale", minimum=1.0, maximum=10.0, step=0.5, value=3.0)
555
  shift = gr.Slider(label="Flow shift", minimum=1.0, maximum=9.0, step=0.5, value=3.0)
556
+ fast = gr.Checkbox(
557
+ label="⚡ Faster generation (feature caching, ~1.4× — slightly softer detail)",
558
+ value=False,
559
+ )
560
  negative = gr.Textbox(label="Negative prompt", value=negative_default, lines=3)
561
  seed, randomize = _seed_row()
562
+ return steps, guidance, shift, fast, negative, seed, randomize
563
 
564
 
565
  # Example runners: examples carry only the natural-language prompt (and first
566
  # frame for I2V) — enhancement is on and every other control keeps its default,
567
  # so the examples table stays to one or two clean columns instead of ten.
568
  def _example_t2v(prompt):
569
+ return generate_t2v(prompt, "832 × 480 (16:9)", 2.0, 24, 3.0, 3.0, False,
570
  DEFAULT_NEGATIVE_PROMPT, 42, False, True)
571
 
572
 
573
  def _example_i2v(image, prompt):
574
+ return generate_i2v(image, prompt, "832 × 480 (16:9)", 2.0, 24, 3.0, 3.0, False,
575
  DEFAULT_NEGATIVE_PROMPT, 42, False, True)
576
 
577
 
578
  def _example_t2i(prompt):
579
+ return generate_t2i(prompt, "1280 × 736 (16:9, 720p)", 30, 3.0, 3.0, False,
580
  DEFAULT_NEGATIVE_PROMPT_IMAGE, 42, False, True)
581
 
582
 
 
631
  t2v_enhance = gr.Checkbox(
632
  label="Enhance prompt (official rewriter — required for plain prompts)", value=True
633
  )
634
+ t2v_steps, t2v_guidance, t2v_shift, t2v_fast, t2v_negative, t2v_seed, t2v_rand = _advanced(
635
  DEFAULT_NEGATIVE_PROMPT, 24
636
  )
637
  t2v_btn = gr.Button("Generate video", variant="primary")
 
642
  t2v_caption_out = gr.Textbox(label="Caption", lines=4)
643
 
644
  t2v_inputs = [
645
+ t2v_prompt, t2v_size, t2v_dur, t2v_steps, t2v_guidance, t2v_shift, t2v_fast,
646
  t2v_negative, t2v_seed, t2v_rand, t2v_enhance,
647
  ]
648
  t2v_outputs = [t2v_out, t2v_seed_out, t2v_caption_out]
 
679
  i2v_enhance = gr.Checkbox(
680
  label="Enhance prompt (official rewriter — required for plain prompts)", value=True
681
  )
682
+ i2v_steps, i2v_guidance, i2v_shift, i2v_fast, i2v_negative, i2v_seed, i2v_rand = _advanced(
683
  DEFAULT_NEGATIVE_PROMPT, 24
684
  )
685
  i2v_btn = gr.Button("Generate video", variant="primary")
 
690
  i2v_caption_out = gr.Textbox(label="Caption", lines=4)
691
 
692
  i2v_inputs = [
693
+ i2v_image, i2v_prompt, i2v_size, i2v_dur, i2v_steps, i2v_guidance, i2v_shift, i2v_fast,
694
  i2v_negative, i2v_seed, i2v_rand, i2v_enhance,
695
  ]
696
  i2v_outputs = [i2v_out, i2v_seed_out, i2v_caption_out]
 
722
  t2i_enhance = gr.Checkbox(
723
  label="Enhance prompt (official rewriter — required for plain prompts)", value=True
724
  )
725
+ t2i_steps, t2i_guidance, t2i_shift, t2i_fast, t2i_negative, t2i_seed, t2i_rand = _advanced(
726
  DEFAULT_NEGATIVE_PROMPT_IMAGE, 30
727
  )
728
  t2i_btn = gr.Button("Generate image", variant="primary")
 
733
  t2i_caption_out = gr.Textbox(label="Caption", lines=4)
734
 
735
  t2i_inputs = [
736
+ t2i_prompt, t2i_size, t2i_steps, t2i_guidance, t2i_shift, t2i_fast,
737
  t2i_negative, t2i_seed, t2i_rand, t2i_enhance,
738
  ]
739
  t2i_outputs = [t2i_out, t2i_seed_out, t2i_caption_out]
lingbot_video/pipeline_lingbot_video.py CHANGED
@@ -375,6 +375,9 @@ class LingBotVideoPipeline(DiffusionPipeline):
375
  batch_cfg: bool = False,
376
  null_cond_clone_zero: bool = False,
377
  guidance_stop: float = 1.0,
 
 
 
378
  t_thresh: Optional[float] = None,
379
  refiner_sigma_tail_steps: int = LOW_NOISE_TAIL_V1_DEFAULT_STEPS,
380
  offload_vae_during_denoise: bool = False,
@@ -487,6 +490,10 @@ class LingBotVideoPipeline(DiffusionPipeline):
487
  total_steps if guidance_stop >= 1.0
488
  else max(1, int(round(float(guidance_stop) * total_steps)))
489
  )
 
 
 
 
490
  for i, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
491
  if cfg_parallel:
492
  dist.broadcast(latents, src=cfg_latent_src, group=cfg_parallel_group)
@@ -552,6 +559,7 @@ class LingBotVideoPipeline(DiffusionPipeline):
552
  timestep_batch,
553
  prompt_model_input,
554
  encoder_attention_mask=prompt_mask,
 
555
  return_dict=False,
556
  )[0].float()
557
 
@@ -563,6 +571,7 @@ class LingBotVideoPipeline(DiffusionPipeline):
563
  timestep_batch,
564
  negative_model_input,
565
  encoder_attention_mask=negative_mask,
 
566
  return_dict=False,
567
  )[0].float()
568
  noise_pred = noise_pred_uncond + guidance_scale * (noise_pred - noise_pred_uncond)
 
375
  batch_cfg: bool = False,
376
  null_cond_clone_zero: bool = False,
377
  guidance_stop: float = 1.0,
378
+ fbcache_threshold: Optional[float] = None,
379
+ fbcache_warmup: int = 3,
380
+ fbcache_max_consecutive: int = 3,
381
  t_thresh: Optional[float] = None,
382
  refiner_sigma_tail_steps: int = LOW_NOISE_TAIL_V1_DEFAULT_STEPS,
383
  offload_vae_during_denoise: bool = False,
 
490
  total_steps if guidance_stop >= 1.0
491
  else max(1, int(round(float(guidance_stop) * total_steps)))
492
  )
493
+ if hasattr(self.transformer, "reset_fbcache"):
494
+ self.transformer.reset_fbcache(
495
+ fbcache_threshold, fbcache_warmup, fbcache_max_consecutive
496
+ )
497
  for i, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
498
  if cfg_parallel:
499
  dist.broadcast(latents, src=cfg_latent_src, group=cfg_parallel_group)
 
559
  timestep_batch,
560
  prompt_model_input,
561
  encoder_attention_mask=prompt_mask,
562
+ fb_cache_branch="cond",
563
  return_dict=False,
564
  )[0].float()
565
 
 
571
  timestep_batch,
572
  negative_model_input,
573
  encoder_attention_mask=negative_mask,
574
+ fb_cache_branch="uncond",
575
  return_dict=False,
576
  )[0].float()
577
  noise_pred = noise_pred_uncond + guidance_scale * (noise_pred - noise_pred_uncond)
lingbot_video/pipeline_lingbot_video_i2v.py CHANGED
@@ -162,6 +162,9 @@ class LingBotVideoImageToVideoPipeline(LingBotVideoPipeline):
162
  batch_cfg: bool = False,
163
  null_cond_clone_zero: bool = False,
164
  guidance_stop: float = 1.0,
 
 
 
165
  return_dict: bool = True,
166
  ) -> Union[LingBotVideoPipelineOutput, Tuple[Union[list, torch.Tensor]]]:
167
  self.check_inputs(height, width, num_frames)
@@ -249,6 +252,10 @@ class LingBotVideoImageToVideoPipeline(LingBotVideoPipeline):
249
  total_steps if guidance_stop >= 1.0
250
  else max(1, int(round(float(guidance_stop) * total_steps)))
251
  )
 
 
 
 
252
 
253
  for i, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
254
  if cfg_parallel:
@@ -313,6 +320,7 @@ class LingBotVideoImageToVideoPipeline(LingBotVideoPipeline):
313
  timestep_batch,
314
  prompt_model_input,
315
  encoder_attention_mask=prompt_mask,
 
316
  return_dict=False,
317
  )[0].float()
318
 
@@ -324,6 +332,7 @@ class LingBotVideoImageToVideoPipeline(LingBotVideoPipeline):
324
  timestep_batch,
325
  negative_model_input,
326
  encoder_attention_mask=negative_mask,
 
327
  return_dict=False,
328
  )[0].float()
329
  noise_pred = noise_pred_uncond + guidance_scale * (
 
162
  batch_cfg: bool = False,
163
  null_cond_clone_zero: bool = False,
164
  guidance_stop: float = 1.0,
165
+ fbcache_threshold: Optional[float] = None,
166
+ fbcache_warmup: int = 3,
167
+ fbcache_max_consecutive: int = 3,
168
  return_dict: bool = True,
169
  ) -> Union[LingBotVideoPipelineOutput, Tuple[Union[list, torch.Tensor]]]:
170
  self.check_inputs(height, width, num_frames)
 
252
  total_steps if guidance_stop >= 1.0
253
  else max(1, int(round(float(guidance_stop) * total_steps)))
254
  )
255
+ if hasattr(self.transformer, "reset_fbcache"):
256
+ self.transformer.reset_fbcache(
257
+ fbcache_threshold, fbcache_warmup, fbcache_max_consecutive
258
+ )
259
 
260
  for i, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
261
  if cfg_parallel:
 
320
  timestep_batch,
321
  prompt_model_input,
322
  encoder_attention_mask=prompt_mask,
323
+ fb_cache_branch="cond",
324
  return_dict=False,
325
  )[0].float()
326
 
 
332
  timestep_batch,
333
  negative_model_input,
334
  encoder_attention_mask=negative_mask,
335
+ fb_cache_branch="uncond",
336
  return_dict=False,
337
  )[0].float()
338
  noise_pred = noise_pred_uncond + guidance_scale * (
lingbot_video/transformer_lingbot_video.py CHANGED
@@ -965,11 +965,58 @@ class LingBotVideoBlock(nn.Module):
965
  return x
966
 
967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
968
  class LingBotVideoTransformer3DModel(ModelMixin, ConfigMixin):
969
  _supports_gradient_checkpointing = False
970
  _no_split_modules = ["LingBotVideoBlock"]
971
  _keep_in_fp32_modules = list(LINGBOT_VIDEO_FP32_MODULES)
972
 
 
 
 
 
 
 
 
 
 
 
 
973
  def to(self, *args, **kwargs):
974
  device, dtype, non_blocking, _ = torch._C._nn._parse_to(*args, **kwargs)
975
  if dtype is None or dtype == torch.float32:
@@ -1100,6 +1147,7 @@ class LingBotVideoTransformer3DModel(ModelMixin, ConfigMixin):
1100
  timestep: torch.Tensor, # (B,) ∈ [0, 1000](= sigma*1000)
1101
  encoder_hidden_states: torch.Tensor, # (B, L, text_dim)
1102
  encoder_attention_mask: Optional[torch.Tensor] = None, # (B, L) 1=valid
 
1103
  return_dict: bool = True,
1104
  ):
1105
  B, C, T, H, W = hidden_states.shape
@@ -1272,16 +1320,47 @@ class LingBotVideoTransformer3DModel(ModelMixin, ConfigMixin):
1272
  temb6 = self.cp_temb6(temb6)
1273
  temb6 = temb6.reshape(temb6.shape[0] * temb6.shape[1], -1)
1274
 
1275
- for block in self.blocks:
1276
- joint = block(
1277
- joint,
1278
- temb6,
1279
- rotary,
1280
- attention_mask,
1281
- moe_padding_mask,
1282
- packed_indices=packed_indices,
1283
- parallel_config=parallel_config,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1284
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1285
  if not packed_cp:
1286
  joint = self.cp_out(joint)
1287
 
 
965
  return x
966
 
967
 
968
+ class _FBCacheState:
969
+ """First-Block-Cache state for a single CFG branch.
970
+
971
+ Reuses the accumulated residual of blocks 1..N-1 on denoising steps whose
972
+ first-block residual barely changes relative to the previous step (the
973
+ signal is the first transformer block's output delta, not the timestep
974
+ embedding). `warmup` forces full compute early; `max_consecutive` bounds
975
+ error drift by refreshing the cache after a run of skips.
976
+ """
977
+
978
+ __slots__ = ("threshold", "warmup", "max_consecutive", "step", "consecutive",
979
+ "prev_first", "rest_residual", "skips")
980
+
981
+ def __init__(self, threshold: float, warmup: int, max_consecutive: int):
982
+ self.threshold = float(threshold)
983
+ self.warmup = int(warmup)
984
+ self.max_consecutive = int(max_consecutive)
985
+ self.step = 0
986
+ self.consecutive = 0
987
+ self.skips = 0
988
+ self.prev_first = None
989
+ self.rest_residual = None
990
+
991
+ def decide(self, first_residual: torch.Tensor) -> bool:
992
+ if (
993
+ self.step < self.warmup
994
+ or self.rest_residual is None
995
+ or self.prev_first is None
996
+ or self.consecutive >= self.max_consecutive
997
+ ):
998
+ return False
999
+ diff = (first_residual - self.prev_first).abs().mean()
1000
+ base = self.prev_first.abs().mean() + 1e-8
1001
+ return bool((diff / base) < self.threshold)
1002
+
1003
+
1004
  class LingBotVideoTransformer3DModel(ModelMixin, ConfigMixin):
1005
  _supports_gradient_checkpointing = False
1006
  _no_split_modules = ["LingBotVideoBlock"]
1007
  _keep_in_fp32_modules = list(LINGBOT_VIDEO_FP32_MODULES)
1008
 
1009
+ def reset_fbcache(self, threshold=None, warmup: int = 3, max_consecutive: int = 3) -> None:
1010
+ """(Re)initialize First-Block-Cache for a generation. threshold=None/<=0
1011
+ disables caching (standard full-block compute)."""
1012
+ if threshold is None or float(threshold) <= 0.0:
1013
+ self._fbcache = None
1014
+ else:
1015
+ self._fbcache = {
1016
+ "cond": _FBCacheState(threshold, warmup, max_consecutive),
1017
+ "uncond": _FBCacheState(threshold, warmup, max_consecutive),
1018
+ }
1019
+
1020
  def to(self, *args, **kwargs):
1021
  device, dtype, non_blocking, _ = torch._C._nn._parse_to(*args, **kwargs)
1022
  if dtype is None or dtype == torch.float32:
 
1147
  timestep: torch.Tensor, # (B,) ∈ [0, 1000](= sigma*1000)
1148
  encoder_hidden_states: torch.Tensor, # (B, L, text_dim)
1149
  encoder_attention_mask: Optional[torch.Tensor] = None, # (B, L) 1=valid
1150
+ fb_cache_branch: Optional[str] = None, # "cond"/"uncond" enables First-Block-Cache
1151
  return_dict: bool = True,
1152
  ):
1153
  B, C, T, H, W = hidden_states.shape
 
1320
  temb6 = self.cp_temb6(temb6)
1321
  temb6 = temb6.reshape(temb6.shape[0] * temb6.shape[1], -1)
1322
 
1323
+ cache = None
1324
+ if fb_cache_branch is not None:
1325
+ caches = getattr(self, "_fbcache", None)
1326
+ if caches is not None:
1327
+ cache = caches.get(fb_cache_branch)
1328
+
1329
+ if cache is None:
1330
+ for block in self.blocks:
1331
+ joint = block(
1332
+ joint,
1333
+ temb6,
1334
+ rotary,
1335
+ attention_mask,
1336
+ moe_padding_mask,
1337
+ packed_indices=packed_indices,
1338
+ parallel_config=parallel_config,
1339
+ )
1340
+ else:
1341
+ # First-Block-Cache: run block 0, then either reuse the cached
1342
+ # residual of blocks 1..N-1 or recompute it.
1343
+ original = joint
1344
+ joint = self.blocks[0](
1345
+ original, temb6, rotary, attention_mask, moe_padding_mask,
1346
+ packed_indices=packed_indices, parallel_config=parallel_config,
1347
  )
1348
+ first_residual = joint - original
1349
+ if cache.decide(first_residual):
1350
+ joint = joint + cache.rest_residual
1351
+ cache.consecutive += 1
1352
+ cache.skips += 1
1353
+ else:
1354
+ h0 = joint
1355
+ for block in self.blocks[1:]:
1356
+ joint = block(
1357
+ joint, temb6, rotary, attention_mask, moe_padding_mask,
1358
+ packed_indices=packed_indices, parallel_config=parallel_config,
1359
+ )
1360
+ cache.rest_residual = joint - h0
1361
+ cache.consecutive = 0
1362
+ cache.prev_first = first_residual
1363
+ cache.step += 1
1364
  if not packed_cp:
1365
  joint = self.cp_out(joint)
1366