| """ |
| GPU test: how many Wan 2.1 1.3B weights does the CGLA DiT block load? |
| |
| Builds a Wan-2.1-T2V-1.3B-shaped DiT whose every ``DiTBlock`` is replaced by |
| the CGLA block (``diffsynth/models/memory/u_vit_cgla_blocks.py:: |
| CGLATransformerBlock`` — the real DFOT SSE-GLA block, sequential attn+mlp, |
| carrying Wan-shaped submodules ``cross_attn``/``norm1/2/3``/``ffn``/ |
| ``modulation``/``gate``). Loads the Wan 2.1 1.3B checkpoint and reports: |
| |
| * which block submodule prefixes loaded successfully (Wan key -> CGLA param) |
| * which were NOT loaded, split into: |
| - "unexpected" (Wan has them, CGLA block does not — e.g. Wan's softmax |
| ``self_attn``), and |
| - "missing" (CGLA block has them, Wan does not — the new SSE-GLA |
| attention ``spatial_*``/``temporal_attn``/``noise_write_gate``/``mlp_*``) |
| * non-block Wan keys (patch_embedding / text_embedding / time_embedding / |
| final_layer / ...) reported separately (not part of the DiTBlock swap) |
| * coverage numbers: #keys, #params, % of Wan block params loaded, % of CGLA |
| block params that are Wan-initialised. |
| |
| Wan 2.1 T2V 1.3B config (``diffsynth/models/wan_video_dit.py`` hash |
| ``9269f8db...``): dim=1536, num_heads=12, num_layers=30, ffn_dim=8960, |
| has_image_input=False, eps=1e-6, patch_size=(1,2,2). These are auto-detected |
| from the checkpoint where possible (dim/ffn_dim/num_layers/has_image_input); |
| ``num_heads`` is not weight-inferable and uses the known config (12). |
| |
| NOTE: this is a weight-LOADING test (no forward pass). Run on a GPU node that |
| has the Wan base model + the echo-memory env: |
| |
| PYTHONPATH=. python3 tests/test_cgla_wan.py |
| # optional: --ckpt /path/to/diffusion_pytorch_model.safetensors |
| # optional: --mechanism prope|ucpe (loading result is identical across |
| # mechanisms; only new params differ) |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
| import sys |
| from collections import Counter, defaultdict |
|
|
| _REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| if _REPO not in sys.path: |
| sys.path.insert(0, _REPO) |
|
|
| import torch |
| import torch.nn as nn |
| from safetensors.torch import load_file as safe_load_file |
|
|
| from diffsynth.models.memory.u_vit_cgla_blocks import CGLATransformerBlock, remap_wan_to_cgla |
|
|
| |
| WAN_T2V_1_3B = dict( |
| dim=1536, |
| num_heads=12, |
| num_layers=30, |
| ffn_dim=8960, |
| has_image_input=False, |
| eps=1e-6, |
| patch_size=(1, 2, 2), |
| ) |
|
|
| |
| |
| |
| CGLA_NUM_PATCHES = 880 |
| CGLA_TEMPORAL_LENGTH = 21 |
| CGLA_EMB_DIM = 1024 |
| CGLA_POSE_DIM = 12 |
| CGLA_HEAD_DIM = 128 |
|
|
| DEFAULT_CKPT = "/apdcephfs_zwfy/share_303204533/jiakuihu/checkpoints/Wan2.1-T2V-1.3B/diffusion_pytorch_model.safetensors" |
|
|
|
|
| def detect_config(sd: dict) -> dict: |
| """Auto-detect dim / ffn_dim / num_layers / has_image_input from the ckpt.""" |
| cfg = dict(WAN_T2V_1_3B) |
| |
| blk_ids = sorted({int(k.split(".")[1]) for k in sd if k.startswith("blocks.")}) |
| if blk_ids: |
| cfg["num_layers"] = len(blk_ids) |
| |
| def _shape(name): |
| return tuple(sd[name].shape) if name in sd else None |
| q = _shape("blocks.0.self_attn.q.weight") or _shape("blocks.0.cross_attn.q.weight") |
| if q is not None: |
| cfg["dim"] = q[0] |
| ffn0 = _shape("blocks.0.ffn.0.weight") |
| if ffn0 is not None: |
| cfg["ffn_dim"] = ffn0[0] |
| cfg["has_image_input"] = "blocks.0.cross_attn.k_img.weight" in sd |
| return cfg |
|
|
|
|
| def build_cgla_dit(cfg: dict, mechanism: str) -> nn.Module: |
| """A Wan-shaped DiT whose blocks are CGLATransformerBlock (no VAE/text). |
| |
| CGLATransformerBlock is a Wan DiTBlock with CGLA (SSE-GLA) replacing the |
| self-attention (linear attention on the full flattened token sequence), then |
| cross-attention to text, then FFN. Wan submodules (cross_attn/norm1/2/3/ |
| ffn/modulation/gate) + the SSE-GLA q/k/v/o projections load from the Wan |
| checkpoint; the rest of the SSE-GLA params are new. |
| """ |
| use_pose_rope = mechanism in ("prope", "ucpe") |
| |
| |
| |
| head_dim = cfg["dim"] // cfg["num_heads"] |
|
|
| class CGLADiT(nn.Module): |
| def __init__(self): |
| super().__init__() |
| self.blocks = nn.ModuleList([ |
| CGLATransformerBlock( |
| has_image_input=cfg["has_image_input"], |
| dim=cfg["dim"], |
| num_heads=cfg["num_heads"], |
| ffn_dim=cfg["ffn_dim"], |
| eps=cfg["eps"], |
| head_dim=head_dim, |
| num_sparse_partition=4, |
| num_writer=1, |
| num_reader=1, |
| pose_dim=CGLA_POSE_DIM, |
| pose_bottleneck=64, |
| gate_logit_normalizer=16, |
| gate_low_rank_dim=16, |
| use_pose_rope=use_pose_rope, |
| use_pose_gate_mod=False, |
| layer_idx=i, |
| emb_dim=CGLA_EMB_DIM, |
| ) |
| for i in range(cfg["num_layers"]) |
| ]) |
|
|
| return CGLADiT() |
|
|
|
|
| def _top(prefix_key: str) -> str: |
| """Block submodule prefix: 'blocks.N.<attr>...' -> '<attr>'; |
| non-block key -> its first component ('patch_embedding.weight' -> 'patch_embedding').""" |
| if prefix_key.startswith("blocks."): |
| parts = prefix_key.split(".") |
| return parts[2] if len(parts) > 2 else parts[-1] |
| return prefix_key.split(".")[0] |
|
|
|
|
| def _numel(sd, keys): |
| return int(sum(sd[k].numel() for k in keys if k in sd)) |
|
|
|
|
| def _prefix_summary(keys): |
| """Group keys by their first component; return {prefix: (n_keys, n_params)}.""" |
| |
| g = defaultdict(list) |
| for k in keys: |
| rest = k.split(".", 2)[2] if k.startswith("blocks.") else k |
| g[rest.split(".")[0]].append(k) |
| return g |
|
|
|
|
| def report(sd, model, cfg, mechanism): |
| n_blk = cfg["num_layers"] |
|
|
| |
| |
| |
| sd = remap_wan_to_cgla(sd) |
|
|
| model_sd = model.state_dict() |
| ckpt_keys = set(sd.keys()) |
| model_keys = set(model_sd.keys()) |
|
|
| |
| ckpt_blk = {k for k in ckpt_keys if k.startswith("blocks.")} |
| model_blk = {k for k in model_keys if k.startswith("blocks.")} |
| |
| ckpt_nonblk = ckpt_keys - ckpt_blk |
|
|
| missing, unexpected = model.load_state_dict(sd, strict=False) |
| |
| |
| missing_blk = [k for k in missing if k.startswith("blocks.")] |
| unexpected_blk = [k for k in unexpected if k.startswith("blocks.")] |
| unexpected_nonblk = [k for k in unexpected if not k.startswith("blocks.") and k in ckpt_nonblk] |
|
|
| loaded_blk = sorted(model_blk - set(missing_blk)) |
|
|
| |
| wan_blk_params = _numel(sd, ckpt_blk) |
| loaded_params = _numel(sd, loaded_blk) |
| cgla_blk_params = _numel(model_sd, model_blk) |
| wan_init_params = loaded_params |
|
|
| |
| def _per_prefix(keys, src): |
| g = defaultdict(lambda: [0, 0]) |
| for k in keys: |
| pref = _top(k) |
| g[pref][0] += 1 |
| g[pref][1] += int(src[k].numel()) if k in src else 0 |
| return g |
|
|
| loaded_g = _per_prefix(loaded_blk, sd) |
| miss_g = _per_prefix(missing_blk, model_sd) |
| unexp_g = _per_prefix(unexpected_blk, sd) |
| nonblk_g = _per_prefix(ckpt_nonblk, sd) |
|
|
| |
| print("=" * 78) |
| print("WAN 2.1 1.3B -> CGLA DiT weight-loading report") |
| print("=" * 78) |
| print(f"checkpoint : {CKPT}") |
| print(f"detected Wan config : {cfg}") |
| print(f"CGLA mechanism : {mechanism}") |
| print(f" (use_pose_rope={mechanism in ('prope','ucpe')}; loading coverage is") |
| print(f" identical across cgla/prope/ucpe — only *new* (non-Wan) params differ)") |
| print(f"num blocks : {n_blk}") |
| print(f"head_dim (SSEGLA) : {CGLA_HEAD_DIM} (= dim/num_heads -> SSEGLA key_dim == dim)") |
| print() |
|
|
| print("-" * 78) |
| print("1) SUCCESSFULLY LOADED (Wan block key -> CGLA param, name+shape match)") |
| print("-" * 78) |
| if loaded_g: |
| print(f" {'prefix':<22}{'keys (×N blk)':>16}{'params':>16}") |
| for pref in sorted(loaded_g): |
| n, p = loaded_g[pref] |
| print(f" {pref:<22}{n:>16}{p:>16,}") |
| print(f" {'(total loaded)':<22}{'':>16}{loaded_params:>16,}") |
| print() |
|
|
| print("-" * 78) |
| print("2a) NOT LOADED — unexpected (in Wan ckpt, NOT in CGLA block)") |
| print("-" * 78) |
| if unexp_g: |
| print(f" {'prefix':<22}{'keys (×N blk)':>16}{'params':>16}") |
| for pref in sorted(unexp_g): |
| n, p = unexp_g[pref] |
| print(f" {pref:<22}{n:>16}{p:>16,}") |
| print(f" {'(total unexpected)':<22}{'':>16}{_numel(sd, unexpected_blk):>16,}") |
| else: |
| print(" (none)") |
| print() |
|
|
| print("-" * 78) |
| print("2b) NOT LOADED — missing (CGLA block has, NOT in Wan ckpt = new CGLA params)") |
| print("-" * 78) |
| if miss_g: |
| print(f" {'prefix':<22}{'keys (×N blk)':>16}{'params':>16}") |
| for pref in sorted(miss_g): |
| n, p = miss_g[pref] |
| print(f" {pref:<22}{n:>16}{p:>16,}") |
| print(f" {'(total missing/new)':<22}{'':>16}{_numel(model_sd, missing_blk):>16,}") |
| else: |
| print(" (none)") |
| print() |
|
|
| print("-" * 78) |
| print("3) NON-BLOCK Wan keys (not part of the DiTBlock swap; e.g. patch/text") |
| print(" /time/final embeddings — the CGLA model has no such submodules)") |
| print("-" * 78) |
| if nonblk_g: |
| print(f" {'prefix':<22}{'keys':>10}{'params':>16}") |
| for pref in sorted(nonblk_g): |
| n, p = nonblk_g[pref] |
| print(f" {pref:<22}{n:>10}{p:>16,}") |
| else: |
| print(" (none)") |
| print() |
|
|
| |
| wan_total = sum(v.numel() for v in sd.values()) |
| pct_wan_loaded = 100.0 * loaded_params / wan_blk_params if wan_blk_params else 0.0 |
| pct_cgla_waninit = 100.0 * wan_init_params / cgla_blk_params if cgla_blk_params else 0.0 |
| print("=" * 78) |
| print("COVERAGE SUMMARY") |
| print("=" * 78) |
| print(f" Wan ckpt total params : {wan_total:>14,}") |
| print(f" Wan BLOCK params (blocks.*.* ) : {wan_blk_params:>14,}") |
| print(f" -> loaded into CGLA block : {loaded_params:>14,} ({pct_wan_loaded:5.2f}% of Wan block params)") |
| print(f" CGLA block params (new + Wan-shape): {cgla_blk_params:>14,}") |
| print(f" -> Wan-initialised (loaded) : {wan_init_params:>14,} ({pct_cgla_waninit:5.2f}% of CGLA block params)") |
| print(f" -> CGLA-new (missing, needs training): {cgla_blk_params - wan_init_params:>14,} " |
| f"({100.0*(cgla_blk_params-wan_init_params)/cgla_blk_params:5.2f}% of CGLA block params)") |
| print("=" * 78) |
|
|
| _report_ssegla_param_audit(model, sd, mechanism) |
|
|
|
|
| def _self_attn_subparam(key: str): |
| """For 'blocks.N.self_attn.<sub>.[...]' -> '<sub>'; else None.""" |
| parts = key.split(".") |
| if len(parts) >= 3 and parts[0] == "blocks" and parts[2] == "self_attn": |
| return parts[3] if len(parts) > 3 else None |
| return None |
|
|
|
|
| def _report_ssegla_param_audit(model, sd, mechanism): |
| """Per-param audit of the SSE-GLA ``self_attn`` submodules. |
| |
| Answers: for each SSEGLA param (q_proj / k_proj / v_proj / o_proj / |
| lora_q_proj / lora_k_proj / gk_proj / e_proj / g_proj / o_norm) — LOADED from |
| Wan (name+shape match via the remap) or REINIT (no Wan key)? And for the |
| PRoPE/UCPE pose modules (pose_encoder / pose_q_proj / pose_k_proj / |
| pose_gk_proj / pose_e_proj / pose_rope / pose_gate_mod) — all REINIT, with |
| their dfot zero-init policy noted (the 'up' projections are zero-init; the |
| 'down' pose_encoder is default-init). |
| """ |
| model_sd = model.state_dict() |
| |
| def _group(keys, src): |
| g = defaultdict(lambda: [0, 0]) |
| for k in keys: |
| sp = _self_attn_subparam(k) |
| if sp is None: |
| continue |
| g[sp][0] += 1 |
| g[sp][1] += int(src[k].numel()) if k in src else 0 |
| return g |
|
|
| model_sub = _group(model_sd.keys(), model_sd) |
| ckpt_sub = _group(sd.keys(), sd) |
|
|
| |
| WAN_REMAP_TARGETS = {"q_proj", "k_proj", "v_proj", "o_proj"} |
| |
| |
| POSE_ZERO_UP = { |
| "pose_q_proj", "pose_k_proj", "pose_gk_proj", "pose_e_proj", |
| "pose_gate_mod", "pose_rope", |
| } |
|
|
| print() |
| print("=" * 78) |
| print("4) SSE-GLA self_attn PARAM-BY-PARAM (loaded vs reinitialised)") |
| print("=" * 78) |
| print(f" {'SSEGLA param':<16}{'Wan source':<20}{'keys (×N blk)':>14}{'params':>14} status") |
| print(" " + "-" * 74) |
| total_loaded_sa = 0 |
| total_reinit_sa = 0 |
| for sp in sorted(model_sub): |
| n, p = model_sub[sp] |
| wan_src = "—" |
| status = "REINIT (no Wan key)" |
| if sp in WAN_REMAP_TARGETS: |
| wan_name = sp.replace("_proj", "") |
| wan_src = f"self_attn.{wan_name}" |
| if sp in ckpt_sub: |
| status = "LOADED" |
| total_loaded_sa += p |
| else: |
| status = "REINIT (Wan key absent)" |
| total_reinit_sa += p |
| else: |
| total_reinit_sa += p |
| note = "" |
| if sp in POSE_ZERO_UP and mechanism in ("prope", "ucpe"): |
| note = " [dfot zero-init 'up' proj]" |
| elif sp == "pose_encoder" and mechanism in ("prope", "ucpe"): |
| note = " [dfot default-init 'down' proj]" |
| print(f" {sp:<16}{wan_src:<20}{n:>14}{p:>14,} {status}{note}") |
| print(" " + "-" * 74) |
| print(f" self_attn loaded (q/k/v/o_proj from Wan): {total_loaded_sa:>14,}") |
| print(f" self_attn reinit (GLA gates/LoRA/norm): {total_reinit_sa:>14,}") |
| print() |
|
|
| |
| print("-" * 78) |
| if mechanism in ("prope", "ucpe"): |
| print(f"PRoPE/UCPE pose modules (use_pose_rope=True) — ALL reinitialised:") |
| print(f" {'pose module':<16}{'init policy (dfot)':<40}{'params':>14}") |
| print(" " + "-" * 70) |
| pose_rows = [ |
| ("pose_encoder", "default-init (down proj; NOT zeroed)"), |
| ("pose_q_proj", "zero-init 'up' proj => pose contributes 0 at step 0"), |
| ("pose_k_proj", "zero-init 'up' proj"), |
| ("pose_gk_proj", "zero-init 'up' proj"), |
| ("pose_e_proj", "zero-init (direct)"), |
| ("pose_rope", "PoseRoPE net[-1] zero-init (angle MLP)"), |
| ] |
| if any(_self_attn_subparam(k) == "pose_gate_mod" for k in model_sd): |
| pose_rows.append(("pose_gate_mod", "zero-init (weight+bias)")) |
| for name, policy in pose_rows: |
| p = model_sub.get(name, [0, 0])[1] |
| present = "present" if name in model_sub else "ABSENT" |
| print(f" {name:<16}{policy:<40}{p:>14,} {present}") |
| print(" => at step 0, pose contributes exactly 0 to q2/k2/gk2/eta (LoRA-style),") |
| print(" so PRoPE/UCPE is numerically identical to vanilla CGLA until trained.") |
| else: |
| print(f"mechanism={mechanism!r}: no pose modules (use_pose_rope=False).") |
| print("=" * 78) |
|
|
|
|
| def parse_args(): |
| import argparse |
| p = argparse.ArgumentParser(description="CGLA <-> Wan 2.1 1.3B weight-loading test") |
| p.add_argument("--ckpt", default=DEFAULT_CKPT, |
| help="path to diffusion_pytorch_model.safetensors") |
| p.add_argument("--mechanism", default="prope", choices=["cgla", "prope", "ucpe"], |
| help="CGLA variant (loading coverage is identical across them)") |
| return p.parse_args() |
|
|
|
|
| CKPT = DEFAULT_CKPT |
|
|
|
|
| def main(): |
| global CKPT |
| args = parse_args() |
| CKPT = args.ckpt |
| if not os.path.isfile(CKPT): |
| print(f"ERROR: checkpoint not found: {CKPT}", file=sys.stderr) |
| print(" set --ckpt /path/to/Wan2.1-T2V-1.3B/diffusion_pytorch_model.safetensors", |
| file=sys.stderr) |
| sys.exit(1) |
|
|
| device = "cuda" if torch.cuda.is_available() else "cpu" |
| print(f"[cgla_wan] loading checkpoint (CPU): {CKPT}") |
| sd = safe_load_file(CKPT) |
| cfg = detect_config(sd) |
| print(f"[cgla_wan] detected config: {cfg}") |
|
|
| |
| model = build_cgla_dit(cfg, args.mechanism) |
| report(sd, model, cfg, args.mechanism) |
|
|
| |
| |
| if device == "cuda": |
| try: |
| model = model.to(device=device, dtype=torch.bfloat16) |
| n = sum(p.numel() for p in model.parameters()) |
| print(f"[cgla_wan] CGLA DiT moved to {device} (bf16); {n:,} params construct OK") |
| except Exception as e: |
| print(f"[cgla_wan] WARN: GPU move failed: {e}") |
| else: |
| print("[cgla_wan] no CUDA — skipping GPU construct check") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|