yum-archive/TaSTT-Whisper
High-performance GPGPU inference of OpenAI's Whisper automatic speech recognition (ASR) model
git clone https://git.yummers.dev/yum-archive/TaSTT-Whisper
e736f91
master
1#include "params.h" 2#include "../../Whisper/API/iContext.cl.h" 3#include "../../Whisper/API/iMediaFoundation.cl.h" 4#include "../../ComLightLib/comLightClient.h" 5#include "miscUtils.h" 6#include <array> 7#include <atomic> 8#include "textWriter.h" 9using namespace Whisper ; 10 11#define STREAM_AUDIO 1 12 13static HRESULT loadWhisperModel (const wchar_t * path ,iModel ** pp ) 14{ 15using namespace Whisper ; 16constexpr eModelImplementation impl = eModelImplementation::GPU ; 17// constexpr eModelImplementation impl = eModelImplementation::Reference; 18constexpr uint32_t flags = 0 ; 19return Whisper ::loadModel (path ,impl ,flags ,nullptr ,pp ); 20} 21 22namespace 23{ 24// Terminal color map. 10 colors grouped in ranges [0.0, 0.1, ..., 0.9] 25// Lowest is red, middle is yellow, highest is green. 26static const std::array < const char * ,10 > k_colors = 27 { 28"\033[38;5;196m" ,"\033[38;5;202m" ,"\033[38;5;208m" ,"\033[38;5;214m" ,"\033[38;5;220m" , 29"\033[38;5;226m" ,"\033[38;5;190m" ,"\033[38;5;154m" ,"\033[38;5;118m" ,"\033[38;5;82m" , 30 }; 31 32 std::string to_timestamp (sTimeSpan ts ,bool comma = false ) 33 { 34sTimeSpanFields fields = ts ; 35uint32_t msec = fields .ticks /10'000 ; 36uint32_t hr = fields .days * 24 + fields .hours ; 37uint32_t min = fields .minutes ; 38uint32_t sec = fields .seconds ; 39 40char buf [32 ]; 41snprintf (buf ,sizeof (buf ),"%02d:%02d:%02d%s%03d" ,hr ,min ,sec ,comma ?"," :"." ,msec ); 42return std::string (buf ); 43 } 44 45static int colorIndex (const sToken & tok ) 46 { 47const float p = tok .probability ; 48const float p3 = p * p * p ; 49int col = (int )(p3 * float (k_colors .size () ) ); 50col = std::max (0 , std::min ( (int )k_colors .size ()- 1 ,col ) ); 51return col ; 52 } 53 54HRESULT __cdeclnewSegmentCallback (iContext * context ,uint32_t n_new ,void * user_data )noexcept 55 { 56ComLight ::CComPtr < iTranscribeResult > results ; 57CHECK (context -> getResults ( eResultFlags::Timestamps | eResultFlags::Tokens ,& results ) ); 58 59sTranscribeLength length ; 60CHECK (results -> getSize (length ) ); 61 62const whisper_params & params = * ( (const whisper_params * )user_data ); 63 64// print the last n_new segments 65const uint32_t s0 = length .countSegments - n_new ; 66if (s0 == 0 ) 67printf ("\n" ); 68 69const sSegment * const segments = results -> getSegments (); 70const sToken * const tokens = results -> getTokens (); 71 72for (uint32_t i = s0 ;i < length .countSegments ;i ++ ) 73 { 74const sSegment & seg = segments [i ]; 75 76if (params .no_timestamps ) 77 { 78if (params .print_colors ) 79 { 80for (uint32_t j = 0 ;j < seg .countTokens ;j ++ ) 81 { 82const sToken & tok = tokens [seg .firstToken + j ]; 83if ( !params .print_special && (tok .flags & eTokenFlags::Special ) ) 84continue ; 85wprintf (L"%S%s%S" ,k_colors [colorIndex (tok ) ],utf16 (tok .text ).c_str (),"\033[0m" ); 86 } 87 } 88else 89wprintf (L"%s" ,utf16 (seg .text ).c_str () ); 90fflush (stdout ); 91continue ; 92 } 93 94 std::string speaker = "" ; 95 96if (params .diarize ) 97 { 98eSpeakerChannel channel ; 99HRESULT hr = context -> detectSpeaker (seg .time ,channel ); 100if (SUCCEEDED (hr )&& channel != eSpeakerChannel::NoStereoData ) 101 { 102using namespace std::string_literals ; 103switch (channel ) 104 { 105case eSpeakerChannel::Unsure : 106speaker = "(speaker ?)" s; 107break ; 108case eSpeakerChannel::Left : 109speaker = "(speaker 0)" s; 110break ; 111case eSpeakerChannel::Right : 112speaker = "(speaker 1)" ; 113break ; 114 } 115 } 116 } 117 118if (params .print_colors ) 119 { 120printf ("[%s --> %s] %s " , 121to_timestamp (seg .time .begin ).c_str (), 122to_timestamp (seg .time .end ).c_str (), 123speaker .c_str () ); 124 125for (uint32_t j = 0 ;j < seg .countTokens ;j ++ ) 126 { 127const sToken & tok = tokens [seg .firstToken + j ]; 128if ( !params .print_special && (tok .flags & eTokenFlags::Special ) ) 129continue ; 130wprintf (L"%S%s%S" ,k_colors [colorIndex (tok ) ],utf16 (tok .text ).c_str (),"\033[0m" ); 131 } 132printf ("\n" ); 133 } 134else 135wprintf (L"[%S --> %S] %S%s\n" ,to_timestamp (seg .time .begin ).c_str (),to_timestamp (seg .time .end ).c_str (),speaker .c_str (),utf16 (seg .text ).c_str () ); 136 } 137return S_OK ; 138 } 139 140HRESULT __cdeclbeginSegmentCallback (iContext * context ,void * user_data )noexcept 141 { 142 std::atomic_bool * flag = (std::atomic_bool * )user_data ; 143bool aborted = flag -> load (); 144return aborted ?S_FALSE :S_OK ; 145 } 146 147HRESULT setupConsoleColors () 148 { 149HANDLE h = GetStdHandle (STD_OUTPUT_HANDLE ); 150if (h == INVALID_HANDLE_VALUE ) 151return HRESULT_FROM_WIN32 (GetLastError () ); 152 153DWORD mode = 0 ; 154if ( !GetConsoleMode (h ,& mode ) ) 155return HRESULT_FROM_WIN32 (GetLastError () ); 156if (0 != (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING ) ) 157return S_FALSE ; 158 159mode |=ENABLE_VIRTUAL_TERMINAL_PROCESSING ; 160if ( !SetConsoleMode (h ,mode ) ) 161return HRESULT_FROM_WIN32 (GetLastError () ); 162return S_OK ; 163 } 164} 165 166int wmain (int argc ,wchar_t * argv [] ) 167{ 168// Whisper::dbgCompareTraces( LR"(C:\Temp\2remove\Whisper\ref.bin)", LR"(C:\Temp\2remove\Whisper\gpu.bin )" ); return 0; 169 170// Tell logger to use the standard output stream for the messages 171 { 172Whisper ::sLoggerSetup logSetup ; 173logSetup .flags = eLoggerFlags::UseStandardError ; 174logSetup .level = eLogLevel::Debug ; 175Whisper ::setupLogger (logSetup ); 176 } 177 178whisper_params params ; 179if ( !params .parse (argc ,argv ) ) 180return 1 ; 181 182if (params .print_colors ) 183 { 184if (FAILED (setupConsoleColors () ) ) 185params .print_colors = false; 186 } 187 188if (params .fname_inp .empty () ) 189 { 190fprintf (stderr ,"error: no input files specified\n" ); 191whisper_print_usage (argc ,argv ,params ); 192return 2 ; 193 } 194 195if (Whisper ::findLanguageKeyA (params .language .c_str () )== UINT_MAX ) 196 { 197fprintf (stderr ,"error: unknown language '%s'\n" ,params .language .c_str () ); 198whisper_print_usage (argc ,argv ,params ); 199return 3 ; 200 } 201 202ComLight ::CComPtr < iModel > model ; 203HRESULT hr = loadWhisperModel (params .model .c_str (),& model ); 204if (FAILED (hr ) ) 205 { 206printError ("failed to load the model" ,hr ); 207return 4 ; 208 } 209 210ComLight ::CComPtr < iContext > context ; 211hr = model -> createContext (& context ); 212if (FAILED (hr ) ) 213 { 214printError ("failed to initialize whisper context" ,hr ); 215return 5 ; 216 } 217 218ComLight ::CComPtr < iMediaFoundation > mf ; 219hr = initMediaFoundation (& mf ); 220if (FAILED (hr ) ) 221 { 222printError ("failed to initialize Media Foundation runtime" ,hr ); 223return 5 ; 224 } 225 226for (const std::wstring & fname :params .fname_inp ) 227 { 228// print some info about the processing 229 { 230if (model -> isMultilingual ()== S_FALSE ) 231 { 232if (params .language != "en" || params .translate ) 233 { 234params .language = "en" ; 235params .translate = false; 236fprintf (stderr ,"%s: WARNING: model is not multilingual, ignoring language and translation options\n" ,__func__ ); 237 } 238 } 239 } 240 241// run the inference 242Whisper ::sFullParams wparams ; 243context -> fullDefaultParams ( eSamplingStrategy::Greedy ,& wparams ); 244 245wparams .resetFlag ( eFullParamsFlags::PrintRealtime | eFullParamsFlags::PrintProgress ); 246wparams .setFlag ( eFullParamsFlags::PrintTimestamps , !params .no_timestamps ); 247wparams .setFlag ( eFullParamsFlags::PrintSpecial ,params .print_special ); 248wparams .setFlag ( eFullParamsFlags::Translate ,params .translate ); 249// When there're multiple input files, assuming they're independent clips 250wparams .setFlag ( eFullParamsFlags::NoContext ); 251wparams .language = Whisper ::makeLanguageKey (params .language .c_str () ); 252wparams .cpuThreads = params .n_threads ; 253if (params .max_context != UINT_MAX ) 254wparams .n_max_text_ctx = params .max_context ; 255wparams .offset_ms = params .offset_t_ms ; 256wparams .duration_ms = params .duration_ms ; 257 258wparams .setFlag ( eFullParamsFlags::TokenTimestamps ,params .output_wts || params .max_len > 0 ); 259wparams .thold_pt = params .word_thold ; 260wparams .max_len = params .output_wts && params .max_len == 0 ?60 :params .max_len ; 261 262wparams .setFlag ( eFullParamsFlags::SpeedupAudio ,params .speed_up ); 263 264// This callback is called on each new segment 265if ( !wparams .flag ( eFullParamsFlags::PrintRealtime ) ) 266 { 267wparams .new_segment_callback = & newSegmentCallback ; 268wparams .new_segment_callback_user_data = & params ; 269 } 270 271// example for abort mechanism 272// in this example, we do not abort the processing, but we could if the flag is set to true 273// the callback is called before every encoder run - if it returns false, the processing is aborted 274 std::atomic_bool is_aborted = false; 275 { 276wparams .encoder_begin_callback = & beginSegmentCallback ; 277wparams .encoder_begin_callback_user_data = & is_aborted ; 278 } 279 280if (STREAM_AUDIO && !wparams .flag ( eFullParamsFlags::TokenTimestamps ) ) 281 { 282ComLight ::CComPtr < iAudioReader > reader ; 283CHECK (mf -> openAudioFile (fname .c_str (),params .diarize ,& reader ) ); 284sProgressSink progressSink {nullptr ,nullptr }; 285hr = context -> runStreamed (wparams ,progressSink ,reader ); 286 } 287else 288 { 289// Token-level timestamps feature is not currently implemented when streaming the audio 290// When these timestamps are requested, fall back to buffered mode. 291ComLight ::CComPtr < iAudioBuffer > buffer ; 292CHECK (mf -> loadAudioFile (fname .c_str (),params .diarize ,& buffer ) ); 293hr = context -> runFull (wparams ,buffer ); 294 } 295 296if (FAILED (hr ) ) 297 { 298printError ("Unable to process audio" ,hr ); 299return 10 ; 300 } 301 302if (params .output_txt ) 303 { 304bool timestamps = !params .no_timestamps ; 305hr = writeText (context ,fname .c_str (),timestamps ); 306if (FAILED (hr ) ) 307printError ("Unable to produce the text file" ,hr ); 308 } 309 310if (params .output_srt ) 311 { 312hr = writeSubRip (context ,fname .c_str () ); 313if (FAILED (hr ) ) 314printError ("Unable to produce the text file" ,hr ); 315 } 316 317if (params .output_vtt ) 318 { 319hr = writeWebVTT (context ,fname .c_str () ); 320if (FAILED (hr ) ) 321printError ("Unable to produce the text file" ,hr ); 322 } 323 } 324 325context -> timingsPrint (); 326context = nullptr ; 327return 0 ; 328}