Wayne-King commited on
Commit
114aca6
·
verified ·
1 Parent(s): 2d469a0

Upload pipeline.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. pipeline.py +159 -0
pipeline.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Echo Team and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Echo-Memory community pipeline for official Wan 2.1 Diffusers weights.
16
+
17
+ Loads `Wan-AI/Wan2.1-T2V-1.3B-Diffusers`, then overlays the released
18
+ `context_k1` row from `Echo-Team/Echo-Memory` after remapping original
19
+ DiffSynth / Wan keys onto the Diffusers transformer.
20
+
21
+ Paper: https://arxiv.org/abs/2606.09803
22
+ Code: https://github.com/Echo-Team-Joy-Future-Academy-JD/Echo-Memory
23
+ """
24
+
25
+ from typing import Dict, Iterable, List, Optional, Tuple
26
+
27
+ import torch
28
+ from huggingface_hub import hf_hub_download
29
+ from safetensors.torch import load_file
30
+
31
+ from diffusers import WanPipeline
32
+
33
+
34
+ DEFAULT_BASE_MODEL = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers"
35
+ DEFAULT_REPO_ID = "Echo-Team/Echo-Memory"
36
+ DEFAULT_FILENAME = "context_k1/epoch-0.safetensors"
37
+ DEFAULT_CONVERTED_REPO_ID = "Wayne-King/echo-memory-diffusers"
38
+ DEFAULT_CONVERTED_FILENAME = "context_k1-diffusers/diffusion_pytorch_model.safetensors"
39
+
40
+ SKIP_SUBSTRINGS = (
41
+ "action_mlp",
42
+ "self_attn_with_action",
43
+ "block_wise_ssm",
44
+ "videossm_hybrid",
45
+ "spatial_memory_module",
46
+ )
47
+
48
+ # Same mapping as `scripts/convert_wan_to_diffusers.py` for Wan 2.1 T2V.
49
+ TRANSFORMER_KEYS_RENAME_DICT = {
50
+ "time_embedding.0": "condition_embedder.time_embedder.linear_1",
51
+ "time_embedding.2": "condition_embedder.time_embedder.linear_2",
52
+ "text_embedding.0": "condition_embedder.text_embedder.linear_1",
53
+ "text_embedding.2": "condition_embedder.text_embedder.linear_2",
54
+ "time_projection.1": "condition_embedder.time_proj",
55
+ "head.modulation": "scale_shift_table",
56
+ "head.head": "proj_out",
57
+ "modulation": "scale_shift_table",
58
+ "ffn.0": "ffn.net.0.proj",
59
+ "ffn.2": "ffn.net.2",
60
+ # The original model names norms as norm1, norm3, norm2.
61
+ # Diffusers uses norm1, norm2, norm3.
62
+ "norm2": "norm__placeholder",
63
+ "norm3": "norm2",
64
+ "norm__placeholder": "norm3",
65
+ "self_attn.q": "attn1.to_q",
66
+ "self_attn.k": "attn1.to_k",
67
+ "self_attn.v": "attn1.to_v",
68
+ "self_attn.o": "attn1.to_out.0",
69
+ "self_attn.norm_q": "attn1.norm_q",
70
+ "self_attn.norm_k": "attn1.norm_k",
71
+ "cross_attn.q": "attn2.to_q",
72
+ "cross_attn.k": "attn2.to_k",
73
+ "cross_attn.v": "attn2.to_v",
74
+ "cross_attn.o": "attn2.to_out.0",
75
+ "cross_attn.norm_q": "attn2.norm_q",
76
+ "cross_attn.norm_k": "attn2.norm_k",
77
+ }
78
+
79
+
80
+ def is_diffusers_transformer_state_dict(keys: Iterable[str]) -> bool:
81
+ keys = list(keys)
82
+ return any(key.startswith("condition_embedder.") or ".attn1." in key for key in keys)
83
+
84
+
85
+ def convert_echo_memory_transformer_state_dict(
86
+ state_dict: Dict[str, torch.Tensor],
87
+ skip_substrings: Iterable[str] = SKIP_SUBSTRINGS,
88
+ ) -> Tuple[Dict[str, torch.Tensor], List[str]]:
89
+ """Convert original Echo-Memory / DiffSynth Wan keys to Diffusers names."""
90
+ skip_substrings = tuple(skip_substrings)
91
+ if is_diffusers_transformer_state_dict(state_dict):
92
+ converted = {
93
+ key: value
94
+ for key, value in state_dict.items()
95
+ if not any(token in key for token in skip_substrings)
96
+ }
97
+ skipped = [key for key in state_dict if key not in converted]
98
+ return converted, skipped
99
+
100
+ converted = {}
101
+ skipped = []
102
+ for key, value in state_dict.items():
103
+ if any(token in key for token in skip_substrings):
104
+ skipped.append(key)
105
+ continue
106
+ new_key = key
107
+ for replace_key, rename_key in TRANSFORMER_KEYS_RENAME_DICT.items():
108
+ new_key = new_key.replace(replace_key, rename_key)
109
+ converted[new_key] = value
110
+ return converted, skipped
111
+
112
+
113
+ class EchoMemoryPipeline(WanPipeline):
114
+ """Wan 2.1 T2V pipeline with an Echo-Memory `context_k1` overlay."""
115
+
116
+ def load_echo_memory_weights(
117
+ self,
118
+ repo_id: str = DEFAULT_REPO_ID,
119
+ filename: str = DEFAULT_FILENAME,
120
+ local_path: Optional[str] = None,
121
+ strict: bool = False,
122
+ ):
123
+ """Download one Echo-Memory row and overlay it on `self.transformer`."""
124
+ ckpt_path = local_path or hf_hub_download(repo_id=repo_id, filename=filename)
125
+ raw = load_file(ckpt_path)
126
+ converted, skipped = convert_echo_memory_transformer_state_dict(raw)
127
+ missing, unexpected = self.transformer.load_state_dict(converted, strict=strict)
128
+ print(
129
+ f"[Echo-Memory] overlaid {len(converted)}/{len(raw)} transformer keys from {ckpt_path} "
130
+ f"(skipped={len(skipped)}, missing={len(missing)}, unexpected={len(unexpected)})"
131
+ )
132
+ return missing, unexpected, skipped
133
+
134
+ def load_converted_echo_memory_weights(
135
+ self,
136
+ repo_id: str = DEFAULT_CONVERTED_REPO_ID,
137
+ filename: str = DEFAULT_CONVERTED_FILENAME,
138
+ local_path: Optional[str] = None,
139
+ strict: bool = False,
140
+ ):
141
+ """Overlay the already-remapped `context_k1` transformer weights."""
142
+ return self.load_echo_memory_weights(
143
+ repo_id=repo_id,
144
+ filename=filename,
145
+ local_path=local_path,
146
+ strict=strict,
147
+ )
148
+
149
+ @classmethod
150
+ def from_echo_memory(
151
+ cls,
152
+ pretrained_model_name_or_path: str = DEFAULT_BASE_MODEL,
153
+ echo_memory_repo: str = DEFAULT_REPO_ID,
154
+ echo_memory_filename: str = DEFAULT_FILENAME,
155
+ **kwargs,
156
+ ):
157
+ pipe = cls.from_pretrained(pretrained_model_name_or_path, **kwargs)
158
+ pipe.load_echo_memory_weights(repo_id=echo_memory_repo, filename=echo_memory_filename)
159
+ return pipe