Skip to content

model: Add NVIDIA Canary-1B-v2 to Transformers - #46825

Open
harshaljanjani wants to merge 22 commits into
huggingface:mainfrom
harshaljanjani:add-canary
Open

model: Add NVIDIA Canary-1B-v2 to Transformers#46825
harshaljanjani wants to merge 22 commits into
huggingface:mainfrom
harshaljanjani:add-canary

Conversation

@harshaljanjani

@harshaljanjani harshaljanjani commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

CI

What does this PR do?

→ This PR adds Canary 1B v2 to Transformers!

References:
Model Checkpoints
GitHub Repository
Transformers Converted Checkpoints
Research Paper
Testing Script

Rebased on top of @eustlb's #40756

cc: @ebezzam @Rocketknight1

Multilingual Transcription and Test Results

  • transformers version: 5.13.0.dev0
  • Platform: Linux-6.8.0-1060-gcp-x86_64-with-glibc2.35
  • Python version: 3.11.15
  • huggingface_hub version: 1.20.1
  • safetensors version: 0.8.0
  • accelerate version: 1.14.0
  • DeepSpeed version: not installed
  • PyTorch version (accelerator?): 2.11.0+cu130 (CUDA)
  • GPU type: NVIDIA L4
  • CUDA version: 13.0
2 1

Before submitting

  • This PR adds a new model to Transformers.
  • Did you read the contributor guidelines, specifically the Pull Request section?
  • Did you make sure to update the documentation with your changes? Here are the documentation guidelines, and here are tips on formatting docstrings.
  • Did you add any necessary tests?

@harshaljanjani
harshaljanjani marked this pull request as ready for review June 23, 2026 03:48
@harshaljanjani
harshaljanjani marked this pull request as draft June 23, 2026 04:30
@harshaljanjani
harshaljanjani marked this pull request as ready for review June 23, 2026 04:50
@harshaljanjani

Copy link
Copy Markdown
Contributor Author

Good day @eustlb @ebezzam, just a gentle ping in regard to model reviews!

@harshaljanjani

Copy link
Copy Markdown
Contributor Author

Good day @ebezzam! Just bumping this up for whenever you find the time; thanks!

@ebezzam ebezzam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hi @harshaljanjani thanks for the contribution! Here are some initial comments to start iterating. I didn't have time for an in-depth review, but hopefully these help to get things in the expected direction!

Main idea is to see how existing models define / implement the various parts, and trying to use modular as much as possible!


