ПрограммированиеФорумОбщее

Нейросети на базе Keras, Tensorflow, Torch и другие. Теория и практика применения. Pyhton/C++/Pascal (20 стр)

Страницы: 1 … 19 20 21 22 … 30 Следующая »
#285
(Правка: 9:51) 9:47, 2 авг 2025

Добил до конца потоковый режим: сделал запись с микрофона в реальном времени.

Всё работает, правда речь англофицируется жёстко, что придаёт дополнительного ржача при прослушивании.

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

Важные моменты:

1) WavLM подавляет шумы и фон 50 Гц.  С микрофона у меня идёт сильная наводка 50 Гц, а в раскодированном варианте её нет.

2) Большая задержка: почти 4 секунды. Это при условии, если подавать на кодек по 64 фрейма (каждый фрейм по 20 мс). Такова расплата  за поток и реальное время: пришлось делать кольцевые буфера на нескольких уровнях:

- на этапе получения порции входного аудио

- на этапе приёма кодированных векторов с шагом 50% от размера сжатого по времени сегмента

- на этапе интерполяции латентного пространства с шагом окна 50% от размера разжатого сегмента

- на этапе воспроизведения звука шаг окна 50%


3) Англофикация речи. Установил, что связка WavLM + Vocos НЕ зависит от языка,  а FocalNET зависит.


Следующими шагами будут:

- попробовать укоротить сегмент до 32 фреймов вместо 64.  Нужно будет переобучить FocalNET.

- обучить FocalNET на русской речи

#286
(Правка: 9:02) 8:18, 3 авг 2025

Gradius
> Большая задержка: почти 4 секунды.

Спец-ухищрениями удалось уменьшить задержку до 1,3 секунды.  При этом длина сегмента (64 фрейма) осталась прежней.

Gradius
> обучить FocalNET на русской речи

Обучил. Стало лучше.


1) Вариант с весами FocalNet, предоставленных автором кодека (обучен на английской речи, на полных высказываниях):


2) Мой вариант весов FocalNet (обучен на русской речи, на фиксированный сегмент):


Это 163 бит/c.


Список фраз для проверки:

+ Показать
#287
9:58, 3 авг 2025

Класс!

#288
18:11, 3 авг 2025

Затащил программу кодека и модели в окружение C++ на ПК.  Работает.

Теперь самое мучительное: заставить это работать на голом железе.  И тут я чешу репу:

либо портировать громоздкий Torch, либо конвертить в ONNX и искать полностью открытый фреймворк, который может инференс ONNX.

Либо радикальный метод: переписать все слои с нуля и разобраться с форматом весов сети.

Посмотрел, как это сделано в STM32 + STMCube:  эти засранцы предлагают цеплять либу, которая умеет запускать ONNX, скомпиленную в GCC.  Открыты только хедеры, сама либа естественно закрытая.

#289
18:37, 3 авг 2025

Gradius

Либо радикальный метод: переписать все слои с нуля и разобраться с форматом весов сети

.
Думаю это не просто.

#290
21:40, 3 авг 2025

Gradius
> Либо радикальный метод: переписать все слои с нуля и разобраться с форматом весов сети.
Если нету готовой библиотеки, этот вариант выглядит наиболее реальным, особенно если не пытаться сразу изобретать швейцарский нож, а решать одну конкретную задачу.

#291
(Правка: 5 авг 2025, 2:15) 6:23, 4 авг 2025

Пока не определился, как именно буду загонять нейросеть на голое железо.  Хочется простых и гениальных решений, как frugally deep для Keras, который я уже перенёс на C++/ARM под голое железо.

Сконвертировал модели в ONNX с помощью либы onnx на Питоне. На этом этапе можно забыть про динамический размер входных тензоров для нейросеток: требуется указывать фиксированный размер:

LEN=320*64

onnx_program=torch.onnx.export(coder,torch.rand(1,LEN),dynamo=True)
onnx_program.save("coder.onnx")

onnx_model=onnx.load("coder.onnx")
onnx.checker.check_model(onnx_model)

Ну и фиг бы с ним: для потоковой/каузальной версии кодека все размеры тензоров строго фиксированы.

Без ошибок и предупреждений:

1 | Нейросети на базе Keras, Tensorflow, Torch и другие. Теория и практика применения. Pyhton/C++/Pascal


Далее попробовал запустить модели ONNX с помощью либы onnxruntime.  Из подводных камней следует отметить, что ONNX модели возвращают тензоры, обёрнутые в лист(list). Так как у меня модели возвращают только один тензор, пришлось "снимать шелуху" - конвертить лист в тензор (индексация - нулевой элемент листа - это и есть первый тензор):

