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

KonstantinPerformance tuning on AMD iGPUe78815d

master
6.7 KiB228 linesraw
1// This compute shader implements yet another version of matrix*matrix product
2// For optimal VRAM access pattern, it requires both arguments to be reshaped into a sequence of horizontal column major panels.
3// The panel height is TILE_SIZE, and the last panel of the matrix needs to be padded with zeros; see matReshapePanels.hlsl shader for the reshaping.
4// So far, it's only used when running on AMD GPUs.
5#ifndef TILE_SIZE
6static const uint TILE_SIZE = 32;
7#endif
8#ifndef TILE_HEIGHT
9static const uint TILE_HEIGHT = 64;
10#endif
11#ifndef THREADS_Y
12static const uint THREADS_Y = 8;
13#endif
14// The above values have a following constraint: TILE_SIZE = THREADS_Y * N * 4 where N is an integer
15
16#ifndef STREAM_SECOND_MATRIX
17#define STREAM_SECOND_MATRIX 1
18#endif
19
20// First tensor, reshaped into dense column major horizontal panels of size [ width, TILE_SIZE ]
21Buffer<float> arg0: register( t0 );
22// Second tensor, reshaped into dense column major horizontal panels of size [ width, TILE_SIZE ]
23Buffer<float> arg1: register( t1 );
24// FP32 output tensor, row major and continuous
25RWBuffer<float> result: register( u0 );
26
27cbuffer Constants: register( b0 )
28{
29	uint4 arg0Size: packoffset( c0 );
30	uint arg0panel: packoffset( c1.y );
31	uint2 arg0LayerStrides: packoffset( c1.z );
32
33	// uint4 arg1Size: packoffset( c2 );
34	uint arg1panel: packoffset( c3.y );
35	uint2 arg1LayerStrides: packoffset( c3.z );
36
37	uint4 resultSize: packoffset( c4 );
38	uint4 resultStrides: packoffset( c5 );
39}
40
41// A smaller tile loaded from the first source matrix
42groupshared float tile0[ TILE_HEIGHT ][ TILE_SIZE ];
43#if !STREAM_SECOND_MATRIX
44// A smaller tile loaded from the second source matrix
45groupshared float tile1[ TILE_HEIGHT ][ TILE_SIZE ];
46#endif
47
48// Count of FP32 accumulators we need in every thread of the shader
49static const uint heightScalars = TILE_SIZE / THREADS_Y;
50// The local accumulators are float4 vectors, compute count of these vectors
51static const uint heightVectors = ( heightScalars + 3 ) / 4;
52
53#if STREAM_SECOND_MATRIX
54void multiplyTiles( const uint3 thread, uint rsi, const uint h, inout float4 acc[ heightVectors ] )
55{
56	uint4 rsi4 = rsi + uint4( 0, THREADS_Y, THREADS_Y * 2, THREADS_Y * 3 );
57	[unroll]
58	for( uint iv = 0; iv < heightVectors; iv++, rsi4 += THREADS_Y * 4 )
59	{
60		float4 r = 0.0;
61		uint4 rsiRow = rsi4;
62		for( uint j = 0; j < h; j++, rsiRow += TILE_SIZE )
63		{
64			const float a = tile0[ j ][ thread.x ];
65			float4 b = 0.0;
66			[unroll]
67			for( uint k = 0; k < 4; k++ )
68			{
69				b[ k ] = arg1[ rsiRow[ k ] ];
70			}
71			r = mad( a, b, r );
72		}
73		acc[ iv ] += r;
74	}
75}
76#else
77void multiplyTiles( const uint3 thread, inout float4 acc[ heightVectors ] )
78{
79	[unroll]
80	for( uint i = 0; i < heightVectors; i++ )
81	{
82		float4 r = 0.0;
83		for( uint j = 0; j < TILE_HEIGHT; j++ )
84		{
85			const float a = tile0[ j ][ thread.x ];
86			float4 b;
87			[unroll]
88			for( uint k = 0; k < 4; k++ )
89			{
90				const uint row = ( i * 4 + k ) * THREADS_Y + thread.y;
91				b[ k ] = tile1[ j ][ row ];
92			}
93			r = mad( a, b, r );
94		}
95		acc[ i ] += r;
96	}
97}
98#endif
99
100void storeTile( const uint3 thread, const uint4 pos, const uint2 size, in float4 acc[ heightVectors ] )
101{
102	if( thread.x >= size.x )
103		return;
104
105	const uint4 prod4 = pos * resultStrides;
106	const uint2 prod2 = prod4.xy + prod4.zw;
107	uint rdi = prod2.x + prod2.y;
108	rdi += resultStrides.y * thread.y;
109	rdi += resultStrides.x * thread.x;
110
111	const uint4 offsets = THREADS_Y * uint4( 0, 1, 2, 3 );	//< a compile-time constant vector
112	uint4 rdi4 = resultStrides.y * offsets + rdi;
113
114	[unroll]
115	for( uint iv = 0; iv < heightVectors; iv++, rdi4 += resultStrides.y * THREADS_Y * 4 )
116	{
117		const float4 source = acc[ iv ];
118		[unroll]
119		for( uint k = 0; k < 4; k++ )
120		{
121			const uint i = ( iv * 4 + k ) * THREADS_Y + thread.y;
122			if( i < size.y )
123				result[ rdi4[ k ] ] = source[ k ];
124		}
125	}
126}
127
128[numthreads( TILE_SIZE, THREADS_Y, 1 )]
129void main( const uint3 group: SV_GroupID, const uint3 thread : SV_GroupThreadID )
130{
131	uint i;
132	// Zero all shared buffers
133	for( i = thread.y; i < TILE_HEIGHT; i += THREADS_Y )
134	{
135		tile0[ i ][ thread.x ] = 0.0;
136#if !STREAM_SECOND_MATRIX
137		tile1[ i ][ thread.x ] = 0.0;
138#endif
139	}
140	// Despite inside GPU cores, the shared memory is still much slower than registers
141	// For this reason, this shader accumulates numbers in local variables. Only uses groupshared memory for tiles of the argument matrices.
142	float4 acc[ heightVectors ];
143	// Zero out the accumulators
144	[unroll]
145	for( i = 0; i < heightVectors; i++ )
146		acc[ i ] = 0.0;
147
148	const uint2 layer = uint2( group.z % resultSize.z, group.z / resultSize.z );
149
150	uint rsi0 = group.x * arg0panel + layer.x * arg0LayerStrides.x + layer.y * arg0LayerStrides.y;
151	uint rsi1 = group.y * arg1panel + layer.x * arg1LayerStrides.x + layer.y * arg1LayerStrides.y;
152
153	const uint threadOffset = thread.y * TILE_SIZE + thread.x;
154	rsi0 += threadOffset;
155#if STREAM_SECOND_MATRIX
156	rsi1 += thread.y;
157#else
158	rsi1 += threadOffset;
159#endif
160
161	const uint completeTiles = arg0Size.x / TILE_HEIGHT;
162	for( i = 0; i < completeTiles; i++ )
163	{
164		// Load [ TILE_SIZE, TILE_HEIGHT ] block from both source tensors into these groupshared buffers
165		for( uint j = thread.y; j < TILE_HEIGHT; j += THREADS_Y )
166		{
167			tile0[ j ][ thread.x ] = arg0[ rsi0 ];
168			rsi0 += THREADS_Y * TILE_SIZE;
169#if !STREAM_SECOND_MATRIX
170			tile1[ j ][ thread.x ] = arg1[ rsi1 ];
171			rsi1 += THREADS_Y * TILE_SIZE;
172#endif
173		}
174
175		// Wait for all threads in the group to complete these loads
176		GroupMemoryBarrierWithGroupSync();
177
178#if STREAM_SECOND_MATRIX
179		multiplyTiles( thread, rsi1, TILE_HEIGHT, acc );
180		rsi1 += TILE_HEIGHT * TILE_SIZE;
181#else
182		// Multiply + accumulate the elements collected in the groupshared buffers
183		multiplyTiles( thread, acc );
184#endif
185		GroupMemoryBarrierWithGroupSync();
186	}
187
188	const uint rem = arg0Size.x % TILE_HEIGHT;
189	if( rem != 0 )
190	{
191		// Load [ TILE_SIZE, rem ] block from both source tensors, and zero out the padding elements
192		for( uint j = thread.y; j < TILE_HEIGHT; j += THREADS_Y )
193		{
194			[branch]
195			if( j < rem )
196			{
197				tile0[ j ][ thread.x ] = arg0[ rsi0 ];
198				rsi0 += THREADS_Y * TILE_SIZE;
199#if !STREAM_SECOND_MATRIX
200				tile1[ j ][ thread.x ] = arg1[ rsi1 ];
201				rsi1 += THREADS_Y * TILE_SIZE;
202#endif
203			}
204			else
205			{
206				tile0[ j ][ thread.x ] = 0.0;
207#if !STREAM_SECOND_MATRIX
208				tile1[ j ][ thread.x ] = 0.0;
209#endif
210			}
211		}
212
213		// Wait for all threads in the group to complete these loads
214		GroupMemoryBarrierWithGroupSync();
215
216		// Multiply + accumulate the elements collected in the groupshared buffers
217#if STREAM_SECOND_MATRIX
218		multiplyTiles( thread, rsi1, rem, acc );
219#else
220		multiplyTiles( thread, acc );
221#endif
222		GroupMemoryBarrierWithGroupSync();
223	}
224
225	const uint2 resultPos = group.xy * TILE_SIZE;
226	const uint2 outputSize = min( TILE_SIZE, resultSize.xy - resultPos );
227	storeTile( thread, uint4( resultPos, layer ), outputSize, acc );
228}