Компания
166,93
рейтинг
7 августа 2015 в 14:33

Разработка → Использование набора инструкций Intel SSSE3 для ускорения реализации алгоритма DNN в задачах распознавания речи, выполняемых на мобильных устройствах перевод

За последние тридцать лет технологии распознавания речи серьёзно продвинулись вперед, начав свой путь в исследовательских лабораториях и дойдя до широкого круга потребителей. Эти технологии начинают играть важную роль в нашей жизни. Их можно встретить на рабочем месте, дома, в машине. Их используют в медицинских целях и в других сферах деятельности. Распознавание речи входит в топ-10 перспективных технологий мирового уровня.


Оригинал картинки Vladstudio

Обзор


В результате исследований последних лет произошла смена основных алгоритмов распознавания речи. Так, прежде это были алгоритмы GMM (Gaussian Mixture Model) и HMM-GMM (Hidden Markov Model – Gaussian Mixture Model). От них произошёл переход к алгоритму DNN (Deep Neural Network). Работа этого алгоритма напоминает деятельность человеческого мозга. Здесь используются сложные вычисления и огромное количество данных.

Благодаря Интернету воспользоваться современными технологиями распознавания речи может любой владелец смартфона. К его услугам – бесчисленное множество серверов. А вот без Интернета службы распознавания речи в мобильных устройствах почти бесполезны. Они редко способны правильно понимать тех, кто пытается с ними «разговаривать».

Можно ли перенести реализацию алгоритма DNN с сервера на смартфон или планшет? Ответ на этот вопрос – да. Благодаря поддержке процессорами от Intel набора инструкций SSSE3, на мобильных устройствах можно пользоваться приложениями для распознавания речи, основанными на алгоритме DNN. При этом подключение к Интернету не требуется. В результате наших испытаний точность распознавания речи таким приложением составила более 80%. Это очень близко к тому, что достижимо при использовании серверных систем. В этом материале мы расскажем об алгоритме DNN и о том, как набор инструкций Intel SSSE3 способен помочь в ускорении расчётов, необходимых для реализации этого алгоритма.

Предварительные сведения


DNN (ГНС) – это сокращение от Deep Neural Network (Глубокая Нейронная Сеть). Это – сеть прямого распространения, содержащая множество скрытых слоёв. DNN находится на переднем крае современных технологий машинного обучения. Для этого алгоритма нашлось множество вариантов практического применения.

Глубокие нейронные сети имеют большое количество скрытых слоёв. При их обучении нужно модифицировать десятки миллионов параметров. Как результат, обучение таких сетей требует значительных затрат времени.

Распознавание речи – типичный пример применения DNN. Упрощённо, приложения для распознавания речи можно представить состоящими из акустической модели (acoustic model), языковой модель (language model) и подсистемы декодирования (decoding). Акустическая модель используется для моделирования распределения вероятностей вариантов произношения. Языковая модель применяется для моделирования связей между словами. На этапе декодирования используются две вышеописанные модели, речь преобразуется в текст. Нейронная сеть умеет моделировать любые словесные конструкции. В то время как глубокая нейронная сеть имеет более сильную способность к выделению существенных признаков данных, чем мелкая (shallow) сеть, она моделирует структуру человеческого мозга, и, таким образом, способна более точно «понять» характеристики вещей. В результате, в сравнении с другими методами, в такой нейронной сети можно более точно смоделировать акустические и языковые модели.


Области применения алгоритма DNN

Схема типичной глубокой нейронной сети


Обычно типичная глубокая нейронная сеть содержит множество линейных и нелинейных слоёв, которые накладываются друг на друга.


Четыре скрытых слоя в акустической модели, построенной на базе DNN

Сеть, схема которой здесь приведена, состоит из набора линейных слоёв. Каждый нейрон из предыдущего слоя связан с каждым нейроном из следующего. Связь входа сети с её выходом можно описать следующей формулой:

YT = XTWT + B

