Архив рубрики ~Обо всем~

3 агента. 3 магистра права. 1 устаревающий графический процессор: разработка параллельного вывода на аппаратном уровне.

3 агента. 3 магистра права. 1 устаревающий графический процессор: разработка параллельного вывода на аппаратном уровне.
3 агента. 3 магистра права. 1 устаревающий графический процессор: разработка параллельного вывода на аппаратном уровне.

Как крошечный демон на C++ использует управление доступом в стиле 5G и асинхронную конвейерную обработку уровней для запуска трех LLM на 8-летней видеокарте.

Делить

Архитектура VRAM Conductor: сочетание управления доступом к соединению в стиле 5G (lmxd) с асинхронной потоковой передача данных в двухбуферном режиме для безопасного мультиплексирования нескольких LLM на одной 8-гигабайтной видеокарте GTX 1080. Изображение создано с помощью Claude 4.7 Opus.

У вас есть три агента ИИ, использующие три разных LLM. У вас есть одна старая видеокарта, и вы бедны, чтобы ее обновление было слишком. Вам нужно запустить этих агентов параллельно, но на этой старой видеокарте работает только один, а два других дают сбой. Вот небольшой демон на C++, который решает эту проблему, и правдивая история о том, как они все работают вместе.

Проблема, в которой вы на самом деле находитесь

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

У вас есть несколько агентов ИИ: агент А, внешний исходный код, агент Б, вскоре после обнаружения уязвимостей безопасности в процессе написания, агент С одновременно составляет документацию. Для обеспечения эффективной работы разработчиков в режиме реального времени без значительных задержек все три агента должны одновременно находиться в памяти. Каждый из них лучше всего работает с различными небольшими LLM-инструкциями — SmolLM здесь, Qwen там, небольшая Лама где-то ещё. Вы направляете их на свой компьютер, на котором есть одна очень старая видеокарта. Вы не можете обновить ее ни в этом квартале, ни в этом году, ни, возможно, за всю свою жизнь (да, вы настолько бедны!). Это NVIDIA GTX 1080 всего с 8 ГБ видеопамяти , и вы годами молча слушали, что этого должно быть достаточно для «небольших» моделей.

Итак, вы делаете очевидное. Вы создаете три терминала для параллельного запуска агентов с помощью трех процессов llama-completion . А затем ждем, чтобы увидеть следующее:

Вы : «Хорошо, даже несмотря на то, что видеокарта старая, три небольших модели должны нормально работать».

llama-completion ( Агент 1, Llama 3.2 1B): «Загрузка бэкенда. Резервирование кэша ключа-значения для n_ctx=172032 , n_batch=8192 , -ngl 99 ». Объем памяти GPU увеличивается до 6536 МиБ из 8192.

Вы : «Круто. Теперь Агент 2 — Qwen2 0.5B».

llama-completion ( Agent 2 ): “Выделение 1536 МиБ на устройстве 0…” cudaMalloc : “❌ не хватает памяти”.

llama-completion ( Агент 2 ): “qwen процесс закончилось». 🫡

Вы : «Хорошо. Тогда СмолЛМ2 360М. Этот совсем крошечный».

llama-completion ( Agent 3 ): “Выделение 5120 МиБ на устройстве 0…” <код>cudaMallocкод>: «❌ не хватает памяти.»

llama-completion ( Агент 3 ): «небольшой процесс завершено».

Ваш nvidia-smi по-прежнему показывает только один процесс размером 6512 МиБ. Ваша демонстрация с агентами на самом деле — это демонстрация с одним агентом и двумя логами сбоев.

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

Остальная часть статьи посвящена всем вещам: минутному изменению причин сбоя второго и третьего процессов и небольшому демону на C++ под названием lmxd , который позволяет запускать все три процесса одновременно без риска нехватки памяти (OOM). Для справки можно указать только одна предыдущая статья — Warpgroup-backend, но даже она необязательна.

Почему три магистерские программы не могут выполняться параллельно (одноминутная версия)

функция llama-completion llama.cpp (и аналогична ей) резервирует кэш-значение (память для каждого токена, используемая ими внимание во время декодирования) для всего сконфигурированное контекстного окна заранее , при создании llama_context . Да, это происходит заранее, а не в меньшей степени, чтобы обеспечить видимую работу процесса декодирования без каких-либо сбоев. С параметрами -c 172032 и -ngl 99 первый процесс без проблем занимает 6536 МиБ/8192 МиБ память вашей карты, прежде чем декодировать хотя бы один токен, и почти из этого не возникает необходимости на вес модели. Это резервирование кэша ключа-значения.

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

0.00.592.688 E ggml_backend_cuda_buffer_type_alloc_buffer: выделение 1536,00 МБ на устройстве 0: ошибка cudaMalloc: недостаточно памяти 0.00.593.132 E llama_init_from_model: не удалось инициализировать контекст: не удалось выделить буфер для kv кэш