lats=compressor.run(None,{"input_1":feats[0]})

Плюс ещё нужно явно указывать имя входного слоя (в строке выше имя слоя - input_1).

Посмотреть имена слоёв (и многое другое) можно в программе Netron:

1 | Нейросети на базе Keras, Tensorflow, Torch и другие. Теория и практика применения. Pyhton/C++/Pascal


Следует понимать, что при конвертировании моделей из Torch в ONNX, мы лишаемся всей мощи Торча, и наши модели "застывают" - все размеры тензоров фиксируются.

А также имеем статический проход - сильно сложные условия внутри модели куролесить не выйдет: нужен детерминизм в работе. К счастью, пришлось проверить и подправить в FocalNET пару мест, связанных с вычислением падов - пады не понадобились, так как размерность по оси времени подбрана так, что вычисление пада =0.

Ну и конечно, пришлось переписывать программу на Питоне:  менять torch на numpy. К счастью, все взаимозаменяемые аналоги есть (у большинства число, порядок и формат аргументов совпадают, но опять же - не везде!) :

torch.bartlett_window() => numpy.bartlett()
torch.hann_window() => numpy.hanning()

torchaudio.load() => scipy.io.wavfile.read()
torchaudio.save() => scipy.io.wavfile.write()

torch.zeros() => numpy.zeros()

torch.unsqueeze() => numpy.expand_dims()

torch.cat() => numpy.concatenate()
torch.transpose() => numpy.transpose()

По началу пришлось гонять тензоры из numpy в torch и наоборот (во время адаптации запуска моделей ONNX):

device="cpu" #"cuda"
torch_tensor = torch.from_numpy(numpy_array).to(device)
numpy_tensor = torch_tensor.numpy().cpu()

Плюс следить за форматом входных и выходных данных: иногда приходится конвертить аудиоданные в диапазон [-1..+1] и обратно в [-32768..+32767] - torch это делает сам, а для numpy приходится делить и умножать на 32768.

Ну и размеры тензоров пришлось подправить - там где загружается аудио и сохраняется: добавить/убрать одно измерение:

load_audio.shape=(1,LEN)
save_audio.shape=(LEN,)

В итоге, полностью исключил torch, остались numpy и onnxruntime. Проверил кодек: ONNX модели работают на слух идентично, как и Torch модели.

Прорверил на бит-экзактность. Всё-же выходные звуковые волны для Torch и ONNX версий немного отличаются - отличия почти не заметны:

+ Показать

Возможно, из-за ошибки округления:

2 | Нейросети на базе Keras, Tensorflow, Torch и другие. Теория и практика применения. Pyhton/C++/Pascal


Дальше надо искать легковесный рантайм для ONNX, наподобие frugally deep.

Правда, кастомные слои не обойти - в модели вокодера(Vocos) есть слой обратного преобразования Фурье, а в кодере - Трансформеры.

Пробежался по моделям.  Нужны вот такие кирпичики:

Unsqueeze Transpose Conv LayerNormalization Div Erf Add Mul Slice Reshape
Abs Cast MatMul ReduceSum Less Clip Split Gather Softmax Sigmoid Sub
Sin Cos Exp Concat DFT Pow Expand Col2lm ScatterND ScatterElements Reciprocal

Как я понял, лесапеды из Гитхаба тут не прокатят - слишком много всего нужно.  Поэтому остаются только промышленные поделия. Как вариант  ONNX Runtime от Микрософта. Но он сильно громоздкий - Dll-ки под сотню мегабайт )))

#292
(Правка: 19:04) 18:59, 4 авг 2025

Переписал кодек под ONNXruntime от Микрософта.  Добился одинакового результата работы с PyTorch: формы звуковой волны на выходе совпадают.

Код на ONNXruntime(C++) получился немного громоздким. На Питоне код смотрится компактнее.

Некоторые вещи пришлось сделать самостоятельно: транспонирование, объединение, фильтрация, нормирование вектора, бинарное квантование. Массивы, циклы, степени, корни...

Быстро вьехать в API ONNXruntime помогла китайская дока (c++ inference):

https://blog.csdn.net/juluwangriyue/article/details/118230181

Инит модели, загрузка входного тензора, выполнение, выгрузка из выходного тензора. Более ничего не надо. Остальное - логика.

Корень зла был в этой фигне (не компилировалось): имя файла модели - строка из двух-байтных символов:

#ifdef _WIN32
 const wchar_t *coder_file=L"coder.onnx";
