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
7.2 KiB213 linesraw
1#include "stdafx.h"
2#include <intrin.h>
3#include "mulMatImpl.h"
4#include "mulMat.kernel.hpp"
5
6#define DBG_TRACK_TEMPLATE_INSTANTIATION 0
7
8#if DBG_TRACK_TEMPLATE_INSTANTIATION
9#include <unordered_set>
10static std::unordered_set<uint16_t> g_mulMatTemplates;
11#endif
12
13namespace
14{
15	using namespace CpuCompute;
16
17	bool checkAvx2Support()
18	{
19		int cpuInfo[ 4 ];
20		__cpuid( cpuInfo, 7 );
21		return ( cpuInfo[ 1 ] & ( 1 << 5 ) ) != 0;
22	}
23
24	// a / b, rounded up to the next integer
25	inline uint32_t divRoundUp( uint32_t a, uint32_t b )
26	{
27		assert( b != 0 );
28		return ( a + ( b - 1 ) ) / b;
29	}
30}
31
32const bool MulMatBase::haveAvx2 = checkAvx2Support();
33
34MulMatBase::MulMatBase( Tensor& result, const Tensor& a, const Tensor& b, ParallelForRunner& pfor, uint8_t panelHeightRegs, uint8_t tileWidthFloats ) :
35	resultPointer( result.fp32() ),
36	pa( a.data() ),
37	pb( b.data() ),
38	runner( pfor )
39{
40	length = a.ne[ 0 ];
41	resultStrides[ 0 ] = result.nb[ 1 ];
42	resultStrides[ 1 ] = result.nb[ 2 ];
43	resultStrides[ 2 ] = result.nb[ 3 ];
44	store( resultSize, result.sizeVec() );
45	store( stridesA, a.stridesVec() );
46	store( stridesB, b.stridesVec() );
47
48	countPanels = divRoundUp( resultSize[ 0 ], panelHeightRegs * 8 );
49	completeTilesPerPanel = resultSize[ 1 ] / tileWidthFloats;
50	lastColumnsInPanel = (uint8_t)( resultSize[ 1 ] % tileWidthFloats );
51	this->panelHeightRegisters = panelHeightRegs;
52	this->tileWidth = tileWidthFloats;
53
54	// Pick a method which reshapes a panel of the matrix A into the shape we need to compute the product
55	// Store the pointer to that method in the field of this class
56	if( a.nb[ 0 ] == 1 )
57	{
58		if( haveAvx2 )
59			pfnMakePanel = &MulMatBase::transposePanelAvx2;
60		else
61			pfnMakePanel = &MulMatBase::transposePanel;
62	}
63	else if( a.nb[ 1 ] == 1 )
64	{
65		switch( panelHeightRegs )
66		{
67		case 1:
68			pfnMakePanel = &MulMatBase::copyPanelColumnMajor8;
69			break;
70		case 2:
71			pfnMakePanel = &MulMatBase::copyPanelColumnMajor16;
72			break;
73		case 4:
74			pfnMakePanel = &MulMatBase::copyPanelColumnMajor32;
75			break;
76		default:
77			throw E_NOTIMPL;
78		}
79	}
80	else
81		pfnMakePanel = &MulMatBase::gatherPanel;
82
83	// That last version is generic and very simple, unlikely to have weird bugs
84	// pfnMakePanel = &MulMatBase::gatherPanel;
85
86#if DBG_TRACK_TEMPLATE_INSTANTIATION
87	uint16_t key = panelHeightRegs;
88	key = key << 8;
89	key |= tileWidthFloats;
90	if( !g_mulMatTemplates.emplace( key ).second )
91		return;
92	logDebug( u8"MulMatImpl<panelHeightRegs = %i, tileWidthFloats = %i>", (int)panelHeightRegs, (int)tileWidthFloats );
93#endif
94}
95
96HRESULT MulMatBase::run( ParallelForRunner& pfor )
97{
98	size_t length = (size_t)countPanels * resultSize[ 2 ] * resultSize[ 3 ];
99	return pfor.parallelFor( *this, length );
100}
101
102const float* MulMatBase::getLayerB( size_t m2, size_t m3 ) const
103{
104	const float* rsi = (const float*)this->pb;
105	rsi += m2 * stridesB[ 2 ];
106	rsi += m3 * stridesB[ 3 ];
107	return rsi;
108}
109
110// This method is the main one, it�s called by the thread pool
111template<uint8_t panelHeightRegs, uint8_t tileWidthFloats>
112HRESULT __stdcall MulMatImpl<panelHeightRegs, tileWidthFloats>::compute( size_t i, size_t end ) const noexcept
113{
114	// Allocate a thread-local buffer for the transposed panel
115	constexpr size_t panelHeightFloats = panelHeightRegs * 8;
116	uint16_t* const panel = (uint16_t*)runner.threadLocalBuffer( floatsPerPanel() * 2 );
117	const size_t resultStride = resultStrides[ 0 ];
118
119	// Load a few numbers from this class into local variables, while upcasting from DWORD into size_t
120	const size_t length = this->length;
121	const std::array<size_t, 2> stridesB{ this->stridesB[ 0 ], this->stridesB[ 1 ] };
122
123	// This outer loop iterates over the panels assigned to the current thread
124	// For example, matrix A of size [ 1024, 1024 ] may be split into panels of size [ 1024, 16 ]
125	// Each iteration of that loop computes matrix product of that panel, with the complete matrix B
126	for( ; i < end; i++ )
127	{
128		const size_t iPanel = i % countPanels;
129		size_t j = i / countPanels;
130		const size_t m2 = j % (size_t)resultSize[ 2 ];
131		const size_t m3 = j / (size_t)resultSize[ 2 ];
132
133		CHECK( ( this->*pfnMakePanel )( panel, iPanel, m2, m3 ) );
134		// We got a column-major panel in the thread local buffer, of size [ length, panelHeightRegs * 8 ]
135		// Hopefully, these buffers should all fit at least in L3 cache
136		// The longest matrix I saw in the debugger had 4096 elements, with panelHeightRegs = 4 that's 256 kb of data in the panel
137		const float* pb = getLayerB( m2, m3 );
138		float* rdi = getPanelDest( iPanel, m2, m3 );
139
140		const size_t storeWidth = std::min( panelHeightFloats, (size_t)resultSize[ 0 ] - iPanel * panelHeightFloats );
141		std::array<__m256, panelHeightRegs> vecPanel;
142#if 1
143		ResultTile<panelHeightRegs, tileWidthFloats> tile;
144
145		// This loop iterates over tiles within the panel.
146		// Each iteration of the loop computes an output tile of the result matrix.
147		for( j = 0; j < completeTilesPerPanel; j++, pb += tileWidthFloats * stridesB[ 1 ], rdi += resultStride * tileWidthFloats )
148		{
149			setZero( tile.arr );
150			const uint16_t* rsiA = panel;
151			const uint16_t* const rsiAEnd = panel + length * panelHeightFloats;
152			const float* rsiB = pb;
153			// This loop runs for `length` iterations, iterates over the first dimensions of both matrices, accumulating these dot products we're after
154			for( ; rsiA < rsiAEnd; rsiA += panelHeightFloats, rsiB += stridesB[ 0 ] )
155			{
156				loadPanel( rsiA, vecPanel );
157				tile.kernel( vecPanel, rsiB, stridesB[ 1 ] );
158			}
159			tile.store( rdi, storeWidth, tileWidthFloats, resultStride );
160		}
161
162		if( 0 != lastColumnsInPanel )
163		{
164			setZero( tile.arr );
165			const uint16_t* rsiA = panel;
166			const uint16_t* rsiAEnd = panel + length * panelHeightFloats;
167			const float* rsiB = pb;
168			for( ; rsiA < rsiAEnd; rsiA += panelHeightFloats, rsiB += stridesB[ 0 ] )
169			{
170				loadPanel( rsiA, vecPanel );
171				tile.kernelPartial( vecPanel, rsiB, stridesB[ 1 ], lastColumnsInPanel );
172			}
173			tile.store( rdi, storeWidth, lastColumnsInPanel, resultStride );
174		}
175#else
176		// This version bypasses horizontal tiling, instead implements a brute force algorithm to multiply the current panel by the complete B matrix
177		// Not terribly efficient, only implemented for debugging purposes
178		const size_t resHeight = resultSize[ 1 ];
179		std::array<__m256, panelHeightRegs> tile;
180		for( size_t j = 0; j < resHeight; j++, pb += stridesB[ 1 ], rdi += resultStride )
181		{
182			setZero( tile );
183
184			const uint16_t* rsiA = panel;
185			const uint16_t* const rsiAEnd = panel + length * panelHeightFloats;
186			const float* rsiB = pb;
187			for( size_t k = 0; k < length; k++, rsiA += panelHeightFloats, rsiB += stridesB[ 0 ] )
188			{
189				loadPanel( rsiA, vecPanel );
190				const __m256 b = _mm256_broadcast_ss( rsiB );
191				for( size_t r = 0; r < panelHeightRegs; r++ )
192					tile[ r ] = _mm256_fmadd_ps( vecPanel[ r ], b, tile[ r ] );
193			}
194
195			alignas( 32 ) std::array<float, panelHeightFloats> arr;
196			for( size_t k = 0; k < panelHeightRegs; k++ )
197				_mm256_store_ps( &arr[ k * 8 ], tile[ k ] );
198			memcpy( rdi, arr.data(), storeWidth * 4 );
199		}
200#endif
201	}
202	return S_OK;
203}
204
205// Instantiate the templates we need
206template class MulMatImpl<4, 1>;
207template class MulMatImpl<1, 1>;
208template class MulMatImpl<4, 2>;
209template class MulMatImpl<1, 2>;
210template class MulMatImpl<2, 3>;
211template class MulMatImpl<1, 3>;
212template class MulMatImpl<2, 4>;
213template class MulMatImpl<1, 4>;