Instructions to use Wayne-King/echo-memory-diffusers with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use Wayne-King/echo-memory-diffusers with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("Wayne-King/echo-memory-diffusers", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
Upload pipeline.py with huggingface_hub
Browse files- 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
|