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
01325d7
master
1#include "stdafx.h" 2#include <immintrin.h> 3#include <optional> 4#include "HybridContext.h" 5#include "../Utils/Trace/tracing.h" 6 7#if BUILD_HYBRID_VERSION 8namespace 9{ 10int threadsCount (int t ) 11 { 12#ifdef NDEBUG 13if (t == 0 ) 14 { 15SYSTEM_INFO si ; 16GetSystemInfo (& si ); 17return (int )si .dwNumberOfProcessors ; 18 } 19if (t <=1 ) 20return 1 ; 21return t ; 22#else 23return 1 ; 24#endif 25 } 26 27constexpr size_t MB = 1u <<20 ; 28} 29 30HybridContext ::HybridContext (const Whisper ::WhisperModel & wm ) : 31ml (threadsCount (0 ) ), 32model (wm .hybridTensors ), 33whisperModel (wm ) 34{ } 35 36namespace 37{ 38enum struct eModelType :uint8_t 39 { 40Tiny = 0 , 41Base = 1 , 42Small = 2 , 43Medium = 3 , 44Large = 4 , 45 }; 46 47static HRESULT detectModelType (const Whisper ::sModelParams & modelParams ,eModelType & mt ) 48 { 49switch (modelParams .n_audio_layer ) 50 { 51case 4 : 52mt = eModelType::Tiny ; 53return S_OK ; 54case 6 : 55mt = eModelType::Base ; 56return S_OK ; 57case 12 : 58mt = eModelType::Small ; 59return S_OK ; 60case 24 : 61mt = eModelType::Medium ; 62return S_OK ; 63case 32 : 64mt = eModelType::Large ; 65return S_OK ; 66 } 67logError (u8"Unrecognized model" ); 68return E_INVALIDARG ; 69 } 70 71struct alignas(2 )RamMB 72 { 73uint8_t dec ,decLayer ; 74constexpr RamMB (uint8_t d ,uint8_t dl ) :dec (d ),decLayer (dl ) { } 75 76__m128i loadBytes ()const 77 { 78__m128i v = _mm_loadu_si16 (this ); 79// Upcast bytes to int64_t. That instruction can load directly from memory, too bad VC++ optimized doesn't care 80v = _mm_cvtepu8_epi64 (v ); 81// Scale from megabytes into bytes, the multiplier is obviously 2^20 82v = _mm_slli_epi64 (v ,20 ); 83return v ; 84 } 85 }; 86 87// The magic numbers are from MEM_REQ_DECODE and MEM_REQ_DECODE_LAYER red/black maps in the reference version, 88// near the top of whisper.cpp source file 89static const std::array < RamMB ,5 > s_memRequirements = 90 { 91RamMB {200 ,32 },// Tiny 92RamMB {202 ,44 },// Base 93RamMB {204 ,64 },// Small 94RamMB {206 ,84 },// Medium 95RamMB {208 ,110 },// Large 96 }; 97} 98 99HRESULT HybridContext ::create () 100{ 101// Allocate buffers for compute 102// We know they're large, so bypassing the heap 103eModelType modelType ; 104CHECK (detectModelType (whisperModel .parameters ,modelType ) ); 105 106const __m128i bytes = s_memRequirements .at ( (uint8_t )modelType ).loadBytes (); 107CHECK (allocCompute .create (_mm_cvtsi128_si64 (bytes ) ) ); 108CHECK (allocComputeLayer .create (_mm_extract_epi64 (bytes ,1 ) ) ); 109 110// Create staging buffers to download output from encoder stage, 111// in the reference version they're named memory_cross_k / memory_cross_v 112CHECK (kvCross .create (whisperModel .parameters ) ); 113 114// Create RAM buffers for memory_k / memory_v 115CHECK (kv .create (whisperModel .parameters ) ); 116 117return S_OK ; 118} 119 120class HybridContext ::SetAllocatorRaii 121{ 122HybridContext & context ; 123CpuCompute ::iMemoryAllocator * prevAlloc ; 124CpuCompute ::iArenaAllocator * newAlloc ; 125public : 126 127SetAllocatorRaii (HybridContext * owner ,CpuCompute ::iArenaAllocator & a ) : 128context (* owner ) 129 { 130prevAlloc = context .ml .setAllocator (& a ); 131newAlloc = & a ; 132 } 133 ~SetAllocatorRaii () 134 { 135context .ml .setAllocator (prevAlloc ); 136newAlloc -> resetArena (); 137 } 138}; 139 140HRESULT HybridContext ::decode (const int * tokens ,const int n_tokens ,const int n_past ,const sDecParams & dp , std::vector < float >& probs ) 141{ 142CHECK (ml .setThreadsCount (dp .n_threads ) ); 143 144// whisper_decode 145const auto & hparams = whisperModel .parameters ; 146const uint32_t n_vocab = hparams .n_vocab ; 147 148const uint32_t n_ctx = hparams .n_text_ctx ; 149const uint32_t n_state = hparams .n_text_state ; 150const uint32_t n_head = hparams .n_text_head ; 151const uint32_t n_layer = hparams .n_text_layer ; 152 153const uint32_t N = n_tokens ; 154const uint32_t M = dp .M ; 155 156SetAllocatorRaii ac {this ,allocCompute }; 157using namespace CpuCompute ; 158Tensor cur = ml .addRows (model .tokenEmbedding ,model .positionalEmbedding ,tokens ,n_tokens ,n_past ); 159Tracing ::tensor ("dec-rows" ,cur ); 160 161Tensor inpL = cur ; 162auto kvCross = this -> kvCross .map (); 163 164for (uint32_t il = 0 ;il < n_layer ;il ++ ) 165 { 166if (0 == il )Tracing ::tensor ("dec-inpL" ,inpL ); 167const auto & layer = model .layers [il ]; 168SetAllocatorRaii acLayer {this ,allocComputeLayer }; 169 170// norm 171Tensor cur = ml .norm (inpL ); 172ml .fmaRepeat (cur ,layer .attnLn0 ); 173if (0 == il )Tracing ::tensor ("dec-norm" ,cur ); 174 175// self-attention 176 { 177Tensor Qcur = ml .mulMat (layer .attnQuery .w ,cur ); 178if (0 == il )Tracing ::tensor ("dec-Qcur-0" ,Qcur ); 179const float scaling = computeScaling ( (int )n_state , (int )n_head ); 180ml .addRepeatScale (Qcur ,layer .attnQuery .b ,scaling ); 181if (0 == il )Tracing ::tensor ("dec-Qcur-1" ,Qcur ); 182 183// note: no bias for Key 184Tensor Kcur = ml .mulMat (layer .attnKey ,cur ); 185ml .scale (Kcur ,scaling ); 186if (0 == il )Tracing ::tensor ("dec-Kcur" ,Kcur ); 187 188Tensor Vcur = ml .mulMat (layer .attnValue .w ,cur ); 189ml .addRepeat (Vcur ,layer .attnValue .b ); 190if (0 == il )Tracing ::tensor ("dec-Vcur" ,Vcur ); 191 192// store key and value to memory 193 { 194const uint32_t len = N * n_state ; 195const uint32_t off = n_state * ( (uint32_t )il * n_ctx + n_past ); 196Tensor k = kv .keysView (len ,off ); 197Tensor v = kv .valuesView (len ,off ); 198 199CHECK (ml .copyImpl (k ,Kcur ) ); 200CHECK (ml .copyImpl (v ,Vcur ) ); 201 } 202 203// ------ 204Tensor Q = ml .permute (ml .copy (Qcur , eDataType::FP32 , {n_state /n_head ,n_head ,N } ),0 ,2 ,1 ,3 ); 205Tensor K = ml .permute (kv .keysView ( (n_past + N )* n_state , (uint32_t )il * n_ctx * n_state ) 206 .reshape3d (n_state /n_head ,n_head ,n_past + N ), 2070 ,2 ,1 ,3 ); 208Tensor KQ = ml .mulMat (K ,Q ); 209if (0 == il )Tracing ::tensor ("dec-KQ-0" ,KQ ); 210ml .diagMaskInf (KQ ,n_past ); 211if (0 == il )Tracing ::tensor ("dec-KQ-1" ,KQ ); 212ml .softMax (KQ ); 213if (0 == il )Tracing ::tensor ("dec-KQ-2" ,KQ ); 214 215Tensor V_trans = ml .permute ( 216kv .valuesView ( (n_past + N )* n_state , (uint32_t )il * n_ctx * n_state ) 217 .reshape3d (n_state /n_head ,n_head ,n_past + N ), 2181 ,2 ,0 ,3 ); 219 220Tensor KQV = ml .mulMat (V_trans ,KQ ); 221if (0 == il )Tracing ::tensor ("dec-KQV" ,KQV ); 222 223Tensor KQV_merged = ml .permute (KQV ,0 ,2 ,1 ,3 ); 224ml .copyInPlace (cur ,KQV_merged , eDataType::FP32 , {n_state ,N } ); 225 } 226 227 { 228cur = ml .mulMat (layer .attnLn1 .w ,cur ); 229ml .addRepeat (cur ,layer .attnLn1 .b ); 230 } 231 232// add the input 233Tensor inpCA = ml .add (cur ,inpL ); 234 235// norm 236 { 237cur = ml .norm (inpCA ); 238ml .fmaRepeat (cur ,layer .crossAttnLn0 ); 239 } 240 241// cross-attention 242 { 243Tensor Qcur = ml .mulMat (layer .crossAttnQuery .w ,cur ); 244ml .addRepeatScale (Qcur ,layer .crossAttnQuery .b ,computeScaling ( (int )n_state , (int )n_head ) ); 245 246// Kcross is already scaled 247const uint32_t len = M * n_state ; 248const uint32_t off = (uint32_t )il * len ; 249Tensor Kcross = kvCross .keysView (len ,off ).reshape3d (n_state /n_head ,n_head ,M ); 250Tensor Vcross = kvCross .valuesView (len ,off ).reshape3d (n_state /n_head ,n_head ,M ); 251 252// ------ 253Tensor Q = ml .permute (ml .copy (Qcur , eDataType::FP32 , {n_state /n_head ,n_head ,N } ),0 ,2 ,1 ,3 ); 254Tensor K = ml .permute (Kcross ,0 ,2 ,1 ,3 ); 255Tensor KQ = ml .mulMat (K ,Q ); 256ml .softMax (KQ ); 257Tensor V_trans = ml .permute (Vcross ,1 ,2 ,0 ,3 ); 258Tensor KQV = ml .mulMat (V_trans ,KQ ); 259if (0 == il )Tracing ::tensor ("dec-KQV" ,KQV ); 260Tensor KQV_merged = ml .permute (KQV ,0 ,2 ,1 ,3 ); 261 262ml .copyInPlace (cur ,KQV_merged , eDataType::FP32 , {n_state ,N } ); 263 } 264 265// projection 266 { 267cur = ml .mulMat (layer .crossAttnLn1 .w ,cur ); 268ml .addRepeat (cur ,layer .crossAttnLn1 .b ); 269 } 270// add the input 271ml .addInPlace (cur ,inpCA ); 272Tensor inpFF = cur ; 273 274// feed-forward network 275 { 276// norm 277cur = ml .norm (inpFF ); 278ml .fmaRepeat (cur ,layer .mlpLn ); 279 280cur = ml .mulMat (layer .mlp0 .w ,cur ); 281ml .addRepeatGelu (cur ,layer .mlp0 .b ); 282 283// The mulMat() below creates a tensor for the output of this layer. 284// We have a special memory storage for these tensors, that's how they survive resets of per-layer arenas 285allocLayerOutput .resetArena (); 286ml .setAllocator (& allocLayerOutput ); 287 288// projection 289cur = ml .mulMat (layer .mlp1 .w ,cur ); 290ml .addRepeat (cur ,layer .mlp1 .b ); 291 } 292 293// output from this layer 294ml .addInPlace (cur ,inpFF ); 295inpL = cur ; 296 } 297 298// norm 299cur = ml .norm (inpL ); 300ml .fmaRepeat (cur ,model .ln ); 301 302cur = ml .mulMat (model .tokenEmbedding ,cur ); 303 304// logits -> probs 305ml .softMax (cur ); 306 307const float * rsi = cur .fp32 (); 308probs .assign (rsi ,rsi + cur .countElements () ); 309Tracing ::vector ("probs" ,probs ); 310return S_OK ; 311} 312 313void * HybridContext ::AllocSingle ::allocate (size_t cb ,size_t align ) 314{ 315if ( !allocated ) 316 { 317allocated = true; 318if (cb <=capacity ) 319 { 320CpuCompute ::dbgMarkUninitializedMemory (buffer .pointer (),capacity ); 321return buffer .pointer (); 322 } 323else 324 { 325HRESULT hr = buffer .allocate (cb ); 326if (SUCCEEDED (hr ) ) 327 { 328capacity = cb ; 329CpuCompute ::dbgMarkUninitializedMemory (buffer .pointer (),capacity ); 330return buffer .pointer (); 331 } 332logErrorHr (hr ,u8"HybridContext.AllocSingle.allocate" ); 333throw hr ; 334 } 335 } 336else 337 { 338logError (u8"HybridContext.AllocSingle only supports 1 tensor" ); 339throw E_UNEXPECTED ; 340 } 341} 342 343void HybridContext ::AllocSingle ::resetArena () 344{ 345allocated = false; 346if (capacity > 0 ) 347CpuCompute ::dbgMarkFreedMemory (buffer .pointer (),capacity ); 348} 349#endif