multimodalart HF Staff commited on
Commit
33ecda4
·
verified ·
1 Parent(s): 42047a1

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,13 +1,36 @@
1
- ---
2
- title: Irg Motionllm
3
- emoji: 🦀
4
- colorFrom: purple
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: '3.12'
9
- app_file: app.py
10
- pinned: false
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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