Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- README.md +36 -13
- app.py +391 -0
- mean.npy +3 -0
- motionvq/__init__.py +0 -0
- motionvq/encdec.py +67 -0
- motionvq/motion_utils.py +277 -0
- motionvq/paramUtil.py +63 -0
- motionvq/quantize_cnn.py +415 -0
- motionvq/resnet.py +82 -0
- motionvq/vqvae.py +136 -0
- requirements.txt +7 -0
- std.npy +3 -0
README.md
CHANGED
|
@@ -1,13 +1,36 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: gradio
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: IRG-MotionLLM Text-to-Motion
|
| 3 |
+
emoji: 🕺
|
| 4 |
+
colorFrom: pink
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: gradio
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: false
|
| 9 |
+
short_description: Text-to-3D human motion generation
|
| 10 |
+
python_version: "3.10"
|
| 11 |
+
startup_duration_timeout: 40m
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# IRG-MotionLLM — Text-to-3D-Motion
|
| 15 |
+
|
| 16 |
+
Interactive demo for
|
| 17 |
+
[**IRG-MotionLLM: Interleaving Motion Generation, Assessment and Refinement
|
| 18 |
+
for Text-to-Motion Generation**](https://huggingface.co/papers/2512.10730).
|
| 19 |
+
|
| 20 |
+
Enter a natural-language description of a human motion and the model generates
|
| 21 |
+
a 3D skeletal animation (HumanML3D, 22 joints).
|
| 22 |
+
|
| 23 |
+
## How it works
|
| 24 |
+
|
| 25 |
+
1. A Gemma-2-2B LLM (fine-tuned by the authors) reads the prompt and, in an
|
| 26 |
+
*interleaved* chain-of-thought, generates → self-assesses → refines discrete
|
| 27 |
+
motion tokens until it is satisfied.
|
| 28 |
+
2. The final motion tokens are decoded by a VQ-VAE into 263-dimensional
|
| 29 |
+
HumanML3D motion features.
|
| 30 |
+
3. The features are converted to 3D joint positions (`recover_from_ric`) and
|
| 31 |
+
rendered to a skeleton video.
|
| 32 |
+
|
| 33 |
+
Model weights: [`Lymann/IRG-MotionLLM-HumanML3D`](https://huggingface.co/Lymann/IRG-MotionLLM-HumanML3D)
|
| 34 |
+
· Code: [HumanMLLM/IRG-MotionLLM](https://github.com/HumanMLLM/IRG-MotionLLM)
|
| 35 |
+
|
| 36 |
+
Models are released under CC-BY-NC-SA 4.0 for research use only.
|
app.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 4 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 5 |
+
os.environ.setdefault("MPLBACKEND", "Agg")
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
import types
|
| 9 |
+
import tempfile
|
| 10 |
+
|
| 11 |
+
import spaces
|
| 12 |
+
import torch
|
| 13 |
+
import numpy as np
|
| 14 |
+
import gradio as gr
|
| 15 |
+
import matplotlib
|
| 16 |
+
|
| 17 |
+
matplotlib.use("Agg")
|
| 18 |
+
|
| 19 |
+
from huggingface_hub import hf_hub_download
|
| 20 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 21 |
+
|
| 22 |
+
from motionvq.vqvae import HumanVQVAE
|
| 23 |
+
from motionvq.motion_utils import recover_from_ric, plot_3d_motion
|
| 24 |
+
from motionvq.paramUtil import t2m_kinematic_chain
|
| 25 |
+
|
| 26 |
+
# ----------------------------------------------------------------------------
|
| 27 |
+
# Config -- values verified against the IRG-MotionLLM / Motion-Agent source and
|
| 28 |
+
# the actual Stage-3 checkpoint tensor shapes:
|
| 29 |
+
# * base LLM : google/gemma-2-2b-it (bf16)
|
| 30 |
+
# * added toks : <Motion>, </Motion>, and 512 motion codes (nb_code = 512)
|
| 31 |
+
# * VQVAE : HumanML3D 263-dim, codebook 512x512, down_t=2, stride_t=2
|
| 32 |
+
# * Stage-3 checkpoint bundles BOTH the merged full LLM and the VQVAE weights
|
| 33 |
+
# ----------------------------------------------------------------------------
|
| 34 |
+
BASE_LLM = "google/gemma-2-2b-it"
|
| 35 |
+
CKPT_REPO = "Lymann/IRG-MotionLLM-HumanML3D"
|
| 36 |
+
CKPT_FILE = "Stage-3/irg_motionllm_unified_rl_stage3.bin"
|
| 37 |
+
|
| 38 |
+
NB_CODE = 512
|
| 39 |
+
JOINTS_NUM = 22
|
| 40 |
+
FPS = 20
|
| 41 |
+
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class VQArgs:
|
| 45 |
+
"""Minimal args namespace expected by HumanVQVAE / VQVAE_251."""
|
| 46 |
+
|
| 47 |
+
dataname = "t2m"
|
| 48 |
+
quantizer = "ema_reset"
|
| 49 |
+
mu = 0.99
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
VQ_ARGS = VQArgs()
|
| 53 |
+
|
| 54 |
+
# System prompt for the IRG (interleaved generation-assessment-refinement) task,
|
| 55 |
+
# copied verbatim from the repo's TextProcessor (unified_mogen_cot_v3).
|
| 56 |
+
IRG_SYSTEM_PROMPT = (
|
| 57 |
+
"You are an assistant who helps users understand or generate 3D human "
|
| 58 |
+
"motion representations."
|
| 59 |
+
)
|
| 60 |
+
IRG_USER_TEMPLATE = (
|
| 61 |
+
"Given a text outlining a human motion objective, employ a step-by-step "
|
| 62 |
+
"thought process to realize the motion: (1) analyze the text, providing a "
|
| 63 |
+
"clear explanation of the reasoning to identify essential elements; "
|
| 64 |
+
"(2) conduct several rounds of motion generation and self-assessment until "
|
| 65 |
+
"the motion is satisfactory. Wrap all responses in <think> and </think> "
|
| 66 |
+
"tags, and formulate a plan before each step.\nGoal Text: {caption}"
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
# ----------------------------------------------------------------------------
|
| 70 |
+
# Load model + VQVAE at module scope (ZeroGPU: .to("cuda") is intercepted)
|
| 71 |
+
# ----------------------------------------------------------------------------
|
| 72 |
+
print("Loading tokenizer + base LLM ...")
|
| 73 |
+
tokenizer = AutoTokenizer.from_pretrained(BASE_LLM, token=HF_TOKEN)
|
| 74 |
+
|
| 75 |
+
llm = AutoModelForCausalLM.from_pretrained(
|
| 76 |
+
BASE_LLM,
|
| 77 |
+
torch_dtype=torch.bfloat16,
|
| 78 |
+
attn_implementation="sdpa",
|
| 79 |
+
token=HF_TOKEN,
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# Recreate the exact tokenizer vocabulary the model was trained with:
|
| 83 |
+
# base vocab + <Motion> + </Motion> + <Motion_0> .. <Motion_{NB_CODE-1}>
|
| 84 |
+
NB_TEXT_TOKENS = len(tokenizer)
|
| 85 |
+
tokenizer.add_tokens(["<Motion>", "</Motion>"])
|
| 86 |
+
for i in range(NB_CODE):
|
| 87 |
+
tokenizer.add_tokens([f"<Motion_{i}>"])
|
| 88 |
+
llm.resize_token_embeddings(len(tokenizer))
|
| 89 |
+
|
| 90 |
+
print("Loading Stage-3 checkpoint ...")
|
| 91 |
+
ckpt_path = hf_hub_download(CKPT_REPO, CKPT_FILE, token=HF_TOKEN)
|
| 92 |
+
state = torch.load(ckpt_path, map_location="cpu")
|
| 93 |
+
if isinstance(state, dict) and "state_dict" in state:
|
| 94 |
+
state = state["state_dict"]
|
| 95 |
+
|
| 96 |
+
# The Stage-3 checkpoint is a full fine-tune (LoRA already merged) and stores
|
| 97 |
+
# both the LLM weights (prefixed "llm.") and the VQVAE weights (prefixed "net.").
|
| 98 |
+
llm_sd = {}
|
| 99 |
+
net_sd = {}
|
| 100 |
+
for k, v in state.items():
|
| 101 |
+
if k.startswith("llm."):
|
| 102 |
+
llm_sd[k[len("llm."):]] = v
|
| 103 |
+
elif k.startswith("net."):
|
| 104 |
+
net_sd[k[len("net."):]] = v
|
| 105 |
+
|
| 106 |
+
missing, unexpected = llm.load_state_dict(llm_sd, strict=False)
|
| 107 |
+
print(f"LLM load -> missing={len(missing)} unexpected={len(unexpected)}")
|
| 108 |
+
llm = llm.to(torch.bfloat16).eval()
|
| 109 |
+
|
| 110 |
+
print("Building + loading VQVAE ...")
|
| 111 |
+
net = HumanVQVAE(
|
| 112 |
+
VQ_ARGS,
|
| 113 |
+
nb_code=NB_CODE,
|
| 114 |
+
code_dim=512,
|
| 115 |
+
output_emb_width=512,
|
| 116 |
+
down_t=2,
|
| 117 |
+
stride_t=2,
|
| 118 |
+
width=512,
|
| 119 |
+
depth=3,
|
| 120 |
+
dilation_growth_rate=3,
|
| 121 |
+
activation="relu",
|
| 122 |
+
norm=None,
|
| 123 |
+
)
|
| 124 |
+
vq_missing, vq_unexpected = net.load_state_dict(net_sd, strict=False)
|
| 125 |
+
print(f"VQVAE load -> missing={len(vq_missing)} unexpected={len(vq_unexpected)}")
|
| 126 |
+
net = net.eval().float()
|
| 127 |
+
for p in net.parameters():
|
| 128 |
+
p.requires_grad = False
|
| 129 |
+
|
| 130 |
+
# HumanML3D normalization statistics (263-dim), extracted from the same
|
| 131 |
+
# T2M-GPT extractor bundle the authors use.
|
| 132 |
+
MEAN = np.load(os.path.join(os.path.dirname(__file__), "mean.npy"))
|
| 133 |
+
STD = np.load(os.path.join(os.path.dirname(__file__), "std.npy"))
|
| 134 |
+
|
| 135 |
+
llm.to("cuda")
|
| 136 |
+
net.to("cuda")
|
| 137 |
+
|
| 138 |
+
EOS_ID = tokenizer.eos_token_id
|
| 139 |
+
MOTION_ID_START = len(tokenizer) - (NB_CODE + 2) # first <Motion_i> token id
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# ----------------------------------------------------------------------------
|
| 143 |
+
# Motion-token extraction helpers (ported from mllm_single_lora.py)
|
| 144 |
+
# ----------------------------------------------------------------------------
|
| 145 |
+
def _find_seq(tokens_list, target):
|
| 146 |
+
for i in range(len(tokens_list) - len(target) + 1):
|
| 147 |
+
if tokens_list[i:i + len(target)] == target:
|
| 148 |
+
return i
|
| 149 |
+
return -1
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _extract_answer_scores(scores, ids_list):
|
| 153 |
+
"""Return the score rows and matching ids inside <answer>..</answer>,
|
| 154 |
+
else fall back to the last <Motion>..</Motion> span, else all."""
|
| 155 |
+
ans_start = tokenizer.encode("<answer>", add_special_tokens=False)
|
| 156 |
+
ans_end = tokenizer.encode("</answer>", add_special_tokens=False)
|
| 157 |
+
s = _find_seq(ids_list, ans_start)
|
| 158 |
+
if s != -1:
|
| 159 |
+
s2 = s + len(ans_start)
|
| 160 |
+
e = _find_seq(ids_list[s2:], ans_end)
|
| 161 |
+
if e != -1:
|
| 162 |
+
e += s2
|
| 163 |
+
return scores[s2:e], ids_list[s2:e]
|
| 164 |
+
return scores, ids_list
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _tokens_from_scores_and_ids(scores, ids_list):
|
| 168 |
+
"""Prefer already-decoded motion token ids that fall in the motion range;
|
| 169 |
+
otherwise argmax over the motion-logit slice. Mirrors the repo logic."""
|
| 170 |
+
motion_logits = scores[:, -(NB_CODE + 2):]
|
| 171 |
+
argmax_ids = torch.argmax(motion_logits, dim=-1)
|
| 172 |
+
out = []
|
| 173 |
+
for i, tid in enumerate(ids_list):
|
| 174 |
+
if MOTION_ID_START <= tid < len(tokenizer):
|
| 175 |
+
out.append(tid - MOTION_ID_START)
|
| 176 |
+
else:
|
| 177 |
+
out.append(int(argmax_ids[i].item()))
|
| 178 |
+
motion_tokens = torch.tensor(out, dtype=torch.long)
|
| 179 |
+
if 1 in motion_tokens.tolist():
|
| 180 |
+
motion_tokens = motion_tokens[:motion_tokens.tolist().index(1)]
|
| 181 |
+
if 0 in motion_tokens.tolist():
|
| 182 |
+
motion_tokens = motion_tokens[motion_tokens.tolist().index(0) + 1:]
|
| 183 |
+
motion_tokens = torch.clamp(motion_tokens - 2, min=0)
|
| 184 |
+
return motion_tokens
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _extract_last_motion_span_ids(ids_list):
|
| 188 |
+
st_id = tokenizer.encode("<Motion>", add_special_tokens=False)[0]
|
| 189 |
+
ed_id = tokenizer.encode("</Motion>", add_special_tokens=False)[0]
|
| 190 |
+
starts = [i for i, t in enumerate(ids_list) if t == st_id]
|
| 191 |
+
ends = [i for i, t in enumerate(ids_list) if t == ed_id]
|
| 192 |
+
if not starts or not ends:
|
| 193 |
+
return None
|
| 194 |
+
# last valid <Motion> ... </Motion> pair
|
| 195 |
+
for st in reversed(starts):
|
| 196 |
+
later_ends = [e for e in ends if e > st]
|
| 197 |
+
if later_ends:
|
| 198 |
+
return st, later_ends[0]
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def build_prompt(caption):
|
| 203 |
+
caption = (caption or "").strip()
|
| 204 |
+
user_prompt = IRG_USER_TEMPLATE.format(caption=caption)
|
| 205 |
+
text = IRG_SYSTEM_PROMPT + "\n\n" + "User: " + user_prompt + "\n\n" + " Response:"
|
| 206 |
+
return text
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def decode_motion_to_joints(motion_tokens):
|
| 210 |
+
"""VQVAE token ids -> 263-dim features -> denormalize -> 22-joint xyz."""
|
| 211 |
+
motion_tokens = motion_tokens.to("cuda").long()
|
| 212 |
+
feats = net.forward_decoder(motion_tokens) # (1, T, 263)
|
| 213 |
+
feats = feats.detach().cpu().numpy()[0]
|
| 214 |
+
feats = MEAN + feats * STD # denormalize
|
| 215 |
+
joints = recover_from_ric(torch.from_numpy(feats).float(), JOINTS_NUM)
|
| 216 |
+
return joints.numpy() # (T, 22, 3)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def render_video(joints, title):
|
| 220 |
+
out_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
|
| 221 |
+
plot_3d_motion(out_path, t2m_kinematic_chain, joints, title=title, fps=FPS, radius=4)
|
| 222 |
+
return out_path
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
# ----------------------------------------------------------------------------
|
| 226 |
+
# Inference
|
| 227 |
+
# ----------------------------------------------------------------------------
|
| 228 |
+
def _duration(caption, reasoning, max_new_tokens, *a, **k):
|
| 229 |
+
base = 55
|
| 230 |
+
if reasoning:
|
| 231 |
+
base = 110
|
| 232 |
+
return min(160, base + int(int(max_new_tokens) / 20))
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
@spaces.GPU(duration=_duration)
|
| 236 |
+
def generate(caption, reasoning=True, max_new_tokens=1300, seed=0,
|
| 237 |
+
progress=gr.Progress(track_tqdm=True)):
|
| 238 |
+
if not caption or not caption.strip():
|
| 239 |
+
raise gr.Error("Please enter a motion description.")
|
| 240 |
+
|
| 241 |
+
if seed and int(seed) > 0:
|
| 242 |
+
torch.manual_seed(int(seed))
|
| 243 |
+
|
| 244 |
+
do_sample = bool(seed and int(seed) > 0)
|
| 245 |
+
max_new_tokens = int(max_new_tokens)
|
| 246 |
+
|
| 247 |
+
prompt = build_prompt(caption)
|
| 248 |
+
enc = tokenizer(prompt, return_tensors="pt").to("cuda")
|
| 249 |
+
input_len = enc.input_ids.shape[1]
|
| 250 |
+
|
| 251 |
+
if reasoning:
|
| 252 |
+
max_len = input_len + max_new_tokens
|
| 253 |
+
else:
|
| 254 |
+
# skip the interleaved reasoning; ask for a compact motion answer
|
| 255 |
+
max_len = input_len + 260
|
| 256 |
+
|
| 257 |
+
gen_kwargs = dict(
|
| 258 |
+
max_length=max_len,
|
| 259 |
+
do_sample=do_sample,
|
| 260 |
+
return_dict_in_generate=True,
|
| 261 |
+
output_scores=True,
|
| 262 |
+
use_cache=True,
|
| 263 |
+
)
|
| 264 |
+
if do_sample:
|
| 265 |
+
gen_kwargs["temperature"] = 1.0
|
| 266 |
+
|
| 267 |
+
with torch.inference_mode():
|
| 268 |
+
outputs = llm.generate(enc.input_ids, attention_mask=enc.attention_mask, **gen_kwargs)
|
| 269 |
+
|
| 270 |
+
gen_ids = outputs.sequences[0, input_len:]
|
| 271 |
+
# scores are per generated step (tuple len == n_new); stack into (n_new, vocab)
|
| 272 |
+
scores = torch.stack(outputs.scores)[:, 0, :]
|
| 273 |
+
|
| 274 |
+
# truncate at EOS
|
| 275 |
+
gen_ids_list = gen_ids.tolist()
|
| 276 |
+
if EOS_ID in gen_ids_list:
|
| 277 |
+
cut = gen_ids_list.index(EOS_ID) + 1
|
| 278 |
+
gen_ids_list = gen_ids_list[:cut]
|
| 279 |
+
scores = scores[:cut]
|
| 280 |
+
|
| 281 |
+
full_text = tokenizer.decode(gen_ids_list, skip_special_tokens=False)
|
| 282 |
+
|
| 283 |
+
# Prefer <answer>..</answer>; fall back to last <Motion>..</Motion> span.
|
| 284 |
+
ans_scores, ans_ids = _extract_answer_scores(scores, gen_ids_list)
|
| 285 |
+
if ans_ids is gen_ids_list or len(ans_ids) == len(gen_ids_list):
|
| 286 |
+
span = _extract_last_motion_span_ids(gen_ids_list)
|
| 287 |
+
if span is not None:
|
| 288 |
+
st, ed = span
|
| 289 |
+
ans_scores = scores[st + 1:ed]
|
| 290 |
+
ans_ids = gen_ids_list[st + 1:ed]
|
| 291 |
+
|
| 292 |
+
motion_tokens = _tokens_from_scores_and_ids(ans_scores, ans_ids)
|
| 293 |
+
|
| 294 |
+
if motion_tokens.numel() == 0:
|
| 295 |
+
raise gr.Error(
|
| 296 |
+
"The model did not produce a valid motion for this prompt. "
|
| 297 |
+
"Try rephrasing, or toggle the reasoning option."
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
joints = decode_motion_to_joints(motion_tokens)
|
| 301 |
+
title = caption.strip()
|
| 302 |
+
if len(title) > 60:
|
| 303 |
+
title = title[:57] + "..."
|
| 304 |
+
video = render_video(joints, title)
|
| 305 |
+
|
| 306 |
+
# Build a readable reasoning trace (strip the internal [plan]/[tag] markup).
|
| 307 |
+
trace = full_text
|
| 308 |
+
trace = trace.replace("<eos>", "").replace("<pad>", "")
|
| 309 |
+
n_rounds = len(re.findall(r"\[generate\]", trace))
|
| 310 |
+
info = (
|
| 311 |
+
f"Frames: {joints.shape[0]} | Motion tokens: {motion_tokens.numel()}"
|
| 312 |
+
f" | Internal generate/refine rounds: {max(n_rounds, 1)}"
|
| 313 |
+
)
|
| 314 |
+
return video, info, trace.strip()
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
# ----------------------------------------------------------------------------
|
| 318 |
+
# UI
|
| 319 |
+
# ----------------------------------------------------------------------------
|
| 320 |
+
DESCRIPTION = """
|
| 321 |
+
# 🕺 IRG-MotionLLM — Text-to-3D-Motion
|
| 322 |
+
|
| 323 |
+
Generate 3D human motion from a text description with
|
| 324 |
+
**[IRG-MotionLLM](https://huggingface.co/papers/2512.10730)** — an LLM
|
| 325 |
+
(Gemma-2-2B) that *interleaves* motion generation, self-assessment and
|
| 326 |
+
refinement, then decodes discrete motion tokens through a VQ-VAE into a
|
| 327 |
+
HumanML3D skeleton animation.
|
| 328 |
+
"""
|
| 329 |
+
|
| 330 |
+
EXAMPLES = [
|
| 331 |
+
["a person walks forward, then turns around and walks back.", True, 1300, 0],
|
| 332 |
+
["a man is doing cartwheels.", True, 1300, 0],
|
| 333 |
+
["a person jumps up high with both hands raised.", True, 1300, 0],
|
| 334 |
+
["someone sits down on a chair and crosses their legs.", True, 1300, 0],
|
| 335 |
+
["a person raises their right hand and waves.", True, 1300, 0],
|
| 336 |
+
]
|
| 337 |
+
|
| 338 |
+
with gr.Blocks(theme=gr.themes.Citrus()) as demo:
|
| 339 |
+
gr.Markdown(DESCRIPTION)
|
| 340 |
+
|
| 341 |
+
with gr.Row():
|
| 342 |
+
with gr.Column(scale=1):
|
| 343 |
+
caption = gr.Textbox(
|
| 344 |
+
label="Motion description",
|
| 345 |
+
placeholder="e.g. a person walks forward, then turns around and walks back.",
|
| 346 |
+
lines=2,
|
| 347 |
+
)
|
| 348 |
+
run_btn = gr.Button("Generate motion", variant="primary")
|
| 349 |
+
with gr.Accordion("Advanced options", open=False):
|
| 350 |
+
reasoning = gr.Checkbox(
|
| 351 |
+
value=True,
|
| 352 |
+
label="Interleaved reasoning (generate → assess → refine)",
|
| 353 |
+
info="Uses the full IRG chain-of-thought. Turn off for a faster, direct generation.",
|
| 354 |
+
)
|
| 355 |
+
max_new_tokens = gr.Slider(
|
| 356 |
+
400, 2000, value=1300, step=50,
|
| 357 |
+
label="Max new tokens (reasoning budget)",
|
| 358 |
+
)
|
| 359 |
+
seed = gr.Slider(
|
| 360 |
+
0, 100000, value=0, step=1,
|
| 361 |
+
label="Seed (0 = greedy / deterministic, >0 = sampling)",
|
| 362 |
+
)
|
| 363 |
+
with gr.Column(scale=1):
|
| 364 |
+
video_out = gr.Video(label="Generated motion", autoplay=True)
|
| 365 |
+
info_out = gr.Textbox(label="Summary", lines=2)
|
| 366 |
+
|
| 367 |
+
with gr.Accordion("Model reasoning trace", open=False):
|
| 368 |
+
trace_out = gr.Textbox(label="Interleaved generation / assessment / refinement", lines=10)
|
| 369 |
+
|
| 370 |
+
gr.Examples(
|
| 371 |
+
examples=EXAMPLES,
|
| 372 |
+
inputs=[caption, reasoning, max_new_tokens, seed],
|
| 373 |
+
outputs=[video_out, info_out, trace_out],
|
| 374 |
+
fn=generate,
|
| 375 |
+
cache_examples=True,
|
| 376 |
+
cache_mode="lazy",
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
run_btn.click(
|
| 380 |
+
fn=generate,
|
| 381 |
+
inputs=[caption, reasoning, max_new_tokens, seed],
|
| 382 |
+
outputs=[video_out, info_out, trace_out],
|
| 383 |
+
)
|
| 384 |
+
caption.submit(
|
| 385 |
+
fn=generate,
|
| 386 |
+
inputs=[caption, reasoning, max_new_tokens, seed],
|
| 387 |
+
outputs=[video_out, info_out, trace_out],
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
if __name__ == "__main__":
|
| 391 |
+
demo.queue().launch()
|
mean.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0bdb5ba69a3a9e34d71990db15bc535ebc024c8d95ddb5574196f96058faa7d3
|
| 3 |
+
size 2232
|
motionvq/__init__.py
ADDED
|
File without changes
|
motionvq/encdec.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
from .resnet import Resnet1D
|
| 3 |
+
|
| 4 |
+
class Encoder(nn.Module):
|
| 5 |
+
def __init__(self,
|
| 6 |
+
input_emb_width = 3,
|
| 7 |
+
output_emb_width = 512,
|
| 8 |
+
down_t = 3,
|
| 9 |
+
stride_t = 2,
|
| 10 |
+
width = 512,
|
| 11 |
+
depth = 3,
|
| 12 |
+
dilation_growth_rate = 3,
|
| 13 |
+
activation='relu',
|
| 14 |
+
norm=None):
|
| 15 |
+
super().__init__()
|
| 16 |
+
|
| 17 |
+
blocks = []
|
| 18 |
+
filter_t, pad_t = stride_t * 2, stride_t // 2
|
| 19 |
+
blocks.append(nn.Conv1d(input_emb_width, width, 3, 1, 1))
|
| 20 |
+
blocks.append(nn.ReLU())
|
| 21 |
+
|
| 22 |
+
for i in range(down_t):
|
| 23 |
+
input_dim = width
|
| 24 |
+
block = nn.Sequential(
|
| 25 |
+
nn.Conv1d(input_dim, width, filter_t, stride_t, pad_t),
|
| 26 |
+
Resnet1D(width, depth, dilation_growth_rate, activation=activation, norm=norm),
|
| 27 |
+
)
|
| 28 |
+
blocks.append(block)
|
| 29 |
+
blocks.append(nn.Conv1d(width, output_emb_width, 3, 1, 1))
|
| 30 |
+
self.model = nn.Sequential(*blocks)
|
| 31 |
+
|
| 32 |
+
def forward(self, x):
|
| 33 |
+
return self.model(x)
|
| 34 |
+
|
| 35 |
+
class Decoder(nn.Module):
|
| 36 |
+
def __init__(self,
|
| 37 |
+
input_emb_width = 3,
|
| 38 |
+
output_emb_width = 512,
|
| 39 |
+
down_t = 3,
|
| 40 |
+
stride_t = 2,
|
| 41 |
+
width = 512,
|
| 42 |
+
depth = 3,
|
| 43 |
+
dilation_growth_rate = 3,
|
| 44 |
+
activation='relu',
|
| 45 |
+
norm=None):
|
| 46 |
+
super().__init__()
|
| 47 |
+
blocks = []
|
| 48 |
+
|
| 49 |
+
filter_t, pad_t = stride_t * 2, stride_t // 2
|
| 50 |
+
blocks.append(nn.Conv1d(output_emb_width, width, 3, 1, 1))
|
| 51 |
+
blocks.append(nn.ReLU())
|
| 52 |
+
for i in range(down_t):
|
| 53 |
+
out_dim = width
|
| 54 |
+
block = nn.Sequential(
|
| 55 |
+
Resnet1D(width, depth, dilation_growth_rate, reverse_dilation=True, activation=activation, norm=norm),
|
| 56 |
+
nn.Upsample(scale_factor=2, mode='nearest'),
|
| 57 |
+
nn.Conv1d(width, out_dim, 3, 1, 1)
|
| 58 |
+
)
|
| 59 |
+
blocks.append(block)
|
| 60 |
+
blocks.append(nn.Conv1d(width, width, 3, 1, 1))
|
| 61 |
+
blocks.append(nn.ReLU())
|
| 62 |
+
blocks.append(nn.Conv1d(width, input_emb_width, 3, 1, 1))
|
| 63 |
+
self.model = nn.Sequential(*blocks)
|
| 64 |
+
|
| 65 |
+
def forward(self, x):
|
| 66 |
+
return self.model(x)
|
| 67 |
+
|
motionvq/motion_utils.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
import shutil as _shutil
|
| 4 |
+
import matplotlib
|
| 5 |
+
matplotlib.use('Agg')
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
_ffmpeg = _shutil.which('ffmpeg')
|
| 8 |
+
if _ffmpeg:
|
| 9 |
+
plt.rcParams['animation.ffmpeg_path'] = _ffmpeg
|
| 10 |
+
from mpl_toolkits.mplot3d import Axes3D
|
| 11 |
+
from matplotlib.animation import FuncAnimation, PillowWriter
|
| 12 |
+
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
| 13 |
+
import mpl_toolkits.mplot3d.axes3d as p3
|
| 14 |
+
|
| 15 |
+
def qrot(q, v):
|
| 16 |
+
"""
|
| 17 |
+
Rotate vector(s) v about the rotation described by quaternion(s) q.
|
| 18 |
+
Expects a tensor of shape (*, 4) for q and a tensor of shape (*, 3) for v,
|
| 19 |
+
where * denotes any number of dimensions.
|
| 20 |
+
Returns a tensor of shape (*, 3).
|
| 21 |
+
"""
|
| 22 |
+
assert q.shape[-1] == 4
|
| 23 |
+
assert v.shape[-1] == 3
|
| 24 |
+
assert q.shape[:-1] == v.shape[:-1]
|
| 25 |
+
|
| 26 |
+
original_shape = list(v.shape)
|
| 27 |
+
# print(q.shape)
|
| 28 |
+
q = q.contiguous().view(-1, 4)
|
| 29 |
+
v = v.contiguous().view(-1, 3)
|
| 30 |
+
|
| 31 |
+
qvec = q[:, 1:]
|
| 32 |
+
uv = torch.cross(qvec, v, dim=1)
|
| 33 |
+
uuv = torch.cross(qvec, uv, dim=1)
|
| 34 |
+
return (v + 2 * (q[:, :1] * uv + uuv)).view(original_shape)
|
| 35 |
+
|
| 36 |
+
def qinv(q):
|
| 37 |
+
assert q.shape[-1] == 4, 'q must be a tensor of shape (*, 4)'
|
| 38 |
+
mask = torch.ones_like(q)
|
| 39 |
+
mask[..., 1:] = -mask[..., 1:]
|
| 40 |
+
return q * mask
|
| 41 |
+
|
| 42 |
+
def recover_root_rot_pos(data):
|
| 43 |
+
rot_vel = data[..., 0]
|
| 44 |
+
r_rot_ang = torch.zeros_like(rot_vel).to(data.device)
|
| 45 |
+
'''Get Y-axis rotation from rotation velocity'''
|
| 46 |
+
r_rot_ang[..., 1:] = rot_vel[..., :-1]
|
| 47 |
+
r_rot_ang = torch.cumsum(r_rot_ang, dim=-1)
|
| 48 |
+
|
| 49 |
+
r_rot_quat = torch.zeros(data.shape[:-1] + (4,)).to(data.device)
|
| 50 |
+
r_rot_quat[..., 0] = torch.cos(r_rot_ang)
|
| 51 |
+
r_rot_quat[..., 2] = torch.sin(r_rot_ang)
|
| 52 |
+
|
| 53 |
+
r_pos = torch.zeros(data.shape[:-1] + (3,)).to(data.device)
|
| 54 |
+
r_pos[..., 1:, [0, 2]] = data[..., :-1, 1:3]
|
| 55 |
+
'''Add Y-axis rotation to root position'''
|
| 56 |
+
r_pos = qrot(qinv(r_rot_quat), r_pos)
|
| 57 |
+
|
| 58 |
+
r_pos = torch.cumsum(r_pos, dim=-2)
|
| 59 |
+
|
| 60 |
+
r_pos[..., 1] = data[..., 3]
|
| 61 |
+
return r_rot_quat, r_pos
|
| 62 |
+
|
| 63 |
+
def recover_from_ric(data, joints_num):
|
| 64 |
+
r_rot_quat, r_pos = recover_root_rot_pos(data)
|
| 65 |
+
positions = data[..., 4:(joints_num - 1) * 3 + 4]
|
| 66 |
+
positions = positions.view(positions.shape[:-1] + (-1, 3))
|
| 67 |
+
|
| 68 |
+
'''Add Y-axis rotation to local joints'''
|
| 69 |
+
positions = qrot(qinv(r_rot_quat[..., None, :]).expand(positions.shape[:-1] + (4,)), positions)
|
| 70 |
+
|
| 71 |
+
'''Add root XZ to joints'''
|
| 72 |
+
positions[..., 0] += r_pos[..., 0:1]
|
| 73 |
+
positions[..., 2] += r_pos[..., 2:3]
|
| 74 |
+
|
| 75 |
+
'''Concate root and joints'''
|
| 76 |
+
positions = torch.cat([r_pos.unsqueeze(-2), positions], dim=-2)
|
| 77 |
+
|
| 78 |
+
return positions
|
| 79 |
+
|
| 80 |
+
def plot_3d_motion(save_path, kinematic_tree, joints, title, figsize=(10, 10), fps=120, radius=4):
|
| 81 |
+
# matplotlib.use('Agg')
|
| 82 |
+
|
| 83 |
+
title_sp = title.split(' ')
|
| 84 |
+
if len(title_sp) > 10:
|
| 85 |
+
title = '\n'.join([' '.join(title_sp[:10]), ' '.join(title_sp[10:])])
|
| 86 |
+
def init():
|
| 87 |
+
# ax.set_xlim3d([-radius / 2, radius / 2])
|
| 88 |
+
# ax.set_ylim3d([0, radius])
|
| 89 |
+
# ax.set_zlim3d([0, radius])
|
| 90 |
+
# # print(title)
|
| 91 |
+
# fig.suptitle(title, fontsize=20)
|
| 92 |
+
# ax.grid(b=False)
|
| 93 |
+
|
| 94 |
+
nb_joints = joints.shape[1]
|
| 95 |
+
limits = 1000 if nb_joints == 21 else 2
|
| 96 |
+
ax.set_xlim(-limits, limits)
|
| 97 |
+
ax.set_ylim(-limits, limits)
|
| 98 |
+
ax.set_zlim(0, limits)
|
| 99 |
+
fig.suptitle(title, fontsize=20)
|
| 100 |
+
ax.grid(False)
|
| 101 |
+
|
| 102 |
+
def plot_xzPlane(minx, maxx, miny, minz, maxz):
|
| 103 |
+
## Plot a plane XZ
|
| 104 |
+
verts = [
|
| 105 |
+
[minx, miny, minz],
|
| 106 |
+
[minx, miny, maxz],
|
| 107 |
+
[maxx, miny, maxz],
|
| 108 |
+
[maxx, miny, minz]
|
| 109 |
+
]
|
| 110 |
+
xz_plane = Poly3DCollection([verts])
|
| 111 |
+
xz_plane.set_facecolor((0.5, 0.5, 0.5, 0.5))
|
| 112 |
+
ax.add_collection3d(xz_plane)
|
| 113 |
+
|
| 114 |
+
# return ax
|
| 115 |
+
|
| 116 |
+
# (seq_len, joints_num, 3)
|
| 117 |
+
data = joints.copy().reshape(len(joints), -1, 3)
|
| 118 |
+
fig = plt.figure(figsize=figsize)
|
| 119 |
+
# ax = p3.Axes3D(fig)
|
| 120 |
+
ax = fig.add_subplot(111, projection='3d')
|
| 121 |
+
init()
|
| 122 |
+
MINS = data.min(axis=0).min(axis=0)
|
| 123 |
+
MAXS = data.max(axis=0).max(axis=0)
|
| 124 |
+
colors = ['red', 'blue', 'black', 'red', 'blue',
|
| 125 |
+
'darkblue', 'darkblue', 'darkblue', 'darkblue', 'darkblue',
|
| 126 |
+
'darkred', 'darkred','darkred','darkred','darkred']
|
| 127 |
+
frame_number = data.shape[0]
|
| 128 |
+
# print(data.shape)
|
| 129 |
+
|
| 130 |
+
height_offset = MINS[1]
|
| 131 |
+
data[:, :, 1] -= height_offset
|
| 132 |
+
trajec = data[:, 0, [0, 2]]
|
| 133 |
+
|
| 134 |
+
data[..., 0] -= data[:, 0:1, 0]
|
| 135 |
+
data[..., 2] -= data[:, 0:1, 2]
|
| 136 |
+
|
| 137 |
+
# print(trajec.shape)
|
| 138 |
+
|
| 139 |
+
def update(index):
|
| 140 |
+
# print(index)
|
| 141 |
+
# ax.lines = []
|
| 142 |
+
# ax.collections = []
|
| 143 |
+
for line in list(ax.lines):
|
| 144 |
+
line.remove()
|
| 145 |
+
for collection in list(ax.collections):
|
| 146 |
+
collection.remove()
|
| 147 |
+
ax.view_init(elev=120, azim=-90)
|
| 148 |
+
try:
|
| 149 |
+
ax.dist = 7.5
|
| 150 |
+
except Exception:
|
| 151 |
+
pass
|
| 152 |
+
# ax =
|
| 153 |
+
plot_xzPlane(MINS[0]-trajec[index, 0], MAXS[0]-trajec[index, 0], 0, MINS[2]-trajec[index, 1], MAXS[2]-trajec[index, 1])
|
| 154 |
+
# ax.scatter(data[index, :22, 0], data[index, :22, 1], data[index, :22, 2], color='black', s=3)
|
| 155 |
+
|
| 156 |
+
if index > 1:
|
| 157 |
+
ax.plot3D(trajec[:index, 0]-trajec[index, 0], np.zeros_like(trajec[:index, 0]), trajec[:index, 1]-trajec[index, 1], linewidth=1.0,
|
| 158 |
+
color='blue')
|
| 159 |
+
# ax = plot_xzPlane(ax, MINS[0], MAXS[0], 0, MINS[2], MAXS[2])
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
for i, (chain, color) in enumerate(zip(kinematic_tree, colors)):
|
| 163 |
+
# print(color)
|
| 164 |
+
if i < 5:
|
| 165 |
+
linewidth = 4.0
|
| 166 |
+
else:
|
| 167 |
+
linewidth = 2.0
|
| 168 |
+
ax.plot3D(data[index, chain, 0], data[index, chain, 1], data[index, chain, 2], linewidth=linewidth, color=color)
|
| 169 |
+
# print(trajec[:index, 0].shape)
|
| 170 |
+
|
| 171 |
+
plt.axis('off')
|
| 172 |
+
ax.set_xticklabels([])
|
| 173 |
+
ax.set_yticklabels([])
|
| 174 |
+
ax.set_zticklabels([])
|
| 175 |
+
|
| 176 |
+
ani = FuncAnimation(fig, update, frames=frame_number, interval=1000/fps, repeat=False)
|
| 177 |
+
|
| 178 |
+
ani.save(save_path, fps=fps)
|
| 179 |
+
plt.close()
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def plot_3d_motion_ipynb(kinematic_tree, joints, title, figsize=(10, 10), fps=120, radius=4):
|
| 183 |
+
"""
|
| 184 |
+
在 Jupyter Notebook 中显示 3D 人体运动动画(不保存文件)
|
| 185 |
+
"""
|
| 186 |
+
title_sp = title.split(' ')
|
| 187 |
+
if len(title_sp) > 10:
|
| 188 |
+
title = '\n'.join([' '.join(title_sp[:10]), ' '.join(title_sp[10:])])
|
| 189 |
+
def init():
|
| 190 |
+
# ax.set_xlim3d([-radius / 2, radius / 2])
|
| 191 |
+
# ax.set_ylim3d([0, radius])
|
| 192 |
+
# ax.set_zlim3d([0, radius])
|
| 193 |
+
# # print(title)
|
| 194 |
+
# fig.suptitle(title, fontsize=20)
|
| 195 |
+
# ax.grid(b=False)
|
| 196 |
+
|
| 197 |
+
nb_joints = joints.shape[1]
|
| 198 |
+
limits = 1000 if nb_joints == 21 else 2
|
| 199 |
+
ax.set_xlim(-limits, limits)
|
| 200 |
+
ax.set_ylim(-limits, limits)
|
| 201 |
+
ax.set_zlim(0, limits)
|
| 202 |
+
fig.suptitle(title, fontsize=20)
|
| 203 |
+
ax.grid(b=False)
|
| 204 |
+
|
| 205 |
+
def plot_xzPlane(minx, maxx, miny, minz, maxz):
|
| 206 |
+
## Plot a plane XZ
|
| 207 |
+
verts = [
|
| 208 |
+
[minx, miny, minz],
|
| 209 |
+
[minx, miny, maxz],
|
| 210 |
+
[maxx, miny, maxz],
|
| 211 |
+
[maxx, miny, minz]
|
| 212 |
+
]
|
| 213 |
+
xz_plane = Poly3DCollection([verts])
|
| 214 |
+
xz_plane.set_facecolor((0.5, 0.5, 0.5, 0.5))
|
| 215 |
+
ax.add_collection3d(xz_plane)
|
| 216 |
+
|
| 217 |
+
# return ax
|
| 218 |
+
|
| 219 |
+
# (seq_len, joints_num, 3)
|
| 220 |
+
data = joints.copy().reshape(len(joints), -1, 3)
|
| 221 |
+
fig = plt.figure(figsize=figsize)
|
| 222 |
+
# ax = p3.Axes3D(fig)
|
| 223 |
+
ax = fig.add_subplot(111, projection='3d')
|
| 224 |
+
init()
|
| 225 |
+
MINS = data.min(axis=0).min(axis=0)
|
| 226 |
+
MAXS = data.max(axis=0).max(axis=0)
|
| 227 |
+
colors = ['red', 'blue', 'black', 'red', 'blue',
|
| 228 |
+
'darkblue', 'darkblue', 'darkblue', 'darkblue', 'darkblue',
|
| 229 |
+
'darkred', 'darkred','darkred','darkred','darkred']
|
| 230 |
+
frame_number = data.shape[0]
|
| 231 |
+
# print(data.shape)
|
| 232 |
+
|
| 233 |
+
height_offset = MINS[1]
|
| 234 |
+
data[:, :, 1] -= height_offset
|
| 235 |
+
trajec = data[:, 0, [0, 2]]
|
| 236 |
+
|
| 237 |
+
data[..., 0] -= data[:, 0:1, 0]
|
| 238 |
+
data[..., 2] -= data[:, 0:1, 2]
|
| 239 |
+
|
| 240 |
+
# print(trajec.shape)
|
| 241 |
+
|
| 242 |
+
def update(index):
|
| 243 |
+
# print(index)
|
| 244 |
+
# ax.lines = []
|
| 245 |
+
# ax.collections = []
|
| 246 |
+
for line in ax.lines:
|
| 247 |
+
line.remove()
|
| 248 |
+
for collection in ax.collections:
|
| 249 |
+
collection.remove()
|
| 250 |
+
ax.view_init(elev=120, azim=-90)
|
| 251 |
+
ax.dist = 7.5
|
| 252 |
+
# ax =
|
| 253 |
+
plot_xzPlane(MINS[0]-trajec[index, 0], MAXS[0]-trajec[index, 0], 0, MINS[2]-trajec[index, 1], MAXS[2]-trajec[index, 1])
|
| 254 |
+
# ax.scatter(data[index, :22, 0], data[index, :22, 1], data[index, :22, 2], color='black', s=3)
|
| 255 |
+
|
| 256 |
+
if index > 1:
|
| 257 |
+
ax.plot3D(trajec[:index, 0]-trajec[index, 0], np.zeros_like(trajec[:index, 0]), trajec[:index, 1]-trajec[index, 1], linewidth=1.0,
|
| 258 |
+
color='blue')
|
| 259 |
+
# ax = plot_xzPlane(ax, MINS[0], MAXS[0], 0, MINS[2], MAXS[2])
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
for i, (chain, color) in enumerate(zip(kinematic_tree, colors)):
|
| 263 |
+
# print(color)
|
| 264 |
+
if i < 5:
|
| 265 |
+
linewidth = 4.0
|
| 266 |
+
else:
|
| 267 |
+
linewidth = 2.0
|
| 268 |
+
ax.plot3D(data[index, chain, 0], data[index, chain, 1], data[index, chain, 2], linewidth=linewidth, color=color)
|
| 269 |
+
# print(trajec[:index, 0].shape)
|
| 270 |
+
|
| 271 |
+
plt.axis('off')
|
| 272 |
+
ax.set_xticklabels([])
|
| 273 |
+
ax.set_yticklabels([])
|
| 274 |
+
ax.set_zticklabels([])
|
| 275 |
+
|
| 276 |
+
ani = FuncAnimation(fig, update, frames=frame_number, interval=1000/fps, repeat=False)
|
| 277 |
+
plt.show()
|
motionvq/paramUtil.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
# Define a kinematic tree for the skeletal struture
|
| 4 |
+
kit_kinematic_chain = [[0, 11, 12, 13, 14, 15], [0, 16, 17, 18, 19, 20], [0, 1, 2, 3, 4], [3, 5, 6, 7], [3, 8, 9, 10]]
|
| 5 |
+
|
| 6 |
+
kit_raw_offsets = np.array(
|
| 7 |
+
[
|
| 8 |
+
[0, 0, 0],
|
| 9 |
+
[0, 1, 0],
|
| 10 |
+
[0, 1, 0],
|
| 11 |
+
[0, 1, 0],
|
| 12 |
+
[0, 1, 0],
|
| 13 |
+
[1, 0, 0],
|
| 14 |
+
[0, -1, 0],
|
| 15 |
+
[0, -1, 0],
|
| 16 |
+
[-1, 0, 0],
|
| 17 |
+
[0, -1, 0],
|
| 18 |
+
[0, -1, 0],
|
| 19 |
+
[1, 0, 0],
|
| 20 |
+
[0, -1, 0],
|
| 21 |
+
[0, -1, 0],
|
| 22 |
+
[0, 0, 1],
|
| 23 |
+
[0, 0, 1],
|
| 24 |
+
[-1, 0, 0],
|
| 25 |
+
[0, -1, 0],
|
| 26 |
+
[0, -1, 0],
|
| 27 |
+
[0, 0, 1],
|
| 28 |
+
[0, 0, 1]
|
| 29 |
+
]
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
t2m_raw_offsets = np.array([[0,0,0],
|
| 33 |
+
[1,0,0],
|
| 34 |
+
[-1,0,0],
|
| 35 |
+
[0,1,0],
|
| 36 |
+
[0,-1,0],
|
| 37 |
+
[0,-1,0],
|
| 38 |
+
[0,1,0],
|
| 39 |
+
[0,-1,0],
|
| 40 |
+
[0,-1,0],
|
| 41 |
+
[0,1,0],
|
| 42 |
+
[0,0,1],
|
| 43 |
+
[0,0,1],
|
| 44 |
+
[0,1,0],
|
| 45 |
+
[1,0,0],
|
| 46 |
+
[-1,0,0],
|
| 47 |
+
[0,0,1],
|
| 48 |
+
[0,-1,0],
|
| 49 |
+
[0,-1,0],
|
| 50 |
+
[0,-1,0],
|
| 51 |
+
[0,-1,0],
|
| 52 |
+
[0,-1,0],
|
| 53 |
+
[0,-1,0]])
|
| 54 |
+
|
| 55 |
+
t2m_kinematic_chain = [[0, 2, 5, 8, 11], [0, 1, 4, 7, 10], [0, 3, 6, 9, 12, 15], [9, 14, 17, 19, 21], [9, 13, 16, 18, 20]]
|
| 56 |
+
t2m_left_hand_chain = [[20, 22, 23, 24], [20, 34, 35, 36], [20, 25, 26, 27], [20, 31, 32, 33], [20, 28, 29, 30]]
|
| 57 |
+
t2m_right_hand_chain = [[21, 43, 44, 45], [21, 46, 47, 48], [21, 40, 41, 42], [21, 37, 38, 39], [21, 49, 50, 51]]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
kit_tgt_skel_id = '03950'
|
| 61 |
+
|
| 62 |
+
t2m_tgt_skel_id = '000021'
|
| 63 |
+
|
motionvq/quantize_cnn.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
class QuantizeEMAReset(nn.Module):
|
| 7 |
+
def __init__(self, nb_code, code_dim, args):
|
| 8 |
+
super().__init__()
|
| 9 |
+
self.nb_code = nb_code
|
| 10 |
+
self.code_dim = code_dim
|
| 11 |
+
self.mu = args.mu
|
| 12 |
+
self.reset_codebook()
|
| 13 |
+
|
| 14 |
+
def reset_codebook(self):
|
| 15 |
+
self.init = False
|
| 16 |
+
self.code_sum = None
|
| 17 |
+
self.code_count = None
|
| 18 |
+
# 修复:不强制指定设备,让模型自动处理数据类型和设备
|
| 19 |
+
self.register_buffer('codebook', torch.zeros(self.nb_code, self.code_dim))
|
| 20 |
+
|
| 21 |
+
def _tile(self, x):
|
| 22 |
+
nb_code_x, code_dim = x.shape
|
| 23 |
+
if nb_code_x < self.nb_code:
|
| 24 |
+
n_repeats = (self.nb_code + nb_code_x - 1) // nb_code_x
|
| 25 |
+
std = 0.01 / np.sqrt(code_dim)
|
| 26 |
+
out = x.repeat(n_repeats, 1)
|
| 27 |
+
out = out + torch.randn_like(out) * std
|
| 28 |
+
else :
|
| 29 |
+
out = x
|
| 30 |
+
return out
|
| 31 |
+
|
| 32 |
+
def init_codebook(self, x):
|
| 33 |
+
out = self._tile(x)
|
| 34 |
+
self.codebook = out[:self.nb_code]
|
| 35 |
+
self.code_sum = self.codebook.clone()
|
| 36 |
+
self.code_count = torch.ones(self.nb_code, device=self.codebook.device)
|
| 37 |
+
self.init = True
|
| 38 |
+
|
| 39 |
+
@torch.no_grad()
|
| 40 |
+
def compute_perplexity(self, code_idx) :
|
| 41 |
+
# Calculate new centres
|
| 42 |
+
code_onehot = torch.zeros(self.nb_code, code_idx.shape[0], device=code_idx.device) # nb_code, N * L
|
| 43 |
+
code_onehot.scatter_(0, code_idx.view(1, code_idx.shape[0]), 1)
|
| 44 |
+
|
| 45 |
+
code_count = code_onehot.sum(dim=-1) # nb_code
|
| 46 |
+
prob = code_count / torch.sum(code_count)
|
| 47 |
+
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
| 48 |
+
return perplexity
|
| 49 |
+
|
| 50 |
+
@torch.no_grad()
|
| 51 |
+
def update_codebook(self, x, code_idx):
|
| 52 |
+
|
| 53 |
+
code_onehot = torch.zeros(self.nb_code, x.shape[0], device=x.device) # nb_code, N * L
|
| 54 |
+
code_onehot.scatter_(0, code_idx.view(1, x.shape[0]), 1)
|
| 55 |
+
|
| 56 |
+
code_sum = torch.matmul(code_onehot, x) # nb_code, w
|
| 57 |
+
code_count = code_onehot.sum(dim=-1) # nb_code
|
| 58 |
+
|
| 59 |
+
out = self._tile(x)
|
| 60 |
+
code_rand = out[:self.nb_code]
|
| 61 |
+
|
| 62 |
+
# Update centres
|
| 63 |
+
self.code_sum = self.mu * self.code_sum + (1. - self.mu) * code_sum # w, nb_code
|
| 64 |
+
self.code_count = self.mu * self.code_count + (1. - self.mu) * code_count # nb_code
|
| 65 |
+
|
| 66 |
+
usage = (self.code_count.view(self.nb_code, 1) >= 1.0).float()
|
| 67 |
+
code_update = self.code_sum.view(self.nb_code, self.code_dim) / self.code_count.view(self.nb_code, 1)
|
| 68 |
+
|
| 69 |
+
self.codebook = usage * code_update + (1 - usage) * code_rand
|
| 70 |
+
prob = code_count / torch.sum(code_count)
|
| 71 |
+
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
return perplexity
|
| 75 |
+
|
| 76 |
+
def preprocess(self, x):
|
| 77 |
+
# NCT -> NTC -> [NT, C]
|
| 78 |
+
x = x.permute(0, 2, 1).contiguous()
|
| 79 |
+
x = x.view(-1, x.shape[-1])
|
| 80 |
+
return x
|
| 81 |
+
|
| 82 |
+
def quantize(self, x):
|
| 83 |
+
# Calculate latent code x_l
|
| 84 |
+
k_w = self.codebook.t()
|
| 85 |
+
distance = torch.sum(x ** 2, dim=-1, keepdim=True) - 2 * torch.matmul(x, k_w) + torch.sum(k_w ** 2, dim=0,
|
| 86 |
+
keepdim=True) # (N * L, b)
|
| 87 |
+
_, code_idx = torch.min(distance, dim=-1)
|
| 88 |
+
return code_idx
|
| 89 |
+
|
| 90 |
+
def dequantize(self, code_idx):
|
| 91 |
+
x = F.embedding(code_idx, self.codebook)
|
| 92 |
+
return x
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def forward(self, x):
|
| 96 |
+
N, width, T = x.shape
|
| 97 |
+
|
| 98 |
+
# Preprocess
|
| 99 |
+
x = self.preprocess(x)
|
| 100 |
+
|
| 101 |
+
# Init codebook if not inited
|
| 102 |
+
if self.training and not self.init:
|
| 103 |
+
self.init_codebook(x)
|
| 104 |
+
|
| 105 |
+
# quantize and dequantize through bottleneck
|
| 106 |
+
code_idx = self.quantize(x)
|
| 107 |
+
x_d = self.dequantize(code_idx)
|
| 108 |
+
|
| 109 |
+
# Update embeddings
|
| 110 |
+
if self.training:
|
| 111 |
+
perplexity = self.update_codebook(x, code_idx)
|
| 112 |
+
else :
|
| 113 |
+
perplexity = self.compute_perplexity(code_idx)
|
| 114 |
+
|
| 115 |
+
# Loss
|
| 116 |
+
commit_loss = F.mse_loss(x, x_d.detach())
|
| 117 |
+
|
| 118 |
+
# Passthrough
|
| 119 |
+
x_d = x + (x_d - x).detach()
|
| 120 |
+
|
| 121 |
+
# Postprocess
|
| 122 |
+
x_d = x_d.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
| 123 |
+
|
| 124 |
+
return x_d, commit_loss, perplexity
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
class Quantizer(nn.Module):
|
| 129 |
+
def __init__(self, n_e, e_dim, beta):
|
| 130 |
+
super(Quantizer, self).__init__()
|
| 131 |
+
|
| 132 |
+
self.e_dim = e_dim
|
| 133 |
+
self.n_e = n_e
|
| 134 |
+
self.beta = beta
|
| 135 |
+
|
| 136 |
+
self.embedding = nn.Embedding(self.n_e, self.e_dim)
|
| 137 |
+
self.embedding.weight.data.uniform_(-1.0 / self.n_e, 1.0 / self.n_e)
|
| 138 |
+
|
| 139 |
+
def forward(self, z):
|
| 140 |
+
|
| 141 |
+
N, width, T = z.shape
|
| 142 |
+
z = self.preprocess(z)
|
| 143 |
+
assert z.shape[-1] == self.e_dim
|
| 144 |
+
z_flattened = z.contiguous().view(-1, self.e_dim)
|
| 145 |
+
|
| 146 |
+
# B x V
|
| 147 |
+
d = torch.sum(z_flattened ** 2, dim=1, keepdim=True) + \
|
| 148 |
+
torch.sum(self.embedding.weight**2, dim=1) - 2 * \
|
| 149 |
+
torch.matmul(z_flattened, self.embedding.weight.t())
|
| 150 |
+
# B x 1
|
| 151 |
+
min_encoding_indices = torch.argmin(d, dim=1)
|
| 152 |
+
z_q = self.embedding(min_encoding_indices).view(z.shape)
|
| 153 |
+
|
| 154 |
+
# compute loss for embedding
|
| 155 |
+
loss = torch.mean((z_q - z.detach())**2) + self.beta * \
|
| 156 |
+
torch.mean((z_q.detach() - z)**2)
|
| 157 |
+
|
| 158 |
+
# preserve gradients
|
| 159 |
+
z_q = z + (z_q - z).detach()
|
| 160 |
+
z_q = z_q.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
| 161 |
+
|
| 162 |
+
min_encodings = F.one_hot(min_encoding_indices, self.n_e).type(z.dtype)
|
| 163 |
+
e_mean = torch.mean(min_encodings, dim=0)
|
| 164 |
+
perplexity = torch.exp(-torch.sum(e_mean*torch.log(e_mean + 1e-10)))
|
| 165 |
+
return z_q, loss, perplexity
|
| 166 |
+
|
| 167 |
+
def quantize(self, z):
|
| 168 |
+
|
| 169 |
+
assert z.shape[-1] == self.e_dim
|
| 170 |
+
|
| 171 |
+
# B x V
|
| 172 |
+
d = torch.sum(z ** 2, dim=1, keepdim=True) + \
|
| 173 |
+
torch.sum(self.embedding.weight ** 2, dim=1) - 2 * \
|
| 174 |
+
torch.matmul(z, self.embedding.weight.t())
|
| 175 |
+
# B x 1
|
| 176 |
+
min_encoding_indices = torch.argmin(d, dim=1)
|
| 177 |
+
return min_encoding_indices
|
| 178 |
+
|
| 179 |
+
def dequantize(self, indices):
|
| 180 |
+
|
| 181 |
+
index_flattened = indices.view(-1)
|
| 182 |
+
z_q = self.embedding(index_flattened)
|
| 183 |
+
z_q = z_q.view(indices.shape + (self.e_dim, )).contiguous()
|
| 184 |
+
return z_q
|
| 185 |
+
|
| 186 |
+
def preprocess(self, x):
|
| 187 |
+
# NCT -> NTC -> [NT, C]
|
| 188 |
+
x = x.permute(0, 2, 1).contiguous()
|
| 189 |
+
x = x.view(-1, x.shape[-1])
|
| 190 |
+
return x
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
class QuantizeReset(nn.Module):
|
| 195 |
+
def __init__(self, nb_code, code_dim, args):
|
| 196 |
+
super().__init__()
|
| 197 |
+
self.nb_code = nb_code
|
| 198 |
+
self.code_dim = code_dim
|
| 199 |
+
self.reset_codebook()
|
| 200 |
+
self.codebook = nn.Parameter(torch.randn(nb_code, code_dim))
|
| 201 |
+
|
| 202 |
+
def reset_codebook(self):
|
| 203 |
+
self.init = False
|
| 204 |
+
self.code_count = None
|
| 205 |
+
|
| 206 |
+
def _tile(self, x):
|
| 207 |
+
nb_code_x, code_dim = x.shape
|
| 208 |
+
if nb_code_x < self.nb_code:
|
| 209 |
+
n_repeats = (self.nb_code + nb_code_x - 1) // nb_code_x
|
| 210 |
+
std = 0.01 / np.sqrt(code_dim)
|
| 211 |
+
out = x.repeat(n_repeats, 1)
|
| 212 |
+
out = out + torch.randn_like(out) * std
|
| 213 |
+
else :
|
| 214 |
+
out = x
|
| 215 |
+
return out
|
| 216 |
+
|
| 217 |
+
def init_codebook(self, x):
|
| 218 |
+
out = self._tile(x)
|
| 219 |
+
self.codebook = nn.Parameter(out[:self.nb_code])
|
| 220 |
+
self.code_count = torch.ones(self.nb_code, device=self.codebook.device)
|
| 221 |
+
self.init = True
|
| 222 |
+
|
| 223 |
+
@torch.no_grad()
|
| 224 |
+
def compute_perplexity(self, code_idx) :
|
| 225 |
+
# Calculate new centres
|
| 226 |
+
code_onehot = torch.zeros(self.nb_code, code_idx.shape[0], device=code_idx.device) # nb_code, N * L
|
| 227 |
+
code_onehot.scatter_(0, code_idx.view(1, code_idx.shape[0]), 1)
|
| 228 |
+
|
| 229 |
+
code_count = code_onehot.sum(dim=-1) # nb_code
|
| 230 |
+
prob = code_count / torch.sum(code_count)
|
| 231 |
+
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
| 232 |
+
return perplexity
|
| 233 |
+
|
| 234 |
+
def update_codebook(self, x, code_idx):
|
| 235 |
+
|
| 236 |
+
code_onehot = torch.zeros(self.nb_code, x.shape[0], device=x.device) # nb_code, N * L
|
| 237 |
+
code_onehot.scatter_(0, code_idx.view(1, x.shape[0]), 1)
|
| 238 |
+
|
| 239 |
+
code_count = code_onehot.sum(dim=-1) # nb_code
|
| 240 |
+
|
| 241 |
+
out = self._tile(x)
|
| 242 |
+
code_rand = out[:self.nb_code]
|
| 243 |
+
|
| 244 |
+
# Update centres
|
| 245 |
+
self.code_count = code_count # nb_code
|
| 246 |
+
usage = (self.code_count.view(self.nb_code, 1) >= 1.0).float()
|
| 247 |
+
|
| 248 |
+
self.codebook.data = usage * self.codebook.data + (1 - usage) * code_rand
|
| 249 |
+
prob = code_count / torch.sum(code_count)
|
| 250 |
+
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
return perplexity
|
| 254 |
+
|
| 255 |
+
def preprocess(self, x):
|
| 256 |
+
# NCT -> NTC -> [NT, C]
|
| 257 |
+
x = x.permute(0, 2, 1).contiguous()
|
| 258 |
+
x = x.view(-1, x.shape[-1])
|
| 259 |
+
return x
|
| 260 |
+
|
| 261 |
+
def quantize(self, x):
|
| 262 |
+
# Calculate latent code x_l
|
| 263 |
+
k_w = self.codebook.t()
|
| 264 |
+
distance = torch.sum(x ** 2, dim=-1, keepdim=True) - 2 * torch.matmul(x, k_w) + torch.sum(k_w ** 2, dim=0,
|
| 265 |
+
keepdim=True) # (N * L, b)
|
| 266 |
+
_, code_idx = torch.min(distance, dim=-1)
|
| 267 |
+
return code_idx
|
| 268 |
+
|
| 269 |
+
def dequantize(self, code_idx):
|
| 270 |
+
x = F.embedding(code_idx, self.codebook)
|
| 271 |
+
return x
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def forward(self, x):
|
| 275 |
+
N, width, T = x.shape
|
| 276 |
+
# Preprocess
|
| 277 |
+
x = self.preprocess(x)
|
| 278 |
+
# Init codebook if not inited
|
| 279 |
+
if self.training and not self.init:
|
| 280 |
+
self.init_codebook(x)
|
| 281 |
+
# quantize and dequantize through bottleneck
|
| 282 |
+
code_idx = self.quantize(x)
|
| 283 |
+
x_d = self.dequantize(code_idx)
|
| 284 |
+
# Update embeddings
|
| 285 |
+
if self.training:
|
| 286 |
+
perplexity = self.update_codebook(x, code_idx)
|
| 287 |
+
else :
|
| 288 |
+
perplexity = self.compute_perplexity(code_idx)
|
| 289 |
+
|
| 290 |
+
# Loss
|
| 291 |
+
commit_loss = F.mse_loss(x, x_d.detach())
|
| 292 |
+
|
| 293 |
+
# Passthrough
|
| 294 |
+
x_d = x + (x_d - x).detach()
|
| 295 |
+
|
| 296 |
+
# Postprocess
|
| 297 |
+
x_d = x_d.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
| 298 |
+
|
| 299 |
+
return x_d, commit_loss, perplexity
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
class QuantizeEMA(nn.Module):
|
| 303 |
+
def __init__(self, nb_code, code_dim, args):
|
| 304 |
+
super().__init__()
|
| 305 |
+
self.nb_code = nb_code
|
| 306 |
+
self.code_dim = code_dim
|
| 307 |
+
self.mu = 0.99
|
| 308 |
+
self.reset_codebook()
|
| 309 |
+
|
| 310 |
+
def reset_codebook(self):
|
| 311 |
+
self.init = False
|
| 312 |
+
self.code_sum = None
|
| 313 |
+
self.code_count = None
|
| 314 |
+
# 修复:不强制指定设备,让模型自动处理数据类型和设备
|
| 315 |
+
self.register_buffer('codebook', torch.zeros(self.nb_code, self.code_dim))
|
| 316 |
+
|
| 317 |
+
def _tile(self, x):
|
| 318 |
+
nb_code_x, code_dim = x.shape
|
| 319 |
+
if nb_code_x < self.nb_code:
|
| 320 |
+
n_repeats = (self.nb_code + nb_code_x - 1) // nb_code_x
|
| 321 |
+
std = 0.01 / np.sqrt(code_dim)
|
| 322 |
+
out = x.repeat(n_repeats, 1)
|
| 323 |
+
out = out + torch.randn_like(out) * std
|
| 324 |
+
else :
|
| 325 |
+
out = x
|
| 326 |
+
return out
|
| 327 |
+
|
| 328 |
+
def init_codebook(self, x):
|
| 329 |
+
out = self._tile(x)
|
| 330 |
+
self.codebook = out[:self.nb_code]
|
| 331 |
+
self.code_sum = self.codebook.clone()
|
| 332 |
+
self.code_count = torch.ones(self.nb_code, device=self.codebook.device)
|
| 333 |
+
self.init = True
|
| 334 |
+
|
| 335 |
+
@torch.no_grad()
|
| 336 |
+
def compute_perplexity(self, code_idx) :
|
| 337 |
+
# Calculate new centres
|
| 338 |
+
code_onehot = torch.zeros(self.nb_code, code_idx.shape[0], device=code_idx.device) # nb_code, N * L
|
| 339 |
+
code_onehot.scatter_(0, code_idx.view(1, code_idx.shape[0]), 1)
|
| 340 |
+
|
| 341 |
+
code_count = code_onehot.sum(dim=-1) # nb_code
|
| 342 |
+
prob = code_count / torch.sum(code_count)
|
| 343 |
+
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
| 344 |
+
return perplexity
|
| 345 |
+
|
| 346 |
+
@torch.no_grad()
|
| 347 |
+
def update_codebook(self, x, code_idx):
|
| 348 |
+
|
| 349 |
+
code_onehot = torch.zeros(self.nb_code, x.shape[0], device=x.device) # nb_code, N * L
|
| 350 |
+
code_onehot.scatter_(0, code_idx.view(1, x.shape[0]), 1)
|
| 351 |
+
|
| 352 |
+
code_sum = torch.matmul(code_onehot, x) # nb_code, w
|
| 353 |
+
code_count = code_onehot.sum(dim=-1) # nb_code
|
| 354 |
+
|
| 355 |
+
# Update centres
|
| 356 |
+
self.code_sum = self.mu * self.code_sum + (1. - self.mu) * code_sum # w, nb_code
|
| 357 |
+
self.code_count = self.mu * self.code_count + (1. - self.mu) * code_count # nb_code
|
| 358 |
+
|
| 359 |
+
code_update = self.code_sum.view(self.nb_code, self.code_dim) / self.code_count.view(self.nb_code, 1)
|
| 360 |
+
|
| 361 |
+
self.codebook = code_update
|
| 362 |
+
prob = code_count / torch.sum(code_count)
|
| 363 |
+
perplexity = torch.exp(-torch.sum(prob * torch.log(prob + 1e-7)))
|
| 364 |
+
|
| 365 |
+
return perplexity
|
| 366 |
+
|
| 367 |
+
def preprocess(self, x):
|
| 368 |
+
# NCT -> NTC -> [NT, C]
|
| 369 |
+
x = x.permute(0, 2, 1).contiguous()
|
| 370 |
+
x = x.view(-1, x.shape[-1])
|
| 371 |
+
return x
|
| 372 |
+
|
| 373 |
+
def quantize(self, x):
|
| 374 |
+
# Calculate latent code x_l
|
| 375 |
+
k_w = self.codebook.t()
|
| 376 |
+
distance = torch.sum(x ** 2, dim=-1, keepdim=True) - 2 * torch.matmul(x, k_w) + torch.sum(k_w ** 2, dim=0,
|
| 377 |
+
keepdim=True) # (N * L, b)
|
| 378 |
+
_, code_idx = torch.min(distance, dim=-1)
|
| 379 |
+
return code_idx
|
| 380 |
+
|
| 381 |
+
def dequantize(self, code_idx):
|
| 382 |
+
x = F.embedding(code_idx, self.codebook)
|
| 383 |
+
return x
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def forward(self, x):
|
| 387 |
+
N, width, T = x.shape
|
| 388 |
+
|
| 389 |
+
# Preprocess
|
| 390 |
+
x = self.preprocess(x)
|
| 391 |
+
|
| 392 |
+
# Init codebook if not inited
|
| 393 |
+
if self.training and not self.init:
|
| 394 |
+
self.init_codebook(x)
|
| 395 |
+
|
| 396 |
+
# quantize and dequantize through bottleneck
|
| 397 |
+
code_idx = self.quantize(x)
|
| 398 |
+
x_d = self.dequantize(code_idx)
|
| 399 |
+
|
| 400 |
+
# Update embeddings
|
| 401 |
+
if self.training:
|
| 402 |
+
perplexity = self.update_codebook(x, code_idx)
|
| 403 |
+
else :
|
| 404 |
+
perplexity = self.compute_perplexity(code_idx)
|
| 405 |
+
|
| 406 |
+
# Loss
|
| 407 |
+
commit_loss = F.mse_loss(x, x_d.detach())
|
| 408 |
+
|
| 409 |
+
# Passthrough
|
| 410 |
+
x_d = x + (x_d - x).detach()
|
| 411 |
+
|
| 412 |
+
# Postprocess
|
| 413 |
+
x_d = x_d.view(N, T, -1).permute(0, 2, 1).contiguous() #(N, DIM, T)
|
| 414 |
+
|
| 415 |
+
return x_d, commit_loss, perplexity
|
motionvq/resnet.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
class nonlinearity(nn.Module):
|
| 5 |
+
def __init__(self):
|
| 6 |
+
super().__init__()
|
| 7 |
+
|
| 8 |
+
def forward(self, x):
|
| 9 |
+
# swish
|
| 10 |
+
return x * torch.sigmoid(x)
|
| 11 |
+
|
| 12 |
+
class ResConv1DBlock(nn.Module):
|
| 13 |
+
def __init__(self, n_in, n_state, dilation=1, activation='silu', norm=None, dropout=None):
|
| 14 |
+
super().__init__()
|
| 15 |
+
padding = dilation
|
| 16 |
+
self.norm = norm
|
| 17 |
+
if norm == "LN":
|
| 18 |
+
self.norm1 = nn.LayerNorm(n_in)
|
| 19 |
+
self.norm2 = nn.LayerNorm(n_in)
|
| 20 |
+
elif norm == "GN":
|
| 21 |
+
self.norm1 = nn.GroupNorm(num_groups=32, num_channels=n_in, eps=1e-6, affine=True)
|
| 22 |
+
self.norm2 = nn.GroupNorm(num_groups=32, num_channels=n_in, eps=1e-6, affine=True)
|
| 23 |
+
elif norm == "BN":
|
| 24 |
+
self.norm1 = nn.BatchNorm1d(num_features=n_in, eps=1e-6, affine=True)
|
| 25 |
+
self.norm2 = nn.BatchNorm1d(num_features=n_in, eps=1e-6, affine=True)
|
| 26 |
+
|
| 27 |
+
else:
|
| 28 |
+
self.norm1 = nn.Identity()
|
| 29 |
+
self.norm2 = nn.Identity()
|
| 30 |
+
|
| 31 |
+
if activation == "relu":
|
| 32 |
+
self.activation1 = nn.ReLU()
|
| 33 |
+
self.activation2 = nn.ReLU()
|
| 34 |
+
|
| 35 |
+
elif activation == "silu":
|
| 36 |
+
self.activation1 = nonlinearity()
|
| 37 |
+
self.activation2 = nonlinearity()
|
| 38 |
+
|
| 39 |
+
elif activation == "gelu":
|
| 40 |
+
self.activation1 = nn.GELU()
|
| 41 |
+
self.activation2 = nn.GELU()
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
self.conv1 = nn.Conv1d(n_in, n_state, 3, 1, padding, dilation)
|
| 46 |
+
self.conv2 = nn.Conv1d(n_state, n_in, 1, 1, 0,)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def forward(self, x):
|
| 50 |
+
x_orig = x
|
| 51 |
+
if self.norm == "LN":
|
| 52 |
+
x = self.norm1(x.transpose(-2, -1))
|
| 53 |
+
x = self.activation1(x.transpose(-2, -1))
|
| 54 |
+
else:
|
| 55 |
+
x = self.norm1(x)
|
| 56 |
+
x = self.activation1(x)
|
| 57 |
+
|
| 58 |
+
x = self.conv1(x)
|
| 59 |
+
|
| 60 |
+
if self.norm == "LN":
|
| 61 |
+
x = self.norm2(x.transpose(-2, -1))
|
| 62 |
+
x = self.activation2(x.transpose(-2, -1))
|
| 63 |
+
else:
|
| 64 |
+
x = self.norm2(x)
|
| 65 |
+
x = self.activation2(x)
|
| 66 |
+
|
| 67 |
+
x = self.conv2(x)
|
| 68 |
+
x = x + x_orig
|
| 69 |
+
return x
|
| 70 |
+
|
| 71 |
+
class Resnet1D(nn.Module):
|
| 72 |
+
def __init__(self, n_in, n_depth, dilation_growth_rate=1, reverse_dilation=True, activation='relu', norm=None):
|
| 73 |
+
super().__init__()
|
| 74 |
+
|
| 75 |
+
blocks = [ResConv1DBlock(n_in, n_in, dilation=dilation_growth_rate ** depth, activation=activation, norm=norm) for depth in range(n_depth)]
|
| 76 |
+
if reverse_dilation:
|
| 77 |
+
blocks = blocks[::-1]
|
| 78 |
+
|
| 79 |
+
self.model = nn.Sequential(*blocks)
|
| 80 |
+
|
| 81 |
+
def forward(self, x):
|
| 82 |
+
return self.model(x)
|
motionvq/vqvae.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
from .encdec import Encoder, Decoder
|
| 3 |
+
from .quantize_cnn import QuantizeEMAReset, Quantizer, QuantizeEMA, QuantizeReset
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class VQVAE_251(nn.Module):
|
| 7 |
+
def __init__(self,
|
| 8 |
+
args,
|
| 9 |
+
nb_code=1024,
|
| 10 |
+
code_dim=512,
|
| 11 |
+
output_emb_width=512,
|
| 12 |
+
down_t=3,
|
| 13 |
+
stride_t=2,
|
| 14 |
+
width=512,
|
| 15 |
+
depth=3,
|
| 16 |
+
dilation_growth_rate=3,
|
| 17 |
+
activation='relu',
|
| 18 |
+
norm=None):
|
| 19 |
+
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.code_dim = code_dim
|
| 22 |
+
self.num_code = nb_code
|
| 23 |
+
self.quant = args.quantizer
|
| 24 |
+
self.encoder = Encoder(251 if args.dataname == 'kit' else 263, output_emb_width, down_t, stride_t, width, depth, dilation_growth_rate, activation=activation, norm=norm)
|
| 25 |
+
self.decoder = Decoder(251 if args.dataname == 'kit' else 263, output_emb_width, down_t, stride_t, width, depth, dilation_growth_rate, activation=activation, norm=norm)
|
| 26 |
+
if args.quantizer == "ema_reset":
|
| 27 |
+
self.quantizer = QuantizeEMAReset(nb_code, code_dim, args)
|
| 28 |
+
elif args.quantizer == "orig":
|
| 29 |
+
self.quantizer = Quantizer(nb_code, code_dim, 1.0)
|
| 30 |
+
elif args.quantizer == "ema":
|
| 31 |
+
self.quantizer = QuantizeEMA(nb_code, code_dim, args)
|
| 32 |
+
elif args.quantizer == "reset":
|
| 33 |
+
self.quantizer = QuantizeReset(nb_code, code_dim, args)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def preprocess(self, x):
|
| 37 |
+
# (bs, T, Jx3) -> (bs, Jx3, T)
|
| 38 |
+
x = x.permute(0,2,1).float()
|
| 39 |
+
return x
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def postprocess(self, x):
|
| 43 |
+
# (bs, Jx3, T) -> (bs, T, Jx3)
|
| 44 |
+
x = x.permute(0,2,1)
|
| 45 |
+
return x
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def encode(self, x):
|
| 49 |
+
N, T, _ = x.shape
|
| 50 |
+
x_in = self.preprocess(x)
|
| 51 |
+
x_encoder = self.encoder(x_in)
|
| 52 |
+
x_encoder = self.postprocess(x_encoder)
|
| 53 |
+
x_encoder = x_encoder.contiguous().view(-1, x_encoder.shape[-1]) # (NT, C)
|
| 54 |
+
code_idx = self.quantizer.quantize(x_encoder)
|
| 55 |
+
code_idx = code_idx.view(N, -1)
|
| 56 |
+
return code_idx
|
| 57 |
+
|
| 58 |
+
def get_embeddings(self, x):
|
| 59 |
+
x_in = self.preprocess(x)
|
| 60 |
+
x_encoder = self.encoder(x_in)
|
| 61 |
+
x_quantized, loss, perplexity = self.quantizer(x_encoder)
|
| 62 |
+
return x_quantized
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def forward(self, x):
|
| 66 |
+
x_in = self.preprocess(x)
|
| 67 |
+
# Encode
|
| 68 |
+
x_encoder = self.encoder(x_in)
|
| 69 |
+
## quantization
|
| 70 |
+
x_quantized, loss, perplexity = self.quantizer(x_encoder)
|
| 71 |
+
## decoder
|
| 72 |
+
x_decoder = self.decoder(x_quantized)
|
| 73 |
+
x_out = self.postprocess(x_decoder)
|
| 74 |
+
return x_out, loss, perplexity
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def forward_decoder(self, x):
|
| 78 |
+
x_d = self.quantizer.dequantize(x)
|
| 79 |
+
x_d = x_d.view(1, -1, self.code_dim).permute(0, 2, 1).contiguous()
|
| 80 |
+
|
| 81 |
+
# decoder
|
| 82 |
+
x_decoder = self.decoder(x_d)
|
| 83 |
+
x_out = self.postprocess(x_decoder)
|
| 84 |
+
return x_out
|
| 85 |
+
|
| 86 |
+
def embeddings_decode(self, x):
|
| 87 |
+
x_decoder = self.decoder(x)
|
| 88 |
+
x_out = self.postprocess(x_decoder)
|
| 89 |
+
return x_out
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class HumanVQVAE(nn.Module):
|
| 93 |
+
def __init__(self,
|
| 94 |
+
args,
|
| 95 |
+
nb_code=512,
|
| 96 |
+
code_dim=512,
|
| 97 |
+
output_emb_width=512,
|
| 98 |
+
down_t=3,
|
| 99 |
+
stride_t=2,
|
| 100 |
+
width=512,
|
| 101 |
+
depth=3,
|
| 102 |
+
dilation_growth_rate=3,
|
| 103 |
+
activation='relu',
|
| 104 |
+
norm=None):
|
| 105 |
+
|
| 106 |
+
super().__init__()
|
| 107 |
+
|
| 108 |
+
self.nb_joints = 21 if args.dataname == 'kit' else 22
|
| 109 |
+
self.vqvae = VQVAE_251(args, nb_code, code_dim, output_emb_width, down_t, stride_t, width, depth, dilation_growth_rate, activation=activation, norm=norm)
|
| 110 |
+
|
| 111 |
+
def forward_encoder(self, x):
|
| 112 |
+
x_out = self.vqvae.forward_encoder(x)
|
| 113 |
+
return x_out
|
| 114 |
+
|
| 115 |
+
def encode(self, x):
|
| 116 |
+
b, t, c = x.size()
|
| 117 |
+
quants = self.vqvae.encode(x) # (N, T)
|
| 118 |
+
return quants
|
| 119 |
+
|
| 120 |
+
def forward(self, x):
|
| 121 |
+
|
| 122 |
+
x_out, loss, perplexity = self.vqvae(x)
|
| 123 |
+
|
| 124 |
+
return x_out, loss, perplexity
|
| 125 |
+
|
| 126 |
+
def forward_decoder(self, x):
|
| 127 |
+
x_out = self.vqvae.forward_decoder(x)
|
| 128 |
+
return x_out
|
| 129 |
+
|
| 130 |
+
def get_embeddings(self, x):
|
| 131 |
+
x_out = self.vqvae.get_embeddings(x)
|
| 132 |
+
return x_out
|
| 133 |
+
|
| 134 |
+
def embeddings_decode(self, x):
|
| 135 |
+
x_out = self.vqvae.embeddings_decode(x)
|
| 136 |
+
return x_out
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
transformers==4.56.1
|
| 3 |
+
accelerate
|
| 4 |
+
numpy<2
|
| 5 |
+
matplotlib==3.7.5
|
| 6 |
+
imageio
|
| 7 |
+
imageio-ffmpeg
|
std.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6a5f7d60301c9465972fc225f8ad0ee8f957e7720431189123eb6d15873a9557
|
| 3 |
+
size 2232
|