Это не ошибка. Это llama.cpp делает безопасным для одного процесса способ: предварительно резервирует значение ключа, чтобы декодирование никогда не прерывалось. Однако, когда это делается три необычных процесса на одной и той же 8-гигабайтной карте, это становится крайне небезопасным, поскольку у карт нет очереди и нет общего учета. Есть только cudaMalloc , который становится буквально подбрасыванием монет, как только карта создается более чем на 80%.

Решение заключается не в «улучшении алгоритма». Это проще простого: ведение учета. Кто-то должен просмотреть карту, решить, что подойдет следующему агенту, и отклонить запрос, прежде чем следующая попытка процесса выберет место. Да, я знаю! Давайте это реализуем. А если вам понадобится помощь, пожалуйста, зайдите в репозиторий на GitHub в конце этой статьи. lmxd — это один долгоживущий процесс (около 1500 строк кода на C++17), который управляет графическим процессором от имени агентов. Агенты больше не создают собственные llama-completion , они просто взаимодействуют с демоном по небольшому текстовому протоколу Unix-сокета ( HELP , STATUS , <код>СПИСОКкод>, REGISTER , UNREGISTER ), а демон решает, подходит ли новый агент, и только после этого загружается модель.

Вся политика сводится к одному предложению: 90 % от общего объема видеопамяти карты , и одно правило: допускать нового агента только в том случае, если currently_used + new_estimate ≤ 90 % cap . Все, что приведено ниже, — это просто честная реализация этих правил.

