bghira commited on
Commit
7c52570
·
verified ·
1 Parent(s): 31aa852

Add condition-embedding replay comparison

Browse files
README.md CHANGED
@@ -340,6 +340,34 @@ Interpretation:
340
 
341
  Machine-readable comparison: [`comparison-metrics.json`](evaluation/v3/comparison-metrics.json).
342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  ## Limitations
344
 
345
  - 5.12-second context.
@@ -350,7 +378,7 @@ Machine-readable comparison: [`comparison-metrics.json`](evaluation/v3/compariso
350
  - Exact-token accuracy understates perceptual equivalence.
351
  - Semantic CE can dominate early because its vocabulary is larger.
352
  - Acoustic heads are independent despite residual-codebook dependence.
353
- - End-to-end condition-embedding replay evaluation is pending.
354
  - No stable packaged loading API.
355
  - Use is subject to MiniMax Music 3, dataset, and MERT terms.
356
 
 
340
 
341
  Machine-readable comparison: [`comparison-metrics.json`](evaluation/v3/comparison-metrics.json).
342
 
343
+ ## Condition-Embedding Replay Comparison
344
+
345
+ Protocol:
346
+
347
+ - 130 exact-alignment holdout tracks;
348
+ - each final checkpoint predicts argmax RVQ codes from cached DAV latents;
349
+ - predicted codes are teacher-forced through the official language model and RVQ depth decoder;
350
+ - hidden states pass through the official condition encoder with recorded chunk stitching;
351
+ - reconstructed condition embeddings are compared with stored condition embeddings;
352
+ - metric: per-track mean cosine over stitched condition-latent frames;
353
+ - true sampled codes provide the replay control.
354
+
355
+ | Model | Parameters | Mean cosine | Standard deviation | 5th-95th percentile |
356
+ |---|---:|---:|---:|---:|
357
+ | Serveurperso v1 | 40,978,944 | 0.663329 | 0.022175 | 0.628052-0.696328 |
358
+ | SimpleTuner v1 | 40,978,944 | 0.762442 | 0.019550 | 0.734519-0.790450 |
359
+ | SimpleTuner v2 | 154,736,064 | 0.769841 | 0.019063 | 0.742991-0.798636 |
360
+ | SimpleTuner v3 | 154,736,064 | **0.770259** | 0.019274 | 0.741585-0.800492 |
361
+ | True-code control | - | 0.999907 | - | - |
362
+
363
+ ![Condition-embedding replay comparison](evaluation/replay-comparison/condition-embedding-replay-comparison.png)
364
+
365
+ V1 exceeds the independent Serveurperso checkpoint by 0.099114 mean cosine. V2 adds 0.007399 over v1. V3 adds 0.000418 over v2. The MERT gain remains small downstream.
366
+
367
+ This test stops before diffusion and DAV decode. It is not an STFT, waveform, lyric-identity, or listening score.
368
+
369
+ Data: [`combined-aggregate.json`](evaluation/replay-comparison/combined-aggregate.json), [`provenance.json`](evaluation/replay-comparison/provenance.json), and [`raw per-record metrics`](evaluation/replay-comparison/raw-metrics-simpletuner-v3.json).
370
+
371
  ## Limitations
372
 
373
  - 5.12-second context.
 
378
  - Exact-token accuracy understates perceptual equivalence.
379
  - Semantic CE can dominate early because its vocabulary is larger.
380
  - Acoustic heads are independent despite residual-codebook dependence.
381
+ - Diffusion render and audio-domain evaluation remain pending.
382
  - No stable packaged loading API.
383
  - Use is subject to MiniMax Music 3, dataset, and MERT terms.
384
 
evaluation/replay-comparison/combined-aggregate.json ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dataset": {
3
+ "exact_alignment_records": 130,
4
+ "repo_id": "bghira/minimax-music3-rvq-reverse-distillation",
5
+ "revision": "5029b1e7f1bbfbf028b76b38564fecccda94a111",
6
+ "split": "holdout"
7
+ },
8
+ "format": "simpletuner-minimaxmusic-replay-comparison-v1",
9
+ "metric": "per-record mean stitched condition-embedding cosine",
10
+ "models": [
11
+ {
12
+ "checkpoint": {
13
+ "checkpoint_bytes": 163955786,
14
+ "checkpoint_sha256": "fd7bfefb197cb7b65a6b6d9b1373c1772a15979f1a4b67d820764d610f0fb9a5",
15
+ "checkpoint_url": "https://www.serveurperso.com/ia/ssd/workspace/git/minimaxmusic.cpp/training/checkpoints/v1/best.pt",
16
+ "epoch": 17,
17
+ "reported_validation_loss": 5.292583839459852,
18
+ "source_commit": "d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1",
19
+ "source_url": "https://github.com/ServeurpersoCom/minimaxmusic.cpp/commit/d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1"
20
+ },
21
+ "model_id": "serveurperso-v1",
22
+ "parameter_count": 40978944,
23
+ "predicted_code_condition_cosine": {
24
+ "count": 130,
25
+ "max": 0.7290002703666687,
26
+ "mean": 0.6633286081827604,
27
+ "min": 0.6026018261909485,
28
+ "quantiles": {
29
+ "0.0": 0.6026018261909485,
30
+ "0.05": 0.6280524551868438,
31
+ "0.25": 0.6483299732208252,
32
+ "0.5": 0.6645751297473907,
33
+ "0.75": 0.6784153282642365,
34
+ "0.95": 0.6963281780481339,
35
+ "1.0": 0.7290002703666687
36
+ },
37
+ "std": 0.02217457298933777
38
+ },
39
+ "true_code_control": {
40
+ "count": 130,
41
+ "max": 0.999957263469696,
42
+ "mean": 0.9999072317893688,
43
+ "min": 0.9983662366867065,
44
+ "quantiles": {
45
+ "0.0": 0.9983662366867065,
46
+ "0.05": 0.999821075797081,
47
+ "0.25": 0.9999240636825562,
48
+ "0.5": 0.9999381899833679,
49
+ "0.75": 0.9999445825815201,
50
+ "0.95": 0.9999500513076782,
51
+ "1.0": 0.999957263469696
52
+ },
53
+ "std": 0.00015022638681815362
54
+ }
55
+ },
56
+ {
57
+ "checkpoint": {
58
+ "checkpoint": "full/output/final",
59
+ "checkpoint_bytes": 163931632,
60
+ "checkpoint_sha256": "1cea88dddf7a48583c13ac661a548b7a3535b933a76356bb158420d24da32f22",
61
+ "global_step": 17640
62
+ },
63
+ "model_id": "simpletuner-v1",
64
+ "parameter_count": 40978944,
65
+ "predicted_code_condition_cosine": {
66
+ "count": 130,
67
+ "max": 0.8286042809486389,
68
+ "mean": 0.7624421353523548,
69
+ "min": 0.7123062014579773,
70
+ "quantiles": {
71
+ "0.0": 0.7123062014579773,
72
+ "0.05": 0.7345191180706024,
73
+ "0.25": 0.747437372803688,
74
+ "0.5": 0.7631425857543945,
75
+ "0.75": 0.7765287756919861,
76
+ "0.95": 0.7904498308897019,
77
+ "1.0": 0.8286042809486389
78
+ },
79
+ "std": 0.019549602827450108
80
+ },
81
+ "true_code_control": {
82
+ "count": 130,
83
+ "max": 0.999957263469696,
84
+ "mean": 0.9999072317893688,
85
+ "min": 0.9983662366867065,
86
+ "quantiles": {
87
+ "0.0": 0.9983662366867065,
88
+ "0.05": 0.999821075797081,
89
+ "0.25": 0.9999240636825562,
90
+ "0.5": 0.9999381899833679,
91
+ "0.75": 0.9999445825815201,
92
+ "0.95": 0.9999500513076782,
93
+ "1.0": 0.999957263469696
94
+ },
95
+ "std": 0.00015022638681815362
96
+ }
97
+ },
98
+ {
99
+ "checkpoint": {
100
+ "checkpoint": "v2/output/final",
101
+ "checkpoint_bytes": 618960456,
102
+ "checkpoint_sha256": "47dfffb7a76d95589752df0c59ff6eaf3496f6fcf5408d324218887e471023c7",
103
+ "global_step": 17660
104
+ },
105
+ "model_id": "simpletuner-v2",
106
+ "parameter_count": 154736064,
107
+ "predicted_code_condition_cosine": {
108
+ "count": 130,
109
+ "max": 0.8361330032348633,
110
+ "mean": 0.7698414775041433,
111
+ "min": 0.7206224203109741,
112
+ "quantiles": {
113
+ "0.0": 0.7206224203109741,
114
+ "0.05": 0.7429912149906158,
115
+ "0.25": 0.7548101097345352,
116
+ "0.5": 0.770626574754715,
117
+ "0.75": 0.7835157215595245,
118
+ "0.95": 0.7986360996961593,
119
+ "1.0": 0.8361330032348633
120
+ },
121
+ "std": 0.019062989675575924
122
+ },
123
+ "true_code_control": {
124
+ "count": 130,
125
+ "max": 0.999957263469696,
126
+ "mean": 0.9999072317893688,
127
+ "min": 0.9983662366867065,
128
+ "quantiles": {
129
+ "0.0": 0.9983662366867065,
130
+ "0.05": 0.999821075797081,
131
+ "0.25": 0.9999240636825562,
132
+ "0.5": 0.9999381899833679,
133
+ "0.75": 0.9999445825815201,
134
+ "0.95": 0.9999500513076782,
135
+ "1.0": 0.999957263469696
136
+ },
137
+ "std": 0.00015022638681815362
138
+ }
139
+ },
140
+ {
141
+ "checkpoint": {
142
+ "checkpoint": "v3/output/final",
143
+ "checkpoint_bytes": 618960456,
144
+ "checkpoint_sha256": "356e97fea65c486a28814dc778b52645807ab8f784c6021d2403f688a2bfe788",
145
+ "global_step": 17660
146
+ },
147
+ "model_id": "simpletuner-v3",
148
+ "parameter_count": 154736064,
149
+ "predicted_code_condition_cosine": {
150
+ "count": 130,
151
+ "max": 0.8356426954269409,
152
+ "mean": 0.7702590204202212,
153
+ "min": 0.7236603498458862,
154
+ "quantiles": {
155
+ "0.0": 0.7236603498458862,
156
+ "0.05": 0.7415853887796402,
157
+ "0.25": 0.7547859847545624,
158
+ "0.5": 0.7704743146896362,
159
+ "0.75": 0.7837727516889572,
160
+ "0.95": 0.8004916191101075,
161
+ "1.0": 0.8356426954269409
162
+ },
163
+ "std": 0.019273954474224628
164
+ },
165
+ "true_code_control": {
166
+ "count": 130,
167
+ "max": 0.999957263469696,
168
+ "mean": 0.9999072317893688,
169
+ "min": 0.9983662366867065,
170
+ "quantiles": {
171
+ "0.0": 0.9983662366867065,
172
+ "0.05": 0.999821075797081,
173
+ "0.25": 0.9999240636825562,
174
+ "0.5": 0.9999381899833679,
175
+ "0.75": 0.9999445825815201,
176
+ "0.95": 0.9999500513076782,
177
+ "1.0": 0.999957263469696
178
+ },
179
+ "std": 0.00015022638681815362
180
+ }
181
+ }
182
+ ]
183
+ }
evaluation/replay-comparison/condition-embedding-replay-comparison.png ADDED
evaluation/replay-comparison/evaluate_replay.py ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import hashlib
6
+ import importlib.util
7
+ import json
8
+ import math
9
+ import os
10
+ import statistics
11
+ import tempfile
12
+ import time
13
+ import zipfile
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ import torch
18
+ import torch.nn.functional as F
19
+ from safetensors import safe_open
20
+ from transformers import Qwen2Tokenizer, Qwen3ForCausalLM
21
+
22
+ from scripts.train_minimax_music_rvq_encoder import (
23
+ EvaluationCheckpoint,
24
+ _cache_paths,
25
+ _read_zip_safetensors,
26
+ _resolve_shard_path,
27
+ build_parser,
28
+ build_pool_matrix,
29
+ frame_latent_starts,
30
+ load_evaluation_model,
31
+ load_records,
32
+ load_trainer_state,
33
+ )
34
+ from simpletuner.helpers.models.minimaxmusic.condition_encoder import MiniMaxMusic3ConditionEncoder
35
+ from simpletuner.helpers.models.minimaxmusic.encoders import _clean_caption, _normalize_lyrics
36
+ from simpletuner.helpers.models.minimaxmusic.rvq_depth_decoder import MiniMaxMusic3RVQDepthDecoder
37
+
38
+
39
+ CHECKPOINT_URL = (
40
+ "https://www.serveurperso.com/ia/ssd/workspace/git/minimaxmusic.cpp/"
41
+ "training/checkpoints/v1/best.pt"
42
+ )
43
+ CHECKPOINT_SHA256 = "fd7bfefb197cb7b65a6b6d9b1373c1772a15979f1a4b67d820764d610f0fb9a5"
44
+ SOURCE_COMMIT = "d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1"
45
+ SOURCE_URL = f"https://github.com/ServeurpersoCom/minimaxmusic.cpp/commit/{SOURCE_COMMIT}"
46
+ OFFICIAL_MODEL_REVISION = "fbdf52fbaaca799592917417eb05f1899f1255ec"
47
+ DATASET_REVISION = "5029b1e7f1bbfbf028b76b38564fecccda94a111"
48
+ WINDOW_FRAMES = 128
49
+ SEMANTIC_OFFSET = 151675
50
+ LM_BLOCK_FRAMES = 256
51
+ MODEL_IDS = ("serveurperso-v1", "simpletuner-v1", "simpletuner-v2", "simpletuner-v3")
52
+
53
+
54
+ def sha256_file(path: Path) -> str:
55
+ digest = hashlib.sha256()
56
+ with path.open("rb") as handle:
57
+ for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
58
+ digest.update(chunk)
59
+ return digest.hexdigest()
60
+
61
+
62
+ def atomic_json(path: Path, value: Any) -> None:
63
+ path.parent.mkdir(parents=True, exist_ok=True)
64
+ encoded = (json.dumps(value, indent=2, sort_keys=True) + "\n").encode("utf-8")
65
+ fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
66
+ try:
67
+ with os.fdopen(fd, "wb") as handle:
68
+ handle.write(encoded)
69
+ handle.flush()
70
+ os.fsync(handle.fileno())
71
+ os.replace(temporary, path)
72
+ directory_fd = os.open(path.parent, os.O_RDONLY)
73
+ try:
74
+ os.fsync(directory_fd)
75
+ finally:
76
+ os.close(directory_fd)
77
+ finally:
78
+ if os.path.exists(temporary):
79
+ os.unlink(temporary)
80
+
81
+
82
+ def append_jsonl(path: Path, value: dict[str, Any]) -> None:
83
+ path.parent.mkdir(parents=True, exist_ok=True)
84
+ payload = (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
85
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
86
+ try:
87
+ written = os.write(fd, payload)
88
+ if written != len(payload):
89
+ raise OSError(f"Short JSONL append: {written} of {len(payload)} bytes.")
90
+ os.fsync(fd)
91
+ finally:
92
+ os.close(fd)
93
+
94
+
95
+ def load_jsonl(path: Path) -> list[dict[str, Any]]:
96
+ if not path.exists():
97
+ return []
98
+ rows: list[dict[str, Any]] = []
99
+ valid_bytes = 0
100
+ with path.open("rb") as handle:
101
+ lines = handle.readlines()
102
+ for index, line in enumerate(lines):
103
+ try:
104
+ rows.append(json.loads(line))
105
+ valid_bytes += len(line)
106
+ except json.JSONDecodeError:
107
+ if index != len(lines) - 1:
108
+ raise
109
+ with path.open("r+b") as handle:
110
+ handle.truncate(valid_bytes)
111
+ handle.flush()
112
+ os.fsync(handle.fileno())
113
+ ids = [row["sample_id"] for row in rows]
114
+ if len(ids) != len(set(ids)):
115
+ raise ValueError(f"Duplicate sample IDs in {path}.")
116
+ return rows
117
+
118
+
119
+ def load_external_encoder(checkpoint: Path, source: Path, device: torch.device):
120
+ if sha256_file(checkpoint) != CHECKPOINT_SHA256:
121
+ raise ValueError("Serveurperso checkpoint SHA-256 mismatch.")
122
+ spec = importlib.util.spec_from_file_location("serveurperso_rvq_v1", source)
123
+ if spec is None or spec.loader is None:
124
+ raise ImportError(f"Cannot load pinned source {source}.")
125
+ module = importlib.util.module_from_spec(spec)
126
+ spec.loader.exec_module(module)
127
+ model = module.RVQEncoder().to(device).eval()
128
+ state = torch.load(checkpoint, map_location="cpu", weights_only=True)
129
+ model.load_state_dict(state["model"])
130
+ provenance = {
131
+ "checkpoint_url": CHECKPOINT_URL,
132
+ "checkpoint_sha256": CHECKPOINT_SHA256,
133
+ "checkpoint_bytes": checkpoint.stat().st_size,
134
+ "source_commit": SOURCE_COMMIT,
135
+ "source_url": SOURCE_URL,
136
+ "epoch": state["epoch"],
137
+ "reported_validation_loss": state["val_loss"],
138
+ }
139
+ return model, provenance
140
+
141
+
142
+ def load_simpletuner_encoder(checkpoint: Path, device: torch.device):
143
+ state = load_trainer_state(str(checkpoint))
144
+ exported = checkpoint / "rvq_encoder.safetensors"
145
+ evaluation_checkpoint = EvaluationCheckpoint(checkpoint.name, checkpoint, int(state["global_step"]))
146
+ model = load_evaluation_model(evaluation_checkpoint).to(device).eval()
147
+ provenance = {
148
+ "checkpoint": f"{checkpoint.parent.parent.name}/output/final",
149
+ "checkpoint_sha256": sha256_file(exported),
150
+ "checkpoint_bytes": exported.stat().st_size,
151
+ "global_step": int(state["global_step"]),
152
+ }
153
+ return model, provenance
154
+
155
+
156
+ def normalize_logits(output: Any) -> list[torch.Tensor]:
157
+ if isinstance(output, tuple) and len(output) == 2 and isinstance(output[1], (list, tuple)):
158
+ return [output[0], *output[1]]
159
+ if isinstance(output, (list, tuple)) and len(output) == 8:
160
+ return list(output)
161
+ raise TypeError(f"Unsupported encoder output type: {type(output)!r}.")
162
+
163
+
164
+ def predict_track(model, record, cache_dir: Path, device: torch.device):
165
+ tensor_path, _ = _cache_paths(cache_dir, record)
166
+ with safe_open(str(tensor_path), framework="pt", device="cpu") as tensors:
167
+ latents = tensors.get_tensor("latents").float()
168
+ codes = tensors.get_tensor("codes").long()
169
+ frame_count = min(record.emitted_frames, codes.shape[0] - 1)
170
+ starts, _ = frame_latent_starts(frame_count, record.alignment)
171
+ while frame_count > 0 and starts[frame_count] > latents.shape[0]:
172
+ frame_count -= 1
173
+ predictions = torch.empty((frame_count, 8), dtype=torch.long)
174
+ assigned = torch.zeros(frame_count, dtype=torch.bool)
175
+ regular_starts = list(range(0, frame_count - WINDOW_FRAMES + 1, WINDOW_FRAMES))
176
+ inference_starts = list(regular_starts)
177
+ tail_start = frame_count - WINDOW_FRAMES
178
+ if tail_start >= 0 and (not inference_starts or inference_starts[-1] != tail_start):
179
+ inference_starts.append(tail_start)
180
+ head_stats = [dict(ce_sum=0.0, top1_correct=0, top5_correct=0, count=0) for _ in range(8)]
181
+ with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16):
182
+ for frame_start in inference_starts:
183
+ bounds = starts[frame_start : frame_start + WINDOW_FRAMES + 1]
184
+ latent_start, latent_end = bounds[0], bounds[-1]
185
+ window_latents = latents[latent_start:latent_end].to(device)
186
+ pool = build_pool_matrix(bounds).to(device)
187
+ logits = normalize_logits(model(window_latents.unsqueeze(0), pool.unsqueeze(0)))
188
+ predicted = torch.stack([head.argmax(-1)[0] for head in logits], dim=-1).cpu()
189
+ take = ~assigned[frame_start : frame_start + WINDOW_FRAMES]
190
+ predictions[frame_start : frame_start + WINDOW_FRAMES][take] = predicted[take]
191
+ assigned[frame_start : frame_start + WINDOW_FRAMES][take] = True
192
+ if frame_start in regular_starts:
193
+ target = codes[frame_start + 1 : frame_start + 1 + WINDOW_FRAMES].to(device)
194
+ for head_index, head_logits in enumerate(logits):
195
+ values = head_logits[0].float()
196
+ labels = target[:, head_index]
197
+ stats = head_stats[head_index]
198
+ stats["ce_sum"] += F.cross_entropy(values, labels, reduction="sum").item()
199
+ stats["top1_correct"] += (values.argmax(-1) == labels).sum().item()
200
+ stats["top5_correct"] += (
201
+ (values.topk(5, dim=-1).indices == labels[:, None]).any(-1).sum().item()
202
+ )
203
+ stats["count"] += labels.numel()
204
+ if not assigned.all():
205
+ raise RuntimeError(f"Predictions do not cover every frame for {record.sample_id}.")
206
+ return codes[: frame_count + 1], predictions, head_stats
207
+
208
+
209
+ def prompt_ids(tokenizer, metadata: dict[str, Any], device: torch.device) -> torch.Tensor:
210
+ caption = _clean_caption(metadata["prompt"])
211
+ lyrics = _normalize_lyrics(metadata["lyrics"])
212
+ text = (
213
+ f"<|im_start|><|caption_start|>{caption}<|caption_end|>"
214
+ f"<|lyrics_start|>{lyrics}<|lyrics_end|><|im_end|><|audio_start|>"
215
+ )
216
+ return tokenizer(text, return_tensors="pt")["input_ids"].to(device)
217
+
218
+
219
+ def replay_frame_hiddens(tokenizer, language_model, depth_decoder, metadata, codes, device):
220
+ codes = codes.to(device)
221
+ ids = prompt_ids(tokenizer, metadata, device)
222
+ with torch.inference_mode():
223
+ semantic = language_model.model.embed_tokens(codes[:-1, :1] + SEMANTIC_OFFSET)
224
+ offsets = torch.arange(7, device=device).view(1, 7) * 1024
225
+ acoustic = depth_decoder.audio_embeddings(codes[:-1, 1:] + offsets).sum(1, keepdim=True)
226
+ feedback = ((semantic + acoustic.to(semantic.dtype)) * (8**-0.5)).squeeze(1).unsqueeze(0)
227
+ output = language_model.model(inputs_embeds=language_model.model.embed_tokens(ids), use_cache=True)
228
+ past = output.past_key_values
229
+ hidden_parts = []
230
+ for start in range(0, feedback.shape[1], LM_BLOCK_FRAMES):
231
+ output = language_model.model(
232
+ inputs_embeds=feedback[:, start : start + LM_BLOCK_FRAMES],
233
+ past_key_values=past,
234
+ use_cache=True,
235
+ )
236
+ past = output.past_key_values
237
+ hidden_parts.append(output.last_hidden_state)
238
+ hidden = torch.cat(hidden_parts, dim=1).squeeze(0)
239
+ emitted = codes[1:]
240
+ sequence = [
241
+ depth_decoder.projection(hidden).unsqueeze(1),
242
+ depth_decoder.projection(
243
+ language_model.model.embed_tokens(emitted[:, :1] + SEMANTIC_OFFSET).squeeze(1)
244
+ ).unsqueeze(1),
245
+ ]
246
+ depth_parts = []
247
+ for codebook in range(7):
248
+ depth_hidden = depth_decoder(torch.cat(sequence, dim=1))[:, -1]
249
+ depth_parts.append(depth_hidden)
250
+ if codebook < 6:
251
+ embedding = depth_decoder.audio_embeddings(emitted[:, codebook + 1] + codebook * 1024)
252
+ sequence.append(depth_decoder.projection(embedding).unsqueeze(1))
253
+ return torch.cat((hidden, torch.cat(depth_parts, dim=-1)), dim=-1).unsqueeze(0)
254
+
255
+
256
+ def stitched_condition(condition_encoder, frame_hiddens, chunks):
257
+ outputs = []
258
+ previous = None
259
+ with torch.inference_mode():
260
+ for chunk in chunks:
261
+ start = int(chunk["semantic_frame_start"])
262
+ end = int(chunk["semantic_frame_end_exclusive"])
263
+ condition = condition_encoder(frame_hiddens[:, start:end])
264
+ if previous is not None:
265
+ overlap = min(previous.shape[1], condition.shape[1])
266
+ condition[:, :overlap] = previous[:, :overlap]
267
+ overlap_start = max(0, condition.shape[1] - 344)
268
+ overlap_end = max(overlap_start, condition.shape[1] - 172)
269
+ previous = condition[:, overlap_start:overlap_end].clone()
270
+ keep_start = int(chunk["kept_flow_latent_start"])
271
+ keep_end = int(chunk["kept_flow_latent_end_exclusive"])
272
+ outputs.append(condition[:, keep_start:keep_end])
273
+ return torch.cat(outputs, dim=1).squeeze(0).float()
274
+
275
+
276
+ def condition_score(predicted: torch.Tensor, reference: torch.Tensor) -> dict[str, float | int]:
277
+ cosine = F.cosine_similarity(predicted, reference, dim=-1)
278
+ squared_error = torch.sum((predicted - reference) ** 2).item()
279
+ reference_squared = torch.sum(reference * reference).item()
280
+ return {
281
+ "cosine_mean": cosine.mean().item(),
282
+ "cosine_std": cosine.std(correction=0).item(),
283
+ "cosine_min": cosine.min().item(),
284
+ "cosine_max": cosine.max().item(),
285
+ "flattened_dot": torch.sum(predicted * reference).item(),
286
+ "predicted_squared": torch.sum(predicted * predicted).item(),
287
+ "reference_squared": reference_squared,
288
+ "squared_error": squared_error,
289
+ "mse": squared_error / predicted.numel(),
290
+ "relative_l2": math.sqrt(squared_error / reference_squared),
291
+ "latent_frames": predicted.shape[0],
292
+ "values": predicted.numel(),
293
+ }
294
+
295
+
296
+ def distribution(values: list[float]) -> dict[str, Any]:
297
+ tensor = torch.tensor(values, dtype=torch.float64)
298
+ quantiles = {str(q): torch.quantile(tensor, q).item() for q in (0.0, 0.05, 0.25, 0.5, 0.75, 0.95, 1.0)}
299
+ return {
300
+ "count": len(values),
301
+ "mean": statistics.fmean(values),
302
+ "std": statistics.pstdev(values),
303
+ "min": min(values),
304
+ "max": max(values),
305
+ "quantiles": quantiles,
306
+ }
307
+
308
+
309
+ def aggregate_condition(rows: list[dict[str, Any]], key: str) -> dict[str, Any]:
310
+ scores = [row["condition_embedding_replay"][key] for row in rows]
311
+ frame_count = sum(score["latent_frames"] for score in scores)
312
+ dot = sum(score["flattened_dot"] for score in scores)
313
+ predicted_squared = sum(score["predicted_squared"] for score in scores)
314
+ reference_squared = sum(score["reference_squared"] for score in scores)
315
+ squared_error = sum(score["squared_error"] for score in scores)
316
+ values = sum(score["values"] for score in scores)
317
+ return {
318
+ "per_record_cosine": distribution([score["cosine_mean"] for score in scores]),
319
+ "latent_weighted_mean_cosine": sum(
320
+ score["cosine_mean"] * score["latent_frames"] for score in scores
321
+ )
322
+ / frame_count,
323
+ "global_flattened_cosine": dot / math.sqrt(predicted_squared * reference_squared),
324
+ "mse": squared_error / values,
325
+ "relative_l2": math.sqrt(squared_error / reference_squared),
326
+ "latent_frames": frame_count,
327
+ }
328
+
329
+
330
+ def aggregate_tokens(rows: list[dict[str, Any]]) -> dict[str, Any]:
331
+ per_head = []
332
+ for head in range(8):
333
+ stats = [row["token_metrics"]["per_head"][head] for row in rows]
334
+ count = sum(item["count"] for item in stats)
335
+ per_head.append(
336
+ {
337
+ "head": head,
338
+ "vocab_size": 16384 if head == 0 else 1024,
339
+ "frames": count,
340
+ "cross_entropy": sum(item["ce_sum"] for item in stats) / count,
341
+ "top1": sum(item["top1_correct"] for item in stats) / count,
342
+ "top5": sum(item["top5_correct"] for item in stats) / count,
343
+ }
344
+ )
345
+ return {
346
+ "loss": statistics.fmean(item["cross_entropy"] for item in per_head),
347
+ "semantic_top1": per_head[0]["top1"],
348
+ "semantic_top5": per_head[0]["top5"],
349
+ "acoustic_top1": statistics.fmean(item["top1"] for item in per_head[1:]),
350
+ "acoustic_top5": statistics.fmean(item["top5"] for item in per_head[1:]),
351
+ "per_head": per_head,
352
+ }
353
+
354
+
355
+ def run(args: argparse.Namespace) -> None:
356
+ device = torch.device("cuda")
357
+ if torch.cuda.device_count() != 1:
358
+ raise RuntimeError("Expose exactly one assigned GPU with CUDA_VISIBLE_DEVICES.")
359
+ output_dir = Path(args.output_dir)
360
+ jsonl_path = output_dir / f"records-{args.model_id}.jsonl"
361
+ rows = load_jsonl(jsonl_path)
362
+ completed = {row["sample_id"] for row in rows}
363
+ if args.encoder_kind == "serveurperso":
364
+ encoder, checkpoint_provenance = load_external_encoder(
365
+ Path(args.checkpoint), Path(args.external_source), device
366
+ )
367
+ else:
368
+ encoder, checkpoint_provenance = load_simpletuner_encoder(Path(args.checkpoint), device)
369
+ encoder.requires_grad_(False)
370
+ tokenizer = Qwen2Tokenizer.from_pretrained(Path(args.official_model_root) / "tokenizer")
371
+ language_model = Qwen3ForCausalLM.from_pretrained(
372
+ Path(args.official_model_root) / "language_model",
373
+ torch_dtype=torch.bfloat16,
374
+ attn_implementation="sdpa",
375
+ ).to(device).eval()
376
+ depth_decoder = MiniMaxMusic3RVQDepthDecoder.from_pretrained(
377
+ Path(args.official_model_root) / "rvq_depth_decoder", torch_dtype=torch.bfloat16
378
+ ).to(device).eval()
379
+ condition_encoder = MiniMaxMusic3ConditionEncoder.from_pretrained(
380
+ Path(args.official_model_root) / "condition_encoder", torch_dtype=torch.bfloat16
381
+ ).to(device).eval()
382
+ for model in (language_model, depth_decoder, condition_encoder):
383
+ model.requires_grad_(False)
384
+
385
+ parser = build_parser()
386
+ dataset_args = parser.parse_args([])
387
+ dataset_args.dataset_repo_id = args.dataset_repo_id
388
+ dataset_args.dataset_revision = DATASET_REVISION
389
+ dataset_args.hf_cache_dir = args.hf_cache_dir
390
+ dataset_args.dataset_cache_dir = None
391
+ dataset_args.validation_fraction = 0.1
392
+ dataset_args.seed = 42
393
+ _, validation = load_records(dataset_args)
394
+ records = [record for record in validation if record.alignment.get("chunk_stitching")]
395
+ if len(records) != 130:
396
+ raise RuntimeError(f"Expected 130 exact holdout records, found {len(records)}.")
397
+
398
+ started = time.time()
399
+ for index, record in enumerate(records, start=1):
400
+ if record.sample_id in completed:
401
+ continue
402
+ true_codes, predicted, token_stats = predict_track(encoder, record, Path(args.latent_cache_dir), device)
403
+ replay_codes = torch.cat((true_codes[:1], predicted), dim=0)
404
+ shard_path = _resolve_shard_path(dataset_args, record)
405
+ with zipfile.ZipFile(shard_path) as archive:
406
+ metadata_name = next(name for name in archive.namelist() if name.endswith("metadata.json"))
407
+ metadata = json.loads(archive.read(metadata_name))
408
+ reference = _read_zip_safetensors(
409
+ archive, record.tensor_file, required_names=("condition_embeddings",)
410
+ )["condition_embeddings"].float().to(device)
411
+ chunks = metadata["alignment"]["chunk_stitching"]
412
+ true_condition = stitched_condition(
413
+ condition_encoder,
414
+ replay_frame_hiddens(tokenizer, language_model, depth_decoder, metadata, true_codes, device),
415
+ chunks,
416
+ )
417
+ predicted_condition = stitched_condition(
418
+ condition_encoder,
419
+ replay_frame_hiddens(tokenizer, language_model, depth_decoder, metadata, replay_codes, device),
420
+ chunks,
421
+ )
422
+ if true_condition.shape != reference.shape or predicted_condition.shape != reference.shape:
423
+ raise RuntimeError(f"Condition shape mismatch for {record.sample_id}.")
424
+ row = {
425
+ "model_id": args.model_id,
426
+ "sample_id": record.sample_id,
427
+ "shard_id": record.shard_id,
428
+ "emitted_frames": int(predicted.shape[0]),
429
+ "condition_latent_frames": int(reference.shape[0]),
430
+ "token_metrics": {"per_head": token_stats},
431
+ "condition_embedding_replay": {
432
+ "true_code_control": condition_score(true_condition, reference),
433
+ "predicted_codes": condition_score(predicted_condition, reference),
434
+ },
435
+ }
436
+ append_jsonl(jsonl_path, row)
437
+ rows.append(row)
438
+ completed.add(record.sample_id)
439
+ progress = {
440
+ "model_id": args.model_id,
441
+ "completed": len(completed),
442
+ "total": len(records),
443
+ "last_sample_id": record.sample_id,
444
+ }
445
+ atomic_json(output_dir / f"progress-{args.model_id}.json", progress)
446
+ predicted_cosine = row["condition_embedding_replay"]["predicted_codes"]["cosine_mean"]
447
+ true_cosine = row["condition_embedding_replay"]["true_code_control"]["cosine_mean"]
448
+ print(
449
+ f"[{args.model_id} {index}/{len(records)}] true={true_cosine:.6f} "
450
+ f"predicted={predicted_cosine:.6f} elapsed={time.time() - started:.1f}s",
451
+ flush=True,
452
+ )
453
+ del true_condition, predicted_condition, reference
454
+ torch.cuda.empty_cache()
455
+
456
+ rows = load_jsonl(jsonl_path)
457
+ if len(rows) != 130:
458
+ raise RuntimeError(f"Evaluation incomplete: {len(rows)} of 130 records.")
459
+ result = {
460
+ "format": "simpletuner-minimaxmusic-external-replay-v1",
461
+ "model_id": args.model_id,
462
+ "parameter_count": sum(parameter.numel() for parameter in encoder.parameters()),
463
+ "checkpoint": checkpoint_provenance,
464
+ "dataset": {
465
+ "repo_id": args.dataset_repo_id,
466
+ "revision": DATASET_REVISION,
467
+ "split": "holdout",
468
+ "exact_alignment_records": len(rows),
469
+ },
470
+ "protocol": {
471
+ "window_frames": WINDOW_FRAMES,
472
+ "code_selection": "argmax",
473
+ "replay": "teacher-forced official language model and RVQ depth decoder",
474
+ "condition": "official condition encoder with recorded chunk stitching",
475
+ "metric": "cosine similarity per stitched condition-embedding latent frame",
476
+ "official_model_revision": OFFICIAL_MODEL_REVISION,
477
+ },
478
+ "condition_embedding_replay": {
479
+ "true_code_control": aggregate_condition(rows, "true_code_control"),
480
+ "predicted_codes": aggregate_condition(rows, "predicted_codes"),
481
+ },
482
+ "token_metrics": aggregate_tokens(rows),
483
+ "records": rows,
484
+ "limitations": [
485
+ "This is teacher-forced hidden-state replay, not a diffusion render or audio-domain score.",
486
+ "Only the 130 holdout records with recorded exact chunk alignment are included.",
487
+ "Complete non-overlapping windows define token metrics; a shifted final window fills replay-only tail frames.",
488
+ "The Serveurperso checkpoint was trained on a separate corpus and has no checkpoint-specific license declaration.",
489
+ ],
490
+ }
491
+ atomic_json(output_dir / f"raw-metrics-{args.model_id}.json", result)
492
+ atomic_json(output_dir / f"progress-{args.model_id}.json", {"model_id": args.model_id, "completed": 130, "total": 130, "status": "complete"})
493
+
494
+
495
+ def combine(args: argparse.Namespace) -> None:
496
+ output_dir = Path(args.output_dir)
497
+ results = [json.loads((output_dir / f"raw-metrics-{model_id}.json").read_text()) for model_id in MODEL_IDS]
498
+ models = []
499
+ for result in results:
500
+ predicted = result["condition_embedding_replay"]["predicted_codes"]["per_record_cosine"]
501
+ models.append(
502
+ {
503
+ "model_id": result["model_id"],
504
+ "parameter_count": result["parameter_count"],
505
+ "checkpoint": result["checkpoint"],
506
+ "predicted_code_condition_cosine": predicted,
507
+ "true_code_control": result["condition_embedding_replay"]["true_code_control"]["per_record_cosine"],
508
+ }
509
+ )
510
+ combined = {
511
+ "format": "simpletuner-minimaxmusic-replay-comparison-v1",
512
+ "metric": "per-record mean stitched condition-embedding cosine",
513
+ "dataset": results[0]["dataset"],
514
+ "models": models,
515
+ }
516
+ atomic_json(output_dir / "combined-aggregate.json", combined)
517
+ atomic_json(output_dir / "comparison-chart-data.json", combined)
518
+ provenance = {
519
+ "serveurperso": {
520
+ "checkpoint_url": CHECKPOINT_URL,
521
+ "checkpoint_sha256": CHECKPOINT_SHA256,
522
+ "source_commit": SOURCE_COMMIT,
523
+ "source_url": SOURCE_URL,
524
+ "code_license": "MIT",
525
+ "checkpoint_license": "not separately declared",
526
+ },
527
+ "official_replay_components": {
528
+ "repo_id": "MiniMaxAI/MiniMax-Music3",
529
+ "revision": OFFICIAL_MODEL_REVISION,
530
+ "license": "MiniMax-Music3 Community License",
531
+ },
532
+ "dataset": {"repo_id": results[0]["dataset"]["repo_id"], "revision": DATASET_REVISION},
533
+ "checkpoints": {result["model_id"]: result["checkpoint"] for result in results},
534
+ }
535
+ atomic_json(output_dir / "provenance.json", provenance)
536
+
537
+
538
+ def build_cli() -> argparse.ArgumentParser:
539
+ parser = argparse.ArgumentParser()
540
+ parser.add_argument("--combine", action="store_true")
541
+ parser.add_argument("--model-id", choices=MODEL_IDS)
542
+ parser.add_argument("--encoder-kind", choices=("serveurperso", "simpletuner"))
543
+ parser.add_argument("--checkpoint")
544
+ parser.add_argument("--external-source")
545
+ parser.add_argument("--official-model-root")
546
+ parser.add_argument("--latent-cache-dir")
547
+ parser.add_argument("--hf-cache-dir")
548
+ parser.add_argument("--dataset-repo-id", default="bghira/minimax-music3-rvq-reverse-distillation")
549
+ parser.add_argument("--output-dir", default="model_cards/external/serveurperso")
550
+ return parser
551
+
552
+
553
+ if __name__ == "__main__":
554
+ arguments = build_cli().parse_args()
555
+ if arguments.combine:
556
+ combine(arguments)
557
+ else:
558
+ required = (
559
+ "model_id",
560
+ "encoder_kind",
561
+ "checkpoint",
562
+ "official_model_root",
563
+ "latent_cache_dir",
564
+ "hf_cache_dir",
565
+ )
566
+ missing = [name for name in required if getattr(arguments, name) is None]
567
+ if missing:
568
+ raise ValueError(f"Missing required arguments: {missing}.")
569
+ if arguments.encoder_kind == "serveurperso" and arguments.external_source is None:
570
+ raise ValueError("--external-source is required for Serveurperso.")
571
+ run(arguments)
evaluation/replay-comparison/provenance.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "checkpoints": {
3
+ "serveurperso-v1": {
4
+ "checkpoint_bytes": 163955786,
5
+ "checkpoint_sha256": "fd7bfefb197cb7b65a6b6d9b1373c1772a15979f1a4b67d820764d610f0fb9a5",
6
+ "checkpoint_url": "https://www.serveurperso.com/ia/ssd/workspace/git/minimaxmusic.cpp/training/checkpoints/v1/best.pt",
7
+ "epoch": 17,
8
+ "reported_validation_loss": 5.292583839459852,
9
+ "source_commit": "d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1",
10
+ "source_url": "https://github.com/ServeurpersoCom/minimaxmusic.cpp/commit/d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1"
11
+ },
12
+ "simpletuner-v1": {
13
+ "checkpoint": "full/output/final",
14
+ "checkpoint_bytes": 163931632,
15
+ "checkpoint_sha256": "1cea88dddf7a48583c13ac661a548b7a3535b933a76356bb158420d24da32f22",
16
+ "global_step": 17640
17
+ },
18
+ "simpletuner-v2": {
19
+ "checkpoint": "v2/output/final",
20
+ "checkpoint_bytes": 618960456,
21
+ "checkpoint_sha256": "47dfffb7a76d95589752df0c59ff6eaf3496f6fcf5408d324218887e471023c7",
22
+ "global_step": 17660
23
+ },
24
+ "simpletuner-v3": {
25
+ "checkpoint": "v3/output/final",
26
+ "checkpoint_bytes": 618960456,
27
+ "checkpoint_sha256": "356e97fea65c486a28814dc778b52645807ab8f784c6021d2403f688a2bfe788",
28
+ "global_step": 17660
29
+ }
30
+ },
31
+ "dataset": {
32
+ "repo_id": "bghira/minimax-music3-rvq-reverse-distillation",
33
+ "revision": "5029b1e7f1bbfbf028b76b38564fecccda94a111"
34
+ },
35
+ "official_replay_components": {
36
+ "license": "MiniMax-Music3 Community License",
37
+ "repo_id": "MiniMaxAI/MiniMax-Music3",
38
+ "revision": "fbdf52fbaaca799592917417eb05f1899f1255ec"
39
+ },
40
+ "serveurperso": {
41
+ "checkpoint_license": "not separately declared",
42
+ "checkpoint_sha256": "fd7bfefb197cb7b65a6b6d9b1373c1772a15979f1a4b67d820764d610f0fb9a5",
43
+ "checkpoint_url": "https://www.serveurperso.com/ia/ssd/workspace/git/minimaxmusic.cpp/training/checkpoints/v1/best.pt",
44
+ "code_license": "MIT",
45
+ "source_commit": "d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1",
46
+ "source_url": "https://github.com/ServeurpersoCom/minimaxmusic.cpp/commit/d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1"
47
+ }
48
+ }
evaluation/replay-comparison/raw-metrics-serveurperso-v1.json ADDED
The diff for this file is too large to render. See raw diff
 
evaluation/replay-comparison/raw-metrics-simpletuner-v1.json ADDED
The diff for this file is too large to render. See raw diff
 
evaluation/replay-comparison/raw-metrics-simpletuner-v2.json ADDED
The diff for this file is too large to render. See raw diff
 
evaluation/replay-comparison/raw-metrics-simpletuner-v3.json ADDED
The diff for this file is too large to render. See raw diff