#else
 const char *coder_file="coder.onnx";
#endif

На счёт "кирпичиков": кодеку нужна поддержка opset версии 18, не ниже. 

Поэтому только портирование микрософтовского кода, без вариантов :)))

#293
(Правка: 21:16) 21:13, 4 авг 2025

Gradius

Быстро вьехать в API ONNXruntime помогла китайская дока (c++ inference):

https://blog.csdn.net/juluwangriyue/article/details/118230181

Очень круто!
Я наверное совсем отстал (старость не радость), кроме кода вообще ничего не воспринимаю. Но есть догадка, что это переводится.
))

Поэтому только портирование микрософтовского кода, без вариантов :)))

Слово "портирование" почему то не вызывает отторжения.

#294
(Правка: 2:16) 2:01, 5 авг 2025

Gradius
> Но он сильно громоздкий - Dll-ки под сотню мегабайт )))

Нагнал с размером. На самом деле нужна только onnxruntime.dll - её размер чуть больше 12 МБ.

Задача: получить статическую либу onnxruntime для ARM Cortex-A7/A8 под голое железо. Что-то мне подсказывает, что T113-s3 с его 128 МБ памяти не хватит. Придётся использовать доску Olinuxino A13, там внешняя память до 2 ГБ.

Интересно, что и какой размер кода/данных выхватит кодек из этой либы?  Вот за что я не люблю эти DLL и SO, так это за то, что эти тонны груза надо постоянно таскать вместе с целевой программой. Статические либы в этом плане смотрятся выигрышнее, особенно на платформах с ограниченной памятью и одним активным приложением.

flint2
> кроме кода вообще ничего не воспринимаю. Но есть догадка, что это переводится.

Там ничего переводить не нужно. Достаточно того кода, который приведён на картинке. Ну и есть встроенный переводчик в Firefox - очень удобно:

+ Показать
#295
23:19, 5 авг 2025

Gradius

+ Показать

Сжал что ли?
Gradius

Задача: получить статическую либу

Какая разница?
Разница в объёмах только в заголовок COFF, а там байты, ка не кило.
Gradius

с его 128 МБ памяти не хватит.

