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-optimization01325d7

master
9.1 KiB349 linesraw
1#include "stdafx.h"
2#include <immintrin.h>
3#include <optional>
4#include "HybridContext.h"
5#include "../Utils/Trace/tracing.h"
6
7#if BUILD_HYBRID_VERSION
8namespace
9{
10	int threadsCount( int t )
11	{
12#ifdef NDEBUG
13		if( t == 0 )
14		{
15			SYSTEM_INFO si;
16			GetSystemInfo( &si );
17			return (int)si.dwNumberOfProcessors;
18		}
19		if( t <= 1 )
20			return 1;
21		return t;
22#else
23		return 1;
24#endif
25	}
26
27	constexpr size_t MB = 1u << 20;
28}
29
30HybridContext::HybridContext( const Whisper::WhisperModel& wm ) :
31	ml( threadsCount( 0 ) ),
32	model( wm.hybridTensors ),
33	whisperModel( wm )
34{ }
35
36namespace
37{
38	enum struct eModelType : uint8_t
39	{
40		Tiny = 0,
41		Base = 1,
42		Small = 2,
43		Medium = 3,
44		Large = 4,
45	};
46
47	static HRESULT detectModelType( const Whisper::sModelParams& modelParams, eModelType& mt )
48	{
49		switch( modelParams.n_audio_layer )
50		{
51		case 4:
52			mt = eModelType::Tiny;
53			return S_OK;
54		case 6:
55			mt = eModelType::Base;
56			return S_OK;
57		case 12:
58			mt = eModelType::Small;
59			return S_OK;
60		case 24:
61			mt = eModelType::Medium;
62			return S_OK;
63		case 32:
64			mt = eModelType::Large;
65			return S_OK;
66		}
67		logError( u8"Unrecognized model" );
68		return E_INVALIDARG;
69	}
70
71	struct alignas( 2 ) RamMB
72	{
73		uint8_t dec, decLayer;
74		constexpr RamMB( uint8_t d, uint8_t dl ) : dec( d ), decLayer( dl ) { }
75
76		__m128i loadBytes() const
77		{
78			__m128i v = _mm_loadu_si16( this );
79			// Upcast bytes to int64_t. That instruction can load directly from memory, too bad VC++ optimized doesn't care
80			v = _mm_cvtepu8_epi64( v );
81			// Scale from megabytes into bytes, the multiplier is obviously 2^20
82			v = _mm_slli_epi64( v, 20 );
83			return v;
84		}
85	};
86
87	// The magic numbers are from MEM_REQ_DECODE and MEM_REQ_DECODE_LAYER red/black maps in the reference version,
88	// near the top of whisper.cpp source file
89	static const std::array<RamMB, 5> s_memRequirements =
90	{
91		RamMB{ 200, 32 },	// Tiny
92		RamMB{ 202, 44 },	// Base
93		RamMB{ 204, 64 },	// Small
94		RamMB{ 206, 84 },	// Medium
95		RamMB{ 208, 110 },	// Large
96	};
97}
98
99HRESULT HybridContext::create()
100{
101	// Allocate buffers for compute
102	// We know they're large, so bypassing the heap
103	eModelType modelType;
104	CHECK( detectModelType( whisperModel.parameters, modelType ) );
105
106	const __m128i bytes = s_memRequirements.at( (uint8_t)modelType ).loadBytes();
107	CHECK( allocCompute.create( _mm_cvtsi128_si64( bytes ) ) );
108	CHECK( allocComputeLayer.create( _mm_extract_epi64( bytes, 1 ) ) );
109
110	// Create staging buffers to download output from encoder stage,
111	// in the reference version they're named memory_cross_k / memory_cross_v
112	CHECK( kvCross.create( whisperModel.parameters ) );
113
114	// Create RAM buffers for memory_k / memory_v
115	CHECK( kv.create( whisperModel.parameters ) );
116
117	return S_OK;
118}
119
120class HybridContext::SetAllocatorRaii
121{
122	HybridContext& context;
123	CpuCompute::iMemoryAllocator* prevAlloc;
124	CpuCompute::iArenaAllocator* newAlloc;
125public:
126
127	SetAllocatorRaii( HybridContext* owner, CpuCompute::iArenaAllocator& a ) :
128		context( *owner )
129	{
130		prevAlloc = context.ml.setAllocator( &a );
131		newAlloc = &a;
132	}
133	~SetAllocatorRaii()
134	{
135		context.ml.setAllocator( prevAlloc );
136		newAlloc->resetArena();
137	}
138};
139
140HRESULT HybridContext::decode( const int* tokens, const int n_tokens, const int n_past, const sDecParams& dp, std::vector<float>& probs )
141{
142	CHECK( ml.setThreadsCount( dp.n_threads ) );
143
144	// whisper_decode
145	const auto& hparams = whisperModel.parameters;
146	const uint32_t n_vocab = hparams.n_vocab;
147
148	const uint32_t n_ctx = hparams.n_text_ctx;
149	const uint32_t n_state = hparams.n_text_state;
150	const uint32_t n_head = hparams.n_text_head;
151	const uint32_t n_layer = hparams.n_text_layer;
152
153	const uint32_t N = n_tokens;
154	const uint32_t M = dp.M;
155
156	SetAllocatorRaii ac{ this, allocCompute };
157	using namespace CpuCompute;
158	Tensor cur = ml.addRows( model.tokenEmbedding, model.positionalEmbedding, tokens, n_tokens, n_past );
159	Tracing::tensor( "dec-rows", cur );
160
161	Tensor inpL = cur;
162	auto kvCross = this->kvCross.map();
163
164	for( uint32_t il = 0; il < n_layer; il++ )
165	{
166		if( 0 == il ) Tracing::tensor( "dec-inpL", inpL );
167		const auto& layer = model.layers[ il ];
168		SetAllocatorRaii acLayer{ this, allocComputeLayer };
169
170		// norm
171		Tensor cur = ml.norm( inpL );
172		ml.fmaRepeat( cur, layer.attnLn0 );
173		if( 0 == il ) Tracing::tensor( "dec-norm", cur );
174
175		// self-attention
176		{
177			Tensor Qcur = ml.mulMat( layer.attnQuery.w, cur );
178			if( 0 == il ) Tracing::tensor( "dec-Qcur-0", Qcur );
179			const float scaling = computeScaling( (int)n_state, (int)n_head );
180			ml.addRepeatScale( Qcur, layer.attnQuery.b, scaling );
181			if( 0 == il ) Tracing::tensor( "dec-Qcur-1", Qcur );
182
183			// note: no bias for Key
184			Tensor Kcur = ml.mulMat( layer.attnKey, cur );
185			ml.scale( Kcur, scaling );
186			if( 0 == il ) Tracing::tensor( "dec-Kcur", Kcur );
187
188			Tensor Vcur = ml.mulMat( layer.attnValue.w, cur );
189			ml.addRepeat( Vcur, layer.attnValue.b );
190			if( 0 == il ) Tracing::tensor( "dec-Vcur", Vcur );
191
192			// store key and value to memory
193			{
194				const uint32_t len = N * n_state;
195				const uint32_t off = n_state * ( (uint32_t)il * n_ctx + n_past );
196				Tensor k = kv.keysView( len, off );
197				Tensor v = kv.valuesView( len, off );
198
199				CHECK( ml.copyImpl( k, Kcur ) );
200				CHECK( ml.copyImpl( v, Vcur ) );
201			}
202
203			// ------
204			Tensor Q = ml.permute( ml.copy( Qcur, eDataType::FP32, { n_state / n_head, n_head, N } ), 0, 2, 1, 3 );
205			Tensor K = ml.permute( kv.keysView( ( n_past + N ) * n_state, (uint32_t)il * n_ctx * n_state )
206				.reshape3d( n_state / n_head, n_head, n_past + N ),
207				0, 2, 1, 3 );
208			Tensor KQ = ml.mulMat( K, Q );
209			if( 0 == il ) Tracing::tensor( "dec-KQ-0", KQ );
210			ml.diagMaskInf( KQ, n_past );
211			if( 0 == il ) Tracing::tensor( "dec-KQ-1", KQ );
212			ml.softMax( KQ );
213			if( 0 == il ) Tracing::tensor( "dec-KQ-2", KQ );
214
215			Tensor V_trans = ml.permute(
216				kv.valuesView( ( n_past + N ) * n_state, (uint32_t)il * n_ctx * n_state )
217				.reshape3d( n_state / n_head, n_head, n_past + N ),
218				1, 2, 0, 3 );
219
220			Tensor KQV = ml.mulMat( V_trans, KQ );
221			if( 0 == il ) Tracing::tensor( "dec-KQV", KQV );
222
223			Tensor KQV_merged = ml.permute( KQV, 0, 2, 1, 3 );
224			ml.copyInPlace( cur, KQV_merged, eDataType::FP32, { n_state, N } );
225		}
226
227		{
228			cur = ml.mulMat( layer.attnLn1.w, cur );
229			ml.addRepeat( cur, layer.attnLn1.b );
230		}
231
232		// add the input
233		Tensor inpCA = ml.add( cur, inpL );
234
235		// norm
236		{
237			cur = ml.norm( inpCA );
238			ml.fmaRepeat( cur, layer.crossAttnLn0 );
239		}
240
241		// cross-attention
242		{
243			Tensor Qcur = ml.mulMat( layer.crossAttnQuery.w, cur );
244			ml.addRepeatScale( Qcur, layer.crossAttnQuery.b, computeScaling( (int)n_state, (int)n_head ) );
245
246			// Kcross is already scaled
247			const uint32_t len = M * n_state;
248			const uint32_t off = (uint32_t)il * len;
249			Tensor Kcross = kvCross.keysView( len, off ).reshape3d( n_state / n_head, n_head, M );
250			Tensor Vcross = kvCross.valuesView( len, off ).reshape3d( n_state / n_head, n_head, M );
251
252			// ------
253			Tensor Q = ml.permute( ml.copy( Qcur, eDataType::FP32, { n_state / n_head, n_head, N } ), 0, 2, 1, 3 );
254			Tensor K = ml.permute( Kcross, 0, 2, 1, 3 );
255			Tensor KQ = ml.mulMat( K, Q );
256			ml.softMax( KQ );
257			Tensor V_trans = ml.permute( Vcross, 1, 2, 0, 3 );
258			Tensor KQV = ml.mulMat( V_trans, KQ );
259			if( 0 == il ) Tracing::tensor( "dec-KQV", KQV );
260			Tensor KQV_merged = ml.permute( KQV, 0, 2, 1, 3 );
261
262			ml.copyInPlace( cur, KQV_merged, eDataType::FP32, { n_state, N } );
263		}
264
265		// projection
266		{
267			cur = ml.mulMat( layer.crossAttnLn1.w, cur );
268			ml.addRepeat( cur, layer.crossAttnLn1.b );
269		}
270		// add the input
271		ml.addInPlace( cur, inpCA );
272		Tensor inpFF = cur;
273
274		// feed-forward network
275		{
276			// norm
277			cur = ml.norm( inpFF );
278			ml.fmaRepeat( cur, layer.mlpLn );
279
280			cur = ml.mulMat( layer.mlp0.w, cur );
281			ml.addRepeatGelu( cur, layer.mlp0.b );
282
283			// The mulMat() below creates a tensor for the output of this layer.
284			// We have a special memory storage for these tensors, that's how they survive resets of per-layer arenas
285			allocLayerOutput.resetArena();
286			ml.setAllocator( &allocLayerOutput );
287
288			// projection
289			cur = ml.mulMat( layer.mlp1.w, cur );
290			ml.addRepeat( cur, layer.mlp1.b );
291		}
292
293		// output from this layer
294		ml.addInPlace( cur, inpFF );
295		inpL = cur;
296	}
297
298	// norm
299	cur = ml.norm( inpL );
300	ml.fmaRepeat( cur, model.ln );
301
302	cur = ml.mulMat( model.tokenEmbedding, cur );
303
304	// logits -> probs
305	ml.softMax( cur );
306
307	const float* rsi = cur.fp32();
308	probs.assign( rsi, rsi + cur.countElements() );
309	Tracing::vector( "probs", probs );
310	return S_OK;
311}
312
313void* HybridContext::AllocSingle::allocate( size_t cb, size_t align )
314{
315	if( !allocated )
316	{
317		allocated = true;
318		if( cb <= capacity )
319		{
320			CpuCompute::dbgMarkUninitializedMemory( buffer.pointer(), capacity );
321			return buffer.pointer();
322		}
323		else
324		{
325			HRESULT hr = buffer.allocate( cb );
326			if( SUCCEEDED( hr ) )
327			{
328				capacity = cb;
329				CpuCompute::dbgMarkUninitializedMemory( buffer.pointer(), capacity );
330				return buffer.pointer();
331			}
332			logErrorHr( hr, u8"HybridContext.AllocSingle.allocate" );
333			throw hr;
334		}
335	}
336	else
337	{
338		logError( u8"HybridContext.AllocSingle only supports 1 tensor" );
339		throw E_UNEXPECTED;
340	}
341}
342
343void HybridContext::AllocSingle::resetArena()
344{
345	allocated = false;
346	if( capacity > 0 )
347		CpuCompute::dbgMarkFreedMemory( buffer.pointer(), capacity );
348}
349#endif