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, micro-optimization15dbcac

master
7.0 KiB292 linesraw
1#include "stdafx.h"
2#include "MlContext.h"
3#include "testUtils.h"
4using namespace DirectCompute;
5
6Tensor MlContext::createTensor( eDataType type, const std::array<uint32_t, 4>& ne )
7{
8	Tensor res;
9	check( res.create( type, ne ) );
10	return res;
11}
12
13Tensor MlContext::createTensor( eDataType type, std::initializer_list<uint32_t> ne )
14{
15	size_t nDims = ne.size();
16	if( 0 == nDims || nDims > 4 )
17		throw E_INVALIDARG;
18	std::array<uint32_t, 4> arr;
19	for( size_t i = 0; i < nDims; i++ )
20		arr[ i ] = ne.begin()[ i ];
21	for( size_t i = nDims; i < 4; i++ )
22		arr[ i ] = 1;
23	return createTensor( type, arr );
24}
25
26Tensor MlContext::conv_1d_1s( const Tensor& a, const Tensor& b )
27{
28	assert( b.isMatrix() );
29	assert( a.ne[ 1 ] == b.ne[ 1 ] );
30	assert( a.ne[ 3 ] == 1 );
31
32	Tensor res = createTensor( eDataType::FP32, { b.ne[ 0 ], a.ne[ 2 ] } );
33
34	convolution( a, b, res );
35	return res;
36}
37
38Tensor MlContext::conv_1d_2s( const Tensor& a, const Tensor& b )
39{
40	assert( b.isMatrix() );
41	assert( a.ne[ 1 ] == b.ne[ 1 ] );
42	assert( a.ne[ 3 ] == 1 );
43
44	Tensor res = createTensor( eDataType::FP32, { b.ne[ 0 ] / 2, a.ne[ 2 ] } );
45#if 0
46	static PrintUniqueTensorSizes printSize( "conv_1d_2s" );
47	printSize.print( a, b );
48#endif
49	convolution2( a, b, res );
50	return res;
51}
52
53namespace
54{
55	inline bool canRepeat( const TensorShape& t0, const TensorShape& t1 )
56	{
57		return ( t1.ne[ 0 ] % t0.ne[ 0 ] == 0 ) &&
58			( t1.ne[ 1 ] % t0.ne[ 1 ] == 0 ) &&
59			( t1.ne[ 2 ] % t0.ne[ 2 ] == 0 ) &&
60			( t1.ne[ 3 ] % t0.ne[ 3 ] == 0 );
61	}
62}
63
64Tensor MlContext::cwiseBinary( const Tensor& a, const Tensor& b, eComputeShader cs )
65{
66	assert( isSameShape( a, b ) );
67	Tensor res = createTensor( a.getType(), a.ne );
68	cwiseBinary( a, b, res, cs );
69	return res;
70}
71
72Tensor __declspec( noinline ) MlContext::view2d( const Tensor& a, uint32_t ne0, uint32_t ne1, uint32_t nb1, uint32_t offset )
73{
74	if( 0 != offset )
75		throw E_NOTIMPL;
76
77	Tensor res = a;
78	res.ne = { ne0, ne1, 1, 1 };
79
80	res.nb[ 1 ] = nb1;
81	res.nb[ 2 ] = res.nb[ 3 ] = nb1 * ne1;
82	return res;
83}
84
85Tensor MlContext::transpose( const Tensor& a )
86{
87	Tensor result;
88
89	// A magic number for _mm_shuffle_epi32 SSE2 instruction to swap two lower int32 lanes in a vector
90	constexpr int swapXy = _MM_SHUFFLE( 3, 2, 0, 1 );
91
92	__m128i v = a.sizeVec();
93	v = _mm_shuffle_epi32( v, swapXy );
94	store( result.ne, v );
95
96	v = a.stridesVec();
97	v = _mm_shuffle_epi32( v, swapXy );
98	store( result.nb, v );
99
100	result.setGpuViews( a, a );
101	return result;
102}
103
104Tensor MlContext::norm( const Tensor& a )
105{
106	Tensor res = createTensor( a.getType(), a.ne );
107	norm( a, res );
108	return res;
109}
110
111Tensor MlContext::mulMat( const Tensor& a, const Tensor& b )
112{
113	if( !canMulMat( a, b ) )
114		throw E_INVALIDARG;
115	Tensor res = createTensor( eDataType::FP32, { a.ne[ 1 ], b.ne[ 1 ], a.ne[ 2 ], b.ne[ 3 ] } );
116	if constexpr( enableInexactOptimizations )
117		mulMatTiled( a, b, res );
118	else
119		mulMat( a, b, res );
120#if 0
121	Tensor testTiled;
122	check( testTiled.create( eDataType::FP32, res.ne ) );
123	mulMatTiled( a, b, testTiled );
124
125	std::vector<float> current, tiled;
126	res.download( current );
127	testTiled.download( tiled );
128	sTensorDiff diff = computeDiff( current.data(), tiled.data(), current.size() );
129	diff.print( "mulMatTiled" );
130#endif
131	return res;
132}
133
134Tensor MlContext::mulMatEx( const Tensor& a, const Tensor& b, const char* tagName )
135{
136	if( !canMulMat( a, b ) )
137		throw E_INVALIDARG;
138	if( 0 != a.nb[ 0 ] )
139		throw E_INVALIDARG;	// The first argument is expected to be pre-transposed
140	
141	const uint16_t tag = profiler.setNextTag( tagName );
142
143	if( b.ne[ 1 ] != 1 )
144	{
145		if( b.nb[ 0 ] != 0 )
146		{
147			Tensor rhs = reshapePanels( b );
148			profiler.setNextTag( tag );
149			return mulMatTiledEx( a, rhs );
150		}
151		else
152		{
153			// Second argument already reshaped into these panels
154			return mulMatTiledEx( a, b );
155		}
156	}
157	else
158	{
159		if( 0 != b.nb[ 0 ] )
160			return mulMatByRowTiledEx( a, b );
161
162		// That shader requires classic VRAM layout of the second argument, gonna fail with pre-transposed one
163		throw E_INVALIDARG;
164	}
165}
166
167Tensor MlContext::permute( const Tensor& a, uint8_t axis0, uint8_t axis1, uint8_t axis2, uint8_t axis3 )
168{
169	assert( axis0 < 4 );
170	assert( axis1 < 4 );
171	assert( axis2 < 4 );
172	assert( axis3 < 4 );
173
174	assert( axis0 != axis1 );
175	assert( axis0 != axis2 );
176	assert( axis0 != axis3 );
177	assert( axis1 != axis2 );
178	assert( axis1 != axis3 );
179	assert( axis2 != axis3 );
180
181	Tensor res = a;
182	res.ne[ axis0 ] = a.ne[ 0 ];
183	res.ne[ axis1 ] = a.ne[ 1 ];
184	res.ne[ axis2 ] = a.ne[ 2 ];
185	res.ne[ axis3 ] = a.ne[ 3 ];
186
187	res.nb[ axis0 ] = a.nb[ 0 ];
188	res.nb[ axis1 ] = a.nb[ 1 ];
189	res.nb[ axis2 ] = a.nb[ 2 ];
190	res.nb[ axis3 ] = a.nb[ 3 ];
191	return res;
192}
193
194Tensor MlContext::flashAttention( const Tensor& q, const Tensor& k, const Tensor& v, bool masked )
195{
196	if( !canMulMat( k, q ) )
197		throw E_INVALIDARG;
198
199	if constexpr( enableInexactOptimizations )
200	{
201		if( !masked )
202		{
203			profiler.setNextTag( "flashAttn.1" );
204			Tensor tmp = mulMat( k, q );
205
206			profiler.setNextTag( "flashAttention" );
207			const float tempScale = (float)( 1.0 / sqrt( (double)(int)q.ne[ 0 ] ) );
208			softMax( tmp, tempScale );
209
210			profiler.setNextTag( "flashAttn.2" );
211			return mulMat( v, tmp );
212		}
213	}
214
215	Tensor res = createTensor( eDataType::FP32, q.ne );
216	flashAttention( q, k, v, res, masked );
217
218#if 0
219	Tensor tmpMat = mulMat( k, q );
220	float scale = (float)( 1.0 / sqrt( (double)(int)q.ne[ 0 ] ) );
221	softMax( tmpMat, scale );
222	Tensor testRes = mulMat( v, tmpMat );
223	computeDiff( res, testRes ).print( "flashAttention mulmat" );
224#endif
225
226	return res;
227}
228
229Tensor MlContext::copy( const Tensor& a, eDataType type, std::initializer_list<uint32_t> size )
230{
231	const size_t dims = size.size();
232	if( 0 == dims || dims > 4 )
233		throw E_BOUNDS;
234
235	size_t nRequested = 1;
236	for( size_t i = 0; i < dims; i++ )
237	{
238		uint32_t n = size.begin()[ i ];
239		nRequested *= n;
240	}
241	if( nRequested != a.countElements() )
242		throw E_INVALIDARG;
243
244	const eDataType st = a.getType();
245	Tensor res;
246	if( a.isContinuous() && st == type )
247	{
248		// Same type, and it's dense - no need to call any compute shaders, equal to reshape
249		res = a;
250		for( size_t i = 0; i < dims; i++ )
251			res.ne[ i ] = size.begin()[ i ];;
252		for( size_t i = dims; i < 4; i++ )
253			res.ne[ i ] = 1;
254		res.setDenseStrides();
255	}
256	else
257	{
258		// Either converting non-continuous to continuous, or converting types
259		res = createTensor( type, size );
260		copyImpl( a, res, st == eDataType::FP32 && type == eDataType::FP16 );
261	}
262	return res;
263}
264
265void MlContext::copyInPlace( Tensor& dest, const Tensor& a, eDataType type, std::initializer_list<uint32_t> size )
266{
267	assert( type == dest.getType() );
268
269	const size_t dims = size.size();
270	if( 0 == dims || dims > 4 )
271		throw E_BOUNDS;
272
273	size_t nRequested = 1;
274	for( size_t i = 0; i < dims; i++ )
275	{
276		uint32_t n = size.begin()[ i ];
277		nRequested *= n;
278	}
279	if( nRequested != a.countElements() || nRequested != dest.countElements() )
280		throw E_INVALIDARG;
281
282	// Reshape the destination
283	for( size_t i = 0; i < dims; i++ )
284		dest.ne[ i ] = size.begin()[ i ];
285	for( size_t i = dims; i < 4; i++ )
286		dest.ne[ i ] = 1;
287	dest.setDenseStrides();
288
289	// Call the shader
290	const eDataType st = a.getType();
291	copyImpl( a, dest, st == eDataType::FP32 && type == eDataType::FP16 );
292}