Transformers documentation

Nemotron 3.5 ASR

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

このモデルは2026年6月27日に Hugging Face Transformers に貢献されました。

Nemotron 3.5 ASR

概要

Nemotron 3.5 ASR は、NVIDIA による 6 億パラメータの多言語音声認識モデルです。低レイテンシのストリーミング処理と高スループットのバッチ処理の両方で高品質な文字起こしを実現するように設計されており、句読点と大文字化をネイティブでサポートしています。ストリーミング処理では、80ms、160ms、560ms、1120ms の設定可能なチャンクサイズを提供しており、ユーザーはアプリケーションの要件に応じて、レイテンシと精度のトレードオフを調整できます。この機能の中心となるのは、キャッシュ対応の FastConformer-RNNT アーキテクチャです。従来のバッファリング型ストリーミングでは、重複する音声ウィンドウを繰り返し再処理しますが、このモデルは新しい入ってくるチャンクのみを処理し、以前のチャンクからキャッシュされたエンコーダのコンテキストを再利用します。これにより、冗長な計算が排除され、効率が大幅に向上し、精度を犠牲にすることなくエンドツーエンドの遅延が最小限に抑えられるため、リアルタイムの文字起こしワークロードに最適です。

使用方法

オフライン文字起こし

パイプライン
AutoModel
from transformers import pipeline

pipe = pipeline(
    "automatic-speech-recognition",
    model="nvidia/nemotron-3.5-asr-streaming-0.6b",
)
out = pipe("https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/bcn_weather.mp3")
print(out)

パイプラインはデフォルトの言語プロンプト(インデックス 0、en-US)を使用します。明示的な言語条件付けや自動検出を行う場合は、プロセッサの language 引数を渡します(AutoModel タブを参照)。

ストリーミング文字起こし

これは実験的な機能であり、APIは変更される可能性があります。

リアルタイム文字起こしでは、音声は次のルールに従ってチャンクに分割されます。

from threading import Thread
from transformers import AutoModelForRNNT, AutoProcessor, TextIteratorStreamer
from transformers.audio_utils import load_audio

model_id = "nvidia/nemotron-3.5-asr-streaming-0.6b"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForRNNT.from_pretrained(model_id, device_map="auto")

processor.set_num_lookahead_tokens(6)
print(f"Streaming latency: {processor.streaming_latency_ms} ms")

# The language prompt rides along on every chunk; use a locale (e.g. "de-DE") or "auto".
language = "en-US"

sampling_rate = processor.feature_extractor.sampling_rate
audio = load_audio(
    "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3",
    sampling_rate=sampling_rate,
)

first_chunk_inputs = processor(
    audio[: processor.num_samples_first_audio_chunk],
    sampling_rate=sampling_rate,
    is_streaming=True,
    is_first_audio_chunk=True,
    language=language,
    return_tensors="pt",
)
first_chunk_inputs = first_chunk_inputs.to(model.device, dtype=model.dtype)


def input_features_generator():
    yield first_chunk_inputs.input_features[:, : processor.num_mel_frames_first_audio_chunk, :]

    mel_frame_idx = processor.num_mel_frames_first_audio_chunk
    hop_length = processor.feature_extractor.hop_length
    n_fft = processor.feature_extractor.n_fft

    start_idx = mel_frame_idx * hop_length - n_fft // 2
    while (end_idx := start_idx + processor.num_samples_per_audio_chunk) < audio.shape[0]:
        inputs = processor(
            audio[start_idx:end_idx],
            sampling_rate=sampling_rate,
            is_streaming=True,
            is_first_audio_chunk=False,
            language=language,
            return_tensors="pt",
        )
        inputs = inputs.to(model.device, dtype=model.dtype)
        yield inputs.input_features

        mel_frame_idx += processor.num_mel_frames_per_audio_chunk
        start_idx = mel_frame_idx * hop_length - n_fft // 2


streamer = TextIteratorStreamer(processor.tokenizer, skip_special_tokens=True)
generate_kwargs = {
    **first_chunk_inputs,
    "input_features": input_features_generator(),
    "streamer": streamer,
}
thread = Thread(target=model.generate, kwargs=generate_kwargs)
thread.start()