def __init__(
self,
embed_dim: int,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if super_kwargs could be used here? I learned about this for here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the precedent link; done!



@auto_docstring
class CanaryPreTrainedModel(PreTrainedModel):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(nit but good practice) could we do modular from an existing model? to save some lines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done as well.

Comment thread src/transformers/models/canary/modular_canary.py Outdated
speech-to-text translation.
"""
)
class CanaryForConditionalGeneration(CanaryPreTrainedModel, GenerationMixin):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we do modular from WhisperForConditionalGeneration?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IMO since WhisperForConditionalGeneration inherits from the custom WhisperGenerationMixin with tons of custom methods specific to Whisper (long-form chunking, timestamp extraction for Whisper-specific token IDs, etc., also bias = False), we'd be adding breakage here if we inherited from the class. Moonshine sets precedent as well by being standalone with the same WhisperModel derived shape. Happy to know if I'm missing something here though!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ah yes I forgot the inheritance from WhisperGenerationMixin! Could we do modular from MoonshineForConditionalGeneration then?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done; only the necessary overrides remain now!

init.copy_(module.positional_embeddings, module._build_table())


class CanaryDecoder(CanaryPreTrainedModel):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TO CHECK: there isn't a similar module elsewhere in the lib? for example Whisper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Deduped it with WhisperDecoder, thanks for the nudge!

Comment on lines +247 to +252
class CanaryModel(CanaryPreTrainedModel):
def __init__(self, config: CanaryConfig):
super().__init__(config)
self.encoder = AutoModel.from_config(config.encoder_config)
self.decoder = CanaryDecoder(config)
self.post_init()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could we do modular from WhisperModel?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, done!

audio = make_list_of_audio(audio)
inputs = self.feature_extractor(audio, **output_kwargs["audio_kwargs"])

prompt_tokens = self._build_prompt_tokens(source_lang, target_lang, pnc, timestamps)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we use a chat template for specifying these prompts? See VibeVoice ASR or Qwen 3 ASR. And so we likely need to define an additional method called apply_transcription_request that will call apply_chat_template which then calls this __call__.

The purpose of this __call__ should be simply:

  • apply feature extrator to audio and insert audio tokens
  • tokenize text
  • (optionally) prepare output labels

you will need to add this chat template to your checkpoint in the conversion script

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done following the aforementioned lib patterns, happy to adjust if anything comes up!

Comment thread src/transformers/models/canary/configuration_canary.py
Comment thread docs/source/en/model_doc/canary.md Outdated

Canary reuses the [Fast Conformer](https://huggingface.co/papers/2305.05084) encoder from [Parakeet](./parakeet.md) (loaded through [`ParakeetEncoder`] / [`ParakeetEncoderConfig`]) and pairs it with a Transformer decoder that uses fixed sinusoidal positional embeddings, cross-attention to the encoder outputs and tied input/output embeddings. The task is selected through a decoder prompt prefix built by [`CanaryProcessor`] of the form `<|startofcontext|> <|startoftranscript|> <source_lang> <target_lang> <pnc|nopnc> <timestamp|notimestamp> ...`, where `source_lang == target_lang` selects transcription and otherwise selects translation.

The original implementation can be found in [NVIDIA NeMo](https://github.com/NVIDIA/NeMo). A Transformers-compatible checkpoint is available at [harshaljanjani/canary-1b-v2-hf](https://huggingface.co/harshaljanjani/canary-1b-v2-hf).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NOTE for later: we'll eventually want to transfer a checkpoint to NVIDIA's org


The original implementation can be found in [NVIDIA NeMo](https://github.com/NVIDIA/NeMo). A Transformers-compatible checkpoint is available at [harshaljanjani/canary-1b-v2-hf](https://huggingface.co/harshaljanjani/canary-1b-v2-hf).

## Usage

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

other example usage, we'd like to cover:

  • batch
  • training (simply showing forward/backward)
  • torch compile
  • model features such as timestamp, translation, diariarization

Check out existing models like AudioFlamingo3, VIbevoice ASR, Qwen3 ASR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, fleshed out all the tasks with examples :)

@harshaljanjani

harshaljanjani commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for your time @ebezzam; left replies, addressed the review comments and verified that there are no regressions across the test suite or in generation. I suspect the failures are alluding to cache warmup and will be fixed with this review when we get to it?

image

@harshaljanjani
harshaljanjani requested a review from ebezzam July 9, 2026 10:07
@harshaljanjani

Copy link
Copy Markdown
Contributor Author

Good day @ebezzam! Just checking in to see if there've been any updates :)

@ebezzam ebezzam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@harshaljanjani thank you for addressing my previous comments so quickly and thoroughly! Here are some more to iterate on, and some pointer to prepare for a possible merge with the original model card.


Canary reuses the [Fast Conformer](https://huggingface.co/papers/2305.05084) encoder from [Parakeet](./parakeet.md) (loaded through [`ParakeetEncoder`] / [`ParakeetEncoderConfig`]) and pairs it with a Transformer decoder that uses fixed sinusoidal positional embeddings, cross-attention to the encoder outputs and tied input/output embeddings. The task is selected through a decoder prompt prefix built by [`CanaryProcessor`] of the form `<|startofcontext|> <|startoftranscript|> <|emo:undefined|> <source_lang> <target_lang> <pnc|nopnc> <|noitn|> <|notimestamp|> <|nodiarize|>`, where `source_lang == target_lang` selects transcription and otherwise selects translation.

The original implementation can be found in [NVIDIA NeMo](https://github.com/NVIDIA/NeMo). A model checkpoint is available at [harshaljanjani/canary-1b-v2-hf](https://huggingface.co/harshaljanjani/canary-1b-v2-hf).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TODO: let's check with NVIDIA about adding the Transformers checkpoint to their model card, as NeMo and Transformers files can exist in the same repo!

Can you open a PR on the HF Hub to add the Transformers file and usage? Like this and this as one PR. And if you can mention this PR + indicate that your HF PR is a draft so that they don't merge it just yet. Thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread docs/source/en/model_doc/canary.md
from datasets import load_dataset, Audio
from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq

processor = AutoProcessor.from_pretrained("harshaljanjani/canary-1b-v2-hf")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TODO (when HF model merged): set checkpoint here and elsewhere to nvidia/canary-1b-v2

Comment on lines +167 to +168
def batch_decode(self, *args, **kwargs):
return self.tokenizer.batch_decode(*args, **kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

no need to have this anymore, decode also handles batch inputs!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed.

Comment on lines +170 to +171
def decode(self, *args, **kwargs):
return self.tokenizer.decode(*args, **kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

no need to specify as ProcessorMixin defines it!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed, thanks!

Comment thread src/transformers/models/canary/modular_canary.py Outdated
Comment thread src/transformers/models/canary/modular_canary.py Outdated
def __init__(self, config: CanaryConfig):
super().__init__(config)
self.max_source_positions = None
self.embed_positions = CanarySinusoidalPositionalEmbedding(self.max_target_positions, config.d_model)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how about calling this module CanaryPositionalEmbedding so that modular can take care of renaming the prefix and we don't need this line?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks!

pass


class CanarySinusoidalPositionalEmbedding(nn.Module):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the most similar module:

class SinusoidsPositionEmbedding(nn.Module):

It is concatenated instead of interleaved, which is generally the approach within Transformers (e.g. Llama embedding as well). Could we convert the checkpoint accordingly so that the existing module can be used? For example

@harshaljanjani harshaljanjani Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done! Now subclasses with max_timescale = 10000 ** ((channels - 2) / channels) to make the table bit-exact with NeMo's interleaved table by reordering the hidden dim. Followed the glm4v example to update the converter accordingly. One note: I kept the 1/sqrt(d) scaling inside the module because LayerNorm's eps breaks scale invariance, so scale_embedding=True is not bit-identical. Re-exported and re-uploaded the checkpoint; checked that all baseline generations are byte-identical :)

Comment thread src/transformers/models/canary/processing_canary.py
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution 🤗!

CI Security Gate — automatic approval blocked

This PR was not automatically approved for CI because the security gate failed.

Possible reasons:

  • The PR touches 50 or more files — only PRs with fewer than 50 changed files are automatically approved
  • A changed file is outside the allowed directories (src/, tests/, docs/, utils/), has a disallowed extension (only .py, .txt, .md permitted outside tests/ and docs/), or is not .md/.yml inside docs/
  • A new high-severity security issue was detected in the changed Python files (Bandit check)

See the workflow run for the exact violations.

A maintainer can review and manually approve CI if a finding is a false positive.

@harshaljanjani

Copy link
Copy Markdown
Contributor Author

Thank you for your time @ebezzam, resolved with e4bd5 and also verified that there are no regressions 🤗
(seems like the CI gate blocks fork PRs, needs a manual approve when you get a chance :))

@harshaljanjani
harshaljanjani requested a review from ebezzam July 16, 2026 14:10
@harshaljanjani

Copy link
Copy Markdown
Contributor Author

Good day @ebezzam, just a gentle ping regarding the review :)

@harshaljanjani

Copy link
Copy Markdown
Contributor Author

@ebezzam Maybe?

@ebezzam ebezzam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@harshaljanjani thanks for your patience! as switching between several things 😄

Hope you don't mind I've pushed a few changes:

  • manually write the configuration file since we don't need modular for it and to avoid the duplicate code (thanks for looking into it with Whisper!)
  • some processing refactoring to use common utilities across multiple models (we'll very likely get some comments on that from a core maintainer, but I think they'd be interested to see such utility sharing! cc @vasqu )
  • test refactoring

Could you try the decoder config refactoring I suggested?


@auto_docstring(checkpoint="harshaljanjani/canary-1b-v2-hf")
@strict
class CanaryConfig(PreTrainedConfig):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Alright thanks for trying! So if we don't use modular for config, let's take it out of here and just write the config file manually. I've pushed the change for that!

Comment thread src/transformers/models/canary/processing_canary.py
Comment on lines +59 to +69
d_model: int = 1024
decoder_layers: int = 8
decoder_attention_heads: int = 8
decoder_ffn_dim: int = 4096
decoder_layerdrop: float | int = 0.0
activation_function: str = "relu"
max_target_positions: int = 1024
dropout: float | int = 0.1
attention_dropout: float | int = 0.1
activation_dropout: float | int = 0.1
scale_embedding: bool = False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it's not something you will have seen in other encoder-decoder models like Whisper/Moonshine (but they also didn't have a separate encoder_config), but let's try creating a separate config for the decoder, namely decoder_config and add it to sub_configs. We can call the class CanaryDecoderConfig and no need to register it with auto model so you can have

sub_configs = {
    "encoder_config": AutoConfig,
    "decoder_config": CanaryDecoderConfig,
}

It will improve readability and maybe future encoder-decoder models could benefit from it!

NOTE: as it's something new, we may get comments from other reviewers on how to further change (or go back on things). But I think there could be interest/support for it, as we've been tending towards splitting things in subconfigs in newer models! thanks for your patience on this and help in prototyping this 🙂

@harshaljanjani harshaljanjani Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, please do check if it's what you expected, thanks! Followed dia for the shape. It has model_type = "canary_decoder" so make fix-repo adds it to the tables exactly like dia_decoder

Comment thread src/transformers/models/canary/configuration_canary.py
position_ids = position_ids.unsqueeze(0)

positions = self.embed_positions(position_ids).to(inputs_embeds.dtype)
# unlike Whisper, NeMo normalizes the summed token and positional embeddings with a LayerNorm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so it's clearer

Suggested change
# unlike Whisper, NeMo normalizes the summed token and positional embeddings with a LayerNorm
# NOTE: unlike Whisper, NeMo normalizes the summed token and positional embeddings with a LayerNorm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

past_key_values=past_key_values,
position_ids=position_ids,
)
# unlike Whisper, the encoder outputs have variable length, so padded frames are masked in cross-attention

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so it's clearer

Suggested change
# unlike Whisper, the encoder outputs have variable length, so padded frames are masked in cross-attention
# NOTE: unlike Whisper, the encoder outputs have variable length, so padded frames are masked in cross-attention

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

# Conflicts:
#	src/transformers/models/qwen3_asr/processing_qwen3_asr.py
Comment thread src/transformers/models/canary/processing_canary.py
@harshaljanjani

Copy link
Copy Markdown
Contributor Author

@ebezzam Thank you for your time, pushed the changes and made sure there are no regressions! The four _check_*_for_generate overrides in the tests because the expected shapes now come from the sub-configs not a flat config, added one-line # Overridden ... comments :)

@harshaljanjani
harshaljanjani requested a review from ebezzam July 30, 2026 03:55

@ebezzam ebezzam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@harshaljanjani thanks for creating the decoder config! Some more small comments on the training example and double-checking that we don't need to shift the labels like in recent fixes.

Comment thread docs/source/en/model_doc/canary.md Outdated
Comment on lines +249 to +251
for output_flag in ("output_attentions", "output_hidden_states"):
if kwargs.get(output_flag) is None:
kwargs[output_flag] = getattr(self.config, output_flag, False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there a specific reason you needed this? it may already be handled within the encoder/decoder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread src/transformers/models/canary/modular_canary.py Outdated
Comment on lines +221 to +223
for output_flag in ("output_attentions", "output_hidden_states"):
if kwargs.get(output_flag) is None:
kwargs[output_flag] = getattr(self.config, output_flag, False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there a specific reason you needed this? it may already be handled within the encoder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only when the user enables it through the config, config.output_hidden_states = True returns None without it since _attn_implementation only is set recursively on the sub-configs, if you'd rather have me propagate them centrally I'm happy to do so

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see Whisper or any other model with such logic, so I think we can altogether remove?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed!

Comment on lines +348 to +350
loss = None
if labels is not None:
loss = self.loss_function(logits, labels, self.config.decoder_config.vocab_size)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you double check if any shifting is needed? There were some changes recently to Moonshine, Cohere, and encoder-decoder models in general

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the precedent, fixed the same way as this and test_encoder_decoder_loss_no_double_shift is un-skipped now

Comment thread src/transformers/models/canary/processing_canary.py Outdated
# Conflicts:
#	src/transformers/models/vibevoice_asr/processing_vibevoice_asr.py
@harshaljanjani

Copy link
Copy Markdown
Contributor Author

Thank you for your time @ebezzam, resolved :)

@harshaljanjani
harshaljanjani requested a review from ebezzam August 4, 2026 05:45

@ebezzam ebezzam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@harshaljanjani thanks for the changes! and actually looking at the loss, made me realize Cohere ASR would be better for the modular, as we can cut down on all of the forward methods I think!

I've left comments on how it could be done. It works for CanaryModel and CanaryForConditionalGeneration (tried locally and double-checked with the integration tests). And I've put some pointers for CanaryDecoder: this one will need updating the state dict and re-converting the checkpoint.

Sorry for bringing this up late in the review cycle, but we've very close to concise implementation 👌 Thanks again!

specific head on top.
"""
)
class CanaryModel(WhisperModel):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sorry just noticing this now! but let's instead do modular from Cohere, I think it might even be a straight pass through (and we drop the get_audio_features approach) like this

