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

KonstantinMinor, cleanup unused43e9180

master
5.3 KiB170 linesraw
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{
22	uint4 arg0Size: packoffset( c0 );
23	uint4 arg0Strides: packoffset( c1 );
24	uint4 arg1Size: packoffset( c2 );
25	uint4 arg1Strides: packoffset( c3 );
26	uint4 resultSize: packoffset( c4 );
27	uint4 resultStrides: packoffset( c5 );
28}
29
30inline uint hadd( uint2 vec )
31{
32	return 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
40groupshared float4 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{
45	uint 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.
48	float4 acc[ heightVectors ];
49	// Zero out the accumulators
50	[unroll]
51	for( i = 0; i < heightVectors; i++ )
52		acc[ i ] = 0.0;
53
54	// Count of rows to compute in this thread group
55	const uint height = min( TILE_Y, arg0Size.y - group.x * TILE_Y );
56
57	uint s0 = hadd( group.yz * arg0Strides.zw );   //< arg0 layer for the thread group
58	s0 += group.x * TILE_Y * arg0Strides.y;        //< arg0 first row for the thread group
59	s0 += hadd( arg0Strides.xy * thread.xy );      //< arg0 load index for the thread
60
61	uint s1 = hadd( group.yz * arg1Strides.zw );   //< arg1 layer for the thread group
62	s1 += thread.x * arg1Strides.x;                //< arg1 load index for the thread
63
64	const 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
68	for( uint t = 0; t < completeTiles; t++, s0 += THREADS_X * arg0Strides.x, s1 += THREADS_X * arg1Strides.x )
69	{
70		// Load THREADS_X elements from arg1
71		const float v1 = arg1[ s1 ];
72
73		uint rsi = s0;
74		[unroll]
75		for( i = 0; i < heightVectors; i++ )
76		{
77			float4 v0 = 0.0;
78			// Load up to 4*THREADS_X elements from arg0
79			[unroll]
80			for( uint j = 0; j < 4; j++, rsi += arg0Strides.y * THREADS_Y )
81			{
82				const uint y = ( i * 4 + j ) * THREADS_Y + thread.y;
83				[branch]
84				if( y < height )
85					v0[ j ] = arg0[ rsi ];
86			}
87			// Multiply + accumulate
88			acc[ i ] = mad( v0, v1, acc[ i ] );
89		}
90	}
91
92	const uint rem = arg0Size.x % THREADS_X;
93	if( thread.x < rem )
94	{
95		// E0 ain't a multiple of THREADS_X, we have a remainder
96
97		// Load `rem` elements from arg1
98		const float v1 = arg1[ s1 ];
99
100		[unroll]
101		for( i = 0; i < heightVectors; i++ )
102		{
103			float4 v0 = 0.0;
104			// Load up to 4*rem elements from arg0
105			[unroll]
106			for( uint j = 0; j < 4; j++, s0 += arg0Strides.y * THREADS_Y )
107			{
108				const uint y = ( i * 4 + j ) * THREADS_Y + thread.y;
109				[branch]
110				if( y < height )
111					v0[ j ] = arg0[ s0 ];
112			}
113			// Multiply + accumulate
114			acc[ 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 ]
121	for( i = 0; i < heightVectors; i++ )
122		reductionBuffer[ i ][ thread.y ][ thread.x ] = acc[ i ];
123	GroupMemoryBarrierWithGroupSync();
124
125	// Run reduction using that shared memory buffer
126	for( i = THREADS_X / 2; i > 1; i /= 2 )
127	{
128		if( thread.x < i )
129		{
130			[unroll]
131			for( uint iv = 0; iv < heightVectors; iv++ )
132			{
133				float4 that = reductionBuffer[ iv ][ thread.y ][ thread.x + i ];
134				float4 tmp = acc[ iv ];
135				tmp += that;
136				reductionBuffer[ iv ][ thread.y ][ thread.x ] = tmp;
137				acc[ iv ] = tmp;
138			}
139		}
140		GroupMemoryBarrierWithGroupSync();
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
146	if( thread.x != 0 )
147		return;
148
149	uint rdi = hadd( group.yz * resultStrides.zw );
150	rdi += ( group.x * TILE_Y + thread.y ) * resultStrides.x;
151	const uint rdiInc = THREADS_Y * resultStrides.x;
152
153	[unroll]
154	for( 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
158		const float4 resultVec = acc[ i ] + reductionBuffer[ i ][ thread.y ][ 1 ];
159
160		// Conditionally store these 4 floats to the output tensor
161		[unroll]
162		for( uint j = 0; j < 4; j++, rdi += rdiInc )
163		{
164			const uint y = ( i * 4 + j ) * THREADS_Y + thread.y;
165			[branch]
166			if( y < height )
167				result[ rdi ] = resultVec[ j ];
168		}
169	}
170}