Реестр, контролирующий ограничение, достаточно мал, чтобы его можно было прочитать за один раз. Из src/vram_ledger.cpp :

 bool VramLedger::try_reserve(uint64_t model_table_bytes) { //Один критический раздел охватывает операции сравнения и сопоставления, поэтому параллельные REGISTER не могут перегружаться. std::lock_guard<:mutex> lock(mu_); если (!initialized_) { return false; } uint64_t спроектирован = 0; if (!add_u64(allocated_bytes_, model_table_bytes, &projected)) { return false; } if (прогнозируется > max_vram_bytes_) { return false; } selected_bytes_ = прогнозируемый; вернуть истину; }

Всего несколько строк реального, действующего кода для политики обеспечения безопасности. Следите за тем, чтобы переполировка не произошла незаметно, и чтобы прогнозируемая длительность задержки была ниже лимита, и только после этого выполните операцию-фиксацию. Мьютекс имеет значение: если позвонит два агента <код>РЕГИСТРАЦИЯкод>В то же время вы не можете допустить, чтобы оба прошли проверку в режиме ожидания и ограничили лимит исправлений из-за ошибки модели. Я знаю, что говорю от имени всех, когда говорю, что мы все отлаживали эту проблему с гонкой, и никому из нас это не понравилось.

Обработчик, который решает, что делать с последовательной последовательностью REGISTER , ещё слова — и порядок операций определяет всё. Из src/daemon_app.cpp :

 uint64_t v_new = 0; if (!table_.lookup(model_key, &v_new)) { return std::string("ERR неизвестный model_key для поиска в таблице VRAMn"); } if (!ledger_.try_reserve(v_new)) { const VramLedger::Snapshot st =ledger_.snapshot(); std::ostringstream ОС; os << "ERR VRAM_LEDGER_DENY code=CAPledger_max_bytes=" << st.max_bytes << "ledger_allocated_bytes=" << st.allocated_bytes << " Request_table_bytes=" << v_new << "n"; вернуть os.str(); } Std::string lerr; if (!llama_.acquire_model(model_key, &lerr)) {ledger_.release(v_new); return std::string("Ошибка получения ERR llama: ") + lerr + "n"; } //Записываем слот агента для каждого контекста, чтобы последующие вызовы DECODE могли управлять танцем KV-свопа //через LlamaContextManager. Создание слота записывается только в записи — контекст еще не создан. станд::строка cerr; if (!ctx_mgr_.create_slot(agent_id, model_key, &cerr)) { //Развернуть: освободить байты счетчика ссылок модели + реестра, чтобы неудачный REGISTER не оставил следов. llama_.release_model(model_key); ledger_.release(v_new); return std::string("ERR ctx_mgr create_slot не удалось: ") + cerr + "n"; } Agent_to_model_[agent_id] = model_key; std::ostringstream ОС; os << "ОК зарегистрированный агент=" << Agent_id << " model=" << model_key << "n";

На данном этапе было бы неплохо уделить несколько минут внимательному изучению этих строк. Они следуют простой последовательности действий: определение приблизительного размера в байтах, резервирование в реестре, а затем загрузка моделей. Если реестр отказывается, демон получает структурированную формулу ERR VRAM_LEDGER_DENY code=CAP и никогда не относится к графическому процессор . Если реестр принимает модель, но загрузка модели завершается неудачей по какой-либо другой причине — дисковый ввод-вывод, поврежденный файл и т. д. д. — резервирование освобождается тем же способом. Либо агент в итоге регистрируется с зарезервированными байтами, либо ничего не происходит.

Вероятно, именно здесь наблюдаются проблемы почти в каждой «наивной» версии. Если сначала загрузить модель, а затем проверить бюджет, вы уже оплатили операцию ввода-вывода на диске для моделей, от которых ожидается выигрыш, и вы отделяете всего одну гонку для успешной загрузки моделей, за байты которых вы никогда не сможете нигде взять плату. Бронируйте до начала разработки. Всегда.

Сам загрузчик моделей делает еще одну скучную, но важную вещь: один процесс, одна llama_backend_init , одна карта путей GGUF к установленным моделям с подсчетом ссылок. Из src/llama_single_service.cpp :

 bool LlamaSingleService::acquire_model(const std::string& path, std::string* err_out) { //Охраняем каждую точку входа ламы, чтобы многоагентная регистрация никогда не ускоряла время выполнения. std::lock_guard<:mutex> lock(mu_); if (!backend_inited_) { //Один раз активируем серверные части CPU/GPU; последующие звонки - это всего лишь дешевые повышения рефсчета. llama_backend_init(); backend_inited_ = правда; } Const auto it = models_.find(path); if (it != models_.end()) { //Повторно используем уже сопоставленный GGUF и увеличиваем счетчик ссылок для нового арендатора агента. это->второй.refcount += 1; вернуть истину; } //Свежий путь: загрузить параметры по умолчанию, затем сопоставить веса с диска через анализаторы llama.cpp. llama_model_params params = llama_model_default_params(); llama_model* model = llama_model_load_from_file(path.c_str(), params); if (model == nullptr) { if (err_out != nullptr) { *err_out = "llama_model_load_from_file не удалось выполнить путь: " + путь; } Вернуть ложь; } Слот ModelSlot{}; слот.модель = модель; слот.refcount = 1; models_.emplace(path, std::move(slot)); вернуть истину; }

Демон вызывает функцию llama_backend_init ровно один раз. Наивный подход с «тремя терминалами» запускает три основных альтернативных контекста CUDA на одной и той же карте, каждый из которых потребляет сотни мегабайт скрытых накладных расходов драйвера, прежде чем будет рассматриваться хотя бы один тензор. Демон отказывается от такого мероприятия — по одному бэкенду на каждый графический процессор. Более того, если два агента запрашивают один и тот же GGUF, он отображает вес в видеопамяти только один раз и просто увеличивает счетчик ссылок.

Вот и вся система: число (90 %), честный бухгалтерский учет, строгий порядок операций и один общий бэкэнд. Это не новая область компьютерных наук. Этот бухгалтерский учет написан на C++ и доступен через Unix-сокет, так что уставший инженер за 2 часа ночи может просто подключиться к ней с nc -U /tmp/lmxd.sock .

Чеки (то есть, один некрасивый и пять столов) скриншотов)

Всю демонстрацию можно упаковать в один скриптовый период (хотя в данный момент он не указан в репозитории, поскольку я сосредоточился на обеспечении, а не на элементах): он запускает наивный стек и стек демона последовательно и выводит пару PNG + транскрипт. Используется то же оборудование, те же три GGUF ( SmolLM2-360M-Instruct-Q4_K_M , Qwen2-0.5B-Instruct-Q4_K_M , Llama-3.2-1B-Instruct-Q4_K_M — в сумме ~1,4 ГиБ на диске, все из аккаунта bartowskiHugging Face), те же -ngl 99 -c 172032 Видеокарта — NVIDIA GTX 1080 (8 ГБ, Pascal) , драйвер 535.309.01, файл llama.cpp закреплена под тегом b9724 .

Дорожка А — три терминала, три двойных файла llama-completion

Исходные данные, до начала обработки: на карте использовано 22 МиБ.

4989154a5c59520dd8b5254ed3fbff7e

Сначала запустите Llama 3.2 1B . Один процесс, сразу 6536 МиБ :

18c495cdfbc353113d49bbcf7187afe9

попробуйте добавить Qwen2 0.5B . Текст сообщения заканчивается ошибкой qwen процесс завершился и сообщением cudaMalloc не удалось: недостаточно памяти при выделении KV-буфера размером 1536 МБ:

b5bb8c2e857f5c006329f16ae51838c5

попробуйте SmolLM2 360M — самый маленький из трех. Результат тот же:

04 naive Third attempt fails

Итоговый результат: один резидентный процесс, два сообщения об день. «Демонстрация с разными агентами» на самом деле представляет собой демонстрацию с одним агентом и двумя трассировками ошибок.

Трек B — lmxd занимает все три

Та же карта. Те же три модели. Запускаем демона с небольшой текстовой таблицей, которая сопоставляет каждый GGUF с его размером на диске, указываем бюджет --admission-percent 90 и отправляем три последовательных строки REGISTER через nc -U. Итоговый обмен STATUS + LIST — это весь заголовок этого сообщения:

1d0094ff9e65ed7cc1003bb65918cdf2

Три разных некоторые модели инструкций, три разных модели агентов с идентификаторами, забронированы 1,58 ГБ при максимальном количестве в 7,73 ГБ , на том же оборудовании, которые обеспечивают треку А в игре только с одним экземпляром.

Заголовок не в том, что «демон работает быстрее». Дело в том, что демону удаётся связать три элемента там, где наивный стек справляется только с одним. И когда он в конечном итоге отклоняет запрос, он возвращает кодовую формулу — ERR VRAM_LEDGER_DENY code=CAPledger_max_bytes=...ledger_allocated_bytes=... request_table_bytes=... — с точно такими же числами чисел, которые нужны оператору для отладки. Неудача, которая заставляет себя сама, — это прекрасно, не правда ли?

Продолжение трека B — зарегистрированные агенты, которые действительно расшифровывают

Приём агентов — это только половина дела. Если эти агенты не могут работать параллельно, то весь этот пост имеет смысл. Другая половина — это решение о том, какой из них получит активный llama_contextпрямо сейчас, потому что в любой момент времени только один из них умножает тензоры. Демон включает и эту половину: жизненный циклический контекст для каждого агента в lmx::LlamaContextManager , реальное декодирование llama.cpp в lmx::AgentRuntime , вытеснение KV-кэш в хосте оперативной памяти через lmx::KvSwapHelper при каждом переключении агента, и всё это за одну дополнительную команду IPC — DECODE .

Тот тот же сокет. Два зарегистрированных агента ( smol , qwen ). Мы запускаем три последовательных вызова DECODE для переключения между ними: холодный запуск, подкачка, которая выгружает кэш-ключ-значение активного агента в памяти хоста, и заключительная подкачка, которая возвращает его обратно в новом контексте. На каждом шагу в ответном резком появлении, что именно демону пришлось переместить:

22d142b7f7efea4881e93603169b3ebb

Три вызова, три разных состояния обмена ключом-значением, за общее время около 440 мс на одной и той же видеокарте GTX 1080:

Вызов KV_swap_evicted KV_swap_restored Что случилось
DECODE smol … никто ЛОЖЬ Холодный старт. Менеджер заново создал llama_context для smol.
DECODE qwen … smol ЛОЖЬ Менеджер сериализовал полное контекстное состояние smol в буфер временных файлов хоста, освободил контекст smol и создал новый контекст qwen.
DECODE smol … qwen истинный Менеджер выгнал qwen таким же образом, а затем восстановил сохраненный хостом KV-файл с нуля в совершенно новом десятилетии, чтобы разговор продолжался.

Что на самом деле находится на графическом процессоре в выбранном режиме, когда обе модели регистрируются и декодирование результата происходит очень быстро:

714f0aec39486c280af1c39455d8b15f

926 МиБдля двух зарегистрированных малых моделей с контекстом реального времени — значительно меньше запланированных 7,7 ГиБ. Процесс lmxd —единственный пользователь CUDA на карте. Приостановленные агенты не используют видеопамять; Их состояние ключа-значения сохраняется в оперативной памяти хоста до тех пор, пока они не будут получены обратно в слот реального времени. Именно поэтому модель «регистрируй много, декодируй любой» остается дешевой.

Форма одного из ответов DECODE для скептиков, сомневающихся в точности передачи данных:

 ОК, схема=lmx-daemon/1 ОК, вызов=DECODE Agent_id=smol ОК, Prompt_tokens=4 сгенерированные_токены=24 Stop_on_eos=false ОК elapsed_ms=125.495 ОК kv_swap_evicted=qwen kv_swap_restored=true BEGIN_RESPONSE [Погода]. Я с нетерпением жду встречи со всеми вами. Я [Ваше имя] и я [Ваш END_RESPONSE

Строки kv_swap_evicted/kv_swap_restored постепенно изменяет весь операционный процесс. Если DECODE завершается за 125 мс, и в строке указано kv_swap_restored=true , вы точно знаете две вещи: демон вернулся один цикл обмена данными PCIe для восстановления связи, и агент, с которым вы только что включили, были участниками (не завершены, не выдана ошибка нехватки памяти) до звонка.

Откровенные революции

На этом этапе я должен признаться: по образованию я не специалист по графическим процессорам. Я считаю, что из телекоммуникационной отрасли — 5G с зачатками исследований в области 6G — и моя «инфраструктурная» проблема в агентном ИИ выглядит так, что мы уже много лет назад решили ее на уровне радиосвязи. Она запускает механизм контроля доступа к соединению (CAC) — может ли сотовая сеть принять этот новый сеанс, не нарушая соглашение об уровне обслуживания (SLA) для всех уже проведенных сеансов? Если да, то принять. Если нет, то отклонить при установке соединения с четким кодом. Никогда не прерывайте текущий вызов, чтобы уменьшить место для нового.

Сравните эти два примера и скажите мне с главным лицом, что это разные проблемы:

Вышка сотовой связи 5G (управление доступом к соединению) lmxd на графическом процессоре (с использованием видеопамяти)
Пропускная способность соты = доступные радиоресурсы Емкость устройства = 90 % от vram_total_bytes
Допущенные монеты потребляют определенный ресурс ресурсов. Допущенные агенты потребляют отдельные ресурсы ledger_allocated_bytes
Новая сессия начинается с указанием предполагаемой суммы запроса. Новый агент приезжает с table_bytes из таблиц VRAM.
До случаев тогда и только тогда, когда existing + new ≤ cell бюджеткод> Допустить, если allocated + new ≤ledger_max_bytes
Решение принимается до обоснования. Решение принимается до загрузки GGUF-файла.
Путь отклонения не наносит вред существующему вызовам. Путь отклонения резервов существующих агентов невредимыми.
Пропустить CAC → камеры разговариваются, все принимают, никого не используют. Пропустите бухгалтерскую книгу → все соревнуются cudaMalloc , выживает только один

Планировщик MAC на каждой вышке сотовой связи, начиная с 3G, принимает именно такое решение. Если бы вы предложили систему LTE, в которой каждый телефон брал вас к себе домой, планировщик «разобрался бы с этим позже», вежливо вывели из зала 3GPP. И тем не менее, именно это и делает тестовую демонстрацию «запустить три процесса llama-cli и надеяться на лучшее» на 8-гигабайтном графическом процессоре. То же самое животное в новом зоопарке.

Последний совет — загружайте только тот слой, который вам нужен действительно.

Давайте на мгновение задумаемся о цифрах. Три агента, которым принадлежат три разных LLM — скажем, примерно по 4 ГБ каждый, с 4-битным квантованием — в сумме занимают около 12 ГБ на диске. В вашей карте всего 8 ГБ. Наивная загрузка «всех трех моделей, полностью резидентных, постоянно» всегда будет содержать проигрыш. Но это не то, что на самом деле нужен прямой проход в любую миллисекунду. Этот этап декодирования трансформера затрагивает один слой за раз. Поэтому, если вы разместите в видеопамяти только один слой (~1,5 ГБ), плюс контекст CUDA (~500 МБ), плюс активный блок кэша-значение, ваш объем памяти в любую миллисекунду никогда не трансформируется 3–4 ГБ, а остальные 14 ГБ весов будут находиться в фиксированной оперативной памяти хоста, ожидая этого. очередь.

Суть в том, что «ожидание своей очереди» не может означать «задержку ядер CUDA, пока мы не получим данные следующего уровня через PCIe». На PCIe 3.0×16 видеокарты GTX 1080 (~12 ГБ/с в исходном режиме) чтение одного слоя размером 1,5 ГБ занимает около 125 мс. Если ждать этого постепенно, вы создадите самую низкую в мире систему LLM. Хитрость — и это настоящая хитрость — заключается в том, что вычисления на слоях N перекрывались с слоем передачи данных N+1 с помощью двух разных потоков CUDA, так что в тот момент, когда ядро ​​закончат умножение, у вас уже будет следующий слой в заранее выделенном слоте подкачки. Подкачка указателей. Повторять. Бесконечно.

Одна фиксированная, блокируемая страницами среда выполнения хоста ( cudaHostAlloc ), два буфера пинг-понга на стороне устройства, два потока CUDA, тайминги cudaEvent для каждого слоя и цикла "горячих точек":

 for (int i = 0; i < cfg_.n_layers; ++i) { //Шаг 1: хост cudaMemcpyAsync->устройство на Transfer_stream, затем синхронизируйте. Никаких перекрытий. check_cuda("cudaEventRecord(transfer_start.serial)", cudaEventRecord(impl_->ev_transfer_start[i], impl_->transfer_stream)); check_cuda( "cudaMemcpyAsync(serial)", cudaMemcpyAsync(d_curr, impl_->pool.slot_ptr(static_cast<:size_t>(i)), cfg_.bytes_per_layer, cudaMemcpyHostToDevice, impl_->transfer_stream)); check_cuda("cudaEventRecord(transfer_end.serial)", cudaEventRecord(impl_->ev_transfer_end[i], impl_->transfer_stream)); check_cuda("cudaStreamSynchronize(transfer.serial)", cudaStreamSynchronize(impl_->transfer_stream)); //Шаг 2: запустить вычислительное ядро ​​на Compute_stream и синхронизировать его перед следующим итером. check_cuda("cudaEventRecord(compute_start.serial)", cudaEventRecord(impl_->ev_compute_start[i], impl_->compute_stream)); Layer_compute_kernel<<compute_stream>>>( static_cast(d_curr), static_cast(impl_->d_output), impl_->elements_per_layer, cfg_.compute_iters); check_cuda("Ошибка запуска ядра (последовательный)", cudaGetLastError()); check_cuda("cudaEventRecord(compute_end.serial)", cudaEventRecord(impl_->ev_compute_end[i], impl_->compute_stream)); check_cuda("cudaStreamSynchronize(compute.serial)", cudaStreamSynchronize(impl_->compute_stream)); }

Поток A (вычисления) измотан от нестабильной работы; поток B (передача) незаметно подводит следующий слой; никто на графическом процессоре никогда не простаивает в ожидании визита. Два вызова cudaStreamSynchronize— единственные истинные последовательные точки на уровне, и в хорошем настроенном конвейере они ничего не делают — поток B завершает передачу примерно 30 мс назад и ждет, пока поток A догонит его. базовым исполнением (передача-вычисление, один поток за раз) на том же оборудовании, с теми же выделениями памяти, теми же синтетическими весами и выводит два значения времени выполнения одновременно. На эталонном компьютере с GTX 1080 в конфигурации по умолчанию (8 слоев × 64 МиБ × 64 итерации FMA на элементе) он выдаёт следующий результат:

 HEADLINE: серийный=151.41 мс, перекрытие=117,85 мс, экономия=22,17%, ускорение=1,285x

Если довести конфигурацию до предела пропускной способности ( --n-layers 16 --bytes-per-layer 67108864 --compute-iters 64 ), экономия увеличивается примерно до 32% (ускорение в 1,47 раза ). Для полной прозрачности протестированное здесь ядро ​​для каждого слоя представляет собой репрезентативный FMA-проход по стоимости. Честная цель состоит в том, чтобы обеспечить работоспособность асинхронного перекрытия шаблона на первом кремнии, а не в утверждении, что мы переписали весь граф декодирования llama.cpp . Замена этого синтетического ядра на полный прямой проход трансформера — это во многом есячая работа; в этой репозитории применяется примитив C++ для работы на аппаратном уровне, что делает эту будущую работу возможной.

Есть и вторая часть этой хитрости, напрямую связанная с архитектурой SwarmKV. При переходе от Модели 1 к Модели 2 геометрия кэша-значение ключа полностью меняется — другое количество головок, другие измерения, другие типы данных. Чтобы сохранить работоспособность, оркестратор должен сериализовать ключ-значение активного блока обратно в оперативную память хоста в тот момент, когда завершается прямой проход Модели 1, освобождение видеопамяти для Модели 2.

Это тот же самый llama_state_get_data → буфер хоста → llama_state_set_data , только это отличие между различными моделями, а не между ветвями одной и той же модели. Мы поставляем этот примитив как lmx::KvSwapHelper , бессостоятельную обертку над сериализацией API из llama.cpp . Когда контекст менеджера выполняет это при каждом <коде>DECODE между агентами, он выдает точно такие же записи kv_swap_evicted и kv_swap_restored , которые вы видели выше. Именно это делает эти цифры математической реальностью, а не просто взглядом.

Как это попробовать, что рамка входит, а чего нет.

Итак, теперь самое время обсудить, как вы можете использовать это решение в своей повседневной жизни. жизни.

Ссылка на репозиторий GitHub: https://github.com/AnubhabBanerjee/VRAM-Conductor

Я назвал его «Кондуктор VRAM», потому что он работает точно так же, как кондуктор автобуса в час пик: вчера билеты забирает пассажиров по вагонам ГПУ и активно сообщает следующему пассажиру: «Извините, автобус полон», прежде чем весь вагон перевернется. Кроме того, мой альтернативный вариант «Вышибала, который не позволяет трем агентам ИИ зарезать друг друга из-за последнего мегабайта памяти» оказался слишком длинным для ссылок на GitHub.

Если вы воспроизведете это на своей собственной открытке:

 git clone  && компакт-диск <каталог репо>cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DLMX_WITH_LLAMA_CUDA=ON cmake --build build -j"$(nproc)" # Автономная демонстрация перекрытия (файл модели не требуется): ./build/src/layer_stream_demo # Daemon. Как только он запущен, каждый РЕГИСТР признает, что каждый ДЕКОДЕ запускает настоящий llama.cpp с танцем # KV-свопа, происходящим прозрачно на каждом коммутаторе агента: ./build/src/lmxd --socket /tmp/lmxd.sock --vram-table configs/model_vram.example.txt & nc -U /tmp/lmxd.sock <<РЕГИСТРАЦИЯ EOF smol /home/you/models/SmolLM2-360M-Instruct-Q4_K_M.gguf РЕГИСТРАЦИЯ qwen /home/you/models/Qwen2-0.5B-Instruct-Q4_K_M.gguf EOF printf 'DECODE smol 24 Здравствуйте, меня зовутn' | nc -U /tmp/lmxd.sock printf 'DECODE qwen 24 Столица Франции —n' | nc -U /tmp/lmxd.sock printf 'DECODE smol 24 Погода сегодняn' | nc -U /tmp/lmxd.sock

Требования вполне ожидаемы: Linux, инструментарий CUDA, NVML, графический процессор NVIDIA (Pascal или более новая версия). Демону необходимо, чтобы положить GGUF в вашу таблицу VRAM действительно; потоковая демонстрация работает с синтетическими весами и требует только инструментария.

Прежде чем кто-либо начнет критиковать, вот честный список того, что этот репозиторий не заявляет:

  • Ядро LayerStreamer для каждого слоя представляют собой репрезентативные алгоритмы FMA с учетом стоимости, а не настоящий прямой проход по трансформерному слою. Замена его на ядро, запускающее квантовые ядра умножения матриц из <код>llama.cppкод>В рамках нашей оркестрации потока требуется либо переписывание стека умножения матрицы с использованием потокового указателя весов, либо внесение изменений в механизм запуска графов llama.cpp — оба проекта рассчитываются на несколько месяцев и явно учитываются в рамках этого репозитория. В схему входит демонстрация инженерного примитива: фиксированный хост + два потока CUDA + двойной буферизованный обмен данными, которые фактически переоткрываются на первом кремниевом кристалле с зависимой экономией времени, на той же карте, которую даже три простых процесса llama-completion нельзя использовать вместе. Путь DECODE запускает реальный llama.cpp от начала до конца — он просто не управляет стримером внутри цикла декодирования.
  • Однослотовая контекстная модель сериализует агентов на графике процессор. Каждый активен только один llama_context; ключ-значение остальное находится в оперативной памяти хоста. Одновременное декодирование двух агентов не входит в задачу — для этого требуется работа над LayerStreamer-inside-decode, описанная выше, которая является значительной частью этой репозитории. stat(1) масштабированы в 1,5 раза). Теперь, когда в деле началось реальное декодирование, эти оценки должны увеличиться, чтобы более точно открыть кэш-значение, активацию и запас кэширования CUDA — либо в виде формулы для каждой модели, либо в виде онлайн-измерения после первого декодирования.
  • Демон считывает NVML-данные один раз при включает , а затем отслеживает изменения только с помощью обычного try_reserve/release . NVML-данные в последнее время отображаются в STATUSдля операторов, но не используется для обнаружения других процессов, храните на время работы демона. Производственные системы будут синхронизироваться по таймеру.
  • Одна видеокарта, один процесс, один клиент одновременно. Размещение нескольких видеокарт и высокая производительность на такт выполнения за рамки данной задачи.

Однако ничто из этого не меняет заголовок.

Сворачивать

Давайте честно: большинство демонстраций «многоагентной обработки на одном графическом процессоре» в 2026 году не использовать хитроумное планирование памяти. Они просто запускают три процесса на видеокарте, закрывают глаза и надеются, что всё заработает само собой.

lmxd — это не какой-то волшебный новый алгоритм ИИ. Это всего лишь вышибала буфер обмена. Он берет управление доступом к соединениям, что является более старым, чем первый телефон-раскладушка, и реализует его с помощью демона на C++, строгого реестра видеопамяти и общего бэкэнда. Результат? Три разные модели инструкции вежливо делят 8-гигабайтную карту вместо того, чтобы писать за нее насмерть.

Вывод прост: отказ от невыполнимой работы дороже, чем оптимизация возможной. Можно иметь такие передовые системы в мире спекулятивного декодирования и маршрутизации МЧС, но это не спасет, если вы слепо допустите третьего агента, который физически не сохраняется в памяти. Хорошо предоставляемые системы проверяют бюджет до начала работы.

Клонируйте репозиторий. Запустите демона. Затем внимательно изучите свои собственные конвейеры, чтобы увидеть, сколько из них тихонько выживают, полагаясь на слепую удачу. Если вы пришли сюда, задаваясь вопросом, почему три запуска LLM на старом графическом процессоре не работают сразу, поздравляю — теперь вы понимаете аппаратные ограничения лучше, чем большинство людей, пишущих эти руководства. С любовью.

Скриншоты входа (трек A, 01 ) и панель STATUS+LIST демона ( 05 ) представляют собой прямые рендеры различных стенограмм nvidia-smi и lmxd 04 приведенных 20.06.2026. Панель DECODE-with-KV-swap ( 06 ) и панель nvidia-smi в стационарном состоянии ( 07 ) (обе в треке B, продолжение) получено от демона lmxd запускающего реальное декодирование llama.cpp на той же видеокарте NVIDIA GTX 1080 22.06.2026. Панели 06 и 07 включают подсветку синтаксиса во время рендеринга — зеленую для строки подтверждения kv_swap_*, синюю для OK, желтую для подсказок $ — для замедления отслеживания процесса замены. Обе панели представляют собой рендеры PIL реального результата демона.

Анубхаб Банерджи Посмотреть все в Анубхаб Банерджи

Источник: towardsdatascience.com

Оцените материал:

Поделиться
Понравилась статья? Расскажите другим
ВКонтакте
Читайте также
Архив рубрики ~Лента новостей~ Козы уследили за направлением человеческого голоса. И смогли таким образом найти еду Архив рубрики ~Лента новостей~ В ходе аналитических игр отрабатываются стратегии реагирования на сценарии катастроф в космической войне. Архив рубрики ~Лента новостей~ Гениальный способ, которым я устранил ужасное сообщение «Внутри iPhone заполнено». Новости робототехники Мягкие роботизированные клетки по морфе встраивают физическую ИИ в оборудование Архив рубрики ~Коротко из Telegram~ Российские ИИ-телевизоры начали активно вытеснять зарубежные модели — покупатели всё… Архив рубрики ~Коротко из Telegram~ 🇨🇳 Китай создал самый мощный суперкомпьютер в мире LineShine заняла… Архив рубрики ~Коротко из Telegram~ ⚙️ Apple выпускает следующие бета-версии для разработчиков: • iOS 26.6… Архив рубрики ~Коротко из Telegram~ Вышел Lumo 2.0: обновление приватного ИИ-чатбота от Proton Команда Proton… Архив рубрики ~Лента новостей~ Threads добавляет новые функции в Live Chats, расширяя доступ к сервису. Архив рубрики ~Коротко из Telegram~ В Азербайджане запретили регистрироваться в соцсетях детям младше 16 лет… Архив рубрики ~Коротко из Telegram~ Нашли ИИ-агента с мегаконтекстом, который не забывает вообще ничего. В… Архив рубрики ~Коротко из Telegram~ Алиса AI серьёзно обновилась. Протестировали сами и рассказываем о самом… Архив рубрики ~Лента новостей~ Эпидемиология дампов памяти: исправление ошибки 18-летней давности | OpenAI Новости робототехники Что делает роботов экономически жизнеспособным бизнесом Архив рубрики ~Лента новостей~ Козы уследили за направлением человеческого голоса. И смогли таким образом найти еду Архив рубрики ~Лента новостей~ В ходе аналитических игр отрабатываются стратегии реагирования на сценарии катастроф в космической войне. Архив рубрики ~Лента новостей~ Гениальный способ, которым я устранил ужасное сообщение «Внутри iPhone заполнено». Новости робототехники Мягкие роботизированные клетки по морфе встраивают физическую ИИ в оборудование Архив рубрики ~Коротко из Telegram~ Российские ИИ-телевизоры начали активно вытеснять зарубежные модели — покупатели всё… Архив рубрики ~Коротко из Telegram~ 🇨🇳 Китай создал самый мощный суперкомпьютер в мире LineShine заняла… Архив рубрики ~Коротко из Telegram~ ⚙️ Apple выпускает следующие бета-версии для разработчиков: • iOS 26.6… Архив рубрики ~Коротко из Telegram~ Вышел Lumo 2.0: обновление приватного ИИ-чатбота от Proton Команда Proton… Архив рубрики ~Лента новостей~ Threads добавляет новые функции в Live Chats, расширяя доступ к сервису. Архив рубрики ~Коротко из Telegram~ В Азербайджане запретили регистрироваться в соцсетях детям младше 16 лет… Архив рубрики ~Коротко из Telegram~ Нашли ИИ-агента с мегаконтекстом, который не забывает вообще ничего. В… Архив рубрики ~Коротко из Telegram~ Алиса AI серьёзно обновилась. Протестировали сами и рассказываем о самом… Архив рубрики ~Лента новостей~ Эпидемиология дампов памяти: исправление ошибки 18-летней давности | OpenAI Новости робототехники Что делает роботов экономически жизнеспособным бизнесом

Оставить комментарий