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
8c4603c
master
1#include "stdafx.h" 2#include "MlContext.h" 3#include "simdUtils.h" 4#include "mulMat.h" 5using namespace CpuCompute ; 6 7MlContext ::MlContext (int threads ) :pfor (threads ) 8{ 9} 10 11Tensor MlContext ::createTensor (eDataType type ,const std::array < uint32_t ,4 >& size ) 12{ 13Tensor res ; 14check (res .create (type ,size ,allocator ) ); 15return res ; 16} 17 18Tensor MlContext ::createTensor (eDataType type , std::initializer_list < uint32_t > size ) 19{ 20Tensor res ; 21check (res .create (type ,size ,allocator ) ); 22return res ; 23} 24 25namespace 26{ 27inline const uint16_t * getRow16 (const Tensor & t ,size_t index ) 28 { 29const uint16_t * rsi = t .fp16 (); 30rsi += index * t .nb [1 ]; 31return rsi ; 32 } 33inline const float * getRow32 (const Tensor & t ,size_t index ) 34 { 35const float * rsi = t .fp32 (); 36rsi += index * t .nb [1 ]; 37return rsi ; 38 } 39} 40 41Tensor MlContext ::addRows (const Tensor & d_te ,const Tensor & d_pe ,const int * tokens ,const int n_tokens ,const int n_past ) 42{ 43if (d_te .type ()!= eDataType::FP16 || d_pe .type ()!= eDataType::FP32 ) 44throw E_INVALIDARG ; 45if (d_te .ne [0 ]!= d_pe .ne [0 ] ) 46throw E_INVALIDARG ; 47if (n_tokens <=0 ) 48throw E_BOUNDS ; 49 50Tensor res = createTensor ( eDataType::FP32 , {d_te .ne [0 ], (uint32_t )n_tokens } ); 51 52const size_t inner = (size_t )d_te .ne [0 ]; 53const size_t outer = (size_t )n_tokens ; 54float * rdi = res .fp32 (); 55for (size_t i = 0 ;i < outer ;i ++ ,rdi += inner ,tokens ++ ) 56 { 57const uint16_t * const source1 = getRow16 (d_te ,* (const uint32_t * )tokens ); 58const float * const source2 = getRow32 (d_pe ,i + (size_t )n_past ); 59addF16to32 (rdi ,source1 ,source2 ,inner ); 60 } 61return res ; 62} 63 64namespace 65{ 66class DispatchHelper3 67 { 68 std::array < uint32_t ,3 > ne ; 69 70public : 71DispatchHelper3 ()= default ; 72DispatchHelper3 (uint32_t x ,uint32_t y ,uint32_t z ) 73 { 74assert (x > 0 && y > 0 && z > 0 ); 75ne [0 ]= x ; 76ne [1 ]= y ; 77ne [2 ]= z ; 78 } 79size_t groupsCount ()const 80 { 81size_t res = ne [0 ]; 82res *=ne [1 ]; 83res *=ne [2 ]; 84return res ; 85 } 86 std::array < uint32_t ,3 > unpack (size_t idx )const 87 { 88assert (idx < groupsCount () ); 89 std::array < uint32_t ,3 > res ; 90res [0 ]= (uint32_t )(idx %ne [0 ] ); 91idx = idx /ne [0 ]; 92res [1 ]= (uint32_t )(idx %ne [1 ] ); 93res [2 ]= (uint32_t )(idx /ne [1 ] ); 94return res ; 95 } 96void next ( std::array < uint32_t ,3 >& i )const 97 { 98i [0 ]++ ; 99if (i [0 ]< ne [0 ] ) 100return ; 101i [0 ]= 0 ; 102i [1 ]++ ; 103if (i [1 ]< ne [1 ] ) 104return ; 105i [1 ]= 0 ; 106i [2 ]++ ; 107 } 108 }; 109 110inline const float * sourceRow (const float * rsi ,const std::array < uint32_t ,3 >& idx ,size_t nb0 ,size_t nb1 ,size_t nb2 ) 111 { 112const size_t r0 = idx [0 ]* nb0 ; 113const size_t r1 = idx [1 ]* nb1 ; 114const size_t r2 = idx [2 ]* nb2 ; 115rsi = rsi + r0 + r1 + r2 ; 116return rsi ; 117 } 118 119struct NormContext :public iComputeRange 120 { 121const float * source ; 122float * result ; 123size_t inner ; 124DispatchHelper3 threads ; 125 std::array < uint32_t ,3 > nbInput ; 126 127HRESULT __stdcall compute (size_t i ,size_t end )const override final 128 { 129ALIGNED_SPAN (temp ,inner ); 130 131 std::array < uint32_t ,3 > idx = threads .unpack (i ); 132float * rdi = result + i * inner ; 133for ( ;i < end ;i ++ ,rdi += inner ,threads .next (idx ) ) 134 { 135const float * rsi = sourceRow (source ,idx ,nbInput [0 ],nbInput [1 ],nbInput [2 ] ); 136norm (rdi ,temp ,rsi ,inner ); 137 } 138return S_OK ; 139 } 140 }; 141} 142 143Tensor MlContext ::norm (const Tensor & arg ) 144{ 145if (arg .type ()!= eDataType::FP32 || arg .nb [0 ]!= 1 ) 146throw E_INVALIDARG ; 147Tensor res = createTensor ( eDataType::FP32 ,arg .ne ); 148 149NormContext context ; 150context .source = arg .fp32 (); 151context .result = res .fp32 (); 152context .inner = arg .ne [0 ]; 153context .threads = DispatchHelper3 (arg .ne [1 ],arg .ne [2 ],arg .ne [3 ] ); 154context .nbInput = {arg .nb [1 ],arg .nb [2 ],arg .nb [3 ] }; 155 156check (pfor .parallelFor (context ,context .threads .groupsCount () ) ); 157return res ; 158} 159 160void MlContext ::fmaRepeat (Tensor & cur ,const Tensor & w ,const Tensor & b ) 161{ 162if ( !(cur .isContinuous ()&& w .isContinuous ()&& b .isContinuous () ) ) 163throw E_INVALIDARG ; 164 165if ( !(cur .type ()== eDataType::FP32 && w .type ()== eDataType::FP32 && b .type ()== eDataType::FP32 ) ) 166throw E_INVALIDARG ; 167 168if ( !isSameShape (w ,b ) ) 169throw E_INVALIDARG ; 170 171DispatchHelper3 helper {cur .ne [1 ],cur .ne [2 ],cur .ne [3 ] }; 172 std::array < uint32_t ,3 > idx = {0 ,0 ,0 }; 173const size_t countRows = helper .groupsCount (); 174 175const size_t innerRes = cur .ne [0 ]; 176const size_t innerPattern = w .ne [0 ]; 177 178float * rdi = cur .fp32 (); 179for (size_t i = 0 ;i < countRows ;i ++ ,helper .next (idx ),rdi += innerRes ) 180 { 181 std::array < uint32_t ,3 > idxPattern ; 182idxPattern [0 ]= idx [0 ] %w .ne [1 ]; 183idxPattern [1 ]= idx [1 ] %w .ne [2 ]; 184idxPattern [2 ]= idx [2 ] %w .ne [3 ]; 185 186const float * s1 = sourceRow (w .fp32 (),idxPattern ,w .nb [1 ],w .nb [2 ],w .nb [3 ] ); 187const float * s2 = sourceRow (b .fp32 (),idxPattern ,b .nb [1 ],b .nb [2 ],b .nb [3 ] ); 188fmaRepeatRow (rdi ,innerRes ,s1 ,s2 ,innerPattern ); 189 } 190} 191 192Tensor MlContext ::mulMat (const Tensor & a ,const Tensor & b ) 193{ 194if ( !DirectCompute ::canMulMat (a ,b ) ) 195throw E_INVALIDARG ; 196 197 std::array < uint32_t ,4 > ne {a .ne [1 ],b .ne [1 ],a .ne [2 ],b .ne [3 ] }; 198Tensor result = createTensor ( eDataType::FP32 ,ne ); 199 200check (CpuCompute ::mulMat (result ,a ,b ,pfor ) ); 201return result ; 202} 203 204// cur = add( repeat( b, cur ), cur ); cur = scale(cur, scaling) 205void MlContext ::addRepeatScale (Tensor & cur ,const Tensor & b ,float scaling ) 206{ 207if ( !(cur .isContinuous ()&& b .isContinuous () ) ) 208throw E_INVALIDARG ; 209if ( !(cur .type ()== eDataType::FP32 && b .type ()== eDataType::FP32 ) ) 210throw E_INVALIDARG ; 211 212DispatchHelper3 helper {cur .ne [1 ],cur .ne [2 ],cur .ne [3 ] }; 213 std::array < uint32_t ,3 > idx = {0 ,0 ,0 }; 214const size_t countRows = helper .groupsCount (); 215 216const size_t innerRes = (uint32_t )cur .ne [0 ]; 217const size_t innerPattern = (uint32_t )b .ne [0 ]; 218 219float * rdi = cur .fp32 (); 220const __m256 scale = _mm256_set1_ps (scaling ); 221for (size_t i = 0 ;i < countRows ;i ++ ,helper .next (idx ),rdi += innerRes ) 222 { 223 std::array < uint32_t ,3 > idxPattern ; 224idxPattern [0 ]= idx [0 ] % (uint32_t )b .ne [1 ]; 225idxPattern [1 ]= idx [1 ] % (uint32_t )b .ne [2 ]; 226idxPattern [2 ]= idx [2 ] % (uint32_t )b .ne [3 ]; 227 228const float * source = sourceRow (b .fp32 (),idxPattern ,b .nb [1 ],b .nb [2 ],b .nb [3 ] ); 229addRepeatScaleRow (rdi ,innerRes ,source ,innerPattern ,scale ); 230 } 231} 232 233void MlContext ::addRepeat (Tensor & cur ,const Tensor & b ) 234{ 235if ( !(cur .isContinuous ()&& b .isContinuous () ) ) 236throw E_INVALIDARG ; 237if ( !(cur .type ()== eDataType::FP32 && b .type ()== eDataType::FP32 ) ) 238throw E_INVALIDARG ; 239 240DispatchHelper3 helper {cur .ne [1 ],cur .ne [2 ],cur .ne [3 ] }; 241 std::array < uint32_t ,3 > idx = {0 ,0 ,0 }; 242const size_t countRows = helper .groupsCount (); 243 244const size_t innerRes = (uint32_t )cur .ne [0 ]; 245const size_t innerPattern = (uint32_t )b .ne [0 ]; 246 247float * rdi = cur .fp32 (); 248for (size_t i = 0 ;i < countRows ;i ++ ,helper .next (idx ),rdi += innerRes ) 249 { 250 std::array < uint32_t ,3 > idxPattern ; 251idxPattern [0 ]= idx [0 ] % (uint32_t )b .ne [1 ]; 252idxPattern [1 ]= idx [1 ] % (uint32_t )b .ne [2 ]; 253idxPattern [2 ]= idx [2 ] % (uint32_t )b .ne [3 ]; 254 255const float * source = sourceRow (b .fp32 (),idxPattern ,b .nb [1 ],b .nb [2 ],b .nb [3 ] ); 256addRepeatRow (rdi ,innerRes ,source ,innerPattern ); 257 } 258} 259 260// cur = scale(cur, scaling) 261void MlContext ::scale (Tensor & cur ,float scaling ) 262{ 263if ( !(cur .isContinuous ()&& cur .type ()== eDataType::FP32 ) ) 264throw E_INVALIDARG ; 265 266const size_t len = cur .countElements (); 267const __m256 scale = _mm256_set1_ps (scaling ); 268scaleRow (cur .fp32 (),len ,scale ); 269} 270 271void MlContext ::diagMaskInf (Tensor & cur ,uint32_t n_past ) 272{ 273if ( !(cur .isContinuous ()&& cur .type ()== eDataType::FP32 ) ) 274throw E_INVALIDARG ; 275 276const size_t n = cur .countRows (); 277const size_t nc = cur .ne [0 ]; 278const size_t nr = cur .ne [1 ]; 279const size_t nz = n /nr ; 280 281for (size_t k = 0 ;k < nz ;k ++ ) 282 { 283for (size_t j = 0 ;j < nr ;j ++ ) 284 { 285float * const rdi = cur .fp32 ()+ k * cur .nb [2 ]+ j * cur .nb [1 ]; 286// +1 because the original code checked for `if( i > n_past + j )` 287// That's why the first index to write is ( n_past + j + 1 ) 288const size_t start = n_past + j + 1 ; 289const ptrdiff_t len = (ptrdiff_t )nc - (ptrdiff_t )start ; 290if (len <=0 ) 291continue ; 292 293// Generates a store string instruction (rep stosd). 294// The magic number is negative infinity in FP32: https://www.h-schmidt.net/FloatConverter/IEEE754.html 295__stosd ( (DWORD * )(rdi + start ),0xff800000u , (size_t )len ); 296 } 297 } 298} 299 300void MlContext ::softMax (Tensor & cur ,float inputScale ) 301{ 302if ( !(cur .isContinuous ()&& cur .type ()== eDataType::FP32 ) ) 303throw E_INVALIDARG ; 304 305struct SoftMaxContext :public iComputeRange 306 { 307float * data ; 308float inputScale ; 309size_t length ,stride ; 310 311HRESULT __stdcall compute (size_t i ,size_t end )const override final 312 { 313float * rdi = data + stride * i ; 314for ( ;i < end ;i ++ ,rdi += stride ) 315 ::softMax (rdi ,length ,inputScale ); 316return S_OK ; 317 } 318 }; 319 320SoftMaxContext context ; 321context .data = cur .fp32 (); 322context .inputScale = inputScale ; 323context .length = cur .ne [0 ]; 324context .stride = cur .nb [1 ]; 325 326const size_t n = cur .countRows (); 327pfor .parallelFor (context ,n ); 328} 329 330namespace 331{ 332template < class R ,class S > 333 __forceinlinevoid copyElement (R * rdi ,const S * rsi ) 334 { 335 static_assert( std::is_same < R ,S > () ); 336* rdi = * rsi ; 337 } 338template <> 339 __forceinlinevoid copyElement < float ,uint16_t > (float * rdi ,const uint16_t * rsi ) 340 { 341__m128i iv = _mm_cvtsi32_si128 (* rsi ); 342__m128 fv = _mm_cvtph_ps (iv ); 343_mm_store_ss (rdi ,fv ); 344 } 345template <> 346 __forceinlinevoid copyElement < uint16_t ,float > (uint16_t * rdi ,const float * rsi ) 347 { 348__m128 fv = _mm_load_ss (rsi ); 349__m128i iv = _mm_cvtps_ph (fv ,0 ); 350* rdi = (uint16_t )(uint32_t )_mm_cvtsi128_si32 (iv ); 351 } 352 353template < class R ,class S > 354 __forceinlinevoid copyRow (R * rdi ,const S * rsi ,size_t length ) 355 { 356 static_assert( std::is_same < R ,S > () ); 357memcpy (rdi ,rsi ,length * sizeof (R ) ); 358 } 359template <> 360 __forceinlinevoid copyRow < uint16_t ,float > (uint16_t * rdi ,const float * rsi ,size_t length ) 361 { 362floatsDowncast (rdi ,rsi ,length ); 363 } 364template <> 365 __forceinlinevoid copyRow < float ,uint16_t > (float * rdi ,const uint16_t * rsi ,size_t length ) 366 { 367floatsUpcast (rdi ,rsi ,length ); 368 } 369 370template < class R ,class S > 371static void __declspec(noinline )copyImpl (R * rdi ,const S * rsi ,const TensorShape & shape ) 372 { 373const bool continuousRows = shape .nb [0 ]== 1 ; 374 375for (size_t i03 = 0 ;i03 < shape .ne [3 ];i03 ++ ,rsi += shape .nb [3 ] ) 376 { 377const S * source2 = rsi ; 378for (size_t i02 = 0 ;i02 < shape .ne [2 ];i02 ++ ,source2 += shape .nb [2 ] ) 379 { 380const S * source1 = source2 ; 381for (size_t i01 = 0 ;i01 < shape .ne [1 ];i01 ++ ,source1 += shape .nb [1 ] ) 382 { 383// Performance optimization here: when the rows are dense, we can copy them much faster with memcpy() 384// Or at least with AVX, when we need to convert between numeric types 385if (continuousRows ) 386 { 387// This branch is very predictable, same outcome for all loop iterations 388copyRow (rdi ,source1 ,shape .ne [0 ] ); 389rdi += shape .ne [0 ]; 390 } 391else 392 { 393const S * source0 = source1 ; 394for (size_t i00 = 0 ;i00 < shape .ne [0 ];i00 ++ ,source0 += shape .nb [0 ] ) 395 { 396copyElement (rdi ,source0 ); 397rdi ++ ; 398 } 399 } 400 } 401 } 402 } 403 } 404} 405 406HRESULT MlContext ::copyImpl (Tensor & result ,const Tensor & source ) 407{ 408if ( !(result .isContinuous ()&& (result .countElements ()== source .countElements () ) ) ) 409return E_INVALIDARG ; 410 411const eDataType typeResult = result .type (); 412const eDataType typeSource = source .type (); 413if (source .isContinuous () ) 414 { 415const size_t elts = result .countElements (); 416if (typeResult == typeSource ) 417 { 418const size_t bytes = elts * elementSize (typeResult ); 419memcpy (result .data (),source .data (),bytes ); 420return S_OK ; 421 } 422if (typeSource == eDataType::FP16 && typeResult == eDataType::FP32 ) 423 { 424floatsUpcast (result .fp32 (),source .fp16 (),elts ); 425return S_OK ; 426 } 427if (typeSource == eDataType::FP32 && typeResult == eDataType::FP16 ) 428 { 429floatsDowncast (result .fp16 (),source .fp32 (),elts ); 430return S_OK ; 431 } 432return E_UNEXPECTED ; 433 } 434else 435 { 436if (typeSource == eDataType::FP16 && typeResult == eDataType::FP16 ) 437 { 438 ::copyImpl (result .fp16 (),source .fp16 (),source ); 439return S_OK ; 440 } 441if (typeSource == eDataType::FP32 && typeResult == eDataType::FP32 ) 442 { 443 ::copyImpl (result .fp32 (),source .fp32 (),source ); 444return S_OK ; 445 } 446if (typeSource == eDataType::FP16 && typeResult == eDataType::FP32 ) 447 { 448 ::copyImpl (result .fp32 (),source .fp16 (),source ); 449return S_OK ; 450 } 451if (typeSource == eDataType::FP32 && typeResult == eDataType::FP16 ) 452 { 453 ::copyImpl (result .fp16 (),source .fp32 (),source ); 454return S_OK ; 455 } 456return E_UNEXPECTED ; 457 } 458} 459 460Tensor MlContext ::copy (const Tensor & a ,eDataType type , std::initializer_list < uint32_t > size ) 461{ 462const size_t dims = size .size (); 463if (0 == dims || dims > 4 ) 464throw E_BOUNDS ; 465 466size_t nRequested = 1 ; 467for (size_t i = 0 ;i < dims ;i ++ ) 468 { 469uint32_t n = size .begin ()[i ]; 470nRequested *=n ; 471 } 472if (nRequested != a .countElements () ) 473throw E_INVALIDARG ; 474 475if (a .type ()== type && a .isContinuous () ) 476 { 477// Same type, and it's dense - no need to move data, equal to reshape 478Tensor res {a }; 479for (size_t i = 0 ;i < dims ;i ++ ) 480res .ne [i ]= size .begin ()[i ];; 481for (size_t i = dims ;i < 4 ;i ++ ) 482res .ne [i ]= 1 ; 483res .setDenseStrides (); 484return res ; 485 } 486else 487 { 488// Need to convert types, and/or transpose the tensor. Make another tensor for the output 489Tensor res = createTensor (type ,size ); 490check (copyImpl (res ,a ) ); 491return res ; 492 } 493} 494 495Tensor MlContext ::permute (const Tensor & a ,uint8_t axis0 ,uint8_t axis1 ,uint8_t axis2 ,uint8_t axis3 ) 496{ 497assert (axis0 < 4 ); 498assert (axis1 < 4 ); 499assert (axis2 < 4 ); 500assert (axis3 < 4 ); 501 502assert (axis0 != axis1 ); 503assert (axis0 != axis2 ); 504assert (axis0 != axis3 ); 505assert (axis1 != axis2 ); 506assert (axis1 != axis3 ); 507assert (axis2 != axis3 ); 508 509Tensor res = a ; 510res .ne [axis0 ]= a .ne [0 ]; 511res .ne [axis1 ]= a .ne [1 ]; 512res .ne [axis2 ]= a .ne [2 ]; 513res .ne [axis3 ]= a .ne [3 ]; 514 515res .nb [axis0 ]= a .nb [0 ]; 516res .nb [axis1 ]= a .nb [1 ]; 517res .nb [axis2 ]= a .nb [2 ]; 518res .nb [axis3 ]= a .nb [3 ]; 519 520return res ; 521} 522 523void MlContext ::copyInPlace (Tensor & dest ,const Tensor & a ,eDataType type , std::initializer_list < uint32_t > size ) 524{ 525assert (type == dest .type () ); 526 527const size_t dims = size .size (); 528if (0 == dims || dims > 4 ) 529throw E_BOUNDS ; 530 531size_t nRequested = 1 ; 532for (size_t i = 0 ;i < dims ;i ++ ) 533 { 534uint32_t n = size .begin ()[i ]; 535nRequested *=n ; 536 } 537if (nRequested != a .countElements ()|| nRequested != dest .countElements () ) 538throw E_INVALIDARG ; 539 540// Reshape the destination 541for (size_t i = 0 ;i < dims ;i ++ ) 542dest .ne [i ]= size .begin ()[i ]; 543for (size_t i = dims ;i < 4 ;i ++ ) 544dest .ne [i ]= 1 ; 545dest .setDenseStrides (); 546 547// Copy the data 548check (copyImpl (dest ,a ) ); 549} 550 551void MlContext ::addInPlace (Tensor & a ,const Tensor & b ) 552{ 553if ( !(a .isContinuous ()&& b .isContinuous ()&& a .type ()== eDataType::FP32 && b .type ()== eDataType::FP32 ) ) 554throw E_NOTIMPL ; 555 556const size_t length = a .countElements (); 557addRowInPlace (a .fp32 (),b .fp32 (),length ); 558} 559 560Tensor MlContext ::add (const Tensor & a ,const Tensor & b ) 561{ 562if ( !(a .isContinuous ()&& b .isContinuous ()&& a .type ()== eDataType::FP32 && b .type ()== eDataType::FP32 ) ) 563throw E_NOTIMPL ; 564 565Tensor res = createTensor ( eDataType::FP32 ,a .ne ); 566const size_t length = a .countElements (); 567addRow (res .fp32 (),a .fp32 (),b .fp32 (),length ); 568return res ; 569} 570 571void MlContext ::addRepeatGelu (Tensor & cur ,const Tensor & b ) 572{ 573if ( !(cur .isContinuous ()&& b .isContinuous () ) ) 574throw E_INVALIDARG ; 575if ( !(cur .type ()== eDataType::FP32 && b .type ()== eDataType::FP32 ) ) 576throw E_INVALIDARG ; 577 578DispatchHelper3 helper {cur .ne [1 ],cur .ne [2 ],cur .ne [3 ] }; 579 std::array < uint32_t ,3 > idx = {0 ,0 ,0 }; 580const size_t countRows = helper .groupsCount (); 581 582const size_t innerRes = (uint32_t )cur .ne [0 ]; 583const size_t innerPattern = (uint32_t )b .ne [0 ]; 584float * rdi = cur .fp32 (); 585auto & lookupTables = getLookupTables (); 586for (size_t i = 0 ;i < countRows ;i ++ ,helper .next (idx ),rdi += innerRes ) 587 { 588 std::array < uint32_t ,3 > idxPattern ; 589idxPattern [0 ]= idx [0 ] % (uint32_t )b .ne [1 ]; 590idxPattern [1 ]= idx [1 ] % (uint32_t )b .ne [2 ]; 591idxPattern [2 ]= idx [2 ] % (uint32_t )b .ne [3 ]; 592 593const float * source = sourceRow (b .fp32 (),idxPattern ,b .nb [1 ],b .nb [2 ],b .nb [3 ] ); 594addRepeatGeluRow (rdi ,innerRes ,source ,innerPattern ,lookupTables ); 595 } 596return ; 597}