diff --git a/.gitignore b/.gitignore
index b45dbdf..eb6f8b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -233,3 +233,7 @@ time_cost.json
costtime.json
batch_eval.txt
*.log
+
+# README comparison assets intentionally included in the PR
+!assets/wan21_comparison/
+!assets/wan21_comparison/*.mp4
diff --git a/NaviCache4Wan2.1/README.md b/NaviCache4Wan2.1/README.md
index abd1ef8..096b4c2 100644
--- a/NaviCache4Wan2.1/README.md
+++ b/NaviCache4Wan2.1/README.md
@@ -1,10 +1,10 @@
# NaviCache for Wan2.1
-NaviCache is a training-free test-time self-calibration caching method for accelerating video diffusion models. This directory provides the NaviCache generation script for [Wan2.1](https://github.com/Wan-Video/Wan2.1).
+NaviCache is a training-free, test-time self-calibration caching method for accelerating [Wan2.1](https://github.com/Wan-Video/Wan2.1) text-to-video and image-to-video generation.
## Usage
-Follow [Wan2.1](https://github.com/Wan-Video/Wan2.1) to clone the repo, finish the installation, and download the required model weights. Then copy `navicache_generate.py` in this repo to the Wan2.1 repo.
+Install Wan2.1 following the [official repository](https://github.com/Wan-Video/Wan2.1), then copy the NaviCache integration script into the Wan2.1 repository:
```bash
git clone https://github.com/Wan-Video/Wan2.1.git
@@ -14,11 +14,7 @@ cp NaviCache/NaviCache4Wan2.1/navicache_generate.py Wan2.1/
cd Wan2.1
```
-Please make sure that `--ckpt_dir` points to the matching Wan2.1 checkpoint directory. The T2V-14B and T2V-1.3B models use different weights. The I2V 480P and I2V 720P models also use different weights.
-
-## Text-to-Video
-
-For T2V with the 1.3B model:
+### T2V 1.3B, 832x480
```bash
python navicache_generate.py \
@@ -27,12 +23,13 @@ python navicache_generate.py \
--ckpt_dir ./Wan2.1-T2V-1.3B \
--offload_model True \
--t5_cpu \
+ --base_seed 42 \
--navicache_thresh 0.05 \
--navicache_align_steps 10 \
- --prompt "Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage."
+ --prompt "A boat sailing leisurely along the Seine River with the Eiffel Tower in background, zoom out"
```
-For T2V with the 14B model:
+### T2V 14B, 1280x720
```bash
python navicache_generate.py \
@@ -41,14 +38,13 @@ python navicache_generate.py \
--ckpt_dir ./Wan2.1-T2V-14B \
--offload_model True \
--t5_cpu \
+ --base_seed 42 \
--navicache_thresh 0.05 \
--navicache_align_steps 10 \
- --prompt "Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage."
+ --prompt "A person is skateboarding"
```
-## Image-to-Video
-
-For I2V with the 480P model:
+### I2V 14B, 832x480
```bash
python navicache_generate.py \
@@ -58,12 +54,13 @@ python navicache_generate.py \
--image examples/i2v_input.JPG \
--offload_model True \
--t5_cpu \
+ --base_seed 42 \
--navicache_thresh 0.05 \
--navicache_align_steps 10 \
- --prompt "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds."
+ --prompt "A beautiful coastal beach in spring, waves lapping on sand by Hokusai, in the style of Ukiyo"
```
-For I2V with the 720P model:
+### I2V 14B, 1280x720
```bash
python navicache_generate.py \
@@ -71,15 +68,96 @@ python navicache_generate.py \
--size 1280*720 \
--ckpt_dir ./Wan2.1-I2V-14B-720P \
--image examples/i2v_input.JPG \
+ --frame_num 61 \
--offload_model True \
--t5_cpu \
+ --base_seed 42 \
--navicache_thresh 0.05 \
--navicache_align_steps 10 \
- --prompt "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds."
+ --prompt "an orange cat"
```
-The I2V commands use the sample image included in Wan2.1 at `examples/i2v_input.JPG`; replace it with your own input image as needed.Generated files are written to `outputs/` unless `--save_file` is specified. Generation timing records are appended to `output/time_cost.json` by default. Use `--out_dir` to choose another timing-output directory.
+## Results
+
+### Inference Latency Comparison
+
+| Wan2.1 configuration | Resolution | Wan2.1 | TeaCache | TeaCache speedup | NaviCache | NaviCache speedup |
+|:---------------------|:----------:|:-------------------:|:----------------------------:|:----------------:|:----------------------:|:-----------------:|
+| T2V 1.3B | 832x480 | 488.0 s | 209.5 s | 2.33x | 160.1 s | 3.05x |
+| T2V 14B | 1280x720 | 3528.1 s | 1784.2 s | 1.98x | 1361.1 s | 2.59x |
+| I2V 14B 480P | 832x480 | 825.9 s | 604.4 s | 1.37x | 481.4 s | 1.72x |
+| I2V 14B 720P | 1280x720 | 1887.2 s | 1296.2 s | 1.46x | 1007.9 s | 1.87x |
+
+### Visual Quality Comparison
+
+#### T2V 1.3B, 832x480
+
+
+
+
+
+| Wan2.1 | TeaCache | NaviCache |
+|:------:|:--------:|:---------:|
+| 488.0 s (1.00x) | 209.5 s (2.33x) | **160.1 s (3.05x)** |
+
+
+Prompt: A boat sailing leisurely along the Seine River with the Eiffel Tower in background, zoom out
+
+A boat sailing leisurely along the Seine River with the Eiffel Tower in background, zoom out
+
+
+
+#### T2V 14B, 1280x720
+
+
+
+
+
+| Wan2.1 | TeaCache | NaviCache |
+|:------:|:--------:|:---------:|
+| 3528.1 s (1.00x) | 1784.2 s (1.98x) | **1361.1 s (2.59x)** |
+
+
+Prompt: A person is skateboarding
+
+A person is skateboarding
+
+
+
+#### I2V 14B, 832x480
+
+
+
+
+
+| Wan2.1 | TeaCache | NaviCache |
+|:------:|:--------:|:---------:|
+| 825.9 s (1.00x) | 604.4 s (1.37x) | **481.4 s (1.72x)** |
+
+
+Prompt
+
+The ribbon dancer spins rapidly across the courtyard, both red silk ribbons tracing wide fluid arcs as her layered costume billows; the camera tracks sideways with strong natural background parallax.
+
+
+
+#### I2V 14B, 1280x720
+
+
+
+
+
+| Wan2.1 | TeaCache | NaviCache |
+|:------:|:--------:|:---------:|
+| 1887.2 s (1.00x) | 1296.2 s (1.46x) | **1007.9 s (1.87x)** |
+
+
+Prompt
+
+The clockwork hummingbird beats both articulated wings rapidly and flies from one red flower to the next; tiny gears turn visibly while the camera arcs around it through the greenhouse with deep parallax.
+
+
## Acknowledgements
-We would like to thank the contributors to [Wan2.1](https://github.com/Wan-Video/Wan2.1).
+We would like to thank the contributors to [Wan2.1](https://github.com/Wan-Video/Wan2.1), [TeaCache](https://github.com/ali-vilab/TeaCache), [EasyCache](https://github.com/H-EmbodVis/EasyCache), and [MagCache](https://github.com/Zehong-Ma/MagCache).
diff --git a/NaviCache4Wan2.2/README.md b/NaviCache4Wan2.2/README.md
new file mode 100644
index 0000000..9d0e369
--- /dev/null
+++ b/NaviCache4Wan2.2/README.md
@@ -0,0 +1,66 @@
+# NaviCache for Wan2.2
+
+NaviCache is a training-free test-time self-calibration caching method for accelerating video diffusion models. This directory provides the NaviCache generation script for [Wan2.2](https://github.com/Wan-Video/Wan2.2).
+
+## Usage
+
+Follow the official [Wan2.2](https://github.com/Wan-Video/Wan2.2) instructions to clone the repository, install the required environment, and download the Wan2.2-TI2V-5B model weights. NaviCache requires no training or offline calibration. From the directory containing the cloned `Wan2.2` repository, copy `navicache_generate.py` to its root:
+
+```bash
+git clone https://github.com/HelloZicky/NaviCache.git
+
+cp NaviCache/NaviCache4Wan2.2/navicache_generate.py Wan2.2/
+cd Wan2.2
+mkdir -p outputs
+```
+
+Make sure that `--ckpt_dir` points to the downloaded Wan2.2-TI2V-5B checkpoint directory. Once the official environment and weights are ready, run either of the following commands to generate videos with NaviCache.
+
+## Text-to-Video
+
+Run Wan2.2-TI2V-5B without an input image:
+
+```bash
+python navicache_generate.py \
+ --task ti2v-5B \
+ --size 1280*704 \
+ --frame_num 121 \
+ --ckpt_dir ./Wan2.2-TI2V-5B \
+ --offload_model True \
+ --convert_model_dtype \
+ --t5_cpu \
+ --navicache_thresh 0.05 \
+ --navicache_align_steps 10 \
+ --navicache_process_noise 0.05 \
+ --navicache_measurement_noise 0.05 \
+ --prompt "Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage." \
+ --save_file ./outputs/wan22_t2v_navicache.mp4
+```
+
+## Image-to-Video
+
+Pass an input image to the same TI2V checkpoint with `--image`:
+
+```bash
+python navicache_generate.py \
+ --task ti2v-5B \
+ --size 1280*704 \
+ --frame_num 121 \
+ --ckpt_dir ./Wan2.2-TI2V-5B \
+ --image examples/i2v_input.JPG \
+ --offload_model True \
+ --convert_model_dtype \
+ --t5_cpu \
+ --navicache_thresh 0.05 \
+ --navicache_align_steps 10 \
+ --navicache_process_noise 0.05 \
+ --navicache_measurement_noise 0.05 \
+ --prompt "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard at a sunny beach." \
+ --save_file ./outputs/wan22_i2v_navicache.mp4
+```
+
+The command uses the sample image included in Wan2.2 at `examples/i2v_input.JPG`; replace it with your own input image as needed. Generated videos are written to the path provided by `--save_file`.
+
+## Acknowledgements
+
+We would like to thank the contributors to [Wan2.2](https://github.com/Wan-Video/Wan2.2).
diff --git a/NaviCache4Wan2.2/navicache_generate.py b/NaviCache4Wan2.2/navicache_generate.py
new file mode 100644
index 0000000..e0d2b9d
--- /dev/null
+++ b/NaviCache4Wan2.2/navicache_generate.py
@@ -0,0 +1,402 @@
+# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
+# Copyright 2026 The NaviCache Authors. All rights reserved.
+#
+# The CLI and generation flow are adapted from Wan2.2 generate.py at commit
+# 42bf4cfaa384bc21833865abc2f9e6c0e67233dc (Apache License 2.0). NaviCache
+# adds only the model-forward cache hook and its command-line options.
+
+"""Generate Wan2.2-TI2V-5B videos with NaviCache.
+
+Place this file in the root of an official Wan2.2 checkout, or invoke it by
+absolute path while the current directory is the Wan2.2 repository root.
+"""
+
+from __future__ import annotations
+
+import argparse
+import logging
+import os
+import random
+import sys
+import types
+from datetime import datetime
+
+import torch
+import torch.distributed as dist
+
+
+DEFAULT_PROMPT = (
+ "Two anthropomorphic cats in comfy boxing gear and bright gloves fight "
+ "intensely on a spotlighted stage."
+)
+
+
+class NaviCacheConfig:
+ """Prompt-local NaviCache parameters shared by the public hook and tests."""
+
+ def __init__(
+ self,
+ *,
+ threshold: float = 0.05,
+ ret_steps: int = 10,
+ kalman_q: float = 0.05,
+ kalman_r: float = 0.05,
+ sample_steps: int = 50,
+ ) -> None:
+ if threshold < 0:
+ raise ValueError("navicache threshold must be non-negative")
+ if ret_steps < 0:
+ raise ValueError("navicache ret_steps must be non-negative")
+ if sample_steps <= 0:
+ raise ValueError("sample_steps must be positive")
+ if ret_steps > sample_steps:
+ raise ValueError("navicache ret_steps cannot exceed sample_steps")
+ if kalman_q < 0 or kalman_r < 0:
+ raise ValueError("Kalman Q and R must be non-negative")
+ self.threshold = float(threshold)
+ self.ret_steps = int(ret_steps)
+ self.kalman_q = float(kalman_q)
+ self.kalman_r = float(kalman_r)
+ self.sample_steps = int(sample_steps)
+
+
+def _mean_abs_cat(tensors):
+ """Mean absolute value over a list of tensors, matching the formal runner."""
+ return torch.cat([tensor.flatten() for tensor in tensors]).abs().mean()
+
+
+def reset_navicache_state(model) -> None:
+ """Reset every prompt-dependent NaviCache state value on ``model``."""
+ model._nc_forward_count = 0
+ model._nc_compute_count = 0
+ model._nc_skip_count = 0
+ model._nc_accumulated_error = 0.0
+ model._nc_should_compute = True
+ model._nc_k = None
+ model._nc_kalman_k = 0.0
+ model._nc_kalman_p = 1.0
+ model._nc_prev_input_even = None
+ model._nc_prev_prev_input_even = None
+ model._nc_prev_output_even = None
+ model._nc_prev_output_odd = None
+ model._nc_cache_even = None
+ model._nc_cache_odd = None
+
+
+def _navicache_forward(
+ self,
+ x,
+ t,
+ context,
+ seq_len,
+ clip_fea=None,
+ y=None,
+):
+ """Wan2.2 forward hook with prompt-local residual reuse.
+
+ Wan2.2's formal TI2V interface uses ``y`` and has no ``clip_fea``
+ parameter. ``clip_fea`` remains accepted by this wrapper only so accidental
+ calls fail at the native boundary rather than being forwarded incorrectly.
+ """
+ config = self._navicache_config
+ raw_input = [tensor.clone() for tensor in x]
+ is_cond_forward = self._nc_forward_count % 2 == 0
+
+ # The conditional/even call makes one decision for the entire CFG pair.
+ if is_cond_forward:
+ first_alignment_forwards = config.ret_steps * 2
+ final_pair_start = config.sample_steps * 2 - 2
+ if (
+ self._nc_forward_count < first_alignment_forwards
+ or self._nc_forward_count >= final_pair_start
+ ):
+ self._nc_should_compute = True
+ self._nc_accumulated_error = 0.0
+ elif (
+ self._nc_prev_input_even is not None
+ and self._nc_prev_output_even is not None
+ and self._nc_k is not None
+ ):
+ input_change = _mean_abs_cat(
+ [
+ current - previous
+ for current, previous in zip(raw_input, self._nc_prev_input_even)
+ ]
+ )
+ output_norm = _mean_abs_cat(self._nc_prev_output_even)
+ self._nc_accumulated_error += (
+ self._nc_k * input_change / (output_norm + 1e-8)
+ )
+ self._nc_should_compute = bool(
+ self._nc_accumulated_error >= config.threshold
+ )
+ if self._nc_should_compute:
+ self._nc_accumulated_error = 0.0
+ else:
+ self._nc_should_compute = True
+
+ self._nc_prev_input_even = [tensor.clone() for tensor in raw_input]
+
+ residual_cache = self._nc_cache_even if is_cond_forward else self._nc_cache_odd
+ if not self._nc_should_compute and residual_cache is not None:
+ self._nc_skip_count += 1
+ self._nc_forward_count += 1
+ return [
+ (tensor + residual).float()
+ for tensor, residual in zip(raw_input, residual_cache)
+ ]
+
+ # The official Wan2.2 model interface is (..., seq_len, y=None). In
+ # particular, do not forward Wan2.1's incompatible clip_fea argument.
+ output = self._navicache_native_forward(x, t, context, seq_len, y=y)
+ self._nc_compute_count += 1
+
+ if is_cond_forward:
+ if (
+ self._nc_prev_output_even is not None
+ and self._nc_prev_prev_input_even is not None
+ ):
+ output_change = _mean_abs_cat(
+ [
+ current - previous
+ for current, previous in zip(output, self._nc_prev_output_even)
+ ]
+ )
+ input_change = _mean_abs_cat(
+ [
+ current - previous
+ for current, previous in zip(
+ self._nc_prev_input_even,
+ self._nc_prev_prev_input_even,
+ )
+ ]
+ )
+ observation = output_change / (input_change + 1e-8)
+ if self._nc_kalman_k is None:
+ self._nc_kalman_k = observation
+ self._nc_kalman_p = 1.0
+ else:
+ self._nc_kalman_p += config.kalman_q
+ gain = self._nc_kalman_p / (
+ self._nc_kalman_p + config.kalman_r + 1e-8
+ )
+ self._nc_kalman_k = self._nc_kalman_k + gain * (
+ observation - self._nc_kalman_k
+ )
+ self._nc_kalman_p = (1.0 - gain) * self._nc_kalman_p
+ self._nc_k = self._nc_kalman_k
+
+ self._nc_prev_prev_input_even = [
+ tensor.clone() for tensor in self._nc_prev_input_even
+ ]
+ self._nc_prev_output_even = [tensor.clone() for tensor in output]
+ self._nc_cache_even = [
+ current - raw
+ for current, raw in zip(output, raw_input)
+ ]
+ else:
+ self._nc_prev_output_odd = [tensor.clone() for tensor in output]
+ self._nc_cache_odd = [
+ current - raw
+ for current, raw in zip(output, raw_input)
+ ]
+
+ self._nc_forward_count += 1
+ return output
+
+
+def install_navicache(model, config: NaviCacheConfig) -> None:
+ """Install an instance-local NaviCache hook on an official Wan2.2 model."""
+ if not hasattr(model, "_navicache_native_forward"):
+ model._navicache_native_forward = model.forward
+ model.forward = types.MethodType(_navicache_forward, model)
+ model._navicache_config = config
+ reset_navicache_state(model)
+
+
+def navicache_stats(model) -> dict[str, object]:
+ """Return lightweight generation statistics without writing trace files."""
+ return {
+ "compute_forwards": model._nc_compute_count,
+ "skip_forwards": model._nc_skip_count,
+ "kalman_k_final": (
+ float(model._nc_k.detach().cpu())
+ if torch.is_tensor(model._nc_k)
+ else model._nc_k
+ ),
+ }
+
+
+def _str2bool(value):
+ if isinstance(value, bool):
+ return value
+ normalized = value.lower()
+ if normalized in {"true", "1", "yes", "y"}:
+ return True
+ if normalized in {"false", "0", "no", "n"}:
+ return False
+ raise argparse.ArgumentTypeError(f"invalid boolean value: {value}")
+
+
+def build_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(
+ description="Generate a video with NaviCache for Wan2.2-TI2V-5B."
+ )
+ parser.add_argument("--task", default="ti2v-5B", choices=["ti2v-5B"])
+ parser.add_argument("--size", default="1280*704", choices=["1280*704", "704*1280"])
+ parser.add_argument("--frame_num", type=int, default=121)
+ parser.add_argument("--ckpt_dir", required=True)
+ parser.add_argument("--offload_model", type=_str2bool, default=None)
+ parser.add_argument("--t5_cpu", action="store_true", default=False)
+ parser.add_argument("--save_file", default=None)
+ parser.add_argument("--prompt", default=DEFAULT_PROMPT)
+ parser.add_argument("--image", default=None)
+ parser.add_argument("--sample_solver", default="unipc", choices=["unipc", "dpm++"])
+ parser.add_argument("--sample_steps", type=int, default=50)
+ parser.add_argument("--sample_shift", type=float, default=5.0)
+ parser.add_argument("--sample_guide_scale", type=float, default=5.0)
+ parser.add_argument("--base_seed", type=int, default=42)
+ parser.add_argument("--convert_model_dtype", action="store_true", default=False)
+ parser.add_argument(
+ "--navicache_thresh",
+ type=float,
+ default=0.05,
+ help="Accumulated normalized predicted-error threshold.",
+ )
+ parser.add_argument(
+ "--navicache_align_steps",
+ type=int,
+ default=10,
+ help="Number of initial diffusion steps computed for alignment.",
+ )
+ parser.add_argument(
+ "--navicache_process_noise",
+ type=float,
+ default=0.05,
+ help="Process-noise covariance used by NaviCache state estimation.",
+ )
+ parser.add_argument(
+ "--navicache_measurement_noise",
+ type=float,
+ default=0.05,
+ help="Measurement-noise covariance used by NaviCache state estimation.",
+ )
+ return parser
+
+
+def _validate_args(args) -> None:
+ if args.frame_num <= 0 or (args.frame_num - 1) % 4 != 0:
+ raise ValueError("frame_num must be 4n+1")
+ if args.sample_steps <= 0:
+ raise ValueError("sample_steps must be positive")
+ if args.base_seed < 0:
+ args.base_seed = random.randint(0, sys.maxsize)
+ if args.offload_model is None:
+ args.offload_model = False if int(os.getenv("WORLD_SIZE", "1")) > 1 else True
+
+
+def generate(args) -> None:
+ """Run the official WanTI2V pipeline with the NaviCache model hook."""
+ _validate_args(args)
+ world_size = int(os.getenv("WORLD_SIZE", "1"))
+ if world_size != 1:
+ raise NotImplementedError(
+ "This public Wan2.2 NaviCache entry is validated for single-GPU inference only."
+ )
+
+ # Delayed imports keep module import and --help usable before Wan2.2 is
+ # installed. Generation itself must run from an official Wan2.2 checkout.
+ try:
+ import wan
+ from PIL import Image
+ from wan.configs import MAX_AREA_CONFIGS, SIZE_CONFIGS, WAN_CONFIGS
+ from wan.utils.utils import save_video
+ except ImportError as exc:
+ raise RuntimeError(
+ "Official Wan2.2 is not importable. Run this script from the root "
+ "of an installed Wan2.2 checkout."
+ ) from exc
+
+ logging.basicConfig(
+ level=logging.INFO,
+ format="[%(asctime)s] %(levelname)s: %(message)s",
+ handlers=[logging.StreamHandler(stream=sys.stdout)],
+ )
+ cfg = WAN_CONFIGS[args.task]
+ image = Image.open(args.image).convert("RGB") if args.image else None
+ logging.info("Generation job args: %s", args)
+ logging.info("Creating WanTI2V pipeline.")
+ pipeline = wan.WanTI2V(
+ config=cfg,
+ checkpoint_dir=args.ckpt_dir,
+ device_id=0,
+ rank=0,
+ t5_fsdp=False,
+ dit_fsdp=False,
+ use_sp=False,
+ t5_cpu=args.t5_cpu,
+ convert_model_dtype=args.convert_model_dtype,
+ )
+ install_navicache(
+ pipeline.model,
+ NaviCacheConfig(
+ threshold=args.navicache_thresh,
+ ret_steps=args.navicache_align_steps,
+ kalman_q=args.navicache_process_noise,
+ kalman_r=args.navicache_measurement_noise,
+ sample_steps=args.sample_steps,
+ ),
+ )
+
+ logging.info("Generating video with NaviCache.")
+ video = pipeline.generate(
+ args.prompt,
+ img=image,
+ size=SIZE_CONFIGS[args.size],
+ max_area=MAX_AREA_CONFIGS[args.size],
+ frame_num=args.frame_num,
+ shift=args.sample_shift,
+ sample_solver=args.sample_solver,
+ sampling_steps=args.sample_steps,
+ guide_scale=args.sample_guide_scale,
+ seed=args.base_seed,
+ offload_model=args.offload_model,
+ )
+ expected_forwards = args.sample_steps * 2
+ if pipeline.model._nc_forward_count != expected_forwards:
+ raise RuntimeError(
+ f"unexpected CFG forward count: {pipeline.model._nc_forward_count} "
+ f"(expected {expected_forwards})"
+ )
+
+ if args.save_file is None:
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ safe_prompt = args.prompt.replace(" ", "_").replace("/", "_")[:50]
+ args.save_file = (
+ f"{args.task}_{args.size}_{safe_prompt}_navicache_{timestamp}.mp4"
+ )
+ logging.info("Saving generated video to %s", args.save_file)
+ save_video(
+ tensor=video[None],
+ save_file=args.save_file,
+ fps=cfg.sample_fps,
+ nrow=1,
+ normalize=True,
+ value_range=(-1, 1),
+ )
+ logging.info("NaviCache stats: %s", navicache_stats(pipeline.model))
+ del video
+ torch.cuda.synchronize()
+ if dist.is_initialized():
+ dist.barrier()
+ dist.destroy_process_group()
+ logging.info("Finished.")
+
+
+def main() -> None:
+ args = build_parser().parse_args()
+ generate(args)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/README.md b/README.md
index d0b332d..8fbc476 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ This is the official repository for **NaviCache: Test-Time Self-Calibration Cach
- **No offline calibration**: no calibration datasets, pre-processing, or per-model fitting.
- **Test-time self-calibration**: the cache updates its feature-change estimator during inference.
-- **Plug-and-play acceleration**: lightweight integration for Wan2.1, HunyuanVideo, and Open-Sora.
+- **Plug-and-play acceleration**: lightweight integration for Wan2.1, Wan2.2, HunyuanVideo, and Open-Sora.
- **Adaptive computation allocation**: skip/update decisions are controlled by an uncertainty-aware gate.
- **Strong speed-quality trade-off**: multiple presets are provided for fast, mid, and slow modes.
@@ -106,6 +106,7 @@ For easier preview and quick visual comparison, we also provide clickable GIF pr
| Model | Task | NaviCache entry point | Example scripts |
|---|---|---|---|
| Wan2.1 | Text-to-Video / Image-to-Video | `NaviCache4Wan2.1/navicache_generate.py` | `scripts/wan/` |
+| Wan2.2-TI2V-5B | Text-to-Video / Image-conditioned Video | [`NaviCache4Wan2.2/navicache_generate.py`](NaviCache4Wan2.2/navicache_generate.py) | [`README`](NaviCache4Wan2.2/README.md) |
| HunyuanVideo | Text-to-Video | `NaviCache4HunyuanVideo/navicache_sample_video.py` | `scripts/hunyuan/` |
| Open-Sora 1.2 | Text-to-Video / Evaluation | `NaviCache4OpenSora/eval/navicache/experiments/opensora.py` | `scripts/opensora/` |
@@ -160,6 +161,18 @@ bash ../NaviCache/scripts/wan/run_wan_t2v_1.3b.sh
bash ../NaviCache/scripts/wan/run_wan_i2v_480p.sh
```
+### Wan2.2-TI2V-5B
+
+The Wan2.2 integration is a standalone entry point for the official TI2V-5B pipeline. Copy it into an official Wan2.2 checkout and run the documented single-GPU command:
+
+```bash
+cd Wan2.2
+cp ../NaviCache/NaviCache4Wan2.2/navicache_generate.py .
+python navicache_generate.py --help
+```
+
+See [`NaviCache4Wan2.2/README.md`](NaviCache4Wan2.2/README.md) for the full command, validated configuration, latency boundary, and quality results.
+
### HunyuanVideo
Run from the official HunyuanVideo repository directory:
@@ -254,6 +267,7 @@ NaviCache/
├── NaviCache4HunyuanVideo/ # NaviCache script for HunyuanVideo
├── NaviCache4OpenSora/ # Open-Sora NaviCache evaluation and VideoSys modules
├── NaviCache4Wan2.1/ # NaviCache script for Wan2.1
+├── NaviCache4Wan2.2/ # Single-GPU NaviCache entry for Wan2.2-TI2V-5B
├── assets/ # Paper figure PDFs
├── scripts/ # Runnable helper scripts
├── requirements.txt
@@ -277,7 +291,7 @@ If you find NaviCache useful, please consider citing:
## 🙏 Acknowledgements
-We thank the contributors of Wan2.1, HunyuanVideo, Open-Sora, VideoSys, TeaCache, EasyCache, MagCache, and PAB for their excellent open-source work and inspiring research.
+We thank the contributors of Wan2.1, Wan2.2, HunyuanVideo, Open-Sora, VideoSys, TeaCache, EasyCache, MagCache, and PAB for their excellent open-source work and inspiring research.
## 📄 License
diff --git a/assets/wan21_comparison/i2v14b480_ribbon_dancer_comparison.mp4 b/assets/wan21_comparison/i2v14b480_ribbon_dancer_comparison.mp4
new file mode 100644
index 0000000..05ec434
Binary files /dev/null and b/assets/wan21_comparison/i2v14b480_ribbon_dancer_comparison.mp4 differ
diff --git a/assets/wan21_comparison/i2v14b720_clockwork_hummingbird_comparison.mp4 b/assets/wan21_comparison/i2v14b720_clockwork_hummingbird_comparison.mp4
new file mode 100644
index 0000000..0a13430
Binary files /dev/null and b/assets/wan21_comparison/i2v14b720_clockwork_hummingbird_comparison.mp4 differ
diff --git a/assets/wan21_comparison/t2v13b480_boat_comparison.mp4 b/assets/wan21_comparison/t2v13b480_boat_comparison.mp4
new file mode 100644
index 0000000..0ce712a
Binary files /dev/null and b/assets/wan21_comparison/t2v13b480_boat_comparison.mp4 differ
diff --git a/assets/wan21_comparison/t2v14b720_skateboarding_comparison.mp4 b/assets/wan21_comparison/t2v14b720_skateboarding_comparison.mp4
new file mode 100644
index 0000000..157c5f2
Binary files /dev/null and b/assets/wan21_comparison/t2v14b720_skateboarding_comparison.mp4 differ
diff --git a/docs/superpowers/plans/2026-08-10-wan22-readme-cli-alignment.md b/docs/superpowers/plans/2026-08-10-wan22-readme-cli-alignment.md
new file mode 100644
index 0000000..38ef182
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-10-wan22-readme-cli-alignment.md
@@ -0,0 +1,354 @@
+# Wan2.2 README and CLI Alignment Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make the Wan2.2 model README match the concise GitHub model-README style and make its public NaviCache CLI names exactly match the repository's established names.
+
+**Architecture:** Change only the argparse public surface and its mapping into the existing `NaviCacheConfig`; the cache algorithm and internal configuration fields remain unchanged. Rewrite the model README as a compact usage document, and enforce the public naming contract with parser tests that require the standard names and reject the removed Wan2.2-only names.
+
+**Tech Stack:** Python 3.11, argparse, pytest, Markdown, Git, PowerShell.
+
+## Global Constraints
+
+- Implementation changes are limited to `NaviCache4Wan2.2/navicache_generate.py`, `tests/test_wan22_navicache.py`, and `NaviCache4Wan2.2/README.md`.
+- The public CLI must expose `--navicache_thresh`, `--navicache_align_steps`, `--navicache_process_noise`, and `--navicache_measurement_noise`.
+- The public CLI must reject `--navicache_ret_steps`, `--navicache_kalman_q`, and `--navicache_kalman_r`; do not add aliases.
+- Keep defaults exactly `0.05`, `10`, `0.05`, and `0.05`, respectively.
+- Do not change `NaviCacheConfig`, cache decisions, Kalman equations, Wan2.2 native-forward arguments, or generation behavior.
+- The model README must use the concise GitHub style and must not contain Results, parameter tables, Limitations, internal provenance, or benchmark-infrastructure language.
+- Do not modify, stage, or commit the existing `NaviCache4Wan2.1/README.md` user change.
+- Do not modify the repository root README or move external benchmark/smoke evidence into the release repository.
+
+## File Responsibilities
+
+- `NaviCache4Wan2.2/navicache_generate.py`: owns the public argparse names and maps them into the existing algorithm configuration.
+- `tests/test_wan22_navicache.py`: owns the parser naming/default/mapping/rejection contract and existing algorithm regression coverage.
+- `NaviCache4Wan2.2/README.md`: owns concise installation and direct T2V/I2V commands using only public option names.
+
+---
+
+### Task 1: Hard-rename the Wan2.2 public CLI with TDD
+
+**Files:**
+- Modify: `tests/test_wan22_navicache.py:213-239`
+- Modify: `NaviCache4Wan2.2/navicache_generate.py:260-284`
+- Modify: `NaviCache4Wan2.2/navicache_generate.py:340-348`
+
+**Interfaces:**
+- Consumes: `build_parser() -> argparse.ArgumentParser` and `NaviCacheConfig(threshold, ret_steps, kalman_q, kalman_r, sample_steps)`.
+- Produces: parsed attributes `navicache_thresh: float`, `navicache_align_steps: int`, `navicache_process_noise: float`, and `navicache_measurement_noise: float`; the existing internal config still receives `ret_steps`, `kalman_q`, and `kalman_r`.
+
+- [ ] **Step 1: Write failing public-name tests**
+
+Replace the four old assertions in `test_public_cli_defaults_match_verified_configuration` and append explicit mapping and rejection tests:
+
+```python
+def test_public_cli_defaults_match_verified_configuration():
+ module = load_public_module()
+ parser = module.build_parser()
+ args = parser.parse_args(["--ckpt_dir", "weights"])
+
+ assert args.task == "ti2v-5B"
+ assert args.size == "1280*704"
+ assert args.frame_num == 121
+ assert args.sample_steps == 50
+ assert args.sample_shift == 5.0
+ assert args.sample_guide_scale == 5.0
+ assert args.base_seed == 42
+ assert args.navicache_thresh == 0.05
+ assert args.navicache_align_steps == 10
+ assert args.navicache_process_noise == 0.05
+ assert args.navicache_measurement_noise == 0.05
+
+
+def test_public_cli_maps_repository_standard_navicache_names():
+ module = load_public_module()
+ args = module.build_parser().parse_args(
+ [
+ "--ckpt_dir",
+ "weights",
+ "--navicache_thresh",
+ "0.07",
+ "--navicache_align_steps",
+ "8",
+ "--navicache_process_noise",
+ "0.03",
+ "--navicache_measurement_noise",
+ "0.04",
+ ]
+ )
+
+ assert args.navicache_thresh == 0.07
+ assert args.navicache_align_steps == 8
+ assert args.navicache_process_noise == 0.03
+ assert args.navicache_measurement_noise == 0.04
+
+
+@pytest.mark.parametrize(
+ "removed_name",
+ [
+ "--navicache_ret_steps",
+ "--navicache_kalman_q",
+ "--navicache_kalman_r",
+ ],
+)
+def test_public_cli_rejects_removed_wan22_only_names(removed_name):
+ module = load_public_module()
+ parser = module.build_parser()
+
+ with pytest.raises(SystemExit):
+ parser.parse_args(["--ckpt_dir", "weights", removed_name, "1"])
+```
+
+- [ ] **Step 2: Run the parser tests and verify RED**
+
+Run:
+
+```powershell
+python -m pytest tests/test_wan22_navicache.py -q
+```
+
+Expected: the new default and explicit mapping tests fail because `navicache_align_steps`, `navicache_process_noise`, and `navicache_measurement_noise` do not yet exist; existing algorithm tests remain green.
+
+- [ ] **Step 3: Implement the minimal public hard rename**
+
+Replace the three Wan2.2-only parser arguments with:
+
+```python
+ parser.add_argument(
+ "--navicache_align_steps",
+ type=int,
+ default=10,
+ help="Number of initial diffusion steps computed for alignment.",
+ )
+ parser.add_argument(
+ "--navicache_process_noise",
+ type=float,
+ default=0.05,
+ help="Process-noise covariance used by NaviCache state estimation.",
+ )
+ parser.add_argument(
+ "--navicache_measurement_noise",
+ type=float,
+ default=0.05,
+ help="Measurement-noise covariance used by NaviCache state estimation.",
+ )
+```
+
+Map the public names into the unchanged internal configuration:
+
+```python
+ NaviCacheConfig(
+ threshold=args.navicache_thresh,
+ ret_steps=args.navicache_align_steps,
+ kalman_q=args.navicache_process_noise,
+ kalman_r=args.navicache_measurement_noise,
+ sample_steps=args.sample_steps,
+ ),
+```
+
+- [ ] **Step 4: Run tests and verify GREEN**
+
+Run:
+
+```powershell
+python -m pytest tests/test_wan22_navicache.py -q
+```
+
+Expected: 14 tests pass, including three parameterized rejection cases.
+
+- [ ] **Step 5: Verify the public help surface**
+
+Run:
+
+```powershell
+$help = python NaviCache4Wan2.2/navicache_generate.py --help 2>&1 | Out-String
+$required = @('--navicache_thresh','--navicache_align_steps','--navicache_process_noise','--navicache_measurement_noise')
+$removed = @('--navicache_ret_steps','--navicache_kalman_q','--navicache_kalman_r')
+foreach ($name in $required) { if (-not $help.Contains($name)) { throw "Missing CLI name: $name" } }
+foreach ($name in $removed) { if ($help.Contains($name)) { throw "Removed CLI name remains: $name" } }
+```
+
+Expected: command exits successfully without throwing.
+
+- [ ] **Step 6: Commit the tested CLI contract**
+
+```powershell
+git add -- NaviCache4Wan2.2/navicache_generate.py tests/test_wan22_navicache.py
+git diff --cached --check
+git commit -m "fix: align Wan2.2 NaviCache CLI names"
+```
+
+Expected: the commit contains exactly the public entry point and its tests; `NaviCache4Wan2.1/README.md` remains unstaged.
+
+---
+
+### Task 2: Rewrite the Wan2.2 README in the GitHub model style
+
+**Files:**
+- Modify: `NaviCache4Wan2.2/README.md:1-84`
+
+**Interfaces:**
+- Consumes: the standard public CLI names produced by Task 1.
+- Produces: one concise model README with directly runnable T2V and image-conditioned commands.
+
+- [ ] **Step 1: Replace the README with the approved compact content**
+
+Use this complete content:
+
+````markdown
+# NaviCache for Wan2.2
+
+NaviCache is a training-free test-time self-calibration caching method for accelerating video diffusion models. This directory provides the NaviCache generation script for [Wan2.2](https://github.com/Wan-Video/Wan2.2).
+
+## Usage
+
+Follow [Wan2.2](https://github.com/Wan-Video/Wan2.2) to clone the repository, finish the installation, and download the Wan2.2-TI2V-5B model weights. Then copy `navicache_generate.py` from this repository to the Wan2.2 repository.
+
+```bash
+git clone https://github.com/Wan-Video/Wan2.2.git
+git clone https://github.com/HelloZicky/NaviCache.git
+
+cp NaviCache/NaviCache4Wan2.2/navicache_generate.py Wan2.2/
+cd Wan2.2
+mkdir -p outputs
+```
+
+Make sure that `--ckpt_dir` points to the downloaded Wan2.2-TI2V-5B checkpoint directory.
+
+## Text-to-Video
+
+Run Wan2.2-TI2V-5B without an input image:
+
+```bash
+python navicache_generate.py \
+ --task ti2v-5B \
+ --size 1280*704 \
+ --frame_num 121 \
+ --ckpt_dir ./Wan2.2-TI2V-5B \
+ --offload_model False \
+ --convert_model_dtype \
+ --navicache_thresh 0.05 \
+ --navicache_align_steps 10 \
+ --navicache_process_noise 0.05 \
+ --navicache_measurement_noise 0.05 \
+ --prompt "Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage." \
+ --save_file ./outputs/wan22_t2v_navicache.mp4
+```
+
+## Image-to-Video
+
+Pass an input image to the same TI2V checkpoint with `--image`:
+
+```bash
+python navicache_generate.py \
+ --task ti2v-5B \
+ --size 1280*704 \
+ --frame_num 121 \
+ --ckpt_dir ./Wan2.2-TI2V-5B \
+ --image examples/i2v_input.JPG \
+ --offload_model False \
+ --convert_model_dtype \
+ --navicache_thresh 0.05 \
+ --navicache_align_steps 10 \
+ --navicache_process_noise 0.05 \
+ --navicache_measurement_noise 0.05 \
+ --prompt "A small wooden sailboat glides across a calm lake at sunrise, cinematic and detailed." \
+ --save_file ./outputs/wan22_i2v_navicache.mp4
+```
+
+The command uses the sample image included in Wan2.2 at `examples/i2v_input.JPG`; replace it with your own input image as needed. Generated videos are written to the path provided by `--save_file`.
+
+## Acknowledgements
+
+We would like to thank the contributors to [Wan2.2](https://github.com/Wan-Video/Wan2.2).
+````
+
+- [ ] **Step 2: Verify structure and removed content**
+
+Run:
+
+```powershell
+$readme = Get-Content -Raw NaviCache4Wan2.2/README.md
+$expectedHeadings = @('# NaviCache for Wan2.2','## Usage','## Text-to-Video','## Image-to-Video','## Acknowledgements')
+foreach ($heading in $expectedHeadings) { if (-not $readme.Contains($heading)) { throw "Missing heading: $heading" } }
+$removedSections = @('## Results','Inference Latency Comparison','Visual Quality Comparison','## Limitations','--navicache_ret_steps','--navicache_kalman_q','--navicache_kalman_r')
+foreach ($text in $removedSections) { if ($readme.Contains($text)) { throw "Removed README content remains: $text" } }
+```
+
+Expected: command exits successfully without throwing.
+
+- [ ] **Step 3: Verify every documented flag exists in argparse**
+
+Run:
+
+```powershell
+$readme = Get-Content -Raw NaviCache4Wan2.2/README.md
+$help = python NaviCache4Wan2.2/navicache_generate.py --help 2>&1 | Out-String
+$documented = [regex]::Matches($readme, '--[a-zA-Z0-9_]+') | ForEach-Object Value | Sort-Object -Unique
+foreach ($flag in $documented) { if (-not $help.Contains($flag)) { throw "README flag absent from CLI: $flag" } }
+```
+
+Expected: every documented flag is present in help output.
+
+- [ ] **Step 4: Scan release text and commit the README**
+
+```powershell
+$bad = Select-String -Path NaviCache4Wan2.2/README.md -Pattern 'TBD|TODO|/data/zhuzhibo|/var/tmp/zhuzhibo|js1.blockelite|driver_launch|prompt_stats_shard' -CaseSensitive:$false
+if ($bad) { $bad; exit 2 }
+git diff --check -- NaviCache4Wan2.2/README.md
+git add -- NaviCache4Wan2.2/README.md
+git diff --cached --check
+git commit -m "docs: align Wan2.2 model README style"
+```
+
+Expected: the commit contains only `NaviCache4Wan2.2/README.md`; the Wan2.1 user modification remains unstaged.
+
+---
+
+### Task 3: Final regression, scope, and push verification
+
+**Files:**
+- Verify: `NaviCache4Wan2.2/navicache_generate.py`
+- Verify: `tests/test_wan22_navicache.py`
+- Verify: `NaviCache4Wan2.2/README.md`
+
+**Interfaces:**
+- Consumes: the committed CLI contract and compact README from Tasks 1 and 2.
+- Produces: a pushed branch whose public docs and parser agree and whose only remaining worktree modification is the protected Wan2.1 file.
+
+- [ ] **Step 1: Run complete local verification**
+
+```powershell
+python -m pytest -q
+python -m py_compile NaviCache4Wan2.2/navicache_generate.py
+python NaviCache4Wan2.2/navicache_generate.py --help | Select-Object -First 20
+git diff --check
+```
+
+Expected: all tests pass, syntax compilation succeeds, help prints the standard names, and diff checking reports no task-file errors.
+
+- [ ] **Step 2: Verify naming and protected scope**
+
+```powershell
+$publicFiles = @('NaviCache4Wan2.2/navicache_generate.py','NaviCache4Wan2.2/README.md')
+$old = Select-String -Path $publicFiles -Pattern '--navicache_ret_steps|--navicache_kalman_q|--navicache_kalman_r'
+if ($old) { $old; exit 2 }
+$status = @(git status --short)
+if ($status.Count -ne 1 -or $status[0] -notmatch 'NaviCache4Wan2\.1/README\.md') { $status; throw 'Unexpected final worktree status' }
+```
+
+Expected: no removed public flag remains, and only `NaviCache4Wan2.1/README.md` is modified in the worktree.
+
+- [ ] **Step 3: Verify remote relation and push**
+
+```powershell
+git fetch origin codex/tidy-release-directory-names
+$relation = @(git rev-list --left-right --count HEAD...origin/codex/tidy-release-directory-names)
+if (($relation -join ' ') -notmatch '^\d+\s+0$') { throw "Remote branch moved or relation is unsafe: $relation" }
+git push origin codex/tidy-release-directory-names
+git fetch origin codex/tidy-release-directory-names
+if ((git rev-parse HEAD) -ne (git rev-parse origin/codex/tidy-release-directory-names)) { throw 'HEAD and origin differ after push' }
+```
+
+Expected: push succeeds and local HEAD equals the origin branch SHA.
diff --git a/docs/superpowers/specs/2026-08-10-wan22-readme-alignment-design.md b/docs/superpowers/specs/2026-08-10-wan22-readme-alignment-design.md
new file mode 100644
index 0000000..1c712b0
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-10-wan22-readme-alignment-design.md
@@ -0,0 +1,55 @@
+# Wan2.2 Model README and CLI Alignment Design
+
+## Objective
+
+Rewrite `NaviCache4Wan2.2/README.md` so it follows the concise model-integration README style currently used by `NaviCache4Wan2.1/README.md` and `NaviCache4HunyuanVideo/README.md` on GitHub `main`. Make the Wan2.2 NaviCache command-line option names exactly match the established public names used by the other integrations.
+
+## Scope
+
+Implementation changes are limited to `NaviCache4Wan2.2/README.md`, `NaviCache4Wan2.2/navicache_generate.py`, and `tests/test_wan22_navicache.py`. The repository root README, algorithm behavior, benchmark evidence, and the existing unstaged `NaviCache4Wan2.1/README.md` user modification will remain unchanged.
+
+## Document Structure
+
+The rewritten README will use this order:
+
+1. `# NaviCache for Wan2.2`
+2. A two-sentence introduction describing NaviCache and linking to official Wan2.2.
+3. `## Usage`, with clone, installation, checkpoint-download, script-copy, and working-directory instructions.
+4. `## Text-to-Video`, with one directly runnable Wan2.2-TI2V-5B command using the validated balanced NaviCache configuration.
+5. `## Image-to-Video`, with one directly runnable command that adds an example input image path.
+6. A short output-path note.
+7. `## Acknowledgements`, thanking and linking to official Wan2.2.
+
+## Command-Line Naming
+
+The Wan2.2 public CLI will expose only the established repository names:
+
+| Meaning | Required public name | Removed Wan2.2-only name |
+|---|---|---|
+| Accumulated error threshold | `--navicache_thresh` | None; already consistent |
+| Initial alignment steps | `--navicache_align_steps` | `--navicache_ret_steps` |
+| Kalman process-noise covariance | `--navicache_process_noise` | `--navicache_kalman_q` |
+| Kalman measurement-noise covariance | `--navicache_measurement_noise` | `--navicache_kalman_r` |
+
+The removed names will not be retained as aliases. The parser will reject them. The renamed arguments will map to the same internal configuration values, so defaults and caching behavior remain unchanged.
+
+## Content Rules
+
+- Match the tone and compactness of the existing GitHub model READMEs.
+- Keep commands consistent with the actual public CLI and its validated defaults.
+- Use `./Wan2.2-TI2V-5B` as the checkpoint example and the official `examples/i2v_input.JPG` sample image.
+- Retain the balanced NaviCache values: threshold `0.05`, alignment steps `10`, process noise `0.05`, and measurement noise `0.05`.
+- State that users should replace the example image with their own file.
+- Remove the parameter table, benchmark Results section, latency explanation, quality metrics, Limitations section, internal runner/provenance language, and detailed implementation commentary.
+- Do not move benchmark or smoke evidence into the release repository; existing external audit records remain the source of verification.
+- Do not document or expose the removed Wan2.2-only command-line names.
+
+## Verification
+
+- Update tests before implementation so they require the established public names, verify their default and explicit-value mapping, and verify that the removed names are rejected.
+- Run the complete Wan2.2 unit-test suite after implementation.
+- Run Python syntax compilation and the public CLI `--help` command.
+- Confirm every documented option appears in CLI help and that the three removed names do not appear.
+- Scan the rewritten README for placeholders, internal server paths, benchmark infrastructure terms, and contradictory output descriptions.
+- Confirm the implementation diff contains only the Wan2.2 README, public entry point, and tests, apart from the already committed design document.
+- Explicitly stage only those three implementation files and preserve the unstaged Wan2.1 user modification.
diff --git a/tests/test_wan22_navicache.py b/tests/test_wan22_navicache.py
new file mode 100644
index 0000000..254553b
--- /dev/null
+++ b/tests/test_wan22_navicache.py
@@ -0,0 +1,278 @@
+from __future__ import annotations
+
+import importlib.util
+from pathlib import Path
+
+import pytest
+import torch
+
+
+ROOT = Path(__file__).resolve().parents[1]
+MODULE_PATH = ROOT / "NaviCache4Wan2.2" / "navicache_generate.py"
+
+
+def load_public_module():
+ spec = importlib.util.spec_from_file_location("navicache_wan22_public", MODULE_PATH)
+ if spec is None or spec.loader is None:
+ raise RuntimeError(f"cannot load {MODULE_PATH}")
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+class FakeModel:
+ def __init__(self):
+ self.native_calls = []
+ self.forward = self.native_forward
+
+ def native_forward(self, x, t, context, seq_len, y=None):
+ branch_offset = 10.0 if context == "cond" else 20.0
+ self.native_calls.append({"context": context, "y": y})
+ return [item + branch_offset for item in x]
+
+
+def config(module, **overrides):
+ values = {
+ "threshold": 0.05,
+ "ret_steps": 0,
+ "kalman_q": 0.05,
+ "kalman_r": 0.05,
+ "sample_steps": 4,
+ }
+ values.update(overrides)
+ return module.NaviCacheConfig(**values)
+
+
+def call_pair(model, value, y=None):
+ raw = [torch.tensor([float(value)])]
+ cond = model.forward(raw, None, "cond", 1, y=y)[0]
+ uncond = model.forward(raw, None, "uncond", 1, y=y)[0]
+ return cond, uncond
+
+
+def test_reset_restores_every_prompt_local_state():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module))
+ module.reset_navicache_state(model)
+
+ model._nc_forward_count = 7
+ model._nc_accumulated_error = 3.0
+ model._nc_k = torch.tensor(2.0)
+ model._nc_kalman_k = torch.tensor(4.0)
+ model._nc_kalman_p = torch.tensor(5.0)
+ model._nc_cache_even = [torch.tensor([1.0])]
+ model._nc_cache_odd = [torch.tensor([2.0])]
+ model._nc_prev_input_even = [torch.tensor([3.0])]
+ model._nc_prev_prev_input_even = [torch.tensor([4.0])]
+ model._nc_prev_output_even = [torch.tensor([5.0])]
+ model._nc_prev_output_odd = [torch.tensor([6.0])]
+
+ module.reset_navicache_state(model)
+
+ assert model._nc_forward_count == 0
+ assert model._nc_compute_count == 0
+ assert model._nc_skip_count == 0
+ assert model._nc_accumulated_error == 0.0
+ assert model._nc_should_compute is True
+ assert model._nc_k is None
+ assert model._nc_kalman_k == 0.0
+ assert model._nc_kalman_p == 1.0
+ assert model._nc_cache_even is None and model._nc_cache_odd is None
+ assert model._nc_prev_input_even is None
+ assert model._nc_prev_prev_input_even is None
+ assert model._nc_prev_output_even is None
+ assert model._nc_prev_output_odd is None
+
+
+def test_ret_steps_force_both_cfg_forwards_to_compute():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module, ret_steps=2, threshold=100.0))
+ module.reset_navicache_state(model)
+
+ call_pair(model, 1.0)
+ call_pair(model, 2.0)
+
+ assert len(model.native_calls) == 4
+ assert model._nc_compute_count == 4
+ assert model._nc_skip_count == 0
+
+
+def test_final_cfg_pair_is_forced_to_compute():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module, threshold=100.0))
+ module.reset_navicache_state(model)
+ model._nc_forward_count = 6
+ model._nc_should_compute = False
+ model._nc_cache_even = [torch.tensor([10.0])]
+ model._nc_cache_odd = [torch.tensor([20.0])]
+
+ call_pair(model, 4.0)
+
+ assert len(model.native_calls) == 2
+ assert model._nc_compute_count == 2
+ assert model._nc_skip_count == 0
+
+
+def test_conditional_decision_controls_pair_and_branch_caches_stay_separate():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module, threshold=100.0))
+ module.reset_navicache_state(model)
+
+ call_pair(model, 1.0)
+ call_pair(model, 2.0)
+ cond, uncond = call_pair(model, 3.0)
+
+ assert len(model.native_calls) == 4
+ assert model._nc_compute_count == 4
+ assert model._nc_skip_count == 2
+ assert torch.equal(cond, torch.tensor([13.0]))
+ assert torch.equal(uncond, torch.tensor([23.0]))
+
+
+def test_accumulated_error_uses_normalized_predicted_change():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module, threshold=0.9))
+ module.reset_navicache_state(model)
+ model._nc_forward_count = 2
+ model._nc_accumulated_error = torch.tensor(0.1)
+ model._nc_k = torch.tensor(2.0)
+ model._nc_prev_input_even = [torch.tensor([1.0])]
+ model._nc_prev_output_even = [torch.tensor([4.0])]
+ model._nc_cache_even = [torch.tensor([10.0])]
+ model._nc_cache_odd = [torch.tensor([20.0])]
+
+ call_pair(model, 2.0)
+
+ assert model._nc_accumulated_error.item() == pytest.approx(0.6)
+ assert model._nc_skip_count == 2
+ assert len(model.native_calls) == 0
+
+
+def test_exact_compute_corrects_kalman_from_k_zero_and_p_one():
+ module = load_public_module()
+ model = FakeModel()
+ options = config(module, threshold=0.0)
+ module.install_navicache(model, options)
+ module.reset_navicache_state(model)
+
+ call_pair(model, 1.0)
+ call_pair(model, 2.0)
+
+ expected_gain = 1.05 / (1.05 + 0.05 + 1e-8)
+ assert float(model._nc_kalman_k) == pytest.approx(expected_gain)
+ assert float(model._nc_kalman_p) == pytest.approx((1.0 - expected_gain) * 1.05)
+ assert float(model._nc_k) == pytest.approx(expected_gain)
+
+
+def test_threshold_zero_matches_native_and_never_skips():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module, threshold=0.0))
+ module.reset_navicache_state(model)
+
+ outputs = [call_pair(model, step + 1.0) for step in range(4)]
+
+ expected = [
+ (torch.tensor([step + 10.0]), torch.tensor([step + 20.0]))
+ for step in range(1, 5)
+ ]
+ assert all(
+ torch.equal(actual_cond, expected_cond)
+ and torch.equal(actual_uncond, expected_uncond)
+ for (actual_cond, actual_uncond), (expected_cond, expected_uncond) in zip(outputs, expected)
+ )
+ assert len(model.native_calls) == 8
+ assert model._nc_skip_count == 0
+
+
+def test_consecutive_prompts_do_not_inherit_cache_or_kalman_state():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module, threshold=100.0))
+ module.reset_navicache_state(model)
+ call_pair(model, 1.0)
+ call_pair(model, 2.0)
+ call_pair(model, 3.0)
+ assert model._nc_skip_count == 2
+
+ module.reset_navicache_state(model)
+ call_pair(model, 100.0)
+
+ assert model._nc_forward_count == 2
+ assert model._nc_compute_count == 2
+ assert model._nc_skip_count == 0
+ assert model._nc_cache_even is not None and model._nc_cache_odd is not None
+
+
+def test_native_forward_receives_y_keyword_and_not_clip_fea():
+ module = load_public_module()
+ model = FakeModel()
+ module.install_navicache(model, config(module, threshold=0.0))
+ module.reset_navicache_state(model)
+ y = [torch.tensor([9.0])]
+
+ call_pair(model, 1.0, y=y)
+
+ assert [call["y"] for call in model.native_calls] == [y, y]
+
+
+def test_public_cli_defaults_match_verified_configuration():
+ module = load_public_module()
+ parser = module.build_parser()
+ args = parser.parse_args(["--ckpt_dir", "weights"])
+
+ assert args.task == "ti2v-5B"
+ assert args.size == "1280*704"
+ assert args.frame_num == 121
+ assert args.sample_steps == 50
+ assert args.sample_shift == 5.0
+ assert args.sample_guide_scale == 5.0
+ assert args.base_seed == 42
+ assert args.navicache_thresh == 0.05
+ assert args.navicache_align_steps == 10
+ assert args.navicache_process_noise == 0.05
+ assert args.navicache_measurement_noise == 0.05
+
+
+def test_public_cli_maps_repository_standard_navicache_names():
+ module = load_public_module()
+ args = module.build_parser().parse_args(
+ [
+ "--ckpt_dir",
+ "weights",
+ "--navicache_thresh",
+ "0.07",
+ "--navicache_align_steps",
+ "8",
+ "--navicache_process_noise",
+ "0.03",
+ "--navicache_measurement_noise",
+ "0.04",
+ ]
+ )
+
+ assert args.navicache_thresh == 0.07
+ assert args.navicache_align_steps == 8
+ assert args.navicache_process_noise == 0.03
+ assert args.navicache_measurement_noise == 0.04
+
+
+@pytest.mark.parametrize(
+ "removed_name",
+ [
+ "--navicache_ret_steps",
+ "--navicache_kalman_q",
+ "--navicache_kalman_r",
+ ],
+)
+def test_public_cli_rejects_removed_wan22_only_names(removed_name):
+ module = load_public_module()
+ parser = module.build_parser()
+
+ with pytest.raises(SystemExit):
+ parser.parse_args(["--ckpt_dir", "weights", removed_name, "1"])