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
43e9180
master
1// Matrix * row product, like [ E0, E1, E2, E3 ] * [ E0, 1, E2, E3 ] = [ E1, 1, E2, E3 ] 2// Dispatch [ ( E1 + TILE_Y - 1 ) / TILE_Y, E2, E3 ] thread groups of this shader 3// This one here is the second most expensive shader in the model, after matrix*matrix product. 4// Optimized heavily, as a result the readability ain't great. 5 6#ifndef TILE_Y 7static const uint TILE_Y = 64 ; 8#endif 9#ifndef THREADS_X 10static const uint THREADS_X = 32 ; 11#endif 12#ifndef THREADS_Y 13static const uint THREADS_Y = 16 ; 14#endif 15 16Buffer < float > arg0 :register ( t0 ); 17Buffer < float > arg1 :register ( t1 ); 18RWBuffer < float > result :register ( u0 ); 19 20cbuffer Constants :register ( b0 ) 21{ 22uint4 arg0Size :packoffset ( c0 ); 23uint4 arg0Strides :packoffset ( c1 ); 24uint4 arg1Size :packoffset ( c2 ); 25uint4 arg1Strides :packoffset ( c3 ); 26uint4 resultSize :packoffset ( c4 ); 27uint4 resultStrides :packoffset ( c5 ); 28} 29 30inline uint hadd ( uint2 vec ) 31{ 32return vec . x + vec . y ; 33} 34 35// Count of FP32 accumulators we need in every thread of the shader 36static const uint heightScalars = TILE_Y / THREADS_Y ; 37// The local accumulators are float4 vectors, compute count of these vectors 38static const uint heightVectors = ( heightScalars + 3 ) / 4 ; 39 40groupsharedfloat4 reductionBuffer [ heightVectors ][ THREADS_Y ][ THREADS_X ]; 41 42[ numthreads ( THREADS_X , THREADS_Y , 1 )] 43void main ( uint3 group : SV_GroupID , uint3 thread : SV_GroupThreadID ) 44{ 45uint i ; 46// Despite inside GPU cores, the shared memory is still much slower than registers 47// For this reason, this shader accumulates numbers in local variables. Only uses groupshared buffer for the final reduction. 48float4 acc [ heightVectors ]; 49// Zero out the accumulators 50[ unroll ] 51for ( i = 0 ; i < heightVectors ; i ++ ) 52acc [ i ] = 0.0 ; 53 54// Count of rows to compute in this thread group 55const uint height = min ( TILE_Y , arg0Size . y - group . x * TILE_Y ); 56 57uint s0 = hadd ( group . yz * arg0Strides . zw ); //< arg0 layer for the thread group 58s0 += group . x * TILE_Y * arg0Strides . y ; //< arg0 first row for the thread group 59s0 += hadd ( arg0Strides . xy * thread . xy ); //< arg0 load index for the thread 60 61uint s1 = hadd ( group . yz * arg1Strides . zw ); //< arg1 layer for the thread group 62s1 += thread . x * arg1Strides . x ; //< arg1 load index for the thread 63 64const uint completeTiles = arg0Size . x / THREADS_X ; 65// Each iteration of that loop loads THREADS_X elements from arg1, 66// a block of [ THREADS_X, height ] elements from arg0, 67// and accumulates these dot products in the local variables 68for ( uint t = 0 ; t < completeTiles ; t ++ , s0 += THREADS_X * arg0Strides . x , s1 += THREADS_X * arg1Strides . x ) 69{ 70// Load THREADS_X elements from arg1 71const float v1 = arg1 [ s1 ]; 72 73uint rsi = s0 ; 74[ unroll ] 75for ( i = 0 ; i < heightVectors ; i ++ ) 76{ 77float4 v0 = 0.0 ; 78// Load up to 4*THREADS_X elements from arg0 79[ unroll ] 80for ( uint j = 0 ; j < 4 ; j ++ , rsi += arg0Strides . y * THREADS_Y ) 81{ 82const uint y = ( i * 4 + j ) * THREADS_Y + thread . y ; 83[ branch ] 84if ( y < height ) 85v0 [ j ] = arg0 [ rsi ]; 86} 87// Multiply + accumulate 88acc [ i ] = mad ( v0 , v1 , acc [ i ] ); 89} 90} 91 92const uint rem = arg0Size . x % THREADS_X ; 93if ( thread . x < rem ) 94{ 95// E0 ain't a multiple of THREADS_X, we have a remainder 96 97// Load `rem` elements from arg1 98const float v1 = arg1 [ s1 ]; 99 100[ unroll ] 101for ( i = 0 ; i < heightVectors ; i ++ ) 102{ 103float4 v0 = 0.0 ; 104// Load up to 4*rem elements from arg0 105[ unroll ] 106for ( uint j = 0 ; j < 4 ; j ++ , s0 += arg0Strides . y * THREADS_Y ) 107{ 108const uint y = ( i * 4 + j ) * THREADS_Y + thread . y ; 109[ branch ] 110if ( y < height ) 111v0 [ j ] = arg0 [ s0 ]; 112} 113// Multiply + accumulate 114acc [ i ] = mad ( v0 , v1 , acc [ i ] ); 115} 116} 117 118// Now we need horizontal sum of these accumulators, reducing [height][THREADS_X] of them into [height][1] column 119// First, store local variables into the shared memory. 120[ unroll ] 121for ( i = 0 ; i < heightVectors ; i ++ ) 122reductionBuffer [ i ][ thread . y ][ thread . x ] = acc [ i ]; 123GroupMemoryBarrierWithGroupSync (); 124 125// Run reduction using that shared memory buffer 126for ( i = THREADS_X / 2 ; i > 1 ; i /= 2 ) 127{ 128if ( thread . x < i ) 129{ 130[ unroll ] 131for ( uint iv = 0 ; iv < heightVectors ; iv ++ ) 132{ 133float4 that = reductionBuffer [ iv ][ thread . y ][ thread . x + i ]; 134float4 tmp = acc [ iv ]; 135tmp += that ; 136reductionBuffer [ iv ][ thread . y ][ thread . x ] = tmp ; 137acc [ iv ] = tmp ; 138} 139} 140GroupMemoryBarrierWithGroupSync (); 141} 142 143// And finally, store that column to global memory. 144// Only running that code on the threads of the group with thread.x = 0, to save a few loads from the groupshared buffer 145// This allows to use registers instead, faster to access 146if ( thread . x != 0 ) 147return ; 148 149uint rdi = hadd ( group . yz * resultStrides . zw ); 150rdi += ( group . x * TILE_Y + thread . y ) * resultStrides . x ; 151const uint rdiInc = THREADS_Y * resultStrides . x ; 152 153[ unroll ] 154for ( i = 0 ; i < heightVectors ; i ++ ) 155{ 156// The previous loop had "i > 1" continue condition, it didn't complete the last step of the reduction 157// The following line is doing that last reduction step 158const float4 resultVec = acc [ i ] + reductionBuffer [ i ][ thread . y ][ 1 ]; 159 160// Conditionally store these 4 floats to the output tensor 161[ unroll ] 162for ( uint j = 0 ; j < 4 ; j ++ , rdi += rdiInc ) 163{ 164const uint y = ( i * 4 + j ) * THREADS_Y + thread . y ; 165[ branch ] 166if ( y < height ) 167result [ rdi ] = resultVec [ j ]; 168} 169} 170}