Diffusers
Safetensors
English
motionbricks
modular-diffusers
motion-generation
motion-inbetweening
soma
Instructions to use OzzyGT/motionbricks_soma_diffusers with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use OzzyGT/motionbricks_soma_diffusers with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("OzzyGT/motionbricks_soma_diffusers", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
Upload 13 files
Browse files- LICENSE +59 -0
- NOTICE +14 -0
- README.md +71 -0
- modular_model_index.json +46 -0
- pose_decoder/config.json +19 -0
- pose_decoder/decoder_motionbricks.py +230 -0
- pose_decoder/diffusion_pytorch_model.safetensors +3 -0
- pose_model/config.json +22 -0
- pose_model/diffusion_pytorch_model.safetensors +3 -0
- pose_model/transformer_motionbricks.py +215 -0
- root_model/config.json +24 -0
- root_model/diffusion_pytorch_model.safetensors +3 -0
- root_model/root_motionbricks.py +430 -0
LICENSE
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
NVIDIA Open Model Agreement
|
| 2 |
+
|
| 3 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 4 |
+
|
| 5 |
+
Release Date: April 2, 2026
|
| 6 |
+
|
| 7 |
+
NVIDIA Works released under this License are intended to be used permissively and enable the further development of AI technologies. Subject to the terms of this License, NVIDIA confirms that:
|
| 8 |
+
|
| 9 |
+
• Works are commercially usable.
|
| 10 |
+
|
| 11 |
+
• You are free to create and distribute Derivative Works.
|
| 12 |
+
|
| 13 |
+
• NVIDIA does not claim ownership to any outputs generated using the Works or Derivative Works.
|
| 14 |
+
|
| 15 |
+
By using, reproducing, modifying, distributing, performing or displaying any portion or element of the Works or Derivative Works, or otherwise accepting the terms of this License, you agree to be bound by this License.
|
| 16 |
+
|
| 17 |
+
1. Definitions.
|
| 18 |
+
|
| 19 |
+
“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 10 of this document.
|
| 20 |
+
|
| 21 |
+
“Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.
|
| 24 |
+
|
| 25 |
+
“Work” shall mean the work of authorship, including machine learning model, software, checkpoints, learnt weights, algorithms, parameters, configuration files and documentation, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
| 26 |
+
|
| 27 |
+
“Derivative Works” shall mean any work, whether in source or object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
| 28 |
+
|
| 29 |
+
2. Grant of License. Subject to the terms and conditions of this License, NVIDIA hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in source or object form.
|
| 30 |
+
|
| 31 |
+
If You institute patent or copyright litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or an output from the Work constitutes direct or contributory patent or copyright infringement, then any licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
| 32 |
+
|
| 33 |
+
3. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in source or object form, provided that You meet the following conditions:
|
| 34 |
+
|
| 35 |
+
a. You must give any other recipients of the Work a copy of this License; and
|
| 36 |
+
|
| 37 |
+
b. You must retain, in the source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
| 38 |
+
|
| 39 |
+
c. If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the following attribution notice within a “Notice” text file with such copies and the following statement: “Licensed by NVIDIA Corporation under the NVIDIA Open Model Agreement.”
|
| 40 |
+
|
| 41 |
+
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
| 42 |
+
|
| 43 |
+
4. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of NVIDIA, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
| 44 |
+
|
| 45 |
+
5. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, NVIDIA provides the Work on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
| 46 |
+
|
| 47 |
+
6. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall NVIDIA be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work, a Derivative Work or an output from the Work or Derivative Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if NVIDIA has been advised of the possibility of such damages.
|
| 48 |
+
|
| 49 |
+
7. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of NVIDIA, and only if You agree to indemnify, defend, and hold NVIDIA harmless for any liability incurred by, or claims asserted against, NVIDIA by reason of your accepting any such warranty or additional liability. You will indemnify and hold harmless NVIDIA from and against any claim by any third party arising out of or related to your use or distribution of the Works, Derivative Works thereof, or output from the Works or Derivative Works.
|
| 50 |
+
|
| 51 |
+
8. Feedback. NVIDIA appreciates your feedback, and You agree that NVIDIA may use it without restriction or compensation to You.
|
| 52 |
+
|
| 53 |
+
9. Governing Law. This Agreement will be governed in all respects by the laws of the United States and the laws of the State of Delaware, without regard to conflict of laws principles or the United Nations Convention on Contracts for the International Sale of Goods. The state and federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to personal jurisdiction and venue in those courts; except that, either party may apply for injunctive remedies or an equivalent type of urgent legal relief in any jurisdiction.
|
| 54 |
+
|
| 55 |
+
10. Trade and Compliance. You agree to comply with all applicable export, import, trade and economic sanctions laws and regulations, as amended, including without limitation U.S. Export Administration Regulations and Office of Foreign Assets Control regulations. These laws include restrictions on destinations, end-users and end-use.
|
| 56 |
+
|
| 57 |
+
(v. March 9, 2026)
|
| 58 |
+
|
| 59 |
+
END OF TERMS AND CONDITIONS
|
NOTICE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Licensed by NVIDIA Corporation under the NVIDIA Open Model Agreement.
|
| 2 |
+
Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
|
| 4 |
+
The agreement is included as LICENSE and published at
|
| 5 |
+
https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-agreement/
|
| 6 |
+
|
| 7 |
+
This repository repacks the SOMA checkpoints of nvidia/MotionBricks (soma/soma-v6-nova-4n8g-ea43dd8-fp32-v1) for
|
| 8 |
+
Diffusers. The float32 tensors of the root model, the pose model and the decoder of the pose VQ-VAE are unchanged,
|
| 9 |
+
renamed for the Diffusers model code next to them; the fused attention input projections were split into query, key
|
| 10 |
+
and value. The VQ-VAE encoder and the training-only codebook statistics were left out. The normalization statistics and
|
| 11 |
+
the neutral joint positions of the skeleton come from the same package and are stored as buffers, with the standard
|
| 12 |
+
deviations as sqrt(std^2 + 1e-5), as in NVIDIA's exported ONNX model.
|
| 13 |
+
|
| 14 |
+
The model code is new, Apache-2.0, and written by The HuggingFace Team.
|
README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
license: other
|
| 5 |
+
license_name: nvidia-open-model-agreement
|
| 6 |
+
license_link: LICENSE
|
| 7 |
+
base_model:
|
| 8 |
+
- nvidia/MotionBricks
|
| 9 |
+
library_name: diffusers
|
| 10 |
+
tags:
|
| 11 |
+
- motionbricks
|
| 12 |
+
- modular-diffusers
|
| 13 |
+
- motion-generation
|
| 14 |
+
- motion-inbetweening
|
| 15 |
+
- soma
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
# MotionBricks SOMA
|
| 19 |
+
|
| 20 |
+
<video controls loop muted playsinline src="https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/main/motionbricks_soma_diffusers/motionbricks_soma_run_stop.mp4"></video>
|
| 21 |
+
|
| 22 |
+
*The start of a Kimodo run, then the 30 frames generated with the code below, then the standing end of another Kimodo take, placed 1.4 m ahead. The generated frames are drawn on SOMA77 with a relaxed finger pose. At normal playback speed; the cameras follow the root.*
|
| 23 |
+
|
| 24 |
+
This is the SOMA model of [nvidia/MotionBricks](https://huggingface.co/nvidia/MotionBricks), repacked into the Diffusers format in float32. It runs as a Modular Diffusers pipeline with the [MotionBricks SOMA custom blocks](https://huggingface.co/OzzyGT/motionbricks_soma_custom_blocks) and generates the transition from the last poses of one motion to target poses in one forward pass, choosing the length of the transition itself.
|
| 25 |
+
|
| 26 |
+
The three components work together:
|
| 27 |
+
|
| 28 |
+
- `root_model` (40M parameters) chooses the length and predicts the root trajectory.
|
| 29 |
+
- `pose_model` (137M parameters) predicts the pose codes of every token of four frames.
|
| 30 |
+
- `pose_decoder` (12M parameters) decodes the codes to frames.
|
| 31 |
+
|
| 32 |
+
Note: You need `diffusers>=0.41.0`.
|
| 33 |
+
|
| 34 |
+
## Usage
|
| 35 |
+
|
| 36 |
+
The custom blocks load the model code shipped in this repository, hence `trust_remote_code` and `trust_components_code`.
|
| 37 |
+
|
| 38 |
+
**Note:** both flags run Python code downloaded from the Hub: `trust_remote_code=True` runs the code of the blocks repository, and `trust_components_code=True` runs the code shipped in the `components_repo`. Only use them with repositories you trust, and review their code before passing different ones.
|
| 39 |
+
|
| 40 |
+
```python
|
| 41 |
+
import numpy as np
|
| 42 |
+
import torch
|
| 43 |
+
from diffusers import ModularPipelineBlocks
|
| 44 |
+
from huggingface_hub import hf_hub_download
|
| 45 |
+
|
| 46 |
+
blocks = ModularPipelineBlocks.from_pretrained(
|
| 47 |
+
"OzzyGT/motionbricks_soma_custom_blocks",
|
| 48 |
+
trust_remote_code=True,
|
| 49 |
+
components_repo="OzzyGT/motionbricks_soma_diffusers",
|
| 50 |
+
trust_components_code=True,
|
| 51 |
+
)
|
| 52 |
+
pipe = blocks.init_pipeline()
|
| 53 |
+
pipe.load_components(dtype=torch.float32)
|
| 54 |
+
pipe.to("cuda")
|
| 55 |
+
|
| 56 |
+
first = hf_hub_download("OzzyGT/diffusers-examples", "motionbricks_soma_diffusers/first.npz", repo_type="dataset")
|
| 57 |
+
second = hf_hub_download("OzzyGT/diffusers-examples", "motionbricks_soma_diffusers/second.npz", repo_type="dataset")
|
| 58 |
+
context = dict(np.load(first))
|
| 59 |
+
target = dict(np.load(second))
|
| 60 |
+
|
| 61 |
+
motion = pipe(context=context, target=target, target_offset=(1.4, 0.0), num_target_poses=4, output="motion")[0]
|
| 62 |
+
np.savez_compressed("transition.npz", **{key: value.cpu().numpy() for key, value in motion.items()})
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
Both clips come from [Kimodo SOMA Modular](https://huggingface.co/OzzyGT/Kimodo_SOMA_Modular): the start of a run, and a standing pose that `target_offset` places 1.4 m ahead of it. The model accepts SOMA30 or SOMA77 poses at 30 FPS and returns a SOMA30 motion; here it brings the run to a stop in 30 frames. See the [custom blocks](https://huggingface.co/OzzyGT/motionbricks_soma_custom_blocks) for the options.
|
| 66 |
+
|
| 67 |
+
Float32 reproduces NVIDIA's ONNX graph with PyTorch's default settings; float16 halves the memory with small drift, while bfloat16 changes the motion noticeably.
|
| 68 |
+
|
| 69 |
+
## Licenses
|
| 70 |
+
|
| 71 |
+
The model weights are licensed under the [NVIDIA Open Model Agreement](LICENSE), the same as the original. The model code in this repository is Apache-2.0. See [NOTICE](NOTICE) for what was changed.
|
modular_model_index.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "ModularPipeline",
|
| 3 |
+
"_blocks_class_name": "MotionBricksBlocks",
|
| 4 |
+
"root_model": [
|
| 5 |
+
"root_motionbricks",
|
| 6 |
+
"MotionBricksRootModel",
|
| 7 |
+
{
|
| 8 |
+
"pretrained_model_name_or_path": null,
|
| 9 |
+
"revision": null,
|
| 10 |
+
"subfolder": "root_model",
|
| 11 |
+
"type_hint": [
|
| 12 |
+
"root_motionbricks",
|
| 13 |
+
"MotionBricksRootModel"
|
| 14 |
+
],
|
| 15 |
+
"variant": null
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"pose_model": [
|
| 19 |
+
"transformer_motionbricks",
|
| 20 |
+
"MotionBricksPoseTransformer",
|
| 21 |
+
{
|
| 22 |
+
"pretrained_model_name_or_path": null,
|
| 23 |
+
"revision": null,
|
| 24 |
+
"subfolder": "pose_model",
|
| 25 |
+
"type_hint": [
|
| 26 |
+
"transformer_motionbricks",
|
| 27 |
+
"MotionBricksPoseTransformer"
|
| 28 |
+
],
|
| 29 |
+
"variant": null
|
| 30 |
+
}
|
| 31 |
+
],
|
| 32 |
+
"pose_decoder": [
|
| 33 |
+
"decoder_motionbricks",
|
| 34 |
+
"MotionBricksPoseDecoder",
|
| 35 |
+
{
|
| 36 |
+
"pretrained_model_name_or_path": null,
|
| 37 |
+
"revision": null,
|
| 38 |
+
"subfolder": "pose_decoder",
|
| 39 |
+
"type_hint": [
|
| 40 |
+
"decoder_motionbricks",
|
| 41 |
+
"MotionBricksPoseDecoder"
|
| 42 |
+
],
|
| 43 |
+
"variant": null
|
| 44 |
+
}
|
| 45 |
+
]
|
| 46 |
+
}
|
pose_decoder/config.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "MotionBricksPoseDecoder",
|
| 3 |
+
"_diffusers_version": "0.41.0",
|
| 4 |
+
"auto_map": {
|
| 5 |
+
"AutoModel": "decoder_motionbricks.MotionBricksPoseDecoder"
|
| 6 |
+
},
|
| 7 |
+
"code_embed_dim": 32,
|
| 8 |
+
"codebook_size": 16,
|
| 9 |
+
"depth": 4,
|
| 10 |
+
"dilation_growth_rate": 3,
|
| 11 |
+
"feature_dim": 365,
|
| 12 |
+
"frames_per_token": 4,
|
| 13 |
+
"max_tokens": 16,
|
| 14 |
+
"num_heads_codebook": 5,
|
| 15 |
+
"num_joints": 30,
|
| 16 |
+
"pose_dim": 268,
|
| 17 |
+
"velocity_dim": 2,
|
| 18 |
+
"width": 512
|
| 19 |
+
}
|
pose_decoder/decoder_motionbricks.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from dataclasses import dataclass
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torch.nn as nn
|
| 19 |
+
import torch.nn.functional as F
|
| 20 |
+
|
| 21 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 22 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 23 |
+
from diffusers.utils import BaseOutput
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class MotionBricksConv1d(nn.Conv1d):
|
| 27 |
+
"""Stride-1 `nn.Conv1d` computed as one matrix product over the shifted inputs, faster than cuDNN at these
|
| 28 |
+
sizes."""
|
| 29 |
+
|
| 30 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
kernel, dilation, padding = self.kernel_size[0], self.dilation[0], self.padding[0]
|
| 32 |
+
length = hidden_states.shape[-1]
|
| 33 |
+
if kernel > 1:
|
| 34 |
+
padded = F.pad(hidden_states, (padding, padding))
|
| 35 |
+
shifted = [padded[..., i * dilation : i * dilation + length] for i in range(kernel)]
|
| 36 |
+
hidden_states = torch.stack(shifted, dim=2).flatten(1, 2)
|
| 37 |
+
weight = self.weight.reshape(self.out_channels, -1)
|
| 38 |
+
return torch.matmul(weight, hidden_states) + self.bias[:, None]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class MotionBricksResnetBlock(nn.Module):
|
| 42 |
+
def __init__(self, channels: int, dilation: int):
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.conv1 = MotionBricksConv1d(channels, channels, 3, padding=dilation, dilation=dilation)
|
| 45 |
+
self.conv2 = MotionBricksConv1d(channels, channels, 1)
|
| 46 |
+
|
| 47 |
+
def forward(self, hidden_states: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
|
| 48 |
+
residual = hidden_states * mask
|
| 49 |
+
hidden_states = self.conv1(F.relu(residual) * mask)
|
| 50 |
+
hidden_states = self.conv2(F.relu(hidden_states) * mask)
|
| 51 |
+
return (hidden_states + residual) * mask
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class MotionBricksUpBlock(nn.Module):
|
| 55 |
+
def __init__(self, channels: int, depth: int, dilation_growth_rate: int):
|
| 56 |
+
super().__init__()
|
| 57 |
+
dilations = [dilation_growth_rate**i for i in reversed(range(depth))]
|
| 58 |
+
self.resnets = nn.ModuleList([MotionBricksResnetBlock(channels, dilation) for dilation in dilations])
|
| 59 |
+
self.conv = MotionBricksConv1d(channels, channels, 3, padding=1)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class MotionBricksConditionedDecoder(nn.Module):
|
| 63 |
+
"""Dilated 1D convolution decoder that upsamples tokens to frames, conditioned on per-frame targets and
|
| 64 |
+
external features."""
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
in_channels: int,
|
| 69 |
+
out_channels: int,
|
| 70 |
+
target_channels: int,
|
| 71 |
+
external_channels: int,
|
| 72 |
+
width: int = 512,
|
| 73 |
+
depth: int = 4,
|
| 74 |
+
dilation_growth_rate: int = 3,
|
| 75 |
+
num_tokens: int = 16,
|
| 76 |
+
num_frames: int = 64,
|
| 77 |
+
):
|
| 78 |
+
super().__init__()
|
| 79 |
+
self.width = width
|
| 80 |
+
self.num_frames = num_frames
|
| 81 |
+
lengths = [num_tokens, num_tokens * 2]
|
| 82 |
+
self.conv_in = MotionBricksConv1d(in_channels, width, 3, padding=1)
|
| 83 |
+
self.target_projections = nn.ModuleList(
|
| 84 |
+
[nn.Linear(target_channels, width * length // num_frames) for length in lengths]
|
| 85 |
+
)
|
| 86 |
+
self.external_projections = nn.ModuleList(
|
| 87 |
+
[nn.Linear(width + external_channels * num_frames // length, width) for length in lengths]
|
| 88 |
+
)
|
| 89 |
+
self.up_blocks = nn.ModuleList([MotionBricksUpBlock(width, depth, dilation_growth_rate) for _ in lengths])
|
| 90 |
+
self.conv_mid = MotionBricksConv1d(width, width, 3, padding=1)
|
| 91 |
+
self.conv_out = MotionBricksConv1d(width, out_channels, 3, padding=1)
|
| 92 |
+
|
| 93 |
+
def forward(
|
| 94 |
+
self,
|
| 95 |
+
hidden_states: torch.Tensor,
|
| 96 |
+
token_mask: torch.Tensor,
|
| 97 |
+
target: torch.Tensor,
|
| 98 |
+
target_mask: torch.Tensor,
|
| 99 |
+
external: torch.Tensor,
|
| 100 |
+
) -> torch.Tensor:
|
| 101 |
+
"""
|
| 102 |
+
Args:
|
| 103 |
+
hidden_states: `(batch_size, in_channels, num_tokens)` token features.
|
| 104 |
+
token_mask: `(batch_size, num_tokens)` float mask of the valid tokens.
|
| 105 |
+
target: `(batch_size, num_frames, target_channels)` per-frame targets.
|
| 106 |
+
target_mask: `(batch_size, num_frames)` boolean mask of the frames with a target.
|
| 107 |
+
external: `(batch_size, num_frames, external_channels)` per-frame conditioning.
|
| 108 |
+
|
| 109 |
+
Returns:
|
| 110 |
+
`(batch_size, out_channels, num_frames)` frame features.
|
| 111 |
+
"""
|
| 112 |
+
batch_size = hidden_states.shape[0]
|
| 113 |
+
mask = token_mask[:, None]
|
| 114 |
+
hidden_states = F.relu(self.conv_in(hidden_states * mask))
|
| 115 |
+
for block, target_projection, external_projection in zip(
|
| 116 |
+
self.up_blocks, self.target_projections, self.external_projections
|
| 117 |
+
):
|
| 118 |
+
length = hidden_states.shape[-1]
|
| 119 |
+
projected = F.relu(target_projection(target))
|
| 120 |
+
grouped = hidden_states.transpose(1, 2).reshape(batch_size, self.num_frames, -1)
|
| 121 |
+
grouped = torch.where(target_mask[..., None], projected, grouped)
|
| 122 |
+
grouped = grouped.reshape(batch_size, length, self.width)
|
| 123 |
+
conditioned = torch.cat([grouped, external.reshape(batch_size, length, -1)], dim=-1)
|
| 124 |
+
hidden_states = F.relu(external_projection(conditioned).transpose(1, 2))
|
| 125 |
+
for resnet in block.resnets:
|
| 126 |
+
hidden_states = resnet(hidden_states, mask)
|
| 127 |
+
hidden_states = F.interpolate(hidden_states, scale_factor=2, mode="nearest")
|
| 128 |
+
mask = mask.repeat_interleave(2, dim=-1)
|
| 129 |
+
hidden_states = block.conv(hidden_states * mask)
|
| 130 |
+
hidden_states = F.relu(self.conv_mid(hidden_states * mask)) * mask
|
| 131 |
+
return self.conv_out(hidden_states) * mask
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@dataclass
|
| 135 |
+
class MotionBricksPoseDecoderOutput(BaseOutput):
|
| 136 |
+
"""
|
| 137 |
+
Args:
|
| 138 |
+
features (`torch.Tensor` of shape `(batch_size, num_frames, feature_dim)`):
|
| 139 |
+
The normalized features of every frame.
|
| 140 |
+
"""
|
| 141 |
+
|
| 142 |
+
features: torch.Tensor
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
class MotionBricksPoseDecoder(ModelMixin, ConfigMixin):
|
| 146 |
+
"""Decoder of the pose VQ-VAE: looks up the codes of every token and decodes them to normalized frame features,
|
| 147 |
+
conditioned on the given poses and the root velocity.
|
| 148 |
+
|
| 149 |
+
The features of a frame are the local root (heading rate, ground velocity, height) followed by the body: joint
|
| 150 |
+
positions relative to the root, 6D global rotations, joint velocities and foot contacts. Their normalization
|
| 151 |
+
statistics and the neutral joint positions of the skeleton are stored as buffers.
|
| 152 |
+
"""
|
| 153 |
+
|
| 154 |
+
_no_split_modules = ["MotionBricksConditionedDecoder"]
|
| 155 |
+
_repeated_blocks = ["MotionBricksResnetBlock"]
|
| 156 |
+
|
| 157 |
+
@register_to_config
|
| 158 |
+
def __init__(
|
| 159 |
+
self,
|
| 160 |
+
num_heads_codebook: int = 5,
|
| 161 |
+
codebook_size: int = 16,
|
| 162 |
+
code_embed_dim: int = 32,
|
| 163 |
+
pose_dim: int = 268,
|
| 164 |
+
velocity_dim: int = 2,
|
| 165 |
+
feature_dim: int = 365,
|
| 166 |
+
num_joints: int = 30,
|
| 167 |
+
width: int = 512,
|
| 168 |
+
depth: int = 4,
|
| 169 |
+
dilation_growth_rate: int = 3,
|
| 170 |
+
frames_per_token: int = 4,
|
| 171 |
+
max_tokens: int = 16,
|
| 172 |
+
):
|
| 173 |
+
super().__init__()
|
| 174 |
+
self.codebook = nn.Parameter(torch.zeros(num_heads_codebook, codebook_size, code_embed_dim))
|
| 175 |
+
self.decoder = MotionBricksConditionedDecoder(
|
| 176 |
+
num_heads_codebook * code_embed_dim,
|
| 177 |
+
feature_dim,
|
| 178 |
+
target_channels=pose_dim,
|
| 179 |
+
external_channels=velocity_dim,
|
| 180 |
+
width=width,
|
| 181 |
+
depth=depth,
|
| 182 |
+
dilation_growth_rate=dilation_growth_rate,
|
| 183 |
+
num_tokens=max_tokens,
|
| 184 |
+
num_frames=max_tokens * frames_per_token,
|
| 185 |
+
)
|
| 186 |
+
self.register_buffer("feature_mean", torch.zeros(feature_dim))
|
| 187 |
+
self.register_buffer("feature_std", torch.ones(feature_dim))
|
| 188 |
+
self.register_buffer("neutral_joints", torch.zeros(num_joints, 3))
|
| 189 |
+
|
| 190 |
+
def denormalize(self, features: torch.Tensor) -> torch.Tensor:
|
| 191 |
+
# The buffers can sit on another device while the model is offloaded.
|
| 192 |
+
std = self.feature_std.to(features.device, features.dtype)
|
| 193 |
+
return features * std + self.feature_mean.to(features.device, features.dtype)
|
| 194 |
+
|
| 195 |
+
def forward(
|
| 196 |
+
self,
|
| 197 |
+
codes: torch.LongTensor,
|
| 198 |
+
num_tokens: torch.Tensor,
|
| 199 |
+
pose: torch.Tensor,
|
| 200 |
+
has_pose: torch.Tensor,
|
| 201 |
+
velocity: torch.Tensor,
|
| 202 |
+
return_dict: bool = True,
|
| 203 |
+
) -> MotionBricksPoseDecoderOutput | tuple:
|
| 204 |
+
"""
|
| 205 |
+
Args:
|
| 206 |
+
codes (`torch.LongTensor` of shape `(batch_size, max_tokens, num_heads_codebook)`):
|
| 207 |
+
The pose code of every token and head.
|
| 208 |
+
num_tokens (`torch.LongTensor` of shape `(batch_size,)`):
|
| 209 |
+
The motion length in tokens.
|
| 210 |
+
pose (`torch.Tensor` of shape `(batch_size, num_frames, pose_dim)`):
|
| 211 |
+
Normalized pose features on the frame timeline.
|
| 212 |
+
has_pose (`torch.BoolTensor` of shape `(batch_size, num_frames)`):
|
| 213 |
+
The frames whose pose is given.
|
| 214 |
+
velocity (`torch.Tensor` of shape `(batch_size, num_frames, velocity_dim)`):
|
| 215 |
+
Normalized ground velocity of the root.
|
| 216 |
+
return_dict (`bool`, defaults to `True`):
|
| 217 |
+
Whether to return a `MotionBricksPoseDecoderOutput` instead of a tuple.
|
| 218 |
+
|
| 219 |
+
Returns:
|
| 220 |
+
`MotionBricksPoseDecoderOutput` or `tuple`: The normalized frame features; a one-element tuple when
|
| 221 |
+
`return_dict` is `False`.
|
| 222 |
+
"""
|
| 223 |
+
heads = torch.arange(self.config.num_heads_codebook, device=codes.device)
|
| 224 |
+
embeds = self.codebook[heads, codes].flatten(2)
|
| 225 |
+
token_mask = (torch.arange(codes.shape[1], device=codes.device)[None] < num_tokens[:, None]).to(embeds.dtype)
|
| 226 |
+
features = self.decoder(embeds.transpose(1, 2), token_mask, pose, has_pose, velocity).transpose(1, 2)
|
| 227 |
+
|
| 228 |
+
if not return_dict:
|
| 229 |
+
return (features,)
|
| 230 |
+
return MotionBricksPoseDecoderOutput(features=features)
|
pose_decoder/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d3ff3958e82b62e409d116276685cd523b7162793224e3af9aa4fbdf66ceebc4
|
| 3 |
+
size 48817860
|
pose_model/config.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "MotionBricksPoseTransformer",
|
| 3 |
+
"_diffusers_version": "0.41.0",
|
| 4 |
+
"auto_map": {
|
| 5 |
+
"AutoModel": "transformer_motionbricks.MotionBricksPoseTransformer"
|
| 6 |
+
},
|
| 7 |
+
"code_embed_dim": 32,
|
| 8 |
+
"codebook_size": 16,
|
| 9 |
+
"ff_dim": 2048,
|
| 10 |
+
"frames_per_token": 4,
|
| 11 |
+
"hidden_dim": 1024,
|
| 12 |
+
"length_embed_dim": 128,
|
| 13 |
+
"max_tokens": 16,
|
| 14 |
+
"min_tokens": 6,
|
| 15 |
+
"num_heads": 16,
|
| 16 |
+
"num_heads_codebook": 5,
|
| 17 |
+
"num_layers": 16,
|
| 18 |
+
"pose_dim": 268,
|
| 19 |
+
"root_dim": 4,
|
| 20 |
+
"root_embed_dim": 256,
|
| 21 |
+
"token_dim": 640
|
| 22 |
+
}
|
pose_model/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f7498f8f8ca90c098370c2e4c7c809cc590d83847ac8d41b2f76af278ba06aa2
|
| 3 |
+
size 546109888
|
pose_model/transformer_motionbricks.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from dataclasses import dataclass
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torch.nn as nn
|
| 19 |
+
import torch.nn.functional as F
|
| 20 |
+
|
| 21 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 22 |
+
from diffusers.models.attention import AttentionMixin, AttentionModuleMixin
|
| 23 |
+
from diffusers.models.attention_dispatch import dispatch_attention_fn
|
| 24 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 25 |
+
from diffusers.utils import BaseOutput
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class MotionBricksMLP(nn.Module):
|
| 29 |
+
"""Two linear layers, each followed by LeakyReLU(0.01), then a linear projection."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features: int, hidden_features: int, out_features: int):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.layers = nn.ModuleList(
|
| 34 |
+
[nn.Linear(in_features, hidden_features), nn.Linear(hidden_features, hidden_features)]
|
| 35 |
+
)
|
| 36 |
+
self.proj_out = nn.Linear(hidden_features, out_features)
|
| 37 |
+
|
| 38 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 39 |
+
for layer in self.layers:
|
| 40 |
+
hidden_states = F.leaky_relu(layer(hidden_states), 0.01)
|
| 41 |
+
return self.proj_out(hidden_states)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class MotionBricksAttnProcessor:
|
| 45 |
+
_attention_backend = None
|
| 46 |
+
_parallel_config = None
|
| 47 |
+
|
| 48 |
+
def __call__(
|
| 49 |
+
self,
|
| 50 |
+
attn: "MotionBricksAttention",
|
| 51 |
+
hidden_states: torch.Tensor,
|
| 52 |
+
attention_mask: torch.Tensor | None = None,
|
| 53 |
+
) -> torch.Tensor:
|
| 54 |
+
batch_size, seq_len, _ = hidden_states.shape
|
| 55 |
+
shape = (batch_size, seq_len, attn.heads, -1)
|
| 56 |
+
query = attn.to_q(hidden_states).view(shape)
|
| 57 |
+
key = attn.to_k(hidden_states).view(shape)
|
| 58 |
+
value = attn.to_v(hidden_states).view(shape)
|
| 59 |
+
hidden_states = dispatch_attention_fn(
|
| 60 |
+
query,
|
| 61 |
+
key,
|
| 62 |
+
value,
|
| 63 |
+
attn_mask=attention_mask,
|
| 64 |
+
backend=self._attention_backend,
|
| 65 |
+
parallel_config=self._parallel_config,
|
| 66 |
+
)
|
| 67 |
+
hidden_states = hidden_states.flatten(2, 3).to(query.dtype)
|
| 68 |
+
return attn.to_out[0](hidden_states)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class MotionBricksAttention(nn.Module, AttentionModuleMixin):
|
| 72 |
+
"""Multi-head self-attention. `attention_mask` is a boolean `(batch_size, 1, 1, seq_len)` mask of the keys to
|
| 73 |
+
attend to."""
|
| 74 |
+
|
| 75 |
+
_default_processor_cls = MotionBricksAttnProcessor
|
| 76 |
+
_available_processors = [MotionBricksAttnProcessor]
|
| 77 |
+
|
| 78 |
+
def __init__(self, dim: int, heads: int):
|
| 79 |
+
super().__init__()
|
| 80 |
+
self.heads = heads
|
| 81 |
+
self.to_q = nn.Linear(dim, dim)
|
| 82 |
+
self.to_k = nn.Linear(dim, dim)
|
| 83 |
+
self.to_v = nn.Linear(dim, dim)
|
| 84 |
+
self.to_out = nn.ModuleList([nn.Linear(dim, dim), nn.Dropout(0.0)])
|
| 85 |
+
self.set_processor(MotionBricksAttnProcessor())
|
| 86 |
+
|
| 87 |
+
def forward(self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None) -> torch.Tensor:
|
| 88 |
+
return self.processor(self, hidden_states, attention_mask)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class MotionBricksTransformerLayer(nn.Module):
|
| 92 |
+
"""Post-norm transformer encoder layer with a ReLU feed-forward."""
|
| 93 |
+
|
| 94 |
+
def __init__(self, dim: int, heads: int, ff_dim: int):
|
| 95 |
+
super().__init__()
|
| 96 |
+
self.attn = MotionBricksAttention(dim, heads)
|
| 97 |
+
self.norm1 = nn.LayerNorm(dim)
|
| 98 |
+
self.ff_in = nn.Linear(dim, ff_dim)
|
| 99 |
+
self.ff_out = nn.Linear(ff_dim, dim)
|
| 100 |
+
self.norm2 = nn.LayerNorm(dim)
|
| 101 |
+
|
| 102 |
+
def forward(self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None) -> torch.Tensor:
|
| 103 |
+
hidden_states = self.norm1(hidden_states + self.attn(hidden_states, attention_mask))
|
| 104 |
+
return self.norm2(hidden_states + self.ff_out(F.relu(self.ff_in(hidden_states))))
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def key_mask(num_tokens: torch.Tensor, max_tokens: int, prefix: int = 0) -> torch.Tensor:
|
| 108 |
+
"""Boolean `(batch_size, 1, 1, prefix + max_tokens)` attention mask: the `prefix` positions, then the first
|
| 109 |
+
`num_tokens` of `max_tokens` token positions."""
|
| 110 |
+
positions = torch.arange(max_tokens, device=num_tokens.device)
|
| 111 |
+
valid = positions[None] < num_tokens[:, None]
|
| 112 |
+
valid = F.pad(valid, (prefix, 0), value=True)
|
| 113 |
+
return valid[:, None, None]
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
@dataclass
|
| 117 |
+
class MotionBricksPoseTransformerOutput(BaseOutput):
|
| 118 |
+
"""
|
| 119 |
+
Args:
|
| 120 |
+
logits (`torch.Tensor` of shape `(batch_size, max_tokens, num_heads_codebook, codebook_size)`):
|
| 121 |
+
The code logits of every token and head.
|
| 122 |
+
"""
|
| 123 |
+
|
| 124 |
+
logits: torch.Tensor
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class MotionBricksPoseTransformer(ModelMixin, ConfigMixin, AttentionMixin):
|
| 128 |
+
"""Masked token transformer that predicts the pose codes of every token in one pass, from the given poses, the
|
| 129 |
+
root of every frame and the length of the motion. Each token covers `frames_per_token` frames and has one code
|
| 130 |
+
per head."""
|
| 131 |
+
|
| 132 |
+
_no_split_modules = ["MotionBricksTransformerLayer"]
|
| 133 |
+
_repeated_blocks = ["MotionBricksTransformerLayer"]
|
| 134 |
+
_skip_layerwise_casting_patterns = ["norm", "embed"]
|
| 135 |
+
|
| 136 |
+
@register_to_config
|
| 137 |
+
def __init__(
|
| 138 |
+
self,
|
| 139 |
+
pose_dim: int = 268,
|
| 140 |
+
root_dim: int = 4,
|
| 141 |
+
num_heads_codebook: int = 5,
|
| 142 |
+
codebook_size: int = 16,
|
| 143 |
+
code_embed_dim: int = 32,
|
| 144 |
+
token_dim: int = 640,
|
| 145 |
+
root_embed_dim: int = 256,
|
| 146 |
+
length_embed_dim: int = 128,
|
| 147 |
+
hidden_dim: int = 1024,
|
| 148 |
+
num_heads: int = 16,
|
| 149 |
+
ff_dim: int = 2048,
|
| 150 |
+
num_layers: int = 16,
|
| 151 |
+
frames_per_token: int = 4,
|
| 152 |
+
min_tokens: int = 6,
|
| 153 |
+
max_tokens: int = 16,
|
| 154 |
+
):
|
| 155 |
+
super().__init__()
|
| 156 |
+
code_dim = num_heads_codebook * code_embed_dim
|
| 157 |
+
# Each head has codebook_size codes and a mask code.
|
| 158 |
+
self.code_embed = nn.Embedding(num_heads_codebook * (codebook_size + 1), code_embed_dim)
|
| 159 |
+
self.code_proj = MotionBricksMLP(code_dim, token_dim, token_dim)
|
| 160 |
+
self.pose_proj = nn.Linear(pose_dim, token_dim // frames_per_token)
|
| 161 |
+
self.root_proj = nn.Linear(root_dim * frames_per_token, root_embed_dim)
|
| 162 |
+
self.length_embed = nn.Embedding(max_tokens - min_tokens + 1, length_embed_dim)
|
| 163 |
+
self.proj_in = nn.Linear(token_dim + root_embed_dim + length_embed_dim, hidden_dim)
|
| 164 |
+
self.position_embed = nn.Parameter(torch.zeros(max_tokens, hidden_dim))
|
| 165 |
+
self.layers = nn.ModuleList(
|
| 166 |
+
[MotionBricksTransformerLayer(hidden_dim, num_heads, ff_dim) for _ in range(num_layers)]
|
| 167 |
+
)
|
| 168 |
+
self.proj_out = nn.Linear(hidden_dim, num_heads_codebook * codebook_size)
|
| 169 |
+
|
| 170 |
+
def forward(
|
| 171 |
+
self,
|
| 172 |
+
pose: torch.Tensor,
|
| 173 |
+
has_pose: torch.Tensor,
|
| 174 |
+
root: torch.Tensor,
|
| 175 |
+
num_tokens: torch.Tensor,
|
| 176 |
+
return_dict: bool = True,
|
| 177 |
+
) -> MotionBricksPoseTransformerOutput | tuple:
|
| 178 |
+
"""
|
| 179 |
+
Args:
|
| 180 |
+
pose (`torch.Tensor` of shape `(batch_size, num_frames, pose_dim)`):
|
| 181 |
+
Normalized pose features on the frame timeline.
|
| 182 |
+
has_pose (`torch.BoolTensor` of shape `(batch_size, num_frames)`):
|
| 183 |
+
The frames whose pose is given.
|
| 184 |
+
root (`torch.Tensor` of shape `(batch_size, num_frames, root_dim)`):
|
| 185 |
+
Normalized root of every frame: ground position and the cosine and sine of the heading.
|
| 186 |
+
num_tokens (`torch.LongTensor` of shape `(batch_size,)`):
|
| 187 |
+
The motion length in tokens.
|
| 188 |
+
return_dict (`bool`, defaults to `True`):
|
| 189 |
+
Whether to return a `MotionBricksPoseTransformerOutput` instead of a tuple.
|
| 190 |
+
|
| 191 |
+
Returns:
|
| 192 |
+
`MotionBricksPoseTransformerOutput` or `tuple`: The code logits; a one-element tuple when `return_dict` is
|
| 193 |
+
`False`.
|
| 194 |
+
"""
|
| 195 |
+
config = self.config
|
| 196 |
+
batch_size = pose.shape[0]
|
| 197 |
+
heads, size = config.num_heads_codebook, config.codebook_size
|
| 198 |
+
mask_codes = size + torch.arange(heads, device=pose.device) * (size + 1)
|
| 199 |
+
codes = self.code_embed(mask_codes).flatten()
|
| 200 |
+
codes = self.code_proj(codes).expand(batch_size, config.max_tokens, -1)
|
| 201 |
+
codes = codes.reshape(batch_size, -1, self.pose_proj.out_features)
|
| 202 |
+
has_pose = has_pose[..., None].to(pose.dtype)
|
| 203 |
+
frames = self.pose_proj(pose) * has_pose + codes * (1 - has_pose)
|
| 204 |
+
frames = frames.reshape(batch_size, config.max_tokens, -1)
|
| 205 |
+
root = self.root_proj(root.reshape(batch_size, config.max_tokens, -1))
|
| 206 |
+
length = self.length_embed(num_tokens - config.min_tokens)[:, None].expand(-1, config.max_tokens, -1)
|
| 207 |
+
hidden_states = F.relu(self.proj_in(torch.cat([frames, root, length], dim=-1))) + self.position_embed
|
| 208 |
+
mask = key_mask(num_tokens, config.max_tokens)
|
| 209 |
+
for layer in self.layers:
|
| 210 |
+
hidden_states = layer(hidden_states, mask)
|
| 211 |
+
logits = self.proj_out(hidden_states).unflatten(-1, (heads, size))
|
| 212 |
+
|
| 213 |
+
if not return_dict:
|
| 214 |
+
return (logits,)
|
| 215 |
+
return MotionBricksPoseTransformerOutput(logits=logits)
|
root_model/config.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "MotionBricksRootModel",
|
| 3 |
+
"_diffusers_version": "0.41.0",
|
| 4 |
+
"auto_map": {
|
| 5 |
+
"AutoModel": "root_motionbricks.MotionBricksRootModel"
|
| 6 |
+
},
|
| 7 |
+
"context_frames": 4,
|
| 8 |
+
"decoder_depth": 4,
|
| 9 |
+
"decoder_dilation_growth_rate": 3,
|
| 10 |
+
"ff_dim": 2048,
|
| 11 |
+
"frames_per_token": 4,
|
| 12 |
+
"global_root_dim": 5,
|
| 13 |
+
"hidden_dim": 512,
|
| 14 |
+
"local_root_dim": 4,
|
| 15 |
+
"max_tokens": 16,
|
| 16 |
+
"min_tokens": 6,
|
| 17 |
+
"num_heads": 16,
|
| 18 |
+
"num_shared_layers": 6,
|
| 19 |
+
"num_token_layers": 2,
|
| 20 |
+
"pose_dim": 268,
|
| 21 |
+
"pose_embed_dim": 256,
|
| 22 |
+
"root_embed_dim": 64,
|
| 23 |
+
"target_frames": 4
|
| 24 |
+
}
|
root_model/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:78f99fbeca2df6f73320b2aa81befda1986b571322ed98fb9b426a40b76586e3
|
| 3 |
+
size 161697028
|
root_model/root_motionbricks.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from dataclasses import dataclass
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torch.nn as nn
|
| 19 |
+
import torch.nn.functional as F
|
| 20 |
+
|
| 21 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 22 |
+
from diffusers.models.attention import AttentionMixin, AttentionModuleMixin
|
| 23 |
+
from diffusers.models.attention_dispatch import dispatch_attention_fn
|
| 24 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 25 |
+
from diffusers.utils import BaseOutput
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class MotionBricksMLP(nn.Module):
|
| 29 |
+
"""Two linear layers, each followed by LeakyReLU(0.01), then a linear projection."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features: int, hidden_features: int, out_features: int):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.layers = nn.ModuleList(
|
| 34 |
+
[nn.Linear(in_features, hidden_features), nn.Linear(hidden_features, hidden_features)]
|
| 35 |
+
)
|
| 36 |
+
self.proj_out = nn.Linear(hidden_features, out_features)
|
| 37 |
+
|
| 38 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 39 |
+
for layer in self.layers:
|
| 40 |
+
hidden_states = F.leaky_relu(layer(hidden_states), 0.01)
|
| 41 |
+
return self.proj_out(hidden_states)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class MotionBricksAttnProcessor:
|
| 45 |
+
_attention_backend = None
|
| 46 |
+
_parallel_config = None
|
| 47 |
+
|
| 48 |
+
def __call__(
|
| 49 |
+
self,
|
| 50 |
+
attn: "MotionBricksAttention",
|
| 51 |
+
hidden_states: torch.Tensor,
|
| 52 |
+
attention_mask: torch.Tensor | None = None,
|
| 53 |
+
) -> torch.Tensor:
|
| 54 |
+
batch_size, seq_len, _ = hidden_states.shape
|
| 55 |
+
shape = (batch_size, seq_len, attn.heads, -1)
|
| 56 |
+
query = attn.to_q(hidden_states).view(shape)
|
| 57 |
+
key = attn.to_k(hidden_states).view(shape)
|
| 58 |
+
value = attn.to_v(hidden_states).view(shape)
|
| 59 |
+
hidden_states = dispatch_attention_fn(
|
| 60 |
+
query,
|
| 61 |
+
key,
|
| 62 |
+
value,
|
| 63 |
+
attn_mask=attention_mask,
|
| 64 |
+
backend=self._attention_backend,
|
| 65 |
+
parallel_config=self._parallel_config,
|
| 66 |
+
)
|
| 67 |
+
hidden_states = hidden_states.flatten(2, 3).to(query.dtype)
|
| 68 |
+
return attn.to_out[0](hidden_states)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class MotionBricksAttention(nn.Module, AttentionModuleMixin):
|
| 72 |
+
"""Multi-head self-attention. `attention_mask` is a boolean `(batch_size, 1, 1, seq_len)` mask of the keys to
|
| 73 |
+
attend to."""
|
| 74 |
+
|
| 75 |
+
_default_processor_cls = MotionBricksAttnProcessor
|
| 76 |
+
_available_processors = [MotionBricksAttnProcessor]
|
| 77 |
+
|
| 78 |
+
def __init__(self, dim: int, heads: int):
|
| 79 |
+
super().__init__()
|
| 80 |
+
self.heads = heads
|
| 81 |
+
self.to_q = nn.Linear(dim, dim)
|
| 82 |
+
self.to_k = nn.Linear(dim, dim)
|
| 83 |
+
self.to_v = nn.Linear(dim, dim)
|
| 84 |
+
self.to_out = nn.ModuleList([nn.Linear(dim, dim), nn.Dropout(0.0)])
|
| 85 |
+
self.set_processor(MotionBricksAttnProcessor())
|
| 86 |
+
|
| 87 |
+
def forward(self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None) -> torch.Tensor:
|
| 88 |
+
return self.processor(self, hidden_states, attention_mask)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class MotionBricksTransformerLayer(nn.Module):
|
| 92 |
+
"""Post-norm transformer encoder layer with a ReLU feed-forward."""
|
| 93 |
+
|
| 94 |
+
def __init__(self, dim: int, heads: int, ff_dim: int):
|
| 95 |
+
super().__init__()
|
| 96 |
+
self.attn = MotionBricksAttention(dim, heads)
|
| 97 |
+
self.norm1 = nn.LayerNorm(dim)
|
| 98 |
+
self.ff_in = nn.Linear(dim, ff_dim)
|
| 99 |
+
self.ff_out = nn.Linear(ff_dim, dim)
|
| 100 |
+
self.norm2 = nn.LayerNorm(dim)
|
| 101 |
+
|
| 102 |
+
def forward(self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None) -> torch.Tensor:
|
| 103 |
+
hidden_states = self.norm1(hidden_states + self.attn(hidden_states, attention_mask))
|
| 104 |
+
return self.norm2(hidden_states + self.ff_out(F.relu(self.ff_in(hidden_states))))
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def key_mask(num_tokens: torch.Tensor, max_tokens: int, prefix: int = 0) -> torch.Tensor:
|
| 108 |
+
"""Boolean `(batch_size, 1, 1, prefix + max_tokens)` attention mask: the `prefix` positions, then the first
|
| 109 |
+
`num_tokens` of `max_tokens` token positions."""
|
| 110 |
+
positions = torch.arange(max_tokens, device=num_tokens.device)
|
| 111 |
+
valid = positions[None] < num_tokens[:, None]
|
| 112 |
+
valid = F.pad(valid, (prefix, 0), value=True)
|
| 113 |
+
return valid[:, None, None]
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class MotionBricksConv1d(nn.Conv1d):
|
| 117 |
+
"""Stride-1 `nn.Conv1d` computed as one matrix product over the shifted inputs, faster than cuDNN at these
|
| 118 |
+
sizes."""
|
| 119 |
+
|
| 120 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 121 |
+
kernel, dilation, padding = self.kernel_size[0], self.dilation[0], self.padding[0]
|
| 122 |
+
length = hidden_states.shape[-1]
|
| 123 |
+
if kernel > 1:
|
| 124 |
+
padded = F.pad(hidden_states, (padding, padding))
|
| 125 |
+
shifted = [padded[..., i * dilation : i * dilation + length] for i in range(kernel)]
|
| 126 |
+
hidden_states = torch.stack(shifted, dim=2).flatten(1, 2)
|
| 127 |
+
weight = self.weight.reshape(self.out_channels, -1)
|
| 128 |
+
return torch.matmul(weight, hidden_states) + self.bias[:, None]
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class MotionBricksResnetBlock(nn.Module):
|
| 132 |
+
def __init__(self, channels: int, dilation: int):
|
| 133 |
+
super().__init__()
|
| 134 |
+
self.conv1 = MotionBricksConv1d(channels, channels, 3, padding=dilation, dilation=dilation)
|
| 135 |
+
self.conv2 = MotionBricksConv1d(channels, channels, 1)
|
| 136 |
+
|
| 137 |
+
def forward(self, hidden_states: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
|
| 138 |
+
residual = hidden_states * mask
|
| 139 |
+
hidden_states = self.conv1(F.relu(residual) * mask)
|
| 140 |
+
hidden_states = self.conv2(F.relu(hidden_states) * mask)
|
| 141 |
+
return (hidden_states + residual) * mask
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class MotionBricksUpBlock(nn.Module):
|
| 145 |
+
def __init__(self, channels: int, depth: int, dilation_growth_rate: int):
|
| 146 |
+
super().__init__()
|
| 147 |
+
dilations = [dilation_growth_rate**i for i in reversed(range(depth))]
|
| 148 |
+
self.resnets = nn.ModuleList([MotionBricksResnetBlock(channels, dilation) for dilation in dilations])
|
| 149 |
+
self.conv = MotionBricksConv1d(channels, channels, 3, padding=1)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class MotionBricksConditionedDecoder(nn.Module):
|
| 153 |
+
"""Dilated 1D convolution decoder that upsamples tokens to frames, conditioned on per-frame targets and
|
| 154 |
+
external features."""
|
| 155 |
+
|
| 156 |
+
def __init__(
|
| 157 |
+
self,
|
| 158 |
+
in_channels: int,
|
| 159 |
+
out_channels: int,
|
| 160 |
+
target_channels: int,
|
| 161 |
+
external_channels: int,
|
| 162 |
+
width: int = 512,
|
| 163 |
+
depth: int = 4,
|
| 164 |
+
dilation_growth_rate: int = 3,
|
| 165 |
+
num_tokens: int = 16,
|
| 166 |
+
num_frames: int = 64,
|
| 167 |
+
):
|
| 168 |
+
super().__init__()
|
| 169 |
+
self.width = width
|
| 170 |
+
self.num_frames = num_frames
|
| 171 |
+
lengths = [num_tokens, num_tokens * 2]
|
| 172 |
+
self.conv_in = MotionBricksConv1d(in_channels, width, 3, padding=1)
|
| 173 |
+
self.target_projections = nn.ModuleList(
|
| 174 |
+
[nn.Linear(target_channels, width * length // num_frames) for length in lengths]
|
| 175 |
+
)
|
| 176 |
+
self.external_projections = nn.ModuleList(
|
| 177 |
+
[nn.Linear(width + external_channels * num_frames // length, width) for length in lengths]
|
| 178 |
+
)
|
| 179 |
+
self.up_blocks = nn.ModuleList([MotionBricksUpBlock(width, depth, dilation_growth_rate) for _ in lengths])
|
| 180 |
+
self.conv_mid = MotionBricksConv1d(width, width, 3, padding=1)
|
| 181 |
+
self.conv_out = MotionBricksConv1d(width, out_channels, 3, padding=1)
|
| 182 |
+
|
| 183 |
+
def forward(
|
| 184 |
+
self,
|
| 185 |
+
hidden_states: torch.Tensor,
|
| 186 |
+
token_mask: torch.Tensor,
|
| 187 |
+
target: torch.Tensor,
|
| 188 |
+
target_mask: torch.Tensor,
|
| 189 |
+
external: torch.Tensor,
|
| 190 |
+
) -> torch.Tensor:
|
| 191 |
+
"""
|
| 192 |
+
Args:
|
| 193 |
+
hidden_states: `(batch_size, in_channels, num_tokens)` token features.
|
| 194 |
+
token_mask: `(batch_size, num_tokens)` float mask of the valid tokens.
|
| 195 |
+
target: `(batch_size, num_frames, target_channels)` per-frame targets.
|
| 196 |
+
target_mask: `(batch_size, num_frames)` boolean mask of the frames with a target.
|
| 197 |
+
external: `(batch_size, num_frames, external_channels)` per-frame conditioning.
|
| 198 |
+
|
| 199 |
+
Returns:
|
| 200 |
+
`(batch_size, out_channels, num_frames)` frame features.
|
| 201 |
+
"""
|
| 202 |
+
batch_size = hidden_states.shape[0]
|
| 203 |
+
mask = token_mask[:, None]
|
| 204 |
+
hidden_states = F.relu(self.conv_in(hidden_states * mask))
|
| 205 |
+
for block, target_projection, external_projection in zip(
|
| 206 |
+
self.up_blocks, self.target_projections, self.external_projections
|
| 207 |
+
):
|
| 208 |
+
length = hidden_states.shape[-1]
|
| 209 |
+
projected = F.relu(target_projection(target))
|
| 210 |
+
grouped = hidden_states.transpose(1, 2).reshape(batch_size, self.num_frames, -1)
|
| 211 |
+
grouped = torch.where(target_mask[..., None], projected, grouped)
|
| 212 |
+
grouped = grouped.reshape(batch_size, length, self.width)
|
| 213 |
+
conditioned = torch.cat([grouped, external.reshape(batch_size, length, -1)], dim=-1)
|
| 214 |
+
hidden_states = F.relu(external_projection(conditioned).transpose(1, 2))
|
| 215 |
+
for resnet in block.resnets:
|
| 216 |
+
hidden_states = resnet(hidden_states, mask)
|
| 217 |
+
hidden_states = F.interpolate(hidden_states, scale_factor=2, mode="nearest")
|
| 218 |
+
mask = mask.repeat_interleave(2, dim=-1)
|
| 219 |
+
hidden_states = block.conv(hidden_states * mask)
|
| 220 |
+
hidden_states = F.relu(self.conv_mid(hidden_states * mask)) * mask
|
| 221 |
+
return self.conv_out(hidden_states) * mask
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
@dataclass
|
| 225 |
+
class MotionBricksRootOutput(BaseOutput):
|
| 226 |
+
"""
|
| 227 |
+
Args:
|
| 228 |
+
num_tokens (`torch.LongTensor` of shape `(batch_size,)`):
|
| 229 |
+
The chosen motion length in tokens; the motion spans `num_tokens * frames_per_token` frames.
|
| 230 |
+
root (`torch.Tensor` of shape `(batch_size, num_frames, 5)`):
|
| 231 |
+
The normalized global root of every frame: position, then cosine and sine of the heading.
|
| 232 |
+
"""
|
| 233 |
+
|
| 234 |
+
num_tokens: torch.LongTensor
|
| 235 |
+
root: torch.Tensor
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
class MotionBricksRootModel(ModelMixin, ConfigMixin, AttentionMixin):
|
| 239 |
+
"""Chooses the length of a transition and predicts its root trajectory from `context_frames` context poses and
|
| 240 |
+
`target_frames` target poses. Stores the normalization statistics of the guide features as buffers."""
|
| 241 |
+
|
| 242 |
+
_no_split_modules = ["MotionBricksTransformerLayer", "MotionBricksConditionedDecoder"]
|
| 243 |
+
_repeated_blocks = ["MotionBricksTransformerLayer"]
|
| 244 |
+
_skip_layerwise_casting_patterns = ["norm", "embed"]
|
| 245 |
+
|
| 246 |
+
@register_to_config
|
| 247 |
+
def __init__(
|
| 248 |
+
self,
|
| 249 |
+
pose_dim: int = 268,
|
| 250 |
+
global_root_dim: int = 5,
|
| 251 |
+
local_root_dim: int = 4,
|
| 252 |
+
pose_embed_dim: int = 256,
|
| 253 |
+
root_embed_dim: int = 64,
|
| 254 |
+
hidden_dim: int = 512,
|
| 255 |
+
num_heads: int = 16,
|
| 256 |
+
ff_dim: int = 2048,
|
| 257 |
+
num_shared_layers: int = 6,
|
| 258 |
+
num_token_layers: int = 2,
|
| 259 |
+
context_frames: int = 4,
|
| 260 |
+
target_frames: int = 4,
|
| 261 |
+
frames_per_token: int = 4,
|
| 262 |
+
min_tokens: int = 6,
|
| 263 |
+
max_tokens: int = 16,
|
| 264 |
+
decoder_depth: int = 4,
|
| 265 |
+
decoder_dilation_growth_rate: int = 3,
|
| 266 |
+
):
|
| 267 |
+
super().__init__()
|
| 268 |
+
guide_frames = context_frames + target_frames
|
| 269 |
+
num_lengths = max_tokens - min_tokens + 1
|
| 270 |
+
guide_dim = pose_embed_dim + 2 * root_embed_dim
|
| 271 |
+
|
| 272 |
+
self.pose_proj = nn.Linear(pose_dim, pose_embed_dim)
|
| 273 |
+
self.local_root_proj = nn.Linear(local_root_dim, root_embed_dim)
|
| 274 |
+
self.global_root_proj = nn.Linear(global_root_dim, root_embed_dim)
|
| 275 |
+
self.missing_pose_embed = nn.Parameter(torch.zeros(pose_embed_dim))
|
| 276 |
+
self.missing_local_root_embed = nn.Parameter(torch.zeros(root_embed_dim))
|
| 277 |
+
self.missing_global_root_embed = nn.Parameter(torch.zeros(root_embed_dim))
|
| 278 |
+
self.context_proj = MotionBricksMLP(guide_dim, hidden_dim, hidden_dim)
|
| 279 |
+
self.target_proj = MotionBricksMLP(guide_dim, hidden_dim, hidden_dim)
|
| 280 |
+
self.guide_position_embed = nn.Parameter(torch.zeros(guide_frames, hidden_dim))
|
| 281 |
+
|
| 282 |
+
# Index num_lengths + 1 asks for a prediction; the length classes are min_tokens..max_tokens and one unused.
|
| 283 |
+
self.length_query_embed = nn.Embedding(num_lengths + 2, hidden_dim)
|
| 284 |
+
self.shared_layers = nn.ModuleList(
|
| 285 |
+
[MotionBricksTransformerLayer(hidden_dim, num_heads, ff_dim) for _ in range(num_shared_layers)]
|
| 286 |
+
)
|
| 287 |
+
self.length_head = nn.Linear(hidden_dim, num_lengths + 1)
|
| 288 |
+
|
| 289 |
+
self.length_embed = nn.Embedding(num_lengths + 1, hidden_dim)
|
| 290 |
+
self.token_position_embed = nn.Parameter(torch.zeros(max_tokens, hidden_dim))
|
| 291 |
+
self.token_layers = nn.ModuleList(
|
| 292 |
+
[MotionBricksTransformerLayer(hidden_dim, num_heads, ff_dim) for _ in range(num_token_layers)]
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
self.missing_frame_embed = nn.Parameter(torch.zeros(hidden_dim))
|
| 296 |
+
self.decoder = MotionBricksConditionedDecoder(
|
| 297 |
+
hidden_dim,
|
| 298 |
+
global_root_dim,
|
| 299 |
+
target_channels=global_root_dim,
|
| 300 |
+
external_channels=hidden_dim,
|
| 301 |
+
width=hidden_dim,
|
| 302 |
+
depth=decoder_depth,
|
| 303 |
+
dilation_growth_rate=decoder_dilation_growth_rate,
|
| 304 |
+
num_tokens=max_tokens,
|
| 305 |
+
num_frames=max_tokens * frames_per_token,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
self.register_buffer("global_root_mean", torch.zeros(global_root_dim))
|
| 309 |
+
self.register_buffer("global_root_std", torch.ones(global_root_dim))
|
| 310 |
+
self.register_buffer("local_root_mean", torch.zeros(local_root_dim))
|
| 311 |
+
self.register_buffer("local_root_std", torch.ones(local_root_dim))
|
| 312 |
+
self.register_buffer("pose_mean", torch.zeros(pose_dim))
|
| 313 |
+
self.register_buffer("pose_std", torch.ones(pose_dim))
|
| 314 |
+
|
| 315 |
+
def _statistics(self, value: torch.Tensor, name: str) -> tuple[torch.Tensor, torch.Tensor]:
|
| 316 |
+
# The buffers can sit on another device while the model is offloaded.
|
| 317 |
+
mean, std = getattr(self, f"{name}_mean"), getattr(self, f"{name}_std")
|
| 318 |
+
return mean.to(value.device, value.dtype), std.to(value.device, value.dtype)
|
| 319 |
+
|
| 320 |
+
def normalize(self, value: torch.Tensor, name: str) -> torch.Tensor:
|
| 321 |
+
"""Normalize `global_root`, `local_root` or `pose` features with this model's statistics."""
|
| 322 |
+
mean, std = self._statistics(value, name)
|
| 323 |
+
return (value - mean) / std
|
| 324 |
+
|
| 325 |
+
def denormalize(self, value: torch.Tensor, name: str) -> torch.Tensor:
|
| 326 |
+
mean, std = self._statistics(value, name)
|
| 327 |
+
return value * std + mean
|
| 328 |
+
|
| 329 |
+
def place_guides(self, guides: torch.Tensor, num_tokens: torch.Tensor, fill) -> torch.Tensor:
|
| 330 |
+
"""Place `(batch_size, guide_frames, ...)` guide values on the frame timeline: the context poses at the start
|
| 331 |
+
and the target poses at the last frames of the chosen length. Other frames hold `fill`."""
|
| 332 |
+
config = self.config
|
| 333 |
+
shape = (guides.shape[0], config.max_tokens * config.frames_per_token, *guides.shape[2:])
|
| 334 |
+
if isinstance(fill, torch.Tensor):
|
| 335 |
+
timeline = fill.to(guides.dtype).expand(shape).clone()
|
| 336 |
+
else:
|
| 337 |
+
timeline = torch.full(shape, fill, dtype=guides.dtype, device=guides.device)
|
| 338 |
+
timeline[:, : config.context_frames] = guides[:, : config.context_frames]
|
| 339 |
+
end = num_tokens * config.frames_per_token
|
| 340 |
+
frames = end[:, None] - config.target_frames + torch.arange(config.target_frames, device=end.device)
|
| 341 |
+
batch = torch.arange(guides.shape[0], device=guides.device)[:, None]
|
| 342 |
+
timeline[batch, frames] = guides[:, config.context_frames :]
|
| 343 |
+
return timeline
|
| 344 |
+
|
| 345 |
+
def forward(
|
| 346 |
+
self,
|
| 347 |
+
global_root: torch.Tensor,
|
| 348 |
+
local_root: torch.Tensor,
|
| 349 |
+
pose: torch.Tensor,
|
| 350 |
+
has_global_root: torch.Tensor,
|
| 351 |
+
has_local_root: torch.Tensor,
|
| 352 |
+
has_pose: torch.Tensor,
|
| 353 |
+
allowed_num_tokens: torch.Tensor,
|
| 354 |
+
return_dict: bool = True,
|
| 355 |
+
) -> MotionBricksRootOutput | tuple:
|
| 356 |
+
"""
|
| 357 |
+
Args:
|
| 358 |
+
global_root (`torch.Tensor` of shape `(batch_size, guide_frames, global_root_dim)`):
|
| 359 |
+
Normalized global root of the guide poses.
|
| 360 |
+
local_root (`torch.Tensor` of shape `(batch_size, guide_frames, local_root_dim)`):
|
| 361 |
+
Normalized local root of the guide poses.
|
| 362 |
+
pose (`torch.Tensor` of shape `(batch_size, guide_frames, pose_dim)`):
|
| 363 |
+
Normalized pose features of the guide poses.
|
| 364 |
+
has_global_root (`torch.BoolTensor` of shape `(batch_size, guide_frames)`):
|
| 365 |
+
Which guide poses have a global root.
|
| 366 |
+
has_local_root (`torch.BoolTensor` of shape `(batch_size, guide_frames)`):
|
| 367 |
+
Which guide poses have a local root.
|
| 368 |
+
has_pose (`torch.BoolTensor` of shape `(batch_size, guide_frames)`):
|
| 369 |
+
Which guide poses have pose features.
|
| 370 |
+
allowed_num_tokens (`torch.BoolTensor` of shape `(batch_size, max_tokens - min_tokens + 1)`):
|
| 371 |
+
The lengths the model may choose, from `min_tokens`.
|
| 372 |
+
return_dict (`bool`, defaults to `True`):
|
| 373 |
+
Whether to return a `MotionBricksRootOutput` instead of a tuple.
|
| 374 |
+
|
| 375 |
+
Returns:
|
| 376 |
+
`MotionBricksRootOutput` or `tuple`: The chosen length and the normalized root; a tuple of the two when
|
| 377 |
+
`return_dict` is `False`.
|
| 378 |
+
"""
|
| 379 |
+
config = self.config
|
| 380 |
+
has_pose_f = has_pose[..., None].to(pose.dtype)
|
| 381 |
+
has_local_f = has_local_root[..., None].to(pose.dtype)
|
| 382 |
+
has_global_f = has_global_root[..., None].to(pose.dtype)
|
| 383 |
+
pose_embeds = self.pose_proj(pose) * has_pose_f + self.missing_pose_embed * (1 - has_pose_f)
|
| 384 |
+
local_embeds = self.local_root_proj(local_root) * has_local_f + self.missing_local_root_embed * (
|
| 385 |
+
1 - has_local_f
|
| 386 |
+
)
|
| 387 |
+
global_embeds = self.global_root_proj(global_root) * has_global_f + self.missing_global_root_embed * (
|
| 388 |
+
1 - has_global_f
|
| 389 |
+
)
|
| 390 |
+
guides = torch.cat([pose_embeds, local_embeds, global_embeds], dim=-1)
|
| 391 |
+
guides = torch.cat(
|
| 392 |
+
[
|
| 393 |
+
self.context_proj(guides[:, : config.context_frames]),
|
| 394 |
+
self.target_proj(guides[:, config.context_frames :]),
|
| 395 |
+
],
|
| 396 |
+
dim=1,
|
| 397 |
+
)
|
| 398 |
+
positioned = guides + self.guide_position_embed
|
| 399 |
+
|
| 400 |
+
batch_size = guides.shape[0]
|
| 401 |
+
num_lengths = config.max_tokens - config.min_tokens + 1
|
| 402 |
+
query = self.length_query_embed(torch.full((batch_size, 1), num_lengths + 1, device=guides.device))
|
| 403 |
+
hidden_states = torch.cat([query, positioned], dim=1)
|
| 404 |
+
for layer in self.shared_layers:
|
| 405 |
+
hidden_states = layer(hidden_states)
|
| 406 |
+
logits = self.length_head(hidden_states[:, 0])
|
| 407 |
+
allowed = F.pad(allowed_num_tokens, (0, 1), value=False)
|
| 408 |
+
length = logits.masked_fill(~allowed, float("-inf")).argmax(dim=-1)
|
| 409 |
+
num_tokens = length + config.min_tokens
|
| 410 |
+
|
| 411 |
+
tokens = self.token_position_embed.expand(batch_size, -1, -1)
|
| 412 |
+
hidden_states = torch.cat([self.length_embed(length)[:, None], positioned, tokens], dim=1)
|
| 413 |
+
mask = key_mask(num_tokens, config.max_tokens, prefix=1 + guides.shape[1])
|
| 414 |
+
for layer in self.token_layers:
|
| 415 |
+
hidden_states = layer(hidden_states, mask)
|
| 416 |
+
tokens = hidden_states[:, -config.max_tokens :]
|
| 417 |
+
|
| 418 |
+
has_guide = has_pose | has_local_root | has_global_root
|
| 419 |
+
frame_guides = torch.where(has_guide[..., None], guides, self.missing_frame_embed)
|
| 420 |
+
external = self.place_guides(frame_guides, num_tokens, fill=self.missing_frame_embed)
|
| 421 |
+
target = self.place_guides(global_root, num_tokens, fill=0)
|
| 422 |
+
target_mask = self.place_guides(has_global_root, num_tokens, fill=False)
|
| 423 |
+
token_mask = (torch.arange(config.max_tokens, device=num_tokens.device)[None] < num_tokens[:, None]).to(
|
| 424 |
+
tokens.dtype
|
| 425 |
+
)
|
| 426 |
+
root = self.decoder(tokens.transpose(1, 2), token_mask, target, target_mask, external).transpose(1, 2)
|
| 427 |
+
|
| 428 |
+
if not return_dict:
|
| 429 |
+
return (num_tokens, root)
|
| 430 |
+
return MotionBricksRootOutput(num_tokens=num_tokens, root=root)
|