Text-to-Speech
English
German
voice-acting
qwen3
moss-audio-tokenizer-v2
audio-generation
ChristophSchuhmann commited on
Commit
d911efa
·
verified ·
1 Parent(s): 90adb6a

Document architecture, prompts, code, and full run statistics

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. README.md +124 -0
  3. assets/qwen3/config.json +30 -0
  4. assets/score_schema.json +1043 -0
  5. assets/sft3/__init__.py +9 -0
  6. assets/sft3/added_tokens.json +31 -0
  7. assets/sft3/config.json +267 -0
  8. assets/sft3/configuration_moss_tts.py +158 -0
  9. assets/sft3/gpt2_decoder.py +721 -0
  10. assets/sft3/modeling_moss_tts.py +623 -0
  11. assets/sft3/processing_moss_tts.py +914 -0
  12. assets/sft3/processor_config.json +7 -0
  13. assets/sft3/qwen3_decoder.py +582 -0
  14. assets/sft3/special_tokens_map.json +21 -0
  15. assets/sft3/tokenizer.json +3 -0
  16. assets/sft3/tokenizer_config.json +253 -0
  17. checkpoints/S1/metadata.json +17 -0
  18. checkpoints/S10/metadata.json +17 -0
  19. checkpoints/S2/metadata.json +17 -0
  20. checkpoints/S3/metadata.json +17 -0
  21. checkpoints/S4/metadata.json +17 -0
  22. checkpoints/S5/metadata.json +17 -0
  23. checkpoints/S6/metadata.json +17 -0
  24. checkpoints/S7/metadata.json +17 -0
  25. checkpoints/S8/metadata.json +17 -0
  26. checkpoints/S9/metadata.json +17 -0
  27. code/anno_timed.py +328 -0
  28. code/caption_curriculum_dataset.py +172 -0
  29. code/cascade_dataset.py +105 -0
  30. code/conditioning.py +153 -0
  31. code/continuation_control.py +51 -0
  32. code/continuous_train.py +428 -0
  33. code/dataset.py +167 -0
  34. code/infer.py +99 -0
  35. code/large_talker.py +60 -0
  36. code/moss_export/__init__.py +9 -0
  37. code/moss_export/configuration_moss_tts.py +158 -0
  38. code/moss_export/gpt2_decoder.py +721 -0
  39. code/moss_export/modeling_moss_tts.py +623 -0
  40. code/moss_export/processing_moss_tts.py +914 -0
  41. code/moss_export/qwen3_decoder.py +582 -0
  42. code/moss_pack.py +95 -0
  43. code/moss_small.py +220 -0
  44. code/pack_moss.py +40 -0
  45. code/packing.py +162 -0
  46. code/plan.py +96 -0
  47. code/plan_caption_production_32nodes.json +137 -0
  48. code/prompt_fmt.py +39 -0
  49. code/state_io.py +86 -0
  50. code/tar_member_map.py +57 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/sft3/tokenizer.json filter=lfs diff=lfs merge=lfs -text
37
+ stats/training_steps.jsonl filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-4.0
3
+ language:
4
+ - en
5
+ - de
6
+ tags:
7
+ - text-to-speech
8
+ - voice-acting
9
+ - qwen3
10
+ - moss-audio-tokenizer-v2
11
+ - audio-generation
12
+ datasets:
13
+ - laion/tts-scaling-ladder-de-en
14
+ - laion/humaneness-voice-small-acting-eval
15
+ model-index:
16
+ - name: Humaneness Voice Small
17
+ results: []
18
+ ---
19
+
20
+ # Humaneness Voice Small
21
+
22
+ Humaneness Voice Small is an experimental English/German text-to-speech and voice-acting model. This repository contains **ten stage-local checkpoints from one continuous S1→S10 training run**, the training and inference source, run statistics, and a guide to the exact prompt surfaces. It is not a standard `AutoModel.from_pretrained()` checkpoint: it uses a Qwen3-0.6B semantic backbone, a MOSS-style local Talker, and the separately loaded [MOSS Audio Tokenizer v2](https://huggingface.co/OpenMOSS-Team/MOSS-Audio-Tokenizer-v2). Read the inference instructions before loading weights.
23
+
24
+ The semantic backbone was **initialized from pretrained Qwen3-0.6B**, not from random weights. The **112,764,928-parameter Talker and bridge were initialized fresh** for this run. This corrects the loose description “trained from scratch” sometimes applied to earlier experiments. The Qwen hidden width is 1,024 and the local Talker width is 2,560. One semantic frame spans 80 ms; the Talker predicts **12 RVQ codebook indices per frame**, or 150 codebook indices per second. It does not predict 32 codebooks per frame. The codec reconstructs 48-kHz audio. The full state is approximately 715 million parameters, including the frozen, unused score conditioner; the model-only exports are BF16 PyTorch state dictionaries.
25
+
26
+ ## Choose a checkpoint
27
+
28
+ The `checkpoints/S*/model_bf16.pt` files are the **lowest independent held-out validation-loss checkpoint within each stage**. The common validation set has 50 held-out prompts in each of two conditioning modes (100 rows/checkpoint), and all 49 complete candidates were swept. The acting benchmark did not select the checkpoints. A lower validation loss does not necessarily mean a more natural voice.
29
+
30
+ | Stage | Ladder tier | Approx. measured presentation-hours¹ | Best global step | Held-out loss | Shared-cell acting reward² |
31
+ |---|---|---:|---:|---:|---:|
32
+ | S1 | x100 | 188,354 | 16,265 | 4.8054 | 0.4292 |
33
+ | S2 | x50 | 116,838 | 26,284 | 4.7232 | 0.4489 |
34
+ | S3 | tier7 | 72,314 | 32,337 | 4.7045 | **0.4628** |
35
+ | S4 | tier6 | 57,419 | 37,182 | **4.6966** | 0.4581 |
36
+ | S5 | tier5 | 28,716 | 38,183 | 4.6987 | 0.4543 |
37
+ | S6 | tier4 | 14,361 | 40,935 | 4.7005 | 0.4539 |
38
+ | S7 | tier3 | 5,750 | 41,466 | 4.7056 | 0.4540 |
39
+ | S8 | tier2 | 2,877 | 41,739 | 4.7109 | 0.4586 |
40
+ | S9 | tier1 | 1,439 | 41,878 | 4.7198 | 0.4540 |
41
+ | S10 | tier0 | 578 | 41,936 | 4.7278 | 0.4572 |
42
+
43
+ ¹ These are **training presentations**, not unique source-audio hours. Multi-format prompts repeat some recordings. The ladder tiers are nested and paired with sidecar material; do not add these hours to estimate unique audio. ² Target-aware automatic reward on 1,651 shared valid acting cells per stage, not a human MOS. The S3–S10 paired difference is +0.00557 with a 95% challenge-bootstrap CI of −0.00701 to +0.01899: **the benchmark does not establish S3 as significantly better than S10**. S3 is a reasonable provisional default; inspect the same prompts in S3/S10 before choosing for a production voice. S4 has the best held-out validation loss. All underlying values and intervals are in `stats/acting_findings.json` and `stats/acting_analysis.json.gz`.
44
+
45
+ The run used 32 JUPITER Booster nodes × 4 GPUs/node, batch 4,096, **one** 2,097-step linear warmup followed by one cosine decay across all 41,936 updates—**no stage LR restarts**. Peak LRs were 8e-5 for the semantic backbone and 2.4e-4 for the Talker, ending at 5% of peak. The source `code/plan_caption_production_32nodes.json` records the exact historical manifests and hashes; its absolute JUPITER paths are provenance, not portable defaults.
46
+
47
+ ## Prompt formats actually trained
48
+
49
+ The outer model template is `<user_inst>` with `Reference(s)`, `Instruction`, `Tokens`, `Quality`, `Sound Event`, `Ambient Sound`, `Language`, and `Text` fields. The examples below show the **Instruction** field. `Text` separately repeats the SCRIPT after `SCRIPT:` for structured prompts, or otherwise follows the inherited packer behavior. `Tokens` is a **codec-frame budget**, not a literal transcript token count. Reference audio, when supplied, is codec-encoded through the dedicated reference channel. Do not paste a reference filename into the instruction.
50
+
51
+ **Structured GENERAL/SCRIPT (C01).** Use when a specific delivery per sentence, explicit durations, pauses or vocal bursts matter:
52
+
53
+ ```text
54
+ GENERAL: frightened, breath-held, intimate, increasingly panicked, adult, low-register
55
+ SCRIPT:
56
+ (quiet alarm, searching hesitation) [3.7 seconds duration] I locked the back door... didn't I?
57
+ (fear rising, clipped self-correction) [4.2 seconds duration] I heard the latch click, but now it is standing open.
58
+ (whispered panic, sharp inhale) [5.1 seconds duration] Wait—please don't move; there is someone breathing in the hallway.
59
+ ```
60
+
61
+ The [prompting guide](https://laion-humaneness-voice-small-acting-eval-space.static.hf.space/index.html) and the historical training code distinguish `GENERAL:` global cues from the `SCRIPT:` line-by-line performance and quoted text of other formats. Sentence durations and pauses are optional training variations, not promises of exact timing. If a vocal burst is requested in the structured SCRIPT, put it at its intended position in the spoken sequence, outside the literal speech text.
62
+
63
+ **Caption + exact transcript (C02–C10).** The description outside quotes is English; the spoken text inside `TRANSCRIPT` may be English or German. The model saw BUD-E/Body Whisper v1.0 and v1.1, Timbre Whisper, Voice Tagging Whisper and one validated Gemma-4-E4B paraphrase per enriched UID. The six possible Gemma presentation modes were *not* all generated for each UID.
64
+
65
+ ```text
66
+ CAPTION: Use a close, low-register voice that begins in contained fear and tightens into whispered panic.
67
+ TRANSCRIPT: "I locked the back door... didn't I? I heard the latch click, but now it is standing open."
68
+ ```
69
+
70
+ For Timbre Whisper and Voice Tagging Whisper, the training loader split comma-separated tags from prose and presented **tags only, prose only, or both**, each one-third of that family's examples. Tag order was shuffled deterministically. The combined form is literal:
71
+
72
+ ```text
73
+ CAPTION: Tags: increasingly_panicked, tense, close-mic, breath-held, low-register. Caption: Use a close, low-register voice that tightens into whispered panic.
74
+ TRANSCRIPT: "I locked the back door... didn't I?"
75
+ ```
76
+
77
+ The model also saw **transcript only with a distinct reference recording** (C11):
78
+
79
+ ```text
80
+ TRANSCRIPT: "I locked the back door... didn't I?"
81
+ ```
82
+
83
+ For ordinary intelligible speech, start with a concise `CAPTION:` and an exact quoted `TRANSCRIPT:`. For voice identity, supply a short, clean, distinct reference audio. The S3 benchmark's Timbre-prose surface (C05) had mean WER 0.0437 across its mixed reference assignments; S3 Gemma-style C10 had the highest *descriptive* caption-style reward (0.505), while S10's Voice-Tagging tags+prose C09 had WER 0.0473 and the highest caption-style emotion-band hit there. These are not causal prompt-format rankings: reference assignments and authored captions differ by condition. The structured C01 format is currently much less reliable for words (S3 mean WER ≈0.348; S10 ≈0.313 across reference modes), but it is the surface that explicitly expresses sentence timing and bursts. It has not shown reliable exact burst-onset control.
84
+
85
+ ## Evaluation: what the scores do and do not say
86
+
87
+ The [public listening Space](https://huggingface.co/spaces/laion/humaneness-voice-small-acting-eval-space) and [direct site](https://laion-humaneness-voice-small-acting-eval-space.static.hf.space/index.html) contain **1,872 takes per checkpoint × ten checkpoints = 18,720 takes**: 50 English/German acting challenges, 11 prompt surfaces, three paired seeds, plus controlled reference and duration variations. Every stage has a [dedicated listening page](https://laion-humaneness-voice-small-acting-eval-space.static.hf.space/S3.html); replace `S3` in that URL with `S1`–`S10`. Separate [extreme S3 prompting](https://laion-humaneness-voice-small-acting-eval-space.static.hf.space/extreme_s3.html) and [S10 reference/CFG exploration](https://laion-humaneness-voice-small-acting-eval-space.static.hf.space/extreme_s10_cfg.html) are exploratory, **not** the frozen 18,720-take benchmark. The audio/score dataset is [here](https://huggingface.co/datasets/laion/humaneness-voice-small-acting-eval).
88
+
89
+ | Stage | Mean WER ↓ | Genuineness ↑ | Burst blend ↑ | Requested-emotion hit ↑ | VoiceNet target hit ↑ | Requested burst type+onset F1 ↑ | Sentence duration MAE, s ↓ |
90
+ |---|---:|---:|---:|---:|---:|---:|---:|
91
+ | S1 | 0.152 | 1.368 | 4.042 | 0.192 | 0.323 | 0.017 | 0.870 |
92
+ | S2 | 0.128 | 1.533 | 3.981 | 0.199 | 0.338 | **0.053** | **0.850** |
93
+ | S3 | 0.121 | **1.636** | 4.470 | 0.193 | 0.329 | 0.011 | 0.884 |
94
+ | S4 | 0.120 | 1.615 | 4.354 | 0.191 | 0.333 | 0.030 | 0.934 |
95
+ | S5 | **0.113** | 1.571 | 4.139 | 0.198 | 0.336 | 0.022 | 0.949 |
96
+ | S6 | 0.122 | 1.609 | 4.577 | 0.201 | 0.326 | 0.031 | 0.924 |
97
+ | S7 | 0.127 | 1.588 | **4.606** | 0.195 | 0.323 | 0.035 | 1.022 |
98
+ | S8 | 0.115 | 1.591 | 4.593 | 0.192 | 0.326 | 0.015 | 1.007 |
99
+ | S9 | 0.124 | 1.565 | 4.561 | 0.196 | 0.324 | 0.022 | 1.024 |
100
+ | S10 | 0.121 | 1.561 | 4.556 | **0.207** | 0.320 | 0.030 | 0.966 |
101
+
102
+ WER comes from Parakeet v3. Genuineness, burst blend, emotion and VoiceNet are learned proxy scores, **not** human listener ratings. Burst realization and duration error are restricted to eligible prompts; duration MAE excludes failed alignments, so consult alignment coverage before comparing it. A per-stage optimum in one column is not an all-round win. The balanced caption-condition comparison suggests reference audio often reduces *descriptive* WER, but the paired C01 reference/no-reference WER differences for S3 and S10 have confidence intervals crossing zero. No universal causal reference benefit is claimed. The benchmark's C01 prompts did carry parenthesized sentence directions, but lacked explicit pause tags, requested 0.7-second bursts only at sentence onset, and used durations for only half the challenges. These are limitations, not successful fine-grained-control results.
103
+
104
+ ## Files and inference
105
+
106
+ - `checkpoints/S1` through `S10`: selected BF16 model-only state plus `metadata.json` with SHA-256 and original checkpoint contract. **Optimizer/RNG state is not uploaded.** Full 8.5-GB-per-checkpoint resume states remain in the original JUPITER scratch run; the public weights alone do not resume the optimizer exactly.
107
+ - `code/continuous_train.py`, `code/train.sbatch`, `code/plan_caption_production_32nodes.json` and adjacent modules: the historical JUPITER source and training contract. The raw plan refers to local manifests and intentionally fails its hash checks if edited; regenerate plans and manifests for new data. `code/infer.py` is the standalone example for the exported weights. The code bundles MOSS export modules, tokenizer configuration and score schema; the audio codec is fetched separately from its upstream Hugging Face repository.
108
+ - `stats/training_steps.jsonl`: every logged training-step scalar line across all four Slurm invocations. `stats/invocation_summaries.json`, `stats/run_configuration.json`, `stats/stage_winners.json`, `stats/validation_loss_full.tar.gz`, `stats/acting_findings.json`, and `stats/acting_analysis.json.gz` hold the remaining scalar summaries, all held-out checkpoint-sweep records, and the public acting aggregate. `stats/CONTENTS.json` gives hashes.
109
+
110
+ Example (requires a CUDA environment with PyTorch, Transformers, NumPy, SoundFile, Torchaudio, Safetensors, and permission to download the separate codec):
111
+
112
+ ```bash
113
+ python code/infer.py --stage S3 --language en --frames 75 \
114
+ --prompt $'CAPTION: Warm, softly amused, conversational.\nTRANSCRIPT: "I cannot believe we made it."' \
115
+ --text 'I cannot believe we made it.' --output example.wav
116
+ ```
117
+
118
+ Add `--reference-wav reference.wav` for a distinct, clean recording under about 3 seconds. Do not use the target recording as its own reference. The example is a source-level starting point; the public listening Space is the already-audited playback path. This repository does **not** contain the large original SFT-3 model weights or original Qwen checkpoint separately, because the exported Humaneness Voice Small states include every trained model weight required for inference.
119
+
120
+ ## Training data, license and responsible use
121
+
122
+ The core nested scaling ladder and associated annotation sidecars are documented at [laion/tts-scaling-ladder-de-en](https://huggingface.co/datasets/laion/tts-scaling-ladder-de-en); inspect each linked dataset's license/provenance before a derivative training run. The DPO corpora are a **separate future-adaptation resource**, not evidence that these base checkpoints were DPO-trained: [real-speech DPO](https://huggingface.co/datasets/laion/tts-realspeech-dpo-en-de), [voice-profile DPO](https://huggingface.co/datasets/laion/laion-voice-profiles-dpo), [CFG DPO](https://huggingface.co/datasets/laion/laion-voice-profiles-dpo-cfg), and [S5 pitch DPO](https://huggingface.co/datasets/laion/humaneness-voice-s5-pitch-dpo-v1). The pitch set's chosen/rejected quality gap is substantial; do not treat it as a clean voice-identity-only preference corpus.
123
+
124
+ Model weights and original release materials are provided under **CC BY 4.0**; attribute LAION / Humaneness Voice Small and preserve attribution to the source projects. Qwen3 and MOSS Audio Tokenizer v2 are separate upstream dependencies under their own stated licenses. Respect speakers' rights, consent, provenance and applicable law. Synthetic voice acting can be mistaken for real speech; disclose generated audio and avoid impersonation or deceptive use. This is a research release with known shakiness, prompt sensitivity and imperfect word/timing/burst control, not a safety-certified production voice.
assets/qwen3/config.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3ForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 151643,
8
+ "eos_token_id": 151645,
9
+ "head_dim": 128,
10
+ "hidden_act": "silu",
11
+ "hidden_size": 1024,
12
+ "initializer_range": 0.02,
13
+ "intermediate_size": 3072,
14
+ "max_position_embeddings": 40960,
15
+ "max_window_layers": 28,
16
+ "model_type": "qwen3",
17
+ "num_attention_heads": 16,
18
+ "num_hidden_layers": 28,
19
+ "num_key_value_heads": 8,
20
+ "rms_norm_eps": 1e-06,
21
+ "rope_scaling": null,
22
+ "rope_theta": 1000000,
23
+ "sliding_window": null,
24
+ "tie_word_embeddings": true,
25
+ "torch_dtype": "bfloat16",
26
+ "transformers_version": "4.51.0",
27
+ "use_cache": true,
28
+ "use_sliding_window": false,
29
+ "vocab_size": 151936
30
+ }
assets/score_schema.json ADDED
@@ -0,0 +1,1043 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": 1,
3
+ "partition": "existing vn_time19 plus the other 38 VoiceNet axes",
4
+ "global_dimensions": [
5
+ {
6
+ "name": "vn:AGEV",
7
+ "label": "Voice Age",
8
+ "normalization_bounds": [
9
+ 0,
10
+ 6
11
+ ],
12
+ "source_global_column": "vn_AGEV_reg",
13
+ "head": "VoiceNet regression"
14
+ },
15
+ {
16
+ "name": "vn:GEND",
17
+ "label": "Perceived Gender",
18
+ "normalization_bounds": [
19
+ 0,
20
+ 6
21
+ ],
22
+ "source_global_column": "vn_GEND_reg",
23
+ "head": "VoiceNet regression"
24
+ },
25
+ {
26
+ "name": "vn:REGS",
27
+ "label": "Register",
28
+ "normalization_bounds": [
29
+ 0,
30
+ 6
31
+ ],
32
+ "source_global_column": "vn_REGS_reg",
33
+ "head": "VoiceNet regression"
34
+ },
35
+ {
36
+ "name": "vn:WARM",
37
+ "label": "Warmth",
38
+ "normalization_bounds": [
39
+ 0,
40
+ 6
41
+ ],
42
+ "source_global_column": "vn_WARM_reg",
43
+ "head": "VoiceNet regression"
44
+ },
45
+ {
46
+ "name": "vn:BRGT",
47
+ "label": "Brightness",
48
+ "normalization_bounds": [
49
+ 0,
50
+ 6
51
+ ],
52
+ "source_global_column": "vn_BRGT_reg",
53
+ "head": "VoiceNet regression"
54
+ },
55
+ {
56
+ "name": "vn:FULL",
57
+ "label": "Fullness",
58
+ "normalization_bounds": [
59
+ 0,
60
+ 6
61
+ ],
62
+ "source_global_column": "vn_FULL_reg",
63
+ "head": "VoiceNet regression"
64
+ },
65
+ {
66
+ "name": "vn:ROUG",
67
+ "label": "Roughness",
68
+ "normalization_bounds": [
69
+ 0,
70
+ 6
71
+ ],
72
+ "source_global_column": "vn_ROUG_reg",
73
+ "head": "VoiceNet regression"
74
+ },
75
+ {
76
+ "name": "vn:METL",
77
+ "label": "Metallic Character",
78
+ "normalization_bounds": [
79
+ 0,
80
+ 6
81
+ ],
82
+ "source_global_column": "vn_METL_reg",
83
+ "head": "VoiceNet regression"
84
+ },
85
+ {
86
+ "name": "vn:HARM",
87
+ "label": "Harmonicity",
88
+ "normalization_bounds": [
89
+ 0,
90
+ 6
91
+ ],
92
+ "source_global_column": "vn_HARM_reg",
93
+ "head": "VoiceNet regression"
94
+ },
95
+ {
96
+ "name": "vn:R_CHST",
97
+ "label": "Chest Resonance",
98
+ "normalization_bounds": [
99
+ 0,
100
+ 6
101
+ ],
102
+ "source_global_column": "vn_R_CHST_reg",
103
+ "head": "VoiceNet regression"
104
+ },
105
+ {
106
+ "name": "vn:R_HEAD",
107
+ "label": "Head Resonance",
108
+ "normalization_bounds": [
109
+ 0,
110
+ 6
111
+ ],
112
+ "source_global_column": "vn_R_HEAD_reg",
113
+ "head": "VoiceNet regression"
114
+ },
115
+ {
116
+ "name": "vn:R_MASK",
117
+ "label": "Mask Resonance",
118
+ "normalization_bounds": [
119
+ 0,
120
+ 6
121
+ ],
122
+ "source_global_column": "vn_R_MASK_reg",
123
+ "head": "VoiceNet regression"
124
+ },
125
+ {
126
+ "name": "vn:R_MIXD",
127
+ "label": "Mixed Resonance",
128
+ "normalization_bounds": [
129
+ 0,
130
+ 6
131
+ ],
132
+ "source_global_column": "vn_R_MIXD_reg",
133
+ "head": "VoiceNet regression"
134
+ },
135
+ {
136
+ "name": "vn:R_NASL",
137
+ "label": "Nasal Resonance",
138
+ "normalization_bounds": [
139
+ 0,
140
+ 6
141
+ ],
142
+ "source_global_column": "vn_R_NASL_reg",
143
+ "head": "VoiceNet regression"
144
+ },
145
+ {
146
+ "name": "vn:R_ORAL",
147
+ "label": "Oral Resonance",
148
+ "normalization_bounds": [
149
+ 0,
150
+ 6
151
+ ],
152
+ "source_global_column": "vn_R_ORAL_reg",
153
+ "head": "VoiceNet regression"
154
+ },
155
+ {
156
+ "name": "vn:R_THRT",
157
+ "label": "Throat Resonance",
158
+ "normalization_bounds": [
159
+ 0,
160
+ 6
161
+ ],
162
+ "source_global_column": "vn_R_THRT_reg",
163
+ "head": "VoiceNet regression"
164
+ },
165
+ {
166
+ "name": "vn:RCQL",
167
+ "label": "Recording Quality",
168
+ "normalization_bounds": [
169
+ 0,
170
+ 6
171
+ ],
172
+ "source_global_column": "vn_RCQL_reg",
173
+ "head": "VoiceNet regression"
174
+ },
175
+ {
176
+ "name": "vn:BKGN",
177
+ "label": "Background Noise",
178
+ "normalization_bounds": [
179
+ 0,
180
+ 6
181
+ ],
182
+ "source_global_column": "vn_BKGN_reg",
183
+ "head": "VoiceNet regression"
184
+ },
185
+ {
186
+ "name": "vn:ESTH",
187
+ "label": "Esthetics",
188
+ "normalization_bounds": [
189
+ 0,
190
+ 6
191
+ ],
192
+ "source_global_column": "vn_ESTH_reg",
193
+ "head": "VoiceNet regression"
194
+ },
195
+ {
196
+ "name": "genu",
197
+ "label": "Genuineness",
198
+ "normalization_bounds": [
199
+ 0,
200
+ 6
201
+ ],
202
+ "source_global_column": "genuineness_0_6",
203
+ "head": "Genuineness regression"
204
+ },
205
+ {
206
+ "name": "blend",
207
+ "label": "Vocal-burst blend",
208
+ "normalization_bounds": [
209
+ 0,
210
+ 10
211
+ ],
212
+ "source_global_column": "blend_0_10",
213
+ "head": "Blend regression"
214
+ }
215
+ ],
216
+ "local_dimensions": [
217
+ {
218
+ "name": "vn:AROU",
219
+ "label": "Arousal",
220
+ "normalization_bounds": [
221
+ 0,
222
+ 6
223
+ ],
224
+ "source_global_column": "vn_AROU_reg",
225
+ "head": "VoiceNet regression"
226
+ },
227
+ {
228
+ "name": "vn:ARSH",
229
+ "label": "Arousal Shift",
230
+ "normalization_bounds": [
231
+ 0,
232
+ 6
233
+ ],
234
+ "source_global_column": "vn_ARSH_reg",
235
+ "head": "VoiceNet regression"
236
+ },
237
+ {
238
+ "name": "vn:ATCK",
239
+ "label": "Attack",
240
+ "normalization_bounds": [
241
+ 0,
242
+ 6
243
+ ],
244
+ "source_global_column": "vn_ATCK_reg",
245
+ "head": "VoiceNet regression"
246
+ },
247
+ {
248
+ "name": "vn:CHNK",
249
+ "label": "Chunking",
250
+ "normalization_bounds": [
251
+ 0,
252
+ 6
253
+ ],
254
+ "source_global_column": "vn_CHNK_reg",
255
+ "head": "VoiceNet regression"
256
+ },
257
+ {
258
+ "name": "vn:CLRT",
259
+ "label": "Articulation Clarity",
260
+ "normalization_bounds": [
261
+ 0,
262
+ 6
263
+ ],
264
+ "source_global_column": "vn_CLRT_reg",
265
+ "head": "VoiceNet regression"
266
+ },
267
+ {
268
+ "name": "vn:COGL",
269
+ "label": "Cognitive Load",
270
+ "normalization_bounds": [
271
+ 0,
272
+ 6
273
+ ],
274
+ "source_global_column": "vn_COGL_reg",
275
+ "head": "VoiceNet regression"
276
+ },
277
+ {
278
+ "name": "vn:DARC",
279
+ "label": "Dynamic Arc",
280
+ "normalization_bounds": [
281
+ 0,
282
+ 6
283
+ ],
284
+ "source_global_column": "vn_DARC_reg",
285
+ "head": "VoiceNet regression"
286
+ },
287
+ {
288
+ "name": "vn:DFLU",
289
+ "label": "Disfluency",
290
+ "normalization_bounds": [
291
+ 0,
292
+ 6
293
+ ],
294
+ "source_global_column": "vn_DFLU_reg",
295
+ "head": "VoiceNet regression"
296
+ },
297
+ {
298
+ "name": "vn:EMPH",
299
+ "label": "Emphasis",
300
+ "normalization_bounds": [
301
+ 0,
302
+ 6
303
+ ],
304
+ "source_global_column": "vn_EMPH_reg",
305
+ "head": "VoiceNet regression"
306
+ },
307
+ {
308
+ "name": "vn:EXPL",
309
+ "label": "Content Appropriateness (3-point Scale)",
310
+ "normalization_bounds": [
311
+ 0,
312
+ 6
313
+ ],
314
+ "source_global_column": "vn_EXPL_reg",
315
+ "head": "VoiceNet regression"
316
+ },
317
+ {
318
+ "name": "vn:FOCS",
319
+ "label": "Focus",
320
+ "normalization_bounds": [
321
+ 0,
322
+ 6
323
+ ],
324
+ "source_global_column": "vn_FOCS_reg",
325
+ "head": "VoiceNet regression"
326
+ },
327
+ {
328
+ "name": "vn:RANG",
329
+ "label": "Pitch Range",
330
+ "normalization_bounds": [
331
+ 0,
332
+ 6
333
+ ],
334
+ "source_global_column": "vn_RANG_reg",
335
+ "head": "VoiceNet regression"
336
+ },
337
+ {
338
+ "name": "vn:RESP",
339
+ "label": "Respiration",
340
+ "normalization_bounds": [
341
+ 0,
342
+ 6
343
+ ],
344
+ "source_global_column": "vn_RESP_reg",
345
+ "head": "VoiceNet regression"
346
+ },
347
+ {
348
+ "name": "vn:SMTH",
349
+ "label": "Smoothness",
350
+ "normalization_bounds": [
351
+ 0,
352
+ 6
353
+ ],
354
+ "source_global_column": "vn_SMTH_reg",
355
+ "head": "VoiceNet regression"
356
+ },
357
+ {
358
+ "name": "vn:STNC",
359
+ "label": "Stance",
360
+ "normalization_bounds": [
361
+ 0,
362
+ 6
363
+ ],
364
+ "source_global_column": "vn_STNC_reg",
365
+ "head": "VoiceNet regression"
366
+ },
367
+ {
368
+ "name": "vn:STRU",
369
+ "label": "Structure",
370
+ "normalization_bounds": [
371
+ 0,
372
+ 6
373
+ ],
374
+ "source_global_column": "vn_STRU_reg",
375
+ "head": "VoiceNet regression"
376
+ },
377
+ {
378
+ "name": "vn:S_ASMR",
379
+ "label": "ASMR Style",
380
+ "normalization_bounds": [
381
+ 0,
382
+ 6
383
+ ],
384
+ "source_global_column": "vn_S_ASMR_reg",
385
+ "head": "VoiceNet regression"
386
+ },
387
+ {
388
+ "name": "vn:S_AUTH",
389
+ "label": "Authoritative Style",
390
+ "normalization_bounds": [
391
+ 0,
392
+ 6
393
+ ],
394
+ "source_global_column": "vn_S_AUTH_reg",
395
+ "head": "VoiceNet regression"
396
+ },
397
+ {
398
+ "name": "vn:S_CART",
399
+ "label": "Cartoonish Style",
400
+ "normalization_bounds": [
401
+ 0,
402
+ 6
403
+ ],
404
+ "source_global_column": "vn_S_CART_reg",
405
+ "head": "VoiceNet regression"
406
+ },
407
+ {
408
+ "name": "vn:S_CASU",
409
+ "label": "Casual Style",
410
+ "normalization_bounds": [
411
+ 0,
412
+ 6
413
+ ],
414
+ "source_global_column": "vn_S_CASU_reg",
415
+ "head": "VoiceNet regression"
416
+ },
417
+ {
418
+ "name": "vn:S_CONV",
419
+ "label": "Conversational Style",
420
+ "normalization_bounds": [
421
+ 0,
422
+ 6
423
+ ],
424
+ "source_global_column": "vn_S_CONV_reg",
425
+ "head": "VoiceNet regression"
426
+ },
427
+ {
428
+ "name": "vn:S_DRAM",
429
+ "label": "Dramatic Style",
430
+ "normalization_bounds": [
431
+ 0,
432
+ 6
433
+ ],
434
+ "source_global_column": "vn_S_DRAM_reg",
435
+ "head": "VoiceNet regression"
436
+ },
437
+ {
438
+ "name": "vn:S_FORM",
439
+ "label": "Formal Style",
440
+ "normalization_bounds": [
441
+ 0,
442
+ 6
443
+ ],
444
+ "source_global_column": "vn_S_FORM_reg",
445
+ "head": "VoiceNet regression"
446
+ },
447
+ {
448
+ "name": "vn:S_MONO",
449
+ "label": "Monologue Style",
450
+ "normalization_bounds": [
451
+ 0,
452
+ 6
453
+ ],
454
+ "source_global_column": "vn_S_MONO_reg",
455
+ "head": "VoiceNet regression"
456
+ },
457
+ {
458
+ "name": "vn:S_NARR",
459
+ "label": "Narrator Style",
460
+ "normalization_bounds": [
461
+ 0,
462
+ 6
463
+ ],
464
+ "source_global_column": "vn_S_NARR_reg",
465
+ "head": "VoiceNet regression"
466
+ },
467
+ {
468
+ "name": "vn:S_NEWS",
469
+ "label": "Newsreader Style",
470
+ "normalization_bounds": [
471
+ 0,
472
+ 6
473
+ ],
474
+ "source_global_column": "vn_S_NEWS_reg",
475
+ "head": "VoiceNet regression"
476
+ },
477
+ {
478
+ "name": "vn:S_PLAY",
479
+ "label": "Playful Style",
480
+ "normalization_bounds": [
481
+ 0,
482
+ 6
483
+ ],
484
+ "source_global_column": "vn_S_PLAY_reg",
485
+ "head": "VoiceNet regression"
486
+ },
487
+ {
488
+ "name": "vn:S_RANT",
489
+ "label": "Ranting/Angry Style",
490
+ "normalization_bounds": [
491
+ 0,
492
+ 6
493
+ ],
494
+ "source_global_column": "vn_S_RANT_reg",
495
+ "head": "VoiceNet regression"
496
+ },
497
+ {
498
+ "name": "vn:S_STRY",
499
+ "label": "Storytelling Style",
500
+ "normalization_bounds": [
501
+ 0,
502
+ 6
503
+ ],
504
+ "source_global_column": "vn_S_STRY_reg",
505
+ "head": "VoiceNet regression"
506
+ },
507
+ {
508
+ "name": "vn:S_TECH",
509
+ "label": "Teacher/Didactic Style",
510
+ "normalization_bounds": [
511
+ 0,
512
+ 6
513
+ ],
514
+ "source_global_column": "vn_S_TECH_reg",
515
+ "head": "VoiceNet regression"
516
+ },
517
+ {
518
+ "name": "vn:S_WHIS",
519
+ "label": "Whisper-Talk Style",
520
+ "normalization_bounds": [
521
+ 0,
522
+ 6
523
+ ],
524
+ "source_global_column": "vn_S_WHIS_reg",
525
+ "head": "VoiceNet regression"
526
+ },
527
+ {
528
+ "name": "vn:TEMP",
529
+ "label": "Tempo",
530
+ "normalization_bounds": [
531
+ 0,
532
+ 6
533
+ ],
534
+ "source_global_column": "vn_TEMP_reg",
535
+ "head": "VoiceNet regression"
536
+ },
537
+ {
538
+ "name": "vn:TENS",
539
+ "label": "Tension",
540
+ "normalization_bounds": [
541
+ 0,
542
+ 6
543
+ ],
544
+ "source_global_column": "vn_TENS_reg",
545
+ "head": "VoiceNet regression"
546
+ },
547
+ {
548
+ "name": "vn:VALN",
549
+ "label": "Valence",
550
+ "normalization_bounds": [
551
+ 0,
552
+ 6
553
+ ],
554
+ "source_global_column": "vn_VALN_reg",
555
+ "head": "VoiceNet regression"
556
+ },
557
+ {
558
+ "name": "vn:VALS",
559
+ "label": "Valence Shift",
560
+ "normalization_bounds": [
561
+ 0,
562
+ 6
563
+ ],
564
+ "source_global_column": "vn_VALS_reg",
565
+ "head": "VoiceNet regression"
566
+ },
567
+ {
568
+ "name": "vn:VFLX",
569
+ "label": "Velocity Flux",
570
+ "normalization_bounds": [
571
+ 0,
572
+ 6
573
+ ],
574
+ "source_global_column": "vn_VFLX_reg",
575
+ "head": "VoiceNet regression"
576
+ },
577
+ {
578
+ "name": "vn:VOLT",
579
+ "label": "Volatility",
580
+ "normalization_bounds": [
581
+ 0,
582
+ 6
583
+ ],
584
+ "source_global_column": "vn_VOLT_reg",
585
+ "head": "VoiceNet regression"
586
+ },
587
+ {
588
+ "name": "vn:VULN",
589
+ "label": "Vulnerability",
590
+ "normalization_bounds": [
591
+ 0,
592
+ 6
593
+ ],
594
+ "source_global_column": "vn_VULN_reg",
595
+ "head": "VoiceNet regression"
596
+ },
597
+ {
598
+ "name": "emo:Affection",
599
+ "label": "Affection",
600
+ "normalization_bounds": [
601
+ 0,
602
+ 6
603
+ ],
604
+ "head": "EmoNet raw regression",
605
+ "source_global_column": "emo_Affection"
606
+ },
607
+ {
608
+ "name": "emo:Amusement",
609
+ "label": "Amusement",
610
+ "normalization_bounds": [
611
+ 0,
612
+ 6
613
+ ],
614
+ "head": "EmoNet raw regression",
615
+ "source_global_column": "emo_Amusement"
616
+ },
617
+ {
618
+ "name": "emo:Anger",
619
+ "label": "Anger",
620
+ "normalization_bounds": [
621
+ 0,
622
+ 6
623
+ ],
624
+ "head": "EmoNet raw regression",
625
+ "source_global_column": "emo_Anger"
626
+ },
627
+ {
628
+ "name": "emo:Astonishment_Surprise",
629
+ "label": "Astonishment_Surprise",
630
+ "normalization_bounds": [
631
+ 0,
632
+ 6
633
+ ],
634
+ "head": "EmoNet raw regression",
635
+ "source_global_column": "emo_Astonishment_Surprise"
636
+ },
637
+ {
638
+ "name": "emo:Awe",
639
+ "label": "Awe",
640
+ "normalization_bounds": [
641
+ 0,
642
+ 6
643
+ ],
644
+ "head": "EmoNet raw regression",
645
+ "source_global_column": "emo_Awe"
646
+ },
647
+ {
648
+ "name": "emo:Bitterness",
649
+ "label": "Bitterness",
650
+ "normalization_bounds": [
651
+ 0,
652
+ 6
653
+ ],
654
+ "head": "EmoNet raw regression",
655
+ "source_global_column": "emo_Bitterness"
656
+ },
657
+ {
658
+ "name": "emo:Concentration",
659
+ "label": "Concentration",
660
+ "normalization_bounds": [
661
+ 0,
662
+ 6
663
+ ],
664
+ "head": "EmoNet raw regression",
665
+ "source_global_column": "emo_Concentration"
666
+ },
667
+ {
668
+ "name": "emo:Confusion",
669
+ "label": "Confusion",
670
+ "normalization_bounds": [
671
+ 0,
672
+ 6
673
+ ],
674
+ "head": "EmoNet raw regression",
675
+ "source_global_column": "emo_Confusion"
676
+ },
677
+ {
678
+ "name": "emo:Contemplation",
679
+ "label": "Contemplation",
680
+ "normalization_bounds": [
681
+ 0,
682
+ 6
683
+ ],
684
+ "head": "EmoNet raw regression",
685
+ "source_global_column": "emo_Contemplation"
686
+ },
687
+ {
688
+ "name": "emo:Contempt",
689
+ "label": "Contempt",
690
+ "normalization_bounds": [
691
+ 0,
692
+ 6
693
+ ],
694
+ "head": "EmoNet raw regression",
695
+ "source_global_column": "emo_Contempt"
696
+ },
697
+ {
698
+ "name": "emo:Contentment",
699
+ "label": "Contentment",
700
+ "normalization_bounds": [
701
+ 0,
702
+ 6
703
+ ],
704
+ "head": "EmoNet raw regression",
705
+ "source_global_column": "emo_Contentment"
706
+ },
707
+ {
708
+ "name": "emo:Disappointment",
709
+ "label": "Disappointment",
710
+ "normalization_bounds": [
711
+ 0,
712
+ 6
713
+ ],
714
+ "head": "EmoNet raw regression",
715
+ "source_global_column": "emo_Disappointment"
716
+ },
717
+ {
718
+ "name": "emo:Disgust",
719
+ "label": "Disgust",
720
+ "normalization_bounds": [
721
+ 0,
722
+ 6
723
+ ],
724
+ "head": "EmoNet raw regression",
725
+ "source_global_column": "emo_Disgust"
726
+ },
727
+ {
728
+ "name": "emo:Distress",
729
+ "label": "Distress",
730
+ "normalization_bounds": [
731
+ 0,
732
+ 6
733
+ ],
734
+ "head": "EmoNet raw regression",
735
+ "source_global_column": "emo_Distress"
736
+ },
737
+ {
738
+ "name": "emo:Doubt",
739
+ "label": "Doubt",
740
+ "normalization_bounds": [
741
+ 0,
742
+ 6
743
+ ],
744
+ "head": "EmoNet raw regression",
745
+ "source_global_column": "emo_Doubt"
746
+ },
747
+ {
748
+ "name": "emo:Elation",
749
+ "label": "Elation",
750
+ "normalization_bounds": [
751
+ 0,
752
+ 6
753
+ ],
754
+ "head": "EmoNet raw regression",
755
+ "source_global_column": "emo_Elation"
756
+ },
757
+ {
758
+ "name": "emo:Embarrassment",
759
+ "label": "Embarrassment",
760
+ "normalization_bounds": [
761
+ 0,
762
+ 6
763
+ ],
764
+ "head": "EmoNet raw regression",
765
+ "source_global_column": "emo_Embarrassment"
766
+ },
767
+ {
768
+ "name": "emo:Emotional_Numbness",
769
+ "label": "Emotional_Numbness",
770
+ "normalization_bounds": [
771
+ 0,
772
+ 6
773
+ ],
774
+ "head": "EmoNet raw regression",
775
+ "source_global_column": "emo_Emotional_Numbness"
776
+ },
777
+ {
778
+ "name": "emo:Fatigue_Exhaustion",
779
+ "label": "Fatigue_Exhaustion",
780
+ "normalization_bounds": [
781
+ 0,
782
+ 6
783
+ ],
784
+ "head": "EmoNet raw regression",
785
+ "source_global_column": "emo_Fatigue_Exhaustion"
786
+ },
787
+ {
788
+ "name": "emo:Fear",
789
+ "label": "Fear",
790
+ "normalization_bounds": [
791
+ 0,
792
+ 6
793
+ ],
794
+ "head": "EmoNet raw regression",
795
+ "source_global_column": "emo_Fear"
796
+ },
797
+ {
798
+ "name": "emo:Helplessness",
799
+ "label": "Helplessness",
800
+ "normalization_bounds": [
801
+ 0,
802
+ 6
803
+ ],
804
+ "head": "EmoNet raw regression",
805
+ "source_global_column": "emo_Helplessness"
806
+ },
807
+ {
808
+ "name": "emo:Hope_Enthusiasm_Optimism",
809
+ "label": "Hope_Enthusiasm_Optimism",
810
+ "normalization_bounds": [
811
+ 0,
812
+ 6
813
+ ],
814
+ "head": "EmoNet raw regression",
815
+ "source_global_column": "emo_Hope_Enthusiasm_Optimism"
816
+ },
817
+ {
818
+ "name": "emo:Impatience_and_Irritability",
819
+ "label": "Impatience_and_Irritability",
820
+ "normalization_bounds": [
821
+ 0,
822
+ 6
823
+ ],
824
+ "head": "EmoNet raw regression",
825
+ "source_global_column": "emo_Impatience_and_Irritability"
826
+ },
827
+ {
828
+ "name": "emo:Infatuation",
829
+ "label": "Infatuation",
830
+ "normalization_bounds": [
831
+ 0,
832
+ 6
833
+ ],
834
+ "head": "EmoNet raw regression",
835
+ "source_global_column": "emo_Infatuation"
836
+ },
837
+ {
838
+ "name": "emo:Interest",
839
+ "label": "Interest",
840
+ "normalization_bounds": [
841
+ 0,
842
+ 6
843
+ ],
844
+ "head": "EmoNet raw regression",
845
+ "source_global_column": "emo_Interest"
846
+ },
847
+ {
848
+ "name": "emo:Intoxication_Altered_States_of_Consciousness",
849
+ "label": "Intoxication_Altered_States_of_Consciousness",
850
+ "normalization_bounds": [
851
+ 0,
852
+ 6
853
+ ],
854
+ "head": "EmoNet raw regression",
855
+ "source_global_column": "emo_Intoxication_Altered_States_of_Consciousness"
856
+ },
857
+ {
858
+ "name": "emo:Jealousy_and_Envy",
859
+ "label": "Jealousy_&_Envy",
860
+ "normalization_bounds": [
861
+ 0,
862
+ 6
863
+ ],
864
+ "head": "EmoNet raw regression",
865
+ "source_global_column": "emo_Jealousy_and_Envy"
866
+ },
867
+ {
868
+ "name": "emo:Longing",
869
+ "label": "Longing",
870
+ "normalization_bounds": [
871
+ 0,
872
+ 6
873
+ ],
874
+ "head": "EmoNet raw regression",
875
+ "source_global_column": "emo_Longing"
876
+ },
877
+ {
878
+ "name": "emo:Malevolence_Malice",
879
+ "label": "Malevolence_Malice",
880
+ "normalization_bounds": [
881
+ 0,
882
+ 6
883
+ ],
884
+ "head": "EmoNet raw regression",
885
+ "source_global_column": "emo_Malevolence_Malice"
886
+ },
887
+ {
888
+ "name": "emo:Pain",
889
+ "label": "Pain",
890
+ "normalization_bounds": [
891
+ 0,
892
+ 6
893
+ ],
894
+ "head": "EmoNet raw regression",
895
+ "source_global_column": "emo_Pain"
896
+ },
897
+ {
898
+ "name": "emo:Pleasure_Ecstasy",
899
+ "label": "Pleasure_Ecstasy",
900
+ "normalization_bounds": [
901
+ 0,
902
+ 6
903
+ ],
904
+ "head": "EmoNet raw regression",
905
+ "source_global_column": "emo_Pleasure_Ecstasy"
906
+ },
907
+ {
908
+ "name": "emo:Pride",
909
+ "label": "Pride",
910
+ "normalization_bounds": [
911
+ 0,
912
+ 6
913
+ ],
914
+ "head": "EmoNet raw regression",
915
+ "source_global_column": "emo_Pride"
916
+ },
917
+ {
918
+ "name": "emo:Relief",
919
+ "label": "Relief",
920
+ "normalization_bounds": [
921
+ 0,
922
+ 6
923
+ ],
924
+ "head": "EmoNet raw regression",
925
+ "source_global_column": "emo_Relief"
926
+ },
927
+ {
928
+ "name": "emo:Sadness",
929
+ "label": "Sadness",
930
+ "normalization_bounds": [
931
+ 0,
932
+ 6
933
+ ],
934
+ "head": "EmoNet raw regression",
935
+ "source_global_column": "emo_Sadness"
936
+ },
937
+ {
938
+ "name": "emo:Sexual_Lust",
939
+ "label": "Sexual_Lust",
940
+ "normalization_bounds": [
941
+ 0,
942
+ 6
943
+ ],
944
+ "head": "EmoNet raw regression",
945
+ "source_global_column": "emo_Sexual_Lust"
946
+ },
947
+ {
948
+ "name": "emo:Shame",
949
+ "label": "Shame",
950
+ "normalization_bounds": [
951
+ 0,
952
+ 6
953
+ ],
954
+ "head": "EmoNet raw regression",
955
+ "source_global_column": "emo_Shame"
956
+ },
957
+ {
958
+ "name": "emo:Sourness",
959
+ "label": "Sourness",
960
+ "normalization_bounds": [
961
+ 0,
962
+ 6
963
+ ],
964
+ "head": "EmoNet raw regression",
965
+ "source_global_column": "emo_Sourness"
966
+ },
967
+ {
968
+ "name": "emo:Teasing",
969
+ "label": "Teasing",
970
+ "normalization_bounds": [
971
+ 0,
972
+ 6
973
+ ],
974
+ "head": "EmoNet raw regression",
975
+ "source_global_column": "emo_Teasing"
976
+ },
977
+ {
978
+ "name": "emo:Thankfulness_Gratitude",
979
+ "label": "Thankfulness_Gratitude",
980
+ "normalization_bounds": [
981
+ 0,
982
+ 6
983
+ ],
984
+ "head": "EmoNet raw regression",
985
+ "source_global_column": "emo_Thankfulness_Gratitude"
986
+ },
987
+ {
988
+ "name": "emo:Triumph",
989
+ "label": "Triumph",
990
+ "normalization_bounds": [
991
+ 0,
992
+ 6
993
+ ],
994
+ "head": "EmoNet raw regression",
995
+ "source_global_column": "emo_Triumph"
996
+ },
997
+ {
998
+ "name": "genu",
999
+ "label": "Genuineness",
1000
+ "normalization_bounds": [
1001
+ 0,
1002
+ 6
1003
+ ],
1004
+ "source_global_column": "genuineness_0_6",
1005
+ "head": "Genuineness regression"
1006
+ },
1007
+ {
1008
+ "name": "blend",
1009
+ "label": "Vocal-burst blend",
1010
+ "normalization_bounds": [
1011
+ 0,
1012
+ 10
1013
+ ],
1014
+ "source_global_column": "blend_0_10",
1015
+ "head": "Blend regression"
1016
+ }
1017
+ ],
1018
+ "global_tokens": 5,
1019
+ "tokens_per_sentence": 10,
1020
+ "missing_values": "zero normalized input with explicit mask=0; never represented as a measured zero",
1021
+ "normalization": "Affine scale by nominal bounds; do not clip raw regressions outside the nominal range.",
1022
+ "global_scope": "Measured whole-clip features, stability-oriented proxies, not proven speaker constants.",
1023
+ "local_scope": "Measured sentence/span audio only, with pinned source/time/text alignment. Never copy whole-clip values to a sub-sentence.",
1024
+ "quality_scope": "BKGN/RCQL/ESTH are the VoiceNet background/recording/aesthetic axes. They are not interchangeable with separate Empathic-Insight Quality heads.",
1025
+ "sources": [
1026
+ {
1027
+ "path": "/e/home/jusers/schuhmann1/jupiter/layer_forensics/w2/actf_w2.py",
1028
+ "sha256": "103f04aaed55cb4e0d8de6886f3c96813f18d1633431a1963611f79bbde3a0b2"
1029
+ },
1030
+ {
1031
+ "path": "/e/home/jusers/schuhmann1/jupiter/layer_forensics/w2/README.md",
1032
+ "sha256": "c5c70cc20bc1e2381af143bfaf3a7cc621179724309995aad8e3d715f6903fad"
1033
+ },
1034
+ {
1035
+ "path": "/e/data1/datasets/staging/mmlaion/dramabox_top3_rebuild/hf/schema.json",
1036
+ "sha256": "3ba48124f225e31374212707bf949ce4df8dcdf9043100d78674c4e51eb315e3"
1037
+ }
1038
+ ],
1039
+ "historical_partition_lines": [
1040
+ 70,
1041
+ 73
1042
+ ]
1043
+ }
assets/sft3/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from .configuration_moss_tts import MossTTSLocalConfig
2
+ from .modeling_moss_tts import MossTTSLocalModel
3
+ from .processing_moss_tts import MossTTSLocalProcessor
4
+
5
+ __all__ = [
6
+ "MossTTSLocalConfig",
7
+ "MossTTSLocalModel",
8
+ "MossTTSLocalProcessor",
9
+ ]
assets/sft3/added_tokens.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "</think>": 151668,
3
+ "</tool_call>": 151658,
4
+ "</tool_response>": 151666,
5
+ "<think>": 151667,
6
+ "<tool_call>": 151657,
7
+ "<tool_response>": 151665,
8
+ "<|audio_end|>": 151670,
9
+ "<|audio_pad|>": 151671,
10
+ "<|audio_start|>": 151669,
11
+ "<|box_end|>": 151649,
12
+ "<|box_start|>": 151648,
13
+ "<|endoftext|>": 151643,
14
+ "<|file_sep|>": 151664,
15
+ "<|fim_middle|>": 151660,
16
+ "<|fim_pad|>": 151662,
17
+ "<|fim_prefix|>": 151659,
18
+ "<|fim_suffix|>": 151661,
19
+ "<|im_end|>": 151645,
20
+ "<|im_start|>": 151644,
21
+ "<|image_pad|>": 151655,
22
+ "<|object_ref_end|>": 151647,
23
+ "<|object_ref_start|>": 151646,
24
+ "<|quad_end|>": 151651,
25
+ "<|quad_start|>": 151650,
26
+ "<|repo_name|>": 151663,
27
+ "<|video_pad|>": 151656,
28
+ "<|vision_end|>": 151653,
29
+ "<|vision_pad|>": 151654,
30
+ "<|vision_start|>": 151652
31
+ }
assets/sft3/config.json ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MossTTSLocalModel"
4
+ ],
5
+ "attn_implementation": "sdpa",
6
+ "audio_assistant_gen_slot_token_id": 151656,
7
+ "audio_assistant_slot_token_id": 151656,
8
+ "audio_codebook_sizes": [
9
+ 1024,
10
+ 1024,
11
+ 1024,
12
+ 1024,
13
+ 1024,
14
+ 1024,
15
+ 1024,
16
+ 1024,
17
+ 1024,
18
+ 1024,
19
+ 1024,
20
+ 1024
21
+ ],
22
+ "audio_end_token_id": 151670,
23
+ "audio_pad_code": 1024,
24
+ "audio_pad_token_id": 1024,
25
+ "audio_start_token_id": 151669,
26
+ "audio_tokenizer_name_or_path": "OpenMOSS-Team/MOSS-Audio-Tokenizer-v2",
27
+ "audio_user_slot_token_id": 151654,
28
+ "audio_vocab_size": 1024,
29
+ "auto_map": {
30
+ "AutoConfig": "configuration_moss_tts.MossTTSLocalConfig",
31
+ "AutoModel": "modeling_moss_tts.MossTTSLocalModel",
32
+ "AutoProcessor": "processing_moss_tts.MossTTSLocalProcessor"
33
+ },
34
+ "dtype": "bfloat16",
35
+ "gpt2_config": {
36
+ "activation_function": "silu",
37
+ "add_cross_attention": false,
38
+ "attn_pdrop": 0.0,
39
+ "bos_token_id": null,
40
+ "cross_attention_hidden_size": null,
41
+ "decoder_start_token_id": null,
42
+ "embd_pdrop": 0.0,
43
+ "eos_token_id": 151645,
44
+ "finetuning_task": null,
45
+ "initializer_range": 0.02,
46
+ "is_decoder": false,
47
+ "layer_norm_epsilon": 1e-06,
48
+ "model_type": "gpt2",
49
+ "n_ctx": 10240,
50
+ "n_embd": 2560,
51
+ "n_head": 32,
52
+ "n_inner": 9728,
53
+ "n_layer": 1,
54
+ "n_positions": 10240,
55
+ "pad_token_id": null,
56
+ "position_embedding_type": "rope",
57
+ "prefix": null,
58
+ "pruned_heads": {},
59
+ "reorder_and_upcast_attn": false,
60
+ "resid_pdrop": 0.0,
61
+ "rope_base": 1000000.0,
62
+ "scale_attn_by_inverse_layer_idx": false,
63
+ "scale_attn_weights": true,
64
+ "sep_token_id": null,
65
+ "summary_activation": null,
66
+ "summary_first_dropout": 0.1,
67
+ "summary_proj_to_labels": true,
68
+ "summary_type": "cls_index",
69
+ "summary_use_proj": true,
70
+ "task_specific_params": null,
71
+ "tf_legacy_loss": false,
72
+ "tie_encoder_decoder": false,
73
+ "tie_word_embeddings": true,
74
+ "tokenizer_class": null,
75
+ "torchscript": false,
76
+ "use_bfloat16": false,
77
+ "use_cache": true,
78
+ "vocab_size": 151936
79
+ },
80
+ "hidden_size": 2560,
81
+ "im_end_token_id": 151645,
82
+ "im_start_token_id": 151644,
83
+ "initializer_range": 0.02,
84
+ "language_config": {
85
+ "add_cross_attention": false,
86
+ "architectures": [
87
+ "Qwen3ForCausalLM"
88
+ ],
89
+ "attention_bias": false,
90
+ "attention_dropout": 0.0,
91
+ "bos_token_id": 151643,
92
+ "cross_attention_hidden_size": null,
93
+ "decoder_start_token_id": null,
94
+ "dtype": "bfloat16",
95
+ "eos_token_id": 151643,
96
+ "finetuning_task": null,
97
+ "gradient_checkpointing_use_reentrant": false,
98
+ "head_dim": 128,
99
+ "hidden_act": "silu",
100
+ "hidden_size": 2560,
101
+ "initializer_range": 0.02,
102
+ "intermediate_size": 9728,
103
+ "is_decoder": false,
104
+ "layer_types": [
105
+ "full_attention",
106
+ "full_attention",
107
+ "full_attention",
108
+ "full_attention",
109
+ "full_attention",
110
+ "full_attention",
111
+ "full_attention",
112
+ "full_attention",
113
+ "full_attention",
114
+ "full_attention",
115
+ "full_attention",
116
+ "full_attention",
117
+ "full_attention",
118
+ "full_attention",
119
+ "full_attention",
120
+ "full_attention",
121
+ "full_attention",
122
+ "full_attention",
123
+ "full_attention",
124
+ "full_attention",
125
+ "full_attention",
126
+ "full_attention",
127
+ "full_attention",
128
+ "full_attention",
129
+ "full_attention",
130
+ "full_attention",
131
+ "full_attention",
132
+ "full_attention",
133
+ "full_attention",
134
+ "full_attention",
135
+ "full_attention",
136
+ "full_attention",
137
+ "full_attention",
138
+ "full_attention",
139
+ "full_attention",
140
+ "full_attention"
141
+ ],
142
+ "max_position_embeddings": 32768,
143
+ "max_window_layers": 36,
144
+ "model_type": "qwen3",
145
+ "num_attention_heads": 32,
146
+ "num_hidden_layers": 36,
147
+ "num_key_value_heads": 8,
148
+ "pad_token_id": 151643,
149
+ "prefix": null,
150
+ "pruned_heads": {},
151
+ "rms_norm_eps": 1e-06,
152
+ "rope_parameters": {
153
+ "rope_theta": 1000000,
154
+ "rope_type": "default"
155
+ },
156
+ "sep_token_id": null,
157
+ "sliding_window": null,
158
+ "task_specific_params": null,
159
+ "tf_legacy_loss": false,
160
+ "tie_encoder_decoder": false,
161
+ "tie_word_embeddings": true,
162
+ "tokenizer_class": null,
163
+ "torchscript": false,
164
+ "use_bfloat16": false,
165
+ "use_cache": false,
166
+ "use_sliding_window": false,
167
+ "vocab_size": 151936
168
+ },
169
+ "local_hidden_size": 2560,
170
+ "local_text_head_mode": "binary",
171
+ "local_transformer_attn_implementation": "sdpa",
172
+ "local_transformer_layers": 1,
173
+ "model_type": "moss_tts_local",
174
+ "n_vq": 12,
175
+ "pad_token_id": 151643,
176
+ "processor_class": "MossTTSLocalProcessor",
177
+ "qwen3_config": {
178
+ "add_cross_attention": false,
179
+ "architectures": [
180
+ "Qwen3ForCausalLM"
181
+ ],
182
+ "attention_bias": false,
183
+ "attention_dropout": 0.0,
184
+ "bos_token_id": 151643,
185
+ "cross_attention_hidden_size": null,
186
+ "decoder_start_token_id": null,
187
+ "dtype": "bfloat16",
188
+ "eos_token_id": 151643,
189
+ "finetuning_task": null,
190
+ "gradient_checkpointing_use_reentrant": false,
191
+ "head_dim": 128,
192
+ "hidden_act": "silu",
193
+ "hidden_size": 2560,
194
+ "initializer_range": 0.02,
195
+ "intermediate_size": 9728,
196
+ "is_decoder": false,
197
+ "layer_types": [
198
+ "full_attention",
199
+ "full_attention",
200
+ "full_attention",
201
+ "full_attention",
202
+ "full_attention",
203
+ "full_attention",
204
+ "full_attention",
205
+ "full_attention",
206
+ "full_attention",
207
+ "full_attention",
208
+ "full_attention",
209
+ "full_attention",
210
+ "full_attention",
211
+ "full_attention",
212
+ "full_attention",
213
+ "full_attention",
214
+ "full_attention",
215
+ "full_attention",
216
+ "full_attention",
217
+ "full_attention",
218
+ "full_attention",
219
+ "full_attention",
220
+ "full_attention",
221
+ "full_attention",
222
+ "full_attention",
223
+ "full_attention",
224
+ "full_attention",
225
+ "full_attention",
226
+ "full_attention",
227
+ "full_attention",
228
+ "full_attention",
229
+ "full_attention",
230
+ "full_attention",
231
+ "full_attention",
232
+ "full_attention",
233
+ "full_attention"
234
+ ],
235
+ "max_position_embeddings": 32768,
236
+ "max_window_layers": 36,
237
+ "model_type": "qwen3",
238
+ "num_attention_heads": 32,
239
+ "num_hidden_layers": 36,
240
+ "num_key_value_heads": 8,
241
+ "pad_token_id": 151643,
242
+ "prefix": null,
243
+ "pruned_heads": {},
244
+ "rms_norm_eps": 1e-06,
245
+ "rope_parameters": {
246
+ "rope_theta": 1000000,
247
+ "rope_type": "default"
248
+ },
249
+ "sep_token_id": null,
250
+ "sliding_window": null,
251
+ "task_specific_params": null,
252
+ "tf_legacy_loss": false,
253
+ "tie_encoder_decoder": false,
254
+ "tie_word_embeddings": true,
255
+ "tokenizer_class": null,
256
+ "torchscript": false,
257
+ "use_bfloat16": false,
258
+ "use_cache": false,
259
+ "use_sliding_window": false,
260
+ "vocab_size": 151936
261
+ },
262
+ "sampling_rate": 48000,
263
+ "tie_word_embeddings": true,
264
+ "transformers_version": "5.14.1",
265
+ "use_static_local_kv_cache": true,
266
+ "vocab_size": 151936
267
+ }
assets/sft3/configuration_moss_tts.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ """Configuration for the MOSS-TTS-Local-Transformer-v1.5 release."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, Optional, Union
7
+
8
+ from transformers.configuration_utils import PretrainedConfig
9
+ from transformers.models.gpt2.configuration_gpt2 import GPT2Config
10
+ from transformers.models.qwen3.configuration_qwen3 import Qwen3Config
11
+
12
+
13
+ SUPPORTED_ATTENTION_IMPLEMENTATIONS = {"flash_attention_2", "sdpa", "eager"}
14
+
15
+
16
+ def _normalize_attention_implementation(value: Optional[str], default: str = "flash_attention_2") -> str:
17
+ normalized = str(value or default).strip().lower()
18
+ if normalized in {"flash", "flash_attn", "flash-attn", "flash_attention"}:
19
+ normalized = "flash_attention_2"
20
+ if normalized not in SUPPORTED_ATTENTION_IMPLEMENTATIONS:
21
+ raise ValueError(
22
+ "attn_implementation must be one of "
23
+ f"{sorted(SUPPORTED_ATTENTION_IMPLEMENTATIONS)}, got {value!r}."
24
+ )
25
+ return normalized
26
+
27
+
28
+ class MossTTSLocalConfig(PretrainedConfig):
29
+ model_type = "moss_tts_local"
30
+ keys_to_ignore_at_inference = ["past_key_values"]
31
+
32
+ def __init__(
33
+ self,
34
+ qwen3_config: Optional[Union[Qwen3Config, Dict[str, Any]]] = None,
35
+ gpt2_config: Optional[Union[GPT2Config, Dict[str, Any]]] = None,
36
+ language_config: Optional[Union[Qwen3Config, Dict[str, Any]]] = None,
37
+ n_vq: int = 12,
38
+ audio_vocab_size: int = 1024,
39
+ audio_codebook_sizes: Optional[list[int]] = None,
40
+ audio_pad_token_id: int = 1024,
41
+ audio_pad_code: Optional[int] = None,
42
+ pad_token_id: int = 151643,
43
+ im_start_token_id: int = 151644,
44
+ im_end_token_id: int = 151645,
45
+ audio_start_token_id: int = 151669,
46
+ audio_end_token_id: int = 151670,
47
+ audio_user_slot_token_id: int = 151654,
48
+ audio_assistant_slot_token_id: int = 151656,
49
+ audio_assistant_gen_slot_token_id: Optional[int] = None,
50
+ sampling_rate: int = 48000,
51
+ audio_tokenizer_name_or_path: Optional[str] = None,
52
+ attn_implementation: str = "flash_attention_2",
53
+ local_transformer_attn_implementation: Optional[str] = None,
54
+ local_text_head_mode: str = "binary",
55
+ initializer_range: float = 0.02,
56
+ **kwargs: Any,
57
+ ) -> None:
58
+ if qwen3_config is None and language_config is not None:
59
+ qwen3_config = language_config
60
+ if isinstance(qwen3_config, dict):
61
+ self.qwen3_config = Qwen3Config(**qwen3_config)
62
+ elif qwen3_config is None:
63
+ self.qwen3_config = Qwen3Config()
64
+ else:
65
+ self.qwen3_config = qwen3_config
66
+
67
+ if isinstance(gpt2_config, dict):
68
+ self.gpt2_config = GPT2Config(**gpt2_config)
69
+ elif gpt2_config is None:
70
+ self.gpt2_config = GPT2Config(
71
+ vocab_size=int(self.qwen3_config.vocab_size),
72
+ n_embd=int(self.qwen3_config.hidden_size),
73
+ n_layer=1,
74
+ n_head=max(1, int(self.qwen3_config.hidden_size) // 80),
75
+ n_positions=int(n_vq) + 1,
76
+ n_ctx=int(n_vq) + 1,
77
+ activation_function="silu",
78
+ layer_norm_epsilon=1e-6,
79
+ resid_pdrop=0.0,
80
+ embd_pdrop=0.0,
81
+ attn_pdrop=0.0,
82
+ )
83
+ else:
84
+ self.gpt2_config = gpt2_config
85
+
86
+ self.n_vq = int(n_vq)
87
+ if self.n_vq <= 0:
88
+ raise ValueError("n_vq must be positive.")
89
+ if audio_codebook_sizes is None:
90
+ self.audio_codebook_sizes = [int(audio_vocab_size)] * self.n_vq
91
+ else:
92
+ self.audio_codebook_sizes = [int(size) for size in audio_codebook_sizes]
93
+ if len(self.audio_codebook_sizes) != self.n_vq:
94
+ raise ValueError(
95
+ f"audio_codebook_sizes must have length n_vq={self.n_vq}, "
96
+ f"got {len(self.audio_codebook_sizes)}."
97
+ )
98
+ if any(size <= 0 for size in self.audio_codebook_sizes):
99
+ raise ValueError("audio_codebook_sizes must contain positive integers.")
100
+ self.audio_vocab_size = int(max(int(audio_vocab_size), max(self.audio_codebook_sizes)))
101
+ self.audio_pad_token_id = int(audio_pad_code if audio_pad_code is not None else audio_pad_token_id)
102
+ self.audio_pad_code = self.audio_pad_token_id
103
+ if self.audio_pad_token_id < self.audio_vocab_size:
104
+ raise ValueError("audio_pad_token_id/audio_pad_code must be outside the audio vocab.")
105
+
106
+ self.pad_token_id = int(pad_token_id)
107
+ self.im_start_token_id = int(im_start_token_id)
108
+ self.im_end_token_id = int(im_end_token_id)
109
+ self.audio_start_token_id = int(audio_start_token_id)
110
+ self.audio_end_token_id = int(audio_end_token_id)
111
+ self.audio_user_slot_token_id = int(audio_user_slot_token_id)
112
+ self.audio_assistant_slot_token_id = int(
113
+ audio_assistant_slot_token_id
114
+ if audio_assistant_gen_slot_token_id is None
115
+ else audio_assistant_gen_slot_token_id
116
+ )
117
+ self.audio_assistant_gen_slot_token_id = self.audio_assistant_slot_token_id
118
+
119
+ self.sampling_rate = int(sampling_rate)
120
+ self.audio_tokenizer_name_or_path = audio_tokenizer_name_or_path
121
+ self.attn_implementation = _normalize_attention_implementation(attn_implementation)
122
+ self.local_transformer_attn_implementation = _normalize_attention_implementation(
123
+ local_transformer_attn_implementation,
124
+ default=self.attn_implementation,
125
+ )
126
+ self.initializer_range = float(initializer_range)
127
+
128
+ self.hidden_size = int(self.qwen3_config.hidden_size)
129
+ self.vocab_size = int(self.qwen3_config.vocab_size)
130
+ self.local_hidden_size = int(self.gpt2_config.hidden_size)
131
+ if self.local_hidden_size != self.hidden_size:
132
+ raise ValueError(
133
+ "This MOSS-TTS-Local-Transformer-v1.5 release expects local hidden size to "
134
+ "match Qwen3 hidden size so audio embeddings and heads are tied."
135
+ )
136
+
137
+ normalized_text_head_mode = str(local_text_head_mode or "full_vocab").strip().lower()
138
+ if normalized_text_head_mode in {"full", "full-vocab", "vocab"}:
139
+ normalized_text_head_mode = "full_vocab"
140
+ if normalized_text_head_mode not in {"full_vocab", "binary"}:
141
+ raise ValueError("local_text_head_mode must be 'full_vocab' or 'binary'.")
142
+ self.local_text_head_mode = normalized_text_head_mode
143
+
144
+ kwargs.setdefault("tie_word_embeddings", True)
145
+ super().__init__(pad_token_id=self.pad_token_id, **kwargs)
146
+
147
+ @property
148
+ def language_config(self) -> Qwen3Config:
149
+ return self.qwen3_config
150
+
151
+ def to_dict(self) -> Dict[str, Any]:
152
+ output = super().to_dict()
153
+ output["qwen3_config"] = self.qwen3_config.to_dict()
154
+ output["language_config"] = self.qwen3_config.to_dict()
155
+ output["gpt2_config"] = self.gpt2_config.to_dict()
156
+ output["audio_pad_code"] = self.audio_pad_token_id
157
+ output["audio_assistant_gen_slot_token_id"] = self.audio_assistant_slot_token_id
158
+ return output
assets/sft3/gpt2_decoder.py ADDED
@@ -0,0 +1,721 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.utils.checkpoint
10
+ from transformers.activations import ACT2FN
11
+ from transformers.modeling_outputs import BaseModelOutputWithPast
12
+ from transformers.models.gpt2.configuration_gpt2 import GPT2Config
13
+
14
+ try:
15
+ from flash_attn import flash_attn_func, flash_attn_varlen_func
16
+ from flash_attn.bert_padding import pad_input, unpad_input
17
+
18
+ _FLASH_ATTN_AVAILABLE = True
19
+ except Exception:
20
+ flash_attn_func = None
21
+ flash_attn_varlen_func = None
22
+ pad_input = None
23
+ unpad_input = None
24
+ _FLASH_ATTN_AVAILABLE = False
25
+
26
+
27
+ @dataclass
28
+ class PackedSequenceMetadata:
29
+ cu_seqlens: torch.Tensor
30
+ max_seqlen: int
31
+ indices: Optional[torch.Tensor] = None
32
+ batch_size: Optional[int] = None
33
+ seq_len: Optional[int] = None
34
+
35
+
36
+ def _is_static_kv_cache_layer(layer_past: object) -> bool:
37
+ return isinstance(layer_past, dict) and bool(layer_past.get("static_kv_cache", False))
38
+
39
+
40
+ class MossTTSNanoGPT2RotaryEmbedding(nn.Module):
41
+ def __init__(self, dim: int, base: float = 10000.0) -> None:
42
+ super().__init__()
43
+ if dim % 2 != 0:
44
+ raise ValueError(f"RoPE head_dim must be even, got {dim}")
45
+ self.dim = int(dim)
46
+ self.base = float(base)
47
+ self.register_buffer("inv_freq", self._compute_inv_freq(), persistent=False)
48
+
49
+ def _compute_inv_freq(self, device: Optional[torch.device] = None) -> torch.Tensor:
50
+ return 1.0 / (
51
+ self.base ** (torch.arange(0, self.dim, 2, device=device, dtype=torch.float32) / self.dim)
52
+ )
53
+
54
+ def forward(
55
+ self,
56
+ position_ids: torch.LongTensor,
57
+ *,
58
+ device: torch.device,
59
+ dtype: torch.dtype,
60
+ ) -> tuple[torch.Tensor, torch.Tensor]:
61
+ if position_ids.ndim == 1:
62
+ position_ids = position_ids.unsqueeze(0)
63
+ inv_freq = self._compute_inv_freq(device=device)
64
+ freqs = torch.einsum("bs,d->bsd", position_ids.to(device=device, dtype=inv_freq.dtype), inv_freq)
65
+ cos = freqs.cos().repeat_interleave(2, dim=-1).unsqueeze(2).to(dtype=dtype)
66
+ sin = freqs.sin().repeat_interleave(2, dim=-1).unsqueeze(2).to(dtype=dtype)
67
+ return cos, sin
68
+
69
+
70
+ def rotate_half(hidden_states: torch.Tensor) -> torch.Tensor:
71
+ even = hidden_states[..., ::2]
72
+ odd = hidden_states[..., 1::2]
73
+ return torch.stack((-odd, even), dim=-1).reshape_as(hidden_states)
74
+
75
+
76
+ def apply_rotary_pos_emb(
77
+ hidden_states: torch.Tensor,
78
+ cos: torch.Tensor,
79
+ sin: torch.Tensor,
80
+ ) -> torch.Tensor:
81
+ return (hidden_states * cos) + (rotate_half(hidden_states) * sin)
82
+
83
+
84
+ class MossTTSNanoGPT2MLP(nn.Module):
85
+ def __init__(self, config: GPT2Config) -> None:
86
+ super().__init__()
87
+ hidden_size = int(config.hidden_size)
88
+ inner_size = int(config.n_inner or 4 * hidden_size)
89
+ self.fc_in = nn.Linear(hidden_size, inner_size)
90
+ self.fc_out = nn.Linear(inner_size, hidden_size)
91
+ self.act = ACT2FN[config.activation_function]
92
+ self.dropout = nn.Dropout(config.resid_pdrop)
93
+
94
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
95
+ hidden_states = self.fc_in(hidden_states)
96
+ hidden_states = self.act(hidden_states)
97
+ hidden_states = self.fc_out(hidden_states)
98
+ return self.dropout(hidden_states)
99
+
100
+
101
+ class MossTTSNanoGPT2Attention(nn.Module):
102
+ def __init__(self, config: GPT2Config, layer_idx: int, attn_implementation: str) -> None:
103
+ super().__init__()
104
+ hidden_size = int(config.hidden_size)
105
+ num_heads = int(config.num_attention_heads)
106
+ if hidden_size % num_heads != 0:
107
+ raise ValueError(f"hidden_size={hidden_size} must be divisible by num_attention_heads={num_heads}")
108
+
109
+ self.num_heads = num_heads
110
+ self.head_dim = hidden_size // num_heads
111
+ self.embed_dim = hidden_size
112
+ self.layer_idx = layer_idx
113
+ self.attn_implementation = attn_implementation
114
+ self.attn_dropout = float(config.attn_pdrop)
115
+ self.resid_dropout = nn.Dropout(config.resid_pdrop)
116
+ self.scale_attn_weights = bool(getattr(config, "scale_attn_weights", True))
117
+ self.scale_attn_by_inverse_layer_idx = bool(getattr(config, "scale_attn_by_inverse_layer_idx", False))
118
+ self.position_embedding_type = str(getattr(config, "position_embedding_type", "absolute")).lower()
119
+ if self.position_embedding_type not in {"absolute", "rope"}:
120
+ raise ValueError(f"Unsupported position_embedding_type={self.position_embedding_type!r}")
121
+
122
+ self.c_attn = nn.Linear(hidden_size, 3 * hidden_size)
123
+ self.c_proj = nn.Linear(hidden_size, hidden_size)
124
+ self.rotary_emb = None
125
+ if self.position_embedding_type == "rope":
126
+ self.rotary_emb = MossTTSNanoGPT2RotaryEmbedding(
127
+ self.head_dim,
128
+ base=float(getattr(config, "rope_base", 10000.0)),
129
+ )
130
+
131
+ def _split_heads(self, tensor: torch.Tensor) -> torch.Tensor:
132
+ if tensor.ndim == 3:
133
+ batch_size, seq_len, _ = tensor.shape
134
+ return tensor.view(batch_size, seq_len, self.num_heads, self.head_dim)
135
+ if tensor.ndim == 2:
136
+ total_tokens, _ = tensor.shape
137
+ return tensor.view(total_tokens, self.num_heads, self.head_dim)
138
+ raise ValueError(f"Unsupported tensor rank for attention split: {tensor.ndim}")
139
+
140
+ def _merge_heads(self, tensor: torch.Tensor) -> torch.Tensor:
141
+ if tensor.ndim == 4:
142
+ batch_size, seq_len, _, _ = tensor.shape
143
+ return tensor.reshape(batch_size, seq_len, self.embed_dim)
144
+ if tensor.ndim == 3:
145
+ total_tokens, _, _ = tensor.shape
146
+ return tensor.reshape(total_tokens, self.embed_dim)
147
+ raise ValueError(f"Unsupported tensor rank for attention merge: {tensor.ndim}")
148
+
149
+ def _causal_attention_mask(
150
+ self,
151
+ attention_mask: Optional[torch.Tensor],
152
+ query_length: int,
153
+ key_length: int,
154
+ device: torch.device,
155
+ ) -> torch.Tensor:
156
+ query_positions = torch.arange(query_length, device=device, dtype=torch.long)
157
+ query_positions = query_positions + max(key_length - query_length, 0)
158
+ key_positions = torch.arange(key_length, device=device, dtype=torch.long)
159
+ causal = key_positions.unsqueeze(0) <= query_positions.unsqueeze(1)
160
+ causal = causal.unsqueeze(0).unsqueeze(0)
161
+ if attention_mask is None:
162
+ return causal
163
+ key_mask = attention_mask[:, None, None, :].to(dtype=torch.bool)
164
+ return causal & key_mask
165
+
166
+ def _eager_attention(
167
+ self,
168
+ query: torch.Tensor,
169
+ key: torch.Tensor,
170
+ value: torch.Tensor,
171
+ attention_mask: Optional[torch.Tensor],
172
+ ) -> torch.Tensor:
173
+ query = query.transpose(1, 2)
174
+ key = key.transpose(1, 2)
175
+ value = value.transpose(1, 2)
176
+
177
+ scale = 1.0
178
+ if self.scale_attn_weights:
179
+ scale /= self.head_dim ** 0.5
180
+ if self.scale_attn_by_inverse_layer_idx:
181
+ scale /= float(self.layer_idx + 1)
182
+
183
+ scores = torch.matmul(query, key.transpose(-1, -2)) * scale
184
+ causal_mask = self._causal_attention_mask(
185
+ attention_mask=attention_mask,
186
+ query_length=query.shape[-2],
187
+ key_length=key.shape[-2],
188
+ device=query.device,
189
+ )
190
+ scores = scores.masked_fill(~causal_mask, torch.finfo(scores.dtype).min)
191
+ probs = torch.softmax(scores, dim=-1)
192
+ if self.training and self.attn_dropout > 0:
193
+ probs = torch.dropout(probs, self.attn_dropout, train=True)
194
+ output = torch.matmul(probs, value)
195
+ return output.transpose(1, 2).contiguous()
196
+
197
+ def _sdpa_attention(
198
+ self,
199
+ query: torch.Tensor,
200
+ key: torch.Tensor,
201
+ value: torch.Tensor,
202
+ attention_mask: Optional[torch.Tensor],
203
+ ) -> torch.Tensor:
204
+ query = query.transpose(1, 2)
205
+ key = key.transpose(1, 2)
206
+ value = value.transpose(1, 2)
207
+ mask = None
208
+ if attention_mask is not None or query.shape[-2] != key.shape[-2]:
209
+ mask = self._causal_attention_mask(
210
+ attention_mask=attention_mask,
211
+ query_length=query.shape[-2],
212
+ key_length=key.shape[-2],
213
+ device=query.device,
214
+ )
215
+ output = torch.nn.functional.scaled_dot_product_attention(
216
+ query,
217
+ key,
218
+ value,
219
+ attn_mask=mask,
220
+ dropout_p=self.attn_dropout if self.training else 0.0,
221
+ is_causal=mask is None,
222
+ )
223
+ return output.transpose(1, 2).contiguous()
224
+
225
+ def _flash_attention(
226
+ self,
227
+ query: torch.Tensor,
228
+ key: torch.Tensor,
229
+ value: torch.Tensor,
230
+ attention_mask: Optional[torch.Tensor],
231
+ packed_metadata: Optional[PackedSequenceMetadata],
232
+ ) -> torch.Tensor:
233
+ if not _FLASH_ATTN_AVAILABLE:
234
+ raise ImportError("flash_attn is not installed, but attn_implementation='flash_attention_2' was requested.")
235
+ if query.device.type != "cuda":
236
+ raise ValueError("flash_attention_2 requires CUDA tensors.")
237
+ if query.dtype not in (torch.float16, torch.bfloat16):
238
+ raise ValueError(
239
+ f"flash_attention_2 requires fp16/bf16 tensors, but received dtype={query.dtype}."
240
+ )
241
+
242
+ dropout_p = self.attn_dropout if self.training else 0.0
243
+ if packed_metadata is not None:
244
+ if packed_metadata.indices is not None:
245
+ query = query.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
246
+ key = key.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
247
+ value = value.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
248
+ output = flash_attn_varlen_func(
249
+ query,
250
+ key,
251
+ value,
252
+ packed_metadata.cu_seqlens,
253
+ packed_metadata.cu_seqlens,
254
+ packed_metadata.max_seqlen,
255
+ packed_metadata.max_seqlen,
256
+ dropout_p=dropout_p,
257
+ causal=True,
258
+ )
259
+ if packed_metadata.indices is None:
260
+ return output
261
+ return pad_input(
262
+ output,
263
+ packed_metadata.indices,
264
+ packed_metadata.batch_size,
265
+ packed_metadata.seq_len,
266
+ )
267
+
268
+ if attention_mask is None or bool(attention_mask.all()):
269
+ return flash_attn_func(
270
+ query,
271
+ key,
272
+ value,
273
+ dropout_p=dropout_p,
274
+ causal=True,
275
+ )
276
+
277
+ if query.shape[1] != key.shape[1]:
278
+ query_attention_mask = attention_mask[:, -query.shape[1] :]
279
+ unpadded_query, query_indices, cu_seqlens_q, max_seqlen_q, _ = unpad_input(
280
+ query,
281
+ query_attention_mask,
282
+ )
283
+ unpadded_key, _, cu_seqlens_k, max_seqlen_k, _ = unpad_input(key, attention_mask)
284
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
285
+ output = flash_attn_varlen_func(
286
+ unpadded_query,
287
+ unpadded_key,
288
+ unpadded_value,
289
+ cu_seqlens_q,
290
+ cu_seqlens_k,
291
+ max_seqlen_q,
292
+ max_seqlen_k,
293
+ dropout_p=dropout_p,
294
+ causal=True,
295
+ )
296
+ return pad_input(output, query_indices, query.shape[0], query.shape[1])
297
+
298
+ unpadded_query, indices, cu_seqlens, max_seqlen, _ = unpad_input(query, attention_mask)
299
+ unpadded_key, _, _, _, _ = unpad_input(key, attention_mask)
300
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
301
+ output = flash_attn_varlen_func(
302
+ unpadded_query,
303
+ unpadded_key,
304
+ unpadded_value,
305
+ cu_seqlens,
306
+ cu_seqlens,
307
+ max_seqlen,
308
+ max_seqlen,
309
+ dropout_p=dropout_p,
310
+ causal=True,
311
+ )
312
+ return pad_input(output, indices, query.shape[0], query.shape[1])
313
+
314
+ def forward(
315
+ self,
316
+ hidden_states: torch.Tensor,
317
+ attention_mask: Optional[torch.Tensor] = None,
318
+ position_ids: Optional[torch.LongTensor] = None,
319
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
320
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
321
+ use_cache: bool = False,
322
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
323
+ qkv = self.c_attn(hidden_states)
324
+ query, key, value = qkv.split(self.embed_dim, dim=-1)
325
+ query = self._split_heads(query)
326
+ key = self._split_heads(key)
327
+ value = self._split_heads(value)
328
+
329
+ if self.rotary_emb is not None:
330
+ if position_ids is None:
331
+ raise ValueError("position_ids must be provided when position_embedding_type='rope'.")
332
+ cos, sin = self.rotary_emb(
333
+ position_ids.to(device=query.device),
334
+ device=query.device,
335
+ dtype=query.dtype,
336
+ )
337
+ query = apply_rotary_pos_emb(query, cos, sin)
338
+ key = apply_rotary_pos_emb(key, cos, sin)
339
+
340
+ static_layer_past = layer_past is not None and _is_static_kv_cache_layer(layer_past)
341
+ if static_layer_past:
342
+ past_length = int(layer_past.get("length", 0))
343
+ new_length = past_length + int(key.shape[1])
344
+ key_cache = layer_past["key"]
345
+ value_cache = layer_past["value"]
346
+ if new_length > int(key_cache.shape[1]):
347
+ raise ValueError(
348
+ f"Static KV cache is too short: need {new_length}, capacity={int(key_cache.shape[1])}."
349
+ )
350
+ key_cache[:, past_length:new_length].copy_(key)
351
+ value_cache[:, past_length:new_length].copy_(value)
352
+ key = key_cache[:, :new_length]
353
+ value = value_cache[:, :new_length]
354
+ layer_past["length"] = new_length
355
+ elif layer_past is not None:
356
+ past_key, past_value = layer_past
357
+ key = torch.cat([past_key.to(device=key.device, dtype=key.dtype), key], dim=1)
358
+ value = torch.cat([past_value.to(device=value.device, dtype=value.dtype), value], dim=1)
359
+
360
+ present = layer_past if (use_cache and static_layer_past) else ((key, value) if use_cache else None)
361
+
362
+ if self.attn_implementation == "flash_attention_2":
363
+ attn_output = self._flash_attention(
364
+ query=query,
365
+ key=key,
366
+ value=value,
367
+ attention_mask=attention_mask,
368
+ packed_metadata=packed_metadata,
369
+ )
370
+ elif self.attn_implementation == "sdpa":
371
+ attn_output = self._sdpa_attention(
372
+ query=query,
373
+ key=key,
374
+ value=value,
375
+ attention_mask=attention_mask,
376
+ )
377
+ else:
378
+ attn_output = self._eager_attention(
379
+ query=query,
380
+ key=key,
381
+ value=value,
382
+ attention_mask=attention_mask,
383
+ )
384
+
385
+ attn_output = self._merge_heads(attn_output)
386
+ attn_output = self.c_proj(attn_output)
387
+ return self.resid_dropout(attn_output), present
388
+
389
+
390
+ class MossTTSNanoGPT2Block(nn.Module):
391
+ def __init__(self, config: GPT2Config, layer_idx: int, attn_implementation: str) -> None:
392
+ super().__init__()
393
+ hidden_size = int(config.hidden_size)
394
+ self.ln_1 = nn.LayerNorm(hidden_size, eps=config.layer_norm_epsilon)
395
+ self.attn = MossTTSNanoGPT2Attention(config, layer_idx=layer_idx, attn_implementation=attn_implementation)
396
+ self.ln_2 = nn.LayerNorm(hidden_size, eps=config.layer_norm_epsilon)
397
+ self.mlp = MossTTSNanoGPT2MLP(config)
398
+
399
+ def forward(
400
+ self,
401
+ hidden_states: torch.Tensor,
402
+ attention_mask: Optional[torch.Tensor] = None,
403
+ position_ids: Optional[torch.LongTensor] = None,
404
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
405
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
406
+ use_cache: bool = False,
407
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
408
+ attn_output, present = self.attn(
409
+ self.ln_1(hidden_states),
410
+ attention_mask=attention_mask,
411
+ position_ids=position_ids,
412
+ packed_metadata=packed_metadata,
413
+ layer_past=layer_past,
414
+ use_cache=use_cache,
415
+ )
416
+ hidden_states = hidden_states + attn_output
417
+ hidden_states = hidden_states + self.mlp(self.ln_2(hidden_states))
418
+ return hidden_states, present
419
+
420
+
421
+ class MossTTSNanoGPT2Model(nn.Module):
422
+ def __init__(self, config: GPT2Config, attn_implementation: str = "eager") -> None:
423
+ super().__init__()
424
+ self.config = config
425
+ self.attn_implementation = attn_implementation
426
+ self.position_embedding_type = str(getattr(config, "position_embedding_type", "absolute")).lower()
427
+ if self.position_embedding_type not in {"absolute", "rope"}:
428
+ raise ValueError(f"Unsupported position_embedding_type={self.position_embedding_type!r}")
429
+ hidden_size = int(config.hidden_size)
430
+ self.wte = nn.Embedding(config.vocab_size, hidden_size)
431
+ self.wpe = nn.Embedding(config.n_positions, hidden_size) if self.position_embedding_type == "absolute" else nn.Identity()
432
+ self.drop = nn.Dropout(config.embd_pdrop)
433
+ self.h = nn.ModuleList(
434
+ [MossTTSNanoGPT2Block(config, layer_idx=index, attn_implementation=attn_implementation) for index in range(config.n_layer)]
435
+ )
436
+ self.ln_f = nn.LayerNorm(hidden_size, eps=config.layer_norm_epsilon)
437
+ self.gradient_checkpointing = False
438
+ self._reset_parameters()
439
+
440
+ def _reset_parameters(self) -> None:
441
+ init_std = float(self.config.initializer_range)
442
+ for module in self.modules():
443
+ if isinstance(module, nn.Linear):
444
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
445
+ if module.bias is not None:
446
+ nn.init.zeros_(module.bias)
447
+ elif isinstance(module, nn.Embedding):
448
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
449
+ elif isinstance(module, nn.LayerNorm):
450
+ nn.init.ones_(module.weight)
451
+ nn.init.zeros_(module.bias)
452
+
453
+ @staticmethod
454
+ def _normalize_num_sequences(
455
+ cu_seqlens: torch.Tensor,
456
+ num_sequences: Optional[torch.Tensor],
457
+ device: torch.device,
458
+ ) -> torch.Tensor:
459
+ if cu_seqlens.ndim == 1:
460
+ cu_seqlens = cu_seqlens.unsqueeze(0)
461
+ if num_sequences is None:
462
+ diffs = cu_seqlens[:, 1:] - cu_seqlens[:, :-1]
463
+ return diffs.gt(0).sum(dim=-1).to(device=device, dtype=torch.long)
464
+ if num_sequences.ndim == 0:
465
+ num_sequences = num_sequences.unsqueeze(0)
466
+ return num_sequences.to(device=device, dtype=torch.long)
467
+
468
+ @staticmethod
469
+ def _packed_segments_from_cu_seqlens(
470
+ cu_seqlens: torch.Tensor,
471
+ num_sequences: Optional[torch.Tensor],
472
+ device: torch.device,
473
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
474
+ if cu_seqlens.ndim == 1:
475
+ cu_seqlens = cu_seqlens.unsqueeze(0)
476
+ cu_seqlens = cu_seqlens.to(device=device)
477
+ batch_size, boundary_count = cu_seqlens.shape
478
+ segment_slots = boundary_count - 1
479
+ if segment_slots <= 0:
480
+ empty = torch.empty(0, dtype=torch.long, device=device)
481
+ return empty, empty, empty
482
+
483
+ counts = MossTTSNanoGPT2Model._normalize_num_sequences(cu_seqlens, num_sequences, device=device)
484
+ counts = counts.clamp(min=0, max=segment_slots)
485
+ segment_slots = int(counts.max().item()) if counts.numel() > 0 else 0
486
+ if segment_slots <= 0:
487
+ empty = torch.empty(0, dtype=torch.long, device=device)
488
+ return empty, empty, empty
489
+ cu_seqlens = cu_seqlens[:, : segment_slots + 1]
490
+ slot_ids = torch.arange(segment_slots, device=device).unsqueeze(0)
491
+ valid_slots = slot_ids < counts.unsqueeze(1)
492
+
493
+ starts = cu_seqlens[:, :-1].to(dtype=torch.long)
494
+ ends = cu_seqlens[:, 1:].to(dtype=torch.long)
495
+ lengths = (ends - starts).clamp_min(0)
496
+ lengths = torch.where(valid_slots, lengths, torch.zeros((), dtype=torch.long, device=device))
497
+
498
+ batch_ids = torch.arange(batch_size, device=device, dtype=torch.long).unsqueeze(1).expand(batch_size, segment_slots)
499
+ batch_ids = batch_ids.reshape(-1)
500
+ starts = starts.reshape(-1)
501
+ lengths = lengths.reshape(-1)
502
+
503
+ valid_segments = lengths.gt(0)
504
+ valid_count = int(valid_segments.to(dtype=torch.long).sum().item())
505
+ if valid_count <= 0:
506
+ empty = torch.empty(0, dtype=torch.long, device=device)
507
+ return empty, empty, empty
508
+ if valid_count == lengths.numel():
509
+ return batch_ids, starts, lengths
510
+
511
+ valid_order = torch.argsort(valid_segments.to(dtype=torch.long), descending=True, stable=True)[:valid_count]
512
+ return (
513
+ batch_ids.index_select(0, valid_order),
514
+ starts.index_select(0, valid_order),
515
+ lengths.index_select(0, valid_order),
516
+ )
517
+
518
+ @staticmethod
519
+ def _packed_token_indices(
520
+ batch_ids: torch.Tensor,
521
+ starts: torch.Tensor,
522
+ lengths: torch.Tensor,
523
+ seq_len: int,
524
+ ) -> tuple[torch.Tensor, torch.Tensor]:
525
+ total_tokens = int(lengths.sum().item())
526
+ if total_tokens <= 0:
527
+ empty = torch.empty(0, dtype=torch.long, device=lengths.device)
528
+ return empty, empty
529
+
530
+ segment_ids = torch.repeat_interleave(
531
+ torch.arange(lengths.numel(), device=lengths.device, dtype=torch.long),
532
+ lengths,
533
+ output_size=total_tokens,
534
+ )
535
+ segment_starts = torch.cumsum(lengths, dim=0) - lengths
536
+ positions = torch.arange(total_tokens, device=lengths.device, dtype=torch.long) - segment_starts[segment_ids]
537
+ indices = batch_ids[segment_ids] * seq_len + starts[segment_ids] + positions
538
+ return indices, positions
539
+
540
+ @staticmethod
541
+ def build_packed_position_ids(
542
+ attention_mask: Optional[torch.Tensor],
543
+ cu_seqlens: torch.Tensor,
544
+ num_sequences: Optional[torch.Tensor],
545
+ sequence_length: Optional[int] = None,
546
+ ) -> torch.Tensor:
547
+ if cu_seqlens.ndim == 1:
548
+ cu_seqlens = cu_seqlens.unsqueeze(0)
549
+ batch_size = cu_seqlens.shape[0]
550
+ seq_len = int(sequence_length or (cu_seqlens.shape[1] - 1))
551
+ device = cu_seqlens.device
552
+ position_ids = torch.zeros((batch_size, seq_len), dtype=torch.long, device=device)
553
+ batch_ids, starts, lengths = MossTTSNanoGPT2Model._packed_segments_from_cu_seqlens(
554
+ cu_seqlens,
555
+ num_sequences,
556
+ device,
557
+ )
558
+ if lengths.numel() > 0:
559
+ indices, positions = MossTTSNanoGPT2Model._packed_token_indices(batch_ids, starts, lengths, seq_len)
560
+ position_ids.view(-1).scatter_(0, indices, positions)
561
+ if attention_mask is not None:
562
+ position_ids = position_ids * attention_mask.to(dtype=position_ids.dtype)
563
+ return position_ids
564
+
565
+ @staticmethod
566
+ def build_packed_metadata(
567
+ hidden_states: torch.Tensor,
568
+ cu_seqlens: torch.Tensor,
569
+ num_sequences: Optional[torch.Tensor],
570
+ ) -> PackedSequenceMetadata:
571
+ if cu_seqlens.ndim == 1:
572
+ cu_seqlens = cu_seqlens.unsqueeze(0)
573
+ device = hidden_states.device
574
+ seq_len = hidden_states.shape[1]
575
+ batch_ids, starts, lengths = MossTTSNanoGPT2Model._packed_segments_from_cu_seqlens(
576
+ cu_seqlens,
577
+ num_sequences,
578
+ device,
579
+ )
580
+ if lengths.numel() == 0:
581
+ raise ValueError("cu_seqlens did not describe any non-empty packed sequences.")
582
+
583
+ indices, _ = MossTTSNanoGPT2Model._packed_token_indices(batch_ids, starts, lengths, seq_len)
584
+ cumulative = torch.empty(lengths.numel() + 1, dtype=torch.int32, device=device)
585
+ cumulative[0] = 0
586
+ cumulative[1:] = lengths.to(dtype=torch.int32).cumsum(dim=0)
587
+ return PackedSequenceMetadata(
588
+ cu_seqlens=cumulative,
589
+ max_seqlen=int(lengths.max().item()),
590
+ indices=indices,
591
+ batch_size=hidden_states.shape[0],
592
+ seq_len=hidden_states.shape[1],
593
+ )
594
+
595
+ def forward(
596
+ self,
597
+ input_ids: Optional[torch.LongTensor] = None,
598
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None,
599
+ attention_mask: Optional[torch.Tensor] = None,
600
+ position_ids: Optional[torch.LongTensor] = None,
601
+ inputs_embeds: Optional[torch.FloatTensor] = None,
602
+ use_cache: Optional[bool] = None,
603
+ output_attentions: Optional[bool] = None,
604
+ output_hidden_states: Optional[bool] = None,
605
+ return_dict: bool = True,
606
+ cu_seqlens: Optional[torch.Tensor] = None,
607
+ num_sequences: Optional[torch.Tensor] = None,
608
+ ) -> BaseModelOutputWithPast:
609
+ del input_ids, output_attentions
610
+
611
+ if inputs_embeds is None:
612
+ raise ValueError("inputs_embeds must be provided.")
613
+
614
+ use_cache = bool(use_cache)
615
+ if use_cache and cu_seqlens is not None:
616
+ raise ValueError("use_cache=True is not supported together with cu_seqlens packing.")
617
+
618
+ hidden_states = inputs_embeds
619
+ query_attention_mask = None
620
+ if attention_mask is not None:
621
+ attention_mask = attention_mask.to(dtype=torch.bool, device=hidden_states.device)
622
+ query_attention_mask = attention_mask[:, -hidden_states.shape[1] :]
623
+
624
+ packed_metadata = None
625
+ if position_ids is None:
626
+ if cu_seqlens is not None:
627
+ if attention_mask is None:
628
+ raise ValueError("attention_mask must be provided with cu_seqlens packing.")
629
+ position_ids = self.build_packed_position_ids(
630
+ attention_mask=attention_mask,
631
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
632
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
633
+ sequence_length=hidden_states.shape[1],
634
+ )
635
+ elif attention_mask is not None:
636
+ position_ids = attention_mask.long().cumsum(dim=-1) - 1
637
+ position_ids = position_ids.masked_fill(~attention_mask, 0)
638
+ position_ids = position_ids[:, -hidden_states.shape[1] :]
639
+ else:
640
+ past_length = 0
641
+ if past_key_values is not None and len(past_key_values) > 0:
642
+ first_layer_past = past_key_values[0]
643
+ if _is_static_kv_cache_layer(first_layer_past):
644
+ past_length = int(first_layer_past.get("length", 0))
645
+ else:
646
+ past_length = first_layer_past[0].shape[1]
647
+ position_ids = torch.arange(hidden_states.shape[1], device=hidden_states.device, dtype=torch.long)
648
+ position_ids = position_ids + past_length
649
+ position_ids = position_ids.unsqueeze(0).expand(hidden_states.shape[0], -1)
650
+
651
+ if cu_seqlens is not None and self.attn_implementation == "flash_attention_2":
652
+ packed_metadata = self.build_packed_metadata(
653
+ hidden_states=hidden_states,
654
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
655
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
656
+ )
657
+
658
+ if self.position_embedding_type == "absolute":
659
+ hidden_states = hidden_states + self.wpe(position_ids)
660
+ hidden_states = self.drop(hidden_states)
661
+ if query_attention_mask is not None:
662
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
663
+
664
+ all_hidden_states = () if output_hidden_states else None
665
+ presents = [] if use_cache else None
666
+ for layer_index, block in enumerate(self.h):
667
+ if output_hidden_states:
668
+ all_hidden_states = all_hidden_states + (hidden_states,)
669
+
670
+ if self.gradient_checkpointing and self.training:
671
+ if use_cache:
672
+ raise ValueError("use_cache=True is not supported when gradient checkpointing is enabled during training.")
673
+
674
+ def custom_forward(*inputs):
675
+ output, _ = block(
676
+ inputs[0],
677
+ attention_mask=inputs[1],
678
+ position_ids=inputs[2],
679
+ packed_metadata=packed_metadata,
680
+ layer_past=None,
681
+ use_cache=False,
682
+ )
683
+ return output
684
+
685
+ hidden_states = torch.utils.checkpoint.checkpoint(
686
+ custom_forward,
687
+ hidden_states,
688
+ attention_mask,
689
+ position_ids,
690
+ use_reentrant=False,
691
+ )
692
+ present = None
693
+ else:
694
+ hidden_states, present = block(
695
+ hidden_states,
696
+ attention_mask=attention_mask,
697
+ position_ids=position_ids,
698
+ packed_metadata=packed_metadata,
699
+ layer_past=None if past_key_values is None else past_key_values[layer_index],
700
+ use_cache=use_cache,
701
+ )
702
+ if query_attention_mask is not None:
703
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
704
+ if presents is not None:
705
+ presents.append(present)
706
+
707
+ hidden_states = self.ln_f(hidden_states)
708
+ if query_attention_mask is not None:
709
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
710
+ if output_hidden_states:
711
+ all_hidden_states = all_hidden_states + (hidden_states,)
712
+
713
+ if not return_dict:
714
+ return (hidden_states, tuple(presents) if presents is not None else None, all_hidden_states, None)
715
+
716
+ return BaseModelOutputWithPast(
717
+ last_hidden_state=hidden_states,
718
+ past_key_values=tuple(presents) if presents is not None else None,
719
+ hidden_states=all_hidden_states,
720
+ attentions=None,
721
+ )
assets/sft3/modeling_moss_tts.py ADDED
@@ -0,0 +1,623 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ """Modeling code for the MOSS-TTS-Local-Transformer-v1.5 HuggingFace release."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Any, Optional, Union
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+ from transformers.modeling_outputs import BaseModelOutputWithPast
12
+ from transformers.modeling_utils import PreTrainedModel
13
+ from transformers.models.gpt2.configuration_gpt2 import GPT2Config
14
+ from transformers.utils import ModelOutput
15
+
16
+ from .configuration_moss_tts import MossTTSLocalConfig
17
+ from .gpt2_decoder import MossTTSNanoGPT2Model
18
+ from .qwen3_decoder import MossQwen3Model
19
+
20
+
21
+ @dataclass
22
+ class MossTTSLocalOutput(ModelOutput):
23
+ last_hidden_state: Optional[torch.FloatTensor] = None
24
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None
25
+ hidden_states: Optional[tuple[torch.FloatTensor, ...]] = None
26
+ attentions: Optional[tuple[torch.FloatTensor, ...]] = None
27
+
28
+
29
+ def _find_last_equal(input_ids: torch.LongTensor, value: int) -> torch.LongTensor:
30
+ matches = input_ids.eq(int(value))
31
+ if not bool(matches.any(dim=1).all().item()):
32
+ raise ValueError(f"Every sample must contain token id {int(value)}.")
33
+ positions = torch.arange(input_ids.shape[1], device=input_ids.device, dtype=torch.long)
34
+ masked_positions = positions.unsqueeze(0).masked_fill(~matches, -1)
35
+ return masked_positions.max(dim=1).values
36
+
37
+
38
+ class MossTTSLocalPreTrainedModel(PreTrainedModel):
39
+ config_class = MossTTSLocalConfig
40
+ base_model_prefix = "transformer"
41
+ supports_gradient_checkpointing = True
42
+ _no_split_modules = ["MossTTSNanoGPT2Block", "MossQwen3DecoderLayer"]
43
+ _supports_flash_attn_2 = True
44
+ _supports_sdpa = True
45
+ _supports_cache_class = True
46
+
47
+ def _set_gradient_checkpointing(self, module: nn.Module, value: bool = False) -> None:
48
+ if isinstance(module, MossTTSNanoGPT2Model) or isinstance(module, MossQwen3Model):
49
+ module.gradient_checkpointing = value
50
+
51
+
52
+ class MossTTSLocalModel(MossTTSLocalPreTrainedModel):
53
+ _tied_weights_keys = None
54
+
55
+ def __init__(self, config: MossTTSLocalConfig) -> None:
56
+ super().__init__(config)
57
+ self._tied_weights_keys = self._build_tied_weights_keys(config)
58
+
59
+ config.qwen3_config.pad_token_id = config.pad_token_id
60
+ config.qwen3_config._attn_implementation = config.attn_implementation
61
+ local_gpt2_config = config.gpt2_config.to_dict()
62
+ local_gpt2_config["n_layer"] = int(getattr(config, "local_transformer_layers", config.gpt2_config.n_layer))
63
+ local_gpt2_config["n_positions"] = int(config.n_vq) + 1
64
+ local_gpt2_config["n_ctx"] = int(config.n_vq) + 1
65
+ local_gpt2_config = GPT2Config(**local_gpt2_config)
66
+ local_gpt2_config.pad_token_id = config.pad_token_id
67
+ local_gpt2_config._attn_implementation = config.local_transformer_attn_implementation
68
+
69
+ self.transformer = MossQwen3Model(config.qwen3_config)
70
+ self.local_transformer = MossTTSNanoGPT2Model(
71
+ local_gpt2_config,
72
+ attn_implementation=config.local_transformer_attn_implementation,
73
+ )
74
+ self.local_transformer.wte = nn.Identity()
75
+
76
+ hidden_size = int(config.hidden_size)
77
+ self.audio_embeddings = nn.ModuleList(
78
+ [
79
+ nn.Embedding(int(config.audio_codebook_sizes[index]), hidden_size)
80
+ for index in range(config.n_vq)
81
+ ]
82
+ )
83
+ self.text_lm_head = nn.Linear(hidden_size, int(config.vocab_size), bias=False)
84
+ self.audio_lm_heads = nn.ModuleList(
85
+ [
86
+ nn.Linear(hidden_size, int(config.audio_codebook_sizes[index]), bias=False)
87
+ for index in range(config.n_vq)
88
+ ]
89
+ )
90
+ self.local_text_lm_head = (
91
+ nn.Linear(hidden_size, 2, bias=False)
92
+ if self._use_binary_local_text_head()
93
+ else None
94
+ )
95
+
96
+ self.post_init()
97
+ self.tie_weights()
98
+ self.initialize_local_text_lm_head_from_text_lm_head()
99
+
100
+ def can_generate(self) -> bool:
101
+ return True
102
+
103
+ @staticmethod
104
+ def _build_tied_weights_keys(config: MossTTSLocalConfig) -> dict[str, str]:
105
+ tied_weights = {"text_lm_head.weight": "transformer.embed_tokens.weight"}
106
+ tied_weights.update(
107
+ {
108
+ f"audio_lm_heads.{index}.weight": f"audio_embeddings.{index}.weight"
109
+ for index in range(config.n_vq)
110
+ }
111
+ )
112
+ return tied_weights
113
+
114
+ def tie_weights(self, *args, **kwargs) -> None:
115
+ del args, kwargs
116
+ self.text_lm_head.weight = self.transformer.embed_tokens.weight
117
+ for embedding, head in zip(self.audio_embeddings, self.audio_lm_heads):
118
+ head.weight = embedding.weight
119
+
120
+ def get_input_embeddings(self) -> nn.Embedding:
121
+ return self.transformer.embed_tokens
122
+
123
+ def set_input_embeddings(self, value: nn.Embedding) -> None:
124
+ self.transformer.embed_tokens = value
125
+ self.tie_weights()
126
+ self.initialize_local_text_lm_head_from_text_lm_head()
127
+
128
+ def get_output_embeddings(self) -> nn.Linear:
129
+ return self.text_lm_head
130
+
131
+ def set_output_embeddings(self, new_embeddings: nn.Linear) -> None:
132
+ self.text_lm_head = new_embeddings
133
+ self.tie_weights()
134
+ self.initialize_local_text_lm_head_from_text_lm_head()
135
+
136
+ def _use_binary_local_text_head(self) -> bool:
137
+ return str(getattr(self.config, "local_text_head_mode", "full_vocab")).strip().lower() == "binary"
138
+
139
+ def _local_text_candidate_ids(self, device: torch.device) -> torch.LongTensor:
140
+ return torch.tensor(
141
+ [
142
+ int(self.config.audio_assistant_slot_token_id),
143
+ int(self.config.audio_end_token_id),
144
+ ],
145
+ dtype=torch.long,
146
+ device=device,
147
+ )
148
+
149
+ def initialize_local_text_lm_head_from_text_lm_head(self) -> None:
150
+ if not self._use_binary_local_text_head() or self.local_text_lm_head is None:
151
+ return
152
+ candidate_ids = self._local_text_candidate_ids(self.text_lm_head.weight.device)
153
+ with torch.no_grad():
154
+ source_weight = self.text_lm_head.weight.index_select(0, candidate_ids)
155
+ if tuple(source_weight.shape) == tuple(self.local_text_lm_head.weight.shape):
156
+ self.local_text_lm_head.weight.copy_(
157
+ source_weight.to(
158
+ device=self.local_text_lm_head.weight.device,
159
+ dtype=self.local_text_lm_head.weight.dtype,
160
+ )
161
+ )
162
+
163
+ def _resolve_fixed_nq(
164
+ self,
165
+ n_vq_for_inference: Optional[int] = None,
166
+ nq: Optional[int] = None,
167
+ ) -> int:
168
+ requested = n_vq_for_inference if n_vq_for_inference is not None else nq
169
+ config_nq = int(self.config.n_vq)
170
+ if requested is not None and int(requested) != config_nq:
171
+ raise ValueError(
172
+ "This MOSS-TTS-Local-Transformer-v1.5 release is trained with a fixed RVQ depth. "
173
+ f"Expected n_vq={config_nq}, got {int(requested)}."
174
+ )
175
+ return config_nq
176
+
177
+ def _build_inputs_embeds(self, input_ids: torch.LongTensor) -> torch.FloatTensor:
178
+ if input_ids.ndim != 3 or input_ids.shape[-1] != self.config.n_vq + 1:
179
+ raise ValueError(
180
+ f"Expected input_ids shape [batch, seq, {self.config.n_vq + 1}], "
181
+ f"got {tuple(input_ids.shape)}."
182
+ )
183
+ text_ids = input_ids[..., 0]
184
+ inputs_embeds = self.transformer.embed_tokens(text_ids)
185
+ for channel_index, embedding in enumerate(self.audio_embeddings):
186
+ channel_ids = input_ids[..., channel_index + 1]
187
+ valid_mask = channel_ids.ne(self.config.audio_pad_token_id)
188
+ safe_ids = channel_ids.masked_fill(~valid_mask, 0)
189
+ audio_embeds = embedding(safe_ids) * valid_mask.unsqueeze(-1)
190
+ inputs_embeds = inputs_embeds + audio_embeds
191
+ return inputs_embeds
192
+
193
+ def _global_hidden_to_local(self, hidden_states: torch.FloatTensor) -> torch.FloatTensor:
194
+ return hidden_states
195
+
196
+ @staticmethod
197
+ def _local_past_length(past_key_values: Optional[tuple[Any, ...]]) -> int:
198
+ if past_key_values is None or len(past_key_values) == 0:
199
+ return 0
200
+ first_layer_past = past_key_values[0]
201
+ if isinstance(first_layer_past, dict) and bool(first_layer_past.get("static_kv_cache", False)):
202
+ return int(first_layer_past.get("length", 0))
203
+ return int(first_layer_past[0].shape[1])
204
+
205
+ def _new_static_local_past_key_values(
206
+ self,
207
+ batch_size: int,
208
+ max_length: int,
209
+ device: torch.device,
210
+ dtype: torch.dtype,
211
+ ) -> tuple[dict[str, Any], ...]:
212
+ layers = []
213
+ for block in self.local_transformer.h:
214
+ attn = block.attn
215
+ cache_shape = (
216
+ int(batch_size),
217
+ int(max_length),
218
+ int(attn.num_heads),
219
+ int(attn.head_dim),
220
+ )
221
+ layers.append(
222
+ {
223
+ "static_kv_cache": True,
224
+ "key": torch.empty(cache_shape, device=device, dtype=dtype),
225
+ "value": torch.empty(cache_shape, device=device, dtype=dtype),
226
+ "length": 0,
227
+ }
228
+ )
229
+ return tuple(layers)
230
+
231
+ def _decode_local_hidden_states_with_cache(
232
+ self,
233
+ local_inputs_embeds: torch.FloatTensor,
234
+ past_key_values: Optional[tuple[Any, ...]] = None,
235
+ ) -> tuple[torch.FloatTensor, Optional[tuple[Any, ...]]]:
236
+ if (
237
+ past_key_values is None
238
+ and not self.training
239
+ and bool(getattr(self.config, "use_static_local_kv_cache", True))
240
+ ):
241
+ max_length = max(int(getattr(self.config, "n_vq", 0)) + 1, int(local_inputs_embeds.shape[1]))
242
+ past_key_values = self._new_static_local_past_key_values(
243
+ batch_size=int(local_inputs_embeds.shape[0]),
244
+ max_length=max_length,
245
+ device=local_inputs_embeds.device,
246
+ dtype=local_inputs_embeds.dtype,
247
+ )
248
+ past_length = self._local_past_length(past_key_values)
249
+ local_seq_len = int(local_inputs_embeds.shape[1])
250
+ local_position_ids = torch.arange(
251
+ past_length,
252
+ past_length + local_seq_len,
253
+ device=local_inputs_embeds.device,
254
+ dtype=torch.long,
255
+ ).unsqueeze(0)
256
+ if int(local_inputs_embeds.shape[0]) != 1:
257
+ local_position_ids = local_position_ids.expand(int(local_inputs_embeds.shape[0]), -1)
258
+ local_outputs = self.local_transformer(
259
+ input_ids=None,
260
+ past_key_values=past_key_values,
261
+ attention_mask=None,
262
+ position_ids=local_position_ids,
263
+ inputs_embeds=local_inputs_embeds,
264
+ use_cache=True,
265
+ output_attentions=False,
266
+ output_hidden_states=False,
267
+ return_dict=True,
268
+ cu_seqlens=None,
269
+ num_sequences=None,
270
+ )
271
+ return local_outputs.last_hidden_state, local_outputs.past_key_values
272
+
273
+ def forward(
274
+ self,
275
+ input_ids: Optional[torch.LongTensor] = None,
276
+ attention_mask: Optional[torch.Tensor] = None,
277
+ position_ids: Optional[torch.LongTensor] = None,
278
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None,
279
+ inputs_embeds: Optional[torch.FloatTensor] = None,
280
+ use_cache: Optional[bool] = None,
281
+ output_attentions: Optional[bool] = None,
282
+ output_hidden_states: Optional[bool] = None,
283
+ return_dict: Optional[bool] = True,
284
+ **kwargs,
285
+ ) -> Union[tuple, MossTTSLocalOutput]:
286
+ del kwargs
287
+ if inputs_embeds is None:
288
+ if input_ids is None:
289
+ raise ValueError("Either input_ids or inputs_embeds must be provided.")
290
+ inputs_embeds = self._build_inputs_embeds(input_ids)
291
+ outputs = self.transformer(
292
+ input_ids=None,
293
+ attention_mask=attention_mask,
294
+ position_ids=position_ids,
295
+ past_key_values=past_key_values,
296
+ inputs_embeds=inputs_embeds,
297
+ use_cache=use_cache,
298
+ output_attentions=output_attentions,
299
+ output_hidden_states=output_hidden_states,
300
+ return_dict=True,
301
+ cu_seqlens=None,
302
+ num_sequences=None,
303
+ )
304
+ if not return_dict:
305
+ return (
306
+ outputs.last_hidden_state,
307
+ outputs.past_key_values,
308
+ outputs.hidden_states,
309
+ outputs.attentions,
310
+ )
311
+ return MossTTSLocalOutput(
312
+ last_hidden_state=outputs.last_hidden_state,
313
+ past_key_values=outputs.past_key_values,
314
+ hidden_states=outputs.hidden_states,
315
+ attentions=outputs.attentions,
316
+ )
317
+
318
+ def _decode_local_last_hidden_state(
319
+ self,
320
+ local_inputs_embeds: torch.FloatTensor,
321
+ ) -> torch.FloatTensor:
322
+ local_seq_len = int(local_inputs_embeds.shape[1])
323
+ local_position_ids = torch.arange(
324
+ 0,
325
+ local_seq_len,
326
+ device=local_inputs_embeds.device,
327
+ dtype=torch.long,
328
+ ).unsqueeze(0)
329
+ if int(local_inputs_embeds.shape[0]) != 1:
330
+ local_position_ids = local_position_ids.expand(int(local_inputs_embeds.shape[0]), -1)
331
+ local_outputs = self.local_transformer(
332
+ input_ids=None,
333
+ attention_mask=None,
334
+ position_ids=local_position_ids,
335
+ inputs_embeds=local_inputs_embeds,
336
+ use_cache=False,
337
+ output_attentions=False,
338
+ output_hidden_states=False,
339
+ return_dict=True,
340
+ cu_seqlens=None,
341
+ num_sequences=None,
342
+ )
343
+ return local_outputs.last_hidden_state[:, -1, :]
344
+
345
+ def _filter_logits(
346
+ self,
347
+ logits: torch.FloatTensor,
348
+ top_k: Optional[int],
349
+ top_p: Optional[float],
350
+ ) -> torch.FloatTensor:
351
+ scores = logits
352
+ if top_k is not None and int(top_k) > 0 and int(top_k) < scores.shape[-1]:
353
+ kth = torch.topk(scores, int(top_k), dim=-1).values[..., -1, None]
354
+ scores = scores.masked_fill(scores < kth, -torch.inf)
355
+ if top_p is not None and 0.0 < float(top_p) < 1.0:
356
+ sorted_scores, sorted_indices = torch.sort(scores, descending=True, dim=-1)
357
+ sorted_probs = torch.softmax(sorted_scores, dim=-1)
358
+ cumulative_probs = sorted_probs.cumsum(dim=-1)
359
+ sorted_mask = cumulative_probs > float(top_p)
360
+ sorted_mask[..., 1:] = sorted_mask[..., :-1].clone()
361
+ sorted_mask[..., 0] = False
362
+ remove_mask = torch.zeros_like(scores, dtype=torch.bool)
363
+ remove_mask.scatter_(dim=-1, index=sorted_indices, src=sorted_mask)
364
+ scores = scores.masked_fill(remove_mask, -torch.inf)
365
+ return scores
366
+
367
+ def _apply_repetition_penalty(
368
+ self,
369
+ scores: torch.FloatTensor,
370
+ previous_token_ids: Optional[torch.LongTensor],
371
+ penalty: float,
372
+ ) -> torch.FloatTensor:
373
+ if previous_token_ids is None or float(penalty) == 1.0:
374
+ return scores
375
+ if previous_token_ids.ndim == 1:
376
+ previous_token_ids = previous_token_ids.unsqueeze(0)
377
+ updated = scores.clone()
378
+ for batch_index in range(updated.shape[0]):
379
+ unique_token_ids = torch.unique(previous_token_ids[batch_index])
380
+ unique_token_ids = unique_token_ids[
381
+ (unique_token_ids >= 0) & (unique_token_ids < updated.shape[-1])
382
+ ]
383
+ if unique_token_ids.numel() == 0:
384
+ continue
385
+ token_scores = updated[batch_index].index_select(0, unique_token_ids)
386
+ token_scores = torch.where(
387
+ token_scores < 0,
388
+ token_scores * float(penalty),
389
+ token_scores / float(penalty),
390
+ )
391
+ updated[batch_index].scatter_(0, unique_token_ids, token_scores)
392
+ return updated
393
+
394
+ def _sample_next_token(
395
+ self,
396
+ logits: torch.FloatTensor,
397
+ do_sample: bool,
398
+ temperature: float,
399
+ top_k: Optional[int],
400
+ top_p: Optional[float],
401
+ previous_token_ids: Optional[torch.LongTensor] = None,
402
+ repetition_penalty: float = 1.0,
403
+ ) -> torch.LongTensor:
404
+ scores = logits.float()
405
+ scores = self._apply_repetition_penalty(scores, previous_token_ids, repetition_penalty)
406
+ if not do_sample:
407
+ return torch.argmax(scores, dim=-1)
408
+ if float(temperature) <= 0:
409
+ raise ValueError("temperature must be positive when do_sample=True.")
410
+ scores = scores / float(temperature)
411
+ scores = self._filter_logits(scores, top_k=top_k, top_p=top_p)
412
+ probs = torch.softmax(scores, dim=-1)
413
+ return torch.multinomial(probs, num_samples=1).squeeze(-1)
414
+
415
+ def _sample_next_assistant_text_token(
416
+ self,
417
+ local_hidden_states: torch.FloatTensor,
418
+ do_sample: bool,
419
+ temperature: float,
420
+ top_k: Optional[int],
421
+ top_p: Optional[float],
422
+ ) -> torch.LongTensor:
423
+ if self._use_binary_local_text_head() and self.local_text_lm_head is not None:
424
+ logits = self.local_text_lm_head(local_hidden_states)
425
+ sampled_indices = self._sample_next_token(
426
+ logits=logits,
427
+ do_sample=do_sample,
428
+ temperature=temperature,
429
+ top_k=top_k,
430
+ top_p=top_p,
431
+ )
432
+ candidate_ids = self._local_text_candidate_ids(logits.device)
433
+ return candidate_ids[sampled_indices]
434
+
435
+ candidate_ids = self._local_text_candidate_ids(local_hidden_states.device)
436
+ logits = self.text_lm_head(local_hidden_states).index_select(dim=-1, index=candidate_ids)
437
+ sampled_indices = self._sample_next_token(
438
+ logits=logits,
439
+ do_sample=do_sample,
440
+ temperature=temperature,
441
+ top_k=top_k,
442
+ top_p=top_p,
443
+ )
444
+ return candidate_ids[sampled_indices]
445
+
446
+ def _build_generation_row(
447
+ self,
448
+ batch_size: int,
449
+ device: torch.device,
450
+ audio_token_ids: torch.LongTensor,
451
+ ) -> torch.LongTensor:
452
+ row = torch.full(
453
+ (batch_size, 1, self.config.n_vq + 1),
454
+ int(self.config.audio_pad_token_id),
455
+ dtype=torch.long,
456
+ device=device,
457
+ )
458
+ row[:, :, 0] = int(self.config.audio_assistant_slot_token_id)
459
+ row[:, :, 1:] = audio_token_ids.unsqueeze(1)
460
+ return row
461
+
462
+ @torch.inference_mode()
463
+ def generate(
464
+ self,
465
+ input_ids: torch.LongTensor,
466
+ attention_mask: Optional[torch.Tensor] = None,
467
+ max_new_tokens: Optional[int] = None,
468
+ max_new_frames: Optional[int] = None,
469
+ do_sample: bool = True,
470
+ text_temperature: float = 1.0,
471
+ text_top_p: float = 1.0,
472
+ text_top_k: int = 50,
473
+ audio_temperature: Optional[float] = None,
474
+ audio_top_p: Optional[float] = None,
475
+ audio_top_k: Optional[int] = None,
476
+ audio_repetition_penalty: Optional[float] = None,
477
+ temperature: float = 1.0,
478
+ top_p: float = 0.95,
479
+ top_k: int = 50,
480
+ repetition_penalty: float = 1.0,
481
+ use_kv_cache: bool = True,
482
+ n_vq_for_inference: Optional[int] = None,
483
+ nq: Optional[int] = None,
484
+ **kwargs,
485
+ ) -> list[tuple[int, torch.LongTensor]]:
486
+ del kwargs
487
+ self._resolve_fixed_nq(n_vq_for_inference=n_vq_for_inference, nq=nq)
488
+
489
+ if input_ids.ndim == 2:
490
+ input_ids = input_ids.unsqueeze(0)
491
+ if input_ids.ndim != 3:
492
+ raise ValueError(f"Expected input_ids with 3 dims, got {tuple(input_ids.shape)}.")
493
+ if input_ids.shape[-1] != self.config.n_vq + 1:
494
+ raise ValueError(
495
+ f"Expected {self.config.n_vq + 1} channels from config.n_vq, got {input_ids.shape[-1]}."
496
+ )
497
+ if attention_mask is None:
498
+ attention_mask = torch.ones(input_ids.shape[:2], dtype=torch.bool, device=input_ids.device)
499
+ elif attention_mask.ndim == 1:
500
+ attention_mask = attention_mask.unsqueeze(0)
501
+ attention_mask = attention_mask.to(device=input_ids.device, dtype=torch.bool)
502
+
503
+ frame_budget = max_new_frames if max_new_frames is not None else max_new_tokens
504
+ if frame_budget is None:
505
+ frame_budget = 4096
506
+ frame_budget = int(frame_budget)
507
+
508
+ audio_temperature = float(temperature if audio_temperature is None else audio_temperature)
509
+ audio_top_p = float(top_p if audio_top_p is None else audio_top_p)
510
+ audio_top_k = int(top_k if audio_top_k is None else audio_top_k)
511
+ audio_repetition_penalty = float(
512
+ repetition_penalty if audio_repetition_penalty is None else audio_repetition_penalty
513
+ )
514
+
515
+ batch_size = input_ids.shape[0]
516
+ input_ids_length = input_ids.shape[1]
517
+ current_input_ids = input_ids
518
+ current_attention_mask = attention_mask
519
+ current_model_input_ids = current_input_ids
520
+ generated_frames: list[torch.LongTensor] = []
521
+ finished = torch.zeros(batch_size, dtype=torch.bool, device=input_ids.device)
522
+ past_key_values = None
523
+ local_dtype = self.local_transformer.ln_f.weight.dtype
524
+
525
+ for _ in range(frame_budget):
526
+ generated_audio_history = torch.stack(generated_frames, dim=1) if generated_frames else None
527
+ global_inputs_embeds = self._build_inputs_embeds(current_model_input_ids)
528
+ global_outputs = self.transformer(
529
+ input_ids=None,
530
+ past_key_values=past_key_values,
531
+ attention_mask=current_attention_mask,
532
+ position_ids=None,
533
+ inputs_embeds=global_inputs_embeds,
534
+ use_cache=use_kv_cache,
535
+ output_attentions=False,
536
+ output_hidden_states=False,
537
+ return_dict=True,
538
+ cu_seqlens=None,
539
+ num_sequences=None,
540
+ )
541
+ global_hidden_states = global_outputs.last_hidden_state[:, -1, :]
542
+ local_global_hidden_states = self._global_hidden_to_local(global_hidden_states).to(dtype=local_dtype)
543
+
544
+ local_prefix_hidden_states, local_prefix_past_key_values = self._decode_local_hidden_states_with_cache(
545
+ local_global_hidden_states.unsqueeze(1)
546
+ )
547
+ local_hidden_states = local_prefix_hidden_states[:, -1, :]
548
+ next_text_tokens = self._sample_next_assistant_text_token(
549
+ local_hidden_states=local_hidden_states,
550
+ do_sample=do_sample,
551
+ temperature=text_temperature,
552
+ top_k=text_top_k,
553
+ top_p=text_top_p,
554
+ )
555
+ should_continue = next_text_tokens.eq(int(self.config.audio_assistant_slot_token_id)) & ~finished
556
+ finished = finished | next_text_tokens.eq(int(self.config.audio_end_token_id))
557
+ if not bool(should_continue.any().item()):
558
+ break
559
+
560
+ next_frame_tokens = []
561
+ for channel_index in range(int(self.config.n_vq)):
562
+ channel_logits = self.audio_lm_heads[channel_index](local_hidden_states)
563
+ channel_token = self._sample_next_token(
564
+ logits=channel_logits,
565
+ do_sample=do_sample,
566
+ temperature=audio_temperature,
567
+ top_k=audio_top_k,
568
+ top_p=audio_top_p,
569
+ previous_token_ids=(
570
+ None
571
+ if generated_audio_history is None
572
+ else generated_audio_history[:, :, channel_index]
573
+ ),
574
+ repetition_penalty=audio_repetition_penalty,
575
+ )
576
+ next_frame_tokens.append(channel_token)
577
+ if channel_index + 1 < int(self.config.n_vq):
578
+ current_local_input = self.audio_embeddings[channel_index](channel_token).to(dtype=local_dtype)
579
+ local_token_hidden_states, local_prefix_past_key_values = (
580
+ self._decode_local_hidden_states_with_cache(
581
+ current_local_input.unsqueeze(1),
582
+ past_key_values=local_prefix_past_key_values,
583
+ )
584
+ )
585
+ local_hidden_states = local_token_hidden_states[:, -1, :]
586
+
587
+ next_frame = torch.stack(next_frame_tokens, dim=-1)
588
+ next_frame = next_frame.masked_fill(
589
+ ~should_continue.unsqueeze(-1),
590
+ int(self.config.audio_pad_token_id),
591
+ )
592
+ generated_frames.append(next_frame)
593
+
594
+ next_row = self._build_generation_row(
595
+ batch_size=batch_size,
596
+ device=input_ids.device,
597
+ audio_token_ids=next_frame,
598
+ )
599
+ if bool((~should_continue).any().item()):
600
+ next_row[~should_continue, 0, 0] = int(self.config.pad_token_id)
601
+ next_row[~should_continue, 0, 1:] = int(self.config.audio_pad_token_id)
602
+
603
+ current_input_ids = torch.cat([current_input_ids, next_row], dim=1)
604
+ current_attention_mask = torch.cat(
605
+ [current_attention_mask, should_continue.unsqueeze(1)],
606
+ dim=1,
607
+ )
608
+ if use_kv_cache:
609
+ current_model_input_ids = next_row
610
+ past_key_values = global_outputs.past_key_values
611
+ else:
612
+ current_model_input_ids = current_input_ids
613
+
614
+ start_indices = _find_last_equal(input_ids[..., 0], int(self.config.audio_start_token_id))
615
+ start_lengths = input_ids_length - start_indices - 1
616
+ outputs: list[tuple[int, torch.LongTensor]] = []
617
+ for start_index, start_length, generation_ids in zip(
618
+ start_indices.tolist(),
619
+ start_lengths.tolist(),
620
+ current_input_ids,
621
+ ):
622
+ outputs.append((int(start_length), generation_ids[int(start_index):].detach().cpu()))
623
+ return outputs
assets/sft3/processing_moss_tts.py ADDED
@@ -0,0 +1,914 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ """Processor for the MOSS-TTS-Local-Transformer-v1.5 HuggingFace release."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import re
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, cast
11
+
12
+ import torch
13
+ import torchaudio
14
+ from transformers import (
15
+ AutoConfig,
16
+ AutoModel,
17
+ AutoTokenizer,
18
+ BatchFeature,
19
+ PreTrainedTokenizerBase,
20
+ ProcessorMixin,
21
+ logging,
22
+ processing_utils,
23
+ )
24
+
25
+ from .configuration_moss_tts import MossTTSLocalConfig
26
+
27
+
28
+ if hasattr(processing_utils, "MODALITY_TO_BASE_CLASS_MAPPING"):
29
+ processing_utils.MODALITY_TO_BASE_CLASS_MAPPING["audio_tokenizer"] = "PreTrainedModel"
30
+ else:
31
+ processing_utils.AUTO_TO_BASE_CLASS_MAPPING["AutoModel"] = "PreTrainedModel"
32
+ logger = logging.get_logger(__name__)
33
+
34
+ AUDIO_PLACEHOLDER = "<|audio|>"
35
+ USER_ROLE_PREFIX = "user\n"
36
+ USER_TEMPLATE_REFERENCE_PREFIX = (
37
+ "<user_inst>\n"
38
+ "- Reference(s):\n"
39
+ )
40
+ USER_TEMPLATE_AFTER_REFERENCE_SUFFIX = (
41
+ "\n"
42
+ "- Text:\n"
43
+ )
44
+ USER_TEMPLATE_SUFFIX = "\n</user_inst>"
45
+ ASSISTANT_TURN_PREFIX = "\n"
46
+ ASSISTANT_ROLE_PREFIX = "assistant\n"
47
+ USER_MESSAGE_FIELDS = (
48
+ "text",
49
+ "reference",
50
+ "instruction",
51
+ "tokens",
52
+ "quality",
53
+ "sound_event",
54
+ "ambient_sound",
55
+ "language",
56
+ )
57
+
58
+
59
+ def _normalize_template_value(value: Any) -> str:
60
+ if value is None:
61
+ return "None"
62
+ resolved = str(value).strip()
63
+ return resolved or "None"
64
+
65
+
66
+ def _render_user_prompt_after_reference(
67
+ language_code: object | None = None,
68
+ prompt_fields: Optional[Dict[str, Any]] = None,
69
+ ) -> str:
70
+ fields = dict(prompt_fields or {})
71
+ return (
72
+ "\n- Instruction:\n"
73
+ + _normalize_template_value(fields.get("instruction"))
74
+ + "\n- Tokens:\n"
75
+ + _normalize_template_value(fields.get("tokens"))
76
+ + "\n- Quality:\n"
77
+ + _normalize_template_value(fields.get("quality"))
78
+ + "\n- Sound Event:\n"
79
+ + _normalize_template_value(fields.get("sound_event"))
80
+ + "\n- Ambient Sound:\n"
81
+ + _normalize_template_value(fields.get("ambient_sound"))
82
+ + "\n- Language:\n"
83
+ + _normalize_template_value(fields.get("language", language_code))
84
+ + USER_TEMPLATE_AFTER_REFERENCE_SUFFIX
85
+ )
86
+
87
+
88
+ @dataclass
89
+ class Message:
90
+ def to_dict(self) -> Dict[str, Any]:
91
+ raise NotImplementedError
92
+
93
+
94
+ @dataclass
95
+ class UserMessage(Message):
96
+ text: Optional[str] = None
97
+ reference: Optional[List[Optional[Union[str, os.PathLike, torch.Tensor]]]] = None
98
+ instruction: Optional[str] = None
99
+ tokens: Optional[int] = None
100
+ quality: Optional[str] = None
101
+ sound_event: Optional[str] = None
102
+ ambient_sound: Optional[str] = None
103
+ language: Optional[str] = None
104
+
105
+ def __post_init__(self) -> None:
106
+ template = """<user_inst>
107
+ - Reference(s):
108
+ {reference}
109
+ - Instruction:
110
+ {instruction}
111
+ - Tokens:
112
+ {tokens}
113
+ - Quality:
114
+ {quality}
115
+ - Sound Event:
116
+ {sound_event}
117
+ - Ambient Sound:
118
+ {ambient_sound}
119
+ - Language:
120
+ {language}
121
+ - Text:
122
+ {text}
123
+ </user_inst>"""
124
+
125
+ audio_codes_list: list[Union[str, os.PathLike, torch.Tensor]] = []
126
+ if self.reference is None:
127
+ reference = "None"
128
+ else:
129
+ reference_items: list[str] = []
130
+ for speaker_idx, speaker_reference in enumerate(self.reference):
131
+ if speaker_reference is None:
132
+ continue
133
+ # Keep raw audio placeholders directly under "- Reference(s):".
134
+ # Speaker labels such as "[S1]:" change the token sequence and
135
+ # can affect voice-clone conditioning.
136
+ reference_items.append(AUDIO_PLACEHOLDER)
137
+ audio_codes_list.append(speaker_reference)
138
+ reference = "\n".join(reference_items) if reference_items else "None"
139
+
140
+ self._content = (
141
+ template.replace("{reference}", str(reference))
142
+ .replace("{instruction}", str(self.instruction))
143
+ .replace("{tokens}", str(self.tokens))
144
+ .replace("{quality}", str(self.quality))
145
+ .replace("{sound_event}", str(self.sound_event))
146
+ .replace("{ambient_sound}", str(self.ambient_sound))
147
+ .replace("{language}", str(self.language))
148
+ .replace("{text}", str(self.text))
149
+ )
150
+ self._audio_codes_list = audio_codes_list
151
+
152
+ def to_dict(self) -> Dict[str, Any]:
153
+ return {
154
+ "role": "user",
155
+ "content": self._content,
156
+ "audio_codes_list": self._audio_codes_list,
157
+ "text": self.text,
158
+ "instruction": self.instruction,
159
+ "tokens": self.tokens,
160
+ "quality": self.quality,
161
+ "sound_event": self.sound_event,
162
+ "ambient_sound": self.ambient_sound,
163
+ "language": self.language,
164
+ }
165
+
166
+
167
+ @dataclass
168
+ class AssistantMessage(Message):
169
+ audio_codes_list: List[Union[str, os.PathLike, torch.Tensor]]
170
+ content: str = AUDIO_PLACEHOLDER
171
+
172
+ def to_dict(self) -> Dict[str, Any]:
173
+ return {
174
+ "role": "assistant",
175
+ "content": self.content,
176
+ "audio_codes_list": self.audio_codes_list,
177
+ }
178
+
179
+
180
+ class MossTTSLocalProcessor(ProcessorMixin):
181
+ attributes = ["tokenizer"]
182
+ tokenizer_class = "AutoTokenizer"
183
+ audio_tokenizer_class = "AutoModel"
184
+
185
+ tokenizer: PreTrainedTokenizerBase
186
+ audio_tokenizer: Any
187
+
188
+ def __init__(
189
+ self,
190
+ tokenizer: PreTrainedTokenizerBase,
191
+ audio_tokenizer: Any = None,
192
+ model_config: Optional[MossTTSLocalConfig] = None,
193
+ **kwargs,
194
+ ) -> None:
195
+ super().__init__(tokenizer=tokenizer, audio_tokenizer=audio_tokenizer, **kwargs)
196
+ self.tokenizer = tokenizer
197
+ self.audio_tokenizer = audio_tokenizer
198
+ self.model_config = model_config or MossTTSLocalConfig()
199
+
200
+ def _id_to_token(token_id: int) -> str:
201
+ token = tokenizer.convert_ids_to_tokens(int(token_id))
202
+ if isinstance(token, list):
203
+ return token[0] if token else ""
204
+ return cast(str, token)
205
+
206
+ self.audio_user_slot_token = _id_to_token(self.model_config.audio_user_slot_token_id)
207
+ self.audio_assistant_slot_token = _id_to_token(self.model_config.audio_assistant_slot_token_id)
208
+ self.audio_start_token = _id_to_token(self.model_config.audio_start_token_id)
209
+ self.audio_end_token = _id_to_token(self.model_config.audio_end_token_id)
210
+
211
+ @classmethod
212
+ def from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs):
213
+ trust_remote_code = kwargs.pop("trust_remote_code", True)
214
+ kwargs.pop("_from_auto", None)
215
+ codec_path = kwargs.pop("codec_path", None)
216
+ codec_weight_dtype = kwargs.pop("codec_weight_dtype", "fp32")
217
+ codec_compute_dtype = kwargs.pop("codec_compute_dtype", None)
218
+ codec_attention_implementation = kwargs.pop("codec_attention_implementation", None)
219
+
220
+ codec_kwargs: Dict[str, Any] = {}
221
+ if codec_weight_dtype is not None:
222
+ # Default to fp32 codec weights; callers can pass codec_weight_dtype="bf16" to reduce memory.
223
+ codec_kwargs["codec_weight_dtype"] = codec_weight_dtype
224
+
225
+ model_ref = Path(str(pretrained_model_name_or_path))
226
+ model_ref_or_name = model_ref if model_ref.exists() else pretrained_model_name_or_path
227
+ model_config = cast(
228
+ MossTTSLocalConfig,
229
+ AutoConfig.from_pretrained(
230
+ model_ref_or_name,
231
+ *args,
232
+ trust_remote_code=trust_remote_code,
233
+ **kwargs,
234
+ ),
235
+ )
236
+
237
+ if codec_path is None:
238
+ try:
239
+ processor_dict, _ = cls.get_processor_dict(
240
+ pretrained_model_name_or_path,
241
+ **dict(kwargs),
242
+ )
243
+ codec_path = processor_dict.get("audio_tokenizer_name_or_path")
244
+ audio_tokenizer_dict = processor_dict.get("audio_tokenizer", {})
245
+ if isinstance(audio_tokenizer_dict, dict):
246
+ codec_path = audio_tokenizer_dict.get("audio_tokenizer_name_or_path") or codec_path
247
+ except Exception:
248
+ codec_path = None
249
+ if codec_path is None:
250
+ codec_path = getattr(model_config, "audio_tokenizer_name_or_path", None)
251
+ if codec_path is None:
252
+ codec_path = "OpenMOSS-Team/MOSS-Audio-Tokenizer-v2"
253
+
254
+ tokenizer = AutoTokenizer.from_pretrained(
255
+ model_ref_or_name,
256
+ *args,
257
+ trust_remote_code=trust_remote_code,
258
+ **kwargs,
259
+ )
260
+ audio_tokenizer = AutoModel.from_pretrained(
261
+ codec_path,
262
+ trust_remote_code=trust_remote_code,
263
+ **kwargs,
264
+ **codec_kwargs,
265
+ )
266
+ if codec_compute_dtype is not None and hasattr(audio_tokenizer, "set_compute_dtype"):
267
+ audio_tokenizer.set_compute_dtype(codec_compute_dtype)
268
+ if codec_attention_implementation is not None and hasattr(
269
+ audio_tokenizer, "set_attention_implementation"
270
+ ):
271
+ audio_tokenizer.set_attention_implementation(codec_attention_implementation)
272
+ return cls(
273
+ tokenizer=tokenizer,
274
+ audio_tokenizer=audio_tokenizer,
275
+ model_config=model_config,
276
+ **kwargs,
277
+ )
278
+
279
+ @staticmethod
280
+ def build_user_message(
281
+ text: Optional[str] = None,
282
+ reference: Optional[List[Optional[Union[str, os.PathLike, torch.Tensor]]]] = None,
283
+ instruction: Optional[str] = None,
284
+ tokens: Optional[int] = None,
285
+ quality: Optional[str] = None,
286
+ sound_event: Optional[str] = None,
287
+ ambient_sound: Optional[str] = None,
288
+ language: Optional[str] = None,
289
+ ) -> Dict[str, Any]:
290
+ if reference is not None and not isinstance(reference, list):
291
+ reference = [reference]
292
+ return UserMessage(
293
+ text=text,
294
+ reference=reference,
295
+ instruction=instruction,
296
+ tokens=tokens,
297
+ quality=quality,
298
+ sound_event=sound_event,
299
+ ambient_sound=ambient_sound,
300
+ language=language,
301
+ ).to_dict()
302
+
303
+ @staticmethod
304
+ def build_assistant_message(
305
+ audio_codes_list: List[Union[str, os.PathLike, torch.Tensor]],
306
+ content: str = AUDIO_PLACEHOLDER,
307
+ ) -> Dict[str, Any]:
308
+ return AssistantMessage(audio_codes_list=audio_codes_list, content=content).to_dict()
309
+
310
+ def _assert_fixed_nq(self, n_vq: Optional[int]) -> int:
311
+ config_nq = int(self.model_config.n_vq)
312
+ if n_vq is not None and int(n_vq) != config_nq:
313
+ raise ValueError(
314
+ "This MOSS-TTS-Local-Transformer-v1.5 release uses the RVQ depth stored in the model config. "
315
+ f"Expected n_vq={config_nq}, got {int(n_vq)}."
316
+ )
317
+ return config_nq
318
+
319
+ def _encode_text(self, text: str) -> list[int]:
320
+ try:
321
+ return list(self.tokenizer.encode(text, add_special_tokens=False))
322
+ except TypeError:
323
+ return list(self.tokenizer.encode(text))
324
+
325
+ def _build_text_rows(self, token_ids: Sequence[int], *, device: Optional[torch.device] = None) -> torch.Tensor:
326
+ rows = torch.full(
327
+ (len(token_ids), int(self.model_config.n_vq) + 1),
328
+ int(self.model_config.audio_pad_token_id),
329
+ dtype=torch.long,
330
+ device=device,
331
+ )
332
+ if token_ids:
333
+ rows[:, 0] = torch.tensor([int(token_id) for token_id in token_ids], dtype=torch.long, device=rows.device)
334
+ return rows
335
+
336
+ def _build_audio_rows(self, audio_tokens: torch.Tensor, slot_token_id: int) -> torch.Tensor:
337
+ rows = torch.full(
338
+ (int(audio_tokens.shape[0]), int(self.model_config.n_vq) + 1),
339
+ int(self.model_config.audio_pad_token_id),
340
+ dtype=torch.long,
341
+ device=audio_tokens.device,
342
+ )
343
+ if rows.shape[0] > 0:
344
+ rows[:, 0] = int(slot_token_id)
345
+ rows[:, 1:] = audio_tokens.to(dtype=torch.long)
346
+ return rows
347
+
348
+ def _user_prompt_prefix_ids(self) -> list[int]:
349
+ return (
350
+ [int(self.model_config.im_start_token_id)]
351
+ + self._encode_text(USER_ROLE_PREFIX)
352
+ + self._encode_text(USER_TEMPLATE_REFERENCE_PREFIX)
353
+ )
354
+
355
+ def _user_prompt_after_reference_ids(
356
+ self,
357
+ language_code: object | None,
358
+ prompt_fields: Optional[Dict[str, Any]],
359
+ ) -> list[int]:
360
+ return self._encode_text(
361
+ _render_user_prompt_after_reference(
362
+ language_code=language_code,
363
+ prompt_fields=prompt_fields,
364
+ )
365
+ )
366
+
367
+ def _assistant_prompt_prefix_ids(self) -> list[int]:
368
+ return (
369
+ self._encode_text(USER_TEMPLATE_SUFFIX)
370
+ + [int(self.model_config.im_end_token_id)]
371
+ + self._encode_text(ASSISTANT_TURN_PREFIX)
372
+ + [int(self.model_config.im_start_token_id)]
373
+ + self._encode_text(ASSISTANT_ROLE_PREFIX)
374
+ )
375
+
376
+ def _prompt_fields_from_user_message(self, message: Dict[str, Any]) -> dict[str, Any]:
377
+ fields = {}
378
+ for key in ("instruction", "tokens", "quality", "sound_event", "ambient_sound"):
379
+ if key in message and message.get(key) is not None:
380
+ fields[key] = message.get(key)
381
+ if "language" in message and message.get("language") is not None:
382
+ fields["language"] = message.get("language")
383
+ return fields
384
+
385
+ def _build_generation_or_voice_clone_codes(
386
+ self,
387
+ message: Dict[str, Any],
388
+ n_vq: int,
389
+ ) -> torch.Tensor:
390
+ if "text" not in message:
391
+ raise ValueError("Direct MOSS-TTS-Local-Transformer-v1.5 generation requires messages built by build_user_message(...).")
392
+ text = "" if message.get("text") is None else str(message.get("text"))
393
+ prompt_fields = self._prompt_fields_from_user_message(message)
394
+ language_code = message.get("language")
395
+ audio_codes_list = self._resolve_audio_items(message.get("audio_codes_list", []), n_vq)
396
+ text_token_ids = self._encode_text(text)
397
+
398
+ if audio_codes_list:
399
+ parts: list[torch.Tensor] = [self._build_text_rows(
400
+ self._user_prompt_prefix_ids(),
401
+ device=audio_codes_list[0].device,
402
+ )]
403
+ for reference_codes in audio_codes_list:
404
+ parts.append(self._build_text_rows([int(self.model_config.audio_start_token_id)], device=reference_codes.device))
405
+ parts.append(self._build_audio_rows(reference_codes, int(self.model_config.audio_user_slot_token_id)))
406
+ parts.append(self._build_text_rows([int(self.model_config.audio_end_token_id)], device=reference_codes.device))
407
+ parts.append(
408
+ self._build_text_rows(
409
+ self._user_prompt_after_reference_ids(language_code, prompt_fields)
410
+ + text_token_ids
411
+ + self._assistant_prompt_prefix_ids()
412
+ + [int(self.model_config.audio_start_token_id)],
413
+ device=audio_codes_list[0].device,
414
+ )
415
+ )
416
+ return torch.cat(parts, dim=0)
417
+
418
+ prompt_token_ids = (
419
+ self._user_prompt_prefix_ids()
420
+ + self._encode_text("None")
421
+ + self._user_prompt_after_reference_ids(language_code, prompt_fields)
422
+ + text_token_ids
423
+ + self._assistant_prompt_prefix_ids()
424
+ + [int(self.model_config.audio_start_token_id)]
425
+ )
426
+ return self._build_text_rows(prompt_token_ids)
427
+
428
+ def _build_continuation_codes(
429
+ self,
430
+ conversation: list[Dict[str, Any]],
431
+ n_vq: int,
432
+ ) -> torch.Tensor:
433
+ if len(conversation) < 2:
434
+ raise ValueError("continuation mode requires a user message followed by an assistant audio message.")
435
+ user_message = conversation[-2]
436
+ assistant_message = conversation[-1]
437
+ if user_message.get("role") != "user" or assistant_message.get("role") != "assistant":
438
+ raise ValueError("continuation mode requires the last two messages to be user, assistant.")
439
+ if "text" not in user_message:
440
+ raise ValueError("Direct MOSS-TTS-Local-Transformer-v1.5 continuation requires user messages built by build_user_message(...).")
441
+
442
+ text = "" if user_message.get("text") is None else str(user_message.get("text"))
443
+ prompt_fields = self._prompt_fields_from_user_message(user_message)
444
+ language_code = user_message.get("language")
445
+ prompt_token_ids = (
446
+ self._user_prompt_prefix_ids()
447
+ + self._encode_text("None")
448
+ + self._user_prompt_after_reference_ids(language_code, prompt_fields)
449
+ + self._encode_text(text)
450
+ + self._assistant_prompt_prefix_ids()
451
+ + [int(self.model_config.audio_start_token_id)]
452
+ )
453
+ audio_codes_list = self._resolve_audio_items(assistant_message.get("audio_codes_list", []), n_vq)
454
+ if not audio_codes_list:
455
+ return self._build_text_rows(prompt_token_ids)
456
+ if len(audio_codes_list) != 1:
457
+ raise ValueError("The MOSS-TTS-Local-Transformer-v1.5 continuation path expects exactly one prompt audio item.")
458
+ prompt_audio_codes = audio_codes_list[0]
459
+ return torch.cat(
460
+ [
461
+ self._build_text_rows(prompt_token_ids, device=prompt_audio_codes.device),
462
+ self._build_audio_rows(prompt_audio_codes, int(self.model_config.audio_assistant_slot_token_id)),
463
+ ],
464
+ dim=0,
465
+ )
466
+
467
+ def _try_build_direct_codes(
468
+ self,
469
+ conversation: list[Dict[str, Any]],
470
+ mode: str,
471
+ n_vq: int,
472
+ ) -> Optional[torch.Tensor]:
473
+ if mode == "generation" and len(conversation) == 1 and conversation[-1].get("role") == "user":
474
+ if "text" in conversation[-1]:
475
+ return self._build_generation_or_voice_clone_codes(conversation[-1], n_vq)
476
+ return None
477
+ if mode == "continuation" and len(conversation) >= 2:
478
+ if "text" in conversation[-2]:
479
+ return self._build_continuation_codes(conversation, n_vq)
480
+ return None
481
+ return None
482
+
483
+ def __call__(self, *args, **kwargs) -> BatchFeature:
484
+ conversations = args[0] if args else kwargs.pop("conversations")
485
+ mode: str = kwargs.pop("mode", "generation")
486
+ apply_chat_template: bool = kwargs.pop("apply_chat_template", True)
487
+ n_vq = self._assert_fixed_nq(kwargs.pop("n_vq", None))
488
+
489
+ kwargs.pop("return_tensors", None)
490
+ kwargs.pop("padding", None)
491
+ kwargs.pop("truncation", None)
492
+
493
+ if mode not in {"generation", "continuation", "computing_loss"}:
494
+ raise ValueError(f"Unsupported mode: {mode}")
495
+ if isinstance(conversations, (Message, dict)):
496
+ conversations = [conversations]
497
+ elif isinstance(conversations, list) and conversations and all(
498
+ isinstance(item, (Message, dict)) for item in conversations
499
+ ):
500
+ conversations = [conversations]
501
+
502
+ input_ids_list: list[torch.Tensor] = []
503
+ for conversation in conversations:
504
+ if isinstance(conversation, (Message, dict)):
505
+ conversation = [conversation]
506
+ conversation = [self._normalize_message(message) for message in conversation]
507
+
508
+ if (mode == "generation") ^ (conversation[-1]["role"] == "user"):
509
+ raise ValueError("generation mode must end with a user message.")
510
+ if mode == "continuation" and conversation[-1]["role"] != "assistant":
511
+ raise ValueError("continuation mode must end with an assistant message.")
512
+
513
+ direct_codes = self._try_build_direct_codes(conversation, mode, n_vq)
514
+ if direct_codes is not None:
515
+ input_ids_list.append(direct_codes)
516
+ continue
517
+
518
+ unified_parts = []
519
+ for message_idx, message in enumerate(conversation):
520
+ content = str(message["content"])
521
+ if apply_chat_template:
522
+ add_generation_prompt = mode == "generation" and message_idx == len(conversation) - 1
523
+ try:
524
+ content = self.tokenizer.apply_chat_template(
525
+ [{"role": message["role"], "content": content}],
526
+ add_generation_prompt=add_generation_prompt,
527
+ tokenize=False,
528
+ )
529
+ except Exception:
530
+ logger.warning("apply_chat_template failed; falling back to raw message content.")
531
+
532
+ raw_audio_items = message.get("audio_codes_list", [])
533
+ audio_codes_list = self._resolve_audio_items(raw_audio_items, n_vq)
534
+ unified_parts.append(
535
+ self._get_unified_codes(
536
+ role=message["role"],
537
+ content=content,
538
+ audio_codes_list=audio_codes_list,
539
+ truncation=(mode == "continuation"),
540
+ )
541
+ )
542
+
543
+ unified_codes = torch.cat(unified_parts, dim=0)
544
+ if mode == "generation":
545
+ audio_start_row = torch.full(
546
+ (1, n_vq + 1),
547
+ int(self.model_config.audio_pad_token_id),
548
+ dtype=unified_codes.dtype,
549
+ device=unified_codes.device,
550
+ )
551
+ audio_start_row[:, 0] = int(self.model_config.audio_start_token_id)
552
+ unified_codes = torch.cat([unified_codes, audio_start_row], dim=0)
553
+ input_ids_list.append(unified_codes)
554
+
555
+ return BatchFeature(data=self._pad(input_ids_list))
556
+
557
+ def _normalize_message(self, message: Union[Message, Dict[str, Any]]) -> Dict[str, Any]:
558
+ if isinstance(message, Message):
559
+ return message.to_dict()
560
+ if not isinstance(message, dict):
561
+ raise TypeError("Each message must be a Message or dict.")
562
+ if "content" in message and "audio_codes_list" in message:
563
+ return message
564
+ role = message.get("role")
565
+ if role == "user":
566
+ return self.build_user_message(**{key: message.get(key) for key in USER_MESSAGE_FIELDS})
567
+ if role == "assistant":
568
+ return self.build_assistant_message(
569
+ audio_codes_list=message.get("audio_codes_list", []),
570
+ content=message.get("content", AUDIO_PLACEHOLDER),
571
+ )
572
+ raise ValueError(f"Unsupported role: {role}")
573
+
574
+ def _resolve_audio_items(
575
+ self,
576
+ raw_audio_items: list[Any],
577
+ n_vq: int,
578
+ ) -> list[torch.Tensor]:
579
+ if not raw_audio_items:
580
+ return []
581
+ resolved: list[Optional[torch.Tensor]] = [None] * len(raw_audio_items)
582
+ paths: list[str] = []
583
+ path_positions: list[int] = []
584
+ for index, item in enumerate(raw_audio_items):
585
+ if isinstance(item, torch.Tensor):
586
+ if item.ndim != 2 or int(item.shape[1]) != n_vq:
587
+ raise ValueError(f"audio code tensor must have shape [T, {n_vq}], got {tuple(item.shape)}.")
588
+ resolved[index] = item.to(dtype=torch.long).cpu()
589
+ elif isinstance(item, (str, os.PathLike)):
590
+ paths.append(str(item))
591
+ path_positions.append(index)
592
+ else:
593
+ raise TypeError("Audio items must be tensors or path-like values.")
594
+ if paths:
595
+ encoded = self.encode_audios_from_path(paths, n_vq=n_vq)
596
+ for position, codes in zip(path_positions, encoded):
597
+ resolved[position] = codes
598
+ return [cast(torch.Tensor, item) for item in resolved]
599
+
600
+ def _pad(self, input_ids_list: list[torch.Tensor]) -> Dict[str, torch.Tensor]:
601
+ device = input_ids_list[0].device
602
+ lengths = torch.tensor([item.shape[0] for item in input_ids_list], device=device)
603
+ padded = torch.nn.utils.rnn.pad_sequence(
604
+ input_ids_list,
605
+ batch_first=True,
606
+ padding_value=int(self.model_config.audio_pad_token_id),
607
+ padding_side="left",
608
+ )
609
+ left_pad_mask = (padded.shape[1] - lengths).unsqueeze(1) > torch.arange(
610
+ padded.shape[1],
611
+ device=device,
612
+ ).unsqueeze(0)
613
+ padded[..., 0][left_pad_mask] = int(self.model_config.pad_token_id)
614
+ attention_mask = torch.zeros(padded.shape[:2], dtype=torch.bool, device=device)
615
+ attention_mask[~left_pad_mask] = True
616
+ return {"input_ids": padded, "attention_mask": attention_mask}
617
+
618
+ @staticmethod
619
+ def _replace_audio_placeholders(
620
+ content: str,
621
+ lengths: list[int],
622
+ slot_token: str,
623
+ audio_start_token: str,
624
+ audio_end_token: str,
625
+ ) -> str:
626
+ placeholder_count = content.count(AUDIO_PLACEHOLDER)
627
+ if placeholder_count != len(lengths):
628
+ raise ValueError(
629
+ f"Number of {AUDIO_PLACEHOLDER} ({placeholder_count}) does not match "
630
+ f"audio item count ({len(lengths)})."
631
+ )
632
+ lengths_iter = iter(lengths)
633
+
634
+ def replacer(_: re.Match) -> str:
635
+ length = int(next(lengths_iter))
636
+ if length <= 0:
637
+ return f"{audio_start_token}{audio_end_token}"
638
+ return f"{audio_start_token}{slot_token * length}{audio_end_token}"
639
+
640
+ return re.sub(re.escape(AUDIO_PLACEHOLDER), replacer, content)
641
+
642
+ def _get_unified_codes(
643
+ self,
644
+ role: str,
645
+ content: str,
646
+ audio_codes_list: list[torch.Tensor],
647
+ truncation: bool,
648
+ ) -> torch.Tensor:
649
+ n_vq = int(self.model_config.n_vq)
650
+ slot_token = self.audio_user_slot_token if role == "user" else self.audio_assistant_slot_token
651
+ content = self._replace_audio_placeholders(
652
+ content=content,
653
+ lengths=[int(codes.shape[0]) for codes in audio_codes_list],
654
+ slot_token=slot_token,
655
+ audio_start_token=self.audio_start_token,
656
+ audio_end_token=self.audio_end_token,
657
+ )
658
+ text_codes = torch.tensor(
659
+ self.tokenizer.encode(content),
660
+ dtype=torch.long,
661
+ device=audio_codes_list[0].device if audio_codes_list else None,
662
+ )
663
+
664
+ audio_start_indices = torch.where(text_codes == int(self.model_config.audio_start_token_id))[0]
665
+ audio_end_indices = torch.where(text_codes == int(self.model_config.audio_end_token_id))[0]
666
+ if len(audio_start_indices) != len(audio_codes_list) or len(audio_end_indices) != len(audio_codes_list):
667
+ raise ValueError("Audio placeholders do not match the encoded audio spans.")
668
+
669
+ if not audio_codes_list:
670
+ audio_codes = torch.full(
671
+ (len(text_codes), n_vq),
672
+ int(self.model_config.audio_pad_token_id),
673
+ dtype=torch.long,
674
+ device=text_codes.device,
675
+ )
676
+ else:
677
+ pieces: list[torch.Tensor] = []
678
+ prefix_idx = 0
679
+ for start_t, end_t, codes in zip(audio_start_indices, audio_end_indices, audio_codes_list):
680
+ start_idx = int(start_t.item())
681
+ end_idx = int(end_t.item())
682
+ pad_before = torch.full(
683
+ (start_idx - prefix_idx + 1, n_vq),
684
+ int(self.model_config.audio_pad_token_id),
685
+ dtype=torch.long,
686
+ device=codes.device,
687
+ )
688
+ pieces.extend([pad_before, codes.to(dtype=torch.long)])
689
+ prefix_idx = end_idx
690
+ if truncation:
691
+ trailing = torch.zeros(
692
+ (0, n_vq),
693
+ dtype=torch.long,
694
+ device=audio_codes_list[0].device,
695
+ )
696
+ else:
697
+ last_end = int(audio_end_indices[-1].item())
698
+ trailing = torch.full(
699
+ (len(text_codes) - last_end, n_vq),
700
+ int(self.model_config.audio_pad_token_id),
701
+ dtype=torch.long,
702
+ device=audio_codes_list[0].device,
703
+ )
704
+ pieces.append(trailing)
705
+ audio_codes = torch.cat(pieces, dim=0)
706
+
707
+ if text_codes.shape[0] != audio_codes.shape[0]:
708
+ min_len = min(text_codes.shape[0], audio_codes.shape[0])
709
+ text_codes = text_codes[:min_len]
710
+ audio_codes = audio_codes[:min_len]
711
+ return torch.cat([text_codes.unsqueeze(1), audio_codes], dim=1)
712
+
713
+ def _parse_text_codes(self, start_length: int, text_codes: torch.LongTensor) -> str:
714
+ text = cast(str, self.tokenizer.decode(text_codes))
715
+ prefix = cast(str, self.tokenizer.decode(text_codes[:start_length]))
716
+ text = text[len(prefix):]
717
+ audio_pattern = re.compile(
718
+ rf"(?:{re.escape(self.audio_start_token)})?"
719
+ rf"(?:{re.escape(self.audio_assistant_slot_token)})*"
720
+ rf"{re.escape(self.audio_end_token)}"
721
+ )
722
+ return audio_pattern.sub(
723
+ lambda match: AUDIO_PLACEHOLDER if self.audio_assistant_slot_token in match.group(0) else "",
724
+ text,
725
+ )
726
+
727
+ def _parse_audio_codes(
728
+ self,
729
+ start_length: int,
730
+ audio_codes: torch.LongTensor,
731
+ *,
732
+ return_stereo: bool = True,
733
+ ) -> list[torch.Tensor]:
734
+ is_pad = audio_codes.eq(int(self.model_config.audio_pad_token_id)).all(dim=1)
735
+ non_pad = ~is_pad
736
+ if not bool(non_pad.any().item()):
737
+ return []
738
+ idx = torch.nonzero(non_pad).squeeze(1)
739
+ breaks = torch.where(idx[1:] != idx[:-1] + 1)[0] + 1
740
+ segment_indices = [idx] if breaks.numel() == 0 else list(torch.tensor_split(idx, breaks.cpu().tolist()))
741
+ code_segments = [audio_codes[segment] for segment in segment_indices]
742
+ decoded = self.decode_audio_codes(code_segments, return_stereo=return_stereo)
743
+
744
+ if start_length > 0 and code_segments and decoded:
745
+ first_code_length = int(code_segments[0].shape[0])
746
+ if first_code_length > 0:
747
+ trim_ratio = max(0.0, min(float(start_length) / float(first_code_length), 1.0))
748
+ if trim_ratio >= 1.0:
749
+ decoded = decoded[1:]
750
+ elif trim_ratio > 0.0:
751
+ trim_samples = int(decoded[0].shape[-1] * trim_ratio)
752
+ decoded[0] = decoded[0][..., trim_samples:]
753
+ return decoded
754
+
755
+ def decode(self, output: Any, *, return_stereo: bool = True) -> list[Optional[AssistantMessage]]:
756
+ generated_messages: list[Optional[AssistantMessage]] = []
757
+ for start_length, generation_ids in output:
758
+ content = self._parse_text_codes(int(start_length), generation_ids[:, 0])
759
+ audio_codes_list = self._parse_audio_codes(
760
+ int(start_length),
761
+ generation_ids[:, 1:],
762
+ return_stereo=return_stereo,
763
+ )
764
+ if content == "":
765
+ generated_messages.append(None)
766
+ else:
767
+ generated_messages.append(
768
+ AssistantMessage(
769
+ content=content,
770
+ audio_codes_list=cast(list[Union[str, torch.Tensor]], audio_codes_list),
771
+ )
772
+ )
773
+ return generated_messages
774
+
775
+ @staticmethod
776
+ def loudness_normalize(
777
+ wav: torch.Tensor,
778
+ target_dbfs: float = -20.0,
779
+ gain_range: tuple[float, float] = (-3.0, 3.0),
780
+ ) -> torch.Tensor:
781
+ wav = wav.to(torch.float32)
782
+ if wav.numel() == 0:
783
+ return wav
784
+ current_dbfs = 10.0 * torch.log10(torch.mean(wav**2) + 1e-9)
785
+ gain = max(gain_range[0], min(float(target_dbfs - current_dbfs), gain_range[1]))
786
+ return wav * (10.0 ** (gain / 20.0))
787
+
788
+ def _get_audio_tokenizer_device(self) -> torch.device:
789
+ audio_tokenizer = getattr(self, "audio_tokenizer", None)
790
+ if audio_tokenizer is None:
791
+ raise RuntimeError("audio_tokenizer is not set.")
792
+ try:
793
+ return next(audio_tokenizer.parameters()).device
794
+ except StopIteration:
795
+ return torch.device("cpu")
796
+
797
+ def encode_audios_from_wav(
798
+ self,
799
+ wav_list: Union[torch.Tensor, list[torch.Tensor]],
800
+ sampling_rate: int,
801
+ n_vq: Optional[int] = None,
802
+ ) -> list[torch.Tensor]:
803
+ n_vq = self._assert_fixed_nq(n_vq)
804
+ if self.audio_tokenizer is None:
805
+ raise RuntimeError("audio_tokenizer is not set.")
806
+ if isinstance(wav_list, torch.Tensor):
807
+ wav_list = [wav_list]
808
+ target_sr = int(self.model_config.sampling_rate)
809
+ device = self._get_audio_tokenizer_device()
810
+ prepared = []
811
+ for wav in wav_list:
812
+ if wav.ndim == 1:
813
+ wav = wav.unsqueeze(0)
814
+ if wav.shape[0] == 1:
815
+ wav = wav.repeat(2, 1)
816
+ elif wav.shape[0] > 2:
817
+ wav = wav[:2]
818
+ if int(sampling_rate) != target_sr:
819
+ wav = torchaudio.functional.resample(wav, int(sampling_rate), target_sr)
820
+ prepared.append(self.loudness_normalize(wav).to(device))
821
+
822
+ if hasattr(self.audio_tokenizer, "batch_encode"):
823
+ encoded = self.audio_tokenizer.batch_encode(prepared, num_quantizers=n_vq)
824
+ audio_codes = encoded.audio_codes
825
+ audio_lengths = encoded.audio_codes_lengths
826
+ else:
827
+ max_len = max(int(wav.shape[-1]) for wav in prepared)
828
+ input_values = torch.zeros(len(prepared), 1, max_len, dtype=torch.float32, device=device)
829
+ padding_mask = torch.zeros(len(prepared), max_len, dtype=torch.bool, device=device)
830
+ for index, wav in enumerate(prepared):
831
+ input_values[index, 0, : wav.shape[-1]] = wav
832
+ padding_mask[index, : wav.shape[-1]] = True
833
+ encoded = self.audio_tokenizer.encode(
834
+ input_values,
835
+ padding_mask=padding_mask,
836
+ num_quantizers=n_vq,
837
+ return_dict=True,
838
+ )
839
+ audio_codes = encoded.audio_codes
840
+ audio_lengths = encoded.audio_codes_lengths
841
+
842
+ if audio_codes is None or audio_lengths is None:
843
+ raise RuntimeError("audio_tokenizer did not return audio_codes/audio_codes_lengths.")
844
+ result = []
845
+ for index in range(int(audio_codes.shape[1])):
846
+ length = int(audio_lengths[index].item())
847
+ result.append(audio_codes[:, index, :length].transpose(0, 1).contiguous().cpu().long())
848
+ return result
849
+
850
+ def encode_audios_from_path(
851
+ self,
852
+ wav_path_list: Union[str, os.PathLike, list[Union[str, os.PathLike]]],
853
+ n_vq: Optional[int] = None,
854
+ ) -> list[torch.Tensor]:
855
+ if isinstance(wav_path_list, (str, os.PathLike)):
856
+ wav_path_list = [wav_path_list]
857
+ wavs = []
858
+ target_sr = int(self.model_config.sampling_rate)
859
+ for wav_path in wav_path_list:
860
+ wav, sr = torchaudio.load(str(wav_path))
861
+ if int(sr) != target_sr:
862
+ wav = torchaudio.functional.resample(wav, int(sr), target_sr)
863
+ wavs.append(wav)
864
+ return self.encode_audios_from_wav(wavs, target_sr, n_vq=n_vq)
865
+
866
+ def decode_audio_codes(
867
+ self,
868
+ audio_tokens_list: Union[torch.Tensor, list[torch.Tensor]],
869
+ *,
870
+ return_stereo: bool = True,
871
+ ) -> list[torch.Tensor]:
872
+ if self.audio_tokenizer is None:
873
+ raise RuntimeError("audio_tokenizer is not set.")
874
+ if isinstance(audio_tokens_list, torch.Tensor):
875
+ audio_tokens_list = [audio_tokens_list]
876
+ if not audio_tokens_list:
877
+ return []
878
+
879
+ n_vq = int(self.model_config.n_vq)
880
+ device = self._get_audio_tokenizer_device()
881
+ codes_list = [
882
+ codes[:, :n_vq].transpose(0, 1).contiguous().to(device=device, dtype=torch.long)
883
+ for codes in audio_tokens_list
884
+ ]
885
+ max_len = max(int(codes.shape[1]) for codes in codes_list)
886
+ audio_codes = torch.zeros(n_vq, len(codes_list), max_len, device=device, dtype=torch.long)
887
+ padding_mask = torch.zeros(len(codes_list), max_len, device=device, dtype=torch.bool)
888
+ for index, codes in enumerate(codes_list):
889
+ length = int(codes.shape[1])
890
+ audio_codes[:, index, :length] = codes
891
+ padding_mask[index, :length] = True
892
+
893
+ decoded = self.audio_tokenizer.decode(
894
+ audio_codes,
895
+ padding_mask=padding_mask,
896
+ num_quantizers=n_vq,
897
+ return_dict=True,
898
+ chunk_duration=8,
899
+ )
900
+ audio = decoded.audio
901
+ audio_lengths = decoded.audio_lengths
902
+ if audio is None or audio_lengths is None:
903
+ raise RuntimeError("audio_tokenizer.decode did not return audio/audio_lengths.")
904
+ wavs = []
905
+ for index in range(int(audio.shape[0])):
906
+ length = int(audio_lengths[index].item())
907
+ wav = audio[index, :, :length].contiguous().cpu().to(torch.float32)
908
+ if not return_stereo:
909
+ if wav.shape[0] == 1:
910
+ wav = wav.squeeze(0)
911
+ else:
912
+ wav = wav.mean(dim=0)
913
+ wavs.append(wav)
914
+ return wavs
assets/sft3/processor_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "processor_class": "MossTTSLocalProcessor",
3
+ "auto_map": {
4
+ "AutoProcessor": "processing_moss_tts.MossTTSLocalProcessor"
5
+ },
6
+ "audio_tokenizer_name_or_path": "OpenMOSS-Team/MOSS-Audio-Tokenizer-v2"
7
+ }
assets/sft3/qwen3_decoder.py ADDED
@@ -0,0 +1,582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.utils.checkpoint
11
+ from safetensors.torch import load_file
12
+ from transformers.activations import ACT2FN
13
+ from transformers.modeling_outputs import BaseModelOutputWithPast
14
+
15
+ from .gpt2_decoder import PackedSequenceMetadata, MossTTSNanoGPT2Model
16
+
17
+ try:
18
+ from flash_attn import flash_attn_func, flash_attn_varlen_func
19
+ from flash_attn.bert_padding import pad_input, unpad_input
20
+
21
+ _FLASH_ATTN_AVAILABLE = True
22
+ except Exception:
23
+ flash_attn_func = None
24
+ flash_attn_varlen_func = None
25
+ pad_input = None
26
+ unpad_input = None
27
+ _FLASH_ATTN_AVAILABLE = False
28
+
29
+
30
+ class MossQwen3RMSNorm(nn.Module):
31
+ def __init__(self, hidden_size: int, eps: float = 1e-6) -> None:
32
+ super().__init__()
33
+ self.weight = nn.Parameter(torch.ones(hidden_size))
34
+ self.variance_epsilon = eps
35
+
36
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
37
+ input_dtype = hidden_states.dtype
38
+ hidden_states = hidden_states.to(torch.float32)
39
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
40
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
41
+ return self.weight * hidden_states.to(input_dtype)
42
+
43
+
44
+ class MossQwen3RotaryEmbedding(nn.Module):
45
+ def __init__(self, config) -> None:
46
+ super().__init__()
47
+ head_dim = int(getattr(config, "head_dim", config.hidden_size // config.num_attention_heads))
48
+ rope_theta = getattr(config, "rope_theta", None)
49
+ if rope_theta is None:
50
+ rope_scaling = getattr(config, "rope_scaling", None)
51
+ if isinstance(rope_scaling, dict):
52
+ rope_theta = rope_scaling.get("rope_theta")
53
+ if rope_theta is None:
54
+ rope_theta = 1000000.0
55
+ rope_theta = float(rope_theta)
56
+ self.head_dim = head_dim
57
+ self.rope_theta = rope_theta
58
+ self.register_buffer("inv_freq", self._compute_inv_freq(), persistent=False)
59
+
60
+ def _compute_inv_freq(self, device: Optional[torch.device] = None) -> torch.Tensor:
61
+ return 1.0 / (
62
+ self.rope_theta ** (torch.arange(0, self.head_dim, 2, device=device, dtype=torch.float32) / self.head_dim)
63
+ )
64
+
65
+ def forward(
66
+ self,
67
+ hidden_states: torch.Tensor,
68
+ position_ids: torch.LongTensor,
69
+ ) -> tuple[torch.Tensor, torch.Tensor]:
70
+ inv_freq = self._compute_inv_freq(device=hidden_states.device)
71
+ freqs = torch.einsum(
72
+ "bs,d->bsd",
73
+ position_ids.to(device=hidden_states.device, dtype=inv_freq.dtype),
74
+ inv_freq,
75
+ )
76
+ emb = torch.cat((freqs, freqs), dim=-1)
77
+ return emb.cos().to(dtype=hidden_states.dtype), emb.sin().to(dtype=hidden_states.dtype)
78
+
79
+
80
+ def rotate_half(hidden_states: torch.Tensor) -> torch.Tensor:
81
+ first_half = hidden_states[..., : hidden_states.shape[-1] // 2]
82
+ second_half = hidden_states[..., hidden_states.shape[-1] // 2 :]
83
+ return torch.cat((-second_half, first_half), dim=-1)
84
+
85
+
86
+ def apply_rotary_pos_emb(
87
+ query: torch.Tensor,
88
+ key: torch.Tensor,
89
+ cos: torch.Tensor,
90
+ sin: torch.Tensor,
91
+ ) -> tuple[torch.Tensor, torch.Tensor]:
92
+ cos = cos.unsqueeze(-2)
93
+ sin = sin.unsqueeze(-2)
94
+ query = (query * cos) + (rotate_half(query) * sin)
95
+ key = (key * cos) + (rotate_half(key) * sin)
96
+ return query, key
97
+
98
+
99
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
100
+ if n_rep == 1:
101
+ return hidden_states
102
+ batch, seq_len, num_key_value_heads, head_dim = hidden_states.shape
103
+ hidden_states = hidden_states[:, :, :, None, :].expand(batch, seq_len, num_key_value_heads, n_rep, head_dim)
104
+ return hidden_states.reshape(batch, seq_len, num_key_value_heads * n_rep, head_dim)
105
+
106
+
107
+ class MossQwen3MLP(nn.Module):
108
+ def __init__(self, config) -> None:
109
+ super().__init__()
110
+ self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
111
+ self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
112
+ self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
113
+ self.act_fn = ACT2FN[getattr(config, "hidden_act", "silu")]
114
+
115
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
116
+ return self.down_proj(self.act_fn(self.gate_proj(hidden_states)) * self.up_proj(hidden_states))
117
+
118
+
119
+ class MossQwen3Attention(nn.Module):
120
+ def __init__(self, config, layer_idx: int) -> None:
121
+ super().__init__()
122
+ self.config = config
123
+ self.layer_idx = int(layer_idx)
124
+ self.hidden_size = int(config.hidden_size)
125
+ self.num_heads = int(config.num_attention_heads)
126
+ self.num_key_value_heads = int(config.num_key_value_heads)
127
+ self.head_dim = int(getattr(config, "head_dim", self.hidden_size // self.num_heads))
128
+ self.num_key_value_groups = self.num_heads // self.num_key_value_heads
129
+ self.scaling = self.head_dim ** -0.5
130
+ self.attention_dropout = float(getattr(config, "attention_dropout", 0.0))
131
+ self.attn_implementation = str(getattr(config, "_attn_implementation", "eager"))
132
+
133
+ self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=bool(config.attention_bias))
134
+ self.k_proj = nn.Linear(
135
+ self.hidden_size,
136
+ self.num_key_value_heads * self.head_dim,
137
+ bias=bool(config.attention_bias),
138
+ )
139
+ self.v_proj = nn.Linear(
140
+ self.hidden_size,
141
+ self.num_key_value_heads * self.head_dim,
142
+ bias=bool(config.attention_bias),
143
+ )
144
+ self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=bool(config.attention_bias))
145
+ self.q_norm = MossQwen3RMSNorm(self.head_dim, eps=float(config.rms_norm_eps))
146
+ self.k_norm = MossQwen3RMSNorm(self.head_dim, eps=float(config.rms_norm_eps))
147
+
148
+ def _causal_attention_mask(
149
+ self,
150
+ attention_mask: Optional[torch.Tensor],
151
+ query_length: int,
152
+ key_length: int,
153
+ device: torch.device,
154
+ ) -> torch.Tensor:
155
+ query_positions = torch.arange(query_length, device=device, dtype=torch.long)
156
+ query_positions = query_positions + max(key_length - query_length, 0)
157
+ key_positions = torch.arange(key_length, device=device, dtype=torch.long)
158
+ causal = key_positions.unsqueeze(0) <= query_positions.unsqueeze(1)
159
+ causal = causal.unsqueeze(0).unsqueeze(0)
160
+ if attention_mask is None:
161
+ return causal
162
+ key_mask = attention_mask[:, None, None, :].to(dtype=torch.bool)
163
+ return causal & key_mask
164
+
165
+ def _eager_attention(
166
+ self,
167
+ query: torch.Tensor,
168
+ key: torch.Tensor,
169
+ value: torch.Tensor,
170
+ attention_mask: Optional[torch.Tensor],
171
+ ) -> torch.Tensor:
172
+ key = repeat_kv(key, self.num_key_value_groups)
173
+ value = repeat_kv(value, self.num_key_value_groups)
174
+ query = query.transpose(1, 2)
175
+ key = key.transpose(1, 2)
176
+ value = value.transpose(1, 2)
177
+ scores = torch.matmul(query, key.transpose(-1, -2)) * self.scaling
178
+ mask = self._causal_attention_mask(
179
+ attention_mask=attention_mask,
180
+ query_length=query.shape[-2],
181
+ key_length=key.shape[-2],
182
+ device=query.device,
183
+ )
184
+ scores = scores.masked_fill(~mask, torch.finfo(scores.dtype).min)
185
+ probs = torch.softmax(scores, dim=-1)
186
+ if self.training and self.attention_dropout > 0:
187
+ probs = torch.dropout(probs, self.attention_dropout, train=True)
188
+ output = torch.matmul(probs, value)
189
+ return output.transpose(1, 2).contiguous()
190
+
191
+ def _sdpa_attention(
192
+ self,
193
+ query: torch.Tensor,
194
+ key: torch.Tensor,
195
+ value: torch.Tensor,
196
+ attention_mask: Optional[torch.Tensor],
197
+ ) -> torch.Tensor:
198
+ key = repeat_kv(key, self.num_key_value_groups)
199
+ value = repeat_kv(value, self.num_key_value_groups)
200
+ query = query.transpose(1, 2)
201
+ key = key.transpose(1, 2)
202
+ value = value.transpose(1, 2)
203
+ mask = None
204
+ if attention_mask is not None or query.shape[-2] != key.shape[-2]:
205
+ mask = self._causal_attention_mask(
206
+ attention_mask=attention_mask,
207
+ query_length=query.shape[-2],
208
+ key_length=key.shape[-2],
209
+ device=query.device,
210
+ )
211
+ output = torch.nn.functional.scaled_dot_product_attention(
212
+ query,
213
+ key,
214
+ value,
215
+ attn_mask=mask,
216
+ dropout_p=self.attention_dropout if self.training else 0.0,
217
+ is_causal=mask is None,
218
+ scale=self.scaling,
219
+ )
220
+ return output.transpose(1, 2).contiguous()
221
+
222
+ def _flash_attention(
223
+ self,
224
+ query: torch.Tensor,
225
+ key: torch.Tensor,
226
+ value: torch.Tensor,
227
+ attention_mask: Optional[torch.Tensor],
228
+ packed_metadata: Optional[PackedSequenceMetadata],
229
+ ) -> torch.Tensor:
230
+ if not _FLASH_ATTN_AVAILABLE:
231
+ raise ImportError("flash_attn is not installed, but attn_implementation='flash_attention_2' was requested.")
232
+ if query.device.type != "cuda":
233
+ raise ValueError("flash_attention_2 requires CUDA tensors.")
234
+ if query.dtype not in (torch.float16, torch.bfloat16):
235
+ raise ValueError(f"flash_attention_2 requires fp16/bf16 tensors, got dtype={query.dtype}.")
236
+
237
+ dropout_p = self.attention_dropout if self.training else 0.0
238
+ if packed_metadata is not None:
239
+ if packed_metadata.indices is not None:
240
+ query = query.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
241
+ key = key.reshape(-1, self.num_key_value_heads, self.head_dim).index_select(0, packed_metadata.indices)
242
+ value = value.reshape(-1, self.num_key_value_heads, self.head_dim).index_select(0, packed_metadata.indices)
243
+ output = flash_attn_varlen_func(
244
+ query,
245
+ key,
246
+ value,
247
+ packed_metadata.cu_seqlens,
248
+ packed_metadata.cu_seqlens,
249
+ packed_metadata.max_seqlen,
250
+ packed_metadata.max_seqlen,
251
+ dropout_p=dropout_p,
252
+ causal=True,
253
+ )
254
+ if packed_metadata.indices is None:
255
+ return output
256
+ return pad_input(
257
+ output,
258
+ packed_metadata.indices,
259
+ packed_metadata.batch_size,
260
+ packed_metadata.seq_len,
261
+ )
262
+
263
+ if attention_mask is None or bool(attention_mask.all()):
264
+ return flash_attn_func(query, key, value, dropout_p=dropout_p, causal=True)
265
+
266
+ if query.shape[1] != key.shape[1]:
267
+ query_attention_mask = attention_mask[:, -query.shape[1] :]
268
+ unpadded_query, query_indices, cu_seqlens_q, max_seqlen_q, _ = unpad_input(
269
+ query,
270
+ query_attention_mask,
271
+ )
272
+ unpadded_key, _, cu_seqlens_k, max_seqlen_k, _ = unpad_input(key, attention_mask)
273
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
274
+ output = flash_attn_varlen_func(
275
+ unpadded_query,
276
+ unpadded_key,
277
+ unpadded_value,
278
+ cu_seqlens_q,
279
+ cu_seqlens_k,
280
+ max_seqlen_q,
281
+ max_seqlen_k,
282
+ dropout_p=dropout_p,
283
+ causal=True,
284
+ )
285
+ return pad_input(output, query_indices, query.shape[0], query.shape[1])
286
+
287
+ unpadded_query, indices, cu_seqlens, max_seqlen, _ = unpad_input(query, attention_mask)
288
+ unpadded_key, _, _, _, _ = unpad_input(key, attention_mask)
289
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
290
+ output = flash_attn_varlen_func(
291
+ unpadded_query,
292
+ unpadded_key,
293
+ unpadded_value,
294
+ cu_seqlens,
295
+ cu_seqlens,
296
+ max_seqlen,
297
+ max_seqlen,
298
+ dropout_p=dropout_p,
299
+ causal=True,
300
+ )
301
+ return pad_input(output, indices, query.shape[0], query.shape[1])
302
+
303
+ def forward(
304
+ self,
305
+ hidden_states: torch.Tensor,
306
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
307
+ attention_mask: Optional[torch.Tensor] = None,
308
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
309
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
310
+ use_cache: bool = False,
311
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
312
+ input_shape = hidden_states.shape[:-1]
313
+ query_states = self.q_norm(
314
+ self.q_proj(hidden_states).view(*input_shape, self.num_heads, self.head_dim)
315
+ )
316
+ key_states = self.k_norm(
317
+ self.k_proj(hidden_states).view(*input_shape, self.num_key_value_heads, self.head_dim)
318
+ )
319
+ value_states = self.v_proj(hidden_states).view(*input_shape, self.num_key_value_heads, self.head_dim)
320
+
321
+ cos, sin = position_embeddings
322
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
323
+
324
+ if layer_past is not None:
325
+ past_key, past_value = layer_past
326
+ key_states = torch.cat([past_key.to(device=key_states.device, dtype=key_states.dtype), key_states], dim=1)
327
+ value_states = torch.cat(
328
+ [past_value.to(device=value_states.device, dtype=value_states.dtype), value_states],
329
+ dim=1,
330
+ )
331
+
332
+ present = (key_states, value_states) if use_cache else None
333
+ if self.attn_implementation == "flash_attention_2":
334
+ attn_output = self._flash_attention(
335
+ query=query_states,
336
+ key=key_states,
337
+ value=value_states,
338
+ attention_mask=attention_mask,
339
+ packed_metadata=packed_metadata,
340
+ )
341
+ elif self.attn_implementation == "sdpa":
342
+ attn_output = self._sdpa_attention(
343
+ query=query_states,
344
+ key=key_states,
345
+ value=value_states,
346
+ attention_mask=attention_mask,
347
+ )
348
+ else:
349
+ attn_output = self._eager_attention(
350
+ query=query_states,
351
+ key=key_states,
352
+ value=value_states,
353
+ attention_mask=attention_mask,
354
+ )
355
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
356
+ return self.o_proj(attn_output), present
357
+
358
+
359
+ class MossQwen3DecoderLayer(nn.Module):
360
+ def __init__(self, config, layer_idx: int) -> None:
361
+ super().__init__()
362
+ self.self_attn = MossQwen3Attention(config=config, layer_idx=layer_idx)
363
+ self.mlp = MossQwen3MLP(config)
364
+ self.input_layernorm = MossQwen3RMSNorm(config.hidden_size, eps=float(config.rms_norm_eps))
365
+ self.post_attention_layernorm = MossQwen3RMSNorm(config.hidden_size, eps=float(config.rms_norm_eps))
366
+
367
+ def forward(
368
+ self,
369
+ hidden_states: torch.Tensor,
370
+ attention_mask: Optional[torch.Tensor] = None,
371
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
372
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
373
+ use_cache: bool = False,
374
+ position_embeddings: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
375
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
376
+ residual = hidden_states
377
+ hidden_states = self.input_layernorm(hidden_states)
378
+ attn_output, present = self.self_attn(
379
+ hidden_states=hidden_states,
380
+ position_embeddings=position_embeddings,
381
+ attention_mask=attention_mask,
382
+ packed_metadata=packed_metadata,
383
+ layer_past=layer_past,
384
+ use_cache=use_cache,
385
+ )
386
+ hidden_states = residual + attn_output
387
+
388
+ residual = hidden_states
389
+ hidden_states = self.post_attention_layernorm(hidden_states)
390
+ hidden_states = residual + self.mlp(hidden_states)
391
+ return hidden_states, present
392
+
393
+
394
+ class MossQwen3Model(nn.Module):
395
+ def __init__(self, config) -> None:
396
+ super().__init__()
397
+ self.config = config
398
+ self.attn_implementation = str(getattr(config, "_attn_implementation", "eager"))
399
+ self.padding_idx = getattr(config, "pad_token_id", None)
400
+ self.vocab_size = int(config.vocab_size)
401
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
402
+ self.layers = nn.ModuleList(
403
+ [MossQwen3DecoderLayer(config, layer_idx=index) for index in range(config.num_hidden_layers)]
404
+ )
405
+ self.norm = MossQwen3RMSNorm(config.hidden_size, eps=float(config.rms_norm_eps))
406
+ self.rotary_emb = MossQwen3RotaryEmbedding(config)
407
+ self.gradient_checkpointing = False
408
+ self.gradient_checkpointing_use_reentrant = bool(
409
+ getattr(config, "gradient_checkpointing_use_reentrant", False)
410
+ )
411
+ self._reset_parameters()
412
+
413
+ def _reset_parameters(self) -> None:
414
+ init_std = float(getattr(self.config, "initializer_range", 0.02))
415
+ for module in self.modules():
416
+ if isinstance(module, nn.Linear):
417
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
418
+ if module.bias is not None:
419
+ nn.init.zeros_(module.bias)
420
+ elif isinstance(module, nn.Embedding):
421
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
422
+
423
+ def get_input_embeddings(self):
424
+ return self.embed_tokens
425
+
426
+ def set_input_embeddings(self, value):
427
+ self.embed_tokens = value
428
+
429
+ def load_qwen3_pretrained_weights(self, pretrained_path: str) -> None:
430
+ model_dir = Path(pretrained_path)
431
+ index_path = model_dir / "model.safetensors.index.json"
432
+ if not index_path.exists():
433
+ raise FileNotFoundError(f"Missing Qwen3 safetensors index: {index_path}")
434
+ with index_path.open("r", encoding="utf-8") as handle:
435
+ index = json.load(handle)
436
+ weight_map = index.get("weight_map", {})
437
+ shard_to_keys: dict[str, list[str]] = {}
438
+ for key, shard in weight_map.items():
439
+ if not key.startswith("model."):
440
+ continue
441
+ shard_to_keys.setdefault(str(shard), []).append(key)
442
+
443
+ state_dict = self.state_dict()
444
+ loaded_state = {}
445
+ for shard, keys in sorted(shard_to_keys.items()):
446
+ shard_tensors = load_file(str(model_dir / shard), device="cpu")
447
+ for key in keys:
448
+ target_key = key[len("model.") :]
449
+ if target_key not in state_dict:
450
+ continue
451
+ tensor = shard_tensors[key]
452
+ if tuple(tensor.shape) != tuple(state_dict[target_key].shape):
453
+ raise ValueError(
454
+ f"Shape mismatch while loading Qwen3 weight {key}: "
455
+ f"checkpoint={tuple(tensor.shape)} model={tuple(state_dict[target_key].shape)}"
456
+ )
457
+ loaded_state[target_key] = tensor
458
+
459
+ missing, unexpected = self.load_state_dict(loaded_state, strict=False)
460
+ unexpected = [key for key in unexpected if key]
461
+ if unexpected:
462
+ raise RuntimeError(f"Unexpected Qwen3 pretrained keys after load: {unexpected[:10]}")
463
+ missing = [key for key in missing if key not in loaded_state]
464
+ if missing:
465
+ raise RuntimeError(f"Missing Qwen3 pretrained keys after load: {missing[:10]}")
466
+
467
+ def forward(
468
+ self,
469
+ input_ids: Optional[torch.LongTensor] = None,
470
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None,
471
+ attention_mask: Optional[torch.Tensor] = None,
472
+ position_ids: Optional[torch.LongTensor] = None,
473
+ inputs_embeds: Optional[torch.FloatTensor] = None,
474
+ use_cache: Optional[bool] = None,
475
+ output_attentions: Optional[bool] = None,
476
+ output_hidden_states: Optional[bool] = None,
477
+ return_dict: bool = True,
478
+ cu_seqlens: Optional[torch.Tensor] = None,
479
+ num_sequences: Optional[torch.Tensor] = None,
480
+ ) -> BaseModelOutputWithPast:
481
+ del input_ids, output_attentions
482
+ if inputs_embeds is None:
483
+ raise ValueError("inputs_embeds must be provided.")
484
+
485
+ use_cache = bool(use_cache)
486
+ if use_cache and cu_seqlens is not None:
487
+ raise ValueError("use_cache=True is not supported together with cu_seqlens packing.")
488
+
489
+ hidden_states = inputs_embeds
490
+ if attention_mask is None:
491
+ attention_mask = torch.ones(hidden_states.shape[:2], dtype=torch.bool, device=hidden_states.device)
492
+ else:
493
+ attention_mask = attention_mask.to(dtype=torch.bool, device=hidden_states.device)
494
+ query_attention_mask = attention_mask[:, -hidden_states.shape[1] :]
495
+
496
+ packed_metadata = None
497
+ if position_ids is None:
498
+ if cu_seqlens is not None:
499
+ position_ids = MossTTSNanoGPT2Model.build_packed_position_ids(
500
+ attention_mask=attention_mask,
501
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
502
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
503
+ sequence_length=hidden_states.shape[1],
504
+ )
505
+ elif attention_mask is not None:
506
+ position_ids = attention_mask.long().cumsum(dim=-1) - 1
507
+ position_ids = position_ids.masked_fill(~attention_mask, 0)
508
+ position_ids = position_ids[:, -hidden_states.shape[1] :]
509
+ else:
510
+ past_length = 0
511
+ if past_key_values is not None and len(past_key_values) > 0:
512
+ past_length = past_key_values[0][0].shape[1]
513
+ position_ids = torch.arange(hidden_states.shape[1], device=hidden_states.device, dtype=torch.long)
514
+ position_ids = position_ids + past_length
515
+ position_ids = position_ids.unsqueeze(0).expand(hidden_states.shape[0], -1)
516
+
517
+ if cu_seqlens is not None and self.attn_implementation == "flash_attention_2":
518
+ packed_metadata = MossTTSNanoGPT2Model.build_packed_metadata(
519
+ hidden_states=hidden_states,
520
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
521
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
522
+ )
523
+
524
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
525
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
526
+
527
+ all_hidden_states = () if output_hidden_states else None
528
+ presents = [] if use_cache else None
529
+ for layer_index, decoder_layer in enumerate(self.layers):
530
+ if output_hidden_states:
531
+ all_hidden_states = all_hidden_states + (hidden_states,)
532
+
533
+ if self.gradient_checkpointing and self.training:
534
+ if use_cache:
535
+ raise ValueError("use_cache=True is not supported when gradient checkpointing is enabled during training.")
536
+
537
+ def custom_forward(*inputs):
538
+ output, _ = decoder_layer(
539
+ hidden_states=inputs[0],
540
+ attention_mask=inputs[1],
541
+ packed_metadata=packed_metadata,
542
+ layer_past=None,
543
+ use_cache=False,
544
+ position_embeddings=position_embeddings,
545
+ )
546
+ return output
547
+
548
+ hidden_states = torch.utils.checkpoint.checkpoint(
549
+ custom_forward,
550
+ hidden_states,
551
+ attention_mask,
552
+ use_reentrant=self.gradient_checkpointing_use_reentrant,
553
+ )
554
+ present = None
555
+ else:
556
+ hidden_states, present = decoder_layer(
557
+ hidden_states=hidden_states,
558
+ attention_mask=attention_mask,
559
+ packed_metadata=packed_metadata,
560
+ layer_past=None if past_key_values is None else past_key_values[layer_index],
561
+ use_cache=use_cache,
562
+ position_embeddings=position_embeddings,
563
+ )
564
+
565
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
566
+ if presents is not None:
567
+ presents.append(present)
568
+
569
+ hidden_states = self.norm(hidden_states)
570
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
571
+ if output_hidden_states:
572
+ all_hidden_states = all_hidden_states + (hidden_states,)
573
+
574
+ if not return_dict:
575
+ return (hidden_states, tuple(presents) if presents is not None else None, all_hidden_states, None)
576
+
577
+ return BaseModelOutputWithPast(
578
+ last_hidden_state=hidden_states,
579
+ past_key_values=tuple(presents) if presents is not None else None,
580
+ hidden_states=all_hidden_states,
581
+ attentions=None,
582
+ )
assets/sft3/special_tokens_map.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|audio_start|>",
4
+ "<|audio_end|>",
5
+ "<|audio_pad|>"
6
+ ],
7
+ "eos_token": {
8
+ "content": "<|im_end|>",
9
+ "lstrip": false,
10
+ "normalized": false,
11
+ "rstrip": false,
12
+ "single_word": false
13
+ },
14
+ "pad_token": {
15
+ "content": "<|endoftext|>",
16
+ "lstrip": false,
17
+ "normalized": false,
18
+ "rstrip": false,
19
+ "single_word": false
20
+ }
21
+ }
assets/sft3/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06902d1fb775216338802205886a24bc715ccc606bd872a892e3d3c83ca1b9e2
3
+ size 11423220
assets/sft3/tokenizer_config.json ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ },
181
+ "151665": {
182
+ "content": "<tool_response>",
183
+ "lstrip": false,
184
+ "normalized": false,
185
+ "rstrip": false,
186
+ "single_word": false,
187
+ "special": false
188
+ },
189
+ "151666": {
190
+ "content": "</tool_response>",
191
+ "lstrip": false,
192
+ "normalized": false,
193
+ "rstrip": false,
194
+ "single_word": false,
195
+ "special": false
196
+ },
197
+ "151667": {
198
+ "content": "<think>",
199
+ "lstrip": false,
200
+ "normalized": false,
201
+ "rstrip": false,
202
+ "single_word": false,
203
+ "special": false
204
+ },
205
+ "151668": {
206
+ "content": "</think>",
207
+ "lstrip": false,
208
+ "normalized": false,
209
+ "rstrip": false,
210
+ "single_word": false,
211
+ "special": false
212
+ },
213
+ "151669": {
214
+ "content": "<|audio_start|>",
215
+ "lstrip": false,
216
+ "normalized": false,
217
+ "rstrip": false,
218
+ "single_word": false,
219
+ "special": true
220
+ },
221
+ "151670": {
222
+ "content": "<|audio_end|>",
223
+ "lstrip": false,
224
+ "normalized": false,
225
+ "rstrip": false,
226
+ "single_word": false,
227
+ "special": true
228
+ },
229
+ "151671": {
230
+ "content": "<|audio_pad|>",
231
+ "lstrip": false,
232
+ "normalized": false,
233
+ "rstrip": false,
234
+ "single_word": false,
235
+ "special": true
236
+ }
237
+ },
238
+ "additional_special_tokens": [
239
+ "<|audio_start|>",
240
+ "<|audio_end|>",
241
+ "<|audio_pad|>"
242
+ ],
243
+ "bos_token": null,
244
+ "clean_up_tokenization_spaces": false,
245
+ "eos_token": "<|im_end|>",
246
+ "errors": "replace",
247
+ "extra_special_tokens": {},
248
+ "model_max_length": 131072,
249
+ "pad_token": "<|endoftext|>",
250
+ "split_special_tokens": false,
251
+ "tokenizer_class": "Qwen2Tokenizer",
252
+ "unk_token": null
253
+ }
checkpoints/S1/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S1",
3
+ "bytes": 1431076525,
4
+ "sha256": "89cf9ee92e83ea8fda7661c53c2346e68b08f407ac359248dabdf42a8c01c602",
5
+ "step": 16265,
6
+ "loss": 4.805444754959801,
7
+ "original_training_state_sha256": "e33af9a8bfcace52d36072dc90db4847a764da709fcf781775c4e40db3de4b96",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S10/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S10",
3
+ "bytes": 1431076525,
4
+ "sha256": "d92965a2656b35a38bbf76aee545bcd64b813235549b29bab4a64b09a256acb4",
5
+ "step": 41936,
6
+ "loss": 4.727833971921586,
7
+ "original_training_state_sha256": "1fbb313660f71bf74aca97237dc0a77e0852193875a4ba32d56e6dfb1ae03c55",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S2/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S2",
3
+ "bytes": 1431076525,
4
+ "sha256": "b05ee43210cb35629368229adb00bdb45546aad47c0a640757e82be480162d0c",
5
+ "step": 26284,
6
+ "loss": 4.723223350145455,
7
+ "original_training_state_sha256": "bed312f0116c75bbbd2ee87af2c3d799b07dce97bb0f47a2e184c3d1d7adf0d3",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S3/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S3",
3
+ "bytes": 1431076525,
4
+ "sha256": "60079723bf797a81107def1e6fc479e5353b3cc51f3780f98ea65a9f5c109f97",
5
+ "step": 32337,
6
+ "loss": 4.704502838977402,
7
+ "original_training_state_sha256": "02a62d17867616531a0af1a8663841e6bd638d0cb39e526f0b7283aa65440dd9",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S4/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S4",
3
+ "bytes": 1431076525,
4
+ "sha256": "c6d07d86ec057844cbbcfd5f6d67b152ddff92df9fcd0063e25de032ed33d6ce",
5
+ "step": 37182,
6
+ "loss": 4.69656242843785,
7
+ "original_training_state_sha256": "59b9f2cbea9cf14b9651bdf587bb9d6622872087d5c6e9997975074987d16273",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S5/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S5",
3
+ "bytes": 1431077587,
4
+ "sha256": "b90981030618ea64f2b0e5cdc4167de222182be4ad1f3dc8b358ebb59d778fba",
5
+ "step": 38183,
6
+ "loss": 4.698665114248258,
7
+ "original_training_state_sha256": "0b844c61ccd4a1f6f052d749dcbb9d5d7922c49c832d8a81bb18a06e4cdfc2fc",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S6/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S6",
3
+ "bytes": 1431076525,
4
+ "sha256": "49b4bbe028faaf99d2501e234db7fd8e5faa1e004b7acbf9a96f1f0cb054c65c",
5
+ "step": 40935,
6
+ "loss": 4.700463458320118,
7
+ "original_training_state_sha256": "ff4ef3c3f0559b38351251f951f8476f60402ee61f6a942834a03f90386dcf80",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S7/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S7",
3
+ "bytes": 1431076525,
4
+ "sha256": "ec2dc60f6df2b8b0a842b35048a9b6c6bb6bedf87640f5affdeea49899009b6d",
5
+ "step": 41466,
6
+ "loss": 4.705551250746067,
7
+ "original_training_state_sha256": "977654975fc889d3c726477b0159740b9c1d4b579434a82b7ca382f4e5d710ff",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S8/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S8",
3
+ "bytes": 1431076525,
4
+ "sha256": "7ea0868438d1fbae7cafc4aad366199cfb5f535c4a7ed17f250d48683ae94dad",
5
+ "step": 41739,
6
+ "loss": 4.710860909975666,
7
+ "original_training_state_sha256": "86285dc461cefb437c0d29a75100d3232982a215f73a3ab23bfb471a3a8f1fde",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
checkpoints/S9/metadata.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stage": "S9",
3
+ "bytes": 1431076525,
4
+ "sha256": "4109129bc881584e27b8ffd0f073f7f3536ba472042fd77fe84a42f9cdb1c008",
5
+ "step": 41878,
6
+ "loss": 4.719842956252719,
7
+ "original_training_state_sha256": "d9ed815939a42d2cfe16ee212a435cc38ed134c0d28f0345fbfd5105f823aafd",
8
+ "contract": {
9
+ "plan_sha256": "277eb0027da710e3b0141c131090792fa6c2da8797ab5a4483bfe8e4f846ddd9",
10
+ "schema_sha256": "8eee17be1be56dcc4c3b5e8ac95b5494dd147451051f2d9a0c34d418f354b9e7",
11
+ "manifest_sha256": "b7d691148613bc04bbcf942d7e63152fe5669c922aff2400c1efdbe5b815227c",
12
+ "world": 128,
13
+ "objective": "global_token_mean_continuous_ladder_v1",
14
+ "seed": 20260923
15
+ },
16
+ "format": "torch.save of base.state_dict() in bfloat16; weights_only=True"
17
+ }
code/anno_timed.py ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Timed-script annotation engine (v2).
3
+
4
+ WHY. Listening to the SFT+DPO model surfaced two failure modes: vocal bursts that run far too
5
+ long and sound unnatural, and occasional hallucinated content that was never prompted. Both are
6
+ BUDGET failures -- nothing in the v1 prompt told the model how long anything was allowed to take.
7
+ v1 carried at most one optional `[7.3 seconds duration]` prefix for the whole clip and bare
8
+ `(sigh)` cues with no duration at all.
9
+
10
+ v2 renders the target text as a TIMED SCRIPT built from the word-level timestamps and the
11
+ DETECTED burst spans, so every second of the clip is accounted for:
12
+
13
+ [0.9 seconds pause] [3.4 seconds duration] Die Handelskammer betreibt diesen Test.
14
+ [0.6 seconds pause] (contented sigh, 0.4 seconds) [1.2 seconds pause]
15
+ [4.1 seconds duration] Die entwickeln und fuehren ihn durch, wissen Sie.
16
+
17
+ Rules, each asserted by `verify()` on the EMITTED string:
18
+ 1. `[D seconds duration]` precedes a speech segment and equals that SEGMENT's last-word offset
19
+ minus its first-word onset -- never the clip's.
20
+ 2. Segments break at sentence-final punctuation. A segment still longer than MAX_SEG_S is
21
+ split again at its largest internal word gap, recursively. A 20 s two-sentence utterance
22
+ therefore renders as `[12.0 ...] <s1> [8.0 ...] <s2>`.
23
+ 3. Every gap >= PAUSE_MIN between consecutive events -- including before the first and after
24
+ the last -- is emitted as `[G seconds pause]`.
25
+ 4. Every detected burst is emitted at its position, as `(label, D seconds)`.
26
+ 5. The emitted numbers sum to the clip duration.
27
+
28
+ FOUR THINGS A NAIVE IMPLEMENTATION GETS WRONG, all measured on this corpus:
29
+
30
+ * The transcript must not be rebuilt by joining word tokens. The forced aligner drops
31
+ numerals ("154 Euro" aligns as "Euro"), so a join silently loses text on 9.9 % of
32
+ real-speech rows. Words are ALIGNED to the original string by character offset and the
33
+ original string is SLICED, so output text is byte-identical apart from inserted tags.
34
+ * The script is built from DETECTED bursts, not from the parenthetical cues already sitting in
35
+ `text_with_bursts`. On the voice-profile corpus those parentheticals are SYNTHESIS
36
+ DIRECTIONS -- 39.4 % were never confirmed by the burst detector. A target text carrying a
37
+ cue that is not in the audio teaches precisely the behaviour being removed.
38
+ * 49.0 % of detected bursts OVERLAP an aligned word span. Adding such a burst's full length
39
+ to the budget double-counts time -- it drove the sum 2.1 s past the clip length on the worst
40
+ row of the first test. A burst's printed duration is its length MINUS its overlap with
41
+ speech; when nothing is left it is printed WITHOUT a number, which is the honest statement
42
+ that it is co-articulated and has no slot of its own.
43
+ * A gap SHORTER than PAUSE_MIN is not worth a tag, but skipping it breaks the sum and the
44
+ residual then reads as an error in the duration values. Such a gap is absorbed into the
45
+ adjacent speech segment (never into a burst -- a burst tag must state the burst's own length).
46
+ """
47
+ import re
48
+
49
+ PAUSE_MIN = 0.20 # gaps below this are absorbed rather than tagged
50
+ MAX_SEG_S = 12.0 # a speech segment longer than this is split again at its largest gap
51
+ MIN_SPLIT_S = 2.0 # never create a segment shorter than this by splitting
52
+ BURST_MIN_S = 0.05 # below this a burst prints without a duration
53
+
54
+ _SENT_END = re.compile(r"[.!?…]+[\"'“”’\)\]]*$")
55
+
56
+
57
+ def _fmt(x):
58
+ return f"{x:.1f}"
59
+
60
+
61
+ def align_words(text, words):
62
+ """-> [(char_start, char_end, t_start, t_end)] for the word tokens locatable in `text`."""
63
+ out, pos = [], 0
64
+ for w in words or []:
65
+ tok = (w.get("w") or "").strip()
66
+ if not tok:
67
+ continue
68
+ try:
69
+ s, e = float(w["s"]), float(w["e"])
70
+ except Exception:
71
+ continue
72
+ if not (e >= s >= -0.001):
73
+ continue
74
+ i = text.find(tok, pos)
75
+ if i < 0:
76
+ continue
77
+ j = i + len(tok)
78
+ out.append((i, j, s, e))
79
+ pos = j
80
+ return out
81
+
82
+
83
+ def _sentence_breaks(text, aw):
84
+ br = {k for k, (i, j, s, e) in enumerate(aw) if _SENT_END.search(text[i:j])}
85
+ br.add(len(aw) - 1)
86
+ return br
87
+
88
+
89
+ def _split_long(aw, lo, hi):
90
+ dur = aw[hi][3] - aw[lo][2]
91
+ if dur <= MAX_SEG_S or hi - lo < 1:
92
+ return [(lo, hi)]
93
+ best, bestscore = None, -1e9
94
+ for k in range(lo, hi):
95
+ if (aw[k][3] - aw[lo][2]) < MIN_SPLIT_S or (aw[hi][3] - aw[k + 1][2]) < MIN_SPLIT_S:
96
+ continue
97
+ gap = aw[k + 1][2] - aw[k][3]
98
+ score = gap - 0.02 * abs((aw[k][3] - aw[lo][2]) - dur / 2.0)
99
+ if score > bestscore:
100
+ bestscore, best = score, k
101
+ if best is None:
102
+ return [(lo, hi)]
103
+ return _split_long(aw, lo, best) + _split_long(aw, best + 1, hi)
104
+
105
+
106
+ def _free_len(s, e, spans):
107
+ """length of [s,e) not covered by any interval in `spans` (sorted, disjoint)."""
108
+ free = e - s
109
+ for (a, b) in spans:
110
+ if b <= s:
111
+ continue
112
+ if a >= e:
113
+ break
114
+ free -= min(e, b) - max(s, a)
115
+ return max(free, 0.0)
116
+
117
+
118
+ def build_events(text, words, bursts, dur_s):
119
+ aw = align_words(text, words)
120
+ bs = []
121
+ for t in bursts or []:
122
+ try:
123
+ s, e, lab = float(t[0]), float(t[1]), str(t[2] or "").strip()
124
+ except Exception:
125
+ continue
126
+ if e <= s:
127
+ continue
128
+ bs.append((s, e, lab))
129
+ bs.sort()
130
+
131
+ if not aw:
132
+ ev = [{"kind": "burst", "s": s, "e": e, "label": l, "dur": e - s, "timed": True}
133
+ for (s, e, l) in bs]
134
+ _absorb(ev, dur_s)
135
+ return ev, aw
136
+
137
+ # 1. sentence segmentation
138
+ br = _sentence_breaks(text, aw)
139
+ segs, lo = [], 0
140
+ for k in range(len(aw)):
141
+ if k in br:
142
+ segs.append((lo, k))
143
+ lo = k + 1
144
+
145
+ # 2. every burst splits the segment it lands in, at the WORD GAP nearest its midpoint.
146
+ # (Requiring the burst to sit entirely inside a gap would fail for the 49 % that overlap
147
+ # a word, and those would then be emitted out of order.)
148
+ cuts = set()
149
+ for (s, e, l) in bs:
150
+ mid = 0.5 * (s + e)
151
+ for (lo, hi) in segs:
152
+ if aw[lo][2] <= mid <= aw[hi][3] and hi > lo:
153
+ k = min(range(lo, hi), key=lambda k: abs(0.5 * (aw[k][3] + aw[k + 1][2]) - mid))
154
+ cuts.add(k)
155
+ break
156
+ out, = [[]]
157
+ for (lo, hi) in segs:
158
+ a = lo
159
+ for k in range(lo, hi):
160
+ if k in cuts:
161
+ out.append((a, k))
162
+ a = k + 1
163
+ out.append((a, hi))
164
+ segs = [x for x in out if x[0] <= x[1]]
165
+
166
+ # 3. length cap
167
+ out = []
168
+ for (lo, hi) in segs:
169
+ out.extend(_split_long(aw, lo, hi))
170
+ segs = out
171
+
172
+ spans = sorted((aw[lo][2], aw[hi][3]) for (lo, hi) in segs)
173
+ ev = [{"kind": "speech", "s": aw[lo][2], "e": aw[hi][3], "lo": lo, "hi": hi} for (lo, hi) in segs]
174
+ for (s, e, l) in bs:
175
+ f = _free_len(s, e, spans)
176
+ ev.append({"kind": "burst", "s": s, "e": e, "label": l,
177
+ "dur": f, "timed": f >= BURST_MIN_S})
178
+ ev.sort(key=lambda d: (d["s"], 0 if d["kind"] == "speech" else 1))
179
+ _absorb(ev, dur_s)
180
+ return ev, aw
181
+
182
+
183
+ def _absorb(ev, dur_s):
184
+ prev = 0.0
185
+ for k, e in enumerate(ev):
186
+ if e["kind"] == "burst" and not e.get("timed"):
187
+ continue # occupies no budget of its own
188
+ gap = e["s"] - prev
189
+ if 0 < gap < PAUSE_MIN:
190
+ if e["kind"] == "speech":
191
+ e["s"] = prev
192
+ elif k > 0 and ev[k - 1]["kind"] == "speech":
193
+ ev[k - 1]["e"] = e["s"]
194
+ prev = max(prev, e["e"] if e["kind"] == "speech" else e["s"] + e["dur"])
195
+ if dur_s and ev:
196
+ last = None
197
+ for e in reversed(ev):
198
+ if e["kind"] == "speech":
199
+ last = e
200
+ break
201
+ if last is not None:
202
+ tail = float(dur_s) - last["e"]
203
+ if 0 < tail < PAUSE_MIN and last is ev[-1]:
204
+ last["e"] = float(dur_s)
205
+
206
+
207
+ def render(text, words, bursts, dur_s, timed=True, drop_bursts=False, trailing=True,
208
+ blank_text=False):
209
+ """-> (script, meta). `timed=False` keeps burst labels but omits every number.
210
+
211
+ `blank_text=True` emits `...` in place of every SPEECH CHUNK and leaves every tag -- pauses,
212
+ per-segment durations, burst labels and their lengths -- exactly where it was. It exists for
213
+ the classifier-free-guidance DPO pairs, where the two clips of a pair say different words and
214
+ the preference must therefore not be decidable from the words. Nothing else about the
215
+ rendering changes: the segmentation, the numbers and their sum are computed from the real text
216
+ and the real word timings, so the emitted budget still describes the real clip.
217
+ """
218
+ text = text or ""
219
+ ev, aw = build_events(text, words, bursts, dur_s)
220
+ # `not aw` used to be part of this guard, which made the wordless branch of build_events
221
+ # (line ~131, "no aligned words -> the events ARE the bursts") unreachable: a clip that is
222
+ # nothing but a vocal burst rendered as its empty text instead of as `(chuckle, 0.5 seconds)`.
223
+ # `not ev` is equivalent for every other input -- with no words and no bursts build_events
224
+ # returns an empty list anyway -- so this only changes rows that are wordless AND carry a
225
+ # burst: 709 of the 3,144,739 corpus rows, plus the standalone-burst training set.
226
+ if not ev:
227
+ head = ("..." if text.strip() else "") if blank_text else text.strip()
228
+ return head, {"n_seg": 0, "n_burst": 0, "n_pause": 0, "n_burst_untimed": 0,
229
+ "timed": False, "acc": 0.0}
230
+
231
+ parts, t, cut = [], 0.0, 0
232
+ n_seg = n_burst = n_pause = n_untimed = 0
233
+ acc = 0.0
234
+ for k, e in enumerate(ev):
235
+ if e["kind"] == "burst" and drop_bursts:
236
+ continue # its span folds into the surrounding pause
237
+ if e["kind"] == "burst" and not e["timed"]:
238
+ parts.append(f"({e['label'].lower() or 'vocal burst'})")
239
+ n_burst += 1
240
+ n_untimed += 1
241
+ continue
242
+ gap = e["s"] - t
243
+ if timed and gap >= PAUSE_MIN:
244
+ parts.append(f"[{_fmt(gap)} seconds pause]")
245
+ acc += round(gap, 1)
246
+ n_pause += 1
247
+ if e["kind"] == "speech":
248
+ is_last_speech = not any(x["kind"] == "speech" for x in ev[k + 1:])
249
+ end_char = len(text) if is_last_speech else aw[e["hi"]][1]
250
+ chunk = text[cut:end_char].strip()
251
+ cut = end_char
252
+ if blank_text:
253
+ chunk = "..." if chunk else ""
254
+ d = e["e"] - e["s"]
255
+ if timed:
256
+ parts.append(f"[{_fmt(d)} seconds duration]")
257
+ acc += round(d, 1)
258
+ if chunk:
259
+ parts.append(chunk)
260
+ n_seg += 1
261
+ t = e["e"]
262
+ else:
263
+ lab = e["label"].lower() or "vocal burst"
264
+ parts.append(f"({lab}, {_fmt(e['dur'])} seconds)" if timed else f"({lab})")
265
+ if timed:
266
+ acc += round(e["dur"], 1)
267
+ n_burst += 1
268
+ t = max(t, e["s"] + e["dur"])
269
+ if timed and trailing and dur_s and (float(dur_s) - t) >= PAUSE_MIN:
270
+ parts.append(f"[{_fmt(float(dur_s) - t)} seconds pause]")
271
+ acc += round(float(dur_s) - t, 1)
272
+ n_pause += 1
273
+
274
+ script = re.sub(r"\s{2,}", " ", " ".join(p for p in parts if p)).strip()
275
+ return script, {"n_seg": n_seg, "n_burst": n_burst, "n_pause": n_pause,
276
+ "n_burst_untimed": n_untimed, "timed": bool(timed), "acc": round(acc, 3)}
277
+
278
+
279
+ # ---------------------------------------------------------------- independent verification
280
+ _TAG = re.compile(r"\[(\d+\.\d) seconds (?:pause|duration)\]")
281
+ _BUR = re.compile(r"\([^,()]*, (\d+\.\d) seconds\)")
282
+
283
+
284
+ def verify(script, dur_s, text=None):
285
+ """Re-parses the EMITTED string. Deliberately does not reuse the renderer's accumulator, so
286
+ a bookkeeping error in the renderer cannot hide behind its own arithmetic."""
287
+ tot = sum(float(m.group(1)) for m in _TAG.finditer(script))
288
+ tot += sum(float(m.group(1)) for m in _BUR.finditer(script))
289
+ stripped = re.sub(r"\([^)]*\)", " ", re.sub(r"\[[^\]]*\]", " ", script))
290
+ stripped = re.sub(r"\s+", " ", stripped).strip()
291
+ return {"sum": round(tot, 2), "dur": round(float(dur_s or 0), 2),
292
+ "residual": round(float(dur_s or 0) - tot, 2), "text_out": stripped}
293
+
294
+
295
+ # ---------------------------------------------------------------- script -> plan
296
+ _ITEM = re.compile(r"\[(\d+\.\d) seconds (pause|duration)\]|\(([^()]*?), (\d+\.\d) seconds\)|\(([^()]*?)\)")
297
+
298
+
299
+ def parse_script(script):
300
+ """Read an emitted timed script back into a plan.
301
+
302
+ Used by the GRPO reward: the prompt states WHERE each burst should fall and HOW LONG it and
303
+ every utterance should be, and the reward has to compare that against what the detector found
304
+ in the generated audio. Parsing the emitted string rather than keeping a parallel structure
305
+ means the reward can only ever score what the model was actually shown.
306
+
307
+ -> (items, total_s) where each item is
308
+ {"kind": "pause"|"speech"|"burst", "t": start_s, "dur": s, "label": str|None}
309
+ """
310
+ items, t = [], 0.0
311
+ for m in _ITEM.finditer(script or ""):
312
+ if m.group(1) is not None:
313
+ d = float(m.group(1))
314
+ items.append({"kind": m.group(2), "t": t, "dur": d, "label": None})
315
+ t += d
316
+ elif m.group(3) is not None:
317
+ d = float(m.group(4))
318
+ items.append({"kind": "burst", "t": t, "dur": d, "label": m.group(3).strip()})
319
+ t += d
320
+ else:
321
+ items.append({"kind": "burst", "t": t, "dur": None, "label": m.group(5).strip()})
322
+ return items, round(t, 2)
323
+
324
+
325
+ def prompted_bursts(script):
326
+ items, _ = parse_script(script)
327
+ return [(i["label"], i["t"], i["dur"]) for i in items
328
+ if i["kind"] == "burst" and i["dur"] is not None]
code/caption_curriculum_dataset.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Lazy container-backed loader for the multi-caption S1->S10 curriculum."""
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import random
10
+
11
+ import numpy as np
12
+
13
+ from timed_prompt import repair_prompt
14
+
15
+
16
+ TAR_MEMBER_MAP = "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/tar_member_map/manifest.json"
17
+ FAMILIES = ("general_script", "bude_v1", "bude_v1_1", "timbre", "voice_tagging", "gemma", "transcript_only")
18
+ DESC_DTYPE = np.dtype([
19
+ ("base", "<u4"), ("reference", "<u4"), ("annotation", "<u4"),
20
+ ("family", "u1"), ("surface", "u1"), ("mode", "u1"), ("reserved", "u1"),
21
+ ])
22
+ MISSING = np.iinfo(np.uint32).max
23
+
24
+
25
+ class JsonlOffsets:
26
+ def __init__(self, records: str | Path, offsets: str | Path):
27
+ self.path = Path(records)
28
+ self.offsets = np.load(offsets, mmap_mode="r", allow_pickle=False)
29
+ self.fd = os.open(self.path, os.O_RDONLY)
30
+ assert self.offsets.dtype == np.int64 and int(self.offsets[0]) == 0
31
+ assert int(self.offsets[-1]) == self.path.stat().st_size
32
+
33
+ def row(self, index: int) -> dict:
34
+ start, end = int(self.offsets[index]), int(self.offsets[index + 1])
35
+ return json.loads(os.pread(self.fd, end - start, start))
36
+
37
+
38
+ class PhysicalBase(JsonlOffsets):
39
+ def __init__(self, manifest: str | Path):
40
+ path = Path(manifest)
41
+ spec = json.loads(path.read_text())
42
+ root = path.parent
43
+ super().__init__(root / spec["records"], root / spec["offsets"])
44
+
45
+
46
+ def shuffled(tags: list[str], uid: str, stage: str, presentation: int, family: str) -> list[str]:
47
+ result = list(tags)
48
+ seed = int.from_bytes(hashlib.blake2b(
49
+ f"20260923|{stage}|{uid}|{family}|{presentation}".encode(), digest_size=8).digest(), "little")
50
+ random.Random(seed).shuffle(result)
51
+ return result
52
+
53
+
54
+ def caption_prompt(caption: str, transcript: str) -> str:
55
+ caption = " ".join(str(caption).replace('"', "'").split())
56
+ assert caption
57
+ return f'CAPTION: {caption}\nTRANSCRIPT: "{transcript}"'
58
+
59
+
60
+ class CaptionCurriculumRecords:
61
+ def __init__(self, manifest: str | Path):
62
+ self.manifest_path = Path(manifest)
63
+ self.spec = json.loads(self.manifest_path.read_text())
64
+ assert self.spec["status"] == "complete"
65
+ assert self.spec["format"] == "m2-caption-curriculum-v1"
66
+ root = self.manifest_path.parent
67
+ self.phase = self.spec["stage"]
68
+ self.descriptors = np.load(root / self.spec["descriptors"], mmap_mode="r", allow_pickle=False)
69
+ self.order = np.load(root / self.spec["order"], mmap_mode="r", allow_pickle=False)
70
+ assert self.descriptors.dtype == DESC_DTYPE
71
+ assert len(self.descriptors) == len(self.order) == self.spec["presentations"]
72
+ self.base = PhysicalBase(self.spec["base_manifest"])
73
+ assets = json.loads(Path(self.spec["assets_manifest"]).read_text())
74
+ assert assets["status"] == "complete"
75
+ asset_root = Path(self.spec["assets_manifest"]).parent
76
+ self.annotations = JsonlOffsets(asset_root / assets["annotations"]["records"],
77
+ asset_root / assets["annotations"]["offsets"])
78
+ self.references = JsonlOffsets(asset_root / assets["references"]["records"],
79
+ asset_root / assets["references"]["offsets"])
80
+
81
+ import sys
82
+ sys.path.insert(0, "/e/scratch/reformo/schuhmann1_moss/code/m2_20k_score")
83
+ from dataset import ShardReader
84
+ from tar_member_map import TarMemberMap
85
+
86
+ class MappedShardReader(ShardReader):
87
+ def __init__(self):
88
+ super().__init__(capacity=16)
89
+ self.packed_members = TarMemberMap(TAR_MEMBER_MAP)
90
+
91
+ def member(self, path, name):
92
+ offset, size = self.packed_members.locate(path, name)
93
+ return self.bytes_at(path, offset, size)
94
+
95
+ self.reader = MappedShardReader()
96
+
97
+ def __len__(self):
98
+ return len(self.order)
99
+
100
+ def example(self, index: int, packer):
101
+ descriptor = self.descriptors[int(self.order[index])]
102
+ base = self.base.row(int(descriptor["base"]))
103
+ uid = base["uid"]
104
+ family = FAMILIES[int(descriptor["family"])]
105
+ mode = "reference" if int(descriptor["mode"]) else "instruction"
106
+ meta = dict(self.reader.record(base["record_locator"]))
107
+ assert meta["uid"] == uid
108
+ meta["frames"] = int(meta.get("moss_frames", base["target_locator"]["full_frames"]))
109
+ target = self.reader.codes(base["target_locator"])
110
+ assert len(target) == meta["frames"]
111
+ transcript = str(meta["text"])
112
+
113
+ annotation = None
114
+ if int(descriptor["annotation"]) != MISSING:
115
+ annotation = self.annotations.row(int(descriptor["annotation"]))
116
+ assert annotation["uid"] == uid
117
+
118
+ prompt_info = {"timed": False, "eligible": False, "duration_tags": 0,
119
+ "format_id": "m2-multiformat-caption-v1-20260923"}
120
+ form = "B"
121
+ if family == "general_script":
122
+ meta["prompt"], prompt_info = repair_prompt(
123
+ meta, caption=None, form="A", mode=mode,
124
+ presentation=int(index), phase=self.phase)
125
+ form = "A"
126
+ elif family == "bude_v1":
127
+ assert base.get("bude_caption")
128
+ meta["prompt"] = caption_prompt(base["bude_caption"], transcript)
129
+ elif family == "bude_v1_1":
130
+ meta["prompt"] = caption_prompt(annotation["bude"], transcript)
131
+ elif family in ("timbre", "voice_tagging"):
132
+ prefix = "timbre" if family == "timbre" else "voice"
133
+ tags = shuffled(annotation[prefix + "_tags"], uid, self.phase, int(index), family)
134
+ prose = annotation[prefix + "_prose"]
135
+ surface = int(descriptor["surface"])
136
+ if surface == 0:
137
+ assert tags
138
+ body = ", ".join(tags)
139
+ elif surface == 1:
140
+ body = prose
141
+ else:
142
+ assert tags and prose
143
+ body = "Tags: " + ", ".join(tags) + ". Caption: " + prose
144
+ meta["prompt"] = caption_prompt(body, transcript)
145
+ elif family == "gemma":
146
+ meta["prompt"] = caption_prompt(annotation["gemma_caption"], transcript)
147
+ elif family == "transcript_only":
148
+ meta["prompt"] = f'TRANSCRIPT: "{transcript}"'
149
+ form = "A"
150
+ else:
151
+ raise AssertionError(family)
152
+
153
+ reference = None
154
+ if mode == "reference":
155
+ assert int(descriptor["reference"]) != MISSING
156
+ reference_row = self.references.row(int(descriptor["reference"]))
157
+ assert reference_row["uid"] == uid and reference_row["reference_uid"] != uid
158
+ reference = self.reader.codes(reference_row["locator"])
159
+ assert 0 < len(reference) <= 37
160
+
161
+ result = packer.pack_mode(meta, target, mode, reference)
162
+ result["accounting"] = {
163
+ "uid": uid, "mode": mode, "form": form, "family": family,
164
+ "frames": len(target), "target_audio_hours": float(meta["dur_s"]) / 3600,
165
+ "reference_frames": len(reference) if reference is not None else 0,
166
+ "prompt_timed": bool(prompt_info["timed"]),
167
+ "prompt_eligible": bool(prompt_info["eligible"]),
168
+ "duration_tags": int(prompt_info["duration_tags"]),
169
+ "prompt_format_id": (prompt_info["format_id"] if family == "general_script"
170
+ else "m2-multiformat-caption-v1-20260923"),
171
+ }
172
+ return result
code/cascade_dataset.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """M2-600M cascade adapter with deterministic timed-prompt repair.
3
+
4
+ Audio/code targets and manifests remain immutable. Every presentation passes
5
+ through ``timed_prompt.repair_prompt``; repair facts are returned in accounting
6
+ so the trainer can prove the observed timed/untimed mixture.
7
+ """
8
+ import json
9
+ import os
10
+ import numpy as np
11
+ from timed_prompt import repair_prompt
12
+
13
+ MODES = ('instruction', 'reference')
14
+ TAR_MEMBER_MAP = '/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/tar_member_map/manifest.json'
15
+
16
+ class CascadeRecords:
17
+ def __init__(self, manifest):
18
+ from pathlib import Path
19
+ self.manifest_path = Path(manifest)
20
+ spec = json.loads(self.manifest_path.read_text())
21
+ assert spec['status'] == 'complete' and spec['presentations'] > 0
22
+ root = self.manifest_path.parent
23
+ self.spec = spec
24
+ self.phase = str(spec.get('phase') or spec.get('ladder_stage') or self.manifest_path.parent.name)
25
+ self.offsets = np.load(root / spec['offsets'], mmap_mode='r', allow_pickle=False)
26
+ assert len(self.offsets) == spec['presentations'] + 1
27
+ assert self.offsets.dtype == np.int64 and int(self.offsets[0]) == 0
28
+ self.order = np.load(root / spec['order'], mmap_mode='r', allow_pickle=False)
29
+ assert len(self.order) == spec['presentations']
30
+ assert self.order.dtype in (np.dtype('uint32'), np.dtype('int64'))
31
+ # Full uniqueness is established once by the manifest verifier. Never
32
+ # materialize 48M Python ints/sets independently on all 128 DDP ranks.
33
+ assert int(self.order.min()) == 0 and int(self.order.max()) == spec['presentations'] - 1
34
+ self.path = root / spec['records']
35
+ self.fd = os.open(self.path, os.O_RDONLY)
36
+ assert int(self.offsets[-1]) == self.path.stat().st_size
37
+ if 'records_bytes' in spec:
38
+ assert self.path.stat().st_size == spec['records_bytes']
39
+ assert (root / spec['offsets']).stat().st_size == spec['offsets_bytes']
40
+ assert (root / spec['order']).stat().st_size == spec['order_bytes']
41
+ import sys
42
+ sys.path.insert(0, '/e/scratch/reformo/schuhmann1_moss/code/m2_20k_score')
43
+ from dataset import ShardReader
44
+ from tar_member_map import TarMemberMap
45
+
46
+ class MappedShardReader(ShardReader):
47
+ def __init__(self):
48
+ # ShardReader uses the same bounded capacity for Parquet
49
+ # reference tables as for TAR member dictionaries. TAR
50
+ # dictionaries are overridden below, but Parquet tables still
51
+ # need a non-zero LRU (capacity=0 caused an immediate eviction
52
+ # followed by move_to_end(KeyError) in S1 smoke 1892952).
53
+ super().__init__(capacity=16)
54
+ self.packed_members = TarMemberMap(TAR_MEMBER_MAP)
55
+
56
+ def member(self, path, name):
57
+ offset, size = self.packed_members.locate(path, name)
58
+ return self.bytes_at(path, offset, size)
59
+
60
+ self.reader = MappedShardReader()
61
+
62
+ def __len__(self):
63
+ return len(self.order)
64
+
65
+ def row(self, index):
66
+ j = int(self.order[index])
67
+ start, end = int(self.offsets[j]), int(self.offsets[j + 1])
68
+ return json.loads(os.pread(self.fd, end - start, start))
69
+
70
+ def example(self, index, packer):
71
+ row = self.row(index)
72
+ mode = row['mode']
73
+ form = row.get('form', 'A')
74
+ assert mode in MODES, row['uid']
75
+ assert form in ('A', 'B'), row['uid']
76
+ meta = self.reader.record(row['record_locator'])
77
+ assert meta['uid'] == row['uid']
78
+ if 'moss_frames' in meta:
79
+ meta['frames'] = int(meta['moss_frames'])
80
+ else:
81
+ # Sidecar element JSONs carry no moss_frames key (pre-upload state);
82
+ # frame count comes from the manifest locator (validated in codes()).
83
+ meta['frames'] = int(row['target_locator']['full_frames'])
84
+ codes = self.reader.codes(row['target_locator'])
85
+ assert len(codes) == meta['frames']
86
+ meta = dict(meta)
87
+ meta['prompt'], prompt_info = repair_prompt(
88
+ meta, caption=row.get('bude_caption'), form=form, mode=mode,
89
+ presentation=int(index), phase=self.phase)
90
+ reference = None
91
+ if mode == 'reference':
92
+ assert row.get('reference_uid') and row['reference_uid'] != row['uid']
93
+ assert row.get('reference_locator')
94
+ reference = self.reader.codes(row['reference_locator'])
95
+ assert 0 < len(reference) <= 37
96
+ result = packer.pack_mode(meta, codes, mode, reference)
97
+ result['accounting'] = {'uid': row['uid'], 'mode': mode, 'form': form,
98
+ 'frames': len(codes),
99
+ 'target_audio_hours': float(meta['dur_s']) / 3600,
100
+ 'reference_frames': len(reference) if reference is not None else 0,
101
+ 'prompt_timed': bool(prompt_info['timed']),
102
+ 'prompt_eligible': bool(prompt_info['eligible']),
103
+ 'duration_tags': int(prompt_info['duration_tags']),
104
+ 'prompt_format_id': prompt_info['format_id']}
105
+ return result
code/conditioning.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Projected score tokens. Missing measurements are masked, never imputed scores.
2
+
3
+ The schema file pins names, units and measurement scopes. A tokenizer reserves
4
+ six textual marker IDs inside M2's existing padded vocabulary; its original
5
+ embedding rows and all original model weights remain loadable without resizing.
6
+ """
7
+ from contextlib import contextmanager
8
+ import json
9
+ from pathlib import Path
10
+ import sys
11
+ import torch
12
+ from torch import nn
13
+
14
+ SMALL = Path('/e/scratch/reformo/schuhmann1_moss/code/small_tts')
15
+ sys.path.insert(0, str(SMALL))
16
+ MARKERS = ('<|score_global_start|>', '<|score_global_slot|>', '<|score_global_end|>',
17
+ '<|score_sentence_start|>', '<|score_sentence_slot|>', '<|score_sentence_end|>')
18
+ GLOBAL_TOKENS = 5
19
+ LOCAL_TOKENS = 10
20
+
21
+
22
+ def install_markers(tokenizer, vocab_size):
23
+ import inspect
24
+ parameters = inspect.signature(tokenizer.add_special_tokens).parameters
25
+ option = ('replace_extra_special_tokens' if 'replace_extra_special_tokens' in parameters
26
+ else 'replace_additional_special_tokens')
27
+ tokenizer.add_special_tokens({'additional_special_tokens': list(MARKERS)}, **{option: False})
28
+ ids = dict(zip(MARKERS, tokenizer.convert_tokens_to_ids(list(MARKERS))))
29
+ assert len(set(ids.values())) == 6 and all(0 <= x < vocab_size for x in ids.values()), ids
30
+ for marker, token in ids.items():
31
+ assert tokenizer.encode(marker, add_special_tokens=False) == [token]
32
+ return ids
33
+
34
+
35
+ def feature_vector(values, dimensions):
36
+ """Return values scaled by documented bounds and an explicit validity mask.
37
+
38
+ Missing values become numerical zero only alongside mask=0. Normalization
39
+ bounds are scale anchors, not clipping bounds: actual regression outputs can
40
+ exceed nominal score ranges. Only non-finite measurements (or violations of
41
+ explicitly configured hard bounds) are rejected.
42
+ """
43
+ import math
44
+ scaled, mask = [], []
45
+ for dim in dimensions:
46
+ value = values.get(dim['name'])
47
+ valid = value is not None
48
+ if valid:
49
+ value = float(value)
50
+ low, high = map(float, dim['normalization_bounds'])
51
+ legal = dim.get('hard_bounds')
52
+ if not math.isfinite(value) or high <= low or (legal is not None and not legal[0] <= value <= legal[1]):
53
+ raise ValueError(f'Invalid measured feature {dim["name"]}={value}')
54
+ value = 2 * (value - low) / (high - low) - 1
55
+ scaled.append(value if valid else 0.0)
56
+ mask.append(float(valid))
57
+ return scaled + mask
58
+
59
+
60
+ class ScoreConditioner(nn.Module):
61
+ def __init__(self, global_dims, local_dims, hidden=1024):
62
+ super().__init__()
63
+ self.global_dims, self.local_dims = global_dims, local_dims
64
+ self.hidden = hidden
65
+ self.global_projection = nn.Sequential(nn.Linear(2 * global_dims, 256), nn.SiLU(),
66
+ nn.Linear(256, GLOBAL_TOKENS * hidden))
67
+ self.local_projection = nn.Sequential(nn.Linear(2 * local_dims, 512), nn.SiLU(),
68
+ nn.Linear(512, LOCAL_TOKENS * hidden))
69
+ self.boundaries = nn.Embedding(4, hidden)
70
+ for module in self.modules():
71
+ if isinstance(module, (nn.Linear, nn.Embedding)):
72
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
73
+ if isinstance(module, nn.Linear):
74
+ nn.init.zeros_(module.bias)
75
+
76
+ def forward(self, global_values, local_values):
77
+ # At least one padded sentence keeps the same DDP graph in every mode.
78
+ B, S, _ = local_values.shape
79
+ g = self.global_projection(global_values).reshape(B, GLOBAL_TOKENS, self.hidden)
80
+ local = self.local_projection(local_values).reshape(B, S, LOCAL_TOKENS, self.hidden)
81
+ boundary = self.boundaries.weight.to(g.dtype)
82
+ gs = boundary[0].reshape(1, 1, -1).expand(B, 1, -1)
83
+ ge = boundary[1].reshape(1, 1, -1).expand(B, 1, -1)
84
+ ls = boundary[2].reshape(1, 1, 1, -1).expand(B, S, 1, -1)
85
+ le = boundary[3].reshape(1, 1, 1, -1).expand(B, S, 1, -1)
86
+ local = torch.cat([ls, local, le], dim=2).reshape(B, S * (LOCAL_TOKENS + 2), self.hidden)
87
+ # Bank index zero is not a score token. Gradients still traverse the
88
+ # complete bank with zero contribution when a batch has no score mode.
89
+ zero = torch.zeros(B, 1, self.hidden, device=g.device, dtype=g.dtype)
90
+ return torch.cat([zero, gs, g, ge, local], dim=1)
91
+
92
+
93
+ def scored_class():
94
+ import moss_small
95
+ parent = moss_small.MossTTSSmallModel.cls()
96
+
97
+ class ScoreM2(parent):
98
+ def __init__(self, config, schema):
99
+ super().__init__(config)
100
+ self.score_conditioner = ScoreConditioner(len(schema['global_dimensions']), len(schema['local_dimensions']),
101
+ hidden=int(config.hidden_size))
102
+ self._generation_conditioning = None
103
+
104
+ def score_embeddings(self, input_ids, conditioning):
105
+ embeds = super()._build_inputs_embeds(input_ids)
106
+ if conditioning is None:
107
+ return embeds
108
+ values, local, index = conditioning
109
+ bank = self.score_conditioner(values, local).to(embeds.dtype)
110
+ if index.shape[1] != input_ids.shape[1]:
111
+ # Cached generation after prefill has only new audio rows.
112
+ if input_ids.shape[1] != 1:
113
+ raise ValueError('Conditioning positions do not match the prompt; KV-cache generation is required')
114
+ return embeds
115
+ scores = bank.gather(1, index.unsqueeze(-1).expand(-1, -1, bank.shape[-1]))
116
+ return torch.where(index.unsqueeze(-1).gt(0), scores, embeds)
117
+
118
+ def _build_inputs_embeds(self, input_ids):
119
+ return self.score_embeddings(input_ids, self._generation_conditioning)
120
+
121
+ def forward(self, input_ids=None, attention_mask=None, score_conditioning=None, **kwargs):
122
+ if score_conditioning is not None:
123
+ kwargs['inputs_embeds'] = self.score_embeddings(input_ids, score_conditioning)
124
+ input_ids = None
125
+ return super().forward(input_ids=input_ids, attention_mask=attention_mask, **kwargs)
126
+
127
+ @contextmanager
128
+ def generation_scores(self, conditioning):
129
+ if self._generation_conditioning is not None:
130
+ raise RuntimeError('Overlapping generation on one score-conditioned model is unsupported')
131
+ self._generation_conditioning = conditioning
132
+ try:
133
+ yield
134
+ finally:
135
+ self._generation_conditioning = None
136
+
137
+ return ScoreM2
138
+
139
+
140
+ def load_start_model(schema_path, checkpoint, log=print):
141
+ import moss_small
142
+ schema = json.loads(Path(schema_path).read_text())
143
+ config = moss_small.make_config('M2')
144
+ model = scored_class()(config, schema)
145
+ state = torch.load(Path(checkpoint) / 'model_bf16.pt', map_location='cpu', weights_only=True)
146
+ missing, unexpected = model.load_state_dict(state, strict=False)
147
+ expected = {'score_conditioner.' + name for name in model.score_conditioner.state_dict()}
148
+ if set(missing) != expected or unexpected:
149
+ raise RuntimeError(f'Initial M2 checkpoint mismatch: missing={missing}, unexpected={unexpected}')
150
+ del state
151
+ model.tie_weights()
152
+ log(f'Loaded final M2 checkpoint {checkpoint}; new score parameters={sum(p.numel() for p in model.score_conditioner.parameters())}')
153
+ return model, config, schema
code/continuation_control.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pure control rules shared by the real trainer and its recovery verifier."""
2
+ import hashlib
3
+ import json
4
+ import math
5
+ from pathlib import Path
6
+
7
+
8
+ def sha(path):
9
+ result=hashlib.sha256()
10
+ with Path(path).open('rb') as stream:
11
+ for block in iter(lambda:stream.read(8<<20),b''):result.update(block)
12
+ return result.hexdigest()
13
+
14
+
15
+ def complete_checkpoint(directory,contract=None,world=None,content=False):
16
+ directory=Path(directory);meta=json.loads((directory/'COMPLETE.json').read_text())
17
+ assert meta['step']>=1
18
+ if contract is not None:assert meta['contract']==contract
19
+ if world is not None:assert meta['world']==world
20
+ assert (directory/'training_state.pt').stat().st_size==meta['state_bytes']>0
21
+ if content:assert sha(directory/'training_state.pt')==meta['state_sha256']
22
+ if 'export_bytes' in meta:
23
+ assert (directory/'model_bf16.pt').stat().st_size==meta['export_bytes']>0
24
+ if content:assert sha(directory/'model_bf16.pt')==meta['export_sha256']
25
+ return meta
26
+
27
+
28
+ def latest_checkpoint(root,contract,world):
29
+ """A crash after directory rename but before pointer update is recoverable."""
30
+ found=[]
31
+ for directory in Path(root).glob('step-*'):
32
+ # A named committed directory with invalid content is a real error;
33
+ # never silently rewind a possibly corrupted training history.
34
+ meta=complete_checkpoint(directory,contract,world)
35
+ assert directory.name==f'step-{meta["step"]:08d}'
36
+ found.append((meta['step'],directory))
37
+ return max(found)[1] if found else None
38
+
39
+
40
+ def epoch_plan(samples,world,per_gpu):
41
+ assert samples>0 and world>0 and per_gpu>0
42
+ batch=world*per_gpu;steps=math.ceil(samples/batch);tail=samples-(steps-1)*batch
43
+ assert tail>=world,'Every final rank must receive a real target; no silent tail drop/padding'
44
+ quarters=sorted({math.ceil(steps*q/4) for q in (1,2,3,4)})
45
+ return {'samples':samples,'world':world,'global_batch':batch,'steps':steps,'tail_samples':tail,
46
+ 'quarter_steps':quarters,'epochs':1,'presentations_per_target':1,'dropped_targets':0,'duplicated_targets':0}
47
+
48
+
49
+ def contract(plan_path,plan,world):
50
+ return {'plan_sha256':sha(plan_path),'schema_sha256':plan['schema_sha256'],'manifest_sha256':plan['manifest_sha256'],
51
+ 'world':world,'objective':'global_token_mean_v1','seed':plan['seed']}
code/continuous_train.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """One optimizer and one LR schedule across the complete S1->S10 ladder."""
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from contextlib import nullcontext
8
+ from datetime import datetime, timedelta
9
+ import hashlib
10
+ import json
11
+ import math
12
+ import os
13
+ from pathlib import Path
14
+ import random
15
+ import statistics
16
+ import sys
17
+ import time
18
+
19
+ SC = Path("/e/scratch/reformo/schuhmann1_moss")
20
+ HERE = Path(__file__).resolve().parent
21
+ V3 = Path("/e/home/jusers/schuhmann1/jupiter/m2_600m_tts_training/cascade_v3_timed")
22
+ M2 = SC / "code/m2_20k_score"
23
+ SMALL = SC / "code/small_tts"
24
+ for candidate in (str(HERE), str(V3), str(M2), str(SMALL)):
25
+ while candidate in sys.path:
26
+ sys.path.remove(candidate)
27
+ sys.path[:0] = [str(HERE), str(V3), str(M2), str(SMALL)]
28
+
29
+
30
+ def sha(path: str | Path) -> str:
31
+ digest = hashlib.sha256()
32
+ with Path(path).open("rb") as stream:
33
+ for block in iter(lambda: stream.read(8 << 20), b""):
34
+ digest.update(block)
35
+ return digest.hexdigest()
36
+
37
+
38
+ def validate(plan: dict, plan_path: Path) -> list[dict]:
39
+ assert plan["kind"] == "m2_continuous_large_talker_v1"
40
+ assert plan["architecture"] == "qwen3_0.6b_plus_fresh_sft3_width_talker"
41
+ assert plan["scheduler"] == "single_linear_warmup_then_cosine_no_stage_restarts"
42
+ assert plan["nodes"] >= 1 and plan["ranks_per_node"] in (1, 4)
43
+ assert plan["world_size"] == plan["nodes"] * plan["ranks_per_node"]
44
+ assert plan["global_batch"] == plan["world_size"] * plan["samples_per_gpu"]
45
+ assert plan["lr_backbone_peak"] == 8e-5
46
+ assert plan["lr_talker_peak"] == 2.4e-4
47
+ assert plan["warmup_steps"] >= 1 and 0 <= plan["lr_floor_factor"] <= 1
48
+ assert plan["initialization"]["semantic"] == "original_pretrained_Qwen3-0.6B"
49
+ assert plan["initialization"]["talker"] == "random_fresh"
50
+ assert plan["checkpoint_policy"] == "all_stage_boundaries_plus_interval"
51
+ expected_names = [f"S{i}" for i in range(1, 11)]
52
+ assert [stage["name"] for stage in plan["stages"]] == expected_names
53
+ specs = []
54
+ total_updates = 0
55
+ for stage in plan["stages"]:
56
+ spec = json.loads(Path(stage["manifest"]).read_text())
57
+ assert spec["status"] == "complete" and spec["presentations"] > 0
58
+ assert sha(stage["manifest"]) == stage["manifest_sha256"]
59
+ natural = math.ceil(spec["presentations"] / plan["global_batch"])
60
+ updates = int(stage.get("max_updates", natural))
61
+ assert 1 <= updates <= natural
62
+ assert updates == stage["updates"]
63
+ # A deliberately truncated smoke never reaches the natural final
64
+ # partial batch. Validate that tail only when this plan consumes the
65
+ # complete stage; every earlier update is a full global batch.
66
+ if updates == natural:
67
+ tail = spec["presentations"] - (natural - 1) * plan["global_batch"]
68
+ assert tail >= plan["world_size"]
69
+ total_updates += updates
70
+ specs.append(spec)
71
+ assert total_updates == plan["total_updates"]
72
+ assert plan["warmup_steps"] < total_updates
73
+ for name, expected in plan["code_sha256"].items():
74
+ source = HERE / name if name in ("continuous_train.py", "large_talker.py",
75
+ "caption_curriculum_dataset.py") else V3 / name
76
+ assert sha(source) == expected, (name, source)
77
+ return specs
78
+
79
+
80
+ def main() -> None:
81
+ parser = argparse.ArgumentParser()
82
+ parser.add_argument("plan")
83
+ args = parser.parse_args()
84
+ plan_path = Path(args.plan).resolve()
85
+ plan = json.loads(plan_path.read_text())
86
+
87
+ import numpy as np
88
+ import torch
89
+ import torch.distributed as dist
90
+ from torch.nn.parallel import DistributedDataParallel as DDP
91
+ from transformers import AutoTokenizer
92
+ from packing import ScorePacker
93
+ from plan import step_indices, microbatches, dynamic_weights
94
+ from continuation_control import latest_checkpoint, complete_checkpoint
95
+ from cascade_dataset import CascadeRecords
96
+ from caption_curriculum_dataset import CaptionCurriculumRecords
97
+ import moss_small
98
+ import state_io
99
+ import va_loss
100
+ from large_talker import build_fresh, parameter_counts
101
+
102
+ specs = validate(plan, plan_path)
103
+ rank, world, local = (int(os.environ[key]) for key in
104
+ ("SLURM_PROCID", "SLURM_NTASKS", "SLURM_LOCALID"))
105
+ assert world == int(plan["world_size"])
106
+ device_index = 0 if torch.cuda.device_count() == 1 else local
107
+ torch.cuda.set_device(device_index)
108
+ device = torch.device("cuda", device_index)
109
+ os.environ.update(RANK=str(rank), WORLD_SIZE=str(world), LOCAL_RANK=str(device_index))
110
+ dist.init_process_group("nccl", timeout=timedelta(minutes=30), device_id=device)
111
+ torch.set_num_threads(4)
112
+ if plan.get("deterministic", False):
113
+ torch.use_deterministic_algorithms(True)
114
+ torch.backends.cudnn.deterministic = True
115
+ torch.backends.cudnn.benchmark = False
116
+
117
+ def log(message: str) -> None:
118
+ if rank == 0:
119
+ print(datetime.now().astimezone().isoformat(timespec="seconds"), message, flush=True)
120
+
121
+ random.seed(plan["seed"])
122
+ np.random.seed(plan["seed"])
123
+ torch.manual_seed(plan["seed"])
124
+ torch.cuda.manual_seed_all(plan["seed"])
125
+ invocation = os.environ["SLURM_JOB_ID"] + os.environ.get("M2_INVOCATION_SUFFIX", "")
126
+ invocation_dir = Path(plan["output"]) / "invocations" / invocation
127
+ invocation_dir.mkdir(parents=True, exist_ok=True)
128
+ if rank == 0:
129
+ assert not (invocation_dir / "steps.jsonl").exists()
130
+
131
+ schema_path = SC / "out/m2_20k_score/score_schema.json"
132
+ schema = json.loads(schema_path.read_text())
133
+ model, config = build_fresh(schema, log=log)
134
+ counts = parameter_counts(model)
135
+ log("Fresh large-Talker model: " + json.dumps(counts, sort_keys=True))
136
+
137
+ from functools import partial
138
+ from torch.distributed.algorithms._checkpoint.checkpoint_wrapper import (
139
+ apply_activation_checkpointing, checkpoint_wrapper, CheckpointImpl)
140
+ for module in model.modules():
141
+ if hasattr(module, "gradient_checkpointing"):
142
+ module.gradient_checkpointing = False
143
+ apply_activation_checkpointing(
144
+ model,
145
+ check_fn=lambda module: type(module).__name__ == "MossQwen3DecoderLayer",
146
+ checkpoint_wrapper_fn=partial(checkpoint_wrapper,
147
+ checkpoint_impl=CheckpointImpl.NO_REENTRANT),
148
+ )
149
+
150
+ class Step(torch.nn.Module):
151
+ def __init__(self, base):
152
+ super().__init__()
153
+ self.base = base
154
+
155
+ def forward(self, input_ids, attention_mask, labels, score_conditioning,
156
+ weights, scale):
157
+ hidden = self.base(input_ids=input_ids, attention_mask=attention_mask,
158
+ score_conditioning=score_conditioning,
159
+ use_cache=False).last_hidden_state
160
+ loss, per = va_loss.compute_supervised_loss_from_hidden(
161
+ self.base, global_hidden_states=hidden, labels=labels,
162
+ channelwise_loss_weight=weights, return_per_channel=True)
163
+ return loss * scale, per
164
+
165
+ step_model = DDP(Step(model.to(device)).train(), device_ids=[device_index],
166
+ broadcast_buffers=False, find_unused_parameters=False,
167
+ gradient_as_bucket_view=True)
168
+ if plan["gradient_communication"] == "bfloat16":
169
+ from torch.distributed.algorithms.ddp_comm_hooks.default_hooks import bf16_compress_hook
170
+ step_model.register_comm_hook(None, bf16_compress_hook)
171
+ else:
172
+ assert plan["gradient_communication"] == "float32"
173
+
174
+ groups = {"backbone": [], "talker": []}
175
+ for name, parameter in model.named_parameters():
176
+ if not parameter.requires_grad:
177
+ continue
178
+ key = "backbone" if name.startswith(("transformer.", "text_lm_head.")) else "talker"
179
+ groups[key].append(parameter)
180
+ optimizer = torch.optim.AdamW([
181
+ {"params": groups["backbone"], "lr": plan["lr_backbone_peak"], "name": "backbone"},
182
+ {"params": groups["talker"], "lr": plan["lr_talker_peak"], "name": "talker"},
183
+ ], betas=(0.9, 0.95), eps=1e-8, weight_decay=0.1, foreach=False)
184
+
185
+ total_updates = int(plan["total_updates"])
186
+ warmup = int(plan["warmup_steps"])
187
+ floor = float(plan["lr_floor_factor"])
188
+
189
+ def lr_factor(step: int) -> float:
190
+ if step < warmup:
191
+ return (step + 1) / warmup
192
+ progress = min(1.0, (step - warmup) / max(1, total_updates - warmup))
193
+ return floor + (1.0 - floor) * 0.5 * (1.0 + math.cos(math.pi * progress))
194
+
195
+ scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_factor)
196
+ _, _, Processor = moss_small.export_classes()
197
+ processor = Processor(tokenizer=AutoTokenizer.from_pretrained(
198
+ moss_small.SFT3, trust_remote_code=True, local_files_only=True),
199
+ audio_tokenizer=None, model_config=config)
200
+ packer = ScorePacker(processor, config, schema)
201
+ datasets = []
202
+ for stage, spec in zip(plan["stages"], specs):
203
+ cls = CaptionCurriculumRecords if spec.get("format") == "m2-caption-curriculum-v1" else CascadeRecords
204
+ datasets.append(cls(stage["manifest"]))
205
+ assert all(len(dataset) == spec["presentations"]
206
+ for dataset, spec in zip(datasets, specs))
207
+
208
+ manifest_digest = hashlib.sha256("".join(
209
+ stage["manifest_sha256"] for stage in plan["stages"]).encode()).hexdigest()
210
+ contract = {
211
+ "plan_sha256": sha(plan_path), "schema_sha256": sha(schema_path),
212
+ "manifest_sha256": manifest_digest, "world": world,
213
+ "objective": "global_token_mean_continuous_ladder_v1", "seed": plan["seed"],
214
+ }
215
+ start_step = 0
216
+ selection = [None]
217
+ if rank == 0:
218
+ try:
219
+ directory = latest_checkpoint(Path(plan["output"]) / "checkpoints", contract, world)
220
+ if directory:
221
+ complete_checkpoint(directory, contract, world, content=True)
222
+ selection[0] = {"path": str(directory) if directory else None}
223
+ except Exception as error:
224
+ selection[0] = {"error": repr(error)}
225
+ dist.broadcast_object_list(selection, src=0)
226
+ assert "error" not in selection[0], selection[0]
227
+ if selection[0]["path"]:
228
+ start_step = state_io.load(selection[0]["path"], step_model, optimizer,
229
+ scheduler, contract)
230
+ log(f"Continuous resume point={start_step}/{total_updates}; one scheduler, no stage restarts")
231
+ if start_step == total_updates:
232
+ log(f"ALREADY_COMPLETE at {start_step}/{total_updates}")
233
+ dist.barrier(); dist.destroy_process_group(); return
234
+ assert start_step < total_updates
235
+
236
+ def prepare(dataset, phase, phase_step):
237
+ began = time.monotonic()
238
+ indices = step_indices(phase, phase_step, rank, world, len(dataset))
239
+ examples = [dataset.example(index, packer) for index in indices]
240
+ assert examples
241
+ assert max(len(example["input_ids"]) for example in examples) <= int(
242
+ getattr(config, "max_position_embeddings", 32768))
243
+ batches = microbatches(examples, plan["max_padded_tokens"], plan["max_examples"])
244
+ prepared = []
245
+ for examples_batch in batches:
246
+ batch = packer.collate(examples_batch)
247
+ frames = sum(example["accounting"]["frames"] for example in examples_batch)
248
+ assert int(batch["labels"][:, :, 1].ge(0).sum()) == frames
249
+ prepared.append((batch, frames, len(examples_batch)))
250
+ stats = [len(examples), sum(x["accounting"]["frames"] for x in examples),
251
+ sum(x["accounting"]["target_audio_hours"] for x in examples),
252
+ sum(x["accounting"]["reference_frames"] for x in examples),
253
+ sum(len(x["input_ids"]) for x in examples),
254
+ sum(batch["attention_mask"].numel() for batch, _, _ in prepared)]
255
+ stats += [sum(x["accounting"]["mode"] == mode for x in examples)
256
+ for mode in ("instruction", "reference")]
257
+ stats += [sum(x["accounting"]["form"] == form for x in examples)
258
+ for form in ("A", "B")]
259
+ allowed_prompt_ids = set(plan.get("allowed_prompt_format_ids", [plan["prompt_contract"]]))
260
+ assert all(x["accounting"]["prompt_format_id"] in allowed_prompt_ids for x in examples)
261
+ stats += [sum(x["accounting"]["prompt_timed"] for x in examples),
262
+ sum(x["accounting"]["prompt_eligible"] for x in examples),
263
+ sum(x["accounting"]["duration_tags"] for x in examples)]
264
+ return prepared, stats, time.monotonic() - began
265
+
266
+ def update(prepared, local_stats):
267
+ stats = torch.tensor(local_stats, dtype=torch.float64, device=device)
268
+ dist.all_reduce(stats)
269
+ values = stats.tolist()
270
+ total_samples, total_frames = int(values[0]), int(values[1])
271
+ optimizer.zero_grad(set_to_none=True)
272
+ local_loss = torch.zeros((), device=device)
273
+ per_sums = torch.zeros(13, device=device)
274
+ for index, (batch, frames, samples) in enumerate(prepared):
275
+ batch = {name: tuple(value.to(device, non_blocking=True) for value in content)
276
+ if name == "score_conditioning" else content.to(device, non_blocking=True)
277
+ for name, content in batch.items() if name not in ("modes", "meta")}
278
+ weights, scale = dynamic_weights(frames, samples, total_frames, total_samples, world)
279
+ context = step_model.no_sync() if index + 1 < len(prepared) else nullcontext()
280
+ with context:
281
+ with torch.autocast("cuda", dtype=torch.bfloat16):
282
+ loss, per = step_model(**batch, weights=weights, scale=scale)
283
+ assert torch.isfinite(loss)
284
+ loss.backward()
285
+ local_loss += loss.detach() / world
286
+ per_sums += per * torch.tensor([frames + samples] + [frames] * 12,
287
+ device=device)
288
+ gradient_norm = torch.nn.utils.clip_grad_norm_(step_model.parameters(), 1.0)
289
+ assert torch.isfinite(gradient_norm)
290
+ optimizer.step()
291
+ scheduler.step()
292
+ dist.all_reduce(local_loss)
293
+ dist.all_reduce(per_sums)
294
+ per_sums /= torch.tensor([total_frames + total_samples] + [total_frames] * 12,
295
+ device=device)
296
+ torch.cuda.synchronize()
297
+ return {
298
+ "loss": float(local_loss), "per_channel": per_sums.tolist(),
299
+ "global_samples": total_samples, "target_frames": total_frames,
300
+ "target_audio_hours": values[2], "reference_frames": int(values[3]),
301
+ "input_rows": int(values[4]), "padded_rows": int(values[5]),
302
+ "mode_counts": dict(zip(("instruction", "reference"), map(int, values[6:8]))),
303
+ "form_counts": dict(zip(("A", "B"), map(int, values[8:10]))),
304
+ "prompt_timed": int(values[10]), "prompt_eligible": int(values[11]),
305
+ "duration_tags": int(values[12]),
306
+ "prompt_format_id": plan["prompt_contract"],
307
+ "gradient_norm": float(gradient_norm),
308
+ "lr_backbone": float(optimizer.param_groups[0]["lr"]),
309
+ "lr_talker": float(optimizer.param_groups[1]["lr"]),
310
+ }
311
+
312
+ metrics = (invocation_dir / "steps.jsonl").open("a", buffering=1) if rank == 0 else None
313
+ stage_offsets = []
314
+ cursor = 0
315
+ for stage in plan["stages"]:
316
+ stage_offsets.append(cursor)
317
+ cursor += stage["updates"]
318
+ assert cursor == total_updates
319
+ global_quarters = {math.ceil(total_updates * q / 4) for q in (1, 2, 3, 4)}
320
+ stage_ends = {offset + stage["updates"] for offset, stage in zip(stage_offsets, plan["stages"])}
321
+ if plan.get("smoke_mode"):
322
+ # A full optimizer state is several GiB. The smoke proves both an early
323
+ # boundary checkpoint and the final checkpoint without writing ten
324
+ # redundant copies during its deliberately tiny stage transitions.
325
+ global_quarters = set()
326
+ stage_ends = set(map(int, plan["smoke_checkpoint_steps"]))
327
+ last_save = time.monotonic()
328
+ invocation_begin = time.monotonic()
329
+ final_step = start_step
330
+ best_loss = None
331
+ best_step = None
332
+ stopped = False
333
+ observed_times = []
334
+
335
+ with ThreadPoolExecutor(max_workers=1) as pool:
336
+ for stage_index, (stage, dataset, offset) in enumerate(
337
+ zip(plan["stages"], datasets, stage_offsets)):
338
+ phase = {"name": stage["name"], "offset": 0,
339
+ "global_batch": plan["global_batch"], "updates": stage["updates"]}
340
+ if start_step >= offset + stage["updates"]:
341
+ continue
342
+ pending = None
343
+ local_start = max(0, start_step - offset)
344
+ log(f"Entering {stage['name']} at local step {local_start}/{stage['updates']} "
345
+ f"without optimizer/scheduler reset")
346
+ for phase_step in range(local_start, stage["updates"]):
347
+ if pending is None:
348
+ pending = pool.submit(prepare, dataset, phase, phase_step)
349
+ began = time.monotonic()
350
+ ready, local_stats, preparation_seconds = pending.result()
351
+ data_wait_seconds = time.monotonic() - began
352
+ pending = None
353
+ if phase_step + 1 < stage["updates"]:
354
+ pending = pool.submit(prepare, dataset, phase, phase_step + 1)
355
+ result = update(ready, local_stats)
356
+ wall_seconds = time.monotonic() - began
357
+ timing = torch.tensor([wall_seconds, data_wait_seconds, preparation_seconds],
358
+ dtype=torch.float64, device=device)
359
+ dist.all_reduce(timing, op=dist.ReduceOp.MAX)
360
+ final_step = offset + phase_step + 1
361
+ observed_times.append(float(timing[0]))
362
+ result.update(
363
+ stage=stage["name"], stage_index=stage_index + 1,
364
+ stage_step=phase_step + 1, stage_updates=stage["updates"],
365
+ step=final_step, total_updates=total_updates,
366
+ steady=final_step > warmup, wall_seconds=float(timing[0]),
367
+ data_wait_seconds=float(timing[1]), preparation_seconds=float(timing[2]),
368
+ job=os.environ["SLURM_JOB_ID"], invocation=invocation,
369
+ )
370
+ if rank == 0:
371
+ metrics.write(json.dumps(result) + "\n")
372
+ log(f"{stage['name']} {phase_step+1}/{stage['updates']} global="
373
+ f"{final_step}/{total_updates} loss={result['loss']:.5f} "
374
+ f"wall={result['wall_seconds']:.3f}s")
375
+ if best_loss is None or result["loss"] < best_loss:
376
+ best_loss, best_step = result["loss"], final_step
377
+
378
+ interval_due = time.monotonic() - last_save >= plan["checkpoint_interval_seconds"]
379
+ deadline_due = time.monotonic() - invocation_begin >= plan["max_invocation_seconds"]
380
+ control = torch.tensor([int(interval_due), int(deadline_due)] if rank == 0 else [0, 0],
381
+ device=device)
382
+ dist.broadcast(control, src=0)
383
+ stopped = bool(control[1]) and final_step < total_updates
384
+ export = final_step in stage_ends or final_step in global_quarters
385
+ if export or bool(control[0]) or stopped or final_step == total_updates:
386
+ saved = state_io.save(Path(plan["output"]) / "checkpoints", step_model,
387
+ optimizer, scheduler, final_step, contract,
388
+ export=export or final_step == total_updates)
389
+ last_save = time.monotonic()
390
+ if rank == 0:
391
+ log("Committed retained checkpoint " + str(saved))
392
+ dist.barrier()
393
+ if stopped:
394
+ log(f"STOPPED_RESUMABLE at global step {final_step}")
395
+ break
396
+ if stopped:
397
+ break
398
+
399
+ peak = torch.tensor(torch.cuda.max_memory_allocated() / 2**30, device=device)
400
+ dist.all_reduce(peak, op=dist.ReduceOp.MAX)
401
+ median_step = statistics.median(observed_times)
402
+ threshold = plan.get("max_acceptable_median_step_seconds")
403
+ throughput_failed = bool(threshold is not None and median_step > float(threshold))
404
+ if rank == 0:
405
+ status = ("FAIL_THROUGHPUT" if throughput_failed else
406
+ ("PASS" if final_step == total_updates else "STOPPED_RESUMABLE"))
407
+ summary = {
408
+ "status": status, "job": os.environ["SLURM_JOB_ID"],
409
+ "invocation": invocation, "final_step": final_step,
410
+ "total_updates": total_updates, "one_continuous_schedule": True,
411
+ "optimizer_or_scheduler_restarts_at_stage_boundaries": 0,
412
+ "best_observed_loss_this_invocation": best_loss,
413
+ "best_observed_step_this_invocation": best_step,
414
+ "median_step_seconds_this_invocation": median_step,
415
+ "p90_step_seconds_this_invocation": float(np.quantile(observed_times, 0.9)),
416
+ "peak_allocated_gib_max_rank": float(peak), **counts,
417
+ "plan_sha256": sha(plan_path), "contract": contract,
418
+ }
419
+ (invocation_dir / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
420
+ log(status + " " + str(invocation_dir / "summary.json"))
421
+ dist.barrier()
422
+ dist.destroy_process_group()
423
+ if throughput_failed:
424
+ raise SystemExit(42)
425
+
426
+
427
+ if __name__ == "__main__":
428
+ main()
code/dataset.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bounded random access to indexed JSONL metadata and existing codec shards.
2
+
3
+ Only offsets are memory-mapped. No corpus-sized Python list or concatenated
4
+ audio/code tensor is copied into each GPU process. TAR member indices and
5
+ Parquet reference tables are kept in small per-thread LRU caches.
6
+ """
7
+ from collections import OrderedDict
8
+ import hashlib
9
+ import io
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+ import tarfile
14
+ import threading
15
+ import numpy as np
16
+
17
+
18
+ class ShardReader:
19
+ def __init__(self, capacity=16, file_capacity=None):
20
+ import resource
21
+ self.capacity = capacity
22
+ soft_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
23
+ self.file_capacity = file_capacity or max(32, min(2048, soft_limit // 2))
24
+ self.local = threading.local()
25
+
26
+ def cache(self):
27
+ if not hasattr(self.local, 'files'):
28
+ self.local.files = OrderedDict()
29
+ self.local.tables = OrderedDict()
30
+ self.local.members = OrderedDict()
31
+ return self.local
32
+
33
+ def bytes_at(self, path, offset, size):
34
+ state = self.cache()
35
+ if path not in state.files:
36
+ state.files[path] = os.open(path, os.O_RDONLY)
37
+ while len(state.files) > self.file_capacity:
38
+ _, fd = state.files.popitem(last=False)
39
+ os.close(fd)
40
+ fd = state.files[path]
41
+ state.files.move_to_end(path)
42
+ value = os.pread(fd, int(size), int(offset))
43
+ if len(value) != size:
44
+ raise IOError(f'Short shard read: {path}@{offset}+{size}')
45
+ return value
46
+
47
+ def member(self, path, name):
48
+ state = self.cache()
49
+ if path not in state.members:
50
+ with tarfile.open(path, 'r:') as archive:
51
+ state.members[path] = {m.name: (m.offset_data, m.size) for m in archive if m.isfile()}
52
+ while len(state.members) > self.capacity:
53
+ state.members.popitem(last=False)
54
+ state.members.move_to_end(path)
55
+ offset, size = state.members[path][name]
56
+ return self.bytes_at(path, offset, size)
57
+
58
+ def codes(self, locator):
59
+ kind = locator.get('kind', 'tar_moss_npy')
60
+ if kind == 'tar_moss_npy':
61
+ blob = (self.bytes_at(locator['path'], locator['offset'], locator['size'])
62
+ if 'offset' in locator else self.member(locator['path'], locator['member']))
63
+ try:
64
+ result = np.load(io.BytesIO(blob), allow_pickle=False)
65
+ except ValueError:
66
+ # Raw little-endian uint16 dump without NumPy header
67
+ # (sidecar packer wrote tobytes()); shape from byte count.
68
+ result = np.frombuffer(blob, dtype=np.uint16).reshape(-1, 12)
69
+ assert result.dtype == np.uint16
70
+ elif kind == 'local_moss_parquet':
71
+ import pyarrow.parquet as pq
72
+ state = self.cache()
73
+ key = (locator['path'], locator['codes_field'], locator['frames_field'])
74
+ if key not in state.tables:
75
+ state.tables[key] = pq.read_table(locator['path'], columns=[locator['codes_field'], locator['frames_field']])
76
+ while len(state.tables) > self.capacity:
77
+ state.tables.popitem(last=False)
78
+ state.tables.move_to_end(key)
79
+ row = state.tables[key].slice(locator['row_index'], 1).to_pylist()[0]
80
+ frames = int(row[locator['frames_field']])
81
+ result = np.frombuffer(row[locator['codes_field']], dtype='<i2').reshape(frames, 12)
82
+ else:
83
+ raise ValueError(f'Unsupported verified codec locator: {kind}')
84
+ assert result.ndim == 2 and result.shape[1] == 12 and len(result) > 0
85
+ assert int(result.min()) >= 0 and int(result.max()) < 1024
86
+ expected = locator.get('full_frames')
87
+ if expected is not None:
88
+ assert len(result) == expected
89
+ if 'crop_frames' in locator:
90
+ start, length = int(locator.get('crop_start_frame', 0)), int(locator['crop_frames'])
91
+ assert start >= 0 and length > 0 and start + length <= len(result)
92
+ result = result[start:start + length]
93
+ return result
94
+
95
+ def record(self, locator):
96
+ blob = (self.bytes_at(locator['path'], locator['offset'], locator['size'])
97
+ if 'offset' in locator else self.member(locator['path'], locator['member']))
98
+ return json.loads(blob)
99
+
100
+
101
+ class IndexedRecords:
102
+ def __init__(self, manifest):
103
+ self.manifest_path = Path(manifest)
104
+ self.spec = json.loads(self.manifest_path.read_text())
105
+ root = self.manifest_path.parent
106
+ self.offsets = np.load(root / self.spec['offsets'], mmap_mode='r', allow_pickle=False)
107
+ self.path = root / self.spec['records']
108
+ self.fd = os.open(self.path, os.O_RDONLY)
109
+ assert len(self.offsets) == self.spec['samples'] + 1
110
+ assert int(self.offsets[0]) == 0 and int(self.offsets[-1]) == self.path.stat().st_size
111
+ self.reader = ShardReader()
112
+
113
+ def __len__(self):
114
+ return len(self.offsets) - 1
115
+
116
+ def row(self, index):
117
+ start, end = int(self.offsets[index]), int(self.offsets[index + 1])
118
+ return json.loads(os.pread(self.fd, end - start, start))
119
+
120
+ def example(self, index, packer):
121
+ from modes import verify_record_mode
122
+ row = self.row(index)
123
+ verify_record_mode(row)
124
+ mode = row['mode']
125
+ meta = self.reader.record(row['record_locator'])
126
+ assert meta['uid'] == row['uid']
127
+ meta['frames'] = int(meta['moss_frames'])
128
+ codes = self.reader.codes(row['target_locator'])
129
+ assert len(codes) == meta['frames']
130
+ reference = None
131
+ if mode == 'reference':
132
+ assert row['reference_uid'] != row['uid'] and row['reference_evidence']
133
+ reference = self.reader.codes(row['reference_locator'])
134
+ assert 0 < len(reference) <= 37
135
+ elif mode == 'score':
136
+ meta['conditioning_measurements'] = row['conditioning_measurements']
137
+ result = packer.pack_mode(meta, codes, mode, reference)
138
+ result['accounting'] = {'uid': row['uid'], 'mode': mode, 'frames': len(codes),
139
+ 'target_audio_hours': float(meta['dur_s']) / 3600,
140
+ 'reference_frames': len(reference) if reference is not None else 0}
141
+ return result
142
+
143
+
144
+ def write_manifest(directory, rows, contract):
145
+ directory = Path(directory)
146
+ directory.mkdir(parents=True, exist_ok=True)
147
+ offsets, n, modes, duration = [0], 0, {}, 0.
148
+ path = directory / 'records.jsonl'
149
+ digest = hashlib.sha256()
150
+ seen = set()
151
+ with path.with_suffix('.jsonl.tmp').open('wb') as stream:
152
+ for row in rows:
153
+ assert row['uid'] not in seen, row['uid']
154
+ seen.add(row['uid'])
155
+ assert row['mode'] in ('instruction', 'reference', 'score')
156
+ blob = (json.dumps(row, ensure_ascii=False, separators=(',', ':')) + '\n').encode()
157
+ stream.write(blob); digest.update(blob); offsets.append(offsets[-1] + len(blob)); n += 1
158
+ modes[row['mode']] = modes.get(row['mode'], 0) + 1
159
+ duration += row.get('dur_s', 0.) / 3600
160
+ path.with_suffix('.jsonl.tmp').replace(path)
161
+ np.save(directory / 'offsets.npy', np.asarray(offsets, dtype=np.int64), allow_pickle=False)
162
+ result = dict(contract, records=path.name, offsets='offsets.npy', samples=n, modes=modes,
163
+ target_audio_hours=duration, records_sha256=digest.hexdigest())
164
+ temporary = directory / 'manifest.json.tmp'
165
+ temporary.write_text(json.dumps(result, indent=2) + '\n')
166
+ temporary.replace(directory / 'manifest.json')
167
+ return directory / 'manifest.json'
code/infer.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Single-clip Humaneness Voice Small inference; use on a CUDA GPU.
3
+
4
+ Example (from the model repository root):
5
+ python code/infer.py --stage S3 --prompt 'CAPTION: warm, amused narration\nTRANSCRIPT: "Hello there."' \
6
+ --text 'Hello there.' --frames 60 --output hello.wav
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import json
12
+ from pathlib import Path
13
+ import sys
14
+
15
+ ROOT = Path(__file__).resolve().parents[1]
16
+ sys.path.insert(0, str(ROOT / 'code'))
17
+
18
+
19
+ def main() -> None:
20
+ parser = argparse.ArgumentParser(description=__doc__)
21
+ parser.add_argument('--stage', choices=[f'S{i}' for i in range(1, 11)], default='S3')
22
+ parser.add_argument('--prompt', required=True, help='Literal GENERAL/SCRIPT, CAPTION/TRANSCRIPT or TRANSCRIPT text')
23
+ parser.add_argument('--text', required=True, help='The exact spoken transcript')
24
+ parser.add_argument('--frames', type=int, required=True, help='Frame budget at 12.5 frames/second')
25
+ parser.add_argument('--language', choices=('en', 'de'), default='en')
26
+ parser.add_argument('--reference-wav', type=Path, help='Optional distinct reference recording')
27
+ parser.add_argument('--seed', type=int, default=777)
28
+ parser.add_argument('--output', type=Path, required=True)
29
+ args = parser.parse_args()
30
+ assert args.frames > 0
31
+
32
+ import numpy as np
33
+ import soundfile as sf
34
+ import torch
35
+ import moss_small
36
+ from large_talker import build_fresh
37
+ from packing import ScorePacker, generated_audio
38
+
39
+ if not torch.cuda.is_available():
40
+ raise RuntimeError('This inference example requires CUDA')
41
+ device = torch.device('cuda:0')
42
+ torch.cuda.set_device(device)
43
+ moss_small.SFT3 = str(ROOT / 'assets/sft3')
44
+ moss_small.QWEN = str(ROOT / 'assets/qwen3')
45
+ # Full published model state includes the Qwen3 backbone weights. Do not
46
+ # download the separate original pretraining file merely to overwrite it.
47
+ moss_small.load_qwen_backbone = lambda model, log=print: None
48
+ schema = json.loads((ROOT / 'assets/score_schema.json').read_text())
49
+ model, config = build_fresh(schema, log=lambda _: None)
50
+ state = torch.load(ROOT / 'checkpoints' / args.stage / 'model_bf16.pt',
51
+ map_location='cpu', weights_only=True)
52
+ model.load_state_dict(state, strict=True)
53
+ model.tie_weights()
54
+ model = model.to(device, dtype=torch.bfloat16).eval()
55
+ del state
56
+
57
+ _, _, Processor = moss_small.export_classes()
58
+ processor = Processor.from_pretrained(
59
+ moss_small.SFT3, codec_path='OpenMOSS-Team/MOSS-Audio-Tokenizer-v2',
60
+ codec_weight_dtype='fp32', codec_compute_dtype='bf16')
61
+ processor.audio_tokenizer = processor.audio_tokenizer.to(device).eval()
62
+ packer = ScorePacker(processor, config, schema)
63
+ reference = None
64
+ if args.reference_wav:
65
+ reference = processor.encode_audios_from_path(args.reference_wav, n_vq=12)[0]
66
+ if not 0 < len(reference) <= 37:
67
+ raise ValueError('Reference must be 1–37 codec frames (at most 2.96 seconds)')
68
+ mode = 'reference' if reference is not None else 'instruction'
69
+ record = {'prompt': args.prompt, 'text': args.text, 'frames': args.frames,
70
+ 'lang': args.language}
71
+ example = packer.pack_mode(record, [], mode, reference, generation=True)
72
+ batch = packer.collate([example])
73
+ ids = batch['input_ids'].to(device)
74
+ mask = batch['attention_mask'].to(device)
75
+ conditioning = tuple(t.to(device) for t in batch['score_conditioning'])
76
+ torch.manual_seed(args.seed)
77
+ torch.cuda.manual_seed_all(args.seed)
78
+ with (torch.inference_mode(), model.generation_scores(conditioning),
79
+ torch.autocast('cuda', dtype=torch.bfloat16)):
80
+ result = model.generate(input_ids=ids, attention_mask=mask,
81
+ max_new_frames=args.frames + 60, do_sample=True,
82
+ audio_temperature=1.0, audio_top_p=0.95, audio_top_k=50,
83
+ audio_repetition_penalty=1.0, use_kv_cache=True)
84
+ codes = generated_audio(result, config).cpu()
85
+ if len(codes) < 2:
86
+ raise RuntimeError('Generated fewer than two codec frames')
87
+ waveform = processor.decode_audio_codes([codes.to(device)], return_stereo=False)[0]
88
+ wave = np.asarray(waveform.float().cpu().numpy()).reshape(-1)
89
+ sample_rate = int(processor.model_config.sampling_rate)
90
+ assert np.isfinite(wave).all()
91
+ assert abs(len(wave) / len(codes) - sample_rate / 12.5) <= 0.01 * sample_rate / 12.5
92
+ args.output.parent.mkdir(parents=True, exist_ok=True)
93
+ sf.write(args.output, wave, sample_rate, subtype='PCM_16')
94
+ print(json.dumps({'output': str(args.output), 'frames': len(codes),
95
+ 'duration_seconds': len(wave) / sample_rate, 'stage': args.stage}))
96
+
97
+
98
+ if __name__ == '__main__':
99
+ main()
code/large_talker.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Qwen3-0.6B semantic path plus a fresh SFT-3-width local Talker.
3
+
4
+ This is the existing M1 bridge architecture without loading the SFT-3 Talker
5
+ weights and without the experimental K4 memory module. Qwen starts from the
6
+ original pretrained Qwen3-0.6B weights; every local/audio/bridge parameter is
7
+ fresh and trainable.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from torch import nn
12
+
13
+
14
+ EXPECTED_LOCAL_TALKER_PARAMETERS = 112_764_928
15
+
16
+
17
+ def build_fresh(schema: dict, log=print):
18
+ import moss_small
19
+ from conditioning import scored_class
20
+
21
+ config = moss_small.make_config("M1")
22
+ model = scored_class()(config, schema)
23
+ moss_small.load_qwen_backbone(model, log=log)
24
+
25
+ # The score adapter is not part of this caption-conditioned run. It stays
26
+ # frozen and receives no score-token prompts, exactly as in the old ladder.
27
+ for parameter in model.score_conditioner.parameters():
28
+ parameter.requires_grad_(False)
29
+
30
+ names = (
31
+ "local_transformer.", "audio_embeddings.", "local_text_lm_head.",
32
+ "proj_in.", "proj_out.",
33
+ )
34
+ local = sum(parameter.numel() for name, parameter in model.named_parameters()
35
+ if name.startswith(names))
36
+ assert local == EXPECTED_LOCAL_TALKER_PARAMETERS, local
37
+ assert int(config.hidden_size) == 1024
38
+ assert int(config.local_hidden_size) == 2560
39
+ assert int(config.n_vq) == 12
40
+ for index in range(int(config.n_vq)):
41
+ assert model.audio_lm_heads[index].weight.data_ptr() == \
42
+ model.audio_embeddings[index].weight.data_ptr()
43
+ return model, config
44
+
45
+
46
+ def parameter_counts(model: nn.Module) -> dict:
47
+ backbone_prefixes = ("transformer.", "text_lm_head.")
48
+ backbone = sum(parameter.numel() for name, parameter in model.named_parameters()
49
+ if name.startswith(backbone_prefixes) and parameter.requires_grad)
50
+ head = sum(parameter.numel() for name, parameter in model.named_parameters()
51
+ if not name.startswith(backbone_prefixes) and parameter.requires_grad)
52
+ frozen = sum(parameter.numel() for parameter in model.parameters()
53
+ if not parameter.requires_grad)
54
+ assert head == EXPECTED_LOCAL_TALKER_PARAMETERS, head
55
+ return {
56
+ "trainable_backbone_parameters": backbone,
57
+ "trainable_talker_parameters": head,
58
+ "frozen_score_conditioner_parameters": frozen,
59
+ "total_parameters": sum(parameter.numel() for parameter in model.parameters()),
60
+ }
code/moss_export/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from .configuration_moss_tts import MossTTSLocalConfig
2
+ from .modeling_moss_tts import MossTTSLocalModel
3
+ from .processing_moss_tts import MossTTSLocalProcessor
4
+
5
+ __all__ = [
6
+ "MossTTSLocalConfig",
7
+ "MossTTSLocalModel",
8
+ "MossTTSLocalProcessor",
9
+ ]
code/moss_export/configuration_moss_tts.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ """Configuration for the MOSS-TTS-Local-Transformer-v1.5 release."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, Optional, Union
7
+
8
+ from transformers.configuration_utils import PretrainedConfig
9
+ from transformers.models.gpt2.configuration_gpt2 import GPT2Config
10
+ from transformers.models.qwen3.configuration_qwen3 import Qwen3Config
11
+
12
+
13
+ SUPPORTED_ATTENTION_IMPLEMENTATIONS = {"flash_attention_2", "sdpa", "eager"}
14
+
15
+
16
+ def _normalize_attention_implementation(value: Optional[str], default: str = "flash_attention_2") -> str:
17
+ normalized = str(value or default).strip().lower()
18
+ if normalized in {"flash", "flash_attn", "flash-attn", "flash_attention"}:
19
+ normalized = "flash_attention_2"
20
+ if normalized not in SUPPORTED_ATTENTION_IMPLEMENTATIONS:
21
+ raise ValueError(
22
+ "attn_implementation must be one of "
23
+ f"{sorted(SUPPORTED_ATTENTION_IMPLEMENTATIONS)}, got {value!r}."
24
+ )
25
+ return normalized
26
+
27
+
28
+ class MossTTSLocalConfig(PretrainedConfig):
29
+ model_type = "moss_tts_local"
30
+ keys_to_ignore_at_inference = ["past_key_values"]
31
+
32
+ def __init__(
33
+ self,
34
+ qwen3_config: Optional[Union[Qwen3Config, Dict[str, Any]]] = None,
35
+ gpt2_config: Optional[Union[GPT2Config, Dict[str, Any]]] = None,
36
+ language_config: Optional[Union[Qwen3Config, Dict[str, Any]]] = None,
37
+ n_vq: int = 12,
38
+ audio_vocab_size: int = 1024,
39
+ audio_codebook_sizes: Optional[list[int]] = None,
40
+ audio_pad_token_id: int = 1024,
41
+ audio_pad_code: Optional[int] = None,
42
+ pad_token_id: int = 151643,
43
+ im_start_token_id: int = 151644,
44
+ im_end_token_id: int = 151645,
45
+ audio_start_token_id: int = 151669,
46
+ audio_end_token_id: int = 151670,
47
+ audio_user_slot_token_id: int = 151654,
48
+ audio_assistant_slot_token_id: int = 151656,
49
+ audio_assistant_gen_slot_token_id: Optional[int] = None,
50
+ sampling_rate: int = 48000,
51
+ audio_tokenizer_name_or_path: Optional[str] = None,
52
+ attn_implementation: str = "flash_attention_2",
53
+ local_transformer_attn_implementation: Optional[str] = None,
54
+ local_text_head_mode: str = "binary",
55
+ initializer_range: float = 0.02,
56
+ **kwargs: Any,
57
+ ) -> None:
58
+ if qwen3_config is None and language_config is not None:
59
+ qwen3_config = language_config
60
+ if isinstance(qwen3_config, dict):
61
+ self.qwen3_config = Qwen3Config(**qwen3_config)
62
+ elif qwen3_config is None:
63
+ self.qwen3_config = Qwen3Config()
64
+ else:
65
+ self.qwen3_config = qwen3_config
66
+
67
+ if isinstance(gpt2_config, dict):
68
+ self.gpt2_config = GPT2Config(**gpt2_config)
69
+ elif gpt2_config is None:
70
+ self.gpt2_config = GPT2Config(
71
+ vocab_size=int(self.qwen3_config.vocab_size),
72
+ n_embd=int(self.qwen3_config.hidden_size),
73
+ n_layer=1,
74
+ n_head=max(1, int(self.qwen3_config.hidden_size) // 80),
75
+ n_positions=int(n_vq) + 1,
76
+ n_ctx=int(n_vq) + 1,
77
+ activation_function="silu",
78
+ layer_norm_epsilon=1e-6,
79
+ resid_pdrop=0.0,
80
+ embd_pdrop=0.0,
81
+ attn_pdrop=0.0,
82
+ )
83
+ else:
84
+ self.gpt2_config = gpt2_config
85
+
86
+ self.n_vq = int(n_vq)
87
+ if self.n_vq <= 0:
88
+ raise ValueError("n_vq must be positive.")
89
+ if audio_codebook_sizes is None:
90
+ self.audio_codebook_sizes = [int(audio_vocab_size)] * self.n_vq
91
+ else:
92
+ self.audio_codebook_sizes = [int(size) for size in audio_codebook_sizes]
93
+ if len(self.audio_codebook_sizes) != self.n_vq:
94
+ raise ValueError(
95
+ f"audio_codebook_sizes must have length n_vq={self.n_vq}, "
96
+ f"got {len(self.audio_codebook_sizes)}."
97
+ )
98
+ if any(size <= 0 for size in self.audio_codebook_sizes):
99
+ raise ValueError("audio_codebook_sizes must contain positive integers.")
100
+ self.audio_vocab_size = int(max(int(audio_vocab_size), max(self.audio_codebook_sizes)))
101
+ self.audio_pad_token_id = int(audio_pad_code if audio_pad_code is not None else audio_pad_token_id)
102
+ self.audio_pad_code = self.audio_pad_token_id
103
+ if self.audio_pad_token_id < self.audio_vocab_size:
104
+ raise ValueError("audio_pad_token_id/audio_pad_code must be outside the audio vocab.")
105
+
106
+ self.pad_token_id = int(pad_token_id)
107
+ self.im_start_token_id = int(im_start_token_id)
108
+ self.im_end_token_id = int(im_end_token_id)
109
+ self.audio_start_token_id = int(audio_start_token_id)
110
+ self.audio_end_token_id = int(audio_end_token_id)
111
+ self.audio_user_slot_token_id = int(audio_user_slot_token_id)
112
+ self.audio_assistant_slot_token_id = int(
113
+ audio_assistant_slot_token_id
114
+ if audio_assistant_gen_slot_token_id is None
115
+ else audio_assistant_gen_slot_token_id
116
+ )
117
+ self.audio_assistant_gen_slot_token_id = self.audio_assistant_slot_token_id
118
+
119
+ self.sampling_rate = int(sampling_rate)
120
+ self.audio_tokenizer_name_or_path = audio_tokenizer_name_or_path
121
+ self.attn_implementation = _normalize_attention_implementation(attn_implementation)
122
+ self.local_transformer_attn_implementation = _normalize_attention_implementation(
123
+ local_transformer_attn_implementation,
124
+ default=self.attn_implementation,
125
+ )
126
+ self.initializer_range = float(initializer_range)
127
+
128
+ self.hidden_size = int(self.qwen3_config.hidden_size)
129
+ self.vocab_size = int(self.qwen3_config.vocab_size)
130
+ self.local_hidden_size = int(self.gpt2_config.hidden_size)
131
+ if self.local_hidden_size != self.hidden_size:
132
+ raise ValueError(
133
+ "This MOSS-TTS-Local-Transformer-v1.5 release expects local hidden size to "
134
+ "match Qwen3 hidden size so audio embeddings and heads are tied."
135
+ )
136
+
137
+ normalized_text_head_mode = str(local_text_head_mode or "full_vocab").strip().lower()
138
+ if normalized_text_head_mode in {"full", "full-vocab", "vocab"}:
139
+ normalized_text_head_mode = "full_vocab"
140
+ if normalized_text_head_mode not in {"full_vocab", "binary"}:
141
+ raise ValueError("local_text_head_mode must be 'full_vocab' or 'binary'.")
142
+ self.local_text_head_mode = normalized_text_head_mode
143
+
144
+ kwargs.setdefault("tie_word_embeddings", True)
145
+ super().__init__(pad_token_id=self.pad_token_id, **kwargs)
146
+
147
+ @property
148
+ def language_config(self) -> Qwen3Config:
149
+ return self.qwen3_config
150
+
151
+ def to_dict(self) -> Dict[str, Any]:
152
+ output = super().to_dict()
153
+ output["qwen3_config"] = self.qwen3_config.to_dict()
154
+ output["language_config"] = self.qwen3_config.to_dict()
155
+ output["gpt2_config"] = self.gpt2_config.to_dict()
156
+ output["audio_pad_code"] = self.audio_pad_token_id
157
+ output["audio_assistant_gen_slot_token_id"] = self.audio_assistant_slot_token_id
158
+ return output
code/moss_export/gpt2_decoder.py ADDED
@@ -0,0 +1,721 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.utils.checkpoint
10
+ from transformers.activations import ACT2FN
11
+ from transformers.modeling_outputs import BaseModelOutputWithPast
12
+ from transformers.models.gpt2.configuration_gpt2 import GPT2Config
13
+
14
+ try:
15
+ from flash_attn import flash_attn_func, flash_attn_varlen_func
16
+ from flash_attn.bert_padding import pad_input, unpad_input
17
+
18
+ _FLASH_ATTN_AVAILABLE = True
19
+ except Exception:
20
+ flash_attn_func = None
21
+ flash_attn_varlen_func = None
22
+ pad_input = None
23
+ unpad_input = None
24
+ _FLASH_ATTN_AVAILABLE = False
25
+
26
+
27
+ @dataclass
28
+ class PackedSequenceMetadata:
29
+ cu_seqlens: torch.Tensor
30
+ max_seqlen: int
31
+ indices: Optional[torch.Tensor] = None
32
+ batch_size: Optional[int] = None
33
+ seq_len: Optional[int] = None
34
+
35
+
36
+ def _is_static_kv_cache_layer(layer_past: object) -> bool:
37
+ return isinstance(layer_past, dict) and bool(layer_past.get("static_kv_cache", False))
38
+
39
+
40
+ class MossTTSNanoGPT2RotaryEmbedding(nn.Module):
41
+ def __init__(self, dim: int, base: float = 10000.0) -> None:
42
+ super().__init__()
43
+ if dim % 2 != 0:
44
+ raise ValueError(f"RoPE head_dim must be even, got {dim}")
45
+ self.dim = int(dim)
46
+ self.base = float(base)
47
+ self.register_buffer("inv_freq", self._compute_inv_freq(), persistent=False)
48
+
49
+ def _compute_inv_freq(self, device: Optional[torch.device] = None) -> torch.Tensor:
50
+ return 1.0 / (
51
+ self.base ** (torch.arange(0, self.dim, 2, device=device, dtype=torch.float32) / self.dim)
52
+ )
53
+
54
+ def forward(
55
+ self,
56
+ position_ids: torch.LongTensor,
57
+ *,
58
+ device: torch.device,
59
+ dtype: torch.dtype,
60
+ ) -> tuple[torch.Tensor, torch.Tensor]:
61
+ if position_ids.ndim == 1:
62
+ position_ids = position_ids.unsqueeze(0)
63
+ inv_freq = self._compute_inv_freq(device=device)
64
+ freqs = torch.einsum("bs,d->bsd", position_ids.to(device=device, dtype=inv_freq.dtype), inv_freq)
65
+ cos = freqs.cos().repeat_interleave(2, dim=-1).unsqueeze(2).to(dtype=dtype)
66
+ sin = freqs.sin().repeat_interleave(2, dim=-1).unsqueeze(2).to(dtype=dtype)
67
+ return cos, sin
68
+
69
+
70
+ def rotate_half(hidden_states: torch.Tensor) -> torch.Tensor:
71
+ even = hidden_states[..., ::2]
72
+ odd = hidden_states[..., 1::2]
73
+ return torch.stack((-odd, even), dim=-1).reshape_as(hidden_states)
74
+
75
+
76
+ def apply_rotary_pos_emb(
77
+ hidden_states: torch.Tensor,
78
+ cos: torch.Tensor,
79
+ sin: torch.Tensor,
80
+ ) -> torch.Tensor:
81
+ return (hidden_states * cos) + (rotate_half(hidden_states) * sin)
82
+
83
+
84
+ class MossTTSNanoGPT2MLP(nn.Module):
85
+ def __init__(self, config: GPT2Config) -> None:
86
+ super().__init__()
87
+ hidden_size = int(config.hidden_size)
88
+ inner_size = int(config.n_inner or 4 * hidden_size)
89
+ self.fc_in = nn.Linear(hidden_size, inner_size)
90
+ self.fc_out = nn.Linear(inner_size, hidden_size)
91
+ self.act = ACT2FN[config.activation_function]
92
+ self.dropout = nn.Dropout(config.resid_pdrop)
93
+
94
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
95
+ hidden_states = self.fc_in(hidden_states)
96
+ hidden_states = self.act(hidden_states)
97
+ hidden_states = self.fc_out(hidden_states)
98
+ return self.dropout(hidden_states)
99
+
100
+
101
+ class MossTTSNanoGPT2Attention(nn.Module):
102
+ def __init__(self, config: GPT2Config, layer_idx: int, attn_implementation: str) -> None:
103
+ super().__init__()
104
+ hidden_size = int(config.hidden_size)
105
+ num_heads = int(config.num_attention_heads)
106
+ if hidden_size % num_heads != 0:
107
+ raise ValueError(f"hidden_size={hidden_size} must be divisible by num_attention_heads={num_heads}")
108
+
109
+ self.num_heads = num_heads
110
+ self.head_dim = hidden_size // num_heads
111
+ self.embed_dim = hidden_size
112
+ self.layer_idx = layer_idx
113
+ self.attn_implementation = attn_implementation
114
+ self.attn_dropout = float(config.attn_pdrop)
115
+ self.resid_dropout = nn.Dropout(config.resid_pdrop)
116
+ self.scale_attn_weights = bool(getattr(config, "scale_attn_weights", True))
117
+ self.scale_attn_by_inverse_layer_idx = bool(getattr(config, "scale_attn_by_inverse_layer_idx", False))
118
+ self.position_embedding_type = str(getattr(config, "position_embedding_type", "absolute")).lower()
119
+ if self.position_embedding_type not in {"absolute", "rope"}:
120
+ raise ValueError(f"Unsupported position_embedding_type={self.position_embedding_type!r}")
121
+
122
+ self.c_attn = nn.Linear(hidden_size, 3 * hidden_size)
123
+ self.c_proj = nn.Linear(hidden_size, hidden_size)
124
+ self.rotary_emb = None
125
+ if self.position_embedding_type == "rope":
126
+ self.rotary_emb = MossTTSNanoGPT2RotaryEmbedding(
127
+ self.head_dim,
128
+ base=float(getattr(config, "rope_base", 10000.0)),
129
+ )
130
+
131
+ def _split_heads(self, tensor: torch.Tensor) -> torch.Tensor:
132
+ if tensor.ndim == 3:
133
+ batch_size, seq_len, _ = tensor.shape
134
+ return tensor.view(batch_size, seq_len, self.num_heads, self.head_dim)
135
+ if tensor.ndim == 2:
136
+ total_tokens, _ = tensor.shape
137
+ return tensor.view(total_tokens, self.num_heads, self.head_dim)
138
+ raise ValueError(f"Unsupported tensor rank for attention split: {tensor.ndim}")
139
+
140
+ def _merge_heads(self, tensor: torch.Tensor) -> torch.Tensor:
141
+ if tensor.ndim == 4:
142
+ batch_size, seq_len, _, _ = tensor.shape
143
+ return tensor.reshape(batch_size, seq_len, self.embed_dim)
144
+ if tensor.ndim == 3:
145
+ total_tokens, _, _ = tensor.shape
146
+ return tensor.reshape(total_tokens, self.embed_dim)
147
+ raise ValueError(f"Unsupported tensor rank for attention merge: {tensor.ndim}")
148
+
149
+ def _causal_attention_mask(
150
+ self,
151
+ attention_mask: Optional[torch.Tensor],
152
+ query_length: int,
153
+ key_length: int,
154
+ device: torch.device,
155
+ ) -> torch.Tensor:
156
+ query_positions = torch.arange(query_length, device=device, dtype=torch.long)
157
+ query_positions = query_positions + max(key_length - query_length, 0)
158
+ key_positions = torch.arange(key_length, device=device, dtype=torch.long)
159
+ causal = key_positions.unsqueeze(0) <= query_positions.unsqueeze(1)
160
+ causal = causal.unsqueeze(0).unsqueeze(0)
161
+ if attention_mask is None:
162
+ return causal
163
+ key_mask = attention_mask[:, None, None, :].to(dtype=torch.bool)
164
+ return causal & key_mask
165
+
166
+ def _eager_attention(
167
+ self,
168
+ query: torch.Tensor,
169
+ key: torch.Tensor,
170
+ value: torch.Tensor,
171
+ attention_mask: Optional[torch.Tensor],
172
+ ) -> torch.Tensor:
173
+ query = query.transpose(1, 2)
174
+ key = key.transpose(1, 2)
175
+ value = value.transpose(1, 2)
176
+
177
+ scale = 1.0
178
+ if self.scale_attn_weights:
179
+ scale /= self.head_dim ** 0.5
180
+ if self.scale_attn_by_inverse_layer_idx:
181
+ scale /= float(self.layer_idx + 1)
182
+
183
+ scores = torch.matmul(query, key.transpose(-1, -2)) * scale
184
+ causal_mask = self._causal_attention_mask(
185
+ attention_mask=attention_mask,
186
+ query_length=query.shape[-2],
187
+ key_length=key.shape[-2],
188
+ device=query.device,
189
+ )
190
+ scores = scores.masked_fill(~causal_mask, torch.finfo(scores.dtype).min)
191
+ probs = torch.softmax(scores, dim=-1)
192
+ if self.training and self.attn_dropout > 0:
193
+ probs = torch.dropout(probs, self.attn_dropout, train=True)
194
+ output = torch.matmul(probs, value)
195
+ return output.transpose(1, 2).contiguous()
196
+
197
+ def _sdpa_attention(
198
+ self,
199
+ query: torch.Tensor,
200
+ key: torch.Tensor,
201
+ value: torch.Tensor,
202
+ attention_mask: Optional[torch.Tensor],
203
+ ) -> torch.Tensor:
204
+ query = query.transpose(1, 2)
205
+ key = key.transpose(1, 2)
206
+ value = value.transpose(1, 2)
207
+ mask = None
208
+ if attention_mask is not None or query.shape[-2] != key.shape[-2]:
209
+ mask = self._causal_attention_mask(
210
+ attention_mask=attention_mask,
211
+ query_length=query.shape[-2],
212
+ key_length=key.shape[-2],
213
+ device=query.device,
214
+ )
215
+ output = torch.nn.functional.scaled_dot_product_attention(
216
+ query,
217
+ key,
218
+ value,
219
+ attn_mask=mask,
220
+ dropout_p=self.attn_dropout if self.training else 0.0,
221
+ is_causal=mask is None,
222
+ )
223
+ return output.transpose(1, 2).contiguous()
224
+
225
+ def _flash_attention(
226
+ self,
227
+ query: torch.Tensor,
228
+ key: torch.Tensor,
229
+ value: torch.Tensor,
230
+ attention_mask: Optional[torch.Tensor],
231
+ packed_metadata: Optional[PackedSequenceMetadata],
232
+ ) -> torch.Tensor:
233
+ if not _FLASH_ATTN_AVAILABLE:
234
+ raise ImportError("flash_attn is not installed, but attn_implementation='flash_attention_2' was requested.")
235
+ if query.device.type != "cuda":
236
+ raise ValueError("flash_attention_2 requires CUDA tensors.")
237
+ if query.dtype not in (torch.float16, torch.bfloat16):
238
+ raise ValueError(
239
+ f"flash_attention_2 requires fp16/bf16 tensors, but received dtype={query.dtype}."
240
+ )
241
+
242
+ dropout_p = self.attn_dropout if self.training else 0.0
243
+ if packed_metadata is not None:
244
+ if packed_metadata.indices is not None:
245
+ query = query.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
246
+ key = key.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
247
+ value = value.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
248
+ output = flash_attn_varlen_func(
249
+ query,
250
+ key,
251
+ value,
252
+ packed_metadata.cu_seqlens,
253
+ packed_metadata.cu_seqlens,
254
+ packed_metadata.max_seqlen,
255
+ packed_metadata.max_seqlen,
256
+ dropout_p=dropout_p,
257
+ causal=True,
258
+ )
259
+ if packed_metadata.indices is None:
260
+ return output
261
+ return pad_input(
262
+ output,
263
+ packed_metadata.indices,
264
+ packed_metadata.batch_size,
265
+ packed_metadata.seq_len,
266
+ )
267
+
268
+ if attention_mask is None or bool(attention_mask.all()):
269
+ return flash_attn_func(
270
+ query,
271
+ key,
272
+ value,
273
+ dropout_p=dropout_p,
274
+ causal=True,
275
+ )
276
+
277
+ if query.shape[1] != key.shape[1]:
278
+ query_attention_mask = attention_mask[:, -query.shape[1] :]
279
+ unpadded_query, query_indices, cu_seqlens_q, max_seqlen_q, _ = unpad_input(
280
+ query,
281
+ query_attention_mask,
282
+ )
283
+ unpadded_key, _, cu_seqlens_k, max_seqlen_k, _ = unpad_input(key, attention_mask)
284
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
285
+ output = flash_attn_varlen_func(
286
+ unpadded_query,
287
+ unpadded_key,
288
+ unpadded_value,
289
+ cu_seqlens_q,
290
+ cu_seqlens_k,
291
+ max_seqlen_q,
292
+ max_seqlen_k,
293
+ dropout_p=dropout_p,
294
+ causal=True,
295
+ )
296
+ return pad_input(output, query_indices, query.shape[0], query.shape[1])
297
+
298
+ unpadded_query, indices, cu_seqlens, max_seqlen, _ = unpad_input(query, attention_mask)
299
+ unpadded_key, _, _, _, _ = unpad_input(key, attention_mask)
300
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
301
+ output = flash_attn_varlen_func(
302
+ unpadded_query,
303
+ unpadded_key,
304
+ unpadded_value,
305
+ cu_seqlens,
306
+ cu_seqlens,
307
+ max_seqlen,
308
+ max_seqlen,
309
+ dropout_p=dropout_p,
310
+ causal=True,
311
+ )
312
+ return pad_input(output, indices, query.shape[0], query.shape[1])
313
+
314
+ def forward(
315
+ self,
316
+ hidden_states: torch.Tensor,
317
+ attention_mask: Optional[torch.Tensor] = None,
318
+ position_ids: Optional[torch.LongTensor] = None,
319
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
320
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
321
+ use_cache: bool = False,
322
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
323
+ qkv = self.c_attn(hidden_states)
324
+ query, key, value = qkv.split(self.embed_dim, dim=-1)
325
+ query = self._split_heads(query)
326
+ key = self._split_heads(key)
327
+ value = self._split_heads(value)
328
+
329
+ if self.rotary_emb is not None:
330
+ if position_ids is None:
331
+ raise ValueError("position_ids must be provided when position_embedding_type='rope'.")
332
+ cos, sin = self.rotary_emb(
333
+ position_ids.to(device=query.device),
334
+ device=query.device,
335
+ dtype=query.dtype,
336
+ )
337
+ query = apply_rotary_pos_emb(query, cos, sin)
338
+ key = apply_rotary_pos_emb(key, cos, sin)
339
+
340
+ static_layer_past = layer_past is not None and _is_static_kv_cache_layer(layer_past)
341
+ if static_layer_past:
342
+ past_length = int(layer_past.get("length", 0))
343
+ new_length = past_length + int(key.shape[1])
344
+ key_cache = layer_past["key"]
345
+ value_cache = layer_past["value"]
346
+ if new_length > int(key_cache.shape[1]):
347
+ raise ValueError(
348
+ f"Static KV cache is too short: need {new_length}, capacity={int(key_cache.shape[1])}."
349
+ )
350
+ key_cache[:, past_length:new_length].copy_(key)
351
+ value_cache[:, past_length:new_length].copy_(value)
352
+ key = key_cache[:, :new_length]
353
+ value = value_cache[:, :new_length]
354
+ layer_past["length"] = new_length
355
+ elif layer_past is not None:
356
+ past_key, past_value = layer_past
357
+ key = torch.cat([past_key.to(device=key.device, dtype=key.dtype), key], dim=1)
358
+ value = torch.cat([past_value.to(device=value.device, dtype=value.dtype), value], dim=1)
359
+
360
+ present = layer_past if (use_cache and static_layer_past) else ((key, value) if use_cache else None)
361
+
362
+ if self.attn_implementation == "flash_attention_2":
363
+ attn_output = self._flash_attention(
364
+ query=query,
365
+ key=key,
366
+ value=value,
367
+ attention_mask=attention_mask,
368
+ packed_metadata=packed_metadata,
369
+ )
370
+ elif self.attn_implementation == "sdpa":
371
+ attn_output = self._sdpa_attention(
372
+ query=query,
373
+ key=key,
374
+ value=value,
375
+ attention_mask=attention_mask,
376
+ )
377
+ else:
378
+ attn_output = self._eager_attention(
379
+ query=query,
380
+ key=key,
381
+ value=value,
382
+ attention_mask=attention_mask,
383
+ )
384
+
385
+ attn_output = self._merge_heads(attn_output)
386
+ attn_output = self.c_proj(attn_output)
387
+ return self.resid_dropout(attn_output), present
388
+
389
+
390
+ class MossTTSNanoGPT2Block(nn.Module):
391
+ def __init__(self, config: GPT2Config, layer_idx: int, attn_implementation: str) -> None:
392
+ super().__init__()
393
+ hidden_size = int(config.hidden_size)
394
+ self.ln_1 = nn.LayerNorm(hidden_size, eps=config.layer_norm_epsilon)
395
+ self.attn = MossTTSNanoGPT2Attention(config, layer_idx=layer_idx, attn_implementation=attn_implementation)
396
+ self.ln_2 = nn.LayerNorm(hidden_size, eps=config.layer_norm_epsilon)
397
+ self.mlp = MossTTSNanoGPT2MLP(config)
398
+
399
+ def forward(
400
+ self,
401
+ hidden_states: torch.Tensor,
402
+ attention_mask: Optional[torch.Tensor] = None,
403
+ position_ids: Optional[torch.LongTensor] = None,
404
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
405
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
406
+ use_cache: bool = False,
407
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
408
+ attn_output, present = self.attn(
409
+ self.ln_1(hidden_states),
410
+ attention_mask=attention_mask,
411
+ position_ids=position_ids,
412
+ packed_metadata=packed_metadata,
413
+ layer_past=layer_past,
414
+ use_cache=use_cache,
415
+ )
416
+ hidden_states = hidden_states + attn_output
417
+ hidden_states = hidden_states + self.mlp(self.ln_2(hidden_states))
418
+ return hidden_states, present
419
+
420
+
421
+ class MossTTSNanoGPT2Model(nn.Module):
422
+ def __init__(self, config: GPT2Config, attn_implementation: str = "eager") -> None:
423
+ super().__init__()
424
+ self.config = config
425
+ self.attn_implementation = attn_implementation
426
+ self.position_embedding_type = str(getattr(config, "position_embedding_type", "absolute")).lower()
427
+ if self.position_embedding_type not in {"absolute", "rope"}:
428
+ raise ValueError(f"Unsupported position_embedding_type={self.position_embedding_type!r}")
429
+ hidden_size = int(config.hidden_size)
430
+ self.wte = nn.Embedding(config.vocab_size, hidden_size)
431
+ self.wpe = nn.Embedding(config.n_positions, hidden_size) if self.position_embedding_type == "absolute" else nn.Identity()
432
+ self.drop = nn.Dropout(config.embd_pdrop)
433
+ self.h = nn.ModuleList(
434
+ [MossTTSNanoGPT2Block(config, layer_idx=index, attn_implementation=attn_implementation) for index in range(config.n_layer)]
435
+ )
436
+ self.ln_f = nn.LayerNorm(hidden_size, eps=config.layer_norm_epsilon)
437
+ self.gradient_checkpointing = False
438
+ self._reset_parameters()
439
+
440
+ def _reset_parameters(self) -> None:
441
+ init_std = float(self.config.initializer_range)
442
+ for module in self.modules():
443
+ if isinstance(module, nn.Linear):
444
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
445
+ if module.bias is not None:
446
+ nn.init.zeros_(module.bias)
447
+ elif isinstance(module, nn.Embedding):
448
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
449
+ elif isinstance(module, nn.LayerNorm):
450
+ nn.init.ones_(module.weight)
451
+ nn.init.zeros_(module.bias)
452
+
453
+ @staticmethod
454
+ def _normalize_num_sequences(
455
+ cu_seqlens: torch.Tensor,
456
+ num_sequences: Optional[torch.Tensor],
457
+ device: torch.device,
458
+ ) -> torch.Tensor:
459
+ if cu_seqlens.ndim == 1:
460
+ cu_seqlens = cu_seqlens.unsqueeze(0)
461
+ if num_sequences is None:
462
+ diffs = cu_seqlens[:, 1:] - cu_seqlens[:, :-1]
463
+ return diffs.gt(0).sum(dim=-1).to(device=device, dtype=torch.long)
464
+ if num_sequences.ndim == 0:
465
+ num_sequences = num_sequences.unsqueeze(0)
466
+ return num_sequences.to(device=device, dtype=torch.long)
467
+
468
+ @staticmethod
469
+ def _packed_segments_from_cu_seqlens(
470
+ cu_seqlens: torch.Tensor,
471
+ num_sequences: Optional[torch.Tensor],
472
+ device: torch.device,
473
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
474
+ if cu_seqlens.ndim == 1:
475
+ cu_seqlens = cu_seqlens.unsqueeze(0)
476
+ cu_seqlens = cu_seqlens.to(device=device)
477
+ batch_size, boundary_count = cu_seqlens.shape
478
+ segment_slots = boundary_count - 1
479
+ if segment_slots <= 0:
480
+ empty = torch.empty(0, dtype=torch.long, device=device)
481
+ return empty, empty, empty
482
+
483
+ counts = MossTTSNanoGPT2Model._normalize_num_sequences(cu_seqlens, num_sequences, device=device)
484
+ counts = counts.clamp(min=0, max=segment_slots)
485
+ segment_slots = int(counts.max().item()) if counts.numel() > 0 else 0
486
+ if segment_slots <= 0:
487
+ empty = torch.empty(0, dtype=torch.long, device=device)
488
+ return empty, empty, empty
489
+ cu_seqlens = cu_seqlens[:, : segment_slots + 1]
490
+ slot_ids = torch.arange(segment_slots, device=device).unsqueeze(0)
491
+ valid_slots = slot_ids < counts.unsqueeze(1)
492
+
493
+ starts = cu_seqlens[:, :-1].to(dtype=torch.long)
494
+ ends = cu_seqlens[:, 1:].to(dtype=torch.long)
495
+ lengths = (ends - starts).clamp_min(0)
496
+ lengths = torch.where(valid_slots, lengths, torch.zeros((), dtype=torch.long, device=device))
497
+
498
+ batch_ids = torch.arange(batch_size, device=device, dtype=torch.long).unsqueeze(1).expand(batch_size, segment_slots)
499
+ batch_ids = batch_ids.reshape(-1)
500
+ starts = starts.reshape(-1)
501
+ lengths = lengths.reshape(-1)
502
+
503
+ valid_segments = lengths.gt(0)
504
+ valid_count = int(valid_segments.to(dtype=torch.long).sum().item())
505
+ if valid_count <= 0:
506
+ empty = torch.empty(0, dtype=torch.long, device=device)
507
+ return empty, empty, empty
508
+ if valid_count == lengths.numel():
509
+ return batch_ids, starts, lengths
510
+
511
+ valid_order = torch.argsort(valid_segments.to(dtype=torch.long), descending=True, stable=True)[:valid_count]
512
+ return (
513
+ batch_ids.index_select(0, valid_order),
514
+ starts.index_select(0, valid_order),
515
+ lengths.index_select(0, valid_order),
516
+ )
517
+
518
+ @staticmethod
519
+ def _packed_token_indices(
520
+ batch_ids: torch.Tensor,
521
+ starts: torch.Tensor,
522
+ lengths: torch.Tensor,
523
+ seq_len: int,
524
+ ) -> tuple[torch.Tensor, torch.Tensor]:
525
+ total_tokens = int(lengths.sum().item())
526
+ if total_tokens <= 0:
527
+ empty = torch.empty(0, dtype=torch.long, device=lengths.device)
528
+ return empty, empty
529
+
530
+ segment_ids = torch.repeat_interleave(
531
+ torch.arange(lengths.numel(), device=lengths.device, dtype=torch.long),
532
+ lengths,
533
+ output_size=total_tokens,
534
+ )
535
+ segment_starts = torch.cumsum(lengths, dim=0) - lengths
536
+ positions = torch.arange(total_tokens, device=lengths.device, dtype=torch.long) - segment_starts[segment_ids]
537
+ indices = batch_ids[segment_ids] * seq_len + starts[segment_ids] + positions
538
+ return indices, positions
539
+
540
+ @staticmethod
541
+ def build_packed_position_ids(
542
+ attention_mask: Optional[torch.Tensor],
543
+ cu_seqlens: torch.Tensor,
544
+ num_sequences: Optional[torch.Tensor],
545
+ sequence_length: Optional[int] = None,
546
+ ) -> torch.Tensor:
547
+ if cu_seqlens.ndim == 1:
548
+ cu_seqlens = cu_seqlens.unsqueeze(0)
549
+ batch_size = cu_seqlens.shape[0]
550
+ seq_len = int(sequence_length or (cu_seqlens.shape[1] - 1))
551
+ device = cu_seqlens.device
552
+ position_ids = torch.zeros((batch_size, seq_len), dtype=torch.long, device=device)
553
+ batch_ids, starts, lengths = MossTTSNanoGPT2Model._packed_segments_from_cu_seqlens(
554
+ cu_seqlens,
555
+ num_sequences,
556
+ device,
557
+ )
558
+ if lengths.numel() > 0:
559
+ indices, positions = MossTTSNanoGPT2Model._packed_token_indices(batch_ids, starts, lengths, seq_len)
560
+ position_ids.view(-1).scatter_(0, indices, positions)
561
+ if attention_mask is not None:
562
+ position_ids = position_ids * attention_mask.to(dtype=position_ids.dtype)
563
+ return position_ids
564
+
565
+ @staticmethod
566
+ def build_packed_metadata(
567
+ hidden_states: torch.Tensor,
568
+ cu_seqlens: torch.Tensor,
569
+ num_sequences: Optional[torch.Tensor],
570
+ ) -> PackedSequenceMetadata:
571
+ if cu_seqlens.ndim == 1:
572
+ cu_seqlens = cu_seqlens.unsqueeze(0)
573
+ device = hidden_states.device
574
+ seq_len = hidden_states.shape[1]
575
+ batch_ids, starts, lengths = MossTTSNanoGPT2Model._packed_segments_from_cu_seqlens(
576
+ cu_seqlens,
577
+ num_sequences,
578
+ device,
579
+ )
580
+ if lengths.numel() == 0:
581
+ raise ValueError("cu_seqlens did not describe any non-empty packed sequences.")
582
+
583
+ indices, _ = MossTTSNanoGPT2Model._packed_token_indices(batch_ids, starts, lengths, seq_len)
584
+ cumulative = torch.empty(lengths.numel() + 1, dtype=torch.int32, device=device)
585
+ cumulative[0] = 0
586
+ cumulative[1:] = lengths.to(dtype=torch.int32).cumsum(dim=0)
587
+ return PackedSequenceMetadata(
588
+ cu_seqlens=cumulative,
589
+ max_seqlen=int(lengths.max().item()),
590
+ indices=indices,
591
+ batch_size=hidden_states.shape[0],
592
+ seq_len=hidden_states.shape[1],
593
+ )
594
+
595
+ def forward(
596
+ self,
597
+ input_ids: Optional[torch.LongTensor] = None,
598
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None,
599
+ attention_mask: Optional[torch.Tensor] = None,
600
+ position_ids: Optional[torch.LongTensor] = None,
601
+ inputs_embeds: Optional[torch.FloatTensor] = None,
602
+ use_cache: Optional[bool] = None,
603
+ output_attentions: Optional[bool] = None,
604
+ output_hidden_states: Optional[bool] = None,
605
+ return_dict: bool = True,
606
+ cu_seqlens: Optional[torch.Tensor] = None,
607
+ num_sequences: Optional[torch.Tensor] = None,
608
+ ) -> BaseModelOutputWithPast:
609
+ del input_ids, output_attentions
610
+
611
+ if inputs_embeds is None:
612
+ raise ValueError("inputs_embeds must be provided.")
613
+
614
+ use_cache = bool(use_cache)
615
+ if use_cache and cu_seqlens is not None:
616
+ raise ValueError("use_cache=True is not supported together with cu_seqlens packing.")
617
+
618
+ hidden_states = inputs_embeds
619
+ query_attention_mask = None
620
+ if attention_mask is not None:
621
+ attention_mask = attention_mask.to(dtype=torch.bool, device=hidden_states.device)
622
+ query_attention_mask = attention_mask[:, -hidden_states.shape[1] :]
623
+
624
+ packed_metadata = None
625
+ if position_ids is None:
626
+ if cu_seqlens is not None:
627
+ if attention_mask is None:
628
+ raise ValueError("attention_mask must be provided with cu_seqlens packing.")
629
+ position_ids = self.build_packed_position_ids(
630
+ attention_mask=attention_mask,
631
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
632
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
633
+ sequence_length=hidden_states.shape[1],
634
+ )
635
+ elif attention_mask is not None:
636
+ position_ids = attention_mask.long().cumsum(dim=-1) - 1
637
+ position_ids = position_ids.masked_fill(~attention_mask, 0)
638
+ position_ids = position_ids[:, -hidden_states.shape[1] :]
639
+ else:
640
+ past_length = 0
641
+ if past_key_values is not None and len(past_key_values) > 0:
642
+ first_layer_past = past_key_values[0]
643
+ if _is_static_kv_cache_layer(first_layer_past):
644
+ past_length = int(first_layer_past.get("length", 0))
645
+ else:
646
+ past_length = first_layer_past[0].shape[1]
647
+ position_ids = torch.arange(hidden_states.shape[1], device=hidden_states.device, dtype=torch.long)
648
+ position_ids = position_ids + past_length
649
+ position_ids = position_ids.unsqueeze(0).expand(hidden_states.shape[0], -1)
650
+
651
+ if cu_seqlens is not None and self.attn_implementation == "flash_attention_2":
652
+ packed_metadata = self.build_packed_metadata(
653
+ hidden_states=hidden_states,
654
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
655
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
656
+ )
657
+
658
+ if self.position_embedding_type == "absolute":
659
+ hidden_states = hidden_states + self.wpe(position_ids)
660
+ hidden_states = self.drop(hidden_states)
661
+ if query_attention_mask is not None:
662
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
663
+
664
+ all_hidden_states = () if output_hidden_states else None
665
+ presents = [] if use_cache else None
666
+ for layer_index, block in enumerate(self.h):
667
+ if output_hidden_states:
668
+ all_hidden_states = all_hidden_states + (hidden_states,)
669
+
670
+ if self.gradient_checkpointing and self.training:
671
+ if use_cache:
672
+ raise ValueError("use_cache=True is not supported when gradient checkpointing is enabled during training.")
673
+
674
+ def custom_forward(*inputs):
675
+ output, _ = block(
676
+ inputs[0],
677
+ attention_mask=inputs[1],
678
+ position_ids=inputs[2],
679
+ packed_metadata=packed_metadata,
680
+ layer_past=None,
681
+ use_cache=False,
682
+ )
683
+ return output
684
+
685
+ hidden_states = torch.utils.checkpoint.checkpoint(
686
+ custom_forward,
687
+ hidden_states,
688
+ attention_mask,
689
+ position_ids,
690
+ use_reentrant=False,
691
+ )
692
+ present = None
693
+ else:
694
+ hidden_states, present = block(
695
+ hidden_states,
696
+ attention_mask=attention_mask,
697
+ position_ids=position_ids,
698
+ packed_metadata=packed_metadata,
699
+ layer_past=None if past_key_values is None else past_key_values[layer_index],
700
+ use_cache=use_cache,
701
+ )
702
+ if query_attention_mask is not None:
703
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
704
+ if presents is not None:
705
+ presents.append(present)
706
+
707
+ hidden_states = self.ln_f(hidden_states)
708
+ if query_attention_mask is not None:
709
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
710
+ if output_hidden_states:
711
+ all_hidden_states = all_hidden_states + (hidden_states,)
712
+
713
+ if not return_dict:
714
+ return (hidden_states, tuple(presents) if presents is not None else None, all_hidden_states, None)
715
+
716
+ return BaseModelOutputWithPast(
717
+ last_hidden_state=hidden_states,
718
+ past_key_values=tuple(presents) if presents is not None else None,
719
+ hidden_states=all_hidden_states,
720
+ attentions=None,
721
+ )
code/moss_export/modeling_moss_tts.py ADDED
@@ -0,0 +1,623 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ """Modeling code for the MOSS-TTS-Local-Transformer-v1.5 HuggingFace release."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Any, Optional, Union
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+ from transformers.modeling_outputs import BaseModelOutputWithPast
12
+ from transformers.modeling_utils import PreTrainedModel
13
+ from transformers.models.gpt2.configuration_gpt2 import GPT2Config
14
+ from transformers.utils import ModelOutput
15
+
16
+ from .configuration_moss_tts import MossTTSLocalConfig
17
+ from .gpt2_decoder import MossTTSNanoGPT2Model
18
+ from .qwen3_decoder import MossQwen3Model
19
+
20
+
21
+ @dataclass
22
+ class MossTTSLocalOutput(ModelOutput):
23
+ last_hidden_state: Optional[torch.FloatTensor] = None
24
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None
25
+ hidden_states: Optional[tuple[torch.FloatTensor, ...]] = None
26
+ attentions: Optional[tuple[torch.FloatTensor, ...]] = None
27
+
28
+
29
+ def _find_last_equal(input_ids: torch.LongTensor, value: int) -> torch.LongTensor:
30
+ matches = input_ids.eq(int(value))
31
+ if not bool(matches.any(dim=1).all().item()):
32
+ raise ValueError(f"Every sample must contain token id {int(value)}.")
33
+ positions = torch.arange(input_ids.shape[1], device=input_ids.device, dtype=torch.long)
34
+ masked_positions = positions.unsqueeze(0).masked_fill(~matches, -1)
35
+ return masked_positions.max(dim=1).values
36
+
37
+
38
+ class MossTTSLocalPreTrainedModel(PreTrainedModel):
39
+ config_class = MossTTSLocalConfig
40
+ base_model_prefix = "transformer"
41
+ supports_gradient_checkpointing = True
42
+ _no_split_modules = ["MossTTSNanoGPT2Block", "MossQwen3DecoderLayer"]
43
+ _supports_flash_attn_2 = True
44
+ _supports_sdpa = True
45
+ _supports_cache_class = True
46
+
47
+ def _set_gradient_checkpointing(self, module: nn.Module, value: bool = False) -> None:
48
+ if isinstance(module, MossTTSNanoGPT2Model) or isinstance(module, MossQwen3Model):
49
+ module.gradient_checkpointing = value
50
+
51
+
52
+ class MossTTSLocalModel(MossTTSLocalPreTrainedModel):
53
+ _tied_weights_keys = None
54
+
55
+ def __init__(self, config: MossTTSLocalConfig) -> None:
56
+ super().__init__(config)
57
+ self._tied_weights_keys = self._build_tied_weights_keys(config)
58
+
59
+ config.qwen3_config.pad_token_id = config.pad_token_id
60
+ config.qwen3_config._attn_implementation = config.attn_implementation
61
+ local_gpt2_config = config.gpt2_config.to_dict()
62
+ local_gpt2_config["n_layer"] = int(getattr(config, "local_transformer_layers", config.gpt2_config.n_layer))
63
+ local_gpt2_config["n_positions"] = int(config.n_vq) + 1
64
+ local_gpt2_config["n_ctx"] = int(config.n_vq) + 1
65
+ local_gpt2_config = GPT2Config(**local_gpt2_config)
66
+ local_gpt2_config.pad_token_id = config.pad_token_id
67
+ local_gpt2_config._attn_implementation = config.local_transformer_attn_implementation
68
+
69
+ self.transformer = MossQwen3Model(config.qwen3_config)
70
+ self.local_transformer = MossTTSNanoGPT2Model(
71
+ local_gpt2_config,
72
+ attn_implementation=config.local_transformer_attn_implementation,
73
+ )
74
+ self.local_transformer.wte = nn.Identity()
75
+
76
+ hidden_size = int(config.hidden_size)
77
+ self.audio_embeddings = nn.ModuleList(
78
+ [
79
+ nn.Embedding(int(config.audio_codebook_sizes[index]), hidden_size)
80
+ for index in range(config.n_vq)
81
+ ]
82
+ )
83
+ self.text_lm_head = nn.Linear(hidden_size, int(config.vocab_size), bias=False)
84
+ self.audio_lm_heads = nn.ModuleList(
85
+ [
86
+ nn.Linear(hidden_size, int(config.audio_codebook_sizes[index]), bias=False)
87
+ for index in range(config.n_vq)
88
+ ]
89
+ )
90
+ self.local_text_lm_head = (
91
+ nn.Linear(hidden_size, 2, bias=False)
92
+ if self._use_binary_local_text_head()
93
+ else None
94
+ )
95
+
96
+ self.post_init()
97
+ self.tie_weights()
98
+ self.initialize_local_text_lm_head_from_text_lm_head()
99
+
100
+ def can_generate(self) -> bool:
101
+ return True
102
+
103
+ @staticmethod
104
+ def _build_tied_weights_keys(config: MossTTSLocalConfig) -> dict[str, str]:
105
+ tied_weights = {"text_lm_head.weight": "transformer.embed_tokens.weight"}
106
+ tied_weights.update(
107
+ {
108
+ f"audio_lm_heads.{index}.weight": f"audio_embeddings.{index}.weight"
109
+ for index in range(config.n_vq)
110
+ }
111
+ )
112
+ return tied_weights
113
+
114
+ def tie_weights(self, *args, **kwargs) -> None:
115
+ del args, kwargs
116
+ self.text_lm_head.weight = self.transformer.embed_tokens.weight
117
+ for embedding, head in zip(self.audio_embeddings, self.audio_lm_heads):
118
+ head.weight = embedding.weight
119
+
120
+ def get_input_embeddings(self) -> nn.Embedding:
121
+ return self.transformer.embed_tokens
122
+
123
+ def set_input_embeddings(self, value: nn.Embedding) -> None:
124
+ self.transformer.embed_tokens = value
125
+ self.tie_weights()
126
+ self.initialize_local_text_lm_head_from_text_lm_head()
127
+
128
+ def get_output_embeddings(self) -> nn.Linear:
129
+ return self.text_lm_head
130
+
131
+ def set_output_embeddings(self, new_embeddings: nn.Linear) -> None:
132
+ self.text_lm_head = new_embeddings
133
+ self.tie_weights()
134
+ self.initialize_local_text_lm_head_from_text_lm_head()
135
+
136
+ def _use_binary_local_text_head(self) -> bool:
137
+ return str(getattr(self.config, "local_text_head_mode", "full_vocab")).strip().lower() == "binary"
138
+
139
+ def _local_text_candidate_ids(self, device: torch.device) -> torch.LongTensor:
140
+ return torch.tensor(
141
+ [
142
+ int(self.config.audio_assistant_slot_token_id),
143
+ int(self.config.audio_end_token_id),
144
+ ],
145
+ dtype=torch.long,
146
+ device=device,
147
+ )
148
+
149
+ def initialize_local_text_lm_head_from_text_lm_head(self) -> None:
150
+ if not self._use_binary_local_text_head() or self.local_text_lm_head is None:
151
+ return
152
+ candidate_ids = self._local_text_candidate_ids(self.text_lm_head.weight.device)
153
+ with torch.no_grad():
154
+ source_weight = self.text_lm_head.weight.index_select(0, candidate_ids)
155
+ if tuple(source_weight.shape) == tuple(self.local_text_lm_head.weight.shape):
156
+ self.local_text_lm_head.weight.copy_(
157
+ source_weight.to(
158
+ device=self.local_text_lm_head.weight.device,
159
+ dtype=self.local_text_lm_head.weight.dtype,
160
+ )
161
+ )
162
+
163
+ def _resolve_fixed_nq(
164
+ self,
165
+ n_vq_for_inference: Optional[int] = None,
166
+ nq: Optional[int] = None,
167
+ ) -> int:
168
+ requested = n_vq_for_inference if n_vq_for_inference is not None else nq
169
+ config_nq = int(self.config.n_vq)
170
+ if requested is not None and int(requested) != config_nq:
171
+ raise ValueError(
172
+ "This MOSS-TTS-Local-Transformer-v1.5 release is trained with a fixed RVQ depth. "
173
+ f"Expected n_vq={config_nq}, got {int(requested)}."
174
+ )
175
+ return config_nq
176
+
177
+ def _build_inputs_embeds(self, input_ids: torch.LongTensor) -> torch.FloatTensor:
178
+ if input_ids.ndim != 3 or input_ids.shape[-1] != self.config.n_vq + 1:
179
+ raise ValueError(
180
+ f"Expected input_ids shape [batch, seq, {self.config.n_vq + 1}], "
181
+ f"got {tuple(input_ids.shape)}."
182
+ )
183
+ text_ids = input_ids[..., 0]
184
+ inputs_embeds = self.transformer.embed_tokens(text_ids)
185
+ for channel_index, embedding in enumerate(self.audio_embeddings):
186
+ channel_ids = input_ids[..., channel_index + 1]
187
+ valid_mask = channel_ids.ne(self.config.audio_pad_token_id)
188
+ safe_ids = channel_ids.masked_fill(~valid_mask, 0)
189
+ audio_embeds = embedding(safe_ids) * valid_mask.unsqueeze(-1)
190
+ inputs_embeds = inputs_embeds + audio_embeds
191
+ return inputs_embeds
192
+
193
+ def _global_hidden_to_local(self, hidden_states: torch.FloatTensor) -> torch.FloatTensor:
194
+ return hidden_states
195
+
196
+ @staticmethod
197
+ def _local_past_length(past_key_values: Optional[tuple[Any, ...]]) -> int:
198
+ if past_key_values is None or len(past_key_values) == 0:
199
+ return 0
200
+ first_layer_past = past_key_values[0]
201
+ if isinstance(first_layer_past, dict) and bool(first_layer_past.get("static_kv_cache", False)):
202
+ return int(first_layer_past.get("length", 0))
203
+ return int(first_layer_past[0].shape[1])
204
+
205
+ def _new_static_local_past_key_values(
206
+ self,
207
+ batch_size: int,
208
+ max_length: int,
209
+ device: torch.device,
210
+ dtype: torch.dtype,
211
+ ) -> tuple[dict[str, Any], ...]:
212
+ layers = []
213
+ for block in self.local_transformer.h:
214
+ attn = block.attn
215
+ cache_shape = (
216
+ int(batch_size),
217
+ int(max_length),
218
+ int(attn.num_heads),
219
+ int(attn.head_dim),
220
+ )
221
+ layers.append(
222
+ {
223
+ "static_kv_cache": True,
224
+ "key": torch.empty(cache_shape, device=device, dtype=dtype),
225
+ "value": torch.empty(cache_shape, device=device, dtype=dtype),
226
+ "length": 0,
227
+ }
228
+ )
229
+ return tuple(layers)
230
+
231
+ def _decode_local_hidden_states_with_cache(
232
+ self,
233
+ local_inputs_embeds: torch.FloatTensor,
234
+ past_key_values: Optional[tuple[Any, ...]] = None,
235
+ ) -> tuple[torch.FloatTensor, Optional[tuple[Any, ...]]]:
236
+ if (
237
+ past_key_values is None
238
+ and not self.training
239
+ and bool(getattr(self.config, "use_static_local_kv_cache", True))
240
+ ):
241
+ max_length = max(int(getattr(self.config, "n_vq", 0)) + 1, int(local_inputs_embeds.shape[1]))
242
+ past_key_values = self._new_static_local_past_key_values(
243
+ batch_size=int(local_inputs_embeds.shape[0]),
244
+ max_length=max_length,
245
+ device=local_inputs_embeds.device,
246
+ dtype=local_inputs_embeds.dtype,
247
+ )
248
+ past_length = self._local_past_length(past_key_values)
249
+ local_seq_len = int(local_inputs_embeds.shape[1])
250
+ local_position_ids = torch.arange(
251
+ past_length,
252
+ past_length + local_seq_len,
253
+ device=local_inputs_embeds.device,
254
+ dtype=torch.long,
255
+ ).unsqueeze(0)
256
+ if int(local_inputs_embeds.shape[0]) != 1:
257
+ local_position_ids = local_position_ids.expand(int(local_inputs_embeds.shape[0]), -1)
258
+ local_outputs = self.local_transformer(
259
+ input_ids=None,
260
+ past_key_values=past_key_values,
261
+ attention_mask=None,
262
+ position_ids=local_position_ids,
263
+ inputs_embeds=local_inputs_embeds,
264
+ use_cache=True,
265
+ output_attentions=False,
266
+ output_hidden_states=False,
267
+ return_dict=True,
268
+ cu_seqlens=None,
269
+ num_sequences=None,
270
+ )
271
+ return local_outputs.last_hidden_state, local_outputs.past_key_values
272
+
273
+ def forward(
274
+ self,
275
+ input_ids: Optional[torch.LongTensor] = None,
276
+ attention_mask: Optional[torch.Tensor] = None,
277
+ position_ids: Optional[torch.LongTensor] = None,
278
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None,
279
+ inputs_embeds: Optional[torch.FloatTensor] = None,
280
+ use_cache: Optional[bool] = None,
281
+ output_attentions: Optional[bool] = None,
282
+ output_hidden_states: Optional[bool] = None,
283
+ return_dict: Optional[bool] = True,
284
+ **kwargs,
285
+ ) -> Union[tuple, MossTTSLocalOutput]:
286
+ del kwargs
287
+ if inputs_embeds is None:
288
+ if input_ids is None:
289
+ raise ValueError("Either input_ids or inputs_embeds must be provided.")
290
+ inputs_embeds = self._build_inputs_embeds(input_ids)
291
+ outputs = self.transformer(
292
+ input_ids=None,
293
+ attention_mask=attention_mask,
294
+ position_ids=position_ids,
295
+ past_key_values=past_key_values,
296
+ inputs_embeds=inputs_embeds,
297
+ use_cache=use_cache,
298
+ output_attentions=output_attentions,
299
+ output_hidden_states=output_hidden_states,
300
+ return_dict=True,
301
+ cu_seqlens=None,
302
+ num_sequences=None,
303
+ )
304
+ if not return_dict:
305
+ return (
306
+ outputs.last_hidden_state,
307
+ outputs.past_key_values,
308
+ outputs.hidden_states,
309
+ outputs.attentions,
310
+ )
311
+ return MossTTSLocalOutput(
312
+ last_hidden_state=outputs.last_hidden_state,
313
+ past_key_values=outputs.past_key_values,
314
+ hidden_states=outputs.hidden_states,
315
+ attentions=outputs.attentions,
316
+ )
317
+
318
+ def _decode_local_last_hidden_state(
319
+ self,
320
+ local_inputs_embeds: torch.FloatTensor,
321
+ ) -> torch.FloatTensor:
322
+ local_seq_len = int(local_inputs_embeds.shape[1])
323
+ local_position_ids = torch.arange(
324
+ 0,
325
+ local_seq_len,
326
+ device=local_inputs_embeds.device,
327
+ dtype=torch.long,
328
+ ).unsqueeze(0)
329
+ if int(local_inputs_embeds.shape[0]) != 1:
330
+ local_position_ids = local_position_ids.expand(int(local_inputs_embeds.shape[0]), -1)
331
+ local_outputs = self.local_transformer(
332
+ input_ids=None,
333
+ attention_mask=None,
334
+ position_ids=local_position_ids,
335
+ inputs_embeds=local_inputs_embeds,
336
+ use_cache=False,
337
+ output_attentions=False,
338
+ output_hidden_states=False,
339
+ return_dict=True,
340
+ cu_seqlens=None,
341
+ num_sequences=None,
342
+ )
343
+ return local_outputs.last_hidden_state[:, -1, :]
344
+
345
+ def _filter_logits(
346
+ self,
347
+ logits: torch.FloatTensor,
348
+ top_k: Optional[int],
349
+ top_p: Optional[float],
350
+ ) -> torch.FloatTensor:
351
+ scores = logits
352
+ if top_k is not None and int(top_k) > 0 and int(top_k) < scores.shape[-1]:
353
+ kth = torch.topk(scores, int(top_k), dim=-1).values[..., -1, None]
354
+ scores = scores.masked_fill(scores < kth, -torch.inf)
355
+ if top_p is not None and 0.0 < float(top_p) < 1.0:
356
+ sorted_scores, sorted_indices = torch.sort(scores, descending=True, dim=-1)
357
+ sorted_probs = torch.softmax(sorted_scores, dim=-1)
358
+ cumulative_probs = sorted_probs.cumsum(dim=-1)
359
+ sorted_mask = cumulative_probs > float(top_p)
360
+ sorted_mask[..., 1:] = sorted_mask[..., :-1].clone()
361
+ sorted_mask[..., 0] = False
362
+ remove_mask = torch.zeros_like(scores, dtype=torch.bool)
363
+ remove_mask.scatter_(dim=-1, index=sorted_indices, src=sorted_mask)
364
+ scores = scores.masked_fill(remove_mask, -torch.inf)
365
+ return scores
366
+
367
+ def _apply_repetition_penalty(
368
+ self,
369
+ scores: torch.FloatTensor,
370
+ previous_token_ids: Optional[torch.LongTensor],
371
+ penalty: float,
372
+ ) -> torch.FloatTensor:
373
+ if previous_token_ids is None or float(penalty) == 1.0:
374
+ return scores
375
+ if previous_token_ids.ndim == 1:
376
+ previous_token_ids = previous_token_ids.unsqueeze(0)
377
+ updated = scores.clone()
378
+ for batch_index in range(updated.shape[0]):
379
+ unique_token_ids = torch.unique(previous_token_ids[batch_index])
380
+ unique_token_ids = unique_token_ids[
381
+ (unique_token_ids >= 0) & (unique_token_ids < updated.shape[-1])
382
+ ]
383
+ if unique_token_ids.numel() == 0:
384
+ continue
385
+ token_scores = updated[batch_index].index_select(0, unique_token_ids)
386
+ token_scores = torch.where(
387
+ token_scores < 0,
388
+ token_scores * float(penalty),
389
+ token_scores / float(penalty),
390
+ )
391
+ updated[batch_index].scatter_(0, unique_token_ids, token_scores)
392
+ return updated
393
+
394
+ def _sample_next_token(
395
+ self,
396
+ logits: torch.FloatTensor,
397
+ do_sample: bool,
398
+ temperature: float,
399
+ top_k: Optional[int],
400
+ top_p: Optional[float],
401
+ previous_token_ids: Optional[torch.LongTensor] = None,
402
+ repetition_penalty: float = 1.0,
403
+ ) -> torch.LongTensor:
404
+ scores = logits.float()
405
+ scores = self._apply_repetition_penalty(scores, previous_token_ids, repetition_penalty)
406
+ if not do_sample:
407
+ return torch.argmax(scores, dim=-1)
408
+ if float(temperature) <= 0:
409
+ raise ValueError("temperature must be positive when do_sample=True.")
410
+ scores = scores / float(temperature)
411
+ scores = self._filter_logits(scores, top_k=top_k, top_p=top_p)
412
+ probs = torch.softmax(scores, dim=-1)
413
+ return torch.multinomial(probs, num_samples=1).squeeze(-1)
414
+
415
+ def _sample_next_assistant_text_token(
416
+ self,
417
+ local_hidden_states: torch.FloatTensor,
418
+ do_sample: bool,
419
+ temperature: float,
420
+ top_k: Optional[int],
421
+ top_p: Optional[float],
422
+ ) -> torch.LongTensor:
423
+ if self._use_binary_local_text_head() and self.local_text_lm_head is not None:
424
+ logits = self.local_text_lm_head(local_hidden_states)
425
+ sampled_indices = self._sample_next_token(
426
+ logits=logits,
427
+ do_sample=do_sample,
428
+ temperature=temperature,
429
+ top_k=top_k,
430
+ top_p=top_p,
431
+ )
432
+ candidate_ids = self._local_text_candidate_ids(logits.device)
433
+ return candidate_ids[sampled_indices]
434
+
435
+ candidate_ids = self._local_text_candidate_ids(local_hidden_states.device)
436
+ logits = self.text_lm_head(local_hidden_states).index_select(dim=-1, index=candidate_ids)
437
+ sampled_indices = self._sample_next_token(
438
+ logits=logits,
439
+ do_sample=do_sample,
440
+ temperature=temperature,
441
+ top_k=top_k,
442
+ top_p=top_p,
443
+ )
444
+ return candidate_ids[sampled_indices]
445
+
446
+ def _build_generation_row(
447
+ self,
448
+ batch_size: int,
449
+ device: torch.device,
450
+ audio_token_ids: torch.LongTensor,
451
+ ) -> torch.LongTensor:
452
+ row = torch.full(
453
+ (batch_size, 1, self.config.n_vq + 1),
454
+ int(self.config.audio_pad_token_id),
455
+ dtype=torch.long,
456
+ device=device,
457
+ )
458
+ row[:, :, 0] = int(self.config.audio_assistant_slot_token_id)
459
+ row[:, :, 1:] = audio_token_ids.unsqueeze(1)
460
+ return row
461
+
462
+ @torch.inference_mode()
463
+ def generate(
464
+ self,
465
+ input_ids: torch.LongTensor,
466
+ attention_mask: Optional[torch.Tensor] = None,
467
+ max_new_tokens: Optional[int] = None,
468
+ max_new_frames: Optional[int] = None,
469
+ do_sample: bool = True,
470
+ text_temperature: float = 1.0,
471
+ text_top_p: float = 1.0,
472
+ text_top_k: int = 50,
473
+ audio_temperature: Optional[float] = None,
474
+ audio_top_p: Optional[float] = None,
475
+ audio_top_k: Optional[int] = None,
476
+ audio_repetition_penalty: Optional[float] = None,
477
+ temperature: float = 1.0,
478
+ top_p: float = 0.95,
479
+ top_k: int = 50,
480
+ repetition_penalty: float = 1.0,
481
+ use_kv_cache: bool = True,
482
+ n_vq_for_inference: Optional[int] = None,
483
+ nq: Optional[int] = None,
484
+ **kwargs,
485
+ ) -> list[tuple[int, torch.LongTensor]]:
486
+ del kwargs
487
+ self._resolve_fixed_nq(n_vq_for_inference=n_vq_for_inference, nq=nq)
488
+
489
+ if input_ids.ndim == 2:
490
+ input_ids = input_ids.unsqueeze(0)
491
+ if input_ids.ndim != 3:
492
+ raise ValueError(f"Expected input_ids with 3 dims, got {tuple(input_ids.shape)}.")
493
+ if input_ids.shape[-1] != self.config.n_vq + 1:
494
+ raise ValueError(
495
+ f"Expected {self.config.n_vq + 1} channels from config.n_vq, got {input_ids.shape[-1]}."
496
+ )
497
+ if attention_mask is None:
498
+ attention_mask = torch.ones(input_ids.shape[:2], dtype=torch.bool, device=input_ids.device)
499
+ elif attention_mask.ndim == 1:
500
+ attention_mask = attention_mask.unsqueeze(0)
501
+ attention_mask = attention_mask.to(device=input_ids.device, dtype=torch.bool)
502
+
503
+ frame_budget = max_new_frames if max_new_frames is not None else max_new_tokens
504
+ if frame_budget is None:
505
+ frame_budget = 4096
506
+ frame_budget = int(frame_budget)
507
+
508
+ audio_temperature = float(temperature if audio_temperature is None else audio_temperature)
509
+ audio_top_p = float(top_p if audio_top_p is None else audio_top_p)
510
+ audio_top_k = int(top_k if audio_top_k is None else audio_top_k)
511
+ audio_repetition_penalty = float(
512
+ repetition_penalty if audio_repetition_penalty is None else audio_repetition_penalty
513
+ )
514
+
515
+ batch_size = input_ids.shape[0]
516
+ input_ids_length = input_ids.shape[1]
517
+ current_input_ids = input_ids
518
+ current_attention_mask = attention_mask
519
+ current_model_input_ids = current_input_ids
520
+ generated_frames: list[torch.LongTensor] = []
521
+ finished = torch.zeros(batch_size, dtype=torch.bool, device=input_ids.device)
522
+ past_key_values = None
523
+ local_dtype = self.local_transformer.ln_f.weight.dtype
524
+
525
+ for _ in range(frame_budget):
526
+ generated_audio_history = torch.stack(generated_frames, dim=1) if generated_frames else None
527
+ global_inputs_embeds = self._build_inputs_embeds(current_model_input_ids)
528
+ global_outputs = self.transformer(
529
+ input_ids=None,
530
+ past_key_values=past_key_values,
531
+ attention_mask=current_attention_mask,
532
+ position_ids=None,
533
+ inputs_embeds=global_inputs_embeds,
534
+ use_cache=use_kv_cache,
535
+ output_attentions=False,
536
+ output_hidden_states=False,
537
+ return_dict=True,
538
+ cu_seqlens=None,
539
+ num_sequences=None,
540
+ )
541
+ global_hidden_states = global_outputs.last_hidden_state[:, -1, :]
542
+ local_global_hidden_states = self._global_hidden_to_local(global_hidden_states).to(dtype=local_dtype)
543
+
544
+ local_prefix_hidden_states, local_prefix_past_key_values = self._decode_local_hidden_states_with_cache(
545
+ local_global_hidden_states.unsqueeze(1)
546
+ )
547
+ local_hidden_states = local_prefix_hidden_states[:, -1, :]
548
+ next_text_tokens = self._sample_next_assistant_text_token(
549
+ local_hidden_states=local_hidden_states,
550
+ do_sample=do_sample,
551
+ temperature=text_temperature,
552
+ top_k=text_top_k,
553
+ top_p=text_top_p,
554
+ )
555
+ should_continue = next_text_tokens.eq(int(self.config.audio_assistant_slot_token_id)) & ~finished
556
+ finished = finished | next_text_tokens.eq(int(self.config.audio_end_token_id))
557
+ if not bool(should_continue.any().item()):
558
+ break
559
+
560
+ next_frame_tokens = []
561
+ for channel_index in range(int(self.config.n_vq)):
562
+ channel_logits = self.audio_lm_heads[channel_index](local_hidden_states)
563
+ channel_token = self._sample_next_token(
564
+ logits=channel_logits,
565
+ do_sample=do_sample,
566
+ temperature=audio_temperature,
567
+ top_k=audio_top_k,
568
+ top_p=audio_top_p,
569
+ previous_token_ids=(
570
+ None
571
+ if generated_audio_history is None
572
+ else generated_audio_history[:, :, channel_index]
573
+ ),
574
+ repetition_penalty=audio_repetition_penalty,
575
+ )
576
+ next_frame_tokens.append(channel_token)
577
+ if channel_index + 1 < int(self.config.n_vq):
578
+ current_local_input = self.audio_embeddings[channel_index](channel_token).to(dtype=local_dtype)
579
+ local_token_hidden_states, local_prefix_past_key_values = (
580
+ self._decode_local_hidden_states_with_cache(
581
+ current_local_input.unsqueeze(1),
582
+ past_key_values=local_prefix_past_key_values,
583
+ )
584
+ )
585
+ local_hidden_states = local_token_hidden_states[:, -1, :]
586
+
587
+ next_frame = torch.stack(next_frame_tokens, dim=-1)
588
+ next_frame = next_frame.masked_fill(
589
+ ~should_continue.unsqueeze(-1),
590
+ int(self.config.audio_pad_token_id),
591
+ )
592
+ generated_frames.append(next_frame)
593
+
594
+ next_row = self._build_generation_row(
595
+ batch_size=batch_size,
596
+ device=input_ids.device,
597
+ audio_token_ids=next_frame,
598
+ )
599
+ if bool((~should_continue).any().item()):
600
+ next_row[~should_continue, 0, 0] = int(self.config.pad_token_id)
601
+ next_row[~should_continue, 0, 1:] = int(self.config.audio_pad_token_id)
602
+
603
+ current_input_ids = torch.cat([current_input_ids, next_row], dim=1)
604
+ current_attention_mask = torch.cat(
605
+ [current_attention_mask, should_continue.unsqueeze(1)],
606
+ dim=1,
607
+ )
608
+ if use_kv_cache:
609
+ current_model_input_ids = next_row
610
+ past_key_values = global_outputs.past_key_values
611
+ else:
612
+ current_model_input_ids = current_input_ids
613
+
614
+ start_indices = _find_last_equal(input_ids[..., 0], int(self.config.audio_start_token_id))
615
+ start_lengths = input_ids_length - start_indices - 1
616
+ outputs: list[tuple[int, torch.LongTensor]] = []
617
+ for start_index, start_length, generation_ids in zip(
618
+ start_indices.tolist(),
619
+ start_lengths.tolist(),
620
+ current_input_ids,
621
+ ):
622
+ outputs.append((int(start_length), generation_ids[int(start_index):].detach().cpu()))
623
+ return outputs
code/moss_export/processing_moss_tts.py ADDED
@@ -0,0 +1,914 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ """Processor for the MOSS-TTS-Local-Transformer-v1.5 HuggingFace release."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import re
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, cast
11
+
12
+ import torch
13
+ import torchaudio
14
+ from transformers import (
15
+ AutoConfig,
16
+ AutoModel,
17
+ AutoTokenizer,
18
+ BatchFeature,
19
+ PreTrainedTokenizerBase,
20
+ ProcessorMixin,
21
+ logging,
22
+ processing_utils,
23
+ )
24
+
25
+ from .configuration_moss_tts import MossTTSLocalConfig
26
+
27
+
28
+ if hasattr(processing_utils, "MODALITY_TO_BASE_CLASS_MAPPING"):
29
+ processing_utils.MODALITY_TO_BASE_CLASS_MAPPING["audio_tokenizer"] = "PreTrainedModel"
30
+ else:
31
+ processing_utils.AUTO_TO_BASE_CLASS_MAPPING["AutoModel"] = "PreTrainedModel"
32
+ logger = logging.get_logger(__name__)
33
+
34
+ AUDIO_PLACEHOLDER = "<|audio|>"
35
+ USER_ROLE_PREFIX = "user\n"
36
+ USER_TEMPLATE_REFERENCE_PREFIX = (
37
+ "<user_inst>\n"
38
+ "- Reference(s):\n"
39
+ )
40
+ USER_TEMPLATE_AFTER_REFERENCE_SUFFIX = (
41
+ "\n"
42
+ "- Text:\n"
43
+ )
44
+ USER_TEMPLATE_SUFFIX = "\n</user_inst>"
45
+ ASSISTANT_TURN_PREFIX = "\n"
46
+ ASSISTANT_ROLE_PREFIX = "assistant\n"
47
+ USER_MESSAGE_FIELDS = (
48
+ "text",
49
+ "reference",
50
+ "instruction",
51
+ "tokens",
52
+ "quality",
53
+ "sound_event",
54
+ "ambient_sound",
55
+ "language",
56
+ )
57
+
58
+
59
+ def _normalize_template_value(value: Any) -> str:
60
+ if value is None:
61
+ return "None"
62
+ resolved = str(value).strip()
63
+ return resolved or "None"
64
+
65
+
66
+ def _render_user_prompt_after_reference(
67
+ language_code: object | None = None,
68
+ prompt_fields: Optional[Dict[str, Any]] = None,
69
+ ) -> str:
70
+ fields = dict(prompt_fields or {})
71
+ return (
72
+ "\n- Instruction:\n"
73
+ + _normalize_template_value(fields.get("instruction"))
74
+ + "\n- Tokens:\n"
75
+ + _normalize_template_value(fields.get("tokens"))
76
+ + "\n- Quality:\n"
77
+ + _normalize_template_value(fields.get("quality"))
78
+ + "\n- Sound Event:\n"
79
+ + _normalize_template_value(fields.get("sound_event"))
80
+ + "\n- Ambient Sound:\n"
81
+ + _normalize_template_value(fields.get("ambient_sound"))
82
+ + "\n- Language:\n"
83
+ + _normalize_template_value(fields.get("language", language_code))
84
+ + USER_TEMPLATE_AFTER_REFERENCE_SUFFIX
85
+ )
86
+
87
+
88
+ @dataclass
89
+ class Message:
90
+ def to_dict(self) -> Dict[str, Any]:
91
+ raise NotImplementedError
92
+
93
+
94
+ @dataclass
95
+ class UserMessage(Message):
96
+ text: Optional[str] = None
97
+ reference: Optional[List[Optional[Union[str, os.PathLike, torch.Tensor]]]] = None
98
+ instruction: Optional[str] = None
99
+ tokens: Optional[int] = None
100
+ quality: Optional[str] = None
101
+ sound_event: Optional[str] = None
102
+ ambient_sound: Optional[str] = None
103
+ language: Optional[str] = None
104
+
105
+ def __post_init__(self) -> None:
106
+ template = """<user_inst>
107
+ - Reference(s):
108
+ {reference}
109
+ - Instruction:
110
+ {instruction}
111
+ - Tokens:
112
+ {tokens}
113
+ - Quality:
114
+ {quality}
115
+ - Sound Event:
116
+ {sound_event}
117
+ - Ambient Sound:
118
+ {ambient_sound}
119
+ - Language:
120
+ {language}
121
+ - Text:
122
+ {text}
123
+ </user_inst>"""
124
+
125
+ audio_codes_list: list[Union[str, os.PathLike, torch.Tensor]] = []
126
+ if self.reference is None:
127
+ reference = "None"
128
+ else:
129
+ reference_items: list[str] = []
130
+ for speaker_idx, speaker_reference in enumerate(self.reference):
131
+ if speaker_reference is None:
132
+ continue
133
+ # Keep raw audio placeholders directly under "- Reference(s):".
134
+ # Speaker labels such as "[S1]:" change the token sequence and
135
+ # can affect voice-clone conditioning.
136
+ reference_items.append(AUDIO_PLACEHOLDER)
137
+ audio_codes_list.append(speaker_reference)
138
+ reference = "\n".join(reference_items) if reference_items else "None"
139
+
140
+ self._content = (
141
+ template.replace("{reference}", str(reference))
142
+ .replace("{instruction}", str(self.instruction))
143
+ .replace("{tokens}", str(self.tokens))
144
+ .replace("{quality}", str(self.quality))
145
+ .replace("{sound_event}", str(self.sound_event))
146
+ .replace("{ambient_sound}", str(self.ambient_sound))
147
+ .replace("{language}", str(self.language))
148
+ .replace("{text}", str(self.text))
149
+ )
150
+ self._audio_codes_list = audio_codes_list
151
+
152
+ def to_dict(self) -> Dict[str, Any]:
153
+ return {
154
+ "role": "user",
155
+ "content": self._content,
156
+ "audio_codes_list": self._audio_codes_list,
157
+ "text": self.text,
158
+ "instruction": self.instruction,
159
+ "tokens": self.tokens,
160
+ "quality": self.quality,
161
+ "sound_event": self.sound_event,
162
+ "ambient_sound": self.ambient_sound,
163
+ "language": self.language,
164
+ }
165
+
166
+
167
+ @dataclass
168
+ class AssistantMessage(Message):
169
+ audio_codes_list: List[Union[str, os.PathLike, torch.Tensor]]
170
+ content: str = AUDIO_PLACEHOLDER
171
+
172
+ def to_dict(self) -> Dict[str, Any]:
173
+ return {
174
+ "role": "assistant",
175
+ "content": self.content,
176
+ "audio_codes_list": self.audio_codes_list,
177
+ }
178
+
179
+
180
+ class MossTTSLocalProcessor(ProcessorMixin):
181
+ attributes = ["tokenizer"]
182
+ tokenizer_class = "AutoTokenizer"
183
+ audio_tokenizer_class = "AutoModel"
184
+
185
+ tokenizer: PreTrainedTokenizerBase
186
+ audio_tokenizer: Any
187
+
188
+ def __init__(
189
+ self,
190
+ tokenizer: PreTrainedTokenizerBase,
191
+ audio_tokenizer: Any = None,
192
+ model_config: Optional[MossTTSLocalConfig] = None,
193
+ **kwargs,
194
+ ) -> None:
195
+ super().__init__(tokenizer=tokenizer, audio_tokenizer=audio_tokenizer, **kwargs)
196
+ self.tokenizer = tokenizer
197
+ self.audio_tokenizer = audio_tokenizer
198
+ self.model_config = model_config or MossTTSLocalConfig()
199
+
200
+ def _id_to_token(token_id: int) -> str:
201
+ token = tokenizer.convert_ids_to_tokens(int(token_id))
202
+ if isinstance(token, list):
203
+ return token[0] if token else ""
204
+ return cast(str, token)
205
+
206
+ self.audio_user_slot_token = _id_to_token(self.model_config.audio_user_slot_token_id)
207
+ self.audio_assistant_slot_token = _id_to_token(self.model_config.audio_assistant_slot_token_id)
208
+ self.audio_start_token = _id_to_token(self.model_config.audio_start_token_id)
209
+ self.audio_end_token = _id_to_token(self.model_config.audio_end_token_id)
210
+
211
+ @classmethod
212
+ def from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs):
213
+ trust_remote_code = kwargs.pop("trust_remote_code", True)
214
+ kwargs.pop("_from_auto", None)
215
+ codec_path = kwargs.pop("codec_path", None)
216
+ codec_weight_dtype = kwargs.pop("codec_weight_dtype", "fp32")
217
+ codec_compute_dtype = kwargs.pop("codec_compute_dtype", None)
218
+ codec_attention_implementation = kwargs.pop("codec_attention_implementation", None)
219
+
220
+ codec_kwargs: Dict[str, Any] = {}
221
+ if codec_weight_dtype is not None:
222
+ # Default to fp32 codec weights; callers can pass codec_weight_dtype="bf16" to reduce memory.
223
+ codec_kwargs["codec_weight_dtype"] = codec_weight_dtype
224
+
225
+ model_ref = Path(str(pretrained_model_name_or_path))
226
+ model_ref_or_name = model_ref if model_ref.exists() else pretrained_model_name_or_path
227
+ model_config = cast(
228
+ MossTTSLocalConfig,
229
+ AutoConfig.from_pretrained(
230
+ model_ref_or_name,
231
+ *args,
232
+ trust_remote_code=trust_remote_code,
233
+ **kwargs,
234
+ ),
235
+ )
236
+
237
+ if codec_path is None:
238
+ try:
239
+ processor_dict, _ = cls.get_processor_dict(
240
+ pretrained_model_name_or_path,
241
+ **dict(kwargs),
242
+ )
243
+ codec_path = processor_dict.get("audio_tokenizer_name_or_path")
244
+ audio_tokenizer_dict = processor_dict.get("audio_tokenizer", {})
245
+ if isinstance(audio_tokenizer_dict, dict):
246
+ codec_path = audio_tokenizer_dict.get("audio_tokenizer_name_or_path") or codec_path
247
+ except Exception:
248
+ codec_path = None
249
+ if codec_path is None:
250
+ codec_path = getattr(model_config, "audio_tokenizer_name_or_path", None)
251
+ if codec_path is None:
252
+ codec_path = "OpenMOSS-Team/MOSS-Audio-Tokenizer-v2"
253
+
254
+ tokenizer = AutoTokenizer.from_pretrained(
255
+ model_ref_or_name,
256
+ *args,
257
+ trust_remote_code=trust_remote_code,
258
+ **kwargs,
259
+ )
260
+ audio_tokenizer = AutoModel.from_pretrained(
261
+ codec_path,
262
+ trust_remote_code=trust_remote_code,
263
+ **kwargs,
264
+ **codec_kwargs,
265
+ )
266
+ if codec_compute_dtype is not None and hasattr(audio_tokenizer, "set_compute_dtype"):
267
+ audio_tokenizer.set_compute_dtype(codec_compute_dtype)
268
+ if codec_attention_implementation is not None and hasattr(
269
+ audio_tokenizer, "set_attention_implementation"
270
+ ):
271
+ audio_tokenizer.set_attention_implementation(codec_attention_implementation)
272
+ return cls(
273
+ tokenizer=tokenizer,
274
+ audio_tokenizer=audio_tokenizer,
275
+ model_config=model_config,
276
+ **kwargs,
277
+ )
278
+
279
+ @staticmethod
280
+ def build_user_message(
281
+ text: Optional[str] = None,
282
+ reference: Optional[List[Optional[Union[str, os.PathLike, torch.Tensor]]]] = None,
283
+ instruction: Optional[str] = None,
284
+ tokens: Optional[int] = None,
285
+ quality: Optional[str] = None,
286
+ sound_event: Optional[str] = None,
287
+ ambient_sound: Optional[str] = None,
288
+ language: Optional[str] = None,
289
+ ) -> Dict[str, Any]:
290
+ if reference is not None and not isinstance(reference, list):
291
+ reference = [reference]
292
+ return UserMessage(
293
+ text=text,
294
+ reference=reference,
295
+ instruction=instruction,
296
+ tokens=tokens,
297
+ quality=quality,
298
+ sound_event=sound_event,
299
+ ambient_sound=ambient_sound,
300
+ language=language,
301
+ ).to_dict()
302
+
303
+ @staticmethod
304
+ def build_assistant_message(
305
+ audio_codes_list: List[Union[str, os.PathLike, torch.Tensor]],
306
+ content: str = AUDIO_PLACEHOLDER,
307
+ ) -> Dict[str, Any]:
308
+ return AssistantMessage(audio_codes_list=audio_codes_list, content=content).to_dict()
309
+
310
+ def _assert_fixed_nq(self, n_vq: Optional[int]) -> int:
311
+ config_nq = int(self.model_config.n_vq)
312
+ if n_vq is not None and int(n_vq) != config_nq:
313
+ raise ValueError(
314
+ "This MOSS-TTS-Local-Transformer-v1.5 release uses the RVQ depth stored in the model config. "
315
+ f"Expected n_vq={config_nq}, got {int(n_vq)}."
316
+ )
317
+ return config_nq
318
+
319
+ def _encode_text(self, text: str) -> list[int]:
320
+ try:
321
+ return list(self.tokenizer.encode(text, add_special_tokens=False))
322
+ except TypeError:
323
+ return list(self.tokenizer.encode(text))
324
+
325
+ def _build_text_rows(self, token_ids: Sequence[int], *, device: Optional[torch.device] = None) -> torch.Tensor:
326
+ rows = torch.full(
327
+ (len(token_ids), int(self.model_config.n_vq) + 1),
328
+ int(self.model_config.audio_pad_token_id),
329
+ dtype=torch.long,
330
+ device=device,
331
+ )
332
+ if token_ids:
333
+ rows[:, 0] = torch.tensor([int(token_id) for token_id in token_ids], dtype=torch.long, device=rows.device)
334
+ return rows
335
+
336
+ def _build_audio_rows(self, audio_tokens: torch.Tensor, slot_token_id: int) -> torch.Tensor:
337
+ rows = torch.full(
338
+ (int(audio_tokens.shape[0]), int(self.model_config.n_vq) + 1),
339
+ int(self.model_config.audio_pad_token_id),
340
+ dtype=torch.long,
341
+ device=audio_tokens.device,
342
+ )
343
+ if rows.shape[0] > 0:
344
+ rows[:, 0] = int(slot_token_id)
345
+ rows[:, 1:] = audio_tokens.to(dtype=torch.long)
346
+ return rows
347
+
348
+ def _user_prompt_prefix_ids(self) -> list[int]:
349
+ return (
350
+ [int(self.model_config.im_start_token_id)]
351
+ + self._encode_text(USER_ROLE_PREFIX)
352
+ + self._encode_text(USER_TEMPLATE_REFERENCE_PREFIX)
353
+ )
354
+
355
+ def _user_prompt_after_reference_ids(
356
+ self,
357
+ language_code: object | None,
358
+ prompt_fields: Optional[Dict[str, Any]],
359
+ ) -> list[int]:
360
+ return self._encode_text(
361
+ _render_user_prompt_after_reference(
362
+ language_code=language_code,
363
+ prompt_fields=prompt_fields,
364
+ )
365
+ )
366
+
367
+ def _assistant_prompt_prefix_ids(self) -> list[int]:
368
+ return (
369
+ self._encode_text(USER_TEMPLATE_SUFFIX)
370
+ + [int(self.model_config.im_end_token_id)]
371
+ + self._encode_text(ASSISTANT_TURN_PREFIX)
372
+ + [int(self.model_config.im_start_token_id)]
373
+ + self._encode_text(ASSISTANT_ROLE_PREFIX)
374
+ )
375
+
376
+ def _prompt_fields_from_user_message(self, message: Dict[str, Any]) -> dict[str, Any]:
377
+ fields = {}
378
+ for key in ("instruction", "tokens", "quality", "sound_event", "ambient_sound"):
379
+ if key in message and message.get(key) is not None:
380
+ fields[key] = message.get(key)
381
+ if "language" in message and message.get("language") is not None:
382
+ fields["language"] = message.get("language")
383
+ return fields
384
+
385
+ def _build_generation_or_voice_clone_codes(
386
+ self,
387
+ message: Dict[str, Any],
388
+ n_vq: int,
389
+ ) -> torch.Tensor:
390
+ if "text" not in message:
391
+ raise ValueError("Direct MOSS-TTS-Local-Transformer-v1.5 generation requires messages built by build_user_message(...).")
392
+ text = "" if message.get("text") is None else str(message.get("text"))
393
+ prompt_fields = self._prompt_fields_from_user_message(message)
394
+ language_code = message.get("language")
395
+ audio_codes_list = self._resolve_audio_items(message.get("audio_codes_list", []), n_vq)
396
+ text_token_ids = self._encode_text(text)
397
+
398
+ if audio_codes_list:
399
+ parts: list[torch.Tensor] = [self._build_text_rows(
400
+ self._user_prompt_prefix_ids(),
401
+ device=audio_codes_list[0].device,
402
+ )]
403
+ for reference_codes in audio_codes_list:
404
+ parts.append(self._build_text_rows([int(self.model_config.audio_start_token_id)], device=reference_codes.device))
405
+ parts.append(self._build_audio_rows(reference_codes, int(self.model_config.audio_user_slot_token_id)))
406
+ parts.append(self._build_text_rows([int(self.model_config.audio_end_token_id)], device=reference_codes.device))
407
+ parts.append(
408
+ self._build_text_rows(
409
+ self._user_prompt_after_reference_ids(language_code, prompt_fields)
410
+ + text_token_ids
411
+ + self._assistant_prompt_prefix_ids()
412
+ + [int(self.model_config.audio_start_token_id)],
413
+ device=audio_codes_list[0].device,
414
+ )
415
+ )
416
+ return torch.cat(parts, dim=0)
417
+
418
+ prompt_token_ids = (
419
+ self._user_prompt_prefix_ids()
420
+ + self._encode_text("None")
421
+ + self._user_prompt_after_reference_ids(language_code, prompt_fields)
422
+ + text_token_ids
423
+ + self._assistant_prompt_prefix_ids()
424
+ + [int(self.model_config.audio_start_token_id)]
425
+ )
426
+ return self._build_text_rows(prompt_token_ids)
427
+
428
+ def _build_continuation_codes(
429
+ self,
430
+ conversation: list[Dict[str, Any]],
431
+ n_vq: int,
432
+ ) -> torch.Tensor:
433
+ if len(conversation) < 2:
434
+ raise ValueError("continuation mode requires a user message followed by an assistant audio message.")
435
+ user_message = conversation[-2]
436
+ assistant_message = conversation[-1]
437
+ if user_message.get("role") != "user" or assistant_message.get("role") != "assistant":
438
+ raise ValueError("continuation mode requires the last two messages to be user, assistant.")
439
+ if "text" not in user_message:
440
+ raise ValueError("Direct MOSS-TTS-Local-Transformer-v1.5 continuation requires user messages built by build_user_message(...).")
441
+
442
+ text = "" if user_message.get("text") is None else str(user_message.get("text"))
443
+ prompt_fields = self._prompt_fields_from_user_message(user_message)
444
+ language_code = user_message.get("language")
445
+ prompt_token_ids = (
446
+ self._user_prompt_prefix_ids()
447
+ + self._encode_text("None")
448
+ + self._user_prompt_after_reference_ids(language_code, prompt_fields)
449
+ + self._encode_text(text)
450
+ + self._assistant_prompt_prefix_ids()
451
+ + [int(self.model_config.audio_start_token_id)]
452
+ )
453
+ audio_codes_list = self._resolve_audio_items(assistant_message.get("audio_codes_list", []), n_vq)
454
+ if not audio_codes_list:
455
+ return self._build_text_rows(prompt_token_ids)
456
+ if len(audio_codes_list) != 1:
457
+ raise ValueError("The MOSS-TTS-Local-Transformer-v1.5 continuation path expects exactly one prompt audio item.")
458
+ prompt_audio_codes = audio_codes_list[0]
459
+ return torch.cat(
460
+ [
461
+ self._build_text_rows(prompt_token_ids, device=prompt_audio_codes.device),
462
+ self._build_audio_rows(prompt_audio_codes, int(self.model_config.audio_assistant_slot_token_id)),
463
+ ],
464
+ dim=0,
465
+ )
466
+
467
+ def _try_build_direct_codes(
468
+ self,
469
+ conversation: list[Dict[str, Any]],
470
+ mode: str,
471
+ n_vq: int,
472
+ ) -> Optional[torch.Tensor]:
473
+ if mode == "generation" and len(conversation) == 1 and conversation[-1].get("role") == "user":
474
+ if "text" in conversation[-1]:
475
+ return self._build_generation_or_voice_clone_codes(conversation[-1], n_vq)
476
+ return None
477
+ if mode == "continuation" and len(conversation) >= 2:
478
+ if "text" in conversation[-2]:
479
+ return self._build_continuation_codes(conversation, n_vq)
480
+ return None
481
+ return None
482
+
483
+ def __call__(self, *args, **kwargs) -> BatchFeature:
484
+ conversations = args[0] if args else kwargs.pop("conversations")
485
+ mode: str = kwargs.pop("mode", "generation")
486
+ apply_chat_template: bool = kwargs.pop("apply_chat_template", True)
487
+ n_vq = self._assert_fixed_nq(kwargs.pop("n_vq", None))
488
+
489
+ kwargs.pop("return_tensors", None)
490
+ kwargs.pop("padding", None)
491
+ kwargs.pop("truncation", None)
492
+
493
+ if mode not in {"generation", "continuation", "computing_loss"}:
494
+ raise ValueError(f"Unsupported mode: {mode}")
495
+ if isinstance(conversations, (Message, dict)):
496
+ conversations = [conversations]
497
+ elif isinstance(conversations, list) and conversations and all(
498
+ isinstance(item, (Message, dict)) for item in conversations
499
+ ):
500
+ conversations = [conversations]
501
+
502
+ input_ids_list: list[torch.Tensor] = []
503
+ for conversation in conversations:
504
+ if isinstance(conversation, (Message, dict)):
505
+ conversation = [conversation]
506
+ conversation = [self._normalize_message(message) for message in conversation]
507
+
508
+ if (mode == "generation") ^ (conversation[-1]["role"] == "user"):
509
+ raise ValueError("generation mode must end with a user message.")
510
+ if mode == "continuation" and conversation[-1]["role"] != "assistant":
511
+ raise ValueError("continuation mode must end with an assistant message.")
512
+
513
+ direct_codes = self._try_build_direct_codes(conversation, mode, n_vq)
514
+ if direct_codes is not None:
515
+ input_ids_list.append(direct_codes)
516
+ continue
517
+
518
+ unified_parts = []
519
+ for message_idx, message in enumerate(conversation):
520
+ content = str(message["content"])
521
+ if apply_chat_template:
522
+ add_generation_prompt = mode == "generation" and message_idx == len(conversation) - 1
523
+ try:
524
+ content = self.tokenizer.apply_chat_template(
525
+ [{"role": message["role"], "content": content}],
526
+ add_generation_prompt=add_generation_prompt,
527
+ tokenize=False,
528
+ )
529
+ except Exception:
530
+ logger.warning("apply_chat_template failed; falling back to raw message content.")
531
+
532
+ raw_audio_items = message.get("audio_codes_list", [])
533
+ audio_codes_list = self._resolve_audio_items(raw_audio_items, n_vq)
534
+ unified_parts.append(
535
+ self._get_unified_codes(
536
+ role=message["role"],
537
+ content=content,
538
+ audio_codes_list=audio_codes_list,
539
+ truncation=(mode == "continuation"),
540
+ )
541
+ )
542
+
543
+ unified_codes = torch.cat(unified_parts, dim=0)
544
+ if mode == "generation":
545
+ audio_start_row = torch.full(
546
+ (1, n_vq + 1),
547
+ int(self.model_config.audio_pad_token_id),
548
+ dtype=unified_codes.dtype,
549
+ device=unified_codes.device,
550
+ )
551
+ audio_start_row[:, 0] = int(self.model_config.audio_start_token_id)
552
+ unified_codes = torch.cat([unified_codes, audio_start_row], dim=0)
553
+ input_ids_list.append(unified_codes)
554
+
555
+ return BatchFeature(data=self._pad(input_ids_list))
556
+
557
+ def _normalize_message(self, message: Union[Message, Dict[str, Any]]) -> Dict[str, Any]:
558
+ if isinstance(message, Message):
559
+ return message.to_dict()
560
+ if not isinstance(message, dict):
561
+ raise TypeError("Each message must be a Message or dict.")
562
+ if "content" in message and "audio_codes_list" in message:
563
+ return message
564
+ role = message.get("role")
565
+ if role == "user":
566
+ return self.build_user_message(**{key: message.get(key) for key in USER_MESSAGE_FIELDS})
567
+ if role == "assistant":
568
+ return self.build_assistant_message(
569
+ audio_codes_list=message.get("audio_codes_list", []),
570
+ content=message.get("content", AUDIO_PLACEHOLDER),
571
+ )
572
+ raise ValueError(f"Unsupported role: {role}")
573
+
574
+ def _resolve_audio_items(
575
+ self,
576
+ raw_audio_items: list[Any],
577
+ n_vq: int,
578
+ ) -> list[torch.Tensor]:
579
+ if not raw_audio_items:
580
+ return []
581
+ resolved: list[Optional[torch.Tensor]] = [None] * len(raw_audio_items)
582
+ paths: list[str] = []
583
+ path_positions: list[int] = []
584
+ for index, item in enumerate(raw_audio_items):
585
+ if isinstance(item, torch.Tensor):
586
+ if item.ndim != 2 or int(item.shape[1]) != n_vq:
587
+ raise ValueError(f"audio code tensor must have shape [T, {n_vq}], got {tuple(item.shape)}.")
588
+ resolved[index] = item.to(dtype=torch.long).cpu()
589
+ elif isinstance(item, (str, os.PathLike)):
590
+ paths.append(str(item))
591
+ path_positions.append(index)
592
+ else:
593
+ raise TypeError("Audio items must be tensors or path-like values.")
594
+ if paths:
595
+ encoded = self.encode_audios_from_path(paths, n_vq=n_vq)
596
+ for position, codes in zip(path_positions, encoded):
597
+ resolved[position] = codes
598
+ return [cast(torch.Tensor, item) for item in resolved]
599
+
600
+ def _pad(self, input_ids_list: list[torch.Tensor]) -> Dict[str, torch.Tensor]:
601
+ device = input_ids_list[0].device
602
+ lengths = torch.tensor([item.shape[0] for item in input_ids_list], device=device)
603
+ padded = torch.nn.utils.rnn.pad_sequence(
604
+ input_ids_list,
605
+ batch_first=True,
606
+ padding_value=int(self.model_config.audio_pad_token_id),
607
+ padding_side="left",
608
+ )
609
+ left_pad_mask = (padded.shape[1] - lengths).unsqueeze(1) > torch.arange(
610
+ padded.shape[1],
611
+ device=device,
612
+ ).unsqueeze(0)
613
+ padded[..., 0][left_pad_mask] = int(self.model_config.pad_token_id)
614
+ attention_mask = torch.zeros(padded.shape[:2], dtype=torch.bool, device=device)
615
+ attention_mask[~left_pad_mask] = True
616
+ return {"input_ids": padded, "attention_mask": attention_mask}
617
+
618
+ @staticmethod
619
+ def _replace_audio_placeholders(
620
+ content: str,
621
+ lengths: list[int],
622
+ slot_token: str,
623
+ audio_start_token: str,
624
+ audio_end_token: str,
625
+ ) -> str:
626
+ placeholder_count = content.count(AUDIO_PLACEHOLDER)
627
+ if placeholder_count != len(lengths):
628
+ raise ValueError(
629
+ f"Number of {AUDIO_PLACEHOLDER} ({placeholder_count}) does not match "
630
+ f"audio item count ({len(lengths)})."
631
+ )
632
+ lengths_iter = iter(lengths)
633
+
634
+ def replacer(_: re.Match) -> str:
635
+ length = int(next(lengths_iter))
636
+ if length <= 0:
637
+ return f"{audio_start_token}{audio_end_token}"
638
+ return f"{audio_start_token}{slot_token * length}{audio_end_token}"
639
+
640
+ return re.sub(re.escape(AUDIO_PLACEHOLDER), replacer, content)
641
+
642
+ def _get_unified_codes(
643
+ self,
644
+ role: str,
645
+ content: str,
646
+ audio_codes_list: list[torch.Tensor],
647
+ truncation: bool,
648
+ ) -> torch.Tensor:
649
+ n_vq = int(self.model_config.n_vq)
650
+ slot_token = self.audio_user_slot_token if role == "user" else self.audio_assistant_slot_token
651
+ content = self._replace_audio_placeholders(
652
+ content=content,
653
+ lengths=[int(codes.shape[0]) for codes in audio_codes_list],
654
+ slot_token=slot_token,
655
+ audio_start_token=self.audio_start_token,
656
+ audio_end_token=self.audio_end_token,
657
+ )
658
+ text_codes = torch.tensor(
659
+ self.tokenizer.encode(content),
660
+ dtype=torch.long,
661
+ device=audio_codes_list[0].device if audio_codes_list else None,
662
+ )
663
+
664
+ audio_start_indices = torch.where(text_codes == int(self.model_config.audio_start_token_id))[0]
665
+ audio_end_indices = torch.where(text_codes == int(self.model_config.audio_end_token_id))[0]
666
+ if len(audio_start_indices) != len(audio_codes_list) or len(audio_end_indices) != len(audio_codes_list):
667
+ raise ValueError("Audio placeholders do not match the encoded audio spans.")
668
+
669
+ if not audio_codes_list:
670
+ audio_codes = torch.full(
671
+ (len(text_codes), n_vq),
672
+ int(self.model_config.audio_pad_token_id),
673
+ dtype=torch.long,
674
+ device=text_codes.device,
675
+ )
676
+ else:
677
+ pieces: list[torch.Tensor] = []
678
+ prefix_idx = 0
679
+ for start_t, end_t, codes in zip(audio_start_indices, audio_end_indices, audio_codes_list):
680
+ start_idx = int(start_t.item())
681
+ end_idx = int(end_t.item())
682
+ pad_before = torch.full(
683
+ (start_idx - prefix_idx + 1, n_vq),
684
+ int(self.model_config.audio_pad_token_id),
685
+ dtype=torch.long,
686
+ device=codes.device,
687
+ )
688
+ pieces.extend([pad_before, codes.to(dtype=torch.long)])
689
+ prefix_idx = end_idx
690
+ if truncation:
691
+ trailing = torch.zeros(
692
+ (0, n_vq),
693
+ dtype=torch.long,
694
+ device=audio_codes_list[0].device,
695
+ )
696
+ else:
697
+ last_end = int(audio_end_indices[-1].item())
698
+ trailing = torch.full(
699
+ (len(text_codes) - last_end, n_vq),
700
+ int(self.model_config.audio_pad_token_id),
701
+ dtype=torch.long,
702
+ device=audio_codes_list[0].device,
703
+ )
704
+ pieces.append(trailing)
705
+ audio_codes = torch.cat(pieces, dim=0)
706
+
707
+ if text_codes.shape[0] != audio_codes.shape[0]:
708
+ min_len = min(text_codes.shape[0], audio_codes.shape[0])
709
+ text_codes = text_codes[:min_len]
710
+ audio_codes = audio_codes[:min_len]
711
+ return torch.cat([text_codes.unsqueeze(1), audio_codes], dim=1)
712
+
713
+ def _parse_text_codes(self, start_length: int, text_codes: torch.LongTensor) -> str:
714
+ text = cast(str, self.tokenizer.decode(text_codes))
715
+ prefix = cast(str, self.tokenizer.decode(text_codes[:start_length]))
716
+ text = text[len(prefix):]
717
+ audio_pattern = re.compile(
718
+ rf"(?:{re.escape(self.audio_start_token)})?"
719
+ rf"(?:{re.escape(self.audio_assistant_slot_token)})*"
720
+ rf"{re.escape(self.audio_end_token)}"
721
+ )
722
+ return audio_pattern.sub(
723
+ lambda match: AUDIO_PLACEHOLDER if self.audio_assistant_slot_token in match.group(0) else "",
724
+ text,
725
+ )
726
+
727
+ def _parse_audio_codes(
728
+ self,
729
+ start_length: int,
730
+ audio_codes: torch.LongTensor,
731
+ *,
732
+ return_stereo: bool = True,
733
+ ) -> list[torch.Tensor]:
734
+ is_pad = audio_codes.eq(int(self.model_config.audio_pad_token_id)).all(dim=1)
735
+ non_pad = ~is_pad
736
+ if not bool(non_pad.any().item()):
737
+ return []
738
+ idx = torch.nonzero(non_pad).squeeze(1)
739
+ breaks = torch.where(idx[1:] != idx[:-1] + 1)[0] + 1
740
+ segment_indices = [idx] if breaks.numel() == 0 else list(torch.tensor_split(idx, breaks.cpu().tolist()))
741
+ code_segments = [audio_codes[segment] for segment in segment_indices]
742
+ decoded = self.decode_audio_codes(code_segments, return_stereo=return_stereo)
743
+
744
+ if start_length > 0 and code_segments and decoded:
745
+ first_code_length = int(code_segments[0].shape[0])
746
+ if first_code_length > 0:
747
+ trim_ratio = max(0.0, min(float(start_length) / float(first_code_length), 1.0))
748
+ if trim_ratio >= 1.0:
749
+ decoded = decoded[1:]
750
+ elif trim_ratio > 0.0:
751
+ trim_samples = int(decoded[0].shape[-1] * trim_ratio)
752
+ decoded[0] = decoded[0][..., trim_samples:]
753
+ return decoded
754
+
755
+ def decode(self, output: Any, *, return_stereo: bool = True) -> list[Optional[AssistantMessage]]:
756
+ generated_messages: list[Optional[AssistantMessage]] = []
757
+ for start_length, generation_ids in output:
758
+ content = self._parse_text_codes(int(start_length), generation_ids[:, 0])
759
+ audio_codes_list = self._parse_audio_codes(
760
+ int(start_length),
761
+ generation_ids[:, 1:],
762
+ return_stereo=return_stereo,
763
+ )
764
+ if content == "":
765
+ generated_messages.append(None)
766
+ else:
767
+ generated_messages.append(
768
+ AssistantMessage(
769
+ content=content,
770
+ audio_codes_list=cast(list[Union[str, torch.Tensor]], audio_codes_list),
771
+ )
772
+ )
773
+ return generated_messages
774
+
775
+ @staticmethod
776
+ def loudness_normalize(
777
+ wav: torch.Tensor,
778
+ target_dbfs: float = -20.0,
779
+ gain_range: tuple[float, float] = (-3.0, 3.0),
780
+ ) -> torch.Tensor:
781
+ wav = wav.to(torch.float32)
782
+ if wav.numel() == 0:
783
+ return wav
784
+ current_dbfs = 10.0 * torch.log10(torch.mean(wav**2) + 1e-9)
785
+ gain = max(gain_range[0], min(float(target_dbfs - current_dbfs), gain_range[1]))
786
+ return wav * (10.0 ** (gain / 20.0))
787
+
788
+ def _get_audio_tokenizer_device(self) -> torch.device:
789
+ audio_tokenizer = getattr(self, "audio_tokenizer", None)
790
+ if audio_tokenizer is None:
791
+ raise RuntimeError("audio_tokenizer is not set.")
792
+ try:
793
+ return next(audio_tokenizer.parameters()).device
794
+ except StopIteration:
795
+ return torch.device("cpu")
796
+
797
+ def encode_audios_from_wav(
798
+ self,
799
+ wav_list: Union[torch.Tensor, list[torch.Tensor]],
800
+ sampling_rate: int,
801
+ n_vq: Optional[int] = None,
802
+ ) -> list[torch.Tensor]:
803
+ n_vq = self._assert_fixed_nq(n_vq)
804
+ if self.audio_tokenizer is None:
805
+ raise RuntimeError("audio_tokenizer is not set.")
806
+ if isinstance(wav_list, torch.Tensor):
807
+ wav_list = [wav_list]
808
+ target_sr = int(self.model_config.sampling_rate)
809
+ device = self._get_audio_tokenizer_device()
810
+ prepared = []
811
+ for wav in wav_list:
812
+ if wav.ndim == 1:
813
+ wav = wav.unsqueeze(0)
814
+ if wav.shape[0] == 1:
815
+ wav = wav.repeat(2, 1)
816
+ elif wav.shape[0] > 2:
817
+ wav = wav[:2]
818
+ if int(sampling_rate) != target_sr:
819
+ wav = torchaudio.functional.resample(wav, int(sampling_rate), target_sr)
820
+ prepared.append(self.loudness_normalize(wav).to(device))
821
+
822
+ if hasattr(self.audio_tokenizer, "batch_encode"):
823
+ encoded = self.audio_tokenizer.batch_encode(prepared, num_quantizers=n_vq)
824
+ audio_codes = encoded.audio_codes
825
+ audio_lengths = encoded.audio_codes_lengths
826
+ else:
827
+ max_len = max(int(wav.shape[-1]) for wav in prepared)
828
+ input_values = torch.zeros(len(prepared), 1, max_len, dtype=torch.float32, device=device)
829
+ padding_mask = torch.zeros(len(prepared), max_len, dtype=torch.bool, device=device)
830
+ for index, wav in enumerate(prepared):
831
+ input_values[index, 0, : wav.shape[-1]] = wav
832
+ padding_mask[index, : wav.shape[-1]] = True
833
+ encoded = self.audio_tokenizer.encode(
834
+ input_values,
835
+ padding_mask=padding_mask,
836
+ num_quantizers=n_vq,
837
+ return_dict=True,
838
+ )
839
+ audio_codes = encoded.audio_codes
840
+ audio_lengths = encoded.audio_codes_lengths
841
+
842
+ if audio_codes is None or audio_lengths is None:
843
+ raise RuntimeError("audio_tokenizer did not return audio_codes/audio_codes_lengths.")
844
+ result = []
845
+ for index in range(int(audio_codes.shape[1])):
846
+ length = int(audio_lengths[index].item())
847
+ result.append(audio_codes[:, index, :length].transpose(0, 1).contiguous().cpu().long())
848
+ return result
849
+
850
+ def encode_audios_from_path(
851
+ self,
852
+ wav_path_list: Union[str, os.PathLike, list[Union[str, os.PathLike]]],
853
+ n_vq: Optional[int] = None,
854
+ ) -> list[torch.Tensor]:
855
+ if isinstance(wav_path_list, (str, os.PathLike)):
856
+ wav_path_list = [wav_path_list]
857
+ wavs = []
858
+ target_sr = int(self.model_config.sampling_rate)
859
+ for wav_path in wav_path_list:
860
+ wav, sr = torchaudio.load(str(wav_path))
861
+ if int(sr) != target_sr:
862
+ wav = torchaudio.functional.resample(wav, int(sr), target_sr)
863
+ wavs.append(wav)
864
+ return self.encode_audios_from_wav(wavs, target_sr, n_vq=n_vq)
865
+
866
+ def decode_audio_codes(
867
+ self,
868
+ audio_tokens_list: Union[torch.Tensor, list[torch.Tensor]],
869
+ *,
870
+ return_stereo: bool = True,
871
+ ) -> list[torch.Tensor]:
872
+ if self.audio_tokenizer is None:
873
+ raise RuntimeError("audio_tokenizer is not set.")
874
+ if isinstance(audio_tokens_list, torch.Tensor):
875
+ audio_tokens_list = [audio_tokens_list]
876
+ if not audio_tokens_list:
877
+ return []
878
+
879
+ n_vq = int(self.model_config.n_vq)
880
+ device = self._get_audio_tokenizer_device()
881
+ codes_list = [
882
+ codes[:, :n_vq].transpose(0, 1).contiguous().to(device=device, dtype=torch.long)
883
+ for codes in audio_tokens_list
884
+ ]
885
+ max_len = max(int(codes.shape[1]) for codes in codes_list)
886
+ audio_codes = torch.zeros(n_vq, len(codes_list), max_len, device=device, dtype=torch.long)
887
+ padding_mask = torch.zeros(len(codes_list), max_len, device=device, dtype=torch.bool)
888
+ for index, codes in enumerate(codes_list):
889
+ length = int(codes.shape[1])
890
+ audio_codes[:, index, :length] = codes
891
+ padding_mask[index, :length] = True
892
+
893
+ decoded = self.audio_tokenizer.decode(
894
+ audio_codes,
895
+ padding_mask=padding_mask,
896
+ num_quantizers=n_vq,
897
+ return_dict=True,
898
+ chunk_duration=8,
899
+ )
900
+ audio = decoded.audio
901
+ audio_lengths = decoded.audio_lengths
902
+ if audio is None or audio_lengths is None:
903
+ raise RuntimeError("audio_tokenizer.decode did not return audio/audio_lengths.")
904
+ wavs = []
905
+ for index in range(int(audio.shape[0])):
906
+ length = int(audio_lengths[index].item())
907
+ wav = audio[index, :, :length].contiguous().cpu().to(torch.float32)
908
+ if not return_stereo:
909
+ if wav.shape[0] == 1:
910
+ wav = wav.squeeze(0)
911
+ else:
912
+ wav = wav.mean(dim=0)
913
+ wavs.append(wav)
914
+ return wavs
code/moss_export/qwen3_decoder.py ADDED
@@ -0,0 +1,582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.utils.checkpoint
11
+ from safetensors.torch import load_file
12
+ from transformers.activations import ACT2FN
13
+ from transformers.modeling_outputs import BaseModelOutputWithPast
14
+
15
+ from .gpt2_decoder import PackedSequenceMetadata, MossTTSNanoGPT2Model
16
+
17
+ try:
18
+ from flash_attn import flash_attn_func, flash_attn_varlen_func
19
+ from flash_attn.bert_padding import pad_input, unpad_input
20
+
21
+ _FLASH_ATTN_AVAILABLE = True
22
+ except Exception:
23
+ flash_attn_func = None
24
+ flash_attn_varlen_func = None
25
+ pad_input = None
26
+ unpad_input = None
27
+ _FLASH_ATTN_AVAILABLE = False
28
+
29
+
30
+ class MossQwen3RMSNorm(nn.Module):
31
+ def __init__(self, hidden_size: int, eps: float = 1e-6) -> None:
32
+ super().__init__()
33
+ self.weight = nn.Parameter(torch.ones(hidden_size))
34
+ self.variance_epsilon = eps
35
+
36
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
37
+ input_dtype = hidden_states.dtype
38
+ hidden_states = hidden_states.to(torch.float32)
39
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
40
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
41
+ return self.weight * hidden_states.to(input_dtype)
42
+
43
+
44
+ class MossQwen3RotaryEmbedding(nn.Module):
45
+ def __init__(self, config) -> None:
46
+ super().__init__()
47
+ head_dim = int(getattr(config, "head_dim", config.hidden_size // config.num_attention_heads))
48
+ rope_theta = getattr(config, "rope_theta", None)
49
+ if rope_theta is None:
50
+ rope_scaling = getattr(config, "rope_scaling", None)
51
+ if isinstance(rope_scaling, dict):
52
+ rope_theta = rope_scaling.get("rope_theta")
53
+ if rope_theta is None:
54
+ rope_theta = 1000000.0
55
+ rope_theta = float(rope_theta)
56
+ self.head_dim = head_dim
57
+ self.rope_theta = rope_theta
58
+ self.register_buffer("inv_freq", self._compute_inv_freq(), persistent=False)
59
+
60
+ def _compute_inv_freq(self, device: Optional[torch.device] = None) -> torch.Tensor:
61
+ return 1.0 / (
62
+ self.rope_theta ** (torch.arange(0, self.head_dim, 2, device=device, dtype=torch.float32) / self.head_dim)
63
+ )
64
+
65
+ def forward(
66
+ self,
67
+ hidden_states: torch.Tensor,
68
+ position_ids: torch.LongTensor,
69
+ ) -> tuple[torch.Tensor, torch.Tensor]:
70
+ inv_freq = self._compute_inv_freq(device=hidden_states.device)
71
+ freqs = torch.einsum(
72
+ "bs,d->bsd",
73
+ position_ids.to(device=hidden_states.device, dtype=inv_freq.dtype),
74
+ inv_freq,
75
+ )
76
+ emb = torch.cat((freqs, freqs), dim=-1)
77
+ return emb.cos().to(dtype=hidden_states.dtype), emb.sin().to(dtype=hidden_states.dtype)
78
+
79
+
80
+ def rotate_half(hidden_states: torch.Tensor) -> torch.Tensor:
81
+ first_half = hidden_states[..., : hidden_states.shape[-1] // 2]
82
+ second_half = hidden_states[..., hidden_states.shape[-1] // 2 :]
83
+ return torch.cat((-second_half, first_half), dim=-1)
84
+
85
+
86
+ def apply_rotary_pos_emb(
87
+ query: torch.Tensor,
88
+ key: torch.Tensor,
89
+ cos: torch.Tensor,
90
+ sin: torch.Tensor,
91
+ ) -> tuple[torch.Tensor, torch.Tensor]:
92
+ cos = cos.unsqueeze(-2)
93
+ sin = sin.unsqueeze(-2)
94
+ query = (query * cos) + (rotate_half(query) * sin)
95
+ key = (key * cos) + (rotate_half(key) * sin)
96
+ return query, key
97
+
98
+
99
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
100
+ if n_rep == 1:
101
+ return hidden_states
102
+ batch, seq_len, num_key_value_heads, head_dim = hidden_states.shape
103
+ hidden_states = hidden_states[:, :, :, None, :].expand(batch, seq_len, num_key_value_heads, n_rep, head_dim)
104
+ return hidden_states.reshape(batch, seq_len, num_key_value_heads * n_rep, head_dim)
105
+
106
+
107
+ class MossQwen3MLP(nn.Module):
108
+ def __init__(self, config) -> None:
109
+ super().__init__()
110
+ self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
111
+ self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
112
+ self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
113
+ self.act_fn = ACT2FN[getattr(config, "hidden_act", "silu")]
114
+
115
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
116
+ return self.down_proj(self.act_fn(self.gate_proj(hidden_states)) * self.up_proj(hidden_states))
117
+
118
+
119
+ class MossQwen3Attention(nn.Module):
120
+ def __init__(self, config, layer_idx: int) -> None:
121
+ super().__init__()
122
+ self.config = config
123
+ self.layer_idx = int(layer_idx)
124
+ self.hidden_size = int(config.hidden_size)
125
+ self.num_heads = int(config.num_attention_heads)
126
+ self.num_key_value_heads = int(config.num_key_value_heads)
127
+ self.head_dim = int(getattr(config, "head_dim", self.hidden_size // self.num_heads))
128
+ self.num_key_value_groups = self.num_heads // self.num_key_value_heads
129
+ self.scaling = self.head_dim ** -0.5
130
+ self.attention_dropout = float(getattr(config, "attention_dropout", 0.0))
131
+ self.attn_implementation = str(getattr(config, "_attn_implementation", "eager"))
132
+
133
+ self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=bool(config.attention_bias))
134
+ self.k_proj = nn.Linear(
135
+ self.hidden_size,
136
+ self.num_key_value_heads * self.head_dim,
137
+ bias=bool(config.attention_bias),
138
+ )
139
+ self.v_proj = nn.Linear(
140
+ self.hidden_size,
141
+ self.num_key_value_heads * self.head_dim,
142
+ bias=bool(config.attention_bias),
143
+ )
144
+ self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=bool(config.attention_bias))
145
+ self.q_norm = MossQwen3RMSNorm(self.head_dim, eps=float(config.rms_norm_eps))
146
+ self.k_norm = MossQwen3RMSNorm(self.head_dim, eps=float(config.rms_norm_eps))
147
+
148
+ def _causal_attention_mask(
149
+ self,
150
+ attention_mask: Optional[torch.Tensor],
151
+ query_length: int,
152
+ key_length: int,
153
+ device: torch.device,
154
+ ) -> torch.Tensor:
155
+ query_positions = torch.arange(query_length, device=device, dtype=torch.long)
156
+ query_positions = query_positions + max(key_length - query_length, 0)
157
+ key_positions = torch.arange(key_length, device=device, dtype=torch.long)
158
+ causal = key_positions.unsqueeze(0) <= query_positions.unsqueeze(1)
159
+ causal = causal.unsqueeze(0).unsqueeze(0)
160
+ if attention_mask is None:
161
+ return causal
162
+ key_mask = attention_mask[:, None, None, :].to(dtype=torch.bool)
163
+ return causal & key_mask
164
+
165
+ def _eager_attention(
166
+ self,
167
+ query: torch.Tensor,
168
+ key: torch.Tensor,
169
+ value: torch.Tensor,
170
+ attention_mask: Optional[torch.Tensor],
171
+ ) -> torch.Tensor:
172
+ key = repeat_kv(key, self.num_key_value_groups)
173
+ value = repeat_kv(value, self.num_key_value_groups)
174
+ query = query.transpose(1, 2)
175
+ key = key.transpose(1, 2)
176
+ value = value.transpose(1, 2)
177
+ scores = torch.matmul(query, key.transpose(-1, -2)) * self.scaling
178
+ mask = self._causal_attention_mask(
179
+ attention_mask=attention_mask,
180
+ query_length=query.shape[-2],
181
+ key_length=key.shape[-2],
182
+ device=query.device,
183
+ )
184
+ scores = scores.masked_fill(~mask, torch.finfo(scores.dtype).min)
185
+ probs = torch.softmax(scores, dim=-1)
186
+ if self.training and self.attention_dropout > 0:
187
+ probs = torch.dropout(probs, self.attention_dropout, train=True)
188
+ output = torch.matmul(probs, value)
189
+ return output.transpose(1, 2).contiguous()
190
+
191
+ def _sdpa_attention(
192
+ self,
193
+ query: torch.Tensor,
194
+ key: torch.Tensor,
195
+ value: torch.Tensor,
196
+ attention_mask: Optional[torch.Tensor],
197
+ ) -> torch.Tensor:
198
+ key = repeat_kv(key, self.num_key_value_groups)
199
+ value = repeat_kv(value, self.num_key_value_groups)
200
+ query = query.transpose(1, 2)
201
+ key = key.transpose(1, 2)
202
+ value = value.transpose(1, 2)
203
+ mask = None
204
+ if attention_mask is not None or query.shape[-2] != key.shape[-2]:
205
+ mask = self._causal_attention_mask(
206
+ attention_mask=attention_mask,
207
+ query_length=query.shape[-2],
208
+ key_length=key.shape[-2],
209
+ device=query.device,
210
+ )
211
+ output = torch.nn.functional.scaled_dot_product_attention(
212
+ query,
213
+ key,
214
+ value,
215
+ attn_mask=mask,
216
+ dropout_p=self.attention_dropout if self.training else 0.0,
217
+ is_causal=mask is None,
218
+ scale=self.scaling,
219
+ )
220
+ return output.transpose(1, 2).contiguous()
221
+
222
+ def _flash_attention(
223
+ self,
224
+ query: torch.Tensor,
225
+ key: torch.Tensor,
226
+ value: torch.Tensor,
227
+ attention_mask: Optional[torch.Tensor],
228
+ packed_metadata: Optional[PackedSequenceMetadata],
229
+ ) -> torch.Tensor:
230
+ if not _FLASH_ATTN_AVAILABLE:
231
+ raise ImportError("flash_attn is not installed, but attn_implementation='flash_attention_2' was requested.")
232
+ if query.device.type != "cuda":
233
+ raise ValueError("flash_attention_2 requires CUDA tensors.")
234
+ if query.dtype not in (torch.float16, torch.bfloat16):
235
+ raise ValueError(f"flash_attention_2 requires fp16/bf16 tensors, got dtype={query.dtype}.")
236
+
237
+ dropout_p = self.attention_dropout if self.training else 0.0
238
+ if packed_metadata is not None:
239
+ if packed_metadata.indices is not None:
240
+ query = query.reshape(-1, self.num_heads, self.head_dim).index_select(0, packed_metadata.indices)
241
+ key = key.reshape(-1, self.num_key_value_heads, self.head_dim).index_select(0, packed_metadata.indices)
242
+ value = value.reshape(-1, self.num_key_value_heads, self.head_dim).index_select(0, packed_metadata.indices)
243
+ output = flash_attn_varlen_func(
244
+ query,
245
+ key,
246
+ value,
247
+ packed_metadata.cu_seqlens,
248
+ packed_metadata.cu_seqlens,
249
+ packed_metadata.max_seqlen,
250
+ packed_metadata.max_seqlen,
251
+ dropout_p=dropout_p,
252
+ causal=True,
253
+ )
254
+ if packed_metadata.indices is None:
255
+ return output
256
+ return pad_input(
257
+ output,
258
+ packed_metadata.indices,
259
+ packed_metadata.batch_size,
260
+ packed_metadata.seq_len,
261
+ )
262
+
263
+ if attention_mask is None or bool(attention_mask.all()):
264
+ return flash_attn_func(query, key, value, dropout_p=dropout_p, causal=True)
265
+
266
+ if query.shape[1] != key.shape[1]:
267
+ query_attention_mask = attention_mask[:, -query.shape[1] :]
268
+ unpadded_query, query_indices, cu_seqlens_q, max_seqlen_q, _ = unpad_input(
269
+ query,
270
+ query_attention_mask,
271
+ )
272
+ unpadded_key, _, cu_seqlens_k, max_seqlen_k, _ = unpad_input(key, attention_mask)
273
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
274
+ output = flash_attn_varlen_func(
275
+ unpadded_query,
276
+ unpadded_key,
277
+ unpadded_value,
278
+ cu_seqlens_q,
279
+ cu_seqlens_k,
280
+ max_seqlen_q,
281
+ max_seqlen_k,
282
+ dropout_p=dropout_p,
283
+ causal=True,
284
+ )
285
+ return pad_input(output, query_indices, query.shape[0], query.shape[1])
286
+
287
+ unpadded_query, indices, cu_seqlens, max_seqlen, _ = unpad_input(query, attention_mask)
288
+ unpadded_key, _, _, _, _ = unpad_input(key, attention_mask)
289
+ unpadded_value, _, _, _, _ = unpad_input(value, attention_mask)
290
+ output = flash_attn_varlen_func(
291
+ unpadded_query,
292
+ unpadded_key,
293
+ unpadded_value,
294
+ cu_seqlens,
295
+ cu_seqlens,
296
+ max_seqlen,
297
+ max_seqlen,
298
+ dropout_p=dropout_p,
299
+ causal=True,
300
+ )
301
+ return pad_input(output, indices, query.shape[0], query.shape[1])
302
+
303
+ def forward(
304
+ self,
305
+ hidden_states: torch.Tensor,
306
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
307
+ attention_mask: Optional[torch.Tensor] = None,
308
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
309
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
310
+ use_cache: bool = False,
311
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
312
+ input_shape = hidden_states.shape[:-1]
313
+ query_states = self.q_norm(
314
+ self.q_proj(hidden_states).view(*input_shape, self.num_heads, self.head_dim)
315
+ )
316
+ key_states = self.k_norm(
317
+ self.k_proj(hidden_states).view(*input_shape, self.num_key_value_heads, self.head_dim)
318
+ )
319
+ value_states = self.v_proj(hidden_states).view(*input_shape, self.num_key_value_heads, self.head_dim)
320
+
321
+ cos, sin = position_embeddings
322
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
323
+
324
+ if layer_past is not None:
325
+ past_key, past_value = layer_past
326
+ key_states = torch.cat([past_key.to(device=key_states.device, dtype=key_states.dtype), key_states], dim=1)
327
+ value_states = torch.cat(
328
+ [past_value.to(device=value_states.device, dtype=value_states.dtype), value_states],
329
+ dim=1,
330
+ )
331
+
332
+ present = (key_states, value_states) if use_cache else None
333
+ if self.attn_implementation == "flash_attention_2":
334
+ attn_output = self._flash_attention(
335
+ query=query_states,
336
+ key=key_states,
337
+ value=value_states,
338
+ attention_mask=attention_mask,
339
+ packed_metadata=packed_metadata,
340
+ )
341
+ elif self.attn_implementation == "sdpa":
342
+ attn_output = self._sdpa_attention(
343
+ query=query_states,
344
+ key=key_states,
345
+ value=value_states,
346
+ attention_mask=attention_mask,
347
+ )
348
+ else:
349
+ attn_output = self._eager_attention(
350
+ query=query_states,
351
+ key=key_states,
352
+ value=value_states,
353
+ attention_mask=attention_mask,
354
+ )
355
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
356
+ return self.o_proj(attn_output), present
357
+
358
+
359
+ class MossQwen3DecoderLayer(nn.Module):
360
+ def __init__(self, config, layer_idx: int) -> None:
361
+ super().__init__()
362
+ self.self_attn = MossQwen3Attention(config=config, layer_idx=layer_idx)
363
+ self.mlp = MossQwen3MLP(config)
364
+ self.input_layernorm = MossQwen3RMSNorm(config.hidden_size, eps=float(config.rms_norm_eps))
365
+ self.post_attention_layernorm = MossQwen3RMSNorm(config.hidden_size, eps=float(config.rms_norm_eps))
366
+
367
+ def forward(
368
+ self,
369
+ hidden_states: torch.Tensor,
370
+ attention_mask: Optional[torch.Tensor] = None,
371
+ packed_metadata: Optional[PackedSequenceMetadata] = None,
372
+ layer_past: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
373
+ use_cache: bool = False,
374
+ position_embeddings: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
375
+ ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]:
376
+ residual = hidden_states
377
+ hidden_states = self.input_layernorm(hidden_states)
378
+ attn_output, present = self.self_attn(
379
+ hidden_states=hidden_states,
380
+ position_embeddings=position_embeddings,
381
+ attention_mask=attention_mask,
382
+ packed_metadata=packed_metadata,
383
+ layer_past=layer_past,
384
+ use_cache=use_cache,
385
+ )
386
+ hidden_states = residual + attn_output
387
+
388
+ residual = hidden_states
389
+ hidden_states = self.post_attention_layernorm(hidden_states)
390
+ hidden_states = residual + self.mlp(hidden_states)
391
+ return hidden_states, present
392
+
393
+
394
+ class MossQwen3Model(nn.Module):
395
+ def __init__(self, config) -> None:
396
+ super().__init__()
397
+ self.config = config
398
+ self.attn_implementation = str(getattr(config, "_attn_implementation", "eager"))
399
+ self.padding_idx = getattr(config, "pad_token_id", None)
400
+ self.vocab_size = int(config.vocab_size)
401
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
402
+ self.layers = nn.ModuleList(
403
+ [MossQwen3DecoderLayer(config, layer_idx=index) for index in range(config.num_hidden_layers)]
404
+ )
405
+ self.norm = MossQwen3RMSNorm(config.hidden_size, eps=float(config.rms_norm_eps))
406
+ self.rotary_emb = MossQwen3RotaryEmbedding(config)
407
+ self.gradient_checkpointing = False
408
+ self.gradient_checkpointing_use_reentrant = bool(
409
+ getattr(config, "gradient_checkpointing_use_reentrant", False)
410
+ )
411
+ self._reset_parameters()
412
+
413
+ def _reset_parameters(self) -> None:
414
+ init_std = float(getattr(self.config, "initializer_range", 0.02))
415
+ for module in self.modules():
416
+ if isinstance(module, nn.Linear):
417
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
418
+ if module.bias is not None:
419
+ nn.init.zeros_(module.bias)
420
+ elif isinstance(module, nn.Embedding):
421
+ nn.init.normal_(module.weight, mean=0.0, std=init_std)
422
+
423
+ def get_input_embeddings(self):
424
+ return self.embed_tokens
425
+
426
+ def set_input_embeddings(self, value):
427
+ self.embed_tokens = value
428
+
429
+ def load_qwen3_pretrained_weights(self, pretrained_path: str) -> None:
430
+ model_dir = Path(pretrained_path)
431
+ index_path = model_dir / "model.safetensors.index.json"
432
+ if not index_path.exists():
433
+ raise FileNotFoundError(f"Missing Qwen3 safetensors index: {index_path}")
434
+ with index_path.open("r", encoding="utf-8") as handle:
435
+ index = json.load(handle)
436
+ weight_map = index.get("weight_map", {})
437
+ shard_to_keys: dict[str, list[str]] = {}
438
+ for key, shard in weight_map.items():
439
+ if not key.startswith("model."):
440
+ continue
441
+ shard_to_keys.setdefault(str(shard), []).append(key)
442
+
443
+ state_dict = self.state_dict()
444
+ loaded_state = {}
445
+ for shard, keys in sorted(shard_to_keys.items()):
446
+ shard_tensors = load_file(str(model_dir / shard), device="cpu")
447
+ for key in keys:
448
+ target_key = key[len("model.") :]
449
+ if target_key not in state_dict:
450
+ continue
451
+ tensor = shard_tensors[key]
452
+ if tuple(tensor.shape) != tuple(state_dict[target_key].shape):
453
+ raise ValueError(
454
+ f"Shape mismatch while loading Qwen3 weight {key}: "
455
+ f"checkpoint={tuple(tensor.shape)} model={tuple(state_dict[target_key].shape)}"
456
+ )
457
+ loaded_state[target_key] = tensor
458
+
459
+ missing, unexpected = self.load_state_dict(loaded_state, strict=False)
460
+ unexpected = [key for key in unexpected if key]
461
+ if unexpected:
462
+ raise RuntimeError(f"Unexpected Qwen3 pretrained keys after load: {unexpected[:10]}")
463
+ missing = [key for key in missing if key not in loaded_state]
464
+ if missing:
465
+ raise RuntimeError(f"Missing Qwen3 pretrained keys after load: {missing[:10]}")
466
+
467
+ def forward(
468
+ self,
469
+ input_ids: Optional[torch.LongTensor] = None,
470
+ past_key_values: Optional[tuple[tuple[torch.Tensor, torch.Tensor], ...]] = None,
471
+ attention_mask: Optional[torch.Tensor] = None,
472
+ position_ids: Optional[torch.LongTensor] = None,
473
+ inputs_embeds: Optional[torch.FloatTensor] = None,
474
+ use_cache: Optional[bool] = None,
475
+ output_attentions: Optional[bool] = None,
476
+ output_hidden_states: Optional[bool] = None,
477
+ return_dict: bool = True,
478
+ cu_seqlens: Optional[torch.Tensor] = None,
479
+ num_sequences: Optional[torch.Tensor] = None,
480
+ ) -> BaseModelOutputWithPast:
481
+ del input_ids, output_attentions
482
+ if inputs_embeds is None:
483
+ raise ValueError("inputs_embeds must be provided.")
484
+
485
+ use_cache = bool(use_cache)
486
+ if use_cache and cu_seqlens is not None:
487
+ raise ValueError("use_cache=True is not supported together with cu_seqlens packing.")
488
+
489
+ hidden_states = inputs_embeds
490
+ if attention_mask is None:
491
+ attention_mask = torch.ones(hidden_states.shape[:2], dtype=torch.bool, device=hidden_states.device)
492
+ else:
493
+ attention_mask = attention_mask.to(dtype=torch.bool, device=hidden_states.device)
494
+ query_attention_mask = attention_mask[:, -hidden_states.shape[1] :]
495
+
496
+ packed_metadata = None
497
+ if position_ids is None:
498
+ if cu_seqlens is not None:
499
+ position_ids = MossTTSNanoGPT2Model.build_packed_position_ids(
500
+ attention_mask=attention_mask,
501
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
502
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
503
+ sequence_length=hidden_states.shape[1],
504
+ )
505
+ elif attention_mask is not None:
506
+ position_ids = attention_mask.long().cumsum(dim=-1) - 1
507
+ position_ids = position_ids.masked_fill(~attention_mask, 0)
508
+ position_ids = position_ids[:, -hidden_states.shape[1] :]
509
+ else:
510
+ past_length = 0
511
+ if past_key_values is not None and len(past_key_values) > 0:
512
+ past_length = past_key_values[0][0].shape[1]
513
+ position_ids = torch.arange(hidden_states.shape[1], device=hidden_states.device, dtype=torch.long)
514
+ position_ids = position_ids + past_length
515
+ position_ids = position_ids.unsqueeze(0).expand(hidden_states.shape[0], -1)
516
+
517
+ if cu_seqlens is not None and self.attn_implementation == "flash_attention_2":
518
+ packed_metadata = MossTTSNanoGPT2Model.build_packed_metadata(
519
+ hidden_states=hidden_states,
520
+ cu_seqlens=cu_seqlens.to(device=hidden_states.device),
521
+ num_sequences=num_sequences.to(device=hidden_states.device) if num_sequences is not None else None,
522
+ )
523
+
524
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
525
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
526
+
527
+ all_hidden_states = () if output_hidden_states else None
528
+ presents = [] if use_cache else None
529
+ for layer_index, decoder_layer in enumerate(self.layers):
530
+ if output_hidden_states:
531
+ all_hidden_states = all_hidden_states + (hidden_states,)
532
+
533
+ if self.gradient_checkpointing and self.training:
534
+ if use_cache:
535
+ raise ValueError("use_cache=True is not supported when gradient checkpointing is enabled during training.")
536
+
537
+ def custom_forward(*inputs):
538
+ output, _ = decoder_layer(
539
+ hidden_states=inputs[0],
540
+ attention_mask=inputs[1],
541
+ packed_metadata=packed_metadata,
542
+ layer_past=None,
543
+ use_cache=False,
544
+ position_embeddings=position_embeddings,
545
+ )
546
+ return output
547
+
548
+ hidden_states = torch.utils.checkpoint.checkpoint(
549
+ custom_forward,
550
+ hidden_states,
551
+ attention_mask,
552
+ use_reentrant=self.gradient_checkpointing_use_reentrant,
553
+ )
554
+ present = None
555
+ else:
556
+ hidden_states, present = decoder_layer(
557
+ hidden_states=hidden_states,
558
+ attention_mask=attention_mask,
559
+ packed_metadata=packed_metadata,
560
+ layer_past=None if past_key_values is None else past_key_values[layer_index],
561
+ use_cache=use_cache,
562
+ position_embeddings=position_embeddings,
563
+ )
564
+
565
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
566
+ if presents is not None:
567
+ presents.append(present)
568
+
569
+ hidden_states = self.norm(hidden_states)
570
+ hidden_states = hidden_states * query_attention_mask.unsqueeze(-1).to(dtype=hidden_states.dtype)
571
+ if output_hidden_states:
572
+ all_hidden_states = all_hidden_states + (hidden_states,)
573
+
574
+ if not return_dict:
575
+ return (hidden_states, tuple(presents) if presents is not None else None, all_hidden_states, None)
576
+
577
+ return BaseModelOutputWithPast(
578
+ last_hidden_state=hidden_states,
579
+ past_key_values=tuple(presents) if presents is not None else None,
580
+ hidden_states=all_hidden_states,
581
+ attentions=None,
582
+ )
code/moss_pack.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Packing of (instruction, text, tokens, reference codes, target codes) into MOSS-TTS tensors.
2
+
3
+ Derived from $NB/vblora/code/vbpack.py (which itself replaced the private MOSS-TTS repo's
4
+ `MossTTSLocalV15SFTDataset._pack_record`, that repo not being on this filesystem). Extended here
5
+ with **reference-audio conditioning**: the processor accepts reference audio as a LongTensor
6
+ [T, n_vq] of codec codes directly (see `_resolve_audio_items` in processing_moss_tts.py), so no
7
+ audio tokenizer is needed at train time.
8
+
9
+ Label derivation is pinned to the loss consumer (`va_loss.compute_supervised_loss_from_hidden`):
10
+ * labels[t] = input_ids[t+1] (hidden at t decodes frame t+1)
11
+ * supervised iff the TARGET text channel is audio_assistant_slot (continue) or audio_end (stop)
12
+ * audio channels >= audio_pad_code -> -100
13
+
14
+ Crucially the reference audio rows sit inside the USER turn, whose target text channel is neither
15
+ assistant_slot nor audio_end, so they are automatically unsupervised. Verified in verify_pack.py.
16
+ """
17
+ import numpy as np
18
+ import torch
19
+
20
+
21
+ def codes_from_blob(blob, frames, n_vq=12):
22
+ """target_codes/ref_codes/chosen_codes/... are int16 little-endian [frames, n_vq]."""
23
+ if blob is None or not frames:
24
+ return None
25
+ a = np.frombuffer(blob, dtype=np.int16)
26
+ n = int(frames) * int(n_vq)
27
+ if a.size < n:
28
+ return None
29
+ return torch.from_numpy(a[:n].reshape(int(frames), int(n_vq)).astype(np.int64))
30
+
31
+
32
+ class Packer:
33
+ def __init__(self, processor, model_config):
34
+ self.proc = processor
35
+ c = model_config
36
+ self.n_vq = int(c.n_vq)
37
+ self.assist = int(c.audio_assistant_slot_token_id)
38
+ self.aend = int(c.audio_end_token_id)
39
+ self.audio_pad = int(getattr(c, "audio_pad_token_id", 1024))
40
+ self.text_pad = int(c.pad_token_id)
41
+
42
+ def pack(self, instruction, text, tokens, codes, ref_codes=None, language="English"):
43
+ """codes: [T, n_vq] long. ref_codes: [R, n_vq] long or None.
44
+ -> dict(input_ids [T,13], labels [T,13], n_sup)"""
45
+ codes = torch.as_tensor(np.asarray(codes), dtype=torch.long)
46
+ ref = None
47
+ if ref_codes is not None:
48
+ ref = [torch.as_tensor(np.asarray(ref_codes), dtype=torch.long)]
49
+ um = self.proc.build_user_message(text=text, instruction=instruction, language=language,
50
+ tokens=tokens, reference=ref)
51
+ am = self.proc.build_assistant_message(audio_codes_list=[codes])
52
+ b = self.proc([[um, am]], mode="computing_loss")
53
+ ids = b["input_ids"][0].long() # [T, 13]
54
+ lab = torch.full_like(ids, -100)
55
+ lab[:-1] = ids[1:]
56
+ tt = lab[:, 0]
57
+ sup = (tt == self.assist) | (tt == self.aend)
58
+ # --- reference-audio contamination guard ---------------------------------------
59
+ # The reference clip inside the USER turn is terminated by <|audio_end|>, which is the
60
+ # SAME token id the model uses for the assistant's STOP decision. Without this guard the
61
+ # position preceding the reference's terminator is supervised as "stop", teaching the
62
+ # model to stop at the end of the user's reference audio. Measured: exactly 1 spurious
63
+ # supervised position per referenced sample (n_sup 154 vs 153 at target_frames=152).
64
+ # Supervise only from one position before the first assistant audio row.
65
+ fa = (ids[:, 0] == self.assist).nonzero()
66
+ if fa.numel() > 0:
67
+ k = int(fa[0])
68
+ sup[: max(0, k - 1)] = False
69
+ lab[~sup] = -100
70
+ au = lab[:, 1:]
71
+ au[(au >= self.audio_pad) | (au < 0)] = -100
72
+ lab[:, 1:] = au
73
+ return {"input_ids": ids, "labels": lab, "n_sup": int(sup.sum())}
74
+
75
+
76
+ def collate(items, text_pad, audio_pad):
77
+ """Right-pad a list of pack() outputs. Padding is fully masked in `labels`."""
78
+ T = max(it["input_ids"].shape[0] for it in items)
79
+ C = items[0]["input_ids"].shape[1]
80
+ B = len(items)
81
+ ids = torch.empty((B, T, C), dtype=torch.long)
82
+ ids[:, :, 0] = text_pad
83
+ ids[:, :, 1:] = audio_pad
84
+ lab = torch.full((B, T, C), -100, dtype=torch.long)
85
+ am = torch.zeros((B, T), dtype=torch.long)
86
+ for i, it in enumerate(items):
87
+ t = it["input_ids"].shape[0]
88
+ ids[i, :t] = it["input_ids"]
89
+ lab[i, :t] = it["labels"]
90
+ am[i, :t] = 1
91
+ out = {"input_ids": ids, "labels": lab, "attention_mask": am}
92
+ for k in ("meta",):
93
+ if k in items[0]:
94
+ out[k] = [it[k] for it in items]
95
+ return out
code/moss_small.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """M1 (Arm P) und M2 (Arm S): Qwen3-0.6B-Backbone + MOSS-Talker-Head.
3
+
4
+ M1: Head (lokaler Transformer, 12 Audio-Embeddings = 12 Audio-Köpfe, binärer Stop-Kopf) aus $SC/out/sft3/export,
5
+ Hidden 2560, EINGEFROREN; trainierbar: Backbone (1024) + proj_out 1024->2560 (vor dem Head) + proj_in 2560->1024
6
+ (Summe der Audio-Embeddings am Backbone-Eingang). Der Head wird NIE in ein Backbone gemergt;
7
+ audio_lm_heads.N.weight IST audio_embeddings.N.weight (gewichtsgeteilt).
8
+ M2: gleiche Architektur, Head mit Hidden 1024 zufällig initialisiert, alles trainierbar.
9
+
10
+ `hidden_size != local_hidden_size` ist im Original nicht vorgesehen (Config wirft, _global_hidden_to_local ist Identität,
11
+ Audio-Embeddings haben hidden_size) -> Unterklasse mit eigenem __init__ und Projektionen."""
12
+ import os, sys, json, importlib.util
13
+ import torch, torch.nn as nn
14
+ from transformers import Qwen3Config
15
+ from transformers.models.gpt2.configuration_gpt2 import GPT2Config
16
+ from safetensors import safe_open
17
+ from safetensors.torch import load_file
18
+
19
+ SC = '/e/scratch/reformo/schuhmann1_moss'
20
+ SFT3 = f'{SC}/out/sft3/export'
21
+ QWEN = f'{SC}/models/qwen3-0.6b'
22
+
23
+
24
+ def _load_export_modules():
25
+ """modeling_moss_tts aus dem SFT3-Export als Paket importieren (relative Imports)."""
26
+ import importlib
27
+ if 'moss_export' in sys.modules:
28
+ return sys.modules['moss_export']
29
+ spec = importlib.util.spec_from_file_location('moss_export', f'{SFT3}/__init__.py',
30
+ submodule_search_locations=[SFT3])
31
+ mod = importlib.util.module_from_spec(spec)
32
+ sys.modules['moss_export'] = mod
33
+ spec.loader.exec_module(mod)
34
+ return mod
35
+
36
+
37
+ def export_classes():
38
+ _load_export_modules()
39
+ import importlib
40
+ cfgm = importlib.import_module('moss_export.configuration_moss_tts')
41
+ modm = importlib.import_module('moss_export.modeling_moss_tts')
42
+ procm = importlib.import_module('moss_export.processing_moss_tts')
43
+ return cfgm.MossTTSLocalConfig, modm.MossTTSLocalModel, procm.MossTTSLocalProcessor
44
+
45
+
46
+ def make_config(kind):
47
+ """kind: 'M1' (local 2560, SFT3-Head) | 'M2' (local 1024, von Null)."""
48
+ MossCfg, _, _ = export_classes()
49
+ base = json.load(open(f'{SFT3}/config.json'))
50
+ q = json.load(open(f'{QWEN}/config.json'))
51
+ for k in ('architectures', 'transformers_version'):
52
+ q.pop(k, None)
53
+ q['use_cache'] = False
54
+ q['gradient_checkpointing_use_reentrant'] = False
55
+ g = dict(base['gpt2_config'])
56
+ if kind == 'M1':
57
+ local = 2560 # n_embd 2560, n_head 32, n_inner 9728 bleiben (SFT3-Head)
58
+ elif kind == 'M2':
59
+ local = 1024
60
+ g['n_embd'] = 1024; g['n_head'] = 16; g['n_inner'] = 4096
61
+ else:
62
+ raise ValueError(kind)
63
+ g_tmp = dict(g); g_tmp['n_embd'] = int(q['hidden_size']) # Config-Prüfung umgehen
64
+ kw = {k: base[k] for k in ('audio_codebook_sizes', 'audio_pad_code', 'audio_pad_token_id', 'audio_start_token_id',
65
+ 'audio_end_token_id', 'audio_user_slot_token_id', 'audio_assistant_slot_token_id',
66
+ 'audio_vocab_size', 'im_end_token_id', 'im_start_token_id', 'n_vq', 'pad_token_id',
67
+ 'local_text_head_mode', 'local_transformer_layers', 'sampling_rate',
68
+ 'audio_tokenizer_name_or_path', 'use_static_local_kv_cache')}
69
+ cfg = MossCfg(qwen3_config=q, gpt2_config=g_tmp, attn_implementation='sdpa',
70
+ local_transformer_attn_implementation='sdpa', **kw)
71
+ cfg.gpt2_config = GPT2Config(**g)
72
+ cfg.local_hidden_size = local
73
+ cfg.small_kind = kind
74
+ cfg.dtype = 'bfloat16'
75
+ return cfg
76
+
77
+
78
+ class MossTTSSmallModel:
79
+ """Fabrik: erzeugt die Unterklasse erst, wenn das Export-Modul geladen ist."""
80
+ _cls = None
81
+
82
+ @classmethod
83
+ def cls(klass):
84
+ if klass._cls is not None:
85
+ return klass._cls
86
+ _, MossTTSLocalModel, _ = export_classes()
87
+ import importlib
88
+ gpt2m = importlib.import_module('moss_export.gpt2_decoder')
89
+ qw = importlib.import_module('moss_export.qwen3_decoder')
90
+
91
+ class _Small(MossTTSLocalModel):
92
+ def __init__(self, config):
93
+ # Elternklasse überspringen (sie baut Embeddings mit hidden_size); PreTrainedModel-Init direkt.
94
+ super(MossTTSLocalModel, self).__init__(config)
95
+ self._tied_weights_keys = self._build_tied_weights_keys(config)
96
+ config.qwen3_config.pad_token_id = config.pad_token_id
97
+ config.qwen3_config._attn_implementation = config.attn_implementation
98
+ lg = config.gpt2_config.to_dict()
99
+ lg['n_layer'] = int(getattr(config, 'local_transformer_layers', config.gpt2_config.n_layer))
100
+ lg['n_positions'] = int(config.n_vq) + 1
101
+ lg['n_ctx'] = int(config.n_vq) + 1
102
+ lg = GPT2Config(**lg)
103
+ lg.pad_token_id = config.pad_token_id
104
+ lg._attn_implementation = config.local_transformer_attn_implementation
105
+ self.transformer = qw.MossQwen3Model(config.qwen3_config)
106
+ self.local_transformer = gpt2m.MossTTSNanoGPT2Model(lg, attn_implementation=config.local_transformer_attn_implementation)
107
+ self.local_transformer.wte = nn.Identity()
108
+ H = int(config.hidden_size); L = int(config.local_hidden_size)
109
+ self.audio_embeddings = nn.ModuleList([nn.Embedding(int(config.audio_codebook_sizes[i]), L) for i in range(config.n_vq)])
110
+ self.text_lm_head = nn.Linear(H, int(config.vocab_size), bias=False)
111
+ self.audio_lm_heads = nn.ModuleList([nn.Linear(L, int(config.audio_codebook_sizes[i]), bias=False) for i in range(config.n_vq)])
112
+ self.local_text_lm_head = nn.Linear(L, 2, bias=False) if self._use_binary_local_text_head() else None
113
+ if H != L:
114
+ self.proj_in = nn.Linear(L, H, bias=False) # Audio-Embedding-Summe -> Backbone
115
+ self.proj_out = nn.Linear(H, L, bias=False) # Backbone-Hidden -> Head
116
+ else:
117
+ self.proj_in = nn.Identity(); self.proj_out = nn.Identity()
118
+ self.post_init()
119
+ self.tie_weights()
120
+ self.initialize_local_text_lm_head_from_text_lm_head()
121
+
122
+ def _build_inputs_embeds(self, input_ids):
123
+ if input_ids.ndim != 3 or input_ids.shape[-1] != self.config.n_vq + 1:
124
+ raise ValueError(f'Expected [B,T,{self.config.n_vq + 1}], got {tuple(input_ids.shape)}')
125
+ text_ids = input_ids[..., 0]
126
+ emb = self.transformer.embed_tokens(text_ids)
127
+ acc = None
128
+ for ci, e in enumerate(self.audio_embeddings):
129
+ ids = input_ids[..., ci + 1]
130
+ valid = ids.ne(self.config.audio_pad_token_id)
131
+ a = e(ids.masked_fill(~valid, 0)) * valid.unsqueeze(-1)
132
+ acc = a if acc is None else acc + a
133
+ return emb + self.proj_in(acc.to(emb.dtype))
134
+
135
+ def _global_hidden_to_local(self, h):
136
+ return self.proj_out(h)
137
+
138
+ klass._cls = _Small
139
+ return _Small
140
+
141
+
142
+ def load_qwen_backbone(model, log=print):
143
+ sd = load_file(f'{QWEN}/model.safetensors', device='cpu')
144
+ tgt = model.transformer.state_dict()
145
+ loaded = {}
146
+ for k, v in sd.items():
147
+ if k.startswith('model.'):
148
+ kk = k[len('model.'):]
149
+ if kk in tgt:
150
+ assert tuple(v.shape) == tuple(tgt[kk].shape), (k, v.shape, tgt[kk].shape)
151
+ loaded[kk] = v.to(tgt[kk].dtype)
152
+ missing, unexpected = model.transformer.load_state_dict(loaded, strict=False)
153
+ missing = [m for m in missing if 'rotary' not in m]
154
+ if missing or unexpected:
155
+ raise RuntimeError(f'Qwen3 load: missing={missing[:10]} unexpected={unexpected[:10]}')
156
+ extra = [k for k in sd if not k.startswith('model.')]
157
+ log(f'[qwen3] loaded {len(loaded)} tensors into backbone; non-backbone keys in file: {extra}')
158
+ model.tie_weights()
159
+
160
+
161
+ def load_sft3_head(model, log=print):
162
+ """Lokaler Transformer + 12 Audio-Embeddings (+ Köpfe, gewichtsgeteilt) + binärer Stop-Kopf aus dem SFT3-Export."""
163
+ want_prefix = ('local_transformer.', 'audio_embeddings.', 'local_text_lm_head.')
164
+ loaded = {}
165
+ with safe_open(f'{SFT3}/model.safetensors', framework='pt', device='cpu') as f:
166
+ keys = list(f.keys())
167
+ for k in keys:
168
+ if k.startswith(want_prefix):
169
+ loaded[k] = f.get_tensor(k)
170
+ sd = model.state_dict()
171
+ for k, v in loaded.items():
172
+ assert k in sd, k
173
+ assert tuple(v.shape) == tuple(sd[k].shape), (k, v.shape, sd[k].shape)
174
+ missing, unexpected = model.load_state_dict({k: v.to(sd[k].dtype) for k, v in loaded.items()}, strict=False)
175
+ assert not unexpected, unexpected
176
+ model.tie_weights()
177
+ # Kontrolle: Head-Gewichte sind Embeddings
178
+ for i in range(model.config.n_vq):
179
+ assert model.audio_lm_heads[i].weight.data_ptr() == model.audio_embeddings[i].weight.data_ptr()
180
+ n = sum(v.numel() for v in loaded.values())
181
+ log(f'[sft3-head] loaded {len(loaded)} tensors ({n/1e6:.1f}M params) — local_transformer + audio_embeddings + stop head')
182
+ return loaded.keys()
183
+
184
+
185
+ def freeze_head(model, log=print):
186
+ n = 0
187
+ for mod in (model.local_transformer, model.audio_embeddings, model.local_text_lm_head):
188
+ for p in mod.parameters():
189
+ p.requires_grad_(False); n += p.numel()
190
+ log(f'[freeze] head frozen: {n/1e6:.1f}M params')
191
+
192
+
193
+ def build(kind, log=print, dtype=torch.float32):
194
+ cfg = make_config(kind)
195
+ cls = MossTTSSmallModel.cls()
196
+ model = cls(cfg)
197
+ load_qwen_backbone(model, log)
198
+ if kind == 'M1':
199
+ load_sft3_head(model, log)
200
+ freeze_head(model, log)
201
+ ntr = sum(p.numel() for p in model.parameters() if p.requires_grad)
202
+ ntot = sum(p.numel() for p in model.parameters())
203
+ log(f'[{kind}] params total={ntot/1e6:.1f}M trainable={ntr/1e6:.1f}M hidden={cfg.hidden_size} local={cfg.local_hidden_size}')
204
+ return model.to(dtype), cfg
205
+
206
+
207
+ def param_groups(model, kind, lr_backbone, lr_new):
208
+ """Backbone (vortrainiert) vs. neue Teile (Projektionen / Head von Null)."""
209
+ bb, new = [], []
210
+ for n, p in model.named_parameters():
211
+ if not p.requires_grad:
212
+ continue
213
+ (bb if n.startswith('transformer.') or n.startswith('text_lm_head.') else new).append(p)
214
+ return [{'params': bb, 'lr': lr_backbone, 'name': 'backbone'}, {'params': new, 'lr': lr_new, 'name': 'new'}]
215
+
216
+
217
+ if __name__ == '__main__':
218
+ kind = sys.argv[1] if len(sys.argv) > 1 else 'M2'
219
+ m, cfg = build(kind)
220
+ print(m.__class__.__name__, cfg.small_kind)
code/pack_moss.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Packen für M1/M2 mit dem SFT3-Prozessor (identisch zu sft_train2.build_examples, ohne Prompt-Zufall)."""
2
+ import sys, torch
3
+ sys.path.insert(0, '/e/data1/datasets/playground/mmlaion/schuhmann1/dramabox/train')
4
+ from moss_pack import collate # Right-Padding, Labels maskiert
5
+ from prompt_fmt import user_content, AUDIO_PLACEHOLDER
6
+
7
+
8
+ class MossPacker:
9
+ def __init__(self, proc, cfg):
10
+ self.proc = proc
11
+ self.assist = int(cfg.audio_assistant_slot_token_id); self.aend = int(cfg.audio_end_token_id)
12
+ self.audio_pad = int(cfg.audio_pad_token_id); self.text_pad = int(cfg.pad_token_id)
13
+
14
+ def pack(self, meta, codes, ref_codes=None):
15
+ use_ref = ref_codes is not None
16
+ content = user_content(meta, use_ref)
17
+ um = {"role": "user", "content": content, "audio_codes_list": [torch.as_tensor(ref_codes)] if use_ref else []}
18
+ am = self.proc.build_assistant_message(audio_codes_list=[torch.as_tensor(codes)])
19
+ b = self.proc([[um, am]], mode="computing_loss")
20
+ ids = b["input_ids"][0].long()
21
+ lab = torch.full_like(ids, -100)
22
+ lab[:-1] = ids[1:]
23
+ tt = lab[:, 0]
24
+ sup = (tt == self.assist) | (tt == self.aend)
25
+ fa = (ids[:, 0] == self.assist).nonzero()
26
+ if fa.numel() > 0:
27
+ sup[: max(0, int(fa[0]) - 1)] = False # Referenz-<|audio_end|> nicht als Stop lernen
28
+ lab[~sup] = -100
29
+ au = lab[:, 1:]
30
+ au[(au >= self.audio_pad) | (au < 0)] = -100
31
+ lab[:, 1:] = au
32
+ return {"input_ids": ids, "labels": lab, "n_sup": int(sup.sum())}
33
+
34
+ def prompt_ids(self, meta, ref_codes=None, frames=None):
35
+ """Generierungs-Eingabe [T,13] (endet mit <|audio_start|>)."""
36
+ use_ref = ref_codes is not None
37
+ content = user_content(meta, use_ref, frames=frames)
38
+ um = {"role": "user", "content": content, "audio_codes_list": [torch.as_tensor(ref_codes)] if use_ref else []}
39
+ b = self.proc([[um]], mode="generation")
40
+ return b["input_ids"][0].long()
code/packing.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Preserve the inherited prompt skeleton and inject measured score slots.
2
+
3
+ Only the global caption and the leading sentence-cue parentheses are replaced
4
+ in score mode. The inherited template repeats the SCRIPT in Instruction and
5
+ Text; both copies map to the same measured local feature bank.
6
+ """
7
+ import re
8
+ import sys
9
+ from pathlib import Path
10
+ import numpy as np
11
+ import torch
12
+ from conditioning import MARKERS, feature_vector, install_markers
13
+
14
+ sys.path.insert(0, '/e/scratch/reformo/schuhmann1_moss/code/small_tts')
15
+ from pack_moss import MossPacker
16
+ from prompt_fmt import user_content
17
+
18
+ GLOBAL_BLOCK = MARKERS[0] + MARKERS[1] * 5 + MARKERS[2]
19
+ LOCAL_BLOCK = MARKERS[3] + MARKERS[4] * 10 + MARKERS[5]
20
+
21
+
22
+ def score_prompt(prompt, sentence_count):
23
+ if not prompt.startswith('GENERAL: ') or '\nSCRIPT:\n' not in prompt:
24
+ raise ValueError('Score mode requires the verified GENERAL/SCRIPT prompt format')
25
+ _, script = prompt.split('\nSCRIPT:\n', 1)
26
+ lines = script.splitlines()
27
+ if len(lines) != sentence_count or not lines:
28
+ raise ValueError(f'Prompt cues and measured sentence rows disagree: {len(lines)} vs {sentence_count}')
29
+ edited, bodies = [], []
30
+ for line in lines:
31
+ match = re.match(r'^\([^)]*\)(.*)$', line)
32
+ if not match:
33
+ raise ValueError('Expected one leading parenthesized sentence cue')
34
+ body = match.group(1)
35
+ bodies.append(body)
36
+ edited.append(LOCAL_BLOCK + body)
37
+ changed = 'GENERAL: ' + GLOBAL_BLOCK + '\nSCRIPT:\n' + '\n'.join(edited)
38
+ # Everything following the cue stays byte-identical, including burst tags,
39
+ # pause spellings, numbers and punctuation. Durations live in the template.
40
+ assert all(new[len(LOCAL_BLOCK):] == old for new, old in zip(edited, bodies))
41
+ return changed
42
+
43
+
44
+ def labels_for(ids, assist, aend, audio_pad):
45
+ labels = torch.full_like(ids, -100)
46
+ labels[:-1] = ids[1:]
47
+ targets = labels[:, 0]
48
+ supervised = targets.eq(assist) | targets.eq(aend)
49
+ first = ids[:, 0].eq(assist).nonzero()
50
+ if not first.numel():
51
+ raise ValueError('No assistant target frames in the packed training example')
52
+ supervised[:max(0, int(first[0]) - 1)] = False
53
+ labels[~supervised] = -100
54
+ audio = labels[:, 1:]
55
+ audio[(audio >= audio_pad) | (audio < 0)] = -100
56
+ return labels
57
+
58
+
59
+ def generated_audio(result, config):
60
+ """MOSS generate returns BOA + 13-channel rows, not a [frames,12] array.
61
+
62
+ Follow the already validated M1/M2 evaluator's extraction. Any unexpected
63
+ remaining code value is an error rather than silently clipping corruption.
64
+ """
65
+ start_length, sequence = result[0]
66
+ assert start_length == 0, 'This continuation path requires an empty target prefix'
67
+ assert sequence.ndim == 2 and sequence.shape[1] == int(config.n_vq) + 1
68
+ assert int(sequence[0, 0]) == int(config.audio_start_token_id)
69
+ audio = sequence[1:, 1:]
70
+ keep = (audio[:, 0] != int(config.audio_pad_token_id)) & (audio[:, 0] >= 0)
71
+ audio = audio[keep]
72
+ if audio.numel():
73
+ assert int(audio.min()) >= 0 and int(audio.max()) < int(config.audio_pad_token_id)
74
+ return audio
75
+
76
+
77
+ class ScorePacker(MossPacker):
78
+ def __init__(self, proc, config, schema):
79
+ super().__init__(proc, config)
80
+ self.schema = schema
81
+ self.markers = install_markers(proc.tokenizer, int(config.vocab_size))
82
+
83
+ def _prepare(self, meta, mode, ref_codes):
84
+ if mode not in ('instruction', 'reference', 'score'):
85
+ raise ValueError(mode)
86
+ if (ref_codes is not None) != (mode == 'reference'):
87
+ raise ValueError('Reference mode must have a genuine non-target reference')
88
+ record = dict(meta)
89
+ global_values = [0.] * (2 * len(self.schema['global_dimensions']))
90
+ local_values = []
91
+ if mode == 'score':
92
+ measurements = meta['conditioning_measurements']
93
+ if not measurements.get('validated'):
94
+ raise ValueError('Score mode requires independently validated measurement provenance')
95
+ local = measurements['sentences']
96
+ record['prompt'] = score_prompt(meta['prompt'], len(local))
97
+ global_values = feature_vector(measurements['global'], self.schema['global_dimensions'])
98
+ for row in local:
99
+ if row['scope'] not in ('sentence_audio', 'whole_target_single_cue_rescored', 'windowed_sentence_audio') or not row.get('provenance'):
100
+ raise ValueError('Whole-clip or unknown measurements cannot populate local score slots')
101
+ local_values.append(feature_vector(row['values'], self.schema['local_dimensions']))
102
+ if not local_values:
103
+ raise ValueError('No local measurements for score mode')
104
+ return record, global_values, local_values
105
+
106
+ def _index(self, ids, mode, sentence_count):
107
+ index = torch.zeros(ids.shape[0], dtype=torch.long)
108
+ text = ids[:, 0].tolist()
109
+ global_start = self.markers[MARKERS[0]]
110
+ local_start = self.markers[MARKERS[3]]
111
+ gs = [i for i, token in enumerate(text) if token == global_start]
112
+ ls = [i for i, token in enumerate(text) if token == local_start]
113
+ if mode != 'score':
114
+ if gs or ls:
115
+ raise ValueError('Score markers unexpectedly present outside score mode')
116
+ return index
117
+ if len(gs) != 1 or len(ls) != 2 * sentence_count:
118
+ raise ValueError(f'Unexpected template score-block coverage: global={len(gs)}, sentence={len(ls)}')
119
+ expect_global = [global_start] + [self.markers[MARKERS[1]]] * 5 + [self.markers[MARKERS[2]]]
120
+ expect_local = [local_start] + [self.markers[MARKERS[4]]] * 10 + [self.markers[MARKERS[5]]]
121
+ i = gs[0]
122
+ assert text[i:i + 7] == expect_global
123
+ index[i:i + 7] = torch.arange(1, 8)
124
+ for repeat, i in enumerate(ls):
125
+ assert text[i:i + 12] == expect_local
126
+ base = 8 + (repeat % sentence_count) * 12
127
+ index[i:i + 12] = torch.arange(base, base + 12)
128
+ return index
129
+
130
+ def pack_mode(self, meta, codes, mode, ref_codes=None, generation=False):
131
+ record, glob, local = self._prepare(meta, mode, ref_codes)
132
+ content = user_content(record, mode == 'reference')
133
+ user = {'role': 'user', 'content': content,
134
+ 'audio_codes_list': [torch.as_tensor(np.asarray(ref_codes).copy(), dtype=torch.long)] if ref_codes is not None else []}
135
+ conversation = [user]
136
+ if not generation:
137
+ conversation.append(self.proc.build_assistant_message(audio_codes_list=[torch.as_tensor(np.asarray(codes), dtype=torch.long)]))
138
+ packed = self.proc([conversation], mode='generation' if generation else 'computing_loss')
139
+ ids = packed['input_ids'][0].long()
140
+ index = self._index(ids, mode, len(local))
141
+ labels = torch.full_like(ids, -100) if generation else labels_for(ids, self.assist, self.aend, self.audio_pad)
142
+ if not generation:
143
+ assert int((labels[:, 1] >= 0).sum()) == len(codes), 'Target frame supervision changed'
144
+ assert int((labels[:, 0] == self.aend).sum()) == 1, 'Reference end leaked into the loss'
145
+ return {'input_ids': ids, 'labels': labels, 'score_index': index,
146
+ 'global_values': glob, 'local_values': local, 'mode': mode}
147
+
148
+ def collate(self, examples):
149
+ from pack_moss import collate
150
+ batch = collate(examples, self.text_pad, self.audio_pad)
151
+ B, T = batch['attention_mask'].shape
152
+ S = max(1, max(len(row['local_values']) for row in examples))
153
+ glob = torch.tensor([row['global_values'] for row in examples], dtype=torch.float32)
154
+ local = torch.zeros(B, S, 2 * len(self.schema['local_dimensions']), dtype=torch.float32)
155
+ index = torch.zeros(B, T, dtype=torch.long)
156
+ for i, row in enumerate(examples):
157
+ index[i, :len(row['score_index'])] = row['score_index']
158
+ if row['local_values']:
159
+ local[i, :len(row['local_values'])] = torch.tensor(row['local_values'])
160
+ batch['score_conditioning'] = (glob, local, index)
161
+ batch['modes'] = [row['mode'] for row in examples]
162
+ return batch
code/plan.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Same global sample stream across ranks, without dropped tail targets."""
2
+ import hashlib
3
+ import json
4
+ import numpy as np
5
+
6
+
7
+ def benchmark_phases(world):
8
+ assert world in (8, 16)
9
+ return [{'name': 'strong', 'offset': 0, 'global_batch': 256, 'updates': 40, 'warmup': 10},
10
+ {'name': 'weak', 'offset': 10240, 'global_batch': world * 32, 'updates': 12, 'warmup': 4, 'replicated_base_world': 8}]
11
+
12
+
13
+ def step_indices(phase, step, rank, world, samples):
14
+ if phase.get('replicated_base_world'):
15
+ # Benchmark only: duplicate the same eight real rank streams on the
16
+ # second pair of nodes, giving exactly equal per-GPU input/token load.
17
+ base = phase['replicated_base_world']
18
+ local_count = phase['global_batch'] // world
19
+ start = phase['offset'] + step * base * local_count
20
+ return list(range(start + rank % base, start + base * local_count, base))
21
+ start = phase['offset'] + step * phase['global_batch']
22
+ end = min(samples, start + phase['global_batch'])
23
+ if start >= samples: return []
24
+ return list(range(start + rank, end, world))
25
+
26
+
27
+ def microbatches(examples, max_padded_tokens=12000, max_examples=16):
28
+ """Length sort only within one already fixed optimizer batch."""
29
+ examples = sorted(examples, key=lambda ex: len(ex['input_ids']))
30
+ batches, current = [], []
31
+ for row in examples:
32
+ n = len(row['input_ids'])
33
+ if current and (n * (len(current) + 1) > max_padded_tokens or len(current) >= max_examples):
34
+ batches.append(current); current = []
35
+ current.append(row)
36
+ if current: batches.append(current)
37
+ return batches
38
+
39
+
40
+ def dynamic_weights(local_frames, local_samples, global_frames, global_samples, world):
41
+ """Preserve one global per-channel token mean across microbatches/ranks.
42
+
43
+ The inherited loss normalizes by sum(weights), so the caller multiplies
44
+ its return value by returned scale. DDP subsequently averages gradients.
45
+ This is algebraically the ordinary inherited objective on the global batch.
46
+ """
47
+ weights = [world * (local_frames + local_samples) / (global_frames + global_samples)]
48
+ weights += [(32. / 12) * world * local_frames / global_frames] * 12
49
+ return weights, sum(weights) / 33.
50
+
51
+
52
+ def check_plan(samples=16384):
53
+ unions = {}
54
+ for world in (8, 16):
55
+ phases = benchmark_phases(world)
56
+ for phase in phases:
57
+ seen = []
58
+ for step in range(phase['updates']):
59
+ global_ids = sorted(i for rank in range(world) for i in step_indices(phase, step, rank, world, samples))
60
+ if phase['name'] == 'weak':
61
+ start = phase['offset'] + step * 256
62
+ expect = sorted(list(range(start, start + 256)) * (world // 8))
63
+ else:
64
+ expect = list(range(phase['offset'] + step * phase['global_batch'], phase['offset'] + (step + 1) * phase['global_batch']))
65
+ assert global_ids == expect
66
+ seen.extend(global_ids)
67
+ assert len(seen) == len(set(seen)) * (world // 8 if phase['name'] == 'weak' else 1)
68
+ unions[(world, phase['name'])] = seen
69
+ assert unions[(8, 'strong')] == unions[(16, 'strong')]
70
+ for step in range(12):
71
+ for rank in range(16):
72
+ assert step_indices(benchmark_phases(16)[1], step, rank, 16, samples) == step_indices(benchmark_phases(8)[1], step, rank % 8, 8, samples)
73
+ # Test mean/gradient coefficients independently, including unequal lengths.
74
+ rng = np.random.default_rng(71)
75
+ channels = rng.normal(size=(263, 13))
76
+ frame_groups = [13, 49, 81, 120]
77
+ sample_groups = [1, 3, 5, 7]
78
+ expected = channels.mean(axis=0)
79
+ actual = np.zeros(13)
80
+ start = 0
81
+ for frames, sample_count in zip(frame_groups, sample_groups):
82
+ weights, scale = dynamic_weights(frames, sample_count, sum(frame_groups), sum(sample_groups), 4)
83
+ # Audio-channel linear coefficients recover the exact global mean after DDP.
84
+ audio_coefficient = weights[1] / sum(weights) * scale / 4
85
+ actual[1:] += channels[start:start + frames, 1:].mean(axis=0) * audio_coefficient
86
+ start += frames
87
+ assert np.allclose(actual[1:], expected[1:] * (32 / 12) / 33)
88
+ return {'status': 'PASS', 'strong_unique_samples': len(unions[(8, 'strong')]),
89
+ 'strong_union_sha256': hashlib.sha256(np.asarray(unions[(8, 'strong')], dtype='<i8').tobytes()).hexdigest(),
90
+ 'worlds': [8, 16], 'global_batch_strong': 256, 'per_gpu_weak': 32,
91
+ 'strong_warmup_updates': 10, 'strong_steady_updates': 30, 'weak_warmup_updates': 4, 'weak_steady_updates': 8,
92
+ 'weak_input_rule': 'Exact same eight real per-GPU streams, replicated once across the additional eight GPUs at four nodes. Benchmark-only repetitions, explicitly counted as presentations.',
93
+ 'loss_weighting': 'Exact global per-channel supervised-token mean, independent of rank and microbatch partition.'}
94
+
95
+
96
+ if __name__ == '__main__': print(json.dumps(check_plan(), indent=2))
code/plan_caption_production_32nodes.json ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "kind": "m2_continuous_large_talker_v1",
3
+ "architecture": "qwen3_0.6b_plus_fresh_sft3_width_talker",
4
+ "initialization": {
5
+ "semantic": "original_pretrained_Qwen3-0.6B",
6
+ "talker": "random_fresh"
7
+ },
8
+ "nodes": 32,
9
+ "ranks_per_node": 4,
10
+ "world_size": 128,
11
+ "samples_per_gpu": 32,
12
+ "global_batch": 4096,
13
+ "seed": 20260923,
14
+ "max_padded_tokens": 12000,
15
+ "max_examples": 16,
16
+ "lr_backbone_peak": 8e-05,
17
+ "lr_talker_peak": 0.00024,
18
+ "lr_floor_factor": 0.05,
19
+ "scheduler": "single_linear_warmup_then_cosine_no_stage_restarts",
20
+ "gradient_communication": "bfloat16",
21
+ "deterministic": false,
22
+ "checkpoint_policy": "all_stage_boundaries_plus_interval",
23
+ "checkpoint_interval_seconds": 3600,
24
+ "max_invocation_seconds": 40000,
25
+ "resume_policy": "latest_complete_same_plan_world",
26
+ "prompt_contract": "m2-multiformat-caption-v1-20260923",
27
+ "allowed_prompt_format_ids": [
28
+ "m2-cascade-timed-repair-v1-20260920",
29
+ "m2-multiformat-caption-v1-20260923"
30
+ ],
31
+ "codec_contract": "MOSS-Audio-Tokenizer-v2; 12 codebooks/frame; 12.5 frames/s; 150 codebook tokens/s",
32
+ "code_sha256": {
33
+ "continuous_train.py": "58b6c82beaaf6ff8e2b65fea6312a28afd75079153c13dfdb9d432de4c42befc",
34
+ "large_talker.py": "3a1d0ed44467a6933dfac2a21a143a1c802482bafb2e992a1e6d8793e3fcdae4",
35
+ "caption_curriculum_dataset.py": "6c01a3e26e5908d01391bfd4aa89815746a6a26aedca397ba1299e5d688a2a64",
36
+ "cascade_dataset.py": "784967ce6d1a545d565a95a16c940ee2743085362576d1566c2b37aa2f40b351",
37
+ "timed_prompt.py": "4d06d0447573284f09804ba516bb2121f65d4cb26ec5a2c01c5bd8d425c8a642",
38
+ "anno_timed.py": "06df8ed3cb7f67d59d1bfca2931e26dd4489344925c01e4412836d2fcf946ec6"
39
+ },
40
+ "created_at": "2026-09-25T08:43:16.244121+02:00",
41
+ "stages": [
42
+ {
43
+ "name": "S1",
44
+ "tier": "x100",
45
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S1/manifest.json",
46
+ "manifest_sha256": "447cc8c91fa507dad7e4c30fbe36e1f5510efcc13e49b2e267e4fdbc04cad56f",
47
+ "presentations": 66621417,
48
+ "measured_hours": 188353.851,
49
+ "updates": 16265
50
+ },
51
+ {
52
+ "name": "S2",
53
+ "tier": "x50",
54
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S2/manifest.json",
55
+ "manifest_sha256": "1db622e32c210ad3bf258251852e4f4fd0095d521da8aa08c91431e1ac175ade",
56
+ "presentations": 41036973,
57
+ "measured_hours": 116837.568,
58
+ "updates": 10019
59
+ },
60
+ {
61
+ "name": "S3",
62
+ "tier": "tier7",
63
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S3/manifest.json",
64
+ "manifest_sha256": "7625d9fb0a74709af1f6ad7698c77369c32e131271b8f88f8714d12e821eee50",
65
+ "presentations": 24789493,
66
+ "measured_hours": 72314.446,
67
+ "updates": 6053
68
+ },
69
+ {
70
+ "name": "S4",
71
+ "tier": "tier6",
72
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S4/manifest.json",
73
+ "manifest_sha256": "255cb00bc50183ceadf7b1dec23b65a3fb6f575daa06002365a9c658e21a6226",
74
+ "presentations": 19842627,
75
+ "measured_hours": 57419.06,
76
+ "updates": 4845
77
+ },
78
+ {
79
+ "name": "S5",
80
+ "tier": "tier5",
81
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S5/manifest.json",
82
+ "manifest_sha256": "eb3b6e0d004f6460adff2274d3041925d5b417a853010dad3c39106b30074a24",
83
+ "presentations": 10152521,
84
+ "measured_hours": 28716.381,
85
+ "updates": 2479
86
+ },
87
+ {
88
+ "name": "S6",
89
+ "tier": "tier4",
90
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S6/manifest.json",
91
+ "manifest_sha256": "90094e2cb294791ff7e24df7f36acddac39e95cca25a6c2fc885bc2b9f2f5bb2",
92
+ "presentations": 5217313,
93
+ "measured_hours": 14361.366,
94
+ "updates": 1274
95
+ },
96
+ {
97
+ "name": "S7",
98
+ "tier": "tier3",
99
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S7/manifest.json",
100
+ "manifest_sha256": "b3d94f4b4b10e78fc4b4a33186b86611f7d1ad69a88a84175f9d9c0a69f98df7",
101
+ "presentations": 2171373,
102
+ "measured_hours": 5749.569,
103
+ "updates": 531
104
+ },
105
+ {
106
+ "name": "S8",
107
+ "tier": "tier2",
108
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S8/manifest.json",
109
+ "manifest_sha256": "0187e2809cee89f6f4431b3827d8046ce2f8d4f059c37e3086e38d42197db865",
110
+ "presentations": 1116702,
111
+ "measured_hours": 2877.426,
112
+ "updates": 273
113
+ },
114
+ {
115
+ "name": "S9",
116
+ "tier": "tier1",
117
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S9/manifest.json",
118
+ "manifest_sha256": "edf6e8fff72d6d6e8128a0c2f06e7a0ee2aa1064c420d85dbc17fd7f0871372f",
119
+ "presentations": 568338,
120
+ "measured_hours": 1439.466,
121
+ "updates": 139
122
+ },
123
+ {
124
+ "name": "S10",
125
+ "tier": "tier0",
126
+ "manifest": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/manifests/S10/manifest.json",
127
+ "manifest_sha256": "24d9d3f500c8132fc07168ad07470f7cafdecec4dde7c7b064d395d49909ec13",
128
+ "presentations": 234815,
129
+ "measured_hours": 577.881,
130
+ "updates": 58
131
+ }
132
+ ],
133
+ "total_updates": 41936,
134
+ "warmup_steps": 2097,
135
+ "output": "/e/scratch/reformo/schuhmann1_moss/out/m2_600m_ladder/caption_curriculum_v1/training",
136
+ "purpose": "authorized continuous S1-to-S10 multi-caption production"
137
+ }
code/prompt_fmt.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prompt-Oberfläche für alle drei Modelle: das SFT3-Nutzer-Template (prompt_lib2.USER_INST_TEMPLATE, byte-gleich),
2
+ Instruction = Hausformat-Prompt der Leiter (GENERAL + SCRIPT), Text = SCRIPT-Teil, Tokens = Frames, Referenz = <|audio|> oder None."""
3
+ AUDIO_PLACEHOLDER = "<|audio|>"
4
+ USER_INST_TEMPLATE = """<user_inst>
5
+ - Reference(s):
6
+ {reference}
7
+ - Instruction:
8
+ {instruction}
9
+ - Tokens:
10
+ {tokens}
11
+ - Quality:
12
+ {quality}
13
+ - Sound Event:
14
+ {sound_event}
15
+ - Ambient Sound:
16
+ {ambient_sound}
17
+ - Language:
18
+ {language}
19
+ - Text:
20
+ {text}
21
+ </user_inst>"""
22
+
23
+
24
+ def lang_name(lang):
25
+ return "German" if str(lang or "en").lower().startswith("de") else "English"
26
+
27
+
28
+ def split_prompt(prompt):
29
+ if 'SCRIPT:\n' in prompt:
30
+ return prompt, prompt.split('SCRIPT:\n', 1)[1].strip()
31
+ return prompt, prompt.strip()
32
+
33
+
34
+ def user_content(meta, use_ref, frames=None):
35
+ instruction, script = split_prompt(meta['prompt'])
36
+ return USER_INST_TEMPLATE.format(
37
+ reference=AUDIO_PLACEHOLDER if use_ref else "None", instruction=instruction.strip() or "None",
38
+ tokens=int(frames if frames is not None else meta['frames']), quality="None", sound_event="None",
39
+ ambient_sound="None", language=lang_name(meta['lang']), text=script or "None")
code/state_io.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Atomic full-state DDP checkpoints with per-rank RNG and pinned contracts."""
2
+ from datetime import datetime
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ import random
7
+ import numpy as np
8
+ import torch
9
+ import torch.distributed as dist
10
+ from continuation_control import sha
11
+
12
+
13
+ def ranks():
14
+ return (dist.get_rank(), dist.get_world_size()) if dist.is_initialized() else (0, 1)
15
+
16
+
17
+ def barrier():
18
+ if dist.is_initialized():
19
+ dist.barrier()
20
+
21
+
22
+ def unwrap(model):
23
+ return model.module if hasattr(model, 'module') else model
24
+
25
+
26
+ def save(root, model, optimizer, scheduler, step, contract, export=False):
27
+ rank, world = ranks()
28
+ root = Path(root)
29
+ rng = {'torch': torch.get_rng_state(), 'cuda': torch.cuda.get_rng_state(),
30
+ 'numpy': np.random.get_state(), 'python': random.getstate()}
31
+ all_rng = [None] * world if rank == 0 else None
32
+ if world > 1:
33
+ dist.gather_object(rng, all_rng, dst=0)
34
+ else:
35
+ all_rng = [rng]
36
+ directory = root / f'step-{step:08d}'
37
+ if rank == 0:
38
+ temporary = root / f'.step-{step:08d}-{os.environ.get("SLURM_JOB_ID", "local")}.partial'
39
+ temporary.mkdir(parents=True, exist_ok=True)
40
+ torch.save({'model': unwrap(model).state_dict(), 'optimizer': optimizer.state_dict(),
41
+ 'scheduler': scheduler.state_dict(), 'rng': all_rng, 'step': step, 'world': world,
42
+ 'contract': contract}, temporary / 'training_state.pt')
43
+ metadata = {'step': step, 'world': world, 'contract': contract,
44
+ 'created_at': datetime.now().astimezone().isoformat(),
45
+ 'state_bytes': (temporary / 'training_state.pt').stat().st_size,
46
+ 'state_sha256': sha(temporary / 'training_state.pt')}
47
+ if export:
48
+ base = unwrap(model).base
49
+ cache, state = {}, {}
50
+ for name, value in base.state_dict().items():
51
+ key = (value.data_ptr(), tuple(value.shape), value.dtype)
52
+ if key not in cache:
53
+ cache[key] = value.detach().to(dtype=torch.bfloat16, device='cpu')
54
+ state[name] = cache[key]
55
+ torch.save(state, temporary / 'model_bf16.pt')
56
+ metadata['export_bytes'] = (temporary / 'model_bf16.pt').stat().st_size
57
+ metadata['export_sha256'] = sha(temporary / 'model_bf16.pt')
58
+ (temporary / 'COMPLETE.json').write_text(json.dumps(metadata, indent=2) + '\n')
59
+ if directory.exists():
60
+ raise RuntimeError(f'Refusing to overwrite existing checkpoint {directory}')
61
+ temporary.replace(directory)
62
+ pointer = root / 'current.json.tmp'
63
+ pointer.write_text(json.dumps({'directory': directory.name, 'step': step}) + '\n')
64
+ pointer.replace(root / 'current.json')
65
+ barrier()
66
+ return directory
67
+
68
+
69
+ def load(directory, model, optimizer, scheduler, contract):
70
+ directory = Path(directory)
71
+ rank, world = ranks()
72
+ metadata = json.loads((directory / 'COMPLETE.json').read_text())
73
+ assert metadata['contract'] == contract and metadata['world'] == world
74
+ assert (directory / 'training_state.pt').stat().st_size == metadata['state_bytes']
75
+ state = torch.load(directory / 'training_state.pt', map_location='cpu', weights_only=False)
76
+ assert state['contract'] == contract and state['world'] == world and len(state['rng']) == world
77
+ unwrap(model).load_state_dict(state['model'], strict=True)
78
+ optimizer.load_state_dict(state['optimizer']); scheduler.load_state_dict(state['scheduler'])
79
+ rng = state['rng'][rank]
80
+ torch.set_rng_state(rng['torch']); torch.cuda.set_rng_state(rng['cuda'])
81
+ np.random.set_state(rng['numpy']); random.setstate(rng['python'])
82
+ step = int(state['step'])
83
+ assert step==metadata['step']
84
+ del state
85
+ barrier()
86
+ return step
code/tar_member_map.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Packed, read-only map from TAR path/member name to byte offset and size.
3
+
4
+ The corpus stays in the existing large TAR containers. A single compressed
5
+ record per TAR replaces repeated full TAR-directory scans on every DDP rank.
6
+ """
7
+ from collections import OrderedDict
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ import threading
12
+ import zlib
13
+
14
+
15
+ class TarMemberMap:
16
+ def __init__(self, manifest, capacity=256):
17
+ self.manifest_path = Path(manifest)
18
+ spec = json.loads(self.manifest_path.read_text())
19
+ assert spec['status'] == 'complete' and spec['format'] == 'zlib-json-tar-member-map-v1'
20
+ root = self.manifest_path.parent
21
+ index_path = root / spec['index']
22
+ data_path = root / spec['data']
23
+ assert index_path.stat().st_size == spec['index_bytes']
24
+ assert data_path.stat().st_size == spec['data_bytes']
25
+ index = json.loads(index_path.read_text())
26
+ assert len(index['paths']) == spec['tars']
27
+ assert all(len(index[k]) == spec['tars'] for k in ('offsets', 'sizes', 'members', 'tar_sizes'))
28
+ self.rows = {path: (int(index['offsets'][i]), int(index['sizes'][i]),
29
+ int(index['members'][i]), int(index['tar_sizes'][i]))
30
+ for i, path in enumerate(index['paths'])}
31
+ self.fd = os.open(data_path, os.O_RDONLY)
32
+ self.capacity = int(capacity)
33
+ self.local = threading.local()
34
+
35
+ def _cache(self):
36
+ if not hasattr(self.local, 'members'):
37
+ self.local.members = OrderedDict()
38
+ return self.local.members
39
+
40
+ def locate(self, path, member):
41
+ cache = self._cache()
42
+ if path not in cache:
43
+ assert path in self.rows, f'TAR absent from packed member map: {path}'
44
+ offset, size, expected_members, expected_tar_size = self.rows[path]
45
+ assert os.path.getsize(path) == expected_tar_size, f'TAR size changed after index build: {path}'
46
+ compressed = os.pread(self.fd, size, offset)
47
+ assert len(compressed) == size
48
+ decoded = json.loads(zlib.decompress(compressed))
49
+ assert len(decoded) == expected_members
50
+ cache[path] = decoded
51
+ while len(cache) > self.capacity:
52
+ cache.popitem(last=False)
53
+ cache.move_to_end(path)
54
+ value = cache[path].get(member)
55
+ assert value is not None, f'Member absent from packed map: {path}::{member}'
56
+ return int(value[0]), int(value[1])
57
+