Но почти тютелька в тютельку.
Есть программы, и сам писал dll2OBJ, или можно перевести dll в двоичный массив в программе и сделать загрузчик, но это лишний мусорный код примерно килобайт 300.
Но это... не знаю зачем... если есть готовые dlll.
Другое дело от операционки отказаться и 'тупо сделать автомат' - 'загрузочный сектор', как на винте, установки, если нужны, и entry point. но тогда нужен двоичный снимок памяти твоей программы, а не файлы и операционка.
Которая только нужна в виде загрузщика COFF.
Наверное нужно быдет reloc`и сделать и для dll и для твоего exe - адреса будут отличатся.
Но это просто и не накладно. Берёшь все адлеса из заголовка и прибавляешь\вычитаешь смещение.
Нримерно 250 байт скомпилированнвй код. Если что - сделаю.
Короче, ето всё можно из ПЗУ запустить переписав часть в ОЗУ. Есть нюансы в пользу улучшения, но пока не суть.

#296
(Правка: 5:12) 3:21, 6 авг 2025

flint2
> Сжал что ли?

Нет. Просто быстро глянул на PDB, а показалось что DLL.

Вот для сравнения Torch для C++:

1 | Нейросети на базе Keras, Tensorflow, Torch и другие. Теория и практика применения. Pyhton/C++/Pascal


Кодек работает, если onnxruntime вообще сбилдить на самых минимальных условиях:

build.bat --config=Release --build_shared_lib --minimal_build --disable_ml_ops --disable_exceptions --disable_rtti --enable_reduced_operator_type_support --skip_tests

Попробовал спуститься с версии 1.22.0 до версии 1.0.0 - кодек также сохраняет работоспособность (проверял на готовых сбилженных Микрософтом либах).  А код DLL становится ещё меньше - 4..5 МБ.

Но самостоятельно сбилдить это чудо из прошлого уже не могу:  как git'ом скачать из интернета репозиторий именно той версии, что нужен?  При этом рекурсивно заставить скачать все пакеты, которые third-party?

Скачал последнюю версию репозитория, затем сделал откат, в итоге нифига не собирается версия 1.1.0. Они там всё ломают так, что возврат к прошлому невозможен.

Чего они там запихивают в эту DLL с каждой версией...

Не могу наладить нормальные отношения с Visual Studio и msvc: не получается сбилдить статическую либу: при выставлении флажков MT вместо MD и указания расширения либы .lib вместо .dll - либа строится, но её размер 300 кБ вместо 8 МБ.  И ничего не работает. Но если собрать DLL, то работает.

Этот onnxruntime слишком избыточен и громоздок.  Тот конфиг, что выше приводил, особенно с флагом:

--minimal_build

редуцирует сборку, при этом ONNX модели нужно уже конвертить в ORT, к счастью, делается это просто:

python -m onnxruntime.tools.convert_onnx_models_to_ort coder.onnx coder.ort

Ещё раз потянулся к обзору лесапедов на гитхабе(ибо осознаю всю тяжесть портирования onnxruntime): ничего путного нет: всё урезано, много каких слоёв не поддерживают. Поэтому остаётся возиться с детищем от Микрософта.

flint2
> Какая разница?

Разница такая, что голое железо не умеет и не хочет  работать с DLL. Да и это не нужно: зачем держать в памяти всю DLL?  Проще на этапе линковки из статической либы взять и присоединить к программе только то, что ей нужно.

flint2
> Но почти тютелька в тютельку.

Реалии таковы, что скорее всего буду начинать запускать на A13 с его 2 ГБ памяти.  А дальше посмотрим.

А если быть точнее, то реалии на сегодня таковы, что не удаётся сделать россыпь из статических либ и присоединить к приложению: линковщик ругается  на множество неразрешённых символов в либах, при этом они даже не используются программой. Вот в mingw/gcc это устраняется специальными флагами при компиляции и линковке.  А в случае msvc - не знаю. По идее должны быть какие-то ключи, делающие подобное. Но не нашёл.

Дальше надо этот код адаптировать под GCC - сделать сборку на mingw, чтобы потом затащить на ARM GCC.  А вот со сборкой onnxruntime под  mingw - люди пишут, что проблемно...

flint2
> Есть программы, и сам писал dll2OBJ, или можно перевести dll в двоичный массив в программе и сделать загрузчик, но это лишний мусорный код примерно килобайт 300.
> Но это... не знаю зачем... если есть готовые dlll.
> Другое дело от операционки отказаться и 'тупо сделать автомат' - 'загрузочный сектор', как на винте, установки, если нужны, и entry point. но тогда нужен двоичный снимок памяти твоей программы, а не файлы и операционка.
> Которая только нужна в виде загрузщика COFF.
> Наверное нужно быдет reloc`и сделать и для dll и для твоего exe - адреса будут отличатся.
> Но это просто и не накладно. Берёшь все адлеса из заголовка и прибавляешь\вычитаешь смещение.
> Нримерно 250 байт скомпилированнвй код. Если что - сделаю.
> Короче, ето всё можно из ПЗУ запустить переписав часть в ОЗУ. Есть нюансы в пользу улучшения, но пока не суть.

У меня загрузчик загружает Plain binary data сразу в память и передаёт туда управление. Лично мне не нужны все эти усложнизмы с хедерами эльфов, коффов, а уж тем более ДЛЛ.

Вообще нет никакой проблемы получить бинарник из эльфа на ARM GCC:

arm-none-eabi-objcopy.exe -O binary main.elf main.bin

Проблема в другом:  получить чистый код, который не тащит абстракции винды, потоков и прочего.

#297
(Правка: 7:32) 7:02, 6 авг 2025

Gradius

Проблема в другом:  получить чистый код, который не тащит абстракции винды, потоков и прочего.

Понятно.
Я и сам хотел потом сказать про абстракции винды,но сервер висел.
Так и не дождался пока заработает...


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

#298
9:32, 6 авг 2025

Удалось собрать onnxruntime последней версии в полностью статическом варианте. Использовал ключи, котрые собирают с минимально необходимым функционалом:

python tools/ci_build/build.py --config Release --parallel --enable_msvc_static_runtime --cmake_generator "Visual Studio 17 2022"  --skip_tests --update --build --build_dir build --minimal_build --disable_ml_ops --disable_exceptions --disable_rtti --enable_reduced_operator_type_support

Далее начал действовать методом "отсечения бритвой Оккама": определил, какие либы не используются кодеком.  Для этого написал вот такой небольшой батничек:

del biglib.lib
dir /b /n *.lib > filelist.txt
lib /out:biglib.lib @filelist.txt
pause

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

На текущий момент времени https://github.com/microsoft/onnxruntime для кодека нужны следующие либы:

+ Показать

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

#299
9:42, 6 авг 2025

Gradius

для кодека нужны следующие либы:

+ Показать

Ахренеть.

Страницы: 1 … 19 20 21 22 … 30 Следующая »
ПрограммированиеФорумОбщее