Skip to content

Updates for Sortformer training - #16227

Open
ipmedenn wants to merge 17 commits into
NVIDIA-NeMo:mainfrom
ipmedenn:update-sortformer-training
Open

Updates for Sortformer training#16227
ipmedenn wants to merge 17 commits into
NVIDIA-NeMo:mainfrom
ipmedenn:update-sortformer-training

Conversation

@ipmedenn

@ipmedenn ipmedenn commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Important

The Update branch button must only be pressed in very rare occassions.
An outdated branch is never blocking the merge of a PR.
Please reach out to the automation team before pressing that button.

What does this PR do ?

This PR extends Sortformer training with the functionality required for offline and streaming 8-speaker diarization. It adds new training configurations, subsegment-based data sampling, logits-based and auxiliary losses, block-causal tail augmentation, speaker-counting metrics, and improvements to checkpoint handling and long-recording inference.

The existing probability-based BCELoss remains supported, and causal-tail augmentation is disabled by default.

Collection: ASR, Speaker Tasks, Common

Changelog

  • Add first-stage offline and second-stage streaming 8-speaker Sortformer training configurations with 80 ms and 10 ms output frames, respectively.
  • Add deterministic initialization and sampling based on the configured training seed.
  • Add the init_from_nemo_model configuration option for warm-starting selected model submodules from a .nemo checkpoint.
  • Add checkpoint averaging after training and repair scheduler max_steps when resuming.
  • Update the end-to-end diarization dataset with:
    • a five-field output containing audio, lengths, targets, and speaker identities;
    • subsegment-based training using one or two source chunks;
    • speaker-count-aware chunk sampling;
    • full-duration negative samples when no suitable speech subsegment is available;
    • optional manifest path validation.
  • Add an oversampling distributed sampler that produces a fixed number of samples per rank and supports resuming within an epoch.
  • Add BCEWithLogitsLoss and clean up the existing probability-based BCELoss.
  • Allow Sortformer forward methods to return speaker and activity logits when required for training while keeping probabilities as the default output.
  • Add optional activity and phantom-speaker auxiliary losses.
  • Preserve warm-start compatibility with checkpoints that do not contain the optional activity head.
  • Add training and validation metrics for speaker-count accuracy and mean absolute error.
  • Add training-only block-causal tail augmentation to TransformerEncoder: the prefix remains bidirectional, while each tail block attends to the prefix and preceding blocks but not future blocks. The augmentation is disabled by default, and validation and inference continue to use full attention.
  • Optimize max_batch_dur feature extraction by processing duration-bounded sub-batches without repeated tensor concatenation, and warn when a single recording exceeds the limit.
  • Add loading of registered Sortformer models from Hugging Face to e2e_diarize_speech.py.
  • Add and update unit tests for the dataset, sampler, losses, model forward paths, auxiliary losses, attention masks, checkpoint averaging, metrics, and feature extraction.

Usage

The offline configuration is intended for the first training stage:

cd examples/speaker_tasks/diarization/neural_diarizer

python ./sortformer_diar_train.py \
    --config-path='../conf/neural_diarizer' \
    --config-name='sortformer_offline_8spk.yaml' \
    model.train_ds.manifest_filepath=/path/to/train_manifest.json \
    model.validation_ds.manifest_filepath=/path/to/validation_manifest.json

The streaming configuration is intended for the second training stage. The first-stage model can be specified through init_from_nemo_model in the configuration:

init_from_nemo_model:
  stage1:
    path: /path/to/SortformerDiarizer_offline_8spk.nemo
    include: ["encoder", "sortformer_modules"]
python ./streaming_sortformer_diar_train.py \
    --config-path='../conf/neural_diarizer' \
    --config-name='sortformer_streaming_8spk.yaml' \
    model.train_ds.manifest_filepath=/path/to/train_manifest.json \
    model.validation_ds.manifest_filepath=/path/to/validation_manifest.json

e2e_diarize_speech.py can load either a local checkpoint through model_path or a registered Hugging Face model through pretrained_name:

python ./e2e_diarize_speech.py \
    pretrained_name='nvidia/diar_streaming_sortformer_4spk-v2.1' \
    batch_size=1 \
    dataset_manifest=/path/to/diarization_manifest.json

GitHub Actions CI

The Jenkins CI system has been replaced by GitHub Actions self-hosted runners.