class CanaryModel(CohereAsrModel):
    def __init__(self, config: CanaryConfig):
        super().__init__(config)
        self.decoder = CanaryDecoder(config.decoder_config)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved.

Comment on lines +287 to +290
class CanaryForConditionalGeneration(MoonshineForConditionalGeneration):
def __init__(self, config: CanaryConfig):
super().__init__(config)
self.proj_out = nn.Linear(config.decoder_config.d_model, config.decoder_config.vocab_size, bias=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this could also be a simpler modular from Cohere with only

class CanaryForConditionalGeneration(CohereAsrForConditionalGeneration):
    def __init__(self, config: CanaryConfig):
        super().__init__(config)
        self.proj_out = nn.Linear(config.decoder_config.d_model, config.decoder_config.vocab_size, bias=True)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved :)

init.copy_(module.positional_embedding, position_embeddings)


class CanaryDecoder(WhisperDecoder):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we may be able to do a simpler modular from CohereAsrDecoder by:

  • in the state dict: renaming layernorm_embedding to embedding_layernorm
  • in the state dict: renaming layer_norm to norm in the state dict
  • in the state dict: renaming embed_scale to pos_emb. Although maybe we still use CanaryPositionalEmbedding you've defined here to make it more explicit that it's a sinusoidal positional embedding -> overwriting self.pos_emb in init
  • (not ideal) overwrite init and add self.proj as an identity layer (it would have been better if that layers weren't in Cohere's decoder as it doesn't depend on the decoder config params but oh well 🤷 )
  • removing config.scale_embedding from the config (anyway it's false which makes self.embed_scale=1)

with such changes, we might be able to avoid defining the forward method!

@harshaljanjani harshaljanjani Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for taking the time to detail this out, it helped a ton! All points applied, the checkpoint re-converted and uploaded to the Hub, and the NVIDIA Hub PR has been updated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thanks for the changes! the modular is much cleaner and leaner now 👏

@harshaljanjani
harshaljanjani requested a review from ebezzam August 4, 2026 15:00

@ebezzam ebezzam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@harshaljanjani thanks for the changes, the modular is looking much, much nicer now 🙂

@vasqu could we get your thoughts on some of the decisions we made for this modular?


model_type = "canary"
keys_to_ignore_at_inference = ["past_key_values"]
sub_configs = {"encoder_config": AutoConfig, "decoder_config": CanaryDecoderConfig}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what do you think about making a separate decoder sub config? instead of having everything in the main config like this


def __init__(self, config: CanaryDecoderConfig):
super().__init__(config)
self.pos_emb = CanaryPositionalEmbedding(config.max_position_embeddings, config.hidden_size)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how about explicitly create a sinusoidal embedding rather than a generic embedding layer like in the Cohere here

def __init__(self, config: CanaryDecoderConfig):
super().__init__(config)
self.pos_emb = CanaryPositionalEmbedding(config.max_position_embeddings, config.hidden_size)
self.proj = nn.Identity()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this ok? need to do this unideal layer for modular, but ideally that layer should have been in the encoder or between the encoder and decoder I feel?

class CanaryModel(CohereAsrModel):
def __init__(self, config: CanaryConfig):
super().__init__(config)
self.decoder = CanaryDecoder(config.decoder_config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

since we make a separate subconfig for the decoder, otherwise would be straight modular

class CanaryForConditionalGeneration(CohereAsrForConditionalGeneration):
def __init__(self, config: CanaryConfig):
super().__init__(config)
self.proj_out = nn.Linear(config.decoder_config.hidden_size, config.decoder_config.vocab_size, bias=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

since we make a separate subconfig for the decoder, otherwise would be straight modular

@ebezzam

ebezzam commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

run-slow: canary

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Workflow Run ⚙️

This comment contains run-slow, running the specified jobs:

models: ["models/canary"]
quantizations: []

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

CI Results

Workflow Run ⚙️

Commit Info

Context Commit Description
RUN a7032477 workflow commit (merge commit)
PR 26d6acbb branch commit (from PR)
main 92d02b50 base commit (on main)

✅ No failing test specific to this PR 🎉 👏 !

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: audioflamingo3, auto, canary, glmasr, qwen3_asr, vibevoice_asr

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 30944486203:2
Result: failure | Jobs: 16 | Tests: 180,319 | Failures: 0 | Duration: 13h 48m

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.

5 participants