Set merged 12-Oct-2026 Humaneness Voice Small as default
Browse files- README.md +17 -9
- checkpoints/HVS-Small-2026-10-12/metadata.json +23 -0
- checkpoints/default.json +10 -7
- code/acting_2026_10_12/lora_layers.py +49 -0
- code/acting_2026_10_12/merge_lora.py +135 -0
- code/acting_2026_10_12/train_lora.py +244 -0
- code/infer.py +5 -5
- docs/HVS_SMALL_2026_10_12.md +19 -0
README.md
CHANGED
|
@@ -19,15 +19,23 @@ model-index:
|
|
| 19 |
|
| 20 |
# Humaneness Voice Small
|
| 21 |
|
| 22 |
-
|
| 23 |
|
| 24 |
-
|
| 25 |
|
| 26 |
-
The
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
The key fix is the historical **2.96-second reference crop**: the original training loader and earlier example inference code used at most 37 × 80-ms codec frames even though the stored reference sequences were longer. This continuation used up to **187 frames / 14.96 seconds**, with a 55/20/20/5 family of script, historical structured, caption-transcript, and transcript-only prompts. It fully updated the active semantic backbone and Talker after a conservative target-text repair gate; it is **not** the Rank-128 LoRA and did not start from that adapter. There were **3,255,217 accepted training targets**, each presented once with its distinct reference. Read the detailed [data selection, repair, training and evaluation report](docs/S3_REF15_FULL_FT.md) and the [checkpoint metadata](checkpoints/S3-Ref15-FullFT/metadata.json) before reusing it.
|
| 29 |
|
| 30 |
-
This
|
| 31 |
|
| 32 |
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.
|
| 33 |
|
|
@@ -48,7 +56,7 @@ The `checkpoints/S*/model_bf16.pt` files are the **lowest independent held-out v
|
|
| 48 |
| S9 | tier1 | 1,439 | 41,878 | 4.7198 | 0.4540 |
|
| 49 |
| S10 | tier0 | 578 | 41,936 | 4.7278 | 0.4572 |
|
| 50 |
|
| 51 |
-
¹ 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**. Among the **original ladder stages**, S3 was a reasonable provisional choice; inspect the same prompts in S3/S10 before choosing for a production voice. The later Ref15 Full FT
|
| 52 |
|
| 53 |
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.
|
| 54 |
|
|
@@ -139,7 +147,7 @@ Throughput was measured on an **RTX 3090**, using S3 at revision `5de86032771c37
|
|
| 139 |
- `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.
|
| 140 |
- `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.
|
| 141 |
|
| 142 |
-
Example using the **
|
| 143 |
|
| 144 |
```bash
|
| 145 |
python code/infer.py --stage default --reference-wav reference.wav --language en --frames 75 \
|
|
@@ -147,7 +155,7 @@ python code/infer.py --stage default --reference-wav reference.wav --language en
|
|
| 147 |
--text 'I cannot believe we made it.' --output example.wav
|
| 148 |
```
|
| 149 |
|
| 150 |
-
Do not use the target recording as its own reference. For reference-free inference, compare `--stage S3`; the
|
| 151 |
|
| 152 |
## Training data, license and responsible use
|
| 153 |
|
|
@@ -167,7 +175,7 @@ Both collections use the S3 checkpoint at step 32,337; consult each adapter card
|
|
| 167 |
<!-- hvs-small-dpo-default-study-2026-10-04 -->
|
| 168 |
## Default DPO-adapter recommendation (matched speaker/emotion study)
|
| 169 |
|
| 170 |
-
For **the historical S3 DPO comparison, use the unadapted S3 checkpoint rather than activating a DPO LoRA by default**. This is an adapter-level recommendation, **not** a reversal of the newer
|
| 171 |
with references and listening examples](https://laion-humaneness-voice-small-acting-eval-space.static.hf.space/dpo_lora_speaker_emotion_2026-10-02.html) compared the same 12
|
| 172 |
English/German acting challenges, prompts and seeds for each condition (432
|
| 173 |
outputs per condition, including 198 reference-conditioned outputs). Its
|
|
@@ -197,7 +205,7 @@ exact prompts, paired audio players and methodological limits.
|
|
| 197 |
|
| 198 |
**None for general use on the historical S3 base:** unadapted S3 achieved
|
| 199 |
lower WER than any DPO LoRA in that matched study. The repository-wide
|
| 200 |
-
reference-conditioned checkpoint default
|
| 201 |
evaluated under a different 50-case protocol; this DPO result does not rank it.
|
| 202 |
If an application explicitly prioritizes similarity to a provided speaker
|
| 203 |
reference, the **best-tested optional DPO LoRA at this date** is
|
|
|
|
| 19 |
|
| 20 |
# Humaneness Voice Small
|
| 21 |
|
| 22 |
+
## Current default: Humaneness Voice Small (12-10-26)
|
| 23 |
|
| 24 |
+
**[Humaneness Voice Small (12-10-26)](checkpoints/HVS-Small-2026-10-12/model_bf16.pt)** is the current, complete BF16 checkpoint. It is the Real+Gemini full-parameter model after two more Gemini acting epochs with a Rank-64 semantic attention LoRA, **merged into the base weights**. You do not need to load an adapter. The Talker weights come from Real+Gemini and were not changed by this final LoRA. `python code/infer.py --stage default` loads this checkpoint; `--stage S3-Ref15-FullFT` still selects the preceding default. All original S1–S10 and Ref15 checkpoints remain available.
|
| 25 |
|
| 26 |
+
The user selected this default by listening, particularly for natural/organic acting. Audition the [matched five-checkpoint acting grid](https://laion-humaneness-voice-small-four-checkpoint-acting.static.hf.space/) with seven emotions, five voices, 12-second references and no-reference cases. The grid audio was generated with the unmerged LoRA; the merged export is checked for structural compatibility and should be numerically close, but no blinded perceptual or universal metric win is claimed. For lineage, exact training recipe, conditioning and caveats, see the [12 October release note](docs/HVS_SMALL_2026_10_12.md).
|
| 27 |
+
|
| 28 |
+
For speaker identity, start with a **distinct, clean 5–15-second reference**. The acting continuation also saw pseudonym-conditioned examples; use its companion acting inference path for pseudonym-only prompts. The older `code/infer.py` remains the general single-clip example and now resolves `default` to the merged weight file. The historical prompt and benchmark sections below describe earlier S1–S10 experiments and should not be mistaken for a fresh evaluation of this new default.
|
| 29 |
+
|
| 30 |
+
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** plus an S3-based long-reference continuation, Gemini/real-speech acting continuations, and the merged 12 October default. It also contains 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.
|
| 31 |
+
|
| 32 |
+
## Previous default: S3 Ref15 Full FT (9 October 2026)
|
| 33 |
+
|
| 34 |
+
The previous **reference-conditioned default checkpoint** is [`checkpoints/S3-Ref15-FullFT/model_bf16.pt`](checkpoints/S3-Ref15-FullFT/model_bf16.pt), the final **step 26,774** of a full-parameter continuation from the **original S3 step 32,337**. The user chose it because it sounded acoustically preferable in listening. Use `code/infer.py --stage S3-Ref15-FullFT` to load it explicitly. The ten original stage checkpoints are unchanged; `--stage S3` explicitly loads the original S3 and is still the comparison to try for **reference-free** generation. For this Ref15 checkpoint, supply a clean, distinct 5–15-second reference WAV; every accepted retraining example had a reference.
|
| 35 |
|
| 36 |
The key fix is the historical **2.96-second reference crop**: the original training loader and earlier example inference code used at most 37 × 80-ms codec frames even though the stored reference sequences were longer. This continuation used up to **187 frames / 14.96 seconds**, with a 55/20/20/5 family of script, historical structured, caption-transcript, and transcript-only prompts. It fully updated the active semantic backbone and Talker after a conservative target-text repair gate; it is **not** the Rank-128 LoRA and did not start from that adapter. There were **3,255,217 accepted training targets**, each presented once with its distinct reference. Read the detailed [data selection, repair, training and evaluation report](docs/S3_REF15_FULL_FT.md) and the [checkpoint metadata](checkpoints/S3-Ref15-FullFT/metadata.json) before reusing it.
|
| 37 |
|
| 38 |
+
This was a **listening-selected acoustic default**, not an objectively established all-round winner. In a matched 50-case non-speaker-disjoint test, final-step WER was 0.096 versus 0.088 for original S3; Orange cosine to the supplied reference was 0.707 versus 0.745, while cosine to the intended target improved from 0.658 to 0.734. Step 25,000 had better measured WER and target cosine than the final step. These learned/ASR proxies and the user's listening impression answer different questions; further blinded and speaker-disjoint evaluation is needed. No optimizer state is uploaded for this continuation.
|
| 39 |
|
| 40 |
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.
|
| 41 |
|
|
|
|
| 56 |
| S9 | tier1 | 1,439 | 41,878 | 4.7198 | 0.4540 |
|
| 57 |
| S10 | tier0 | 578 | 41,936 | 4.7278 | 0.4572 |
|
| 58 |
|
| 59 |
+
¹ 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**. Among the **original ladder stages**, S3 was a reasonable provisional choice; inspect the same prompts in S3/S10 before choosing for a production voice. The later Ref15 Full FT was the previous reference-conditioned default by listening choice, under the caveats above. 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`.
|
| 60 |
|
| 61 |
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.
|
| 62 |
|
|
|
|
| 147 |
- `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.
|
| 148 |
- `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.
|
| 149 |
|
| 150 |
+
Example using the **current merged default with a reference** (requires a CUDA environment with PyTorch, Transformers, NumPy, SoundFile, Torchaudio, Safetensors, and permission to download the separate codec):
|
| 151 |
|
| 152 |
```bash
|
| 153 |
python code/infer.py --stage default --reference-wav reference.wav --language en --frames 75 \
|
|
|
|
| 155 |
--text 'I cannot believe we made it.' --output example.wav
|
| 156 |
```
|
| 157 |
|
| 158 |
+
Do not use the target recording as its own reference. For reference-free inference, compare `--stage S3` as a historical control; the current merged default also saw pseudonym and no-reference acting conditions, while the preceding Ref15-only stage was trained with references. **Correction (8 October 2026):** an earlier public `code/infer.py` rejected any reference over 37 MOSS codec frames (2.96 s). This was an inference-code limit as well as the historical S1–S10 training-loader crop, not an architectural limit. The updated example accepts a configurable reference ceiling (15 s default, 20 s maximum), reports a warning below 5 s and no longer silently blocks longer audio. The new Full FT was trained with up to 14.96 s; using longer than 15 s at inference is out of distribution and warned about. The original S1–S10 weights were trained with at most 2.96 s references, so longer-reference behavior from an original stage is experimental, not a guaranteed improvement. The separate [S3 Ref15 Rank-128 LoRA](https://huggingface.co/laion/Humaneness-Voice-Small-Ref15-LoRA-r128) is a different, frozen-backbone comparison. 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.
|
| 159 |
|
| 160 |
## Training data, license and responsible use
|
| 161 |
|
|
|
|
| 175 |
<!-- hvs-small-dpo-default-study-2026-10-04 -->
|
| 176 |
## Default DPO-adapter recommendation (matched speaker/emotion study)
|
| 177 |
|
| 178 |
+
For **the historical S3 DPO comparison, use the unadapted S3 checkpoint rather than activating a DPO LoRA by default**. This is an adapter-level recommendation, **not** a reversal of the newer 12 October merged checkpoint default above; the DPO LoRAs were trained against S3 and should not be assumed compatible or beneficial on Ref15 Full FT. The historical S3 prompt/CFG recommendations above also refer to S3, not a completed Ref15 tuning sweep. The [matched generated-audio study
|
| 179 |
with references and listening examples](https://laion-humaneness-voice-small-acting-eval-space.static.hf.space/dpo_lora_speaker_emotion_2026-10-02.html) compared the same 12
|
| 180 |
English/German acting challenges, prompts and seeds for each condition (432
|
| 181 |
outputs per condition, including 198 reference-conditioned outputs). Its
|
|
|
|
| 205 |
|
| 206 |
**None for general use on the historical S3 base:** unadapted S3 achieved
|
| 207 |
lower WER than any DPO LoRA in that matched study. The repository-wide
|
| 208 |
+
reference-conditioned checkpoint default was Ref15 Full FT before the 12 October release, which was
|
| 209 |
evaluated under a different 50-case protocol; this DPO result does not rank it.
|
| 210 |
If an application explicitly prioritizes similarity to a provided speaker
|
| 211 |
reference, the **best-tested optional DPO LoRA at this date** is
|
checkpoints/HVS-Small-2026-10-12/metadata.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"title": "Humaneness Voice Small (12-10-26)",
|
| 3 |
+
"checkpoint": "HVS-Small-2026-10-12",
|
| 4 |
+
"format": "complete adapter-free BF16 PyTorch state_dict",
|
| 5 |
+
"base": "/e/scratch/laionize/schuhmann1/acting_challenges_finetune/small/real_gemini_training/checkpoints/step-00015238/model.safetensors",
|
| 6 |
+
"base_sha256": "b1ad2bcbe94a5760c37b4e57a752a894db0033adc28fc62cb885d532cf4e46f2",
|
| 7 |
+
"adapter": "/e/scratch/laionize/schuhmann1/acting_challenges_finetune/small/gemini_lora_r64_2ep_v1/training/adapter.safetensors",
|
| 8 |
+
"adapter_sha256": "3c9e01bc448b0732785240a64dd47f8ea7a7b4d09106b0d24a208e35677a85fe",
|
| 9 |
+
"model_file": "model_bf16.pt",
|
| 10 |
+
"model_bytes": 1431106131,
|
| 11 |
+
"model_sha256": "45f1de4bcc62bef038afc812ac8681b3e6507d9d97e4c18d27518f5a5a346bd4",
|
| 12 |
+
"rank": 64,
|
| 13 |
+
"alpha": 64,
|
| 14 |
+
"merged_modules": 112,
|
| 15 |
+
"target_modules": "Qwen3 semantic attention q_proj, k_proj, v_proj, o_proj",
|
| 16 |
+
"source_step": 15238,
|
| 17 |
+
"adapter_step": 2298,
|
| 18 |
+
"optimizer_state_included": false,
|
| 19 |
+
"merge_validation": "112 nonzero finite deltas; saved state strict-loads without adapter; prompt-only generation canary passed",
|
| 20 |
+
"generation_canary_frames": 24,
|
| 21 |
+
"delta_max_abs": 0.039547283202409744,
|
| 22 |
+
"lineage": "S3 Ref15 Full FT -> Gemini acting full FT -> Real+Gemini full FT -> two-epoch Gemini Rank-64 LoRA -> merged export"
|
| 23 |
+
}
|
checkpoints/default.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
{
|
| 2 |
-
"checkpoint": "
|
| 3 |
-
"
|
| 4 |
-
"
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
-
"
|
|
|
|
|
|
|
|
|
|
| 9 |
"reference_free_fallback": "S3"
|
| 10 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"checkpoint": "HVS-Small-2026-10-12",
|
| 3 |
+
"display_name": "Humaneness Voice Small (12-10-26)",
|
| 4 |
+
"model_file": "checkpoints/HVS-Small-2026-10-12/model_bf16.pt",
|
| 5 |
+
"metadata_file": "checkpoints/HVS-Small-2026-10-12/metadata.json",
|
| 6 |
+
"chosen_on": "2026-10-12",
|
| 7 |
+
"selection_basis": "user listening preference on matched acting grid",
|
| 8 |
+
"merged_adapter": true,
|
| 9 |
+
"reference_or_alias_conditioning": true,
|
| 10 |
+
"reference_max_seconds_recommended": 15,
|
| 11 |
+
"previous_default": "S3-Ref15-FullFT",
|
| 12 |
"reference_free_fallback": "S3"
|
| 13 |
}
|
code/acting_2026_10_12/lora_layers.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Rank-configurable Qwen3 semantic q/k/v/o LoRA for Humaneness Voice Small.
|
| 2 |
+
|
| 3 |
+
Training used rank=alpha=64 and adapted 28 × 4 linear projections. The model
|
| 4 |
+
loader and data mappings remain described in the release note; this is only
|
| 5 |
+
the adapter layer definition, not a standalone training pipeline.
|
| 6 |
+
"""
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import math
|
| 10 |
+
import torch
|
| 11 |
+
from torch import nn
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class LoRALinear(nn.Module):
|
| 15 |
+
def __init__(self, base: nn.Linear, rank: int = 64, alpha: int = 64):
|
| 16 |
+
super().__init__()
|
| 17 |
+
self.base = base
|
| 18 |
+
for parameter in self.base.parameters():
|
| 19 |
+
parameter.requires_grad_(False)
|
| 20 |
+
self.a = nn.Linear(base.in_features, rank, bias=False, dtype=torch.float32,
|
| 21 |
+
device=base.weight.device)
|
| 22 |
+
self.b = nn.Linear(rank, base.out_features, bias=False, dtype=torch.float32,
|
| 23 |
+
device=base.weight.device)
|
| 24 |
+
nn.init.kaiming_uniform_(self.a.weight, a=math.sqrt(5))
|
| 25 |
+
nn.init.zeros_(self.b.weight)
|
| 26 |
+
self.scale = alpha / rank
|
| 27 |
+
self.enabled = True
|
| 28 |
+
|
| 29 |
+
def forward(self, x):
|
| 30 |
+
result = self.base(x)
|
| 31 |
+
if self.enabled:
|
| 32 |
+
result = result + self.scale * self.b(self.a(x.float())).to(result.dtype)
|
| 33 |
+
return result
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def attach(model, rank: int = 64, alpha: int = 64):
|
| 37 |
+
suffixes = ('.self_attn.q_proj', '.self_attn.k_proj',
|
| 38 |
+
'.self_attn.v_proj', '.self_attn.o_proj')
|
| 39 |
+
wrapped = []
|
| 40 |
+
for name, module in list(model.named_modules()):
|
| 41 |
+
if not name.startswith('transformer.layers.') or not name.endswith(suffixes):
|
| 42 |
+
continue
|
| 43 |
+
assert isinstance(module, nn.Linear), name
|
| 44 |
+
parent, leaf = name.rsplit('.', 1)
|
| 45 |
+
adapted = LoRALinear(module, rank=rank, alpha=alpha)
|
| 46 |
+
setattr(model.get_submodule(parent), leaf, adapted)
|
| 47 |
+
wrapped.append((name, adapted))
|
| 48 |
+
assert len(wrapped) == 112, len(wrapped)
|
| 49 |
+
return wrapped
|
code/acting_2026_10_12/merge_lora.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Merge the finished Gemini Rank-64 semantic LoRA into Real+Gemini Small.
|
| 3 |
+
|
| 4 |
+
Run on a JUPITER Booster node. The export is a complete BF16 PyTorch state
|
| 5 |
+
dict in the same format as the original Humaneness Voice Small checkpoints.
|
| 6 |
+
No optimizer state or source checkpoint is changed.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import hashlib
|
| 11 |
+
import json
|
| 12 |
+
import os
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import sys
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
from safetensors.torch import load_file
|
| 18 |
+
|
| 19 |
+
HOME = Path('/e/home/jusers/schuhmann1/jupiter')
|
| 20 |
+
DATA = Path('/e/scratch/laionize/schuhmann1/acting_challenges_finetune')
|
| 21 |
+
SOURCE = DATA / 'small/real_gemini_training/checkpoints/step-00015238/model.safetensors'
|
| 22 |
+
ADAPTER = DATA / 'small/gemini_lora_r64_2ep_v1/training/adapter.safetensors'
|
| 23 |
+
OUT = DATA / 'small/hf_release_2026_10_12/checkpoints/HVS-Small-2026-10-12'
|
| 24 |
+
sys.path[:0] = [str(DATA), str(HOME / 'hvs_ref_only_r128_v1/public_release')]
|
| 25 |
+
import train_fullft as ft
|
| 26 |
+
from lora_layers import attach
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def sha(path: Path) -> str:
|
| 30 |
+
digest = hashlib.sha256()
|
| 31 |
+
with path.open('rb') as stream:
|
| 32 |
+
for block in iter(lambda: stream.read(8 << 20), b''):
|
| 33 |
+
digest.update(block)
|
| 34 |
+
return digest.hexdigest()
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def main() -> None:
|
| 38 |
+
torch.set_num_threads(8)
|
| 39 |
+
assert torch.cuda.is_available(), 'Run merge on a GPU compute node'
|
| 40 |
+
assert SOURCE.is_file() and ADAPTER.is_file()
|
| 41 |
+
done = json.loads((ADAPTER.parent / 'DONE.json').read_text())
|
| 42 |
+
assert done['status'] == 'COMPLETE', done
|
| 43 |
+
OUT.mkdir(parents=True, exist_ok=True)
|
| 44 |
+
target = OUT / 'model_bf16.pt'
|
| 45 |
+
metadata = OUT / 'metadata.json'
|
| 46 |
+
if target.exists() or metadata.exists():
|
| 47 |
+
raise FileExistsError('Release export already exists; inspect it, do not overwrite it')
|
| 48 |
+
|
| 49 |
+
model, config = ft.load_model('small')
|
| 50 |
+
source = load_file(str(SOURCE), device='cpu')
|
| 51 |
+
assert source and all(name.startswith('base.') for name in source)
|
| 52 |
+
missing, unexpected = model.load_state_dict(
|
| 53 |
+
{name[5:]: value for name, value in source.items()}, strict=False)
|
| 54 |
+
tied = {'text_lm_head.weight'} | {f'audio_lm_heads.{i}.weight' for i in range(12)}
|
| 55 |
+
assert not unexpected and set(missing) <= tied, (missing, unexpected)
|
| 56 |
+
model.tie_weights()
|
| 57 |
+
del source
|
| 58 |
+
wrapped = attach(model, rank=64, alpha=64)
|
| 59 |
+
assert len(wrapped) == 112
|
| 60 |
+
adapter = load_file(str(ADAPTER), device='cpu')
|
| 61 |
+
expected = {name + suffix for name, _ in wrapped for suffix in ('.a.weight', '.b.weight')}
|
| 62 |
+
assert set(adapter) == expected, (len(adapter), len(expected))
|
| 63 |
+
|
| 64 |
+
changes = []
|
| 65 |
+
with torch.no_grad():
|
| 66 |
+
for name, module in wrapped:
|
| 67 |
+
a = adapter[name + '.a.weight'].to('cuda', dtype=torch.float32)
|
| 68 |
+
b = adapter[name + '.b.weight'].to('cuda', dtype=torch.float32)
|
| 69 |
+
old = module.base.weight.to('cuda', dtype=torch.float32)
|
| 70 |
+
delta = (b @ a) * module.scale
|
| 71 |
+
assert torch.isfinite(delta).all() and torch.count_nonzero(delta) > 0, name
|
| 72 |
+
merged = (old + delta).to('cpu', dtype=torch.bfloat16)
|
| 73 |
+
module.base.weight.copy_(merged.to(module.base.weight.dtype))
|
| 74 |
+
parent, leaf = name.rsplit('.', 1)
|
| 75 |
+
setattr(model.get_submodule(parent), leaf, module.base)
|
| 76 |
+
changes.append({'name': name, 'max_abs_delta': float(delta.abs().max().item())})
|
| 77 |
+
if len(changes) % 28 == 0:
|
| 78 |
+
print(json.dumps({'event': 'merged', 'modules': len(changes)}), flush=True)
|
| 79 |
+
assert len(changes) == 112
|
| 80 |
+
assert not any('.a.weight' in key or '.b.weight' in key for key in model.state_dict())
|
| 81 |
+
model = model.to('cpu', dtype=torch.bfloat16).eval()
|
| 82 |
+
state = model.state_dict()
|
| 83 |
+
assert state and all(value.dtype == torch.bfloat16 for value in state.values()
|
| 84 |
+
if value.is_floating_point())
|
| 85 |
+
temp = target.with_suffix('.pt.partial')
|
| 86 |
+
torch.save(state, temp)
|
| 87 |
+
del state
|
| 88 |
+
# The saved artifact must load strictly as an ordinary, adapter-free model.
|
| 89 |
+
check = torch.load(temp, map_location='cpu', weights_only=True)
|
| 90 |
+
model.load_state_dict(check, strict=True)
|
| 91 |
+
del check
|
| 92 |
+
# Exact public-grid prompt path, without loading or writing loose audio.
|
| 93 |
+
from packing import generated_audio
|
| 94 |
+
processor, _ = ft.load_processor('small')
|
| 95 |
+
user = processor.build_user_message(
|
| 96 |
+
text='Hello there.',
|
| 97 |
+
instruction='GENERAL: warm, natural speech.\nSCRIPT:\n(warm, conversational) Hello there.',
|
| 98 |
+
tokens=24, reference=None, language='English')
|
| 99 |
+
ids = processor([[user]], mode='generation')['input_ids'][0].long().unsqueeze(0).to('cuda')
|
| 100 |
+
mask = torch.ones(ids.shape[:2], dtype=torch.long, device='cuda')
|
| 101 |
+
model = model.to('cuda', dtype=torch.bfloat16).eval()
|
| 102 |
+
torch.manual_seed(20261012)
|
| 103 |
+
with torch.inference_mode(), torch.autocast('cuda', dtype=torch.bfloat16):
|
| 104 |
+
generated = model.generate(
|
| 105 |
+
input_ids=ids, attention_mask=mask, max_new_frames=45,
|
| 106 |
+
do_sample=True, audio_temperature=1.0, audio_top_p=.95,
|
| 107 |
+
audio_top_k=50, audio_repetition_penalty=1.0, use_kv_cache=True)
|
| 108 |
+
codes = generated_audio(generated, config)
|
| 109 |
+
assert len(codes) >= 2 and codes.shape[-1] == 12, codes.shape
|
| 110 |
+
del generated
|
| 111 |
+
os.replace(temp, target)
|
| 112 |
+
record = {
|
| 113 |
+
'title': 'Humaneness Voice Small (12-10-26)',
|
| 114 |
+
'checkpoint': 'HVS-Small-2026-10-12',
|
| 115 |
+
'format': 'complete adapter-free BF16 PyTorch state_dict',
|
| 116 |
+
'base': str(SOURCE), 'base_sha256': sha(SOURCE),
|
| 117 |
+
'adapter': str(ADAPTER), 'adapter_sha256': sha(ADAPTER),
|
| 118 |
+
'model_file': target.name, 'model_bytes': target.stat().st_size,
|
| 119 |
+
'model_sha256': sha(target), 'rank': 64, 'alpha': 64,
|
| 120 |
+
'merged_modules': len(changes),
|
| 121 |
+
'target_modules': 'Qwen3 semantic attention q_proj, k_proj, v_proj, o_proj',
|
| 122 |
+
'source_step': 15238, 'adapter_step': 2298,
|
| 123 |
+
'optimizer_state_included': False,
|
| 124 |
+
'merge_validation': '112 nonzero finite deltas; saved state strict-loads without adapter; prompt-only generation canary passed',
|
| 125 |
+
'generation_canary_frames': len(codes),
|
| 126 |
+
'delta_max_abs': max(row['max_abs_delta'] for row in changes),
|
| 127 |
+
'lineage': 'S3 Ref15 Full FT -> Gemini acting full FT -> Real+Gemini full FT -> two-epoch Gemini Rank-64 LoRA -> merged export',
|
| 128 |
+
}
|
| 129 |
+
metadata.write_text(json.dumps(record, indent=2) + '\n')
|
| 130 |
+
print(json.dumps({'status': 'EXPORTED', 'output': str(target),
|
| 131 |
+
'bytes': target.stat().st_size, 'sha256': record['model_sha256']}), flush=True)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
if __name__ == '__main__':
|
| 135 |
+
main()
|
code/acting_2026_10_12/train_lora.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Two Gemini-only epochs of q/k/v/o Rank-64 LoRA above Real+Gemini Small.
|
| 3 |
+
|
| 4 |
+
Each target is presented once per epoch. Its conditioning alternates between
|
| 5 |
+
same-voice reference audio and pseudonym, so both are seen exactly once over
|
| 6 |
+
two epochs. The three frozen prompt views are balanced by UID; no loose audio
|
| 7 |
+
files are produced. Training and resume checkpoints include optimizer/RNG.
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import hashlib
|
| 13 |
+
import json
|
| 14 |
+
import math
|
| 15 |
+
import os
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
import signal
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
|
| 21 |
+
import numpy as np
|
| 22 |
+
import torch
|
| 23 |
+
from safetensors.torch import load_file, save_file
|
| 24 |
+
|
| 25 |
+
HOME = Path('/e/home/jusers/schuhmann1/jupiter')
|
| 26 |
+
DATA = Path('/e/scratch/laionize/schuhmann1/acting_challenges_finetune')
|
| 27 |
+
BASE = DATA / 'small/real_gemini_training/checkpoints/step-00015238/model.safetensors'
|
| 28 |
+
OUT = DATA / 'small/gemini_lora_r64_2ep_v1'
|
| 29 |
+
sys.path[:0] = [str(DATA), str(HOME / 'hvs_ref_only_r128_v1/public_release')]
|
| 30 |
+
import train_fullft as ft
|
| 31 |
+
from acting_data import ActingData, pack_example
|
| 32 |
+
from lora_layers import attach
|
| 33 |
+
|
| 34 |
+
RANK = 64
|
| 35 |
+
PEAK_LR = 1.0e-4
|
| 36 |
+
SEED = 20261012
|
| 37 |
+
ACCUM = 2
|
| 38 |
+
STOP = False
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _signal(_number, _frame):
|
| 42 |
+
global STOP
|
| 43 |
+
STOP = True
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def atomic_json(path, data):
|
| 47 |
+
temp = path.with_suffix(path.suffix + '.partial')
|
| 48 |
+
temp.write_text(json.dumps(data, indent=2, sort_keys=True) + '\n')
|
| 49 |
+
os.replace(temp, path)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def load_base():
|
| 53 |
+
model, config = ft.load_model('small')
|
| 54 |
+
wrapped = load_file(str(BASE), device='cpu')
|
| 55 |
+
assert wrapped and all(k.startswith('base.') for k in wrapped)
|
| 56 |
+
state = {k[5:]: v for k, v in wrapped.items()}
|
| 57 |
+
missing, unexpected = model.load_state_dict(state, strict=False)
|
| 58 |
+
allowed = {'text_lm_head.weight'} | {f'audio_lm_heads.{i}.weight' for i in range(12)}
|
| 59 |
+
assert not unexpected and set(missing) <= allowed, (missing, unexpected)
|
| 60 |
+
model.tie_weights()
|
| 61 |
+
del wrapped, state
|
| 62 |
+
for p in model.parameters():
|
| 63 |
+
p.requires_grad_(False)
|
| 64 |
+
adapted = attach(model, rank=RANK, alpha=RANK)
|
| 65 |
+
assert len(adapted) == 112
|
| 66 |
+
# Some Gemini targets exceed 1,900 codec frames. Checkpoint semantic
|
| 67 |
+
# decoder activations as in the proven full-FT path; a frozen backbone
|
| 68 |
+
# still needs its input activations to compute LoRA gradients.
|
| 69 |
+
assert ft.enable_grad_ckpt(model) >= 20
|
| 70 |
+
return model, config
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def view(data, uid, epoch, index):
|
| 74 |
+
# Both reference and alias paths occur exactly once for every UID across
|
| 75 |
+
# the two epochs, while the 3 GENERAL/SCRIPT views stay balanced.
|
| 76 |
+
cond = 'reference' if (index + epoch) % 2 == 0 else 'alias'
|
| 77 |
+
prompt_epoch = 6 + epoch + sum(uid.encode()) % 3
|
| 78 |
+
return data.example_condition(uid, prompt_epoch, cond)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def main():
|
| 82 |
+
parser = argparse.ArgumentParser()
|
| 83 |
+
parser.add_argument('--smoke-steps', type=int, default=0)
|
| 84 |
+
args = parser.parse_args()
|
| 85 |
+
assert BASE.is_file() and (BASE.parent / 'READY.json').is_file()
|
| 86 |
+
assert json.loads((DATA / 'PROMPTS_READY.json').read_text())['status'] == 'READY'
|
| 87 |
+
from accelerate import Accelerator, DistributedDataParallelKwargs
|
| 88 |
+
from accelerate.utils import set_seed
|
| 89 |
+
accelerator = Accelerator(gradient_accumulation_steps=ACCUM, mixed_precision='bf16',
|
| 90 |
+
kwargs_handlers=[DistributedDataParallelKwargs(
|
| 91 |
+
find_unused_parameters=False, gradient_as_bucket_view=True)])
|
| 92 |
+
world, rank = accelerator.num_processes, accelerator.process_index
|
| 93 |
+
assert world == (4 if args.smoke_steps else 16), world
|
| 94 |
+
set_seed(SEED, device_specific=True)
|
| 95 |
+
torch.set_num_threads(4)
|
| 96 |
+
signal.signal(signal.SIGUSR1, _signal)
|
| 97 |
+
signal.signal(signal.SIGTERM, _signal)
|
| 98 |
+
data = ActingData('train')
|
| 99 |
+
validation = ActingData('validation')
|
| 100 |
+
proc, _ = ft.load_processor('small')
|
| 101 |
+
model, config = load_base()
|
| 102 |
+
parameters = [(n, p) for n, p in model.named_parameters() if p.requires_grad]
|
| 103 |
+
assert len(parameters) == 224 and all(n.endswith(('.a.weight', '.b.weight')) for n, _ in parameters)
|
| 104 |
+
optimizer = torch.optim.AdamW([p for _, p in parameters], lr=PEAK_LR,
|
| 105 |
+
betas=(.9, .95), weight_decay=0., foreach=False)
|
| 106 |
+
wrapper, optimizer = accelerator.prepare(ft.TrainStep(model), optimizer)
|
| 107 |
+
result = OUT / ('smoke' if args.smoke_steps else 'training')
|
| 108 |
+
result.mkdir(parents=True, exist_ok=True)
|
| 109 |
+
n = len(data.ids)
|
| 110 |
+
longest_index = max(range(n), key=lambda i: data.by_id[data.ids[i]][0][2])
|
| 111 |
+
steps_per_epoch = math.ceil(n / (world * ACCUM))
|
| 112 |
+
total = steps_per_epoch * 2
|
| 113 |
+
contract = {'kind': 'humaneness-small-real-gemini-plus-gemini-lora-r64-two-epochs-v1',
|
| 114 |
+
'base': str(BASE), 'base_bytes': BASE.stat().st_size,
|
| 115 |
+
'assignment_sha256': ft.file_sha(DATA / 'acting_challenges_three_view_v2.sqlite'),
|
| 116 |
+
'prompt_cache_sha256': ft.file_sha(DATA / 'prompt_cache.sqlite'),
|
| 117 |
+
'rank': RANK, 'alpha': RANK, 'target': 'semantic Qwen3 attention q,k,v,o only',
|
| 118 |
+
'adapted_linear_modules': 112, 'trainable_parameters': sum(p.numel() for _, p in parameters),
|
| 119 |
+
'dataset_rows_per_epoch': n, 'world': world, 'accum': ACCUM,
|
| 120 |
+
'steps_per_epoch': steps_per_epoch, 'total_steps': total,
|
| 121 |
+
'peak_lr': PEAK_LR, 'warmup_fraction': .05, 'floor_factor': .1,
|
| 122 |
+
'seed': SEED, 'conditioning': 'each UID once reference and once alias over two epochs',
|
| 123 |
+
'prompt_views': 'balanced rotating GENERAL/SCRIPT views and original 10% full caption dropout'}
|
| 124 |
+
digest = hashlib.sha256(json.dumps(contract, sort_keys=True).encode()).hexdigest()
|
| 125 |
+
if accelerator.is_main_process:
|
| 126 |
+
atomic_json(result / 'RUN_CONTRACT.json', {**contract, 'contract_sha256': digest})
|
| 127 |
+
accelerator.wait_for_everyone()
|
| 128 |
+
ckpts = sorted((result / 'checkpoints').glob('step-*/READY.json'))
|
| 129 |
+
step = 0
|
| 130 |
+
if ckpts:
|
| 131 |
+
latest = ckpts[-1]
|
| 132 |
+
meta = json.loads(latest.read_text())
|
| 133 |
+
assert meta['contract_sha256'] == digest
|
| 134 |
+
accelerator.load_state(str(latest.parent))
|
| 135 |
+
step = int(meta['step'])
|
| 136 |
+
if accelerator.is_main_process:
|
| 137 |
+
print(json.dumps({'event': 'start', 'world': world, 'step': step, 'total': total,
|
| 138 |
+
'steps_per_epoch': steps_per_epoch, 'parameters': contract['trainable_parameters']}), flush=True)
|
| 139 |
+
begun = time.monotonic()
|
| 140 |
+
last_save = begun
|
| 141 |
+
orders = [np.random.default_rng(SEED + e).permutation(n) for e in range(2)]
|
| 142 |
+
warm = max(1, round(total * .05))
|
| 143 |
+
|
| 144 |
+
def save(current, reason):
|
| 145 |
+
path = result / 'checkpoints' / f'step-{current:08d}'
|
| 146 |
+
temp = result / 'checkpoints' / f'step-{current:08d}.partial'
|
| 147 |
+
conflict = torch.tensor([int(accelerator.is_main_process and (path.exists() or temp.exists()))],
|
| 148 |
+
dtype=torch.int32, device=accelerator.device)
|
| 149 |
+
if world > 1:
|
| 150 |
+
torch.distributed.broadcast(conflict, src=0)
|
| 151 |
+
if conflict.item():
|
| 152 |
+
raise FileExistsError(path)
|
| 153 |
+
accelerator.save_state(str(temp))
|
| 154 |
+
accelerator.wait_for_everyone()
|
| 155 |
+
if accelerator.is_main_process:
|
| 156 |
+
atomic_json(temp / 'READY.json', {'step': current, 'reason': reason,
|
| 157 |
+
'contract_sha256': digest,
|
| 158 |
+
'optimizer_and_rng_saved': True})
|
| 159 |
+
os.replace(temp, path)
|
| 160 |
+
print(json.dumps({'event': 'checkpoint', 'step': current, 'path': str(path),
|
| 161 |
+
'reason': reason}), flush=True)
|
| 162 |
+
accelerator.wait_for_everyone()
|
| 163 |
+
|
| 164 |
+
for current in range(step + 1, total + 1):
|
| 165 |
+
epoch, offset = divmod(current - 1, steps_per_epoch)
|
| 166 |
+
valid = min(world * ACCUM, n - offset * world * ACCUM)
|
| 167 |
+
assert valid > 0
|
| 168 |
+
if current <= warm:
|
| 169 |
+
factor = current / warm
|
| 170 |
+
else:
|
| 171 |
+
frac = min(1., (current - warm) / max(1, total - warm))
|
| 172 |
+
factor = .1 + .9 * .5 * (1 + math.cos(math.pi * frac))
|
| 173 |
+
optimizer.param_groups[0]['lr'] = PEAK_LR * factor
|
| 174 |
+
last_loss = None
|
| 175 |
+
for micro in range(ACCUM):
|
| 176 |
+
position = (offset * ACCUM + micro) * world + rank
|
| 177 |
+
active = position < n
|
| 178 |
+
index = int(orders[epoch][position]) if active else 0
|
| 179 |
+
if args.smoke_steps and current == 2 and micro == 0 and rank == 0:
|
| 180 |
+
index = longest_index # Exercise worst-case attention memory.
|
| 181 |
+
uid = data.ids[index]
|
| 182 |
+
row = view(data, uid, epoch, index)
|
| 183 |
+
batch = {k: v.to(accelerator.device, non_blocking=True)
|
| 184 |
+
for k, v in pack_example(proc, config, row).items()}
|
| 185 |
+
with accelerator.accumulate(wrapper):
|
| 186 |
+
loss = wrapper(batch['input_ids'], batch['attention_mask'], batch['labels'])
|
| 187 |
+
if not bool(torch.isfinite(loss).item()):
|
| 188 |
+
raise FloatingPointError((current, uid, 'loss'))
|
| 189 |
+
accelerator.backward(loss * (world * ACCUM / valid if active else 0.))
|
| 190 |
+
if accelerator.sync_gradients:
|
| 191 |
+
norm = accelerator.clip_grad_norm_(wrapper.parameters(), 1.)
|
| 192 |
+
if not bool(torch.isfinite(norm).item()):
|
| 193 |
+
raise FloatingPointError((current, uid, 'norm'))
|
| 194 |
+
optimizer.step()
|
| 195 |
+
optimizer.zero_grad(set_to_none=True)
|
| 196 |
+
last_loss = loss.detach().float()
|
| 197 |
+
if current <= 3 or current % 20 == 0 or current == total:
|
| 198 |
+
mean_loss = float(accelerator.gather(last_loss.reshape(1)).mean())
|
| 199 |
+
if accelerator.is_main_process:
|
| 200 |
+
event = {'event': 'step', 'step': current, 'total': total,
|
| 201 |
+
'epoch': epoch+1, 'loss_last_micro': mean_loss,
|
| 202 |
+
'lr': optimizer.param_groups[0]['lr'],
|
| 203 |
+
'elapsed_s': round(time.monotonic()-begun, 1),
|
| 204 |
+
'peak_gpu_gb': round(torch.cuda.max_memory_allocated()/1e9, 2)}
|
| 205 |
+
with (result / 'steps.jsonl').open('a') as log:
|
| 206 |
+
log.write(json.dumps(event) + '\n')
|
| 207 |
+
print(json.dumps(event), flush=True)
|
| 208 |
+
names = [None, 'smoke', 'final', 'epoch', 'periodic', 'runtime', 'signal']
|
| 209 |
+
reason = ('smoke' if args.smoke_steps and current >= args.smoke_steps else
|
| 210 |
+
'final' if current == total else 'epoch' if current % steps_per_epoch == 0 else
|
| 211 |
+
'periodic' if time.monotonic()-last_save >= 1800 else
|
| 212 |
+
'runtime' if time.monotonic()-begun >= 33000 else
|
| 213 |
+
'signal' if STOP else None) if accelerator.is_main_process else None
|
| 214 |
+
code = torch.tensor([names.index(reason)], device=accelerator.device, dtype=torch.int32)
|
| 215 |
+
if world > 1:
|
| 216 |
+
torch.distributed.broadcast(code, src=0)
|
| 217 |
+
reason = names[int(code.item())]
|
| 218 |
+
if reason:
|
| 219 |
+
save(current, reason)
|
| 220 |
+
last_save = time.monotonic()
|
| 221 |
+
if args.smoke_steps and current >= args.smoke_steps:
|
| 222 |
+
return
|
| 223 |
+
if reason in ('runtime', 'signal'):
|
| 224 |
+
sys.exit(75)
|
| 225 |
+
accelerator.wait_for_everyone()
|
| 226 |
+
if accelerator.is_main_process:
|
| 227 |
+
bare = accelerator.unwrap_model(wrapper).base
|
| 228 |
+
adapter = {name.replace('._checkpoint_wrapped_module', ''):
|
| 229 |
+
p.detach().float().cpu().contiguous()
|
| 230 |
+
for name, p in bare.named_parameters() if p.requires_grad}
|
| 231 |
+
assert len(adapter) == len(parameters)
|
| 232 |
+
temp = result / 'adapter.safetensors.partial'
|
| 233 |
+
save_file(adapter, str(temp), metadata={'format': 'hvs-gemini-lora-r64-2ep-v1',
|
| 234 |
+
'contract_sha256': digest})
|
| 235 |
+
os.replace(temp, result / 'adapter.safetensors')
|
| 236 |
+
atomic_json(result / 'DONE.json', {'status': 'COMPLETE', 'step': total,
|
| 237 |
+
'adapter': str(result / 'adapter.safetensors'),
|
| 238 |
+
'contract_sha256': digest})
|
| 239 |
+
print(json.dumps({'event': 'done', 'step': total,
|
| 240 |
+
'adapter': str(result / 'adapter.safetensors')}), flush=True)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
if __name__ == '__main__':
|
| 244 |
+
main()
|
code/infer.py
CHANGED
|
@@ -19,9 +19,9 @@ sys.path.insert(0, str(ROOT / 'code'))
|
|
| 19 |
|
| 20 |
def main() -> None:
|
| 21 |
parser = argparse.ArgumentParser(description=__doc__)
|
| 22 |
-
parser.add_argument('--stage', choices=['default', 'S3-Ref15-FullFT'] +
|
| 23 |
[f'S{i}' for i in range(1, 11)], default='default',
|
| 24 |
-
help='default =
|
| 25 |
parser.add_argument('--prompt', required=True, help='Literal GENERAL/SCRIPT, CAPTION/TRANSCRIPT or TRANSCRIPT text')
|
| 26 |
parser.add_argument('--text', required=True, help='The exact spoken transcript')
|
| 27 |
parser.add_argument('--frames', type=int, required=True, help='Frame budget at 12.5 frames/second')
|
|
@@ -36,7 +36,7 @@ def main() -> None:
|
|
| 36 |
if not 0 < args.reference_max_seconds <= 20:
|
| 37 |
parser.error('--reference-max-seconds must be in (0, 20]')
|
| 38 |
if args.stage in ('default', 'S3-Ref15-FullFT') and args.reference_max_seconds > 15:
|
| 39 |
-
print('Warning:
|
| 40 |
'longer inference references are out of distribution', file=sys.stderr)
|
| 41 |
|
| 42 |
import numpy as np
|
|
@@ -57,9 +57,9 @@ def main() -> None:
|
|
| 57 |
moss_small.load_qwen_backbone = lambda model, log=print: None
|
| 58 |
schema = json.loads((ROOT / 'assets/score_schema.json').read_text())
|
| 59 |
model, config = build_fresh(schema, log=lambda _: None)
|
| 60 |
-
checkpoint_name = '
|
| 61 |
if checkpoint_name == 'S3-Ref15-FullFT' and args.reference_wav is None:
|
| 62 |
-
print('Warning: the Ref15
|
| 63 |
'for reference-free inference compare --stage S3', file=sys.stderr)
|
| 64 |
state = torch.load(ROOT / 'checkpoints' / checkpoint_name / 'model_bf16.pt',
|
| 65 |
map_location='cpu', weights_only=True)
|
|
|
|
| 19 |
|
| 20 |
def main() -> None:
|
| 21 |
parser = argparse.ArgumentParser(description=__doc__)
|
| 22 |
+
parser.add_argument('--stage', choices=['default', 'HVS-Small-2026-10-12', 'S3-Ref15-FullFT'] +
|
| 23 |
[f'S{i}' for i in range(1, 11)], default='default',
|
| 24 |
+
help='default = Humaneness Voice Small (12-10-26); original stages remain available')
|
| 25 |
parser.add_argument('--prompt', required=True, help='Literal GENERAL/SCRIPT, CAPTION/TRANSCRIPT or TRANSCRIPT text')
|
| 26 |
parser.add_argument('--text', required=True, help='The exact spoken transcript')
|
| 27 |
parser.add_argument('--frames', type=int, required=True, help='Frame budget at 12.5 frames/second')
|
|
|
|
| 36 |
if not 0 < args.reference_max_seconds <= 20:
|
| 37 |
parser.error('--reference-max-seconds must be in (0, 20]')
|
| 38 |
if args.stage in ('default', 'S3-Ref15-FullFT') and args.reference_max_seconds > 15:
|
| 39 |
+
print('Warning: these continuations trained with at most about 15 s of reference; '
|
| 40 |
'longer inference references are out of distribution', file=sys.stderr)
|
| 41 |
|
| 42 |
import numpy as np
|
|
|
|
| 57 |
moss_small.load_qwen_backbone = lambda model, log=print: None
|
| 58 |
schema = json.loads((ROOT / 'assets/score_schema.json').read_text())
|
| 59 |
model, config = build_fresh(schema, log=lambda _: None)
|
| 60 |
+
checkpoint_name = 'HVS-Small-2026-10-12' if args.stage == 'default' else args.stage
|
| 61 |
if checkpoint_name == 'S3-Ref15-FullFT' and args.reference_wav is None:
|
| 62 |
+
print('Warning: the Ref15 checkpoint was tuned only with reference audio; '
|
| 63 |
'for reference-free inference compare --stage S3', file=sys.stderr)
|
| 64 |
state = torch.load(ROOT / 'checkpoints' / checkpoint_name / 'model_bf16.pt',
|
| 65 |
map_location='cpu', weights_only=True)
|
docs/HVS_SMALL_2026_10_12.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Humaneness Voice Small (12-10-26)
|
| 2 |
+
|
| 3 |
+
This is the current, adapter-free Humaneness Voice Small default. The complete model state is `checkpoints/HVS-Small-2026-10-12/model_bf16.pt`; `checkpoints/default.json` and `code/infer.py` resolve `default` to it. The previous S3 Ref15 Full FT and the original ten ladder checkpoints are retained, not overwritten. This release is under the repository's CC BY 4.0 license; upstream codec and foundation-model dependencies retain their own terms.
|
| 4 |
+
|
| 5 |
+
## What was trained
|
| 6 |
+
|
| 7 |
+
The lineage is: original S3 ladder checkpoint → full-parameter Ref15 retraining with longer, distinct speaker references → three Gemini acting epochs → real-speech/Gemini full-parameter continuation → two Gemini-only epochs of a Rank-64 LoRA on the last continuation. The final LoRA trained **18,350,080 parameters** in **112 Qwen3 semantic q/k/v/o attention projections** (28 layers × 4 projections). The MOSS-style Talker was not updated by these final two LoRA epochs. The Real+Gemini parent is step 15,238; the LoRA completed step 2,298. Its training contract used 36,754 Gemini rows per epoch, a 5% warmup, peak learning rate 1e-4, and a cosine-like decay to 10% of peak. Every target appeared once per epoch; across two epochs it was conditioned once on another same-voice reference and once on a voice pseudonym. Three GENERAL/SCRIPT views rotated, with the existing 10% full-caption dropout. This checkpoint contains weights only, **not optimizer or RNG state**.
|
| 8 |
+
|
| 9 |
+
The export added each LoRA delta to the Real+Gemini base projection in FP32, cast the result to BF16, removed all adapter modules, then saved a complete ordinary PyTorch state dict. All 112 deltas were finite and nonzero, and the saved export strictly loaded without an adapter. The SHA-256 and exact local source hashes are in `checkpoints/HVS-Small-2026-10-12/metadata.json`. The final BF16 rounding may differ slightly from running the adapter unmerged; the public grid was generated from the unmerged adapter at the same training step.
|
| 10 |
+
|
| 11 |
+
## Listening and use
|
| 12 |
+
|
| 13 |
+
The [public, matched acting grid](https://laion-humaneness-voice-small-four-checkpoint-acting.static.hf.space/) compares original S10, Ref15, Gemini acting, Real+Gemini and this final stage using the same rows, references, prompts, seeds and decoding settings. Its 105 conditions include 35 natural/spontaneous prompt cases and 70 original reference/no-reference cases; audio is on-demand MP3. It is a listening aid, not a blinded preference test. The checkpoint was chosen by user listening; no claim is made that it improves WER, speaker similarity or every emotion across a representative benchmark. Different weights can take different stochastic paths despite matched seeds.
|
| 14 |
+
|
| 15 |
+
Use the model repository's `code/infer.py --stage default` for a reference-conditioned single-clip example. A clean, distinct 5–15 s reference is recommended; do not use the target audio as its own reference. The model was also trained with pseudonym conditioning, but the historical general inference example is not the exact alias-demo pathway. For the exact pseudonym/reference prompt construction used in the grid, inspect the linked demo's manifests and the acting-code release. A `GENERAL:` / `SCRIPT:` instruction with exact spoken `--text` is the principal acting format of the final two-epoch continuation. Longer references, unseen names and out-of-domain languages are not guaranteed.
|
| 16 |
+
|
| 17 |
+
## Reproducibility and limits
|
| 18 |
+
|
| 19 |
+
The LoRA training source and merge recipe are under `code/acting_2026_10_12/`. The exact original training depends on the separate Gemini acting dataset and JUPITER-side mappings described by the source; it is not reproduced from weights alone. The model remains experimental speech synthesis. Evaluate identity rights, consent, linguistic performance and potential deception before use. Do not impersonate people.
|