# Iterate over the streamer to get text chunks as they are generated
print("Model output (streaming):", end=" ", flush=True)
for text_chunk in streamer:
    print(text_chunk, end="", flush=True)
thread.join()

ストリーミングのレイテンシ

レイテンシはnum_lookahead_tokensによって設定されます。これは、各チャンクが出力される前に待機する右側のアテンションコンテキスト(サブサンプリングされたエンコーダーフレームにおけるルックアヘッド)です。値を大きくすると、各チャンクがより多くの将来のオーディオを参照できるようになり、レイテンシが高くなる代わりに精度が向上します。サポートされているトレードオフを確認し、1つを選択して、結果として得られるレイテンシを読み取ります。

from transformers import AutoProcessor

processor = AutoProcessor.from_pretrained("nvidia/nemotron-3.5-asr-streaming-0.6b")

# Each supported `num_lookahead_tokens` mapped to its streaming latency in milliseconds:
print(processor.supported_streaming_latencies_ms)
# {3: 320, 0: 80, 6: 560, 13: 1120}

# Select a right attention context (this also re-derives the streaming chunk sizes used above):
processor.set_num_lookahead_tokens(6)

# Latency of the current selection:
print(processor.streaming_latency_ms)
# 560

set_num_lookahead_tokens はプロセッサが出力するチャンクのサイズを決定し、対応する num_lookahead_tokens は generate に到達する必要があります(上記のスニペットでは、num_lookahead_tokens を保持する **inputs/**first_chunk_inputs を経由して渡されます)。ストリーミング generate では、これが省略されている場合、例外が発生します。

Nemotron3_5AsrConfig

class transformers.Nemotron3_5AsrConfig

< >

( transformers_version: str | None = Nonearchitectures: list[str] | None = Noneoutput_hidden_states: bool | None = Falsereturn_dict: bool | None = Truedtype: str | torch.dtype | None = Nonechunk_size_feed_forward: int = 0id2label: dict[int, str] | dict[str, str] | None = Nonelabel2id: dict[str, int] | dict[str, str] | None = Noneproblem_type: Literal['regression', 'single_label_classification', 'multi_label_classification'] | None = Noneis_encoder_decoder: bool = Truevocab_size: int = 13088decoder_hidden_size: int = 640num_decoder_layers: int = 2hidden_act: str = 'relu'max_symbols_per_step: int = 10encoder_config: dict | transformers.configuration_utils.PreTrainedConfig | None = Nonepad_token_id: int = 0blank_token_id: int = 13087num_prompts: int = 128prompt_intermediate_size: int = 2048default_prompt_id: int = 101 )

パラメータ

  • is_encoder_decoder (bool、任意、デフォルトはTrue) — モデルがエンコーダー/デコーダーとして使用されるかどうかを示します。
  • vocab_size (int, 任意、デフォルト値は 13088) — 結合ネットワークの出力の語彙サイズ(空白トークンを含む)。
  • decoder_hidden_size (int、任意、デフォルト値は 640) — LSTM 予測ネットワークの隠れ層サイズ(NeMo の pred_hidden)。
  • num_decoder_layers (int, 省略可能、デフォルト値は 2) — 予測ネットワークにおける LSTM レイヤーの数。
  • hidden_act (str、任意、デフォルトは "relu") — ジョイントネットワークにおける活性化関数。
  • max_symbols_per_step (int、任意、デフォルト値は 10)— 貪欲デコーディング中に、エンコーダの各タイムステップで出力される空白以外のシンボルの最大数。
  • encoder_config (Union[dict, NemotronAsrStreamingEncoderConfig]、省略可) — エンコーダーの設定オブジェクトまたは辞書です。エンコーダーはNemotronAsrStreamingのものと同一であるため、NemotronAsrStreamingEncoderConfigをそのまま再利用します。
  • pad_token_id (int、任意、デフォルトは 0) — 語彙内でパディングに使用されるトークン ID。
  • blank_token_id (int、任意、デフォルト値は 13087) — RNN-T 復号用の空白トークン ID。
  • num_prompts (int、任意、デフォルト値は 128)— 言語プロンプトのスロット数です。ターゲット言語は、このサイズを持つ one-hot ベクトルとしてエンコードされ、エンコーダーの時間軸に沿ってブロードキャストされた後、prompt_kernel 融合 MLP への入力としてエンコーダー出力と連結されます。
  • prompt_intermediate_size (int、任意、デフォルトは 2048) — prompt_kernel の融合 MLP(Linear(hidden + num_prompts -> intermediate) -> ReLU -> Linear(intermediate -> hidden))の隠れ層サイズです。
  • default_prompt_id (int、任意、デフォルト値は 101) — prompt_ids が指定されていない場合に、モデルを条件付けするために使用されるプロンプトのインデックスです。デフォルトでは、auto の言語検出スロット(NeMo のプロンプト辞書におけるインデックス 101)が使用され、target_lang="auto" をデフォルトとする NeMo の動作と一致します。これにより、モデルが言語を自動検出し、<xx-XX> タグを出力します。

これは Nemotron3_5AsrForRNNT の設定を保存するための設定クラスです。指定された引数に基づいて Nemotron3 5 Asr モデルをインスタンス化し、モデルのアーキテクチャを定義するために使用されます。デフォルト値で設定をインスタンス化すると、nvidia/nemotron-3.5-asr-streaming-0.6b と同様の設定が得られます。

設定オブジェクトはPreTrainedConfigを継承し、モデルの出力を制御するために使用できます。詳細については、PreTrainedConfigのドキュメントを参照してください。

例:

>>> from transformers import Nemotron3_5AsrForRNNT, Nemotron3_5AsrConfig

>>> configuration = Nemotron3_5AsrConfig()
>>> model = Nemotron3_5AsrForRNNT(configuration)
>>> configuration = model.config

Nemotron3_5AsrProcessor

class transformers.Nemotron3_5AsrProcessor

< >

( feature_extractortokenizerblank_token = '<blank>'decoder_type = Nonesupported_num_lookahead_tokens = Nonedefault_num_lookahead_tokens = Noneprompt_dictionary = Nonenum_prompts = 128 )

パラメータ

  • feature_extractor (NemotronAsrStreamingFeatureExtractor) — フィーチャーエクストラクターは必須の入力です。
  • tokenizer (ParakeetTokenizer) — トークナイザーは必須の入力です。
  • blank_token (str、任意、デフォルトは "<blank>") — RNN-T 復号用の空白トークン。
  • decoder_type (str, 任意) — デコーディング/タイムスタンプ出力モード(例: "ctc"、"rnnt"、"tdt")。None の場合、後方互換性のためにデコーダータイプが自動的に推論されます。
  • supported_num_lookahead_tokens (list[int]、省略可) — サポートされている右方向アテンションコンテキスト(サブサンプリングされたエンコーダーフレームにおけるルックアヘッド)です。NemotronAsrStreamingEncoderConfig.supported_num_lookahead_tokensを反映しています。streaming_latency_msの検証および、返されるnum_lookahead_tokensの導出に使用されます。
  • default_num_lookahead_tokens (int, 省略可) — streaming_latency_ms が指定されていない場合に使用される右コンテキストです。デフォルトでは、supported_num_lookahead_tokens の最初のエントリが使用されます。
  • prompt_dictionary (dict[str, int]、省略可) — ターゲット言語の文字列(例: "en-US"、"de-DE"、単なるコード "de"、または "auto")からそのプロンプトインデックスへのマッピングです。デフォルトでは、NeMo チェックポイントのプロンプト辞書が使用されます。
  • num_prompts (int、任意、デフォルトは 128)— 言語プロンプトのスロット数(ワンホットプロンプトベクトルのサイズ)。Nemotron3_5AsrConfig.num_prompts と同じです。

Nemotron3_5AsrProcessor を構築します。このクラスは、feature extractor と tokenizer を単一の processor にラップします。

Nemotron3_5AsrProcessor は、NemotronAsrStreamingFeatureExtractor と ParakeetTokenizer のすべての機能を提供します。詳細については、~NemotronAsrStreamingFeatureExtractor および ~ParakeetTokenizer を参照してください。

decode

< >

( *argsdurations = None**kwargs )

decode() に引数を転送し、NeMo ライブラリと同様に、durations が提供されている場合はトークンレベルのタイムスタンプを後処理します。

set_num_lookahead_tokens

< >

( num_lookahead_tokens: int )

ストリーミング処理に使用する適切なアテンションコンテキスト(サブサンプリングされたエンコーダーフレーム内のlookahead)を選択します。

default_num_lookahead_tokensを設定します。これにより、すべての派生ストリーミングプロパティ(num_mel_frames_first_audio_chunk、num_mel_frames_per_audio_chunk、num_samples_first_audio_chunk、num_samples_per_audio_chunk)が新しい値から再導出されます。num_lookahead_tokensは、supported_num_lookahead_tokensのいずれかの値である必要があります。

ここで生成されるチャンクサイズと一致するように、フォワードで使用するアテンションの右コンテキストが正しく機能するよう、model.generate にも同じ num_lookahead_tokens を渡してください。そうしないと、ストリーミング generate が例外を送出します。

Nemotron3_5AsrRNNTOutput

class transformers.Nemotron3_5AsrRNNTOutput

< >

( last_hidden_state: typing.Optional[torch.FloatTensor] = Nonepooler_output: typing.Optional[torch.FloatTensor] = Nonehidden_states: tuple[torch.FloatTensor, ...] | None = Noneattentions: tuple[torch.FloatTensor, ...] | None = Noneloss: typing.Optional[torch.FloatTensor] = Nonelogits: typing.Optional[torch.FloatTensor] = Nonedecoder_cache: transformers.models.nemotron3_5_asr.generation_nemotron3_5_asr.Nemotron3_5AsrRNNTDecoderCache | None = Noneencoder_past_key_values: transformers.cache_utils.Cache | None = Nonepadding_cache: NemotronAsrStreamingEncoderCausalConvPaddingCache | None = None )

パラメータ

  • last_hidden_state (torch.FloatTensor、形状 (batch_size, sequence_length, hidden_size)) — モデルの最終層の出力における隠れ状態のシーケンスです。
  • pooler_output (torch.FloatTensor、shape (batch_size, hidden_size)) — シーケンスの最初のトークン(分類トークン)の最終層の隠れ状態を、補助的な事前学習タスクに使用される層でさらに処理した後の出力です。例えば、BERT 系のモデルでは、線形層と tanh 活性化関数を経て処理された分類トークンを返します。この線形層の重みは、事前学習中の次文予測(分類)目的関数から学習されます。
  • hidden_states (tuple(torch.FloatTensor), 省略可能, が返されるのは output_hidden_states=True が渡された場合、または config.output_hidden_states=True) — のタプル torch.FloatTensor ((埋め込み層を持つモデルの場合は埋め込みの出力用 1 つ + 各層の出力用 1 つずつ)で、形状は (batch_size, sequence_length, hidden_size).

    各層の出力におけるモデルのHidden-states、および任意の初期埋め込み出力。

  • attentions (tuple(torch.FloatTensor), 省略可能, が返されるのは output_attentions=True が渡された場合、または config.output_attentions=True) — のタプル torch.FloatTensor (の形状を持つ、各レイヤーごとに1つの要素を含む (batch_size, num_heads, sequence_length, sequence_length).

    アテンションのsoftmax後のアテンション重み。自己アテンションヘッド内で加重平均を計算するために使用されます。

  • loss (torch.FloatTensor、任意、labels が指定された場合のみ返されます) — RNN-T transducer の損失。
  • logits (torch.FloatTensor、shape (batch_size, encoder_length, decoder_length, vocab_size + 1)) — すべてのエンコーダ/デコーダフレームペアについて、語彙および空白シンボルに対するジョイントネットワークのスコア。
  • decoder_cache (Nemotron3_5AsrRNNTDecoderCache、optional) — 更新されたデコーダーLSTMキャッシュ。空白の予測時に再利用され、LSTMステップをスキップします。
  • encoder_past_key_values (Cache, 任意) — use_cache=True を指定して音声エンコードを行う場合(キャッシュ対応ストリーミング)に返される、更新されたエンコーダーアテンションの K/V スライディングウィンドウキャッシュです。次のチャンクの forward に渡してください。
  • padding_cache (NemotronAsrStreamingEncoderCausalConvPaddingCache、optional) — use_cache=True でオーディオをエンコードする際に返される、更新された統合ストリーミング conv キャッシュ(サブサンプリング Conv2d + conformer depthwise Conv1d)です。次のチャンクの forward に渡してください。

Nemotron3_5AsrForRNNT

class transformers.Nemotron3_5AsrForRNNT

< >

( config: Nemotron3_5AsrConfig )

パラメータ

  • config (Nemotron3_5AsrConfig) — モデルのすべてのパラメータを含むモデル設定クラスです。設定ファイルで初期化しても、モデルに関連する重みは読み込まれず、設定のみが読み込まれます。モデルの重みをロードするには、from_pretrained() メソッドを参照してください。

RNN-T(Recurrent Neural Network Transducer)ヘッドと言語IDプロンプト条件付けを備えたNemotron3_5Asrエンコーダー。

このモデルは PreTrainedModel を継承しています。すべてのモデルに実装されている汎用的なメソッド(ダウンロードや保存、入力埋め込みのリサイズ、ヘッドのプルーニングなど)については、スーパークラスのドキュメントを参照してください。

このモデルは、PyTorchのtorch.nn.Moduleのサブクラスでもあります。 通常のPyTorch Moduleとして使用し、一般的な使用方法や動作に関する詳細については、PyTorchのドキュメントを参照してください。

forward

< >

( input_features: typing.Optional[torch.Tensor] = Noneattention_mask: typing.Optional[torch.Tensor] = Nonedecoder_input_ids: typing.Optional[torch.LongTensor] = Nonedecoder_cache: transformers.models.nemotron3_5_asr.generation_nemotron3_5_asr.Nemotron3_5AsrRNNTDecoderCache | None = Noneuse_decoder_cache: bool | None = Noneencoder_outputs: transformers.modeling_outputs.BaseModelOutputWithPooling | None = Nonelabels: typing.Optional[torch.Tensor] = Nonenum_lookahead_tokens: int | None = Noneprompt_ids: typing.Optional[torch.LongTensor] = None**kwargs: *<class 'transformers.utils.generic.TransformersKwargs'> ) → Nemotron3_5AsrRNNTOutput or tuple(torch.FloatTensor)

パラメータ

  • input_features (torch.Tensor、形状 (batch_size, sequence_length, feature_dim)、任意) — 入力音声特徴に対応するテンソルです。音声特徴は feature_extractor_class を使用して取得できます。詳細は feature_extractor_class.__call__ を参照してください(processor_class は音声の処理に feature_extractor_class を使用します)。
  • attention_mask (torch.Tensor の形状 (batch_size, sequence_length), 、任意) — パディングトークンのインデックスに対するアテンションを回避するためのマスク。 [0, 1]:
    • マスクされていないトークンに対して1
    • マスクされたトークンに対しては 0 です。

    アテンションマスクとは?

  • decoder_input_ids (torch.LongTensor、形状 (batch_size, 1)、任意) — 単一ステップ推論用のデコーダー入力トークンID。
  • decoder_cache (Nemotron3_5AsrRNNTDecoderCache、optional) — デコーダのLSTMキャッシュです。空白の予測時に再利用して、LSTMステップをスキップします。
  • use_decoder_cache (bool, 省略可) — デコーダキャッシュが提供されていない場合に、デコーダキャッシュを割り当てて使用するかどうかを指定します。
  • encoder_outputs (tuple(torch.FloatTensor)、任意) — 事前計算されたエンコーダーの出力(last_hidden_state、pooler_output など)。
  • labels (torch.Tensor、shape (batch_size, sequence_length)、任意) — マスク言語モデリング損失を計算するためのラベルです。インデックスは [0, ..., config.vocab_size] の範囲内であるか、-100 である必要があります(input_ids のドキュメント文字列を参照)。インデックスが -100 に設定されたトークンは無視(マスク)され、損失は [0, ..., config.vocab_size] の範囲内のラベルを持つトークンに対してのみ計算されます。
  • num_lookahead_tokens (int, 任意) — エンコーダーに転送される右側のアテンションコンテキスト(サブサンプリングされたエンコーダーフレームにおけるルックアヘッド)。 デフォルト値は config.encoder_config.default_num_lookahead_tokens です。
  • prompt_ids (torch.LongTensor、形状 (batch_size,)、任意) — 言語 ID 条件付け用の言語プロンプトインデックスです。プロセッサが language から生成します。prompt_projector が消費するブロードキャストされた one-hot 形式に変換されます。

戻り値

Nemotron3_5AsrRNNTOutput または tuple(torch.FloatTensor)

None や入力に応じて、さまざまな要素で構成される Nemotron3_5AsrRNNTOutput または torch.FloatTensor のタプル(return_dict=False が渡された場合、または config.return_dict=False の場合)。

Nemotron3_5AsrForRNNTのforwardメソッドは、__call__特殊メソッドをオーバーライドします。

この関数内でフォワードパスの手順を定義する必要がありますが、その後、この関数ではなくModuleインスタンスを呼び出すべきです。前者は前処理と後処理の実行を適切に処理するのに対し、後者はそれらを黙って無視するためです。

  • last_hidden_state (torch.FloatTensor、shape (batch_size, sequence_length, hidden_size)) — モデルの最終層の出力における隠れ状態のシーケンス。

  • pooler_output (torch.FloatTensor、shape (batch_size, hidden_size)) — 補助事前学習タスクに使用される層をさらに通して処理された後、シーケンスの最初のトークン(分類トークン)の最終層の隠れ状態。例えば、BERT 系のモデルでは、線形層と tanh 活性化関数を経て処理された後の分類トークンを返します。この線形層の重みは、事前学習中の次文予測(分類)目的から学習されます。

  • hidden_states (tuple(torch.FloatTensor)、optional、output_hidden_states=True が渡された場合または config.output_hidden_states=True の場合に返されます)— torch.FloatTensor のタプル(埋め込み層を持つモデルの場合、埋め込みの出力が 1 つ、各層の出力がそれぞれ 1 つずつ)で、形状は (batch_size, sequence_length, hidden_size) です。

    各層の出力におけるモデルのHidden-states、および任意の初期埋め込み出力。

  • attentions (tuple(torch.FloatTensor)、optional、output_attentions=True が渡された場合または config.output_attentions=True の場合に返されます)— torch.FloatTensor のタプル(各レイヤーごとに1つ)、形状は (batch_size, num_heads, sequence_length, sequence_length)。

    アテンションのsoftmax後のアテンション重み。自己アテンションヘッド内で加重平均を計算するために使用されます。

  • loss (torch.FloatTensor、optional、labels が指定された場合のみ返されます) — RNN-T transducer の損失です。

  • logits (torch.FloatTensor、shape (batch_size, encoder_length, decoder_length, vocab_size + 1)) — 各エンコーダ/デコーダフレームペアについて、語彙および空白シンボルに対するジョイントネットワークのスコア。

  • decoder_cache (Nemotron3_5AsrRNNTDecoderCache、optional) — 更新されたデコーダーLSTMキャッシュ。空白の予測時に再利用して、LSTMステップをスキップします。

  • encoder_past_key_values (Cache、任意) — use_cache=True でオーディオをエンコードする際に返される、更新されたエンコーダーアテンションの K/V スライディングウィンドウキャッシュ(キャッシュ対応ストリーミング)。次のチャンクの forward に渡します。

  • padding_cache (NemotronAsrStreamingEncoderCausalConvPaddingCache、optional) — 更新された統合ストリーミング conv キャッシュ(サブサンプリング Conv2d + conformer depthwise Conv1d)。use_cache=True でオーディオをエンコードする際に返されます。次のチャンクの forward に渡してください。

例:

>>> from transformers import AutoProcessor, Nemotron3_5AsrForRNNT
>>> from datasets import load_dataset, Audio

>>> model_id = "nvidia/nemotron-3.5-asr-streaming-0.6b"
>>> processor = AutoProcessor.from_pretrained(model_id)
>>> model = Nemotron3_5AsrForRNNT.from_pretrained(model_id)

>>> ds = load_dataset("hf-internal-testing/librispeech_asr_dummy", "clean", split="validation")
>>> ds = ds.cast_column("audio", Audio(sampling_rate=processor.feature_extractor.sampling_rate))

>>> inputs = processor(ds[0]["audio"]["array"], language="en-US")
>>> outputs = model(**inputs)

generate

< >

( inputs = Nonegeneration_config = None**kwargs )

Update on GitHub