Skip to content

Commit 38b352e

Browse files
hans00claude
andcommitted
feat: add OpenMOSS XY-Tokenizer (16 kHz → 24 kHz neural codec)
XY-Tokenizer is a Whisper-style audio codec from OpenMOSS-Team / MOSS-TTSD. End-to-end pipeline: 16 kHz mono PCM → Whisper-style mel-fbank (n_fft=400, hop=160, 80 mels, Slaney scale) → parallel semantic + acoustic OmniAudioEncoder (2× conv stride-2 + 12-layer Whisper transformer with q/v/out biases, k bias-free, sinusoidal PE) → semantic_encoder_adapter (4-layer Whisper transformer) → concat[sem_adapter, acoust] (1536 ch) → pre_rvq_adapter (Linear 1536→768 + 4-layer transformer) → ResidualDownConv (conv-gated SiLU pool, avg=4) → 3072 ch → quantizer.input_proj (1×1 WNConv, weight-norm baked at convert) → 8-level Euclidean-NN Residual VQ (1024×512 codebook with pre-baked `codebook_sq_norm` so `argmin ||z - cb[i]||^2` becomes one matmul + bias) → quantizer.output_proj → post_rvq_adapter (Linear 3072→768 + 4 layers + Linear 768→3072) → UpConv (ConvTranspose1d stride=4) → 12-layer Whisper-style mel decoder + deconv1 (s=2) + deconv2 (s=1) → enhanced Vocos backbone (embed conv 80→512 k=7, initial LN, 30 ConvNeXt blocks, final LN, head Linear 512→962) → CPU iSTFT (n_fft=960, hop=240, periodic Hann) → 24 kHz mono audio Encoder threads `n_valid` through every transformer block to mirror HF's `attention_mask`-based SDPA bias, and uses GELU-erf (matching `F.gelu`) throughout — without those two pieces the encoder body diverges from HF and codes drift. `tests/e2e/xy_tokenizer_decode_smoke.py` verifies all three parts: cpp decode of HF codes ≥ 0.95 corr, cpp encode codes ≥ 95 % match HF, cpp e2e ≥ 0.95 corr vs HF reconstruction. On the smoke clip: - cpp decode(HF codes) vs HF rec: corr 0.97 - cpp encode codes vs HF codes: 100 % match - cpp e2e vs HF e2e: corr 0.97 On a longer (3 s) clip the encoder hits 295/296 = 99.66 % code match and the e2e hits corr 0.975 against the HF reference. Mel-fbank is computed CPU-side (Slaney triangular filterbank, log10 + global max-clamp + (x+4)/4 normalize, reflection-padded center-True STFT to match HF's WhisperFeatureExtractor). Everything else runs as a single ggml graph per public call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7d68473 commit 38b352e

10 files changed

Lines changed: 1953 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ add_library(codec STATIC
6363
src/models/xcodec2.cpp
6464
src/models/snac.cpp
6565
src/models/moss_audio.cpp
66+
src/models/xy_tokenizer.cpp
6667
)
6768

6869
target_include_directories(codec

include/codec.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ enum codec_arch {
2626
CODEC_ARCH_XCODEC2 = 11,
2727
CODEC_ARCH_SNAC = 12,
2828
CODEC_ARCH_MOSS_AUDIO = 13,
29+
CODEC_ARCH_XY_TOKENIZER = 14,
2930
};
3031

3132
enum codec_status {

scripts/convert-to-gguf.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ def detect_model_type_from_config(config_path: Path) -> str:
6464
return "snac"
6565
elif "moss-audio" in model_type or "moss_audio" in model_type or "mossaudio" in model_type:
6666
return "moss_audio"
67+
elif "xy_tokenizer" in model_type or "xy-tokenizer" in model_type:
68+
return "xy_tokenizer"
6769
else:
6870
# Try to infer from architecture or other fields
6971
arch = config.get("architectures", [""])[0].lower() if config.get("architectures") else ""
@@ -93,6 +95,8 @@ def detect_model_type_from_config(config_path: Path) -> str:
9395
return "snac"
9496
elif "moss" in arch and "audio" in arch:
9597
return "moss_audio"
98+
elif "xy_tokenizer" in arch or "xytokenizer" in arch:
99+
return "xy_tokenizer"
96100

97101
raise ValueError(f"Unknown model type: {model_type}. Cannot auto-detect.")
98102

@@ -126,6 +130,8 @@ def infer_model_type_from_filename(filename: str) -> str | None:
126130
return 'snac'
127131
elif 'moss' in name_lower and 'audio' in name_lower:
128132
return 'moss_audio'
133+
elif 'xy_tokenizer' in name_lower or 'xy-tokenizer' in name_lower:
134+
return 'xy_tokenizer'
129135
return None
130136

131137

scripts/converters/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .xcodec2 import XCodec2Converter
1515
from .snac import SnacConverter
1616
from .moss_audio import MossAudioConverter
17+
from .xy_tokenizer import XYTokenizerConverter
1718

1819
# Registry of supported models
1920
_CONVERTER_REGISTRY = {
@@ -31,6 +32,7 @@
3132
'snac': SnacConverter,
3233
'moss_audio': MossAudioConverter,
3334
'moss_audio_nano': MossAudioConverter,
35+
'xy_tokenizer': XYTokenizerConverter,
3436
}
3537

3638

scripts/converters/xy_tokenizer.py

Lines changed: 397 additions & 0 deletions
Large diffs are not rendered by default.

src/codec.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "models/xcodec2.h"
1414
#include "models/snac.h"
1515
#include "models/moss_audio.h"
16+
#include "models/xy_tokenizer.h"
1617
#include "ops/safe_math.h"
1718
#include "runtime/graph.h"
1819
#include "runtime/gguf_kv.h"
@@ -133,6 +134,9 @@ enum codec_arch codec_arch_from_string(const std::string & arch) {
133134
if (arch == "moss_audio_tokenizer" || arch == "moss-audio-tokenizer" || arch == "moss_audio") {
134135
return CODEC_ARCH_MOSS_AUDIO;
135136
}
137+
if (arch == "xy_tokenizer" || arch == "xy-tokenizer") {
138+
return CODEC_ARCH_XY_TOKENIZER;
139+
}
136140

137141
return CODEC_ARCH_UNKNOWN;
138142
}
@@ -165,6 +169,8 @@ static const codec_model_vtable * codec_model_vtable_for_arch(enum codec_arch ar
165169
return codec_snac_vtable();
166170
case CODEC_ARCH_MOSS_AUDIO:
167171
return codec_moss_audio_vtable();
172+
case CODEC_ARCH_XY_TOKENIZER:
173+
return codec_xy_tokenizer_vtable();
168174
case CODEC_ARCH_UNKNOWN:
169175
default:
170176
return nullptr;
@@ -186,6 +192,7 @@ const char * codec_arch_name(enum codec_arch arch) {
186192
case CODEC_ARCH_XCODEC2: return "XCodec2";
187193
case CODEC_ARCH_SNAC: return "SNAC";
188194
case CODEC_ARCH_MOSS_AUDIO: return "MOSS-Audio-Tokenizer";
195+
case CODEC_ARCH_XY_TOKENIZER: return "XY-Tokenizer";
189196
case CODEC_ARCH_UNKNOWN:
190197
default: return "unknown";
191198
}

0 commit comments

Comments
 (0)