Trusted PRs run automatically through copy-pr-bot. For an untrusted PR, a maintainer can trigger CI by commenting
/ok to test <head-sha>; repeat this after a new push if the PR remains untrusted.

Before your PR is "Ready for review"

Pre checks:

  • Make sure you read and followed Contributor guidelines
  • Did you write any new necessary tests?
  • Did you add or update any necessary documentation?
  • Does the PR affect components that are optional to install? (Ex: Numba, Pynini, Apex etc)
    • Reviewer: Does the PR have correct import guards for all optional libraries?

PR Type:

  • New Feature
  • Bugfix
  • Documentation

If you haven't finished some of the above items you can still open "Draft" PR.

Who can review?

Anyone in the community is free to review the PR once the checks have passed.
Contributor guidelines contains specific people who can review PRs to various areas.

Additional Information

Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
@copy-pr-bot

copy-pr-bot Bot commented Sep 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Comment thread nemo/collections/asr/models/sortformer_diar_models.py Fixed
Comment thread nemo/collections/asr/models/sortformer_diar_models.py Fixed
Comment thread nemo/collections/asr/data/audio_to_diar_label.py Fixed
Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>
@ipmedenn
ipmedenn marked this pull request as ready for review September 7, 2026 16:14

@nithinraok nithinraok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about doc updates?

Comment thread examples/speaker_tasks/diarization/neural_diarizer/e2e_diarize_speech.py Outdated
@ipmedenn

ipmedenn commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

What about doc updates?

User-facing documentation for the new training workflow is planned as a follow-up PR. This PR includes docstrings and inline descriptions for the new configuration parameters, and I have noted the documentation follow-up in the PR description.

Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>

@tango4j tango4j left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes to approve

return preds
return torch.sigmoid(self.forward_speaker_logits(hidden_out))

def forward_activity_logits(self, hidden_out: torch.Tensor) -> Optional[torch.Tensor]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add more description on the purpose of this function as one of the loss calculation heads

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in d2ee5e0

Comment thread examples/speaker_tasks/diarization/neural_diarizer/e2e_diarize_speech.py Outdated

def _get_aux_train_evaluations(self, preds, targets, target_lens) -> dict:
@staticmethod
def _speaker_count_metrics(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider moving this to a stand-alone file in metrics folder.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in d2ee5e0

@tango4j tango4j left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make the changes in loss folder's aux_diarization_loss.py

import torch.nn.functional as F


def activity_loss(

@tango4j tango4j Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This activity_loss should be a class method just like other loss *.py file.
This is because "loss" is always expected to be a class type that has type definitions and variable initializations.

Please follow the convention of: class name "ActivityLoss"
input_types
output_types
init
forward

It should be just like:
https://github.com/NVIDIA-NeMo/Speech/blob/main/nemo/collections/asr/losses/bce_loss.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loss class should be referred as follows:

=========
activity_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.ActivityLoss
reduction: mean

return loss


def phantom_loss(

@tango4j tango4j Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same as activity_loss.
loss should not be a function.
In NeMo Speech, it is expected to be a class that has forward method.

Please follow the convention of having: class name "PhantomLoss"
input_types
output_types
init
forward

It should be just like:
https://github.com/NVIDIA-NeMo/Speech/blob/main/nemo/collections/asr/losses/bce_loss.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loss class should be referred as follows:

=========
phantom_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.PhantomLoss
reduction: mean

loss:
_target_: nemo.collections.asr.losses.bce_loss.BCEWithLogitsLoss
reduction: mean

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here, the following should be added:

activity_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.ActivityLoss
reduction: mean

phantom_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.PhantomLoss
reduction: mean

@tango4j tango4j left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please construct the loss class as other loss files (just like bce_loss)

loss:
_target_: nemo.collections.asr.losses.bce_loss.BCEWithLogitsLoss
reduction: mean

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here, the following should be added:

activity_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.ActivityLoss
reduction: mean

phantom_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.PhantomLoss
reduction: mean

return loss


def phantom_loss(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loss class should be referred as follows:

=========
phantom_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.PhantomLoss
reduction: mean

import torch.nn.functional as F


def activity_loss(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loss class should be referred as follows:

=========
activity_loss:
target: nemo.collections.asr.losses.aux_diarization_loss.ActivityLoss
reduction: mean

Signed-off-by: ipmedenn <65592416+ipmedenn@users.noreply.github.com>

@nithinraok nithinraok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tango4j tango4j left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are made exactly as requested.

@tango4j

tango4j commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

/ok to test f497938

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants