agentleFS
NVIDIA-NeMoNVIDIA-NeMo/Speech/.claude/skills/train-speech-llm/SKILL.md

train-speech-llm

Skill19k starsChanged 3 days agoApache-2.0View on GitHub

Train a SALM speech LLM from a pretrained LLM and pretrained speech encoders with NeMo speechlm2 and the Automodel backend on a multi-node cluster. Use to set up, launch, monitor, continue, fork, or debug a large multitask training run (ASR, contextual ASR, translation, speech QA, multi-speaker, text retention), to design or change its data blend, or to find out why such a run regressed in quality or throughput. To adapt an already trained SALM checkpoint on a single GPU, use finetune-speech-llm instead.

We split this skill into the rules it gives an agent

10 rules. About 0 are common, 6 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every check in this skill guards against one of them:

    line 27
  2. Very specific:

    LLM: Use a chat checkpoint and its matching prompt_format (nemotron-nano-v3 for Nemotron 3 Nano).

    line 42
  3. Very specific:

    Empty or very short targets: Run an independent ASR model on a random sample drawn from many shards.

    line 95
  4. Very specific:

    Schema: Check that the expected text_field is present, that sample rates match, and that multi-speaker rows respect num_speakers.

    line 98
  5. Distinctive:

    Single-node smoke test: Run 100 to 200 steps with the production container and code, scaled down to one node.

    line 141
  6. Distinctive:

    Dtype check: Read the metadata of the first saved distributed checkpoint.

    line 145
  7. Distinctive:

    Restart check: Stop the smoke run after a save and resume it.

    line 147
  8. Distinctive:

    Exclude bad nodes: Keep a list of nodes to exclude and re-check it before every launch.

    line 162
  9. Distinctive:

    Read the current step from the rank-zero log.

    line 181
  10. Very specific:

    Use MTP speculative decoding.

    line 193

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Training a speech LLM from pretrained components

This skill turns a pretrained LLM and pretrained speech encoders into one SALM model and trains it end to end. The run uses a weighted multitask blend, tens of thousands of steps and tens of nodes. The procedure is written for a hybrid Mamba-attention MoE LLM such as Nemotron 3 Nano, and most of it applies to any backbone.

  • examples/speechlm2/conf/salm_nemotron.yaml is the annotated starting config. Start from it rather than from salm_automodel.yaml, whose portable defaults are slow at this scale.
  • examples/speechlm2/salm_train.py trains the model, and examples/speechlm2/to_hf.py exports a checkpoint for vLLM.
  • .claude/skills/migrate-to-resumable-dataloader/ sets up indexed, resumable data loading, which this skill requires.
  • .claude/skills/debug-training-logs/ finds the root cause when a distributed job dies.

Two terms recur throughout. A source is one leaf entry of the data blend, and a draw is one example sampled from a source.

The governing risk: silent damage

A run of this size costs tens of thousands of GPU-hours. The failures that waste it rarely crash. They surface thousands of steps later as an evaluation regression, and by then the only fix is a fork from an earlier checkpoint. Every check in this skill guards against one of them:

What you noticeActual causeCaught by
Insertions climb and WER drifts upward over thousands of stepsprecision: bf16-true cast the LLM's FP32 tensors (Mamba, router) to BF16Step 4, dtype check
WER swings between neighboring checkpoints, with many empty outputsA "non-speech" source contained real speech labeled with an empty answer, so the model learned to stop earlyStep 2, audits
After a restart, examples are garbled or mismatchedA manifest changed after its index was builtStep 2, immutable data
A source contributes nothing, and no error appearsWrong source type or a missing text_field, so every draw is droppedStep 4, loss-token probe
Long examples never appear in trainingmax_tokens filters examples out; it does not cap themStep 3
A fork resumes from its parent's latest step instead of the requested oneThe fork reused the parent's log directory, and resume_if_exists overrode resume_from_checkpointStep 5

None of these raises an error. Do not launch a run, or resume one after any change, without Steps 2 and 4.

Step 1: Assemble the model

  • LLM. Use a chat checkpoint and its matching prompt_format (nemotron-nano-v3 for Nemotron 3 Nano). The formatter prepends an empty <think></think> to every training answer, so serve with enable_thinking=false.
  • Speech encoders. pe_encoder_path mounts a parallel-expert encoder: an ASR encoder and a diarization encoder side by side, fused per frame, so one model handles both single- and multi-speaker transcription. It replaces the encoder of pretrained_asr, which still supplies the preprocessor. Use the same feature normalization (asr_normalize_type, diar_normalize_type) in training and serving: a mismatch moves WER and produces no other symptom. Without a speaker branch, drop pe_encoder_path and data.multispeaker_cfg.
  • Chunking and packing. With encoder_chunk_size_seconds: 30, the encoders handle long recordings at the cost and memory of short ones. A transformer or parallel-expert encoder also accepts packed input (packed_encoder_sequences), which removes encoder padding. Mirror the encoder's front end and subsampling in audio_token_estimator.
  • Connector. IdentityConnector passes encoder states unchanged to perception's single linear projection into the LLM's hidden size. Its d_model must equal the encoder's output width.
  • Multi-token prediction. Train two MTP layers jointly with the backbone. They cost little during training and speed up serving through speculative decoding. Configure them only under model.mtp. The legacy top-level compute_mtp flag has no effect on training.
  • Checkpoints. Download the HF checkpoints into the cluster's shared cache before the first job, so that hundreds of ranks do not download them at once.

Step 2: Build the data blend

Structure

Each entry in input_cfg has a type that decides how its rows become conversations:

  • lhotse_as_conversation turns speech rows into [system, context, audio] → answer. Its preference_sampling option handles rows that carry several alternative targets: it draws one task per row according to fixed task weights. Set skip_invalid_target: true so that rows lacking the drawn target are skipped rather than trained with an empty answer. preference_sampling has no effect under any other type.
  • group nests a blend whose data is already in conversation form, such as multi-speaker data or text-only chat.

tags overwrite fields of the same name (context, system_prompt) on every row of their source.

Weights

Weight sources by the answer tokens they contribute to the loss, not by hours or rows. Sources differ by more than ten times in supervised tokens per draw. A short contextual-ASR answer and a long multi-speaker transcript are one draw each, so weights based on hours or rows misallocate the gradient.

  1. Measure a_i, the mean answer tokens per draw, for every source. Measure through the real training path: indexed reader, preference sampling, prompt formatter and loss mask. Sample at least 1,000 rows per source and require zero errors.
  2. Choose each source's target share t_i of all answer tokens. Temperature 0.5 is a good default: t_i ∝ m_i^0.5, where m_i is the source's total answer tokens. Give capability-retention data, such as text-only chat, a small explicit share instead.
  3. Set each draw weight to w_i ∝ t_i / a_i, and normalize within every list of siblings.
  4. Measure again whenever a source's content, prompt or task weights change. Weights are valid only for the blend revision they were measured on.

Audits

Audit every source before it joins a blend:

  • Empty or very short targets. Run an independent ASR model on a random sample drawn from many shards. Corpora labeled "non-speech" often contain intelligible speech. Trained with an empty answer, such an example supervises only EOS, and the model learns to stop before transcribing.
  • Schema. Check that the expected text_field is present, that sample rates match, and that multi-speaker rows respect num_speakers.
  • Access. From inside the training container and as the training user, read a sample from across the source, not only its first record. Every path prefix in the manifests must be mounted.

Immutability

An index pack holds the precomputed indexes that let the dataloader resume a source exactly; a pack root is the directory of packs for one blend. Freeze the manifests first, then build their packs into a new pack root for each blend revision. Build into a temporary directory and rename it to its final path when complete. A weight-only change can reuse existing packs. A content change needs a new blend revision, rebuilt packs, re-measured weights and a new config file. Never rebuild a pack that a running job reads.

Step 3: Configure

salm_nemotron.yaml encodes the settings below. They decide speed, stability and whether data goes missing silently.

SettingValueWhy
trainer.precisionbf16-automodelAutomodel owns parameter dtypes, so FP32 tensors stay FP32. Never use bf16-true.
model.optimizerTE FusedAdam with FP32 master weights and BF16 momentsHalves optimizer-state memory and keeps update precision
model.lr_schedulerCosine decay after a linear warmupRetune the peak rate for each backbone and global batch size
data.train_ds.batch_tokens65,536 per rankExact with use_packed_sequence_sampling and measure_total_length
data.train_ds.max_tokensequal to batch_tokensIt is a filter. A lower value silently drops long examples.
data.train_ds.use_bucketingfalsePacking already removes padding, so bucketing only correlates batches
data.train_ds.audio_token_estimatormirrors the encoder front endExact packing needs it to count audio tokens
model.packed_sequencestrueThe LLM sees examples concatenated without padding. Set packed_encoder_sequences too when the encoder supports it.
model.cross_entropy_backendfused_linearNever materializes full-vocabulary logits
model.automodel_backendTE attention and linear layers, grouped-GEMM experts, DeepEP dispatcherTreat as fixed. Never fall back to torch kernels to debug.
trainer.strategy8-way expert parallelism, FSDP across all ranks, activation checkpointing in LLM and perceptionFits 80 GB GPUs at 65,536 tokens per rank
model.train_gatetrueLets the MoE router adapt to speech. aux_loss_coeff applies only when above 0; a value of 0 keeps the checkpoint's coefficient rather than disabling the loss.
data.train_ds.indexed, use_stateful_dataloader, index_pack_rooton, with an immutable pack rootRestores the exact data order after every restart
exp_manager.checkpoint_callback_paramsevery 500 steps, save_top_k: -1Keeps every checkpoint for selection by decoding
exp_manager.max_time_per_runa few minutes under the job's wall-time limitLeaves time for the final distributed checkpoint save

Model-config keys that no code reads are ignored silently, and unknown data keys only log a warning. After adding or renaming a key, find the code that reads it in the exact source tree you package for the job.

Step 4: Preflight

  1. Loss-token probe. Pass 50 to 100 rows from each source through the real dataset, formatter and collator, and print the decoded loss-bearing tokens. Every assistant turn must be supervised and nothing else may be. No source may produce EOS as its only target, unless it is true non-speech that passed the audit.
  2. Single-node smoke test. Run 100 to 200 steps with the production container and code, scaled down to one node. At load, there must be no missing-key or dropped-tensor warnings. During training, require finite loss, packing_efficiency near 1.0 and peak memory with a few GiB of headroom. A single node does not predict multi-node step time.
  3. Dtype check. Read the metadata of the first saved distributed checkpoint. Every tensor that the HF checkpoint stores in FP32 must still be FP32. Repeat the check after any change to precision settings or to Automodel.
  4. Restart check. Stop the smoke run after a save and resume it. The log must show that the optimizer, scheduler, RNG and dataloader states were restored, and training must continue from the saved step.

Point TRITON_CACHE_DIR and TORCHINDUCTOR_CACHE_DIR at shared storage and warm them once with the production image. Otherwise every job spends minutes compiling kernels before its first step.

Step 5: Launch

  • Chain jobs. Submit the run as a chain of jobs, each within the wall-time limit. Each job saves a checkpoint when its timer expires. A job can still exit with an error after that save, for example from a crash during teardown, and an afterok dependency then cancels the rest of the chain. Chain with afterany and confirm each job's save. To stop the run, cancel every queued job; a forgotten afterany job keeps training.
  • Fork from a specific step. resume_if_exists resumes from the -last checkpoint in the log directory and overrides resume_from_checkpoint. To fork from step N, give the fork a new explicit_log_dir and set resume_from_checkpoint to the parent's step=N checkpoint.
  • Exclude bad nodes. Keep a list of nodes to exclude and re-check it before every launch. Typical offenders are a GPU that fails at startup, a thermally throttled GPU that doubles step time, and a faulty network link that triggers NCCL watchdog timeouts.
  • Crashes that repeat at the same step. A crash that recurs at the same step on every attempt depends on that step's data or on the parallel layout. Inspect the batch, then try a different layout before retrying. A crash that does not recur only needs a resume.

Step 6: Monitor

Take the step time, dataloader wait and memory of the first stable hour as the baseline. Expect the first hour after each resume to run slower while caches warm up.

SignalAct when
Step time, rolling medianIt stays above 1.5 times the baseline for more than 30 minutes. With healthy loss, look for a slow node.
Dataloader waitIt grows from tens of milliseconds toward a second. Suspect storage, I/O or too few workers.
Per-rank memory and utilizationOne rank differs from the rest. Suspect a straggler.
Loss and MTP lossA spike appears. Resume from the previous checkpoint and see whether it recurs at the same step; a spike that does not recur is noise.

Read the current step from the rank-zero log. Logger dashboards can lag behind it, and epoch or batch counters do not equal the optimizer step.

Step 7: Evaluate and select checkpoints

Accept a checkpoint before evaluating it. Require two identical file listings taken a few minutes apart, no temporary or zero-byte files, a passing dtype check, and at least one finite training step after the save. Evaluate the numbered step=N checkpoint. The -last checkpoint is overwritten as training continues.

Select checkpoints by decoding, never by loss. Export with to_hf.py, serve with vLLM, and score the full benchmark suite:

  • Use MTP speculative decoding. With vLLM 0.32 or newer, serve with --use-replayssm --mamba-backend flashinfer --mamba-cache-mode none --no-enable-prefix-caching. The older --mamba-cache-mode all setting is no longer available.
  • Serving pads the vocabulary, and its untrained rows can outscore every real token. The NeMo vLLM plugin masks them (speechlm_output_vocab_size); replicate that mask if you serve with another stack. Count <|audio|> tokens and empty outputs in every evaluation.
  • Report insertions, deletions and empty outputs, not only WER. The silent failures above first show up in these counts.
  • Score a text-only benchmark as well, to catch regressions in the LLM's text capability.
  • Compare checkpoints across the whole suite. Neighboring checkpoints fluctuate, so a single benchmark cannot pick a winner.

A model that learned to stop early cannot be repaired at decode time. Forcing a first token recovers the affected utterances but also breaks correct abstention on true non-speech. Remove the bad data and continue training; a later RL stage that rewards complete transcripts can also correct the remaining early stops.

Step 8: Change a run in flight

Fork from an accepted checkpoint into a new log directory and a new config revision. A full-state resume restores the optimizer, scheduler and dataloader states along with the weights.

ChangeHowNever
Learning rate or scheduleNew config, full-state resumeEdit a config that a queued job will read
Source weightsNew weights on the same packs, full-state resumeReweight without measuring answer tokens again
Add a sourceDeclare likely future sources from the start with weight 0.0, then raise the weightInsert a new entry into a running blend: the saved dataloader state no longer matches it
Remove a sourceSet its weight to 0.0Delete its entry, which misaligns the saved dataloader state
Fix a source's contentNew pack root, re-measured weights, new revisionRebuild a pack in place

Make one change per fork, name its parent, and compare the two at matched steps under an identical evaluation setup. A change usually improves its target benchmarks while moving unrelated ones. Adopt it only after scoring the full suite.

Download now (ZIP)

Trending

Less talk lately.about the whole repository

Dec 2025: 0 mentionsJan 2026: 0 mentionsFeb 2026: 0 mentionsMar 2026: 0 mentionsApr 2026: 0 mentionsMay 2026: 1 mentionJun 2026: 1 mentionJul 2026: 6 mentionsAug 2026: 7 mentionsSep 2026: 4 mentionsOct 2026: 9 mentionsNov 2026: 0 mentions
Dec 2025Jun 2026Nov 2026
Mentions a month of the NVIDIA-NeMo/Speech repository that our weekly crawl found. There is no adoption data for this file yet.

Who's talking about NVIDIA-NeMo/Speech

The biggest 5 of 29 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 29

Talked about on GitHub and the web (29 times).

On GitHub · 27

22 more on GitHub

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.