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

KonstantinSource codes8c4603c

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