Class: Cohere::Transcribe::VAD::Silero

Inherits:
Object
  • Object
show all
Defined in:
lib/cohere/transcribe/vad/silero.rb

Overview

Thread-confined, bounded ONNX sequence runner for Silero VAD v6.

The model consumes one row per 512-sample frame. Each row contains the preceding 64 waveform samples followed by the current frame. Recurrent h/c state and waveform context are carried across bounded temporal calls, while every new audio file starts from zero state.

Defined Under Namespace

Classes: Execution

Constant Summary collapse

WINDOW_SAMPLES =
Timestamps::WINDOW_SAMPLES
CONTEXT_SAMPLES =
64
MODEL_ROW_SAMPLES =
WINDOW_SAMPLES + CONTEXT_SAMPLES
MAX_SEQUENCE_FRAMES =
256
DEFAULT_INTRA_OP_THREADS =
1
STATE_WIDTH =
128
MODEL_PATH =
File.expand_path("silero_vad_v6.onnx", __dir__).freeze
ENGINE =
"onnx"
PROVIDER =
"CPUExecutionProvider"
SESSION_OPTIONS =
{
  providers: [PROVIDER].freeze,
  inter_op_num_threads: 1,
  intra_op_num_threads: 1,
  enable_cpu_mem_arena: false,
  log_severity_level: 4
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_path: MODEL_PATH, session: nil, session_factory: nil, block_frames: MAX_SEQUENCE_FRAMES, threads: DEFAULT_INTRA_OP_THREADS) ⇒ Silero

Returns a new instance of Silero.

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/cohere/transcribe/vad/silero.rb', line 54

def initialize(model_path: MODEL_PATH, session: nil, session_factory: nil,
               block_frames: MAX_SEQUENCE_FRAMES, threads: DEFAULT_INTRA_OP_THREADS)
  @model_path = File.expand_path(model_path.to_s).freeze
  raise ArgumentError, "block_frames must be a positive integer" unless block_frames.is_a?(Integer) && block_frames.positive?
  raise ArgumentError, "threads must be a positive integer" unless threads.is_a?(Integer) && threads.positive?

  @block_frames = block_frames
  @intra_op_threads = threads
  @session = session
  @session_factory = session_factory
  @current_model_load_seconds = 0.0
  @current_inference_seconds = 0.0
  @last_execution = execution(model_calls: 0, valid_frames: 0)
end

Instance Attribute Details

#block_framesObject (readonly)

Returns the value of attribute block_frames.



52
53
54
# File 'lib/cohere/transcribe/vad/silero.rb', line 52

def block_frames
  @block_frames
end

#intra_op_threadsObject (readonly)

Returns the value of attribute intra_op_threads.



52
53
54
# File 'lib/cohere/transcribe/vad/silero.rb', line 52

def intra_op_threads
  @intra_op_threads
end

#last_executionObject (readonly)

Returns the value of attribute last_execution.



52
53
54
# File 'lib/cohere/transcribe/vad/silero.rb', line 52

def last_execution
  @last_execution
end

#model_pathObject (readonly)

Returns the value of attribute model_path.



52
53
54
# File 'lib/cohere/transcribe/vad/silero.rb', line 52

def model_path
  @model_path
end

Instance Method Details

#engineObject



69
70
71
# File 'lib/cohere/transcribe/vad/silero.rb', line 69

def engine
  ENGINE
end

#providerObject



73
74
75
# File 'lib/cohere/transcribe/vad/silero.rb', line 73

def provider
  PROVIDER
end

#provider_optionsObject

Match ONNX Runtime's execution-provider introspection. Thread counts belong to SessionOptions and are reported separately in the profile.



79
80
81
# File 'lib/cohere/transcribe/vad/silero.rb', line 79

def provider_options
  @provider_options ||= { PROVIDER => {}.freeze }.freeze
end

#speech_probabilities(audio) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/cohere/transcribe/vad/silero.rb', line 83

def speech_probabilities(audio)
  @current_model_load_seconds = 0.0
  @current_inference_seconds = 0.0
  audio_length = validate_mono_audio!(audio)
  if audio_length.zero?
    @last_execution = execution(model_calls: 0, valid_frames: 0)
    return []
  end

  return speech_probabilities_numo(audio, audio_length) if numo_array?(audio)

  hidden = zero_state
  cell = zero_state
  previous_context = Array.new(CONTEXT_SAMPLES, 0.0)
  frame_count = (audio_length + WINDOW_SAMPLES - 1) / WINDOW_SAMPLES
  outputs = []

  model_calls = 0
  (0...frame_count).step(block_frames) do |frame_start|
    chunk_frames = [block_frames, frame_count - frame_start].min
    frames = build_frames(audio, audio_length, frame_start, chunk_frames)
    contexts = build_contexts(frames, previous_context)
    previous_context = frames.last.last(CONTEXT_SAMPLES).dup
    model_rows = contexts.zip(frames).map { |context, frame| context + frame }

    probabilities, hidden, cell = run_model(model_rows, hidden, cell)
    chunk_probabilities = one_dimensional_output(probabilities)
    unless chunk_probabilities.length == chunk_frames
      raise TranscriptionRuntimeError,
            "Silero ONNX returned #{chunk_probabilities.length} probabilities " \
            "for #{chunk_frames} frames"
    end
    outputs.concat(chunk_probabilities)
    model_calls += 1
  end

  @last_execution = execution(model_calls: model_calls, valid_frames: frame_count)
  outputs
end

#speech_timestamps(audio, sampling_rate: SAMPLE_RATE, threshold: 0.5, min_speech_duration_ms: 250, max_speech_duration_s: Float::INFINITY, min_silence_duration_ms: 100, speech_pad_ms: 30, neg_threshold: nil, min_silence_at_max_speech: 98, use_max_poss_sil_at_max_speech: true, cancel_check: nil) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/cohere/transcribe/vad/silero.rb', line 123

def speech_timestamps(
  audio,
  sampling_rate: SAMPLE_RATE,
  threshold: 0.5,
  min_speech_duration_ms: 250,
  max_speech_duration_s: Float::INFINITY,
  min_silence_duration_ms: 100,
  speech_pad_ms: 30,
  neg_threshold: nil,
  min_silence_at_max_speech: 98,
  use_max_poss_sil_at_max_speech: true,
  cancel_check: nil
)
  probabilities = speech_probabilities(audio)
  started = monotonic
  begin
    Timestamps.from_probabilities(
      audio.length,
      probabilities,
      sampling_rate: sampling_rate,
      threshold: threshold,
      min_speech_duration_ms: min_speech_duration_ms,
      max_speech_duration_s: max_speech_duration_s,
      min_silence_duration_ms: min_silence_duration_ms,
      speech_pad_ms: speech_pad_ms,
      neg_threshold: neg_threshold,
      min_silence_at_max_speech: min_silence_at_max_speech,
      use_max_poss_sil_at_max_speech: use_max_poss_sil_at_max_speech,
      cancel_check: cancel_check
    )
  ensure
    @last_execution = @last_execution.with(postprocess_seconds: monotonic - started)
  end
end