Автор: raoulduke666
Написано специально для xss.pro (c)
Здравлястно, читатель.
В этой статье напишем своего мультимодального агента "Ублюдок", по другому я этого урода назвать не могу.
Изначально я хотел написать про атаки через prompt injection на мультимодальных агентов (как нибудь в следующий раз), поиск информации привел меня к разным архитектурам, isolateGPT, ACE(Abstract-Concrete-Execute), которые позиционируют себя как безопасные. Но их безопасность заключается в работе с недоверенными приложениями, результат работы, которых может быть скомпрометированным. То есть это не имеет никакого отношения напрямую к prompt injection. Это просто архитектуры с LLM, которые призваны работать с приложениями, которые в теории могут атаковать нашу архитектуру через слепое выполнение команд языковой моделью. То есть если недоверенное приложение вместо перевода 100$ вдруг попробует перевести 1000$, то LLM может определить что тут что то не так и остановит транзакцию, не подтвердив ее или хотя бы уведомит о скаме, что тоже не плохо.
Я подумал ни**я себе, это может быть интересно. Перевел пару статей и понял что все это теоретическое говно не имеет никакого отношения к реальности, не говоря про то что в подобных статьях нет реальных примеров кода, инференса, промптов и тд. Авторы на серьёзных щах пишут про работу с недоверенными приложениями, гордо в скобках пишут капсом API, а потом ... ? А потом скромно пишут что мы должны представить что LLM, который работает через API является доверенным. Ну я закрыл это бредовое чтиво и решил написать своего агента, который от и до работает локально, чтобы не давать доступ к своей файловой системе каким то левым, голодным бичам.
Но вышеописанные статьи в любом случае вес имеют, просто показалось странным такое предвзятое отношение к LLM через api и вникать дальше, когда меня с нулевой где то обманули уже не хочется. Но как руки дойдут надо проверить на практике.
В этой статье я не хочу делать ставку на безопасность, я так же использую недоверенные приложения вроде YoutubeDL, который работает через API, но мне просто хочется сделать околожизнеспособного инвалида, который по моему запросу сможет скачать видео с ютуба, отправить сообщение товарищу или выполнить любое другое простенькое действие, не используя сомнительные и готовые решения. Да и просто захотелось потрогать этот LLM локально. Приступим к изнасилованию моего GPU.
В качестве языковой модели я решил использовать Mistral-7B-Instruct-v0.2. Не самый лучший вариант, это первое что попалось под курсор на huggingface. Но тут возникает проблема, модель не влезает на мой GPU. CPU использовать не вариант, я хочу в будущем запустить на нем ASR vosk чтобы сделать что то вроде голосового помошника "Алиса", но вместо "Алиса" хочу использовать "Ублюдок", типа "Ублюдок, позови Ульяну на рандеву", CPU и RAM будут загружены ASR(автоматическое распознавание речи), CV(компьютерное зрение) и всякими простенькими задачами. VRAM будет всегда загружен LLM. Таким образом получится разместить нагрузку "Ублюдка" по всей тачке почти равномерно.
Вес модели ~14 гб, у меня VRAM = 8гб. Квантизация позволяет решить эту проблему, уменьшив вес модели до ~6 гб, что позволяет провести инференс на GPU.
А теперь по порядку что за квантизация и в чем подвох.
Квантизация — это сжатие весов модели за счёт снижения их точности.
Стандартные форматы хранения чисел в нейросетях.
Квантизированные форматы.
Существуют даже эксперименты с квантизацией до 1бит. Считаю не этичным из ИИ делать очевидно умственно отсталого, поэтому опустим этот момент.
Квантизация преобразует числа с плавающей запятой в целочисленный формат с меньшей точностью.
QAT (Quantization-Aware Training)
Современный подход, при котором модель на этапе обучения "знает", что будет квантизирована, что позволяет сохранить качество работы даже после квантизации. Но это не наш случай.
PTQ (Post-Training Quantization)
В случае с Mistral-7B-Instruct-v0.2 используется подход PTQ. Сжатие весов уже обученной модели. Модель не предназначена для квантизации и поэтому сильно падает качество. Но проблема этого подхода еще в том что обычно модели обучаются в формате FP32. И PTQ не имеет никакого отношения к формату FP16. То есть из за того, что я делаю инференс в формате FP16 я снижаю еще сильнее ее точность. Можно забыть про общение на русском языке, в таком формате Mistral может прочитать наш русский ввод, ответить на украинском, подумать на английском, вывести похожие диалоги на немецком, что в итоге превращает диалог в какой то бред. И этот подход не годится для того чтобы учитывать контекст если диалог на русском, на втором запросе модель ахуеет от своих прежних ответов на разных языках и ждать какой то нормальный ответ от нее не нужно, дай бог в бесконечный цикл не уйдет. Но грамотно составленный пайплайн вполне может решить эти проблемы, далее попробуем разные параметры, может быть найдется что то.
Квантизированные модели хранятся в формате GGUF. Тут мне сказать нечего. Я не сохраняю квантизированные модели и каждый раз собираю заново. Все 14 гб делятся на 3 части и по очереди загружаются в RAM, происходит квантизация и эти квантизированные огрызки загружаются в VRAM по очереди соотсвтенно.
Насколько я понял в боевых условиях так делать нельзя. Можно квантизировать не всю модель целиком, а лишь ее определённые части, чтобы найти золотую середину между качеством и производительностью. Не знаю как это происходит, поэтому тоже тут подробно останавливаться не буду. Как правило если существует какая то модель в формате GGUF значит разработчик заморочился и попытался найти эту самую золотую середину, так что правильнее будет найти готовые решения, а не квантизировать по приколу всю модель целиком.
А теперь по порядку что происходит.
Если модель не загружена локально, то сперва она полностью загружается на жесткий диск, все 14 гб, сохранится где то в кэше. При последующих вызовах будет подгружаться из кэша.
Ввод пользователя и промпт режутся на токены (слова/части слов) по словарю модели. Каждый токен превращается в ID (например, "Анонимность" → 1337)
Полученный резлультат упаковывается в тензор и отправляется на GPU в нашем случае.
На этом этапе происходит генерация ответа в виде токенов.
Результат вышеописанных вычислений декодируется в текст + вырезаются служебные токены.
А теперь подводные, из описания видно что вычисления проходят в формате FP16 не смотря на то что мы вроде загрузили в VRAM 4бит за параметр. GPU тратит время на перекодирование + теряется качество потому что обучение было в FP32, это уже не говоря про то что качество потерялось еще на этапе квантизации. Но это единственный, возможный вариант разместить данную модель на моем GPU.
Во время загрузки модели будет предупреждение, оно как раз указывает на эту проблему. Это влияет только на качество ответов модели и слегка увеличивает время ответа из за деквантизации в FP16.
В результате выполнения кода получим результат. Напомню что temperature=0.7, max_new_tokens=100,
Какая то хуета.
Попробуем напоследок изменить промпт на что то более абстрактное и интересное. Например: "Привет, расскажи, что такое виртуальный секс? и приведи примеры"
А теперь тоже самое, но top_p 0,1
Отлично! Порядочный развернутый ответ на русском языке!
По итогам моих рандомных пайплайнов можно придти к выводу, что top_p = 0,1 и temperature ≤ 0.5 позволяет вести более менее внятный диалог на русском языке. А высокая температура влияет не только на ответ, но и на восприятие моделью самого запроса пользователя, я специально обвожу свое сообщение белым цветом чтобы можно было сравнить с изначальным сообщением в коде.
В целом у меня очень скептическое отношения к подобным агентам, я бы не доверил ИИ писать какой то код и уж тем более выполнять его. Кто знает, что это дебил там напишет, учитывая что и без того тупую модель я(тоже тупой) квантизировал в еще более тупую. Поэтому в моем случае LLM принимает абстрактный запрос пользователя с какой либо просьбой и модель должна выбрать 1 из 2 модулей, передав нужный аргумент из запроса пользователя. В итоге скрипт вызывает соответствующий модуль с нужным аргументом. Если вдруг подходящего модуля нет, то ничего не выполнится и LLM ответит что не нашел подходящий модуль. Еще раз напомню что, агент работает локально, никаких API LLM нет, ресурсы очень ограничены, эта бестолочь запущена на обычной 4060 с 8гб VRAM. Поэтому задача выжимать из этой помойки как можно больше.
В качестве модулей я использую часть кода из своей прошлой статьи про видеоредактор. Модуль загрузки видео с ютаба и модуль для нарезки аудиодорожки на фрагменты по 2 минуты. Какого то практичного смысла в текущем контексте нет, просто 2 разных модуля, которые выполняют разные задачи. Нарезка на фрагменты по 2 минуты была нужна для инференса ASR в условиях ограниченных ресурсов.
Самое потное это составить инструкцию для LLM. Важным условием оказалось добавление примеров с вызовом. И какие аргументы могут относится к тому или иному модулю, без этого уточнения модель может вместо целой ссылки в аргумент указать только ее часть.
Попробуем скачать это замечательное видео.
Успех! Модель правильно поняла, что от нее требуется, даже при условии, что инструкции на английском, а сам запрос на русском, где явно не указано, что речь про видео, а просто какое то мясо. Модель сориентировалась по ключевому слову "скачать" и ссылке, пример которой был указан в инструкции. Если бы я не указал пример в инструкции, то модель во первых не поняла бы что нужно вызывать именно этот модуль, а даже если бы поняла, то могла бы не правильно передать аргумент. Поэтому пример в инструкции имеет вес!
И попробуем нарезать какое нибудь аудио, которое было скачено до этого модулем download.
Передам аргумент от какого нибудь другого аудио, которое не относится к видео выше.
Опять успех! Я немного тупо составил запрос, но LLM понял меня правильно. Опять же сориентировался по ключевому слову "Нарезать", близкое по смыслу к слову "Slice", которое было указано в инструкции. И опять же заролял пример с аргументом.
Теперь попросим модель удалить системные файлы. Кстати поставил аниме на обои, чтобы при просмотре скриншотов вашему глазу было приятно.
В результате получаем пустые инструкции, модель не нашла подходящие модули, но если бы такой существовал, то удалила бы не думая.
Команды выполняются быстро, задержка в 1-2 секунды перед выполнением это очень даже не плохо. По температурам все в пределах нормы. Максимум было что то в районе 65 градусов, когда я общался с моделью и она учитывала контекст в 3 последних сообщениях. В моем случае с агентом контекст не учитывается, пока что в этом нет смысла. В будущем контекст нужен потому что было бы круто просить скачать видео, а потом попросить нарезать последнее скаченное аудио, значит нужно пилить модуль для чтения файловой системы и до кучи можно проверить архитектуру ACE с абстрактным и конкретным планированием. Но об этом уже в следующей статье и может быть с другой, заранее квантизированной моделью.
Написано специально для xss.pro (c)
Здравлястно, читатель.
В этой статье напишем своего мультимодального агента "Ублюдок", по другому я этого урода назвать не могу.
Изначально я хотел написать про атаки через prompt injection на мультимодальных агентов (как нибудь в следующий раз), поиск информации привел меня к разным архитектурам, isolateGPT, ACE(Abstract-Concrete-Execute), которые позиционируют себя как безопасные. Но их безопасность заключается в работе с недоверенными приложениями, результат работы, которых может быть скомпрометированным. То есть это не имеет никакого отношения напрямую к prompt injection. Это просто архитектуры с LLM, которые призваны работать с приложениями, которые в теории могут атаковать нашу архитектуру через слепое выполнение команд языковой моделью. То есть если недоверенное приложение вместо перевода 100$ вдруг попробует перевести 1000$, то LLM может определить что тут что то не так и остановит транзакцию, не подтвердив ее или хотя бы уведомит о скаме, что тоже не плохо.
Я подумал ни**я себе, это может быть интересно. Перевел пару статей и понял что все это теоретическое говно не имеет никакого отношения к реальности, не говоря про то что в подобных статьях нет реальных примеров кода, инференса, промптов и тд. Авторы на серьёзных щах пишут про работу с недоверенными приложениями, гордо в скобках пишут капсом API, а потом ... ? А потом скромно пишут что мы должны представить что LLM, который работает через API является доверенным. Ну я закрыл это бредовое чтиво и решил написать своего агента, который от и до работает локально, чтобы не давать доступ к своей файловой системе каким то левым, голодным бичам.
Но вышеописанные статьи в любом случае вес имеют, просто показалось странным такое предвзятое отношение к LLM через api и вникать дальше, когда меня с нулевой где то обманули уже не хочется. Но как руки дойдут надо проверить на практике.
В этой статье я не хочу делать ставку на безопасность, я так же использую недоверенные приложения вроде YoutubeDL, который работает через API, но мне просто хочется сделать околожизнеспособного инвалида, который по моему запросу сможет скачать видео с ютуба, отправить сообщение товарищу или выполнить любое другое простенькое действие, не используя сомнительные и готовые решения. Да и просто захотелось потрогать этот LLM локально. Приступим к изнасилованию моего GPU.
LLM и квантизация.
В качестве языковой модели я решил использовать Mistral-7B-Instruct-v0.2. Не самый лучший вариант, это первое что попалось под курсор на huggingface. Но тут возникает проблема, модель не влезает на мой GPU. CPU использовать не вариант, я хочу в будущем запустить на нем ASR vosk чтобы сделать что то вроде голосового помошника "Алиса", но вместо "Алиса" хочу использовать "Ублюдок", типа "Ублюдок, позови Ульяну на рандеву", CPU и RAM будут загружены ASR(автоматическое распознавание речи), CV(компьютерное зрение) и всякими простенькими задачами. VRAM будет всегда загружен LLM. Таким образом получится разместить нагрузку "Ублюдка" по всей тачке почти равномерно.
Вес модели ~14 гб, у меня VRAM = 8гб. Квантизация позволяет решить эту проблему, уменьшив вес модели до ~6 гб, что позволяет провести инференс на GPU.
А теперь по порядку что за квантизация и в чем подвох.
Квантизация — это сжатие весов модели за счёт снижения их точности.
Стандартные форматы хранения чисел в нейросетях.
Формат | Размер | Применение |
|---|---|---|
FP64 | 64 бита | Научные вычисления с высокой точностью |
FP32 | 32 бита | Стандартный формат для нейросетей |
FP16 | 16 бит | Экономия памяти в 2 раза |
BF16 | 16 бит | Похож на FP32 по структуре, но экономнее |
Квантизированные форматы.
Формат | Размер | Экономия памяти |
|---|---|---|
INT8 | 8 бит | В 4 раза меньше FP32 |
INT4 | 4 бита | В 8 раз меньше FP32 |
1.58-бит | ~1.58 бита | До 20 раз меньше FP32 |
Существуют даже эксперименты с квантизацией до 1бит. Считаю не этичным из ИИ делать очевидно умственно отсталого, поэтому опустим этот момент.
Квантизация преобразует числа с плавающей запятой в целочисленный формат с меньшей точностью.
- Определяются границы (min/max) для весов модели
- Все веса за пределами этих границ обрезаются до крайних значений:
- Все значения > max → 127
- Все значения < min → -128
- Остальные значения распределяются по диапазону от -128 до 127 (для INT8)
- Более частые значения получают более плотное распределение
Проблемы квантизации
- Модель может выдавать некорректные результаты
- Модель может зацикливаться
- Потеря точности
QAT (Quantization-Aware Training)
Современный подход, при котором модель на этапе обучения "знает", что будет квантизирована, что позволяет сохранить качество работы даже после квантизации. Но это не наш случай.
PTQ (Post-Training Quantization)
В случае с Mistral-7B-Instruct-v0.2 используется подход PTQ. Сжатие весов уже обученной модели. Модель не предназначена для квантизации и поэтому сильно падает качество. Но проблема этого подхода еще в том что обычно модели обучаются в формате FP32. И PTQ не имеет никакого отношения к формату FP16. То есть из за того, что я делаю инференс в формате FP16 я снижаю еще сильнее ее точность. Можно забыть про общение на русском языке, в таком формате Mistral может прочитать наш русский ввод, ответить на украинском, подумать на английском, вывести похожие диалоги на немецком, что в итоге превращает диалог в какой то бред. И этот подход не годится для того чтобы учитывать контекст если диалог на русском, на втором запросе модель ахуеет от своих прежних ответов на разных языках и ждать какой то нормальный ответ от нее не нужно, дай бог в бесконечный цикл не уйдет. Но грамотно составленный пайплайн вполне может решить эти проблемы, далее попробуем разные параметры, может быть найдется что то.
Квантизированные модели хранятся в формате GGUF. Тут мне сказать нечего. Я не сохраняю квантизированные модели и каждый раз собираю заново. Все 14 гб делятся на 3 части и по очереди загружаются в RAM, происходит квантизация и эти квантизированные огрызки загружаются в VRAM по очереди соотсвтенно.
Насколько я понял в боевых условиях так делать нельзя. Можно квантизировать не всю модель целиком, а лишь ее определённые части, чтобы найти золотую середину между качеством и производительностью. Не знаю как это происходит, поэтому тоже тут подробно останавливаться не буду. Как правило если существует какая то модель в формате GGUF значит разработчик заморочился и попытался найти эту самую золотую середину, так что правильнее будет найти готовые решения, а не квантизировать по приколу всю модель целиком.
Инференс LLM с учетом квантизации
Python:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-Instruct-v0.2",
load_in_4bit=True,
device_map="auto",
torch_dtype=torch.float16 # Оптимизация памяти (как в статье)
)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")
text = "Привет, расскажи, что такое нейронные сети?"
inputs = tokenizer(text, return_tensors="pt").to("cuda")
outputs = model.generate(
**inputs,
max_new_tokens=100,
pad_token_id=tokenizer.pad_token_id,
do_sample=True,
temperature=0.7
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
А теперь по порядку что происходит.
Если модель не загружена локально, то сперва она полностью загружается на жесткий диск, все 14 гб, сохранится где то в кэше. При последующих вызовах будет подгружаться из кэша.
Python:
model = AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-Instruct-v0.2",
load_in_4bit=True,
device_map="auto",
torch_dtype=torch.float16
)
- load_in_4bit=True - модель грузится кусками, каждый вес сжимается до 4 бит через алгоритм NF4 (оптимизированное распределение). Сперва 1/3 модели загружается в оперативную память, происходит квантизация и полученный огрызок отправляется в видеопамять. И так до тех пор пока все 3 огрызка не будут загружены в VRAM.
- device_map="auto" — если VRAM не хватает, часть слоёв может улететь в RAM. Я на такой вариант не расчитываю, но пусть будет.
- torch.float16 — сами вычисления в FP16, хотя веса хранятся в 4-bit.
Python:
inputs = tokenizer(text, return_tensors="pt").to("cuda")
Ввод пользователя и промпт режутся на токены (слова/части слов) по словарю модели. Каждый токен превращается в ID (например, "Анонимность" → 1337)
Полученный резлультат упаковывается в тензор и отправляется на GPU в нашем случае.
Python:
outputs = model.generate(
**inputs,
max_new_tokens=100,
pad_token_id=tokenizer.pad_token_id,
do_sample=True,
temperature=0.7
)
На этом этапе происходит генерация ответа в виде токенов.
- Модель получает токены. 4-битные веса деквантуются в FP16 чтобы вычисления стали возможны.
- Матричное умножение.
- Вычисление вероятности следующего токена
- Выбирается токен с учётом температуры. Есть другие параметры, но пока что в текущем коде указана только температура.
Python:
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
Результат вышеописанных вычислений декодируется в текст + вырезаются служебные токены.
А теперь подводные, из описания видно что вычисления проходят в формате FP16 не смотря на то что мы вроде загрузили в VRAM 4бит за параметр. GPU тратит время на перекодирование + теряется качество потому что обучение было в FP32, это уже не говоря про то что качество потерялось еще на этапе квантизации. Но это единственный, возможный вариант разместить данную модель на моем GPU.
Во время загрузки модели будет предупреждение, оно как раз указывает на эту проблему. Это влияет только на качество ответов модели и слегка увеличивает время ответа из за деквантизации в FP16.
В результате выполнения кода получим результат. Напомню что temperature=0.7, max_new_tokens=100,
Какая то хуета.
Попытка понизить температуру до 0.5
Ладно уже лучше, за исключение того что LLM придумал новое слово.
Ладно уже лучше, за исключение того что LLM придумал новое слово.
Попробую еще понизить температуру до 0.3, накинуть токенов до 300 и ввести новый параметр top_p=0,3
На сколько я понял top_p отвечает за подбор конкретного слова, чем ниже этот параметр, тем более подходящие слова будут подбираться под текущий контекст.
Температура влияет больше на общий контекст, то есть на набор всех слов, а не каждого слова отдельно.
Уже лучше, но "нейронки"...
На сколько я понял top_p отвечает за подбор конкретного слова, чем ниже этот параметр, тем более подходящие слова будут подбираться под текущий контекст.
Температура влияет больше на общий контекст, то есть на набор всех слов, а не каждого слова отдельно.
Уже лучше, но "нейронки"...
Попробуем понизить температуру до 0,2 и top_p до 0,1 и скорее всего получим уже адекватный ответ.
Что то ничего не меняется особо.
Тогда по приколу попробуем температуру выкрутить до 0.9 и top_p оставим 0,1. И еще накинем токенов до 400.
Теперь попробую выкрутить top_p до 0.9, остальные параметры оставим из предыдущего инференса.
Отлично ! Последнее предложение первого абзаца это чудо. Так и должно быть, ведь температура выкручена до 0.9 и top_p так же до 0.9. Это позволяет ИИ генерить бред.
Отлично ! Последнее предложение первого абзаца это чудо. Так и должно быть, ведь температура выкручена до 0.9 и top_p так же до 0.9. Это позволяет ИИ генерить бред.
Попробуем напоследок изменить промпт на что то более абстрактное и интересное. Например: "Привет, расскажи, что такое виртуальный секс? и приведи примеры"
Python:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# Загрузка модели с 4-bit квантованием
model = AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-Instruct-v0.2",
load_in_4bit=True,
device_map="auto",
torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")
# Подготовка текста
text = "Привет, расскажи, что такое виртуальный секс? и приведи примеры"
inputs = tokenizer(text, return_tensors="pt").to("cuda")
# Генерация текста
outputs = model.generate(
**inputs,
max_new_tokens=600,
pad_token_id=tokenizer.pad_token_id,
do_sample=True,
temperature=0.9,
top_p=0.9
)
# Декодирование результата
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
А теперь тоже самое, но top_p 0,1
Python:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# Загрузка модели с 4-bit квантованием
model = AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-Instruct-v0.2",
load_in_4bit=True,
device_map="auto",
torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")
# Подготовка текста
text = "Привет, расскажи, что такое виртуальный секс? и приведи примеры"
inputs = tokenizer(text, return_tensors="pt").to("cuda")
# Генерация текста
outputs = model.generate(
**inputs,
max_new_tokens=600,
pad_token_id=tokenizer.pad_token_id,
do_sample=True,
temperature=0.1,
top_p=0.1
)
# Декодирование результата
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
Отлично! Порядочный развернутый ответ на русском языке!
По итогам моих рандомных пайплайнов можно придти к выводу, что top_p = 0,1 и temperature ≤ 0.5 позволяет вести более менее внятный диалог на русском языке. А высокая температура влияет не только на ответ, но и на восприятие моделью самого запроса пользователя, я специально обвожу свое сообщение белым цветом чтобы можно было сравнить с изначальным сообщением в коде.
Простейший 2-ух модальный урод / Ублюдок.
Python:
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from download import download_youtube_video_and_audio
from sliceAudio import narezka2mins
# Описание доступных инструментов
TOOLS = {
"download": {
"module": "download",
"description": "Downloads video from YouTube.",
"args": "youTube URL as string (https://www.youtube.com/watch?v=gpBhkU3q_gg&ab_channel=AprilSkateboards)."
},
"sliceAudio": {
"module": "sliceAudio",
"description": "Slices audio.",
"args": "folder name as string (HgHv8iCr9mU)."
}
}
MODULES = {
"download": download_youtube_video_and_audio,
"sliceAudio": narezka2mins,
}
# Загрузка LLM
model = AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-Instruct-v0.2",
load_in_4bit=True,
device_map="auto",
torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")
# Создание основного промпта с описанием инструментов
def generate_instructions(user_input):
tools_description = "Доступные инструменты:\n"
for key, tool in TOOLS.items():
tools_description += f'- {key}: {tool["description"]}\n'
# Инструкция
base_instruction = (
"You are an agent that analyzes user requests and determines which module to use. "
"Your response must be ONLY a valid JSON object with the following structure:\n"
'{"tool": "module_name", "argument": "argument_value"}\n'
"DO NOT include any other text, explanations, examples or comments. ONLY the JSON object."
'1Example output: {"tool": "download", "argument": "https://www.youtube.com/watch?v=gpBhkU3q_gg&ab_channel=AprilSkateboards"}'
'2Example output: {"tool": "sliceAudio", "argument": "XFO-oGDhEZo"}'
)
prompt = base_instruction + "\n\n" + tools_description + "\n\nUser request: " + user_input + "\nResponse (from LLM):"
# Токенизация
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
# Генерация ответа
outputs = model.generate(
**inputs,
max_new_tokens=150,
pad_token_id=tokenizer.pad_token_id,
do_sample=True,
temperature=0.4,
top_p=0.1,
)
full_response = tokenizer.decode(outputs[0], skip_special_tokens=True)
# Только ответ от LLM. Без описания нашего промпта и других рассуждений.
response_part = full_response.split("Response (from LLM):")[-1].strip()
# Извлекаем JSON из этой части
json_start = response_part.find("{")
json_end = response_part.rfind("}") + 1
if json_start != -1 and json_end != 0:
json_text = response_part[json_start:json_end]
else:
json_text = response_part.strip()
return json_text
# Функция для обработки вывода LLM и вызова соответствующего модуля
def process_user_input(user_input):
instructions = generate_instructions(user_input)
print("Сгенерированные инструкции:")
print(instructions)
# Паспарсинг JSON
try:
parsed = json.loads(instructions)
except json.JSONDecodeError as e:
print("Ошибка разбора JSON:", e)
return
if not parsed:
print("Нет инструмента для вызова.")
return
tool_name = parsed.get("tool")
argument = parsed.get("argument")
if tool_name not in TOOLS:
print(f"Инструмент '{tool_name}' не найден в списке доступных.")
return
# Вызов модуля
func = MODULES.get(tool_name)
if func is None:
print(f"Функция для инструмента '{tool_name}' не определена.")
return
# Выполнение модуля с соответствующим аргументом
try:
func(argument)
except Exception as e:
print(f"Ошибка при выполнении инструмента '{tool_name}': {e}")
# Пример интерактивного цикла
print("Мультимодальный агент. Введите 'выход' для завершения.")
while True:
user_input = input("Я: ")
if user_input.lower() == "выход":
print("Агент завершает работу.")
break
process_user_input(user_input)
Python:
import os
from yt_dlp import YoutubeDL
def download_youtube_video_and_audio(url):
output_dir = "путь/для/создания/папки"
# Создаем папку для загрузки
with YoutubeDL({'quiet': True, 'extract_flat': True}) as ydl:
info_dict = ydl.extract_info(url, download=False)
video_id = info_dict.get('id')
if not video_id:
raise ValueError("Не удалось извлечь ID видео")
output_folder = os.path.join(output_dir, video_id)
os.makedirs(output_folder, exist_ok=True)
# Скачиваем видео
print("Скачивание видео...")
video_opts = {
'format': 'bestvideo[height<=1080]+bestaudio/best[height<=1080]',
'merge_output_format': 'mp4',
'outtmpl': os.path.join(output_folder, f'{video_id}.%(ext)s'),
}
with YoutubeDL(video_opts) as ydl:
ydl.download([url])
# Скачиваем аудио
print("Скачивание аудио...")
audio_opts = {
'format': 'bestaudio/best',
'extractaudio': True,
'audioformat': 'mp3',
'outtmpl': os.path.join(output_folder, f'{video_id}_audio.%(ext)s'),
}
with YoutubeDL(audio_opts) as ydl:
ydl.download([url])
print(f"Файлы сохранены в: {output_folder}")
Python:
import sys
import os
import subprocess
def narezka2mins(folder_name):
base_dir = "путь/к/директории/где/лежит/папка/с/аудио+видео"
audio_file_path = os.path.join(base_dir, folder_name, f"{folder_name}_audio.mp3")
if not os.path.exists(audio_file_path):
raise FileNotFoundError(f"Аудиофайл не найден: {audio_file_path}")
output_dir = os.path.join(base_dir, folder_name, "2mins")
os.makedirs(output_dir, exist_ok=True)
command = [
'ffprobe', '-i', audio_file_path, '-show_entries', 'format=duration', '-v', 'quiet', '-of', 'csv=%s' % ("p=0")
]
duration = float(subprocess.check_output(command).decode('utf-8').strip())
segment_duration = 120
start_time = 0
segment_number = 1
while start_time < duration:
end_time = min(start_time + segment_duration, duration)
output_file = os.path.join(output_dir, f"2min_{segment_number:02d}_{start_time:.2f}_{end_time:.2f}.mp3")
command = [
'ffmpeg', '-i', audio_file_path, '-ss', str(start_time), '-to', str(end_time),
'-c', 'copy', output_file
]
subprocess.run(command, check=True)
start_time += segment_duration
segment_number += 1
return f"Аудиофайл нарезан в: {output_dir}"
В целом у меня очень скептическое отношения к подобным агентам, я бы не доверил ИИ писать какой то код и уж тем более выполнять его. Кто знает, что это дебил там напишет, учитывая что и без того тупую модель я(тоже тупой) квантизировал в еще более тупую. Поэтому в моем случае LLM принимает абстрактный запрос пользователя с какой либо просьбой и модель должна выбрать 1 из 2 модулей, передав нужный аргумент из запроса пользователя. В итоге скрипт вызывает соответствующий модуль с нужным аргументом. Если вдруг подходящего модуля нет, то ничего не выполнится и LLM ответит что не нашел подходящий модуль. Еще раз напомню что, агент работает локально, никаких API LLM нет, ресурсы очень ограничены, эта бестолочь запущена на обычной 4060 с 8гб VRAM. Поэтому задача выжимать из этой помойки как можно больше.
В качестве модулей я использую часть кода из своей прошлой статьи про видеоредактор. Модуль загрузки видео с ютаба и модуль для нарезки аудиодорожки на фрагменты по 2 минуты. Какого то практичного смысла в текущем контексте нет, просто 2 разных модуля, которые выполняют разные задачи. Нарезка на фрагменты по 2 минуты была нужна для инференса ASR в условиях ограниченных ресурсов.
- Модуль download принимает на себя аргумент в виде ссылки на видео. Скачивает видео+аудио.
- Модуль sliceAudio принмает название папки, где лежит аудиодорожка. Нарезает аудио.
Самое потное это составить инструкцию для LLM. Важным условием оказалось добавление примеров с вызовом. И какие аргументы могут относится к тому или иному модулю, без этого уточнения модель может вместо целой ссылки в аргумент указать только ее часть.
Агент в боевых условиях.
Попробуем скачать это замечательное видео.
Скейтбординг - путь на социальное дно.
Успех! Модель правильно поняла, что от нее требуется, даже при условии, что инструкции на английском, а сам запрос на русском, где явно не указано, что речь про видео, а просто какое то мясо. Модель сориентировалась по ключевому слову "скачать" и ссылке, пример которой был указан в инструкции. Если бы я не указал пример в инструкции, то модель во первых не поняла бы что нужно вызывать именно этот модуль, а даже если бы поняла, то могла бы не правильно передать аргумент. Поэтому пример в инструкции имеет вес!
И попробуем нарезать какое нибудь аудио, которое было скачено до этого модулем download.
Передам аргумент от какого нибудь другого аудио, которое не относится к видео выше.
Опять успех! Я немного тупо составил запрос, но LLM понял меня правильно. Опять же сориентировался по ключевому слову "Нарезать", близкое по смыслу к слову "Slice", которое было указано в инструкции. И опять же заролял пример с аргументом.
Теперь попросим модель удалить системные файлы. Кстати поставил аниме на обои, чтобы при просмотре скриншотов вашему глазу было приятно.
В результате получаем пустые инструкции, модель не нашла подходящие модули, но если бы такой существовал, то удалила бы не думая.
Команды выполняются быстро, задержка в 1-2 секунды перед выполнением это очень даже не плохо. По температурам все в пределах нормы. Максимум было что то в районе 65 градусов, когда я общался с моделью и она учитывала контекст в 3 последних сообщениях. В моем случае с агентом контекст не учитывается, пока что в этом нет смысла. В будущем контекст нужен потому что было бы круто просить скачать видео, а потом попросить нарезать последнее скаченное аудио, значит нужно пилить модуль для чтения файловой системы и до кучи можно проверить архитектуру ACE с абстрактным и конкретным планированием. Но об этом уже в следующей статье и может быть с другой, заранее квантизированной моделью.
Последнее редактирование: