Text Generation
Transformers
Safetensors
English
mugen
motion
human-motion
text-to-motion
motion-to-text
motion-captioning
motion-generation
autoencoder
gpt2
custom_code
Instructions to use zy22b/MUGEN with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use zy22b/MUGEN with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="zy22b/MUGEN", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("zy22b/MUGEN", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use zy22b/MUGEN with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "zy22b/MUGEN" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "zy22b/MUGEN", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/zy22b/MUGEN
- SGLang
How to use zy22b/MUGEN with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "zy22b/MUGEN" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "zy22b/MUGEN", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "zy22b/MUGEN" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "zy22b/MUGEN", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use zy22b/MUGEN with Docker Model Runner:
docker model run hf.co/zy22b/MUGEN
initial commit
Browse files- .gitattributes +0 -34
- README.md +368 -0
- added_tokens.json +6 -0
- config.json +58 -0
- configuration_mugen.py +231 -0
- merges.txt +0 -0
- model.safetensors +3 -0
- modeling_mugen.py +899 -0
- special_tokens_map.json +15 -0
- tokenizer.json +0 -0
- tokenizer_config.json +57 -0
- vocab.json +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,3 +1,371 @@
|
|
| 1 |
---
|
| 2 |
license: mit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
license: mit
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- motion
|
| 7 |
+
- human-motion
|
| 8 |
+
- text-to-motion
|
| 9 |
+
- motion-to-text
|
| 10 |
+
- motion-captioning
|
| 11 |
+
- motion-generation
|
| 12 |
+
- autoencoder
|
| 13 |
+
- gpt2
|
| 14 |
+
datasets:
|
| 15 |
+
- humanml3d
|
| 16 |
+
pipeline_tag: text-generation
|
| 17 |
+
library_name: transformers
|
| 18 |
+
base_model:
|
| 19 |
+
- openai-community/gpt2
|
| 20 |
---
|
| 21 |
+
|
| 22 |
+
# MUGEN
|
| 23 |
+
|
| 24 |
+
**A Unified Framework for Efficient Motion Understanding and Generation**
|
| 25 |
+
|
| 26 |
+
MUGEN is a unified motion-language model. One model turns a description into
|
| 27 |
+
human motion, and describes an observed motion in words. Both directions share a
|
| 28 |
+
single motion representation, and that representation is continuous: **no
|
| 29 |
+
codebook, one draw.**
|
| 30 |
+
|
| 31 |
+
- 📦 Code: [github.com/JYe16/MUGEN](https://github.com/JYe16/MUGEN)
|
| 32 |
+
- 🏋️ This checkpoint: HumanML3D, **K = 2** latent slots, 183.7M parameters
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## What makes it different
|
| 37 |
+
|
| 38 |
+
Unified motion-language systems have traditionally coupled generation and
|
| 39 |
+
understanding through a shared *discrete* motion codebook, but quantization
|
| 40 |
+
limits generation quality. The strongest generators buy that quality back at
|
| 41 |
+
growing cost: stacked residual codebooks enlarge the representation, and masked
|
| 42 |
+
decoding stages, long autoregressive rollouts and denoising chains of tens to
|
| 43 |
+
hundreds of steps stretch inference. None of that decoding machinery serves
|
| 44 |
+
understanding.
|
| 45 |
+
|
| 46 |
+
MUGEN pays neither cost. Generating a motion costs **K language-model steps, one
|
| 47 |
+
draw, and one decoder pass** — with K = 2 here, that is two rollout steps.
|
| 48 |
+
|
| 49 |
+
## Architecture
|
| 50 |
+
|
| 51 |
+
```
|
| 52 |
+
MugenForConditionalGeneration 183,690,603 params
|
| 53 |
+
├── motion_autoencoder (AdaptiveLengthAutoEncoder) 48.9M
|
| 54 |
+
│ ├── encoder_backbone dilated Conv1d ResNet, per-frame features
|
| 55 |
+
│ ├── latent_queries K learnable queries -> the K slots
|
| 56 |
+
│ ├── encoder_blocks 4 x cross-attention (frames -> slots)
|
| 57 |
+
│ ├── decoder_blocks 4 x cross-attention (slots -> frames)
|
| 58 |
+
│ └── decoder_refiner dilated Conv1d ResNet -> 263-d motion
|
| 59 |
+
├── language_model (GPT2LMHeadModel) 124.4M
|
| 60 |
+
│ └── 12 layers, 768 hidden, vocab 50261 (GPT-2 + <MOT> + 3 reserved)
|
| 61 |
+
├── layer router 9.1M
|
| 62 |
+
│ ├── text_mem_proj prompt tokens -> routing memory
|
| 63 |
+
│ ├── cross_attn_blocks 2 x cross-attention, per-slot routing queries
|
| 64 |
+
│ ├── static_router_logits (K, 12) text-independent component
|
| 65 |
+
│ └── layer_router MLP -> (K, 12) text-conditional component
|
| 66 |
+
├── calibrated latent head 0.9M
|
| 67 |
+
│ ├── projector hidden -> (mu, logvar) per slot
|
| 68 |
+
│ ├── latent_factors (K*512, 64) global covariance basis U
|
| 69 |
+
│ └── factor_scale_head hidden -> per-factor log amplitudes
|
| 70 |
+
└── motion_in_projector latent slots -> GPT-2 embeddings (captioning) 0.4M
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
**Adaptive-length autoencoder.** Cross-attention compresses a clip of *any*
|
| 74 |
+
length into K = 2 continuous vectors of width 512, and a second cross-attention
|
| 75 |
+
stack expands them back to *any* requested frame count. Decoder queries encode a
|
| 76 |
+
frame's relative phase within the clip rather than an absolute frame index, which
|
| 77 |
+
is why one decoder serves every length, and why you can decode the same latents
|
| 78 |
+
at 60 frames and at 200 frames.
|
| 79 |
+
|
| 80 |
+
**Depth-routed hidden states.** A text-conditioned router gives each latent slot
|
| 81 |
+
its own soft mixture over all twelve transformer layers, so a slot reads from the
|
| 82 |
+
depth it needs instead of squeezing every piece of motion evidence through the
|
| 83 |
+
final layer. Both halves of the routing logit are tanh-bounded, so no logit
|
| 84 |
+
margin can saturate the routing softmax.
|
| 85 |
+
|
| 86 |
+
**Calibrated latent head.** The head predicts
|
| 87 |
+
`N(mu, U diag(a)^2 U^T + diag(sigma^2))` over the whole flattened `K x 512`
|
| 88 |
+
latent, with rank 64, trained by exact maximum likelihood. One draw therefore
|
| 89 |
+
carries text-conditional variance that is *correlated across slots*, which is
|
| 90 |
+
what a single-step sampler has to supply all at once.
|
| 91 |
+
|
| 92 |
+
| | |
|
| 93 |
+
|---|---|
|
| 94 |
+
| Motion features | HumanML3D, 263-d per frame, 20 fps, 22 joints |
|
| 95 |
+
| Latent slots | K = 2, width 512 |
|
| 96 |
+
| Language model | GPT-2 (124M), fine-tuned |
|
| 97 |
+
| Covariance rank | 64 |
|
| 98 |
+
| Training | Joint generation + understanding, 240 epochs (Stage 2) |
|
| 99 |
+
| Parameters | 183,690,603 |
|
| 100 |
+
| Precision | float32 |
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## Installation
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
pip install torch transformers safetensors numpy
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
`trust_remote_code=True` is required: the autoencoder, the router and the
|
| 111 |
+
calibrated head are custom modules that ship with this repository.
|
| 112 |
+
|
| 113 |
+
**Nothing else is needed.** The model carries its own tokenizer and its own
|
| 114 |
+
HumanML3D feature statistics, so it returns motion in real units without you
|
| 115 |
+
downloading the dataset.
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## Quick start
|
| 120 |
+
|
| 121 |
+
```python
|
| 122 |
+
import torch
|
| 123 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 124 |
+
|
| 125 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 126 |
+
"zy22b/MUGEN", trust_remote_code=True
|
| 127 |
+
).eval()
|
| 128 |
+
tokenizer = AutoTokenizer.from_pretrained("zy22b/MUGEN")
|
| 129 |
+
|
| 130 |
+
features = model.generate_motion(
|
| 131 |
+
["a person walks forward and then waves with the right hand."],
|
| 132 |
+
lengths=[120], # frames at 20 fps, so 6 seconds
|
| 133 |
+
tokenizer=tokenizer,
|
| 134 |
+
)
|
| 135 |
+
print(features.shape) # torch.Size([1, 120, 263])
|
| 136 |
+
|
| 137 |
+
joints = model.features_to_joints(features)
|
| 138 |
+
print(joints.shape) # torch.Size([1, 120, 22, 3]) metres
|
| 139 |
+
|
| 140 |
+
print(model.generate_caption(features, tokenizer=tokenizer))
|
| 141 |
+
# ['a person walks forward, turns to the left, and walks back.']
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
That last line captions the motion the model just generated, so it changes from
|
| 145 |
+
call to call: the motion is *sampled*, not a fixed function of the prompt.
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## Inference tutorial
|
| 150 |
+
|
| 151 |
+
### 1. Text to motion
|
| 152 |
+
|
| 153 |
+
```python
|
| 154 |
+
prompts = [
|
| 155 |
+
"a person walks forward and then waves with the right hand.",
|
| 156 |
+
"the person jumps up and lands with both feet together.",
|
| 157 |
+
"a man slowly sits down on a chair.",
|
| 158 |
+
"a person runs in a circle to the left.",
|
| 159 |
+
]
|
| 160 |
+
|
| 161 |
+
features = model.generate_motion(prompts, lengths=120, tokenizer=tokenizer)
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
`generate_motion` returns `(batch, frames, 263)` denormalised HumanML3D features.
|
| 165 |
+
|
| 166 |
+
| Argument | Meaning |
|
| 167 |
+
|---|---|
|
| 168 |
+
| `texts` | one description, or a list of them |
|
| 169 |
+
| `lengths` | frames per description at 20 fps; an `int` applies to the whole batch |
|
| 170 |
+
| `temperature` | multiplier on the sampled perturbation; defaults to the calibrated `1.0` |
|
| 171 |
+
| `generator` | a `torch.Generator` for a reproducible draw |
|
| 172 |
+
| `denormalize` | `True` (default) returns raw HumanML3D units |
|
| 173 |
+
|
| 174 |
+
**Choosing a length.** HumanML3D clips are multiples of 4 frames and at most 196
|
| 175 |
+
(9.8 s), so stay in `[20, 196]` and on the multiple-of-4 grid to stay in
|
| 176 |
+
distribution. The decoder will happily produce other lengths, but you are then
|
| 177 |
+
outside what it was trained on.
|
| 178 |
+
|
| 179 |
+
**Per-row lengths.** Because decoder queries are relative-phase, a row must be
|
| 180 |
+
decoded at its own length rather than cropped from a longer one. Pass a list and
|
| 181 |
+
the model handles it; the returned tensor is zero-padded to the longest row.
|
| 182 |
+
|
| 183 |
+
```python
|
| 184 |
+
features = model.generate_motion(prompts[:2], lengths=[60, 196], tokenizer=tokenizer)
|
| 185 |
+
# (2, 196, 263); row 0 is valid up to frame 60
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
### 2. Temperature, and why the default is 1.0
|
| 189 |
+
|
| 190 |
+
The head predicts a distribution, and `temperature` scales the whole zero-mean
|
| 191 |
+
perturbation around its mean.
|
| 192 |
+
|
| 193 |
+
```python
|
| 194 |
+
# The calibrated conditional distribution. This is the protocol every reported
|
| 195 |
+
# number uses, and different calls give different motions for the same prompt.
|
| 196 |
+
features = model.generate_motion(prompts, lengths=120, tokenizer=tokenizer)
|
| 197 |
+
|
| 198 |
+
# The distribution mean. Deterministic, and a *regression* protocol: numbers
|
| 199 |
+
# obtained this way are not comparable to generative ones.
|
| 200 |
+
mean_motion = model.generate_motion(prompts, lengths=120, tokenizer=tokenizer,
|
| 201 |
+
temperature=0.0)
|
| 202 |
+
|
| 203 |
+
# Reproducible sampling.
|
| 204 |
+
g = torch.Generator().manual_seed(1234)
|
| 205 |
+
features = model.generate_motion(prompts, lengths=120, tokenizer=tokenizer, generator=g)
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
Sampling at temperature 1.0 is what makes the same prompt yield genuinely
|
| 209 |
+
different, plausible motions. Captioning five seeded draws of the same prompt
|
| 210 |
+
shows how far apart they land:
|
| 211 |
+
|
| 212 |
+
```
|
| 213 |
+
prompt: "a person walks forward and then waves with the right hand."
|
| 214 |
+
seed 0 a person walks forward and then turns around and walks back.
|
| 215 |
+
seed 1 a person walks forward and reaches out with their right hand.
|
| 216 |
+
seed 2 a person walks forward and then waves with their left hand.
|
| 217 |
+
seed 3 a person walks forward and puts their right hand on their head.
|
| 218 |
+
seed 4 a person walks forward and then puts something on a counter.
|
| 219 |
+
```
|
| 220 |
+
|
| 221 |
+
Lowering the temperature trades that diversity for proximity to the conditional
|
| 222 |
+
mean.
|
| 223 |
+
|
| 224 |
+
### 3. Motion to text
|
| 225 |
+
|
| 226 |
+
```python
|
| 227 |
+
import numpy as np
|
| 228 |
+
|
| 229 |
+
motion = np.load("000004.npy") # (T, 263) HumanML3D features
|
| 230 |
+
clip = torch.from_numpy(motion).float().unsqueeze(0)
|
| 231 |
+
|
| 232 |
+
print(model.generate_caption(clip, tokenizer=tokenizer))
|
| 233 |
+
# ['a person bends over to touch the ground with their hands, then stands up straight.']
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
Pass `normalized=True` if your features are already standardised by the dataset
|
| 237 |
+
statistics. Captioning is greedy by default; `max_new_tokens` and `num_beams`
|
| 238 |
+
override the config values.
|
| 239 |
+
|
| 240 |
+
### 4. The shared latent interface
|
| 241 |
+
|
| 242 |
+
The K slots are the only motion representation in the system: the language model
|
| 243 |
+
generates them for text-to-motion and reads these same vectors back for
|
| 244 |
+
captioning. You can work with them directly.
|
| 245 |
+
|
| 246 |
+
```python
|
| 247 |
+
latents = model.encode_motion(clip) # (1, 2, 512): a whole clip, two vectors
|
| 248 |
+
print(latents.shape)
|
| 249 |
+
|
| 250 |
+
# Decode at the original length, and at a different one.
|
| 251 |
+
same = model.decode_motion(latents, clip.shape[1])
|
| 252 |
+
longer = model.decode_motion(latents, 200)
|
| 253 |
+
|
| 254 |
+
# Caption straight from latents, skipping the encoder.
|
| 255 |
+
print(model.generate_caption(latents=latents, tokenizer=tokenizer))
|
| 256 |
+
|
| 257 |
+
# Text -> latents, without decoding.
|
| 258 |
+
z = model.text_to_latents(["a person waves."], tokenizer=tokenizer)
|
| 259 |
+
```
|
| 260 |
+
|
| 261 |
+
Interpolating between two clips is then a two-line operation:
|
| 262 |
+
|
| 263 |
+
```python
|
| 264 |
+
a = model.encode_motion(clip_a)
|
| 265 |
+
b = model.encode_motion(clip_b)
|
| 266 |
+
blend = model.decode_motion(0.5 * a + 0.5 * b, 120)
|
| 267 |
+
```
|
| 268 |
+
|
| 269 |
+
### 5. Running on GPU, and in batches
|
| 270 |
+
|
| 271 |
+
```python
|
| 272 |
+
model = model.to("cuda")
|
| 273 |
+
features = model.generate_motion(prompts, lengths=120, tokenizer=tokenizer) # follows the model
|
| 274 |
+
```
|
| 275 |
+
|
| 276 |
+
Batched prompts are left-padded, which reproduces the training and evaluation
|
| 277 |
+
behaviour exactly. Single-prompt calls involve no padding at all.
|
| 278 |
+
|
| 279 |
+
### 6. Saving and rendering
|
| 280 |
+
|
| 281 |
+
`features_to_joints` gives `(batch, frames, 22, 3)` positions in metres, on the
|
| 282 |
+
HumanML3D skeleton, with the y axis vertical.
|
| 283 |
+
|
| 284 |
+
```python
|
| 285 |
+
joints = model.features_to_joints(features)
|
| 286 |
+
np.save("sample.npy", joints[0].cpu().numpy())
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
For animation, feed those positions to any HumanML3D-compatible renderer. The
|
| 290 |
+
[code repository](https://github.com/JYe16/MUGEN) includes
|
| 291 |
+
`motGPT/utils/render_utils.py`, which writes an mp4 directly from a `[T, J, 3]`
|
| 292 |
+
array.
|
| 293 |
+
|
| 294 |
+
### 7. Sanity check
|
| 295 |
+
|
| 296 |
+
If you swap in different feature statistics, the failure is silent: shapes stay
|
| 297 |
+
right and the numbers look plausible while the whole skeleton is mis-scaled. One
|
| 298 |
+
assertion catches it.
|
| 299 |
+
|
| 300 |
+
```python
|
| 301 |
+
joints = model.features_to_joints(features)
|
| 302 |
+
high, low = float(joints[..., 1].max()), float(joints[..., 1].min())
|
| 303 |
+
assert 1.0 < high < 2.5 and -0.5 < low < 0.5, "implausible body height"
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
---
|
| 307 |
+
|
| 308 |
+
## Reproducibility
|
| 309 |
+
|
| 310 |
+
The inference path in `modeling_mugen.py` was verified against the original
|
| 311 |
+
training code on the same checkpoint. Routed hidden states, `mu`, `logvar`, the
|
| 312 |
+
factor log-amplitudes, the autoencoder encode and decode outputs, and a
|
| 313 |
+
seeded draw all agree to **`max|diff| = 0.000e+00`**, and generated captions match
|
| 314 |
+
token for token.
|
| 315 |
+
|
| 316 |
+
Two consequences worth knowing:
|
| 317 |
+
|
| 318 |
+
- A given seed reproduces the same draw here as it does in the research code,
|
| 319 |
+
because the factor noise is drawn before the diagonal noise in both.
|
| 320 |
+
- `router_eval_tau` (1.5) is the converged end of the training temperature
|
| 321 |
+
anneal. Changing it changes which transformer depth each slot reads from, so
|
| 322 |
+
leave it alone unless you are deliberately studying the router.
|
| 323 |
+
|
| 324 |
+
Tested with `transformers` 4.47 and PyTorch 2.x. The rollout passes explicit
|
| 325 |
+
`position_ids`, so behaviour does not drift with the transformers version.
|
| 326 |
+
|
| 327 |
+
## Limitations
|
| 328 |
+
|
| 329 |
+
- Trained on **HumanML3D** only: everyday single-person motion, English
|
| 330 |
+
descriptions, 20 fps, 22 joints. Multi-person interaction, object manipulation,
|
| 331 |
+
and highly stylised motion are out of distribution.
|
| 332 |
+
- Length is an **input**, not something the model infers from the text. Ask for
|
| 333 |
+
120 frames and you get 120 frames whether or not the description warrants it.
|
| 334 |
+
- K = 2 slots is a deliberately tight budget, chosen for this dataset. It is not
|
| 335 |
+
a universal setting: the SnapMoGen model in the code repository uses K = 4, and
|
| 336 |
+
the best budget is dataset-dependent.
|
| 337 |
+
- Captions come from a fine-tuned GPT-2 (124M) and are short and generic
|
| 338 |
+
relative to human references. They describe the dominant action reliably and
|
| 339 |
+
fine detail unreliably. On HumanML3D clip `000004`, whose reference reads *"a
|
| 340 |
+
man is pretending to be a chicken, constantly pecking at the ground and waving
|
| 341 |
+
his arms like a chicken"*, the model produces *"a person bends over to touch
|
| 342 |
+
the ground with their hands, then stands up straight"*: the gross movement is
|
| 343 |
+
right, the intent and the arm motion are gone.
|
| 344 |
+
- Not a safety-filtered model. It generates skeletal motion and short English
|
| 345 |
+
captions, and it has no content filtering of any kind.
|
| 346 |
+
|
| 347 |
+
## Citation
|
| 348 |
+
|
| 349 |
+
The paper is under review. A citation entry will be added here once it appears;
|
| 350 |
+
in the meantime please cite the repository and this model card.
|
| 351 |
+
|
| 352 |
+
```bibtex
|
| 353 |
+
@misc{mugen2026,
|
| 354 |
+
title = {MUGEN: A Unified Framework for Efficient Motion Understanding and Generation},
|
| 355 |
+
note = {Under review. Code: https://github.com/JYe16/MUGEN,
|
| 356 |
+
model: https://huggingface.co/zy22b/MUGEN},
|
| 357 |
+
year = {2026}
|
| 358 |
+
}
|
| 359 |
+
```
|
| 360 |
+
|
| 361 |
+
## Acknowledgements
|
| 362 |
+
|
| 363 |
+
Built on [HumanML3D](https://github.com/EricGuo5513/HumanML3D),
|
| 364 |
+
[MotionGPT](https://github.com/OpenMotionLab/MotionGPT),
|
| 365 |
+
[MLD](https://github.com/ChenFengYe/motion-latent-diffusion), and our earlier
|
| 366 |
+
discrete-codebook system
|
| 367 |
+
[GeoMotionGPT](https://huggingface.co/zy22b/GeoMotionGPT).
|
| 368 |
+
|
| 369 |
+
## License
|
| 370 |
+
|
| 371 |
+
MIT
|
added_tokens.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"<MOT>": 50260,
|
| 3 |
+
"<motion_id_0>": 50257,
|
| 4 |
+
"<motion_id_1>": 50258,
|
| 5 |
+
"<motion_id_2>": 50259
|
| 6 |
+
}
|
config.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"activation_function": "gelu_new",
|
| 3 |
+
"alae_activation": "gelu",
|
| 4 |
+
"alae_depth": 3,
|
| 5 |
+
"alae_dilation_growth_rate": 3,
|
| 6 |
+
"alae_dim_feedforward": 2048,
|
| 7 |
+
"alae_dropout": 0.05,
|
| 8 |
+
"alae_hidden_dim": 512,
|
| 9 |
+
"alae_max_decode_len": 256,
|
| 10 |
+
"alae_nhead": 8,
|
| 11 |
+
"alae_norm": null,
|
| 12 |
+
"alae_num_decoder_layers": 4,
|
| 13 |
+
"alae_num_encoder_layers": 4,
|
| 14 |
+
"alae_num_res_blocks": 2,
|
| 15 |
+
"architectures": [
|
| 16 |
+
"MugenForConditionalGeneration"
|
| 17 |
+
],
|
| 18 |
+
"attn_pdrop": 0.1,
|
| 19 |
+
"auto_map": {
|
| 20 |
+
"AutoConfig": "configuration_mugen.MugenConfig",
|
| 21 |
+
"AutoModelForCausalLM": "modeling_mugen.MugenForConditionalGeneration"
|
| 22 |
+
},
|
| 23 |
+
"bos_token_id": 50256,
|
| 24 |
+
"embd_pdrop": 0.1,
|
| 25 |
+
"eos_token_id": 50256,
|
| 26 |
+
"eval_sample_temperature": 1.0,
|
| 27 |
+
"fps": 20,
|
| 28 |
+
"initializer_range": 0.02,
|
| 29 |
+
"k_latent_slots": 2,
|
| 30 |
+
"latent_dim": 512,
|
| 31 |
+
"latent_low_rank": 64,
|
| 32 |
+
"layer_norm_epsilon": 1e-05,
|
| 33 |
+
"length_multiple": 4,
|
| 34 |
+
"m2t_max_new_tokens": 64,
|
| 35 |
+
"m2t_num_beams": 1,
|
| 36 |
+
"m2t_prompt_prefix": "Please describe the following human motion using plain text:",
|
| 37 |
+
"model_type": "mugen",
|
| 38 |
+
"mot_token": "<MOT>",
|
| 39 |
+
"mot_token_id": 50260,
|
| 40 |
+
"motion_input_dim": 263,
|
| 41 |
+
"n_embd": 768,
|
| 42 |
+
"n_head": 12,
|
| 43 |
+
"n_inner": null,
|
| 44 |
+
"n_layer": 12,
|
| 45 |
+
"n_positions": 1024,
|
| 46 |
+
"num_cross_attn_layers": 2,
|
| 47 |
+
"num_joints": 22,
|
| 48 |
+
"pad_token_id": 50256,
|
| 49 |
+
"resid_pdrop": 0.1,
|
| 50 |
+
"router_delta_scale": 4.0,
|
| 51 |
+
"router_eval_tau": 1.5,
|
| 52 |
+
"router_hidden": 512,
|
| 53 |
+
"router_static_scale": 4.0,
|
| 54 |
+
"t2m_prompt_template": "Please generate human motion based on the following textual description: {text} {mot}",
|
| 55 |
+
"torch_dtype": "float32",
|
| 56 |
+
"transformers_version": "4.51.0",
|
| 57 |
+
"vocab_size": 50261
|
| 58 |
+
}
|
configuration_mugen.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MUGEN configuration.
|
| 2 |
+
|
| 3 |
+
MUGEN couples an adaptive-length motion autoencoder (ALAE) with a GPT-2
|
| 4 |
+
language model. The autoencoder compresses a motion clip of any length into K
|
| 5 |
+
continuous latent slots, and those slots are the only motion representation in
|
| 6 |
+
the system: the language model generates them for text-to-motion and reads
|
| 7 |
+
them back for motion understanding (captioning).
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from transformers import PretrainedConfig
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class MugenConfig(PretrainedConfig):
|
| 14 |
+
"""Configuration for :class:`MugenForConditionalGeneration`.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
motion_input_dim (`int`, *optional*, defaults to 263):
|
| 18 |
+
Dimensionality of one motion frame. 263 is the HumanML3D feature
|
| 19 |
+
layout (root velocities, RIC joint positions, 6D rotations, foot
|
| 20 |
+
contacts).
|
| 21 |
+
k_latent_slots (`int`, *optional*, defaults to 2):
|
| 22 |
+
Number of continuous latent slots K. The whole clip, whatever its
|
| 23 |
+
length, is represented by exactly K vectors.
|
| 24 |
+
latent_dim (`int`, *optional*, defaults to 512):
|
| 25 |
+
Width of one latent slot.
|
| 26 |
+
alae_hidden_dim (`int`, *optional*, defaults to 512):
|
| 27 |
+
Width of the autoencoder's convolutional trunk.
|
| 28 |
+
alae_depth (`int`, *optional*, defaults to 3):
|
| 29 |
+
Number of dilated convolutions inside one residual block.
|
| 30 |
+
alae_dilation_growth_rate (`int`, *optional*, defaults to 3):
|
| 31 |
+
Dilation growth factor across the residual stack.
|
| 32 |
+
alae_activation (`str`, *optional*, defaults to `"gelu"`):
|
| 33 |
+
Activation used throughout the autoencoder.
|
| 34 |
+
alae_norm (`str`, *optional*, defaults to `None`):
|
| 35 |
+
Normalisation inside the residual blocks (`None`, `"LN"`, `"GN"`,
|
| 36 |
+
`"BN"`).
|
| 37 |
+
alae_num_res_blocks (`int`, *optional*, defaults to 2):
|
| 38 |
+
Residual blocks in the encoder trunk and the decoder refiner.
|
| 39 |
+
alae_num_encoder_layers (`int`, *optional*, defaults to 4):
|
| 40 |
+
Cross-attention blocks that compress frames into the K slots.
|
| 41 |
+
alae_num_decoder_layers (`int`, *optional*, defaults to 4):
|
| 42 |
+
Cross-attention blocks that expand the K slots back to frames.
|
| 43 |
+
alae_nhead (`int`, *optional*, defaults to 8):
|
| 44 |
+
Attention heads in the autoencoder's cross-attention blocks.
|
| 45 |
+
alae_dim_feedforward (`int`, *optional*, defaults to 2048):
|
| 46 |
+
Feed-forward width in those blocks.
|
| 47 |
+
alae_dropout (`float`, *optional*, defaults to 0.05):
|
| 48 |
+
Dropout in those blocks (inactive at inference).
|
| 49 |
+
alae_max_decode_len (`int`, *optional*, defaults to 256):
|
| 50 |
+
Size of the decoder's positional table. Query positions are spread
|
| 51 |
+
across the whole table so a position encodes the frame's relative
|
| 52 |
+
phase in the clip rather than an absolute frame index, which is
|
| 53 |
+
what lets one decoder serve every clip length.
|
| 54 |
+
vocab_size (`int`, *optional*, defaults to 50261):
|
| 55 |
+
Language-model vocabulary: GPT-2's 50257 plus three reserved
|
| 56 |
+
motion-id slots and the `<MOT>` seed token.
|
| 57 |
+
n_positions, n_embd, n_layer, n_head, n_inner, activation_function,
|
| 58 |
+
resid_pdrop, embd_pdrop, attn_pdrop, layer_norm_epsilon,
|
| 59 |
+
initializer_range:
|
| 60 |
+
Standard GPT-2 hyperparameters.
|
| 61 |
+
num_cross_attn_layers (`int`, *optional*, defaults to 2):
|
| 62 |
+
Cross-attention blocks in the layer router's text encoder.
|
| 63 |
+
router_hidden (`int`, *optional*, defaults to 512):
|
| 64 |
+
Hidden width of the router MLP.
|
| 65 |
+
router_static_scale (`float`, *optional*, defaults to 4.0):
|
| 66 |
+
Bound on the router's text-independent logit component.
|
| 67 |
+
router_delta_scale (`float`, *optional*, defaults to 4.0):
|
| 68 |
+
Bound on the router's text-conditional logit component. Both parts
|
| 69 |
+
are tanh-capped, so no logit margin can saturate the softmax.
|
| 70 |
+
router_eval_tau (`float`, *optional*, defaults to 1.5):
|
| 71 |
+
Inference temperature of the routing softmax. This is the converged
|
| 72 |
+
end of the training anneal; changing it changes which transformer
|
| 73 |
+
depth each slot reads from.
|
| 74 |
+
latent_low_rank (`int`, *optional*, defaults to 64):
|
| 75 |
+
Rank r of the calibrated head's covariance. The head predicts
|
| 76 |
+
`N(mu, U diag(a)^2 U^T + diag(sigma^2))` over the flattened K*D
|
| 77 |
+
latent, so a single draw carries variance correlated across slots.
|
| 78 |
+
eval_sample_temperature (`float`, *optional*, defaults to 1.0):
|
| 79 |
+
Default multiplier on the sampled perturbation around `mu`. 1.0 is
|
| 80 |
+
the calibrated conditional distribution and the setting every
|
| 81 |
+
reported number uses; 0.0 decodes the mean.
|
| 82 |
+
m2t_max_new_tokens (`int`, *optional*, defaults to 64):
|
| 83 |
+
Caption length cap for motion-to-text generation.
|
| 84 |
+
m2t_num_beams (`int`, *optional*, defaults to 1):
|
| 85 |
+
Beam count for captioning.
|
| 86 |
+
mot_token (`str`, *optional*, defaults to `"<MOT>"`):
|
| 87 |
+
Seed token that ends the text-to-motion prompt.
|
| 88 |
+
mot_token_id (`int`, *optional*, defaults to 50260):
|
| 89 |
+
Token id of `mot_token` in the shipped tokenizer.
|
| 90 |
+
t2m_prompt_template (`str`, *optional*):
|
| 91 |
+
Prompt used for text-to-motion. Must end in `mot_token`.
|
| 92 |
+
m2t_prompt_prefix (`str`, *optional*):
|
| 93 |
+
Instruction placed before the motion slots for captioning.
|
| 94 |
+
fps (`int`, *optional*, defaults to 20):
|
| 95 |
+
Frame rate of the HumanML3D features this model was trained on.
|
| 96 |
+
num_joints (`int`, *optional*, defaults to 22):
|
| 97 |
+
Joint count recovered by `features_to_joints`.
|
| 98 |
+
length_multiple (`int`, *optional*, defaults to 4):
|
| 99 |
+
HumanML3D crops clips to a multiple of 4 frames; requesting a
|
| 100 |
+
length off this grid moves you away from the training distribution.
|
| 101 |
+
|
| 102 |
+
Example:
|
| 103 |
+
```python
|
| 104 |
+
from transformers import AutoConfig
|
| 105 |
+
|
| 106 |
+
config = AutoConfig.from_pretrained("zy22b/MUGEN", trust_remote_code=True)
|
| 107 |
+
print(config.k_latent_slots) # 2
|
| 108 |
+
```
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
model_type = "mugen"
|
| 112 |
+
|
| 113 |
+
def __init__(
|
| 114 |
+
self,
|
| 115 |
+
# ---- motion autoencoder (ALAE) ----
|
| 116 |
+
motion_input_dim: int = 263,
|
| 117 |
+
k_latent_slots: int = 2,
|
| 118 |
+
latent_dim: int = 512,
|
| 119 |
+
alae_hidden_dim: int = 512,
|
| 120 |
+
alae_depth: int = 3,
|
| 121 |
+
alae_dilation_growth_rate: int = 3,
|
| 122 |
+
alae_activation: str = "gelu",
|
| 123 |
+
alae_norm=None,
|
| 124 |
+
alae_num_res_blocks: int = 2,
|
| 125 |
+
alae_num_encoder_layers: int = 4,
|
| 126 |
+
alae_num_decoder_layers: int = 4,
|
| 127 |
+
alae_nhead: int = 8,
|
| 128 |
+
alae_dim_feedforward: int = 2048,
|
| 129 |
+
alae_dropout: float = 0.05,
|
| 130 |
+
alae_max_decode_len: int = 256,
|
| 131 |
+
# ---- language model (GPT-2) ----
|
| 132 |
+
vocab_size: int = 50261,
|
| 133 |
+
n_positions: int = 1024,
|
| 134 |
+
n_embd: int = 768,
|
| 135 |
+
n_layer: int = 12,
|
| 136 |
+
n_head: int = 12,
|
| 137 |
+
n_inner=None,
|
| 138 |
+
activation_function: str = "gelu_new",
|
| 139 |
+
resid_pdrop: float = 0.1,
|
| 140 |
+
embd_pdrop: float = 0.1,
|
| 141 |
+
attn_pdrop: float = 0.1,
|
| 142 |
+
layer_norm_epsilon: float = 1e-5,
|
| 143 |
+
initializer_range: float = 0.02,
|
| 144 |
+
# ---- layer router ----
|
| 145 |
+
num_cross_attn_layers: int = 2,
|
| 146 |
+
router_hidden: int = 512,
|
| 147 |
+
router_static_scale: float = 4.0,
|
| 148 |
+
router_delta_scale: float = 4.0,
|
| 149 |
+
router_eval_tau: float = 1.5,
|
| 150 |
+
# ---- calibrated latent head ----
|
| 151 |
+
latent_low_rank: int = 64,
|
| 152 |
+
eval_sample_temperature: float = 1.0,
|
| 153 |
+
# ---- motion understanding ----
|
| 154 |
+
m2t_max_new_tokens: int = 64,
|
| 155 |
+
m2t_num_beams: int = 1,
|
| 156 |
+
# ---- prompting ----
|
| 157 |
+
mot_token: str = "<MOT>",
|
| 158 |
+
mot_token_id: int = 50260,
|
| 159 |
+
t2m_prompt_template: str = (
|
| 160 |
+
"Please generate human motion based on the following textual "
|
| 161 |
+
"description: {text} {mot}"
|
| 162 |
+
),
|
| 163 |
+
m2t_prompt_prefix: str = (
|
| 164 |
+
"Please describe the following human motion using plain text:"
|
| 165 |
+
),
|
| 166 |
+
# ---- data conventions ----
|
| 167 |
+
fps: int = 20,
|
| 168 |
+
num_joints: int = 22,
|
| 169 |
+
length_multiple: int = 4,
|
| 170 |
+
# ---- special tokens ----
|
| 171 |
+
bos_token_id: int = 50256,
|
| 172 |
+
eos_token_id: int = 50256,
|
| 173 |
+
pad_token_id: int = 50256,
|
| 174 |
+
**kwargs,
|
| 175 |
+
):
|
| 176 |
+
self.motion_input_dim = motion_input_dim
|
| 177 |
+
self.k_latent_slots = k_latent_slots
|
| 178 |
+
self.latent_dim = latent_dim
|
| 179 |
+
self.alae_hidden_dim = alae_hidden_dim
|
| 180 |
+
self.alae_depth = alae_depth
|
| 181 |
+
self.alae_dilation_growth_rate = alae_dilation_growth_rate
|
| 182 |
+
self.alae_activation = alae_activation
|
| 183 |
+
self.alae_norm = alae_norm
|
| 184 |
+
self.alae_num_res_blocks = alae_num_res_blocks
|
| 185 |
+
self.alae_num_encoder_layers = alae_num_encoder_layers
|
| 186 |
+
self.alae_num_decoder_layers = alae_num_decoder_layers
|
| 187 |
+
self.alae_nhead = alae_nhead
|
| 188 |
+
self.alae_dim_feedforward = alae_dim_feedforward
|
| 189 |
+
self.alae_dropout = alae_dropout
|
| 190 |
+
self.alae_max_decode_len = alae_max_decode_len
|
| 191 |
+
|
| 192 |
+
self.vocab_size = vocab_size
|
| 193 |
+
self.n_positions = n_positions
|
| 194 |
+
self.n_embd = n_embd
|
| 195 |
+
self.n_layer = n_layer
|
| 196 |
+
self.n_head = n_head
|
| 197 |
+
self.n_inner = n_inner
|
| 198 |
+
self.activation_function = activation_function
|
| 199 |
+
self.resid_pdrop = resid_pdrop
|
| 200 |
+
self.embd_pdrop = embd_pdrop
|
| 201 |
+
self.attn_pdrop = attn_pdrop
|
| 202 |
+
self.layer_norm_epsilon = layer_norm_epsilon
|
| 203 |
+
self.initializer_range = initializer_range
|
| 204 |
+
|
| 205 |
+
self.num_cross_attn_layers = num_cross_attn_layers
|
| 206 |
+
self.router_hidden = router_hidden
|
| 207 |
+
self.router_static_scale = router_static_scale
|
| 208 |
+
self.router_delta_scale = router_delta_scale
|
| 209 |
+
self.router_eval_tau = router_eval_tau
|
| 210 |
+
|
| 211 |
+
self.latent_low_rank = latent_low_rank
|
| 212 |
+
self.eval_sample_temperature = eval_sample_temperature
|
| 213 |
+
|
| 214 |
+
self.m2t_max_new_tokens = m2t_max_new_tokens
|
| 215 |
+
self.m2t_num_beams = m2t_num_beams
|
| 216 |
+
|
| 217 |
+
self.mot_token = mot_token
|
| 218 |
+
self.mot_token_id = mot_token_id
|
| 219 |
+
self.t2m_prompt_template = t2m_prompt_template
|
| 220 |
+
self.m2t_prompt_prefix = m2t_prompt_prefix
|
| 221 |
+
|
| 222 |
+
self.fps = fps
|
| 223 |
+
self.num_joints = num_joints
|
| 224 |
+
self.length_multiple = length_multiple
|
| 225 |
+
|
| 226 |
+
super().__init__(
|
| 227 |
+
bos_token_id=bos_token_id,
|
| 228 |
+
eos_token_id=eos_token_id,
|
| 229 |
+
pad_token_id=pad_token_id,
|
| 230 |
+
**kwargs,
|
| 231 |
+
)
|
merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:81bb85c54cdd870742c2ff84929476ac58c112da94386fb79cbb810c3b8ad79e
|
| 3 |
+
size 734822348
|
modeling_mugen.py
ADDED
|
@@ -0,0 +1,899 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MUGEN: a unified motion-language model.
|
| 2 |
+
|
| 3 |
+
The model has three parts:
|
| 4 |
+
|
| 5 |
+
1. An adaptive-length motion autoencoder (ALAE). Cross-attention compresses a
|
| 6 |
+
clip of any length into K continuous latent slots, and a second
|
| 7 |
+
cross-attention stack expands those slots back to any requested number of
|
| 8 |
+
frames. There is no codebook and no quantisation anywhere in the pipeline.
|
| 9 |
+
2. A GPT-2 language model. For text-to-motion it rolls out K hidden states
|
| 10 |
+
after a `<MOT>` seed token; for captioning it reads the K slots back as
|
| 11 |
+
continuous input embeddings.
|
| 12 |
+
3. A layer router and a calibrated latent head. The router gives each slot its
|
| 13 |
+
own soft mixture over all twelve transformer layers, so a slot reads from
|
| 14 |
+
the depth it needs instead of the final layer only. The head predicts a
|
| 15 |
+
joint Gaussian over the whole flattened latent set with a rank-64 plus
|
| 16 |
+
diagonal covariance, so one draw carries text-conditional variance that is
|
| 17 |
+
correlated across slots.
|
| 18 |
+
|
| 19 |
+
Generating a motion therefore costs K language-model steps, one draw, and one
|
| 20 |
+
decoder pass. No iterative refinement, no residual stages, no denoising chain.
|
| 21 |
+
|
| 22 |
+
Usage:
|
| 23 |
+
```python
|
| 24 |
+
import torch
|
| 25 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 26 |
+
|
| 27 |
+
model = AutoModelForCausalLM.from_pretrained("zy22b/MUGEN", trust_remote_code=True).eval()
|
| 28 |
+
tokenizer = AutoTokenizer.from_pretrained("zy22b/MUGEN")
|
| 29 |
+
|
| 30 |
+
feats = model.generate_motion(["a person walks forward and waves."],
|
| 31 |
+
lengths=[120], tokenizer=tokenizer)
|
| 32 |
+
joints = model.features_to_joints(feats) # (1, 120, 22, 3)
|
| 33 |
+
caption = model.generate_caption(feats, tokenizer=tokenizer)
|
| 34 |
+
```
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
from __future__ import annotations
|
| 38 |
+
|
| 39 |
+
import math
|
| 40 |
+
from typing import List, Optional, Sequence, Tuple, Union
|
| 41 |
+
|
| 42 |
+
import torch
|
| 43 |
+
import torch.nn as nn
|
| 44 |
+
import torch.nn.functional as F
|
| 45 |
+
from transformers import GPT2Config, GPT2LMHeadModel, PreTrainedModel
|
| 46 |
+
from transformers.modeling_outputs import ModelOutput
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
from .configuration_mugen import MugenConfig
|
| 50 |
+
except ImportError: # standalone / trust_remote_code loading
|
| 51 |
+
from configuration_mugen import MugenConfig
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# =====================================================================
|
| 55 |
+
# Motion autoencoder building blocks
|
| 56 |
+
# =====================================================================
|
| 57 |
+
|
| 58 |
+
class Swish(nn.Module):
|
| 59 |
+
"""x * sigmoid(x)."""
|
| 60 |
+
|
| 61 |
+
def forward(self, x):
|
| 62 |
+
return x * torch.sigmoid(x)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _activation(name: str) -> nn.Module:
|
| 66 |
+
if name == "relu":
|
| 67 |
+
return nn.ReLU()
|
| 68 |
+
if name == "gelu":
|
| 69 |
+
return nn.GELU()
|
| 70 |
+
if name == "silu":
|
| 71 |
+
return Swish()
|
| 72 |
+
raise ValueError(f"Unsupported activation: {name}")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class ResConv1DBlock(nn.Module):
|
| 76 |
+
"""Dilated residual convolution over the time axis."""
|
| 77 |
+
|
| 78 |
+
def __init__(self, n_in, n_state, dilation=1, activation="gelu", norm=None):
|
| 79 |
+
super().__init__()
|
| 80 |
+
padding = dilation
|
| 81 |
+
self.norm = norm
|
| 82 |
+
if norm == "LN":
|
| 83 |
+
self.norm1 = nn.LayerNorm(n_in)
|
| 84 |
+
self.norm2 = nn.LayerNorm(n_in)
|
| 85 |
+
elif norm == "GN":
|
| 86 |
+
self.norm1 = nn.GroupNorm(32, n_in, eps=1e-6, affine=True)
|
| 87 |
+
self.norm2 = nn.GroupNorm(32, n_in, eps=1e-6, affine=True)
|
| 88 |
+
elif norm == "BN":
|
| 89 |
+
self.norm1 = nn.BatchNorm1d(n_in, eps=1e-6, affine=True)
|
| 90 |
+
self.norm2 = nn.BatchNorm1d(n_in, eps=1e-6, affine=True)
|
| 91 |
+
else:
|
| 92 |
+
self.norm1 = nn.Identity()
|
| 93 |
+
self.norm2 = nn.Identity()
|
| 94 |
+
|
| 95 |
+
self.activation1 = _activation(activation)
|
| 96 |
+
self.activation2 = _activation(activation)
|
| 97 |
+
self.conv1 = nn.Conv1d(n_in, n_state, 3, 1, padding, dilation)
|
| 98 |
+
self.conv2 = nn.Conv1d(n_state, n_in, 1, 1, 0)
|
| 99 |
+
|
| 100 |
+
def forward(self, x):
|
| 101 |
+
x_orig = x
|
| 102 |
+
if self.norm == "LN":
|
| 103 |
+
x = self.norm1(x.transpose(-2, -1))
|
| 104 |
+
x = self.activation1(x.transpose(-2, -1))
|
| 105 |
+
else:
|
| 106 |
+
x = self.norm1(x)
|
| 107 |
+
x = self.activation1(x)
|
| 108 |
+
x = self.conv1(x)
|
| 109 |
+
if self.norm == "LN":
|
| 110 |
+
x = self.norm2(x.transpose(-2, -1))
|
| 111 |
+
x = self.activation2(x.transpose(-2, -1))
|
| 112 |
+
else:
|
| 113 |
+
x = self.norm2(x)
|
| 114 |
+
x = self.activation2(x)
|
| 115 |
+
x = self.conv2(x)
|
| 116 |
+
return x + x_orig
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class Resnet1D(nn.Module):
|
| 120 |
+
"""Stack of dilated residual convolutions."""
|
| 121 |
+
|
| 122 |
+
def __init__(self, n_in, n_depth, dilation_growth_rate=1,
|
| 123 |
+
reverse_dilation=True, activation="gelu", norm=None):
|
| 124 |
+
super().__init__()
|
| 125 |
+
blocks = [
|
| 126 |
+
ResConv1DBlock(n_in, n_in, dilation=dilation_growth_rate ** depth,
|
| 127 |
+
activation=activation, norm=norm)
|
| 128 |
+
for depth in range(n_depth)
|
| 129 |
+
]
|
| 130 |
+
if reverse_dilation:
|
| 131 |
+
blocks = blocks[::-1]
|
| 132 |
+
self.model = nn.Sequential(*blocks)
|
| 133 |
+
|
| 134 |
+
def forward(self, x):
|
| 135 |
+
return self.model(x)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class CrossAttentionBlock(nn.Module):
|
| 139 |
+
"""Pre-norm self-attention, cross-attention and feed-forward block.
|
| 140 |
+
|
| 141 |
+
`memory_key_padding_mask` (True marks padding) is forwarded to the
|
| 142 |
+
cross-attention. Passing `None` reproduces the unmasked block exactly.
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
def __init__(self, d_model, nhead, dim_feedforward, dropout=0.1, activation="gelu"):
|
| 146 |
+
super().__init__()
|
| 147 |
+
self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout, batch_first=True)
|
| 148 |
+
self.cross_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout, batch_first=True)
|
| 149 |
+
self.linear1 = nn.Linear(d_model, dim_feedforward)
|
| 150 |
+
self.linear2 = nn.Linear(dim_feedforward, d_model)
|
| 151 |
+
self.dropout = nn.Dropout(dropout)
|
| 152 |
+
|
| 153 |
+
self.norm1 = nn.LayerNorm(d_model)
|
| 154 |
+
self.norm2 = nn.LayerNorm(d_model)
|
| 155 |
+
self.norm3 = nn.LayerNorm(d_model)
|
| 156 |
+
self.drop1 = nn.Dropout(dropout)
|
| 157 |
+
self.drop2 = nn.Dropout(dropout)
|
| 158 |
+
self.drop3 = nn.Dropout(dropout)
|
| 159 |
+
self.activation = _activation(activation)
|
| 160 |
+
|
| 161 |
+
def forward(self, target, memory, memory_key_padding_mask=None):
|
| 162 |
+
x = self.norm1(target)
|
| 163 |
+
x_sa, _ = self.self_attn(x, x, x, need_weights=False)
|
| 164 |
+
target = target + self.drop1(x_sa)
|
| 165 |
+
|
| 166 |
+
x = self.norm2(target)
|
| 167 |
+
x_ca, _ = self.cross_attn(
|
| 168 |
+
x, memory, memory, need_weights=False,
|
| 169 |
+
key_padding_mask=memory_key_padding_mask,
|
| 170 |
+
)
|
| 171 |
+
target = target + self.drop2(x_ca)
|
| 172 |
+
|
| 173 |
+
x = self.norm3(target)
|
| 174 |
+
x_ff = self.linear2(self.dropout(self.activation(self.linear1(x))))
|
| 175 |
+
target = target + self.drop3(x_ff)
|
| 176 |
+
return target
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
class MotionEncoderBackbone(nn.Module):
|
| 180 |
+
"""Per-frame convolutional trunk feeding the cross-attention encoder."""
|
| 181 |
+
|
| 182 |
+
def __init__(self, input_dim, hidden_dim, latent_dim, depth,
|
| 183 |
+
dilation_growth_rate, activation, norm, num_res_blocks):
|
| 184 |
+
super().__init__()
|
| 185 |
+
blocks = [
|
| 186 |
+
nn.Conv1d(input_dim, hidden_dim, kernel_size=3, stride=1, padding=1),
|
| 187 |
+
_activation(activation),
|
| 188 |
+
]
|
| 189 |
+
for _ in range(num_res_blocks):
|
| 190 |
+
blocks.append(Resnet1D(hidden_dim, depth, dilation_growth_rate,
|
| 191 |
+
reverse_dilation=False, activation=activation, norm=norm))
|
| 192 |
+
self.backbone = nn.Sequential(*blocks)
|
| 193 |
+
self.output_proj = nn.Conv1d(hidden_dim, latent_dim, kernel_size=1)
|
| 194 |
+
|
| 195 |
+
def forward(self, x):
|
| 196 |
+
return self.output_proj(self.backbone(x))
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
class MotionDecoderRefiner(nn.Module):
|
| 200 |
+
"""Convolutional refiner turning decoded per-frame features into motion."""
|
| 201 |
+
|
| 202 |
+
def __init__(self, latent_dim, hidden_dim, output_dim, depth,
|
| 203 |
+
dilation_growth_rate, activation, norm, num_res_blocks):
|
| 204 |
+
super().__init__()
|
| 205 |
+
blocks = [
|
| 206 |
+
nn.Conv1d(latent_dim, hidden_dim, kernel_size=3, stride=1, padding=1),
|
| 207 |
+
_activation(activation),
|
| 208 |
+
]
|
| 209 |
+
for _ in range(num_res_blocks):
|
| 210 |
+
blocks.append(Resnet1D(hidden_dim, depth, dilation_growth_rate,
|
| 211 |
+
reverse_dilation=True, activation=activation, norm=norm))
|
| 212 |
+
blocks.extend([
|
| 213 |
+
nn.Conv1d(hidden_dim, hidden_dim, kernel_size=3, stride=1, padding=1),
|
| 214 |
+
_activation(activation),
|
| 215 |
+
nn.Conv1d(hidden_dim, output_dim, kernel_size=3, stride=1, padding=1),
|
| 216 |
+
])
|
| 217 |
+
self.model = nn.Sequential(*blocks)
|
| 218 |
+
|
| 219 |
+
def forward(self, x):
|
| 220 |
+
return self.model(x)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def build_sine_position_encoding(length, dim, device, dtype=torch.float32):
|
| 224 |
+
pe = torch.zeros(length, dim, device=device, dtype=dtype)
|
| 225 |
+
position = torch.arange(length, device=device, dtype=dtype).unsqueeze(1)
|
| 226 |
+
div_term = torch.exp(
|
| 227 |
+
torch.arange(0, dim, 2, device=device, dtype=dtype) * (-math.log(10000.0) / dim)
|
| 228 |
+
)
|
| 229 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 230 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 231 |
+
return pe
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
class AdaptiveLengthAutoEncoder(nn.Module):
|
| 235 |
+
"""Compress a clip of any length into K latent slots, and back again.
|
| 236 |
+
|
| 237 |
+
`encode` returns `(batch, K, latent_dim)` regardless of the input length.
|
| 238 |
+
`decode` takes those slots plus a target frame count. Decoder query
|
| 239 |
+
positions are spread evenly across the whole positional table, so a query
|
| 240 |
+
encodes a frame's relative phase within the clip rather than an absolute
|
| 241 |
+
frame index. That is what lets one decoder serve every clip length.
|
| 242 |
+
"""
|
| 243 |
+
|
| 244 |
+
def __init__(self, config: MugenConfig):
|
| 245 |
+
super().__init__()
|
| 246 |
+
self.input_dim = config.motion_input_dim
|
| 247 |
+
self.k = config.k_latent_slots
|
| 248 |
+
self.latent_dim = config.latent_dim
|
| 249 |
+
self.max_decode_len = config.alae_max_decode_len
|
| 250 |
+
|
| 251 |
+
common = dict(
|
| 252 |
+
depth=config.alae_depth,
|
| 253 |
+
dilation_growth_rate=config.alae_dilation_growth_rate,
|
| 254 |
+
activation=config.alae_activation,
|
| 255 |
+
norm=config.alae_norm,
|
| 256 |
+
num_res_blocks=config.alae_num_res_blocks,
|
| 257 |
+
)
|
| 258 |
+
self.encoder_backbone = MotionEncoderBackbone(
|
| 259 |
+
input_dim=config.motion_input_dim,
|
| 260 |
+
hidden_dim=config.alae_hidden_dim,
|
| 261 |
+
latent_dim=config.latent_dim,
|
| 262 |
+
**common,
|
| 263 |
+
)
|
| 264 |
+
self.latent_queries = nn.Parameter(torch.randn(self.k, config.latent_dim) * 0.02)
|
| 265 |
+
|
| 266 |
+
block_kwargs = dict(
|
| 267 |
+
d_model=config.latent_dim,
|
| 268 |
+
nhead=config.alae_nhead,
|
| 269 |
+
dim_feedforward=config.alae_dim_feedforward,
|
| 270 |
+
dropout=config.alae_dropout,
|
| 271 |
+
activation=config.alae_activation,
|
| 272 |
+
)
|
| 273 |
+
self.encoder_blocks = nn.ModuleList(
|
| 274 |
+
[CrossAttentionBlock(**block_kwargs) for _ in range(config.alae_num_encoder_layers)]
|
| 275 |
+
)
|
| 276 |
+
self.encoder_norm = nn.LayerNorm(config.latent_dim)
|
| 277 |
+
|
| 278 |
+
self.decoder_blocks = nn.ModuleList(
|
| 279 |
+
[CrossAttentionBlock(**block_kwargs) for _ in range(config.alae_num_decoder_layers)]
|
| 280 |
+
)
|
| 281 |
+
self.decoder_norm = nn.LayerNorm(config.latent_dim)
|
| 282 |
+
self.decoder_refiner = MotionDecoderRefiner(
|
| 283 |
+
latent_dim=config.latent_dim,
|
| 284 |
+
hidden_dim=config.alae_hidden_dim,
|
| 285 |
+
output_dim=config.motion_input_dim,
|
| 286 |
+
**common,
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
self.register_buffer(
|
| 290 |
+
"decoder_query_pe",
|
| 291 |
+
build_sine_position_encoding(self.max_decode_len, config.latent_dim,
|
| 292 |
+
device=torch.device("cpu")),
|
| 293 |
+
persistent=False,
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
def ensure_decode_capacity(self, target_len: int):
|
| 297 |
+
target_len = int(target_len)
|
| 298 |
+
if target_len <= self.max_decode_len:
|
| 299 |
+
return
|
| 300 |
+
self.max_decode_len = target_len
|
| 301 |
+
self.register_buffer(
|
| 302 |
+
"decoder_query_pe",
|
| 303 |
+
build_sine_position_encoding(target_len, self.latent_dim,
|
| 304 |
+
device=torch.device("cpu")),
|
| 305 |
+
persistent=False,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
def encode(self, motion: torch.Tensor) -> torch.Tensor:
|
| 309 |
+
"""`(batch, time, input_dim)` -> `(batch, K, latent_dim)`."""
|
| 310 |
+
x_in = motion.permute(0, 2, 1).float()
|
| 311 |
+
memory = self.encoder_backbone(x_in).permute(0, 2, 1)
|
| 312 |
+
queries = self.latent_queries.unsqueeze(0).expand(memory.shape[0], -1, -1)
|
| 313 |
+
latents = queries
|
| 314 |
+
for block in self.encoder_blocks:
|
| 315 |
+
latents = block(latents, memory)
|
| 316 |
+
return self.encoder_norm(latents)
|
| 317 |
+
|
| 318 |
+
def decode(self, latents: torch.Tensor, target_len: int) -> torch.Tensor:
|
| 319 |
+
"""`(batch, K, latent_dim)` -> `(batch, target_len, input_dim)`."""
|
| 320 |
+
target_len = int(target_len)
|
| 321 |
+
self.ensure_decode_capacity(target_len)
|
| 322 |
+
if target_len > 1:
|
| 323 |
+
idx = torch.linspace(0, self.max_decode_len - 1, target_len,
|
| 324 |
+
device=latents.device).round().long()
|
| 325 |
+
else:
|
| 326 |
+
idx = torch.zeros(target_len, device=latents.device, dtype=torch.long)
|
| 327 |
+
query_pe = self.decoder_query_pe.to(device=latents.device, dtype=latents.dtype)[idx]
|
| 328 |
+
decoded = query_pe.unsqueeze(0).expand(latents.shape[0], -1, -1)
|
| 329 |
+
for block in self.decoder_blocks:
|
| 330 |
+
decoded = block(decoded, latents)
|
| 331 |
+
decoded = self.decoder_norm(decoded).permute(0, 2, 1)
|
| 332 |
+
return self.decoder_refiner(decoded).permute(0, 2, 1)
|
| 333 |
+
|
| 334 |
+
def forward(self, motion: torch.Tensor, target_len: Optional[int] = None):
|
| 335 |
+
if target_len is None:
|
| 336 |
+
target_len = motion.shape[1]
|
| 337 |
+
latents = self.encode(motion)
|
| 338 |
+
return self.decode(latents, target_len), latents
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
# =====================================================================
|
| 342 |
+
# Quaternion helpers for joint recovery
|
| 343 |
+
# =====================================================================
|
| 344 |
+
|
| 345 |
+
def _qinv(q: torch.Tensor) -> torch.Tensor:
|
| 346 |
+
mask = torch.ones_like(q)
|
| 347 |
+
mask[..., 1:] = -mask[..., 1:]
|
| 348 |
+
return q * mask
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
def _qrot(q: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
|
| 352 |
+
original_shape = list(v.shape)
|
| 353 |
+
q = q.contiguous().view(-1, 4)
|
| 354 |
+
v = v.contiguous().view(-1, 3)
|
| 355 |
+
qvec = q[:, 1:]
|
| 356 |
+
uv = torch.cross(qvec, v, dim=1)
|
| 357 |
+
uuv = torch.cross(qvec, uv, dim=1)
|
| 358 |
+
return (v + 2 * (q[:, :1] * uv + uuv)).view(original_shape)
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
def recover_from_ric(features: torch.Tensor, num_joints: int) -> torch.Tensor:
|
| 362 |
+
"""HumanML3D features -> joint positions.
|
| 363 |
+
|
| 364 |
+
`features` are the DENORMALISED 263-dimensional vectors; the return value
|
| 365 |
+
is `(..., time, num_joints, 3)` in metres.
|
| 366 |
+
"""
|
| 367 |
+
rot_vel = features[..., 0]
|
| 368 |
+
r_rot_ang = torch.zeros_like(rot_vel)
|
| 369 |
+
r_rot_ang[..., 1:] = rot_vel[..., :-1]
|
| 370 |
+
r_rot_ang = torch.cumsum(r_rot_ang, dim=-1)
|
| 371 |
+
|
| 372 |
+
r_rot_quat = torch.zeros(features.shape[:-1] + (4,),
|
| 373 |
+
dtype=features.dtype, device=features.device)
|
| 374 |
+
r_rot_quat[..., 0] = torch.cos(r_rot_ang)
|
| 375 |
+
r_rot_quat[..., 2] = torch.sin(r_rot_ang)
|
| 376 |
+
|
| 377 |
+
r_pos = torch.zeros(features.shape[:-1] + (3,),
|
| 378 |
+
dtype=features.dtype, device=features.device)
|
| 379 |
+
r_pos[..., 1:, [0, 2]] = features[..., :-1, 1:3]
|
| 380 |
+
r_pos = _qrot(_qinv(r_rot_quat), r_pos)
|
| 381 |
+
r_pos = torch.cumsum(r_pos, dim=-2)
|
| 382 |
+
r_pos[..., 1] = features[..., 3]
|
| 383 |
+
|
| 384 |
+
positions = features[..., 4:(num_joints - 1) * 3 + 4]
|
| 385 |
+
positions = positions.view(positions.shape[:-1] + (-1, 3))
|
| 386 |
+
positions = _qrot(
|
| 387 |
+
_qinv(r_rot_quat[..., None, :]).expand(positions.shape[:-1] + (4,)), positions
|
| 388 |
+
)
|
| 389 |
+
positions[..., 0] += r_pos[..., 0:1]
|
| 390 |
+
positions[..., 2] += r_pos[..., 2:3]
|
| 391 |
+
return torch.cat([r_pos.unsqueeze(-2), positions], dim=-2)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
# =====================================================================
|
| 395 |
+
# Model
|
| 396 |
+
# =====================================================================
|
| 397 |
+
|
| 398 |
+
class MugenOutput(ModelOutput):
|
| 399 |
+
"""Output of :meth:`MugenForConditionalGeneration.forward`.
|
| 400 |
+
|
| 401 |
+
Attributes:
|
| 402 |
+
latents: `(batch, K, latent_dim)` slots, in raw autoencoder space.
|
| 403 |
+
reconstruction: `(batch, time, motion_input_dim)` when a motion was given.
|
| 404 |
+
logits: language-model logits when `input_ids` was given.
|
| 405 |
+
"""
|
| 406 |
+
|
| 407 |
+
latents: Optional[torch.FloatTensor] = None
|
| 408 |
+
reconstruction: Optional[torch.FloatTensor] = None
|
| 409 |
+
logits: Optional[torch.FloatTensor] = None
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
class MugenPreTrainedModel(PreTrainedModel):
|
| 413 |
+
config_class = MugenConfig
|
| 414 |
+
base_model_prefix = "mugen"
|
| 415 |
+
supports_gradient_checkpointing = False
|
| 416 |
+
|
| 417 |
+
def _init_weights(self, module):
|
| 418 |
+
std = self.config.initializer_range
|
| 419 |
+
if isinstance(module, (nn.Linear, nn.Conv1d)):
|
| 420 |
+
module.weight.data.normal_(mean=0.0, std=std)
|
| 421 |
+
if module.bias is not None:
|
| 422 |
+
module.bias.data.zero_()
|
| 423 |
+
elif isinstance(module, nn.Embedding):
|
| 424 |
+
module.weight.data.normal_(mean=0.0, std=std)
|
| 425 |
+
if module.padding_idx is not None:
|
| 426 |
+
module.weight.data[module.padding_idx].zero_()
|
| 427 |
+
elif isinstance(module, nn.LayerNorm):
|
| 428 |
+
module.bias.data.zero_()
|
| 429 |
+
module.weight.data.fill_(1.0)
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
class MugenForConditionalGeneration(MugenPreTrainedModel):
|
| 433 |
+
"""MUGEN: text-to-motion generation and motion-to-text understanding.
|
| 434 |
+
|
| 435 |
+
The public API is three methods:
|
| 436 |
+
|
| 437 |
+
* :meth:`generate_motion` turns text into motion features.
|
| 438 |
+
* :meth:`generate_caption` turns motion into text.
|
| 439 |
+
* :meth:`encode_motion` / :meth:`decode_motion` expose the latent interface
|
| 440 |
+
both directions share.
|
| 441 |
+
|
| 442 |
+
:meth:`features_to_joints` converts motion features into joint positions
|
| 443 |
+
for rendering or measurement.
|
| 444 |
+
"""
|
| 445 |
+
|
| 446 |
+
_tied_weights_keys = ["language_model.lm_head.weight"]
|
| 447 |
+
_keys_to_ignore_on_load_missing = [r"motion_autoencoder\.decoder_query_pe"]
|
| 448 |
+
|
| 449 |
+
def __init__(self, config: MugenConfig):
|
| 450 |
+
super().__init__(config)
|
| 451 |
+
|
| 452 |
+
self.k = int(config.k_latent_slots)
|
| 453 |
+
self.latent_dim = int(config.latent_dim)
|
| 454 |
+
|
| 455 |
+
# ---- part 1: the motion autoencoder ----
|
| 456 |
+
self.motion_autoencoder = AdaptiveLengthAutoEncoder(config)
|
| 457 |
+
|
| 458 |
+
# ---- part 2: the language model ----
|
| 459 |
+
gpt2_config = GPT2Config(
|
| 460 |
+
vocab_size=config.vocab_size,
|
| 461 |
+
n_positions=config.n_positions,
|
| 462 |
+
n_embd=config.n_embd,
|
| 463 |
+
n_layer=config.n_layer,
|
| 464 |
+
n_head=config.n_head,
|
| 465 |
+
n_inner=config.n_inner,
|
| 466 |
+
activation_function=config.activation_function,
|
| 467 |
+
resid_pdrop=config.resid_pdrop,
|
| 468 |
+
embd_pdrop=config.embd_pdrop,
|
| 469 |
+
attn_pdrop=config.attn_pdrop,
|
| 470 |
+
layer_norm_epsilon=config.layer_norm_epsilon,
|
| 471 |
+
initializer_range=config.initializer_range,
|
| 472 |
+
bos_token_id=config.bos_token_id,
|
| 473 |
+
eos_token_id=config.eos_token_id,
|
| 474 |
+
tie_word_embeddings=True,
|
| 475 |
+
)
|
| 476 |
+
self.language_model = GPT2LMHeadModel(gpt2_config)
|
| 477 |
+
self.n_layers = int(config.n_layer)
|
| 478 |
+
hidden_size = int(config.n_embd)
|
| 479 |
+
|
| 480 |
+
# ---- part 3a: the layer router ----
|
| 481 |
+
# A slot's routing logits are a bounded static table plus a bounded
|
| 482 |
+
# text-conditional correction. Both halves are tanh-capped, so no logit
|
| 483 |
+
# margin can grow large enough to saturate the routing softmax.
|
| 484 |
+
self.static_router_logits = nn.Parameter(torch.zeros(self.k, self.n_layers))
|
| 485 |
+
self.text_mem_proj = nn.Linear(hidden_size, self.latent_dim)
|
| 486 |
+
self.stage2_latent_queries = nn.Parameter(torch.zeros(self.k, self.latent_dim))
|
| 487 |
+
self.cross_attn_blocks = nn.ModuleList([
|
| 488 |
+
CrossAttentionBlock(
|
| 489 |
+
d_model=self.latent_dim,
|
| 490 |
+
nhead=config.alae_nhead,
|
| 491 |
+
dim_feedforward=config.alae_dim_feedforward,
|
| 492 |
+
dropout=config.alae_dropout,
|
| 493 |
+
activation=config.alae_activation,
|
| 494 |
+
)
|
| 495 |
+
for _ in range(int(config.num_cross_attn_layers))
|
| 496 |
+
])
|
| 497 |
+
self.cross_attn_norm = nn.LayerNorm(self.latent_dim)
|
| 498 |
+
self.layer_router = nn.Sequential(
|
| 499 |
+
nn.Linear(self.latent_dim, int(config.router_hidden)),
|
| 500 |
+
nn.GELU(),
|
| 501 |
+
nn.Linear(int(config.router_hidden), self.n_layers),
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
# ---- part 3b: the calibrated latent head ----
|
| 505 |
+
self.projector_norm = nn.LayerNorm(hidden_size)
|
| 506 |
+
self.projector = nn.Linear(hidden_size, 2 * self.latent_dim)
|
| 507 |
+
self.feedback_norm = nn.LayerNorm(hidden_size)
|
| 508 |
+
self.latent_low_rank = int(config.latent_low_rank)
|
| 509 |
+
if self.latent_low_rank > 0:
|
| 510 |
+
self.latent_factors = nn.Parameter(
|
| 511 |
+
torch.zeros(self.k * self.latent_dim, self.latent_low_rank)
|
| 512 |
+
)
|
| 513 |
+
self.factor_scale_head = nn.Linear(hidden_size, self.latent_low_rank)
|
| 514 |
+
|
| 515 |
+
# ---- the understanding direction ----
|
| 516 |
+
self.motion_in_projector = nn.Linear(self.latent_dim, hidden_size)
|
| 517 |
+
|
| 518 |
+
# ---- normalisation statistics ----
|
| 519 |
+
# latent_*: standardise the latent space the head predicts in.
|
| 520 |
+
# feature_*: HumanML3D feature statistics, so the model can hand back
|
| 521 |
+
# denormalised motion without the caller owning the dataset.
|
| 522 |
+
self.register_buffer("latent_mean", torch.zeros(self.k, self.latent_dim))
|
| 523 |
+
self.register_buffer("latent_std", torch.ones(self.k, self.latent_dim))
|
| 524 |
+
self.register_buffer("feature_mean", torch.zeros(config.motion_input_dim))
|
| 525 |
+
self.register_buffer("feature_std", torch.ones(config.motion_input_dim))
|
| 526 |
+
|
| 527 |
+
self._tokenizer = None
|
| 528 |
+
self.post_init()
|
| 529 |
+
|
| 530 |
+
# ------------------------------------------------------------------
|
| 531 |
+
# plumbing
|
| 532 |
+
# ------------------------------------------------------------------
|
| 533 |
+
def get_input_embeddings(self):
|
| 534 |
+
return self.language_model.get_input_embeddings()
|
| 535 |
+
|
| 536 |
+
def set_input_embeddings(self, value):
|
| 537 |
+
self.language_model.set_input_embeddings(value)
|
| 538 |
+
|
| 539 |
+
def get_output_embeddings(self):
|
| 540 |
+
return self.language_model.get_output_embeddings()
|
| 541 |
+
|
| 542 |
+
def set_output_embeddings(self, new_embeddings):
|
| 543 |
+
self.language_model.set_output_embeddings(new_embeddings)
|
| 544 |
+
|
| 545 |
+
def set_tokenizer(self, tokenizer):
|
| 546 |
+
"""Cache a tokenizer so the generate helpers can be called without one."""
|
| 547 |
+
self._tokenizer = tokenizer
|
| 548 |
+
return self
|
| 549 |
+
|
| 550 |
+
def _get_tokenizer(self, tokenizer=None):
|
| 551 |
+
if tokenizer is not None:
|
| 552 |
+
return tokenizer
|
| 553 |
+
if self._tokenizer is None:
|
| 554 |
+
from transformers import AutoTokenizer
|
| 555 |
+
name = self.config._name_or_path or "zy22b/MUGEN"
|
| 556 |
+
self._tokenizer = AutoTokenizer.from_pretrained(name)
|
| 557 |
+
return self._tokenizer
|
| 558 |
+
|
| 559 |
+
# ------------------------------------------------------------------
|
| 560 |
+
# the latent interface both directions share
|
| 561 |
+
# ------------------------------------------------------------------
|
| 562 |
+
@torch.no_grad()
|
| 563 |
+
def encode_motion(self, motion: torch.Tensor, normalized: bool = False) -> torch.Tensor:
|
| 564 |
+
"""Motion -> K latent slots.
|
| 565 |
+
|
| 566 |
+
Args:
|
| 567 |
+
motion: `(batch, time, 263)` HumanML3D features.
|
| 568 |
+
normalized: `True` if `motion` is already standardised by the
|
| 569 |
+
dataset statistics. `False` (the default) normalises it here.
|
| 570 |
+
|
| 571 |
+
Returns:
|
| 572 |
+
`(batch, K, latent_dim)` slots in raw autoencoder space.
|
| 573 |
+
"""
|
| 574 |
+
motion = motion.to(self.device, dtype=torch.float32)
|
| 575 |
+
if not normalized:
|
| 576 |
+
motion = (motion - self.feature_mean) / self.feature_std
|
| 577 |
+
return self.motion_autoencoder.encode(motion)
|
| 578 |
+
|
| 579 |
+
@torch.no_grad()
|
| 580 |
+
def decode_motion(self, latents: torch.Tensor, length: int,
|
| 581 |
+
denormalize: bool = True) -> torch.Tensor:
|
| 582 |
+
"""K latent slots -> `(batch, length, 263)` motion features."""
|
| 583 |
+
latents = latents.to(self.device, dtype=torch.float32)
|
| 584 |
+
feats = self.motion_autoencoder.decode(latents, target_len=int(length))
|
| 585 |
+
if denormalize:
|
| 586 |
+
feats = feats * self.feature_std + self.feature_mean
|
| 587 |
+
return feats
|
| 588 |
+
|
| 589 |
+
def standardize_latents(self, latents: torch.Tensor) -> torch.Tensor:
|
| 590 |
+
return (latents - self.latent_mean) / self.latent_std
|
| 591 |
+
|
| 592 |
+
def destandardize_latents(self, latents: torch.Tensor) -> torch.Tensor:
|
| 593 |
+
return latents * self.latent_std + self.latent_mean
|
| 594 |
+
|
| 595 |
+
# ------------------------------------------------------------------
|
| 596 |
+
# text -> latents
|
| 597 |
+
# ------------------------------------------------------------------
|
| 598 |
+
def _build_t2m_prompts(self, texts: Sequence[str]) -> List[str]:
|
| 599 |
+
tpl = self.config.t2m_prompt_template
|
| 600 |
+
return [tpl.format(text=t.strip(), mot=self.config.mot_token) for t in texts]
|
| 601 |
+
|
| 602 |
+
def _routed_hidden(self, input_ids: torch.Tensor,
|
| 603 |
+
attention_mask: torch.Tensor) -> torch.Tensor:
|
| 604 |
+
"""Roll out K steps and return the routed per-slot hidden states.
|
| 605 |
+
|
| 606 |
+
Returns `(batch, K, hidden_size)`. Each slot is a soft mixture over all
|
| 607 |
+
`n_layers` transformer layers of its own rollout step, with mixture
|
| 608 |
+
weights produced by the router from the prompt.
|
| 609 |
+
"""
|
| 610 |
+
K = self.k
|
| 611 |
+
device = input_ids.device
|
| 612 |
+
prompt_len = input_ids.shape[1]
|
| 613 |
+
|
| 614 |
+
def _positions(start: int, count: int) -> torch.Tensor:
|
| 615 |
+
return torch.arange(start, start + count, dtype=torch.long,
|
| 616 |
+
device=device).unsqueeze(0)
|
| 617 |
+
|
| 618 |
+
out = self.language_model(
|
| 619 |
+
input_ids=input_ids,
|
| 620 |
+
attention_mask=attention_mask,
|
| 621 |
+
position_ids=_positions(0, prompt_len),
|
| 622 |
+
output_hidden_states=True,
|
| 623 |
+
use_cache=True,
|
| 624 |
+
return_dict=True,
|
| 625 |
+
)
|
| 626 |
+
|
| 627 |
+
# Router memory is the FULL last-layer token sequence plus its pad mask,
|
| 628 |
+
# so the routing queries can attend to individual words.
|
| 629 |
+
memory = self.text_mem_proj(out.hidden_states[-1])
|
| 630 |
+
pad_mask = attention_mask == 0
|
| 631 |
+
|
| 632 |
+
def _stack_all_layers(hidden_states) -> torch.Tensor:
|
| 633 |
+
cols = [h[:, -1:, :] for h in hidden_states[1:]]
|
| 634 |
+
return torch.stack(cols, dim=2) # (B, 1, L, H)
|
| 635 |
+
|
| 636 |
+
all_steps = [_stack_all_layers(out.hidden_states)]
|
| 637 |
+
last_hiddens = [out.hidden_states[-1][:, -1:, :]]
|
| 638 |
+
past = out.past_key_values
|
| 639 |
+
cur_attn = attention_mask
|
| 640 |
+
|
| 641 |
+
for step_idx in range(K - 1):
|
| 642 |
+
next_input = self.feedback_norm(last_hiddens[-1])
|
| 643 |
+
cur_attn = torch.cat(
|
| 644 |
+
[cur_attn, torch.ones((cur_attn.shape[0], 1), dtype=cur_attn.dtype, device=device)],
|
| 645 |
+
dim=1,
|
| 646 |
+
)
|
| 647 |
+
step = self.language_model(
|
| 648 |
+
inputs_embeds=next_input,
|
| 649 |
+
attention_mask=cur_attn,
|
| 650 |
+
position_ids=_positions(prompt_len + step_idx, 1),
|
| 651 |
+
past_key_values=past,
|
| 652 |
+
output_hidden_states=True,
|
| 653 |
+
use_cache=True,
|
| 654 |
+
return_dict=True,
|
| 655 |
+
)
|
| 656 |
+
past = step.past_key_values
|
| 657 |
+
all_steps.append(_stack_all_layers(step.hidden_states))
|
| 658 |
+
last_hiddens.append(step.hidden_states[-1][:, -1:, :])
|
| 659 |
+
|
| 660 |
+
H_all = torch.cat(all_steps, dim=1) # (B, K, L, H)
|
| 661 |
+
B = H_all.shape[0]
|
| 662 |
+
|
| 663 |
+
latents = self.stage2_latent_queries.unsqueeze(0).expand(B, -1, -1)
|
| 664 |
+
for block in self.cross_attn_blocks:
|
| 665 |
+
latents = block(latents, memory, memory_key_padding_mask=pad_mask)
|
| 666 |
+
latents = self.cross_attn_norm(latents) # (B, K, D)
|
| 667 |
+
|
| 668 |
+
s_s = float(self.config.router_static_scale)
|
| 669 |
+
s_d = float(self.config.router_delta_scale)
|
| 670 |
+
static = s_s * torch.tanh(self.static_router_logits / s_s) # (K, L)
|
| 671 |
+
delta = s_d * torch.tanh(self.layer_router(latents).float() / s_d)
|
| 672 |
+
logits = static.unsqueeze(0).float() + delta # (B, K, L)
|
| 673 |
+
|
| 674 |
+
tau = float(self.config.router_eval_tau)
|
| 675 |
+
weights = F.softmax(logits / tau, dim=-1).to(H_all.dtype)
|
| 676 |
+
return torch.einsum("bkl,bklh->bkh", weights, H_all)
|
| 677 |
+
|
| 678 |
+
def _latent_distribution(self, hidden: torch.Tensor):
|
| 679 |
+
"""Routed hidden states -> `(mu, logvar, log_a)` in standardised space."""
|
| 680 |
+
h = self.projector_norm(hidden)
|
| 681 |
+
mu, logvar = self.projector(h).chunk(2, dim=-1)
|
| 682 |
+
logvar = logvar.clamp(-10.0, 10.0)
|
| 683 |
+
log_a = None
|
| 684 |
+
if self.latent_low_rank > 0:
|
| 685 |
+
log_a = self.factor_scale_head(h.mean(dim=1)) # (B, r)
|
| 686 |
+
return mu, logvar, log_a
|
| 687 |
+
|
| 688 |
+
def sample_latents(self, mu: torch.Tensor, logvar: torch.Tensor,
|
| 689 |
+
log_a: Optional[torch.Tensor], temperature: float,
|
| 690 |
+
generator: Optional[torch.Generator] = None) -> torch.Tensor:
|
| 691 |
+
"""One structured draw `z = mu + t * (U (a * eps1) + sigma * eps2)`.
|
| 692 |
+
|
| 693 |
+
The rank-r factor term carries the variance that is correlated across
|
| 694 |
+
slots; the diagonal term carries the rest. `temperature` scales the
|
| 695 |
+
whole zero-mean perturbation, so 0.0 returns `mu` exactly.
|
| 696 |
+
"""
|
| 697 |
+
B, K, D = mu.shape
|
| 698 |
+
if temperature <= 0.0:
|
| 699 |
+
return mu
|
| 700 |
+
|
| 701 |
+
def _randn(like):
|
| 702 |
+
if generator is None:
|
| 703 |
+
return torch.randn_like(like)
|
| 704 |
+
return torch.randn(like.shape, generator=generator,
|
| 705 |
+
device=like.device, dtype=like.dtype)
|
| 706 |
+
|
| 707 |
+
# The factor noise is drawn BEFORE the diagonal noise, matching the
|
| 708 |
+
# training implementation, so a given seed reproduces the same draw
|
| 709 |
+
# here as it does there.
|
| 710 |
+
factor_part = 0.0
|
| 711 |
+
if log_a is not None:
|
| 712 |
+
a = torch.exp(log_a.clamp(-6.0, 4.0)) # (B, r)
|
| 713 |
+
factor_part = torch.einsum(
|
| 714 |
+
"dr,br->bd", self.latent_factors, a * _randn(a)
|
| 715 |
+
)
|
| 716 |
+
sigma = torch.exp(0.5 * logvar).reshape(B, K * D)
|
| 717 |
+
diag_part = sigma * _randn(sigma)
|
| 718 |
+
z = mu.reshape(B, K * D) + float(temperature) * (factor_part + diag_part)
|
| 719 |
+
return z.reshape(B, K, D)
|
| 720 |
+
|
| 721 |
+
@torch.no_grad()
|
| 722 |
+
def text_to_latents(self, texts: Union[str, Sequence[str]], tokenizer=None,
|
| 723 |
+
temperature: Optional[float] = None,
|
| 724 |
+
generator: Optional[torch.Generator] = None) -> torch.Tensor:
|
| 725 |
+
"""Text -> K latent slots in raw autoencoder space."""
|
| 726 |
+
if isinstance(texts, str):
|
| 727 |
+
texts = [texts]
|
| 728 |
+
tokenizer = self._get_tokenizer(tokenizer)
|
| 729 |
+
prev_side = tokenizer.padding_side
|
| 730 |
+
tokenizer.padding_side = "left"
|
| 731 |
+
try:
|
| 732 |
+
enc = tokenizer(
|
| 733 |
+
self._build_t2m_prompts(texts),
|
| 734 |
+
return_tensors="pt", padding=True, truncation=True, max_length=256,
|
| 735 |
+
)
|
| 736 |
+
finally:
|
| 737 |
+
tokenizer.padding_side = prev_side
|
| 738 |
+
enc = {k: v.to(self.device) for k, v in enc.items()}
|
| 739 |
+
|
| 740 |
+
hidden = self._routed_hidden(enc["input_ids"], enc["attention_mask"])
|
| 741 |
+
mu, logvar, log_a = self._latent_distribution(hidden)
|
| 742 |
+
temp = float(self.config.eval_sample_temperature if temperature is None else temperature)
|
| 743 |
+
z_norm = self.sample_latents(mu, logvar, log_a, temp, generator=generator)
|
| 744 |
+
return self.destandardize_latents(z_norm)
|
| 745 |
+
|
| 746 |
+
@torch.no_grad()
|
| 747 |
+
def generate_motion(self, texts: Union[str, Sequence[str]],
|
| 748 |
+
lengths: Union[int, Sequence[int], None] = None,
|
| 749 |
+
tokenizer=None,
|
| 750 |
+
temperature: Optional[float] = None,
|
| 751 |
+
denormalize: bool = True,
|
| 752 |
+
generator: Optional[torch.Generator] = None) -> torch.Tensor:
|
| 753 |
+
"""Text -> motion features.
|
| 754 |
+
|
| 755 |
+
Args:
|
| 756 |
+
texts: one description, or a batch of them.
|
| 757 |
+
lengths: target frame count per description (20 fps). A single int
|
| 758 |
+
applies to the whole batch. Defaults to 120 frames (6 seconds).
|
| 759 |
+
HumanML3D clips are multiples of 4 frames and at most 196, so
|
| 760 |
+
stay in that range to stay in distribution.
|
| 761 |
+
tokenizer: the tokenizer shipped with this repo. Loaded lazily when
|
| 762 |
+
omitted.
|
| 763 |
+
temperature: multiplier on the sampled perturbation. Defaults to
|
| 764 |
+
`config.eval_sample_temperature` (1.0), the calibrated
|
| 765 |
+
conditional distribution. 0.0 decodes the distribution mean,
|
| 766 |
+
which is not the protocol the published numbers use.
|
| 767 |
+
denormalize: return features in raw HumanML3D units. Keep this on
|
| 768 |
+
unless you intend to feed the output back into the encoder.
|
| 769 |
+
generator: optional `torch.Generator` for reproducible draws.
|
| 770 |
+
|
| 771 |
+
Returns:
|
| 772 |
+
`(batch, max_length, 263)`. When lengths differ within a batch, the
|
| 773 |
+
tensor is padded with zeros and each row is valid up to its own
|
| 774 |
+
length.
|
| 775 |
+
"""
|
| 776 |
+
if isinstance(texts, str):
|
| 777 |
+
texts = [texts]
|
| 778 |
+
texts = list(texts)
|
| 779 |
+
if lengths is None:
|
| 780 |
+
lengths = [120] * len(texts)
|
| 781 |
+
elif isinstance(lengths, int):
|
| 782 |
+
lengths = [lengths] * len(texts)
|
| 783 |
+
lengths = [int(v) for v in lengths]
|
| 784 |
+
if len(lengths) != len(texts):
|
| 785 |
+
raise ValueError(f"got {len(texts)} texts but {len(lengths)} lengths")
|
| 786 |
+
|
| 787 |
+
latents = self.text_to_latents(texts, tokenizer=tokenizer,
|
| 788 |
+
temperature=temperature, generator=generator)
|
| 789 |
+
|
| 790 |
+
max_len = max(lengths)
|
| 791 |
+
if len(set(lengths)) == 1:
|
| 792 |
+
return self.decode_motion(latents, max_len, denormalize=denormalize)
|
| 793 |
+
|
| 794 |
+
# Mixed lengths: the decoder's relative-phase queries mean a row must be
|
| 795 |
+
# decoded at its own length, not cropped from the longest one.
|
| 796 |
+
out = latents.new_zeros((len(texts), max_len, self.config.motion_input_dim))
|
| 797 |
+
for i, length in enumerate(lengths):
|
| 798 |
+
out[i, :length] = self.decode_motion(
|
| 799 |
+
latents[i: i + 1], length, denormalize=denormalize
|
| 800 |
+
)[0]
|
| 801 |
+
return out
|
| 802 |
+
|
| 803 |
+
# ------------------------------------------------------------------
|
| 804 |
+
# motion -> text
|
| 805 |
+
# ------------------------------------------------------------------
|
| 806 |
+
def _m2t_prompt_embeds(self, latents: torch.Tensor, tokenizer):
|
| 807 |
+
"""`[instruction, <MOT>, K motion slots]` as input embeddings."""
|
| 808 |
+
B = latents.shape[0]
|
| 809 |
+
device = latents.device
|
| 810 |
+
wte = self.language_model.get_input_embeddings()
|
| 811 |
+
|
| 812 |
+
prefix_ids = torch.tensor(
|
| 813 |
+
tokenizer(self.config.m2t_prompt_prefix, add_special_tokens=False)["input_ids"],
|
| 814 |
+
dtype=torch.long, device=device,
|
| 815 |
+
).unsqueeze(0).expand(B, -1)
|
| 816 |
+
mot_ids = torch.full((B, 1), int(self.config.mot_token_id),
|
| 817 |
+
dtype=torch.long, device=device)
|
| 818 |
+
|
| 819 |
+
prompt = torch.cat(
|
| 820 |
+
[wte(prefix_ids), wte(mot_ids), self.motion_in_projector(latents)], dim=1
|
| 821 |
+
)
|
| 822 |
+
attn = torch.ones(prompt.shape[:2], dtype=torch.long, device=device)
|
| 823 |
+
return prompt, attn
|
| 824 |
+
|
| 825 |
+
@torch.no_grad()
|
| 826 |
+
def generate_caption(self, motion: Optional[torch.Tensor] = None,
|
| 827 |
+
latents: Optional[torch.Tensor] = None,
|
| 828 |
+
tokenizer=None, normalized: bool = False,
|
| 829 |
+
max_new_tokens: Optional[int] = None,
|
| 830 |
+
num_beams: Optional[int] = None) -> List[str]:
|
| 831 |
+
"""Motion -> one caption per clip.
|
| 832 |
+
|
| 833 |
+
Pass either `motion` (`(batch, time, 263)` features) or `latents`
|
| 834 |
+
(`(batch, K, latent_dim)`) if you already have the slots. Set
|
| 835 |
+
`normalized=True` when `motion` is already standardised.
|
| 836 |
+
"""
|
| 837 |
+
if (motion is None) == (latents is None):
|
| 838 |
+
raise ValueError("pass exactly one of `motion` or `latents`")
|
| 839 |
+
tokenizer = self._get_tokenizer(tokenizer)
|
| 840 |
+
if latents is None:
|
| 841 |
+
latents = self.encode_motion(motion, normalized=normalized)
|
| 842 |
+
latents = latents.to(self.device, dtype=torch.float32)
|
| 843 |
+
|
| 844 |
+
prompt, attn = self._m2t_prompt_embeds(latents, tokenizer)
|
| 845 |
+
gen = self.language_model.generate(
|
| 846 |
+
inputs_embeds=prompt,
|
| 847 |
+
attention_mask=attn,
|
| 848 |
+
max_new_tokens=int(self.config.m2t_max_new_tokens
|
| 849 |
+
if max_new_tokens is None else max_new_tokens),
|
| 850 |
+
do_sample=False,
|
| 851 |
+
num_beams=int(self.config.m2t_num_beams if num_beams is None else num_beams),
|
| 852 |
+
pad_token_id=tokenizer.eos_token_id,
|
| 853 |
+
)
|
| 854 |
+
# Called with inputs_embeds and no input_ids, generate returns only the
|
| 855 |
+
# newly produced tokens.
|
| 856 |
+
return [t.strip() for t in tokenizer.batch_decode(gen, skip_special_tokens=True)]
|
| 857 |
+
|
| 858 |
+
# ------------------------------------------------------------------
|
| 859 |
+
# utilities
|
| 860 |
+
# ------------------------------------------------------------------
|
| 861 |
+
def features_to_joints(self, features: torch.Tensor,
|
| 862 |
+
normalized: bool = False) -> torch.Tensor:
|
| 863 |
+
"""Motion features -> `(batch, time, num_joints, 3)` joint positions in metres."""
|
| 864 |
+
features = features.to(self.device, dtype=torch.float32)
|
| 865 |
+
if normalized:
|
| 866 |
+
features = features * self.feature_std + self.feature_mean
|
| 867 |
+
return recover_from_ric(features, int(self.config.num_joints))
|
| 868 |
+
|
| 869 |
+
def forward(self,
|
| 870 |
+
motion: Optional[torch.Tensor] = None,
|
| 871 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 872 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 873 |
+
target_length: Optional[int] = None,
|
| 874 |
+
normalized: bool = False,
|
| 875 |
+
return_dict: bool = True,
|
| 876 |
+
**kwargs) -> MugenOutput:
|
| 877 |
+
"""Autoencode a motion, run the language model, or both.
|
| 878 |
+
|
| 879 |
+
This is the plumbing entry point. For generation use
|
| 880 |
+
:meth:`generate_motion` and :meth:`generate_caption`.
|
| 881 |
+
"""
|
| 882 |
+
latents = reconstruction = logits = None
|
| 883 |
+
if motion is not None:
|
| 884 |
+
motion = motion.to(self.device, dtype=torch.float32)
|
| 885 |
+
if not normalized:
|
| 886 |
+
motion = (motion - self.feature_mean) / self.feature_std
|
| 887 |
+
latents = self.motion_autoencoder.encode(motion)
|
| 888 |
+
reconstruction = self.motion_autoencoder.decode(
|
| 889 |
+
latents, target_len=int(target_length or motion.shape[1])
|
| 890 |
+
)
|
| 891 |
+
if input_ids is not None:
|
| 892 |
+
logits = self.language_model(
|
| 893 |
+
input_ids=input_ids, attention_mask=attention_mask, return_dict=True
|
| 894 |
+
).logits
|
| 895 |
+
return MugenOutput(latents=latents, reconstruction=reconstruction, logits=logits)
|
| 896 |
+
|
| 897 |
+
|
| 898 |
+
MugenConfig.register_for_auto_class()
|
| 899 |
+
MugenForConditionalGeneration.register_for_auto_class("AutoModelForCausalLM")
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
{
|
| 4 |
+
"content": "<MOT>",
|
| 5 |
+
"lstrip": false,
|
| 6 |
+
"normalized": false,
|
| 7 |
+
"rstrip": false,
|
| 8 |
+
"single_word": false
|
| 9 |
+
}
|
| 10 |
+
],
|
| 11 |
+
"bos_token": "<|endoftext|>",
|
| 12 |
+
"eos_token": "<|endoftext|>",
|
| 13 |
+
"pad_token": "<|endoftext|>",
|
| 14 |
+
"unk_token": "<|endoftext|>"
|
| 15 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"added_tokens_decoder": {
|
| 4 |
+
"50256": {
|
| 5 |
+
"content": "<|endoftext|>",
|
| 6 |
+
"lstrip": false,
|
| 7 |
+
"normalized": true,
|
| 8 |
+
"rstrip": false,
|
| 9 |
+
"single_word": false,
|
| 10 |
+
"special": true
|
| 11 |
+
},
|
| 12 |
+
"50257": {
|
| 13 |
+
"content": "<motion_id_0>",
|
| 14 |
+
"lstrip": false,
|
| 15 |
+
"normalized": true,
|
| 16 |
+
"rstrip": false,
|
| 17 |
+
"single_word": false,
|
| 18 |
+
"special": false
|
| 19 |
+
},
|
| 20 |
+
"50258": {
|
| 21 |
+
"content": "<motion_id_1>",
|
| 22 |
+
"lstrip": false,
|
| 23 |
+
"normalized": true,
|
| 24 |
+
"rstrip": false,
|
| 25 |
+
"single_word": false,
|
| 26 |
+
"special": false
|
| 27 |
+
},
|
| 28 |
+
"50259": {
|
| 29 |
+
"content": "<motion_id_2>",
|
| 30 |
+
"lstrip": false,
|
| 31 |
+
"normalized": true,
|
| 32 |
+
"rstrip": false,
|
| 33 |
+
"single_word": false,
|
| 34 |
+
"special": false
|
| 35 |
+
},
|
| 36 |
+
"50260": {
|
| 37 |
+
"content": "<MOT>",
|
| 38 |
+
"lstrip": false,
|
| 39 |
+
"normalized": false,
|
| 40 |
+
"rstrip": false,
|
| 41 |
+
"single_word": false,
|
| 42 |
+
"special": true
|
| 43 |
+
}
|
| 44 |
+
},
|
| 45 |
+
"additional_special_tokens": [
|
| 46 |
+
"<MOT>"
|
| 47 |
+
],
|
| 48 |
+
"bos_token": "<|endoftext|>",
|
| 49 |
+
"clean_up_tokenization_spaces": false,
|
| 50 |
+
"eos_token": "<|endoftext|>",
|
| 51 |
+
"extra_special_tokens": {},
|
| 52 |
+
"legacy": true,
|
| 53 |
+
"model_max_length": 1024,
|
| 54 |
+
"pad_token": "<|endoftext|>",
|
| 55 |
+
"tokenizer_class": "GPT2Tokenizer",
|
| 56 |
+
"unk_token": "<|endoftext|>"
|
| 57 |
+
}
|
vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|