Wojtekb30 commited on
Commit
316a030
·
verified ·
1 Parent(s): 3aa2b0b

Upload 11 files

Browse files
Files changed (11) hide show
  1. 000001.npy +3 -0
  2. 000012.npy +3 -0
  3. Mean.npy +3 -0
  4. README.md +102 -3
  5. Std.npy +3 -0
  6. TestRVQ.py +144 -0
  7. TrainRVQ.py +68 -0
  8. config.json +23 -0
  9. motion_rvq_weights.safetensors +3 -0
  10. rvq_humanml_dataset.py +42 -0
  11. rvq_model.py +140 -0
000001.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0c737af5a59d4fc9667092ce8bd31a4830dc20ec00444123c1ecabaed5715abd
3
+ size 36948
000012.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1ddd5759d18e10a7fbafd4f5ce49a18d7fffd65706d3c020145b4b1b0c357228
3
+ size 84288
Mean.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:26e136555dab04c94a129d446c26e6b9939cbf045fbf77bcf5462c1fb5a2001c
3
+ size 1180
README.md CHANGED
@@ -1,3 +1,102 @@
1
- ---
2
- license: cc-by-nc-4.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: pytorch
3
+ tags:
4
+ - motion
5
+ - rvq
6
+ - vector-quantization
7
+ - human-motion
8
+ - safetensors
9
+ - humanML
10
+ - motion-reconstructor
11
+ license: cc-by-nc-4.0
12
+ datasets:
13
+ - Wojtekb30/HumanML3D-500ms-FPP-descriptions-CoTs-1
14
+ ---
15
+
16
+ # Motion RVQ (Move Reconstruction)
17
+
18
+ This model uses Residual Vector Quantization (RVQ) to reconstruct motion represented as 263-dimensional frame vectors.
19
+ It is a custom PyTorch model (not a Transformers `AutoModel`) and is loaded from `safetensors` with `rvq_model.py`.
20
+
21
+ ## Model Summary
22
+
23
+ - Architecture: encoder -> 4-level RVQ -> decoder
24
+ - Input shape: `(T, 263)` per sequence (frame-major)
25
+ - Training window: 100 frames (with crop/pad in dataset loader)
26
+ - Output: reconstructed motion sequence in the same 263-dim representation
27
+
28
+ ## Repository Files
29
+
30
+ - `motion_rvq_weights.safetensors` - main published checkpoint
31
+ - `config.json` - model configuration metadata
32
+ - `rvq_model.py` - model architecture (`MotionRVQ_VAE`)
33
+ - `TestRVQ.py` - inference + 3-panel visualization
34
+ - `TrainRVQ.py` - training script
35
+ - `rvq_humanml_dataset.py` - training dataset loader
36
+ - `Mean.npy`, `Std.npy` - normalization statistics
37
+ - `000001.npy`, `000012.npy` - sample motion files
38
+
39
+ `motion_rvq_weights.pth` can be treated as a legacy artifact; code uses `motion_rvq_weights.safetensors`.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install torch safetensors numpy matplotlib
45
+ ```
46
+
47
+ ## Inference
48
+
49
+ Run the provided visualization script:
50
+
51
+ ```bash
52
+ python TestRVQ.py
53
+ ```
54
+
55
+ By default, `TestRVQ.py` uses `000001.npy`. You can change `FILE_TO_TEST` in `TestRVQ.py` to another sequence.
56
+
57
+ Minimal loading example:
58
+
59
+ ```python
60
+ from pathlib import Path
61
+ import torch
62
+ from safetensors.torch import load_file
63
+ from rvq_model import MotionRVQ_VAE
64
+
65
+ base = Path(".")
66
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
67
+
68
+ model = MotionRVQ_VAE().to(device)
69
+ state_dict = load_file(str(base / "motion_rvq_weights.safetensors"), device=str(device))
70
+ model.load_state_dict(state_dict)
71
+ model.eval()
72
+ ```
73
+
74
+ ## Training From Scratch
75
+
76
+ Expected layout:
77
+
78
+ ```text
79
+ rvq/
80
+ TrainRVQ.py
81
+ rvq_model.py
82
+ rvq_humanml_dataset.py
83
+ Mean.npy
84
+ Std.npy
85
+ new_joint_vecs/
86
+ *.npy
87
+ ```
88
+
89
+ Run training:
90
+
91
+ ```bash
92
+ python TrainRVQ.py
93
+ ```
94
+
95
+ Output checkpoint:
96
+
97
+ - `motion_rvq_weights.safetensors`
98
+
99
+ ## Limitations
100
+
101
+ - This model reconstructs motion vectors; it is not a text-to-motion generator.
102
+ - Input format must match the same 263-dim representation and normalization scheme used during training.
Std.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6565a65ed9b31e23c328829a309e1c482be8b85fd23b43d65451a9b19a917f40
3
+ size 1180
TestRVQ.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib.animation as animation
5
+ import torch.nn.functional as F
6
+ from pathlib import Path
7
+ from safetensors.torch import load_file
8
+
9
+ # Import model architecture
10
+ from rvq_model import MotionRVQ_VAE
11
+
12
+ # ==========================================
13
+ # 1. Configuration
14
+ # ==========================================
15
+ BASE_DIR = Path(__file__).resolve().parent
16
+ FILE_TO_TEST = BASE_DIR / "000001.npy"
17
+ WEIGHTS_PATH = BASE_DIR / "motion_rvq_weights.safetensors"
18
+
19
+ # ==========================================
20
+ # 2. Model Initialization
21
+ # ==========================================
22
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
23
+ model = MotionRVQ_VAE().to(device)
24
+
25
+ try:
26
+ state_dict = load_file(str(WEIGHTS_PATH), device=str(device))
27
+ model.load_state_dict(state_dict)
28
+ print(f"Successfully loaded weights from {WEIGHTS_PATH}!")
29
+ except FileNotFoundError:
30
+ print(f"ERROR: Could not find file {WEIGHTS_PATH}.")
31
+ exit()
32
+
33
+ model.eval()
34
+
35
+ # ==========================================
36
+ # 3. Forward Pass (With Padding and Normalization)
37
+ # ==========================================
38
+ original_data = np.load(FILE_TO_TEST)
39
+ T_orig = original_data.shape[0]
40
+
41
+ # Load normalization vectors
42
+ mean = np.load(BASE_DIR / "Mean.npy")
43
+ std = np.load(BASE_DIR / "Std.npy")
44
+
45
+ # Padding for stride=4 compression
46
+ pad_len = (4 - (T_orig % 4)) % 4
47
+ if pad_len > 0:
48
+ last_frame = original_data[-1:]
49
+ padded_data = np.concatenate([original_data, np.repeat(last_frame, pad_len, axis=0)], axis=0)
50
+ else:
51
+ padded_data = original_data
52
+
53
+ padded_data = (padded_data - mean) / std
54
+ input_tensor = torch.from_numpy(padded_data).float().unsqueeze(0).permute(0, 2, 1).to(device)
55
+
56
+ with torch.no_grad():
57
+ # 1. Get tokens from all levels
58
+ z = model.encoder(input_tensor)
59
+ _, tokens, _ = model.rvq(z)
60
+
61
+ # 2. Function for "partial" decoding
62
+ def decode_from_levels(num_levels):
63
+ z_q_partial = 0
64
+ for i in range(num_levels):
65
+ # Get indices only for level "i"
66
+ indices = tokens[:, i, :]
67
+ quantizer = model.rvq.quantizers[i]
68
+
69
+ # Convert token id (e.g. 145) into its 1024-codebook vector
70
+ level_z_q = F.embedding(indices, quantizer.embedding)
71
+ level_z_q = level_z_q.permute(0, 2, 1) # Shape expected by decoder
72
+
73
+ # Add residual vector to the running latent
74
+ z_q_partial = z_q_partial + level_z_q
75
+
76
+ return model.decoder(z_q_partial)
77
+
78
+ # Generate motion using only level 1 and all 4 levels
79
+ recon_tensor_1_lvl = decode_from_levels(1)
80
+ recon_tensor_4_lvl = decode_from_levels(4)
81
+
82
+ # Back to NumPy
83
+ recon_1_lvl = recon_tensor_1_lvl.squeeze(0).permute(1, 0).cpu().numpy()[:T_orig, :]
84
+ recon_4_lvl = recon_tensor_4_lvl.squeeze(0).permute(1, 0).cpu().numpy()[:T_orig, :]
85
+
86
+ # De-normalization
87
+ recon_1_lvl = (recon_1_lvl * std) + mean
88
+ recon_4_lvl = (recon_4_lvl * std) + mean
89
+
90
+ # ==========================================
91
+ # 4. Skeleton extraction utility
92
+ # ==========================================
93
+ def get_3d_joints(data_263):
94
+ num_frames = data_263.shape[0]
95
+ joints = np.zeros((num_frames, 22, 3))
96
+ for i in range(num_frames):
97
+ root_y = data_263[i, 3]
98
+ joints[i, 0] = [0, root_y, 0]
99
+ local_positions = data_263[i, 4:67].reshape(21, 3)
100
+ joints[i, 1:] = local_positions + [0, root_y, 0]
101
+ return joints
102
+
103
+ joints_orig = get_3d_joints(original_data)
104
+ joints_1_lvl = get_3d_joints(recon_1_lvl)
105
+ joints_4_lvl = get_3d_joints(recon_4_lvl)
106
+
107
+ # ==========================================
108
+ # 5. Three-panel visualization
109
+ # ==========================================
110
+ kinematic_tree = [
111
+ [0, 1, 4, 7, 10], [0, 2, 5, 8, 11], [0, 3, 6, 9, 12, 15],
112
+ [9, 13, 16, 18, 20], [9, 14, 17, 19, 21]
113
+ ]
114
+
115
+ fig = plt.figure(figsize=(15, 5))
116
+ ax1 = fig.add_subplot(131, projection='3d')
117
+ ax2 = fig.add_subplot(132, projection='3d')
118
+ ax3 = fig.add_subplot(133, projection='3d')
119
+
120
+ def update(frame):
121
+ for ax in [ax1, ax2, ax3]:
122
+ ax.clear()
123
+ ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(0, 2)
124
+ ax.view_init(elev=10., azim=-90)
125
+ ax.axis('off')
126
+
127
+ ax1.set_title(f"ORIGINAL\n(Frame {frame})")
128
+ ax2.set_title("RECONSTRUCTION: 1 LEVEL\n(Coarse tokens only)")
129
+ ax3.set_title("RECONSTRUCTION: 4 LEVELS\n(Full RVQ detail)")
130
+
131
+ for chain in kinematic_tree:
132
+ # Original (Blue)
133
+ ax1.plot(joints_orig[frame, chain, 0], joints_orig[frame, chain, 2], joints_orig[frame, chain, 1],
134
+ linewidth=3, marker='o', markersize=4, color='blue')
135
+ # 1 Level (Orange)
136
+ ax2.plot(joints_1_lvl[frame, chain, 0], joints_1_lvl[frame, chain, 2], joints_1_lvl[frame, chain, 1],
137
+ linewidth=3, marker='o', markersize=4, color='orange')
138
+ # 4 Levels (Red)
139
+ ax3.plot(joints_4_lvl[frame, chain, 0], joints_4_lvl[frame, chain, 2], joints_4_lvl[frame, chain, 1],
140
+ linewidth=3, marker='o', markersize=4, color='red')
141
+
142
+ ani = animation.FuncAnimation(fig, update, frames=T_orig, interval=50, repeat=True)
143
+ plt.tight_layout()
144
+ plt.show()
TrainRVQ.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import multiprocessing
2
+ from pathlib import Path
3
+
4
+ import torch
5
+ import torch.nn.functional as F
6
+ import torch.optim as optim
7
+ from safetensors.torch import save_file
8
+
9
+ from rvq_model import MotionRVQ_VAE
10
+
11
+
12
+ if __name__ == "__main__":
13
+ multiprocessing.freeze_support()
14
+
15
+ from rvq_humanml_dataset import DataLoader, HumanML3DDataset
16
+
17
+ base_dir = Path(__file__).resolve().parent
18
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
19
+ print(f"Training on: {device}")
20
+
21
+ model = MotionRVQ_VAE().to(device)
22
+
23
+ dataset = HumanML3DDataset(data_dir=str(base_dir / "new_joint_vecs"), window_size=100)
24
+ dataloader = DataLoader(
25
+ dataset,
26
+ batch_size=32,
27
+ shuffle=True,
28
+ num_workers=4,
29
+ drop_last=True,
30
+ )
31
+
32
+ optimizer = optim.AdamW(model.parameters(), lr=2e-4, weight_decay=1e-4)
33
+ num_epochs = 5
34
+
35
+ model.train()
36
+ for epoch in range(num_epochs):
37
+ epoch_loss = 0.0
38
+
39
+ for batch_idx, batch in enumerate(dataloader):
40
+ batch = batch.to(device)
41
+ optimizer.zero_grad()
42
+
43
+ reconstructed, _, commit_loss = model(batch)
44
+ pos_loss = F.mse_loss(reconstructed, batch)
45
+
46
+ vel_orig = batch[:, :, 1:] - batch[:, :, :-1]
47
+ vel_recon = reconstructed[:, :, 1:] - reconstructed[:, :, :-1]
48
+ vel_loss = F.mse_loss(vel_recon, vel_orig)
49
+
50
+ reconstruction_loss = pos_loss + (1.5 * vel_loss)
51
+ loss = reconstruction_loss + commit_loss
52
+ loss.backward()
53
+ optimizer.step()
54
+
55
+ epoch_loss += loss.item()
56
+
57
+ if batch_idx % 50 == 0:
58
+ print(
59
+ f"Epoch [{epoch + 1}/{num_epochs}] Batch [{batch_idx}/{len(dataloader)}] "
60
+ f"MSE: {reconstruction_loss.item():.4f} | Commit: {commit_loss.item():.4f}"
61
+ )
62
+
63
+ print(f"--- End of epoch {epoch + 1} | Avg loss: {epoch_loss / len(dataloader):.4f} ---")
64
+
65
+ weights_path = base_dir / "motion_rvq_weights.safetensors"
66
+ state_dict = {k: v.detach().cpu() for k, v in model.state_dict().items()}
67
+ save_file(state_dict, str(weights_path))
68
+ print(f"Training complete and model saved to: {weights_path}")
config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MotionRVQ_VAE"
4
+ ],
5
+ "model_type": "motion_rvq_vae",
6
+ "library_name": "pytorch",
7
+ "torch_dtype": "float32",
8
+ "in_channels": 263,
9
+ "out_channels": 263,
10
+ "latent_dim": 512,
11
+ "encoder_hidden_channels": 512,
12
+ "decoder_hidden_channels": 512,
13
+ "rvq_num_levels": 4,
14
+ "rvq_num_embeddings": 1024,
15
+ "rvq_embedding_dim": 512,
16
+ "rvq_commitment_cost": 0.25,
17
+ "rvq_decay": 0.99,
18
+ "rvq_epsilon": 1e-5,
19
+ "downsample_stride": 4,
20
+ "window_size": 100,
21
+ "input_representation": "263-dim frame vectors",
22
+ "task": "motion_reconstruction"
23
+ }
motion_rvq_weights.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7bbee9c0dfbe42875ace86db0f584f8634e83711f231bbaca551eb65a3e504f8
3
+ size 74587252
rvq_humanml_dataset.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import glob
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import torch
6
+ from torch.utils.data import Dataset, DataLoader
7
+
8
+
9
+ class HumanML3DDataset(Dataset):
10
+ def __init__(self, data_dir='./new_joint_vecs', window_size=100):
11
+ self.data_dir = str(data_dir)
12
+ self.window_size = window_size
13
+ self.file_paths = glob.glob(os.path.join(self.data_dir, '*.npy'))
14
+
15
+ data_dir_path = Path(self.data_dir).resolve()
16
+ base_dir = data_dir_path.parent
17
+ self.mean = torch.from_numpy(np.load(base_dir / 'Mean.npy')).float()
18
+ self.std = torch.from_numpy(np.load(base_dir / 'Std.npy')).float()
19
+
20
+ print(f"Dataset initialized: found {len(self.file_paths)} files.")
21
+
22
+ def __len__(self):
23
+ return len(self.file_paths)
24
+
25
+ def __getitem__(self, idx):
26
+ data = np.load(self.file_paths[idx])
27
+ tensor_data = torch.from_numpy(data).float()
28
+
29
+ tensor_data = (tensor_data - self.mean) / self.std
30
+
31
+ t_len, _ = tensor_data.shape
32
+ if t_len > self.window_size:
33
+ start = torch.randint(0, t_len - self.window_size + 1, (1,)).item()
34
+ tensor_data = tensor_data[start : start + self.window_size, :]
35
+ elif t_len < self.window_size:
36
+ padding_size = self.window_size - t_len
37
+ last_pose = tensor_data[-1].unsqueeze(0)
38
+ padding = last_pose.repeat(padding_size, 1)
39
+ tensor_data = torch.cat([tensor_data, padding], dim=0)
40
+
41
+ tensor_data = tensor_data.permute(1, 0)
42
+ return tensor_data
rvq_model.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+
6
+ class EMAVectorQuantizer(nn.Module):
7
+ def __init__(
8
+ self,
9
+ num_embeddings=512,
10
+ embedding_dim=256,
11
+ commitment_cost=0.25,
12
+ decay=0.99,
13
+ epsilon=1e-5,
14
+ ):
15
+ super().__init__()
16
+ self.num_embeddings = num_embeddings
17
+ self.embedding_dim = embedding_dim
18
+ self.commitment_cost = commitment_cost
19
+ self.decay = decay
20
+ self.epsilon = epsilon
21
+
22
+ embed = torch.randn(num_embeddings, embedding_dim)
23
+ self.register_buffer("embedding", embed)
24
+ self.register_buffer("cluster_size", torch.zeros(num_embeddings))
25
+ self.register_buffer("ema_w", embed.clone())
26
+
27
+ def forward(self, z):
28
+ z = z.permute(0, 2, 1).contiguous()
29
+ z_flattened = z.view(-1, self.embedding_dim)
30
+
31
+ distances = (
32
+ torch.sum(z_flattened**2, dim=1, keepdim=True)
33
+ + torch.sum(self.embedding**2, dim=1)
34
+ - 2 * torch.matmul(z_flattened, self.embedding.t())
35
+ )
36
+
37
+ min_encoding_indices = torch.argmin(distances, dim=1)
38
+ z_q = F.embedding(min_encoding_indices, self.embedding)
39
+
40
+ if self.training:
41
+ encodings = F.one_hot(min_encoding_indices, self.num_embeddings).float()
42
+ self.cluster_size.data.mul_(self.decay).add_(encodings.sum(0), alpha=1 - self.decay)
43
+
44
+ n = self.cluster_size.sum()
45
+ cluster_size = (self.cluster_size + self.epsilon) / (
46
+ n + self.num_embeddings * self.epsilon
47
+ ) * n
48
+
49
+ dw = torch.matmul(encodings.t(), z_flattened)
50
+ self.ema_w.data.mul_(self.decay).add_(dw, alpha=1 - self.decay)
51
+ self.embedding.data.copy_(self.ema_w / cluster_size.unsqueeze(1))
52
+
53
+ loss = self.commitment_cost * F.mse_loss(z_q.detach(), z_flattened)
54
+ z_q = z_flattened + (z_q - z_flattened).detach()
55
+ z_q = z_q.view(z.shape).permute(0, 2, 1).contiguous()
56
+
57
+ return z_q, min_encoding_indices.view(z.shape[0], z.shape[1]), loss
58
+
59
+
60
+ class RVQ(nn.Module):
61
+ def __init__(self, num_levels=3, num_embeddings=512, embedding_dim=256):
62
+ super().__init__()
63
+ self.num_levels = num_levels
64
+ self.quantizers = nn.ModuleList(
65
+ [EMAVectorQuantizer(num_embeddings, embedding_dim) for _ in range(num_levels)]
66
+ )
67
+
68
+ def forward(self, z):
69
+ quantized_out = 0
70
+ residual = z
71
+ all_indices = []
72
+ total_loss = 0
73
+
74
+ for quantizer in self.quantizers:
75
+ z_q, indices, loss = quantizer(residual)
76
+ quantized_out = quantized_out + z_q
77
+ residual = residual - z_q
78
+ all_indices.append(indices)
79
+ total_loss += loss
80
+
81
+ return quantized_out, torch.stack(all_indices, dim=1), total_loss
82
+
83
+
84
+ class ResBlock1D(nn.Module):
85
+ def __init__(self, channels):
86
+ super().__init__()
87
+ self.net = nn.Sequential(
88
+ nn.Conv1d(channels, channels, kernel_size=3, padding=1),
89
+ nn.LeakyReLU(0.2, inplace=True),
90
+ nn.Conv1d(channels, channels, kernel_size=3, padding=1),
91
+ )
92
+
93
+ def forward(self, x):
94
+ return x + self.net(x)
95
+
96
+
97
+ class MotionEncoder(nn.Module):
98
+ def __init__(self, in_channels=263, latent_dim=512):
99
+ super().__init__()
100
+ self.net = nn.Sequential(
101
+ nn.Conv1d(in_channels, 512, kernel_size=3, padding=1),
102
+ nn.LeakyReLU(0.2, inplace=True),
103
+ ResBlock1D(512),
104
+ ResBlock1D(512),
105
+ ResBlock1D(512),
106
+ nn.Conv1d(512, latent_dim, kernel_size=8, stride=4, padding=2),
107
+ )
108
+
109
+ def forward(self, x):
110
+ return self.net(x)
111
+
112
+
113
+ class MotionDecoder(nn.Module):
114
+ def __init__(self, latent_dim=512, out_channels=263):
115
+ super().__init__()
116
+ self.net = nn.Sequential(
117
+ nn.ConvTranspose1d(latent_dim, 512, kernel_size=8, stride=4, padding=2),
118
+ nn.LeakyReLU(0.2, inplace=True),
119
+ ResBlock1D(512),
120
+ ResBlock1D(512),
121
+ ResBlock1D(512),
122
+ nn.Conv1d(512, out_channels, kernel_size=3, padding=1),
123
+ )
124
+
125
+ def forward(self, z_q):
126
+ return self.net(z_q)
127
+
128
+
129
+ class MotionRVQ_VAE(nn.Module):
130
+ def __init__(self):
131
+ super().__init__()
132
+ self.encoder = MotionEncoder(in_channels=263, latent_dim=512)
133
+ self.rvq = RVQ(num_levels=4, num_embeddings=1024, embedding_dim=512)
134
+ self.decoder = MotionDecoder(latent_dim=512, out_channels=263)
135
+
136
+ def forward(self, x):
137
+ z = self.encoder(x)
138
+ z_q, token_indices, commitment_loss = self.rvq(z)
139
+ x_recon = self.decoder(z_q)
140
+ return x_recon, token_indices, commitment_loss