Document architecture, prompts, code, and full run statistics
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- README.md +124 -0
- assets/qwen3/config.json +30 -0
- assets/score_schema.json +1043 -0
- assets/sft3/__init__.py +9 -0
- assets/sft3/added_tokens.json +31 -0
- assets/sft3/config.json +267 -0
- assets/sft3/configuration_moss_tts.py +158 -0
- assets/sft3/gpt2_decoder.py +721 -0
- assets/sft3/modeling_moss_tts.py +623 -0
- assets/sft3/processing_moss_tts.py +914 -0
- assets/sft3/processor_config.json +7 -0
- assets/sft3/qwen3_decoder.py +582 -0
- assets/sft3/special_tokens_map.json +21 -0
- assets/sft3/tokenizer.json +3 -0
- assets/sft3/tokenizer_config.json +253 -0
- checkpoints/S1/metadata.json +17 -0
- checkpoints/S10/metadata.json +17 -0
- checkpoints/S2/metadata.json +17 -0
- checkpoints/S3/metadata.json +17 -0
- checkpoints/S4/metadata.json +17 -0
- checkpoints/S5/metadata.json +17 -0
- checkpoints/S6/metadata.json +17 -0
- checkpoints/S7/metadata.json +17 -0
- checkpoints/S8/metadata.json +17 -0
- checkpoints/S9/metadata.json +17 -0
- code/anno_timed.py +328 -0
- code/caption_curriculum_dataset.py +172 -0
- code/cascade_dataset.py +105 -0
- code/conditioning.py +153 -0
- code/continuation_control.py +51 -0
- code/continuous_train.py +428 -0
- code/dataset.py +167 -0
- code/infer.py +99 -0
- code/large_talker.py +60 -0
- code/moss_export/__init__.py +9 -0
- code/moss_export/configuration_moss_tts.py +158 -0
- code/moss_export/gpt2_decoder.py +721 -0
- code/moss_export/modeling_moss_tts.py +623 -0
- code/moss_export/processing_moss_tts.py +914 -0
- code/moss_export/qwen3_decoder.py +582 -0
- code/moss_pack.py +95 -0
- code/moss_small.py +220 -0
- code/pack_moss.py +40 -0
- code/packing.py +162 -0
- code/plan.py +96 -0
- code/plan_caption_production_32nodes.json +137 -0
- code/prompt_fmt.py +39 -0
- code/state_io.py +86 -0
- 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 |
+
|