XT – это вектор-строка, вход нейронной сети. В применении к распознаванию речи мы обычно помещаем 4 фрагмента данных для одновременной работы над ними, таким образом, создавая входную матрицу 4xM. WT и B это, соответственно, линейная матрица преобразования нейронной сети и вектор смещения. Обычно размерность такой сети очень велика, во всех слоях имеется одинаковое количество нейронов, то есть, сеть имеет квадратную форму.

Набор инструкций Intel SSSE3


Intel называет набор команд Supplemental Streaming SIMD Extensions 3, или, для краткости, просто SSSE3, расширением набора команд SSE3. Это – часть технологии SIMD, интегрированной в микропроцессоры Intel. Данная технология рассчитана на улучшение возможностей по обработке мультимедийных данных. Она предназначена для ускорения выполнения задач кодирования и декодирования информации и для ускорения проведения различных расчётов. Используя набор инструкций SSSE3, мы можем обрабатывать несколько потоков данных с помощью одной инструкции за один тактовый цикл. Это позволяет значительно повысить эффективность приложений. В частности, команды SSSE3 применимы к матричным вычислениям.

Для использования набора инструкций SSSE3 нужно подключить соответствующие заголовочные файлы SIMD:

#include  <mmintrin.h> //MMX
#include  <xmmintrin.h> //SSE
#include  <emmintrin.h> //SSE2
#include  <pmmintrin.h> //SSE3
#include  <tmmintrin.h> //SSSE3
#include  <smmintrin.h> //SSSE4.1
#include  <nmmintrin.h> //SSSE4.2
#include  <wmmintrin.h> //AES
#include  <immintrin.h> //AVX

Заголовочный файл tmmintrin.h обеспечивает работу с SSSE3, ниже приведено описание функций, которые в нём определены.

/*Горизонтальное сложение [с насыщением] упакованных слов, двойных слов,
{X,}MM2/m{128,64} (b) to {X,}MM1 (a).*/
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=a0+a1,r1=a2+a3,r2=a4+a5,r3=a6+a7,r4=b0+b1,r5=b2+b3,r6=b4+b5, r7=b6+b7
extern __m128i _mm_hadd_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0+a1,r1=a2+a3,r2=b0+b1,r3=b2+b3
extern __m128i _mm_hadd_epi32 (__m128i a, __m128i b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=SATURATE_16(a0+a1), ..., r3=SATURATE_16(a6+a7),
//r4=SATURATE_16(b0+b1), ..., r7=SATURATE_16(b6+b7)
extern __m128i _mm_hadds_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0+a1, r1=a2+a3, r2=b0+b1, r3=b2+b3
extern __m64 _mm_hadd_pi16 (__m64 a, __m64 b);
//a=(a0, a1), b=(b0, b1), 则r0=a0+a1, r1=b0+b1
extern __m64 _mm_hadd_pi32 (__m64 a, __m64 b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=SATURATE_16(a0+a1), r1=SATURATE_16(a2+a3),
//r2=SATURATE_16(b0+b1), r3=SATURATE_16(b2+b3)
extern __m64 _mm_hadds_pi16 (__m64 a, __m64 b);
  
/*Горизонтальное вычитание [с насыщением] упакованных слов, двойных слов,
{X,}MM2/m{128,64} (b) from {X,}MM1 (a).*/
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//затем r0=a0-a1, r1=a2-a3, r2=a4-a5, r3=a6-a7, r4=b0-b1, r5=b2-b3, r6=b4-b5, r7=b6-b7
extern __m128i _mm_hsub_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0-a1, r1=a2-a3, r2=b0-b1, r3=b2-b3
extern __m128i _mm_hsub_epi32 (__m128i a, __m128i b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=SATURATE_16(a0-a1), ..., r3=SATURATE_16(a6-a7),
//r4=SATURATE_16(b0-b1), ..., r7=SATURATE_16(b6-b7)
extern __m128i _mm_hsubs_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0-a1, r1=a2-a3, r2=b0-b1, r3=b2-b3
extern __m64 _mm_hsub_pi16 (__m64 a, __m64 b);
//a=(a0, a1), b=(b0, b1), 则r0=a0-a1, r1=b0-b1
extern __m64 _mm_hsub_pi32 (__m64 a, __m64 b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=SATURATE_16(a0-a1), r1=SATURATE_16(a2-a3),
//r2=SATURATE_16(b0-b1), r3=SATURATE_16(b2-b3)
extern __m64 _mm_hsubs_pi16 (__m64 a, __m64 b);

/*Умножение и сложение упакованных слов,
{X,}MM2/m{128,64} (b) to {X,}MM1 (a).*/
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, ..., a13, a14, a15), b=(b0, b1, b2, ..., b13, b14, b15)
//then r0=SATURATE_16((a0*b0)+(a1*b1)), ..., r7=SATURATE_16((a14*b14)+(a15*b15))
//Параметр a содержит байты без знака. Параметр b содержит байты со знаком.
extern __m128i _mm_maddubs_epi16 (__m128i a, __m128i b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=SATURATE_16((a0*b0)+(a1*b1)), ..., r3=SATURATE_16((a6*b6)+(a7*b7))
//Параметр a содержит байты без знака. Параметр b содержит байты со знаком.
extern __m64 _mm_maddubs_pi16 (__m64 a, __m64 b);

/*Упакованное умножение старших элементов целых чисел с округлением и масштабированием,
{X,}MM2/m{128,64} (b) to {X,}MM1 (a).*/
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=INT16(((a0*b0)+0x4000) >> 15), ..., r7=INT16(((a7*b7)+0x4000) >> 15)
extern __m128i _mm_mulhrs_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=INT16(((a0*b0)+0x4000) >> 15), ..., r3=INT16(((a3*b3)+0x4000) >> 15)
extern __m64 _mm_mulhrs_pi16 (__m64 a, __m64 b);

/*Упакованная перестановка байтов
{X,}MM2/m{128,64} (b) by {X,}MM1 (a).*/
//SELECT(a, n) extracts the nth 8-bit parameter from a. The 0th 8-bit parameter
//is the least significant 8-bits, b=(b0, b1, b2, ..., b13, b14, b15), b is mask
//then r0 = (b0 & 0x80) ? 0 : SELECT(a, b0 & 0x0f), ...,
//r15 = (b15 & 0x80) ? 0 : SELECT(a, b15 & 0x0f)
extern __m128i _mm_shuffle_epi8 (__m128i a, __m128i b);
//SELECT(a, n) extracts the nth 8-bit parameter from a. The 0th 8-bit parameter
//is the least significant 8-bits, b=(b0, b1, ..., b7), b is mask
//then r0= (b0 & 0x80) ? 0 : SELECT(a, b0 & 0x07),...,
//r7=(b7 & 0x80) ? 0 : SELECT(a, b7 & 0x07)
extern __m64 _mm_shuffle_pi8 (__m64 a, __m64 b);

/*Знак упакованных байтов, слов, двойных слов, {X,}MM2/m{128,64} (b) to {X,}MM1 (a).*/
//a=(a0, a1, a2, ..., a13, a14, a15), b=(b0, b1, b2, ..., b13, b14, b15)
//then r0=(b0 < 0) ? -a0 : ((b0 == 0) ? 0 : a0), ...,
//r15= (b15 < 0) ? -a15 : ((b15 == 0) ? 0 : a15)
extern __m128i _mm_sign_epi8 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//r0=(b0 < 0) ? -a0 : ((b0 == 0) ? 0 : a0), ...,
//r7= (b7 < 0) ? -a7 : ((b7 == 0) ? 0 : a7)
extern __m128i _mm_sign_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=(b0 < 0) ? -a0 : ((b0 == 0) ? 0 : a0), ...,
//r3= (b3 < 0) ? -a3 : ((b3 == 0) ? 0 : a3)
extern __m128i _mm_sign_epi32 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=(b0 < 0) ? -a0 : ((b0 == 0) ? 0 : a0), ...,
//r7= (b7 < 0) ? -a7 : ((b7 == 0) ? 0 : a7)  
extern __m64 _mm_sign_pi8 (__m64 a, __m64 b);  
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)  
//r0=(b0 < 0) ? -a0 : ((b0 == 0) ? 0 : a0), ...,  
//r3= (b3 < 0) ? -a3 : ((b3 == 0) ? 0 : a3)  
extern __m64 _mm_sign_pi16 (__m64 a, __m64 b);  
//a=(a0, a1), b=(b0, b1), 则r0=(b0 < 0) ? -a0 : ((b0 == 0) ? 0 : a0),  
//r1= (b1 < 0) ? -a1 : ((b1 == 0) ? 0 : a1)  
extern __m64 _mm_sign_pi32 (__m64 a, __m64 b);  

/*Упакованное выравнивание и сдвиг вправо на n*8 битов,
{X,}MM2/m{128,64} (b) to {X,}MM1 (a).*/
//n: константа, которая задаёт, на сколько байтов 
//вправо будет сдвинут промежуточный результат, 
//если n > 32, итоговое значение будет нулём.
//CONCAT(a, b) это 256-битное беззнаковое промежуточное значение,
//которое представляет собой объединение параметров a и b. 
//Результат – это промежуточное значение, сдвинутое вправо на n байт.
//then r= (CONCAT(a, b) >> (n * 8)) & 0xffffffffffffffff
extern __m128i _mm_alignr_epi8 (__m128i a, __m128i b, int n);
//n: целочисленная константа, которая указывает, на сколько байтов вправо
//нужно сдвинуть промежуточный результат.
//Если n > 16, в результате получится ноль.
//CONCAT(a, b) это 128-битное беззнаковое промежуточное значение,
//которое представляет собой объединение параметров a и b.
//Результирующие значения - правые 64 бита, полученные
//после сдвига этого промежуточного результата вправо на n байтов. 
//then r = (CONCAT(a, b) >> (n * 8)) & 0xffffffff
extern __m64 _mm_alignr_pi8 (__m64 a, __m64 b, int n);

/*Абсолютное значение упакованных байтов, слов, двойных слов,
{X,}MM2/m{128,64} (b) to {X,}MM1 (a).*/
//a=(a0, a1, a2, ..., a13, a14, a15)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r15 = (a15 < 0) ? -a15 : a15
extern __m128i _mm_abs_epi8 (__m128i a);
//a=(a0, a1, a2, a3, a4, a5, a6, a7)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r7 = (a7 < 0) ? -a7 : a7
extern __m128i _mm_abs_epi16 (__m128i a);
//a=(a0, a1, a2, a3)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r3 = (a3 < 0) ? -a3 : a3
extern __m128i _mm_abs_epi32 (__m128i a);
//a=(a0, a1, a2, a3, a4, a5, a6, a7)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r7 = (a7 < 0) ? -a7 : a7
extern __m64 _mm_abs_pi8 (__m64 a);
//a=(a0, a1, a2, a3)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r3 = (a3 < 0) ? -a3 : a3
extern __m64 _mm_abs_pi16 (__m64 a);
//a=(a0, a1), then r0 = (a0 < 0) ? -a0 : a0, r1 = (a1 < 0) ? -a1 : a1
extern __m64 _mm_abs_pi32 (__m64 a);

Определения структур данных __m64 и __m128 находятся в заголовочном файле для MMX (mmintrin.h) и SSE (xmmintrin.h).

__m64:

typedef union __declspec(intrin_type) _CRT_ALIGN(8) __m64  
{  
	unsigned __int64    m64_u64;  
	float               m64_f32[2];  
	__int8              m64_i8[8];  
	__int16             m64_i16[4];  
	__int32             m64_i32[2];      
	__int64             m64_i64;  
	unsigned __int8     m64_u8[8];  
	unsigned __int16    m64_u16[4];  
	unsigned __int32    m64_u32[2];  
} __m64; 

__m128:

typedef union __declspec(intrin_type) _CRT_ALIGN(16) __m128 {  
	float               m128_f32[4];  
	unsigned __int64    m128_u64[2];  
	__int8              m128_i8[16];  
	__int16             m128_i16[8];  
	__int32             m128_i32[4];  
	__int64             m128_i64[2];  
	unsigned __int8     m128_u8[16];  
	unsigned __int16    m128_u16[8];  
	unsigned __int32    m128_u32[4];  
} __m128; 

Пример: использование функций SSSE3 для ускорения вычислений, примеряющихся в алгоритме DNN


Здесь мы рассмотрим пару функций. На их примере будет показано, как SSSE3 используется для ускорения расчётов при реализации алгоритма DNN.

__m128i _mm_maddubs_epi16 (__m128i a, __m128i b) Сложение с насыщением

Эта функция очень важна при выполнении матричных вычислений в алгоритме DNN. Параметр – это 128-битный регистр (register), который используется для хранения 16-ти целых чисел без знака (8-ми битных). Параметр b – это целое со знаком, тоже 8-ми битное. Возвращаемый результат – это 8 16-битных целых чисел со знаком. Эта функция отлично подходит для выполнения матричных вычислений:

r0 := SATURATE_16((a0*b0) + (a1*b1))
r1 := SATURATE_16((a2*b2) + (a3*b3))
…
r7 := SATURATE_16((a14*b14) + (a15*b15))

__m128i _mm_hadd_epi32 (__m128i a, __m128i b) Сложение смежных элементов

Эту функцию можно назвать функцией, которая выполняет попарное сложение. Параметры a и b – это 128-битные регистры, которые хранят по 4 целых 32-битных числа со знаком. В соответствии с обычной операцией по сложению соответствующих элементов в двух векторах, команда выполняет сложение смежных элементов входного вектора:

r0 := a0 + a1
r1 := a2 + a3
r2 := b0 + b1
r3 := b2 + b3

Предположим, у нас есть задача вычислений на векторах, типичная для реализации DNN.

Имеются пять векторов: a1, b1, b2, b3, b4. Вектор a1 – это одномерный массив из 16-ти целых чисел типа signed char. Векторы b1, b2, b3, b4 – массивы целых чисел из 16 элементов каждый типа unsigned char. Нам нужно получить скалярные произведения a1*b1, a1*b2, a1*b3, a1*b4 результат надо сохранить в виде 32-битного целого числа со знаком.

Если мы воспользуемся обычным для программирования на C подходом, то код для решения этой задачи будет выглядеть так:

unsigned char b1[16],b2[16],b3[16],b4[16];
signed char a1[16];
int c[4],i;
//
//Инициализация b1,b2,b3,b4 и a1, c инициализируется нулями
// 
for(i=0;i<16;i++){
c[0] += (short)a1[i]*(short)b1[i];
c[1] += (short)a1[i]*(short)b2[i];
c[2] += (short)a1[i]*(short)b3[i];
c[3] += (short)a1[i]*(short)b4[i];
}

Предположим, что за один тактовый цикл можно выполнить одну операцию умножения и одну операцию сложения. Получаем – 64 тактовых цикла на выполнение расчётов.

Теперь воспользуемся набором инструкций SSSE3 для решений той же задачи.

register __m128i a1,b1,b2,b3,b4,c,d1,d2,d3,d4;
//Инициализация a1, b1, b2, b3 и b4, c инициализируется нулями //
d1 = _mm_maddubs_epi16(a1,b1);
d1 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d1, d1), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d1, d1), 16));
d2 = _mm_maddubs_epi16(a1,b2);
d2 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d2, d2), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d2, d2), 16));
d3 = _mm_hadd_epi32(d1, d2);
d1 = _mm_maddubs_epi16(a1,b3);
d1 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d1, d1), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d1, d1), 16));
d2 = _mm_maddubs_epi16(a1,b4);
d2 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d2, d2), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d2, d2), 16));
d4 = _mm_hadd_epi32(d1, d2);
c = _mm_hadd_epi32(d3, d4);

Результат мы сохраняем в 128-битном регистре (с), в котором помещаются 4 целых числа. Учитывая конвейерную обработку данных, на вычисления уйдёт 12 или 13 тактовых циклов. Если сравнить эти данные, получится следующее:
Вариант реализации
Тактовые циклы процессора
Выигрыш
Обычное программирование на C
64
Использование SSSE3
13
~500%

Сравнительное тестирование


Проведем эксперимент, взяв за основу вышеприведенный код. Создадим две функции, которые выполняют одни и те же вычисления разными способами. Одна из них, в итоге, возвращает сумму элементов целочисленного массива c, вторая – сумму 32-битных целочисленных элементов 128-битного регистра с. Инициализация переменных производится при каждом вызове функций. Всего осуществляется по 10000000 вызовов каждой из функций, тест работает в фоновом потоке.


Интерфейс приложения для тестирования производительности

Вот какие результаты даёт испытание release-версии приложения на планшете Asus Fonepad 8 с CPU Intel Atom Z3530. На устройстве установлена Android 5.0.

Сравнение скорости выполнения кода, написанного с использованием и без использования SSSE3

Использование SSSE3, мс.
Использование обычного C, мс.
1
547
3781
2
507
3723
3
528
3762
4
517
3731
5
531
3755
6
517
3769
7
502
3752
8
529
3750
9
514
3745
10
510
3721
Среднее
520.2
3748.9
В результате оказывается, что код, реализующий вычисления с использованием инструкций SSSE3 выполняется, в среднем, в 7.2 раза быстрее, чем обычный.

Исходный код проекта, который можно импортировать в Android Studio, можно найти здесь.

Итоги


Как известно, при распознавании речи с помощью глубокой нейронной сети проводится множество матричных вычислений. Если эти вычисления оптимизировать, можно достичь наилучшей, чем когда-либо, производительности на платформе IA. Мы работаем совместно с компанией ISV Unisound, которая предоставляет сервисы распознавания речи в Китае. Unisound удалось достичь прироста производительности в 10% при использовании ПО, основанного на DNN, на ARM-устройствах.

DNN в наши дни становится основным алгоритмом для распознавания речи. Его, в частности, используют такие службы, как Google Now, Baidu Voice, Tencent Wechat, iFlytek Speech Service, Unisound Speech Service и многие другие. В то же время, имеется набор инструкций SSSE3, способный помочь в оптимизации расчётов, на которых строится процесс распознавания речи. Если везде, где используется DNN, реализуют подобную оптимизацию, это повысит качество распознавания речи и позволит полнее раскрыть возможности платформы IA.
Автор: @CooperMaster Li Alven
Intel
рейтинг 166,93

Комментарии (11)

  • 0
    А вот без Интернета службы распознавания речи в мобильных устройствах почти бесполезны. Они редко способны правильно понимать тех, кто пытается с ними «разговаривать».

    По-моему уже несколько лет Android может это делать без интернета. И довольно хоршо и без SSE. (Бесплатно и без СМС).
    • +5
      У arm процессоров свой набор векторных инструкций — neon.
  • 0
    Хотелось бы видеть результаты решения хотя бы модельной задачи. И сравнение с конкурентами вроде Snapdragon'овского Hexagon.
  • +4
    О! Моя картинка :-) Вот она в виде обои — www.vladstudio.com/wallpaper/?sound_wave
    • +3
      А обрезать авторство и использовать в своих целях это вообще законно?
      • +2
        Не очень. С другой стороны, ловить и ругать — жизнь пройдет. Тут еще не страшно, я встречал зонтики со своими картинками в магазинах :)
  • +9
    В статье ничего нет про DNN и распознавание голоса!
    • +4
      Так это же маркетинг, ничего личного. Покупайте процессоры Intel!
  • 0
    А разве под андроид можно писать на си?
    • +1
      NDK уже лет пять как существует.
  • +1
    SSE — это, конечно, хорошо. Только магия всё равно кроется в оптимизации доступа к кэшу.
    Потому что умножать наивные матрицы наивным алгоритмом — это сплошные кешмиссы, хоть с SSE, хоть без него.
    А всякую магию — порядок Мортона, алгоритм Штрассена и т.д. — товарищи из интела мягко обошли стороной.

    Я как-то игрался с библиотеками линейной алгебры для разгона DNN, так вот, рукодельный код на SSE3 (и более того, на целочисленной арифметике вместо плавающей) соревновался по скорости с Eigen и почти всегда в разы проигрывал плавающей Intel MKL. На десктопе, разумеется. (Все остальные бласы вообще сливали).

    Конечно, если Intel не портировало MKL на ARM, то придётся — на безрыбьи — велосипедить.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка