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
19.6 KiB748 linesraw
1#include "stdafx.h"
2#include "MlContext.h"
3#include "../D3D/shaderNames.h"
4#include "LookupTables.h"
5#include "../D3D/shaders.h"
6#include "../D3D/Binder.h"
7#include "../D3D/MappedResource.h"
8#include "../D3D/downloadBuffer.h"
9#include "testUtils.h"
10#include "reshapedMultiply.h"
11using namespace DirectCompute;
12
13// TODO: change this to a field, and set to false when the GPU doesn't support FP64 math
14// Most notably, Intel has dropped the support recently:
15// https://www.intel.com/content/www/us/en/developer/articles/guide/lp-api-developer-optimization-guide.html#inpage-nav-3-8-undefined
16// "To improve power and performance", LOL
17constexpr bool usePreciseComputeShaders = true;
18
19MlContext::MlContext( Whisper::ProfileCollection& profileColl ) :
20	profiler( profileColl )
21{
22	check( cb.create() );
23	check( profiler.create() );
24}
25
26void MlContext::bindShader( eComputeShader cs )
27{
28	DirectCompute::bindShader( cs );
29	profiler.computeShader( cs );
30}
31
32void MlContext::mulMatDot( const Tensor& src0, const Tensor& src1, Tensor& res )
33{
34	const auto& size1 = src1.ne;
35	if( 1 != size1[ 3 ] )
36		throw E_UNEXPECTED;
37
38	const size_t tempLength = size1[ 0 ] * size1[ 1 ] * size1[ 2 ] * size1[ 3 ];
39	const TensorGpuViews& tempBuffer = temp.fp16( tempLength );
40	cb.bind();
41
42	bindShader( eComputeShader::mulMatDotReshape );
43	cb.update( src1 );
44	Binder bind;
45	bind.bind( src1, tempBuffer );
46	context()->Dispatch( size1[ 1 ], size1[ 2 ], 1 );
47
48	bindShader( eComputeShader::mulMatDotMain );
49	cb.update( src0, src1, res );
50	bind.bind( src0, tempBuffer, res );
51
52	const auto& size0 = src0.ne;
53	// total rows in src0
54	const uint32_t nr = size0[ 1 ] * size0[ 2 ] * size0[ 3 ];
55	context()->Dispatch( size1[ 1 ], nr, 1 );
56}
57
58void MlContext::mulMatMad( const Tensor& a, const Tensor& b, Tensor& res )
59{
60	// CaptureRaii renderDoc;
61	const uint32_t resultElts = res.countElements();
62	constexpr uint32_t nth = 4;
63
64	uint32_t fp16;
65	TensorGpuViews tempBuffer;
66
67	const eDataType dataType = a.getType();
68	if( dataType == eDataType::FP16 )
69	{
70		fp16 = TRUE;
71		tempBuffer = temp.fp16( resultElts * nth );
72	}
73	else if( dataType == eDataType::FP32 )
74	{
75		fp16 = FALSE;
76		tempBuffer = temp.fp32( resultElts * nth );
77	}
78	else
79		throw E_INVALIDARG;
80
81	TensorShape resultShape = res;
82	resultShape.nb = { fp16, resultElts, 0, 0 };
83
84	cb.update( a, b, resultShape );
85	bindShader( eComputeShader::mulMatMadMain );
86	cb.bind();
87
88	Binder bind;
89	bind.bind( { a, b }, { res, tempBuffer } );
90	context()->Dispatch( b.ne[ 1 ], b.ne[ 2 ], b.ne[ 3 ] );
91}
92
93void MlContext::mulMatTiled( const Tensor& a, const Tensor& b, Tensor& res )
94{
95	cb.update( a, b, res );
96	cb.bind();
97
98	Binder bind;
99	bind.bind( a, b, res );
100
101	if( b.ne[ 1 ] == 1 )
102	{
103		if( b.ne[ 0 ] != 1 )
104		{
105#if 0
106			static PrintUniqueTensorSizes printSize( "mulMatByRow" );
107			printSize.print( a, b );
108#endif
109			// Tensor B is a single row, we have optimized compute shaders for that use case
110			// Even 2 of them, tiled and sequential. Select between these two shaders.
111			constexpr uint32_t minHeightToTile = 2;
112			if( a.ne[ 1 ] < minHeightToTile )
113			{
114				bindShader( eComputeShader::mulMatByRow );
115				context()->Dispatch( a.ne[ 1 ], a.ne[ 2 ], a.ne[ 3 ] );
116			}
117			else
118			{
119				bindShader( eComputeShader::mulMatByRowTiled );
120				constexpr uint32_t TILE_Y = 64;
121				const uint32_t groupsX = ( a.ne[ 1 ] + TILE_Y - 1 ) / TILE_Y;
122				context()->Dispatch( groupsX, a.ne[ 2 ], a.ne[ 3 ] );
123			}
124		}
125		else
126		{
127			// Tensor B is a single element: we have an optimized shader for that as well
128			bindShader( eComputeShader::mulMatByScalar );
129			context()->Dispatch( a.ne[ 2 ], a.ne[ 3 ], 1 );
130		}
131	}
132	else
133	{
134		// According to visual studio debugger, when the second argument of this method is a 2D matrix, the first argument is 2D as well.
135		// Assuming both arguments are 2D matrices.
136		// For optimal VRAM bandwidth utilization, we compute such matrix products in square tiles, a tile is 32x32 elements.
137		// Dispatching one thread group for each tile of the output matrix.
138		bindShader( eComputeShader::mulMatTiled );
139
140		// These compute shaders correctly handle partial tiles on the right and bottom edges of the output matrix, that's why rounding up
141		constexpr uint32_t TILE_SIZE = 32;
142		const uint32_t x = ( res.ne[ 0 ] + TILE_SIZE - 1 ) / TILE_SIZE;
143		const uint32_t y = ( res.ne[ 1 ] + TILE_SIZE - 1 ) / TILE_SIZE;
144
145		const uint32_t z = res.ne[ 2 ] * res.ne[ 3 ];
146		context()->Dispatch( x, y, z );
147	}
148}
149
150void MlContext::mulMat( const Tensor& src0, const Tensor& src1, Tensor& res )
151{
152	const uint32_t nb00 = src0.nb[ 0 ];
153	const uint32_t nb01 = src0.nb[ 1 ];
154	if( nb01 >= nb00 )
155		mulMatDot( src0, src1, res );
156	else
157		mulMatMad( src0, src1, res );
158}
159
160namespace
161{
162	// Must match the HLSL in flashAttention.hlsl
163	struct sFlashAttentionConstants
164	{
165		TensorShape q, k, v, res;
166		BOOL masked;
167		float scale;
168		uint32_t tempBufferStride;
169		uint32_t zzPadding;
170	};
171
172	struct sFlashAttnDispatchInfo
173	{
174		uint32_t tempStride;
175		uint32_t groupsCount;
176	};
177
178	sFlashAttnDispatchInfo makeFlashAttentionConstants( CComPtr<ID3D11Buffer>& buffer, const Tensor& q, const Tensor& k, const Tensor& v, Tensor& res, bool masked )
179	{
180		if( nullptr == buffer )
181		{
182			CD3D11_BUFFER_DESC desc{ sizeof( sFlashAttentionConstants ), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE };
183			check( device()->CreateBuffer( &desc, nullptr, &buffer ) );
184		}
185
186		sFlashAttnDispatchInfo result;
187
188		sFlashAttentionConstants cb;
189		cb.q = q;
190		cb.k = k;
191		cb.v = v;
192		cb.res = res;
193		cb.masked = masked ? TRUE : FALSE;
194
195		const int neq0 = (int)cb.q.ne[ 0 ];
196		const int D = neq0;
197		cb.scale = (float)( 1.0 / sqrt( (double)(int)D ) );
198
199		const uint32_t nek1 = cb.k.ne[ 1 ];
200		constexpr uint32_t align = 32 / 4;
201		result.tempStride = ( ( nek1 + align - 1 ) / align ) * align;
202		cb.tempBufferStride = result.tempStride;
203		cb.zzPadding = 0;
204		result.groupsCount = cb.q.ne[ 1 ] * cb.q.ne[ 2 ] * cb.q.ne[ 3 ];
205
206		MappedResource mapped;
207		check( mapped.map( buffer, false ) );
208		memcpy( mapped.data(), &cb, sizeof( cb ) );
209		return result;
210	}
211}
212
213void MlContext::flashAttention( const Tensor& q, const Tensor& k, const Tensor& v, Tensor& res, bool masked )
214{
215	sFlashAttnDispatchInfo di = makeFlashAttentionConstants( flashAttentionConstants, q, k, v, res, masked );
216
217	const uint32_t tempLength = di.tempStride * di.groupsCount;
218	const TensorGpuViews& tb = temp.fp32( tempLength );
219
220	csSetCB( flashAttentionConstants );
221	ID3D11DeviceContext* const ctx = context();
222
223	Binder bind;
224	bind.bind( { q, k, v, lookupTables.exponent() }, { res, tb } );
225
226	if constexpr( usePreciseComputeShaders && !enableInexactOptimizations )
227	{
228		bindShader( eComputeShader::flashAttentionCompat1 );
229		ctx->Dispatch( di.groupsCount, 1, 1 );
230
231		bindShader( eComputeShader::flashAttentionCompat2 );
232		ctx->Dispatch( ( di.groupsCount + 31 ) / 32, 1, 1 );
233
234		bindShader( eComputeShader::flashAttentionCompat3 );
235		ctx->Dispatch( di.groupsCount, 1, 1 );
236	}
237	else
238	{
239		// This version is not too bad, e.g. maxAbsDiff = 2.7895e-05, avgDiffSquared = 1.61783e-14
240		// And probably much faster.
241		// But still, it does not deliver bitwise equality with the reference CPU version
242		bindShader( eComputeShader::flashAttention );
243		ctx->Dispatch( di.groupsCount, 1, 1 );
244	}
245}
246
247namespace
248{
249	// Round up the number to be a multiple of 32
250	inline uint32_t roundUp32( uint32_t x )
251	{
252		return ( x + 31 ) & ( ~31u );
253	}
254}
255
256void MlContext::convolutionImpl( const Tensor& a, const Tensor& b, Tensor& res, bool is2 )
257{
258	const uint32_t ne00 = a.ne[ 0 ];
259	const uint32_t ne01 = a.ne[ 1 ];
260	const uint32_t ne02 = a.ne[ 2 ];
261
262	const uint32_t ne10 = b.ne[ 0 ];
263	const uint32_t ne11 = b.ne[ 1 ];
264
265	const uint32_t nb00 = a.nb[ 0 ];
266	const uint32_t nb01 = a.nb[ 1 ];
267	const uint32_t nb02 = a.nb[ 2 ];
268
269	const uint32_t nb10 = b.nb[ 0 ];
270	const uint32_t nb11 = b.nb[ 1 ];
271
272	const uint32_t nb1 = res.nb[ 1 ];
273
274	const uint32_t ew0 = roundUp32( ne01 );
275
276	const uint32_t nk = ne00;
277	const uint32_t nh = nk / 2;
278
279	const uint32_t lenTemp1 = ne02 * ew0 * ne00;
280	const uint32_t lenTemp2 = ( ne10 + ne00 ) * ew0;
281
282	const TensorGpuViews& temp1 = temp.fp16( lenTemp1, true );
283	const TensorGpuViews& temp2 = temp.fp16_2( lenTemp2, true );
284
285	cb.bind();
286
287	bindShader( eComputeShader::convolutionPrep1 );
288	cb.update( a );
289	Binder bind;
290	bind.bind( a, temp1 );
291	context()->Dispatch( ne01, ne02, 1 );
292
293	bindShader( eComputeShader::convolutionPrep2 );
294	cb.update( a, b );
295	bind.bind( b, temp2 );
296	context()->Dispatch( ne11, 1, 1 );
297
298	cb.update( a, b, res );
299	bind.bind( temp1, temp2, res );
300	if( is2 )
301	{
302		if constexpr( enableInexactOptimizations )
303		{
304			constexpr uint32_t KERNEL = 3;
305			constexpr uint32_t TILE_Y = 8;
306			if( a.ne[ 0 ] == KERNEL )
307			{
308				const uint32_t x = ( ( ne10 / 2 ) + TILE_Y - 1 ) / TILE_Y;
309				bindShader( eComputeShader::convolutionMain2Fixed );
310				context()->Dispatch( x, ne02, 1 );
311				return;
312			}
313		}
314		bindShader( eComputeShader::convolutionMain2 );
315		context()->Dispatch( ne10 / 2, ne02, 1 );
316	}
317	else
318	{
319		bindShader( eComputeShader::convolutionMain );
320		context()->Dispatch( ne10, ne02, 1 );
321	}
322#if 0
323	std::vector<uint16_t> tmp;
324	downloadBuffer( temp1, tmp );
325	dbgWriteBinaryFile( L"conv-gpu-arg1.bin", tmp.data(), lenTemp1 * 2 );
326	downloadBuffer( temp2, tmp );
327	dbgWriteBinaryFile( L"conv-gpu-arg2.bin", tmp.data(), lenTemp1 * 2 );
328	res.download( tempVector );
329	dbgWriteBinaryFile( L"conv-gpu-result.bin", tempVector.data(), tempVector.size() * 4 );
330#endif
331}
332
333void MlContext::norm( const Tensor& a, Tensor& res )
334{
335	const uint32_t ne01 = a.ne[ 1 ];
336	const uint32_t ne02 = a.ne[ 2 ];
337	const uint32_t ne03 = a.ne[ 3 ];
338
339	cb.bind();
340	cb.update( a, res );
341	Binder bind;
342	bind.bind( a, res );
343
344	if constexpr( usePreciseComputeShaders && !enableInexactOptimizations )
345	{
346		bindShader( eComputeShader::normCompat );
347		context()->Dispatch( ( ne01 + 31 ) / 32, ne02, ne03 );
348	}
349	else
350	{
351		constexpr uint32_t FIXED_ROW_SIZE = 1024;
352		eComputeShader cs = ( a.ne[ 0 ] == FIXED_ROW_SIZE ) ? eComputeShader::normFixed : eComputeShader::norm;
353		bindShader( cs );
354		context()->Dispatch( ne01, ne02, ne03 );
355	}
356}
357
358void MlContext::cwiseBinary( const Tensor& a, const Tensor& b, Tensor& res, eComputeShader cs )
359{
360	assert( isSameShape( a, b ) );
361	assert( isSameShape( a, res ) );
362
363	bindShader( cs );
364	cb.bind();
365	check( cb.update( a, b, res ) );
366	Binder bind;
367	bind.bind( a, b, res );
368
369	uint32_t rows = a.countRows();
370	context()->Dispatch( rows, 1, 1 );
371}
372
373Tensor MlContext::add( const Tensor& a, const Tensor& b )
374{
375	return cwiseBinary( a, b, eComputeShader::add );
376}
377
378void MlContext::addInPlace( Tensor& a, const Tensor& b )
379{
380	if( !isSameShape( a, b ) )
381		throw E_INVALIDARG;
382	assert( a.getType() == eDataType::FP32 );
383
384	check( cb.update( a, b ) );
385	bindShader( eComputeShader::addInPlace );
386	cb.bind();
387
388	Binder bind;
389	bind.bind( b, a );
390	context()->Dispatch( a.ne[ 1 ], a.ne[ 2 ], a.ne[ 3 ] );
391}
392
393void MlContext::copyImpl( const Tensor& a, Tensor& res, bool downcastFp32 )
394{
395	assert( res.isContinuous() );
396	const eComputeShader cs = a.isContinuous() ? eComputeShader::copyConvert : eComputeShader::copyTranspose;
397	bindShader( cs );
398
399	cb.bind();
400	// These two shaders don't need shape of the destination because dense, but they wants a boolean flag whether to implement rounding while downcasting
401	TensorShape dummyShape;
402	dummyShape.setZero();
403	dummyShape.ne[ 0 ] = downcastFp32 ? TRUE : FALSE;
404	check( cb.update( a, dummyShape ) );
405
406	Binder bind;
407	bind.bind( a, res );
408	context()->Dispatch( a.ne[ 1 ], a.ne[ 2 ], a.ne[ 3 ] );
409}
410
411namespace
412{
413	uint32_t bitcast( float val )
414	{
415		__m128 f = _mm_set_ss( val );
416		__m128i i = _mm_castps_si128( f );
417		return (uint32_t)_mm_cvtsi128_si32( i );
418	}
419}
420
421void MlContext::scale( Tensor& a, float mul )
422{
423	if( !a.isContinuous() )
424		throw E_INVALIDARG;
425
426	bindShader( eComputeShader::scaleInPlace );
427	cb.bind();
428	TensorShape dummyShape;
429	dummyShape.setZero();
430	dummyShape.ne[ 0 ] = bitcast( mul );
431	check( cb.update( a, dummyShape ) );
432
433	Binder bind;
434	bind.bind( a );
435	context()->Dispatch( a.countRows(), 1, 1 );
436}
437
438void MlContext::addRepeat( Tensor& a, const Tensor& b )
439{
440	check( cb.update( a, b ) );
441	bindShader( eComputeShader::addRepeat );
442	cb.bind();
443
444	Binder bind;
445	bind.bind( b, a );
446	context()->Dispatch( a.ne[ 1 ], a.ne[ 2 ], a.ne[ 3 ] );
447}
448
449void MlContext::addRepeatScale( Tensor& a, const Tensor& b, float scale )
450{
451#if 0
452	addRepeat( a, b );
453	this->scale( a, scale );
454	return;
455#endif
456
457	TensorShape dummyShape;
458	dummyShape.setZero();
459	dummyShape.ne[ 0 ] = bitcast( scale );
460	check( cb.update( a, b, dummyShape ) );
461	bindShader( eComputeShader::addRepeatScale );
462	cb.bind();
463
464	Binder bind;
465	bind.bind( b, a );
466	context()->Dispatch( a.ne[ 1 ], a.ne[ 2 ], a.ne[ 3 ] );
467}
468
469void MlContext::fmaRepeat( Tensor& a, const Tensor& mul, const Tensor& add )
470{
471	eComputeShader cs;
472	if( isSameShapeAndLayout( mul, add ) )
473	{
474		cs = eComputeShader::fmaRepeat1;
475		check( cb.update( a, mul ) );
476	}
477	else
478	{
479		cs = eComputeShader::fmaRepeat2;
480		check( cb.update( a, mul, add ) );
481	}
482
483	bindShader( cs );
484	cb.bind();
485	Binder bind;
486	bind.bind( mul, add, a );
487	context()->Dispatch( a.ne[ 1 ], a.ne[ 2 ], a.ne[ 3 ] );
488}
489
490void MlContext::diagMaskInf( Tensor& a, uint32_t n_past )
491{
492	if( !a.isContinuous() )
493		throw E_INVALIDARG;
494
495	bindShader( eComputeShader::diagMaskInf );
496	TensorShape dummyShape;
497	dummyShape.setZero();
498	dummyShape.ne[ 0 ] = n_past;
499
500	cb.bind();
501	check( cb.update( a, dummyShape ) );
502
503	Binder bind;
504	bind.bind( a );
505
506	const uint32_t n = a.countRows();
507	const uint32_t nr = a.ne[ 1 ];
508	const uint32_t nz = n / nr;
509	context()->Dispatch( nr, nz, 1 );
510}
511
512void MlContext::softMax( Tensor& a, float inputScale )
513{
514	if( !a.isContinuous() )
515		throw E_INVALIDARG;
516
517	if constexpr( usePreciseComputeShaders && !enableInexactOptimizations )
518	{
519		assert( inputScale == 1.0f );
520		bindShader( eComputeShader::softMaxCompat );
521		const uint32_t nr = a.countRows();
522		TensorShape dummyShape;
523		dummyShape.setZero();
524		dummyShape.ne[ 0 ] = nr;
525
526		cb.bind();
527		check( cb.update( a, dummyShape ) );
528
529		Binder bind;
530		bind.bind( lookupTables.exponent(), a );
531		context()->Dispatch( ( nr + 31 ) / 32, 1, 1 );
532	}
533	else
534	{
535#if 0
536		static PrintUniqueTensorSizes printSizes( "softMax" );
537		printSizes.print( a );
538#endif
539		constexpr uint32_t FIXED_ROW_SIZE = 1500;
540
541		eComputeShader cs;
542		if( a.ne[ 0 ] == FIXED_ROW_SIZE )
543			cs = eComputeShader::softMaxFixed;
544		else if( a.ne[ 0 ] >= ( 1024 * 4 ) )
545			cs = eComputeShader::softMaxLong;
546		else
547			cs = eComputeShader::softMax;
548
549		bindShader( cs );
550		const uint32_t nr = a.countRows();
551		TensorShape dummyShape;
552		dummyShape.setZero();
553		dummyShape.ne[ 0 ] = nr;
554		dummyShape.ne[ 1 ] = bitcast( inputScale );
555
556		cb.bind();
557		check( cb.update( a, dummyShape ) );
558
559		Binder bind;
560		bind.bind( lookupTables.exponent(), a );
561		context()->Dispatch( nr, 1, 1 );
562	}
563}
564
565void MlContext::addRepeatGelu( Tensor& a, const Tensor& b )
566{
567	check( cb.update( a, b ) );
568	bindShader( eComputeShader::addRepeatGelu );
569	cb.bind();
570
571	Binder bind;
572	bind.bind( b, lookupTables.gelu(), a );
573	context()->Dispatch( a.ne[ 1 ], a.ne[ 2 ], a.ne[ 3 ] );
574}
575
576namespace
577{
578	inline bool canAddRows( const Tensor& tokenEmbedding, const Tensor& positionalEmbedding, const Tensor& embd, uint32_t pastTokensCount )
579	{
580		if( tokenEmbedding.ne[ 0 ] != positionalEmbedding.ne[ 0 ] )
581			return false;	// Different row lengths
582		if( embd.ne[ 0 ] + pastTokensCount > positionalEmbedding.ne[ 1 ] )
583			return false;	// Too many rows requested, positionalEmbedding matrix doesn't have that many 
584		return true;
585	}
586}
587
588Tensor MlContext::addRows( const Tensor& tokenEmbedding, const Tensor& positionalEmbedding, const Tensor& embd, uint32_t pastTokensCount )
589{
590	if( !canAddRows( tokenEmbedding, positionalEmbedding, embd, pastTokensCount ) )
591		throw E_INVALIDARG;
592
593	const uint32_t rowLength = tokenEmbedding.ne[ 0 ];
594	const uint32_t rows = embd.ne[ 0 ];
595	Tensor result = createTensor( eDataType::FP32, { rowLength, rows } );
596
597	TensorShape constants;
598	// rowLength
599	constants.ne[ 0 ] = rowLength;
600	// pastTokensCount
601	constants.ne[ 1 ] = pastTokensCount;
602	// outputRowStride
603	constants.ne[ 2 ] = result.nb[ 1 ];
604	// embStrides
605	constants.nb[ 0 ] = tokenEmbedding.nb[ 0 ];
606	constants.nb[ 1 ] = tokenEmbedding.nb[ 1 ];
607	// posStrides
608	constants.nb[ 2 ] = positionalEmbedding.nb[ 0 ];
609	constants.nb[ 3 ] = positionalEmbedding.nb[ 1 ];
610	check( cb.update( constants ) );
611
612	bindShader( eComputeShader::addRows );
613	cb.bind();
614	Binder bind;
615	bind.bind( { tokenEmbedding, positionalEmbedding, embd }, { result } );
616	context()->Dispatch( rows, 1, 1 );
617	return result;
618}
619
620Tensor MlContext::reshapePanels( const Tensor& a )
621{
622	constexpr uint32_t TILE_SIZE = ReshapedMultiply::TILE_SIZE;
623
624	const eDataType dataType = a.getType();
625	// Reshaping into column major horizontal panels, height = TILE_SIZE, width = width of the source matrix
626
627	// Round height to multiple of tile size
628	std::array<uint32_t, 4> ne = a.ne;
629	// Dispatch a group of threads thread per panel
630	const uint32_t groupsX = ( ne[ 1 ] + TILE_SIZE - 1 ) / TILE_SIZE;
631	ne[ 1 ] = groupsX * TILE_SIZE;;
632	// Each panel has [ size.x, TILE_SIZE ] elements
633	const uint32_t panelSize = ne[ 0 ] * TILE_SIZE;
634
635	Tensor result = createTensor( dataType, ne );
636
637	TensorShape constants;
638	constants.setZero();
639	// uint panelSize : packoffset( c2.y );
640	constants.ne[ 1 ] = panelSize;
641	// uint2 layerStrides: packoffset( c2.z );
642	constants.ne[ 2 ] = result.nb[ 2 ];
643	constants.ne[ 3 ] = result.nb[ 3 ];
644
645	check( cb.update( a, constants ) );
646	bindShader( eComputeShader::matReshapePanels );
647	cb.bind();
648
649	Binder bind;
650	bind.bind( a, result );
651	context()->Dispatch( groupsX, a.ne[ 2 ], a.ne[ 3 ] );
652
653#if 0
654	if( dataType == eDataType::FP32 )
655	{
656		std::vector<float> v1, v2;
657		a.download( v1 );
658		result.download( v2 );
659		__debugbreak();
660	}
661	else if( dataType == eDataType::FP16 )
662	{
663		std::vector<uint16_t> v1, v2;
664		a.download( v1 );
665		result.download( v2 );
666		__debugbreak();
667	}
668#endif
669
670	// Set up size and stride expected by the mulMatTiledEx compute shader
671	result.ne = a.ne;
672	result.nb[ 0 ] = 0;
673	result.nb[ 1 ] = panelSize;
674	return result;
675}
676
677Tensor MlContext::mulMatTiledEx( const Tensor& a, const Tensor& b )
678{
679	constexpr uint32_t TILE_SIZE = ReshapedMultiply::TILE_SIZE;
680
681	if( !canMulMat( a, b ) )
682		throw E_INVALIDARG;	// Wrong size
683	if( 0 != ( a.nb[ 0 ] | b.nb[ 0 ] ) )
684		throw E_INVALIDARG;	// Both tensors are expected to be pre-transposed into these panels
685
686	Tensor res = createTensor( eDataType::FP32, { a.ne[ 1 ], b.ne[ 1 ], a.ne[ 2 ], b.ne[ 3 ] } );
687
688	check( cb.update( a, b, res ) );
689	bindShader( eComputeShader::mulMatTiledEx );
690	cb.bind();
691
692	Binder bind;
693	bind.bind( a, b, res );
694
695	const uint32_t x = ( res.ne[ 0 ] + TILE_SIZE - 1 ) / TILE_SIZE;
696	const uint32_t y = ( res.ne[ 1 ] + TILE_SIZE - 1 ) / TILE_SIZE;
697	const uint32_t z = res.ne[ 2 ] * res.ne[ 3 ];
698	context()->Dispatch( x, y, z );
699
700	return res;
701}
702
703Tensor MlContext::mulMatByRowTiledEx( const Tensor& a, const Tensor& b )
704{
705	constexpr uint32_t TILE_SIZE = ReshapedMultiply::TILE_SIZE;
706	assert( canMulMat( a, b ) );
707	assert( b.ne[ 1 ] == 1 );
708
709	Tensor res = createTensor( eDataType::FP32, { a.ne[ 1 ], 1, a.ne[ 2 ], b.ne[ 3 ] } );
710
711	check( cb.update( a, b, res ) );
712	bindShader( eComputeShader::mulMatByRowTiledEx );
713	cb.bind();
714
715	Binder bind;
716	bind.bind( a, b, res );
717
718	const uint32_t x = ( res.ne[ 0 ] + TILE_SIZE - 1 ) / TILE_SIZE;
719	const uint32_t y = res.ne[ 2 ];
720	const uint32_t z = res.ne[ 3 ];
721	context()->Dispatch( x, y, z );
722
723	return res;
724}
725
726void MlContext::addRepeatEx( Tensor& dest, const Tensor& pattern, const Tensor& finalAdd )
727{
728	if( !isSameShape( dest, finalAdd ) )
729		throw E_INVALIDARG;
730	assert( dest.getType() == eDataType::FP32 );
731
732	check( cb.update( dest, pattern, finalAdd ) );
733	bindShader( eComputeShader::addRepeatEx );
734	cb.bind();
735
736	Binder bind;
737	bind.bind( pattern, finalAdd, dest );
738	context()->Dispatch( dest.ne[ 1 ], dest.ne[ 2 ], dest.ne[ 3 ] );
739}
740
741__m128i MlContext::getMemoryUse() const
742{
743	__m128i v = cb.getMemoryUse();
744	v = _mm_add_epi64( v, temp.getMemoryUse() );
745	v = _mm_add_epi64( v, bufferMemoryUsage( flashAttentionConstants ) );
746	v = _mm_add_epi64( v, lookupTables.getMemoryUsage() );
747	return v;
748}