Red eyes is all you need, или пихаем LLM в FPGA
Вдохновился недавней новостью, о том, что LLM зашили в железо, и решил попробовать повторить в меньших масштабах, написав проект на verilog, где ~854K модель зашивается в Artix-7 (XC7A200T). Задачей было уложиться в бюджет 365 BRAM блоков (потому что я слишком нищий для более серьезной борды), поэтому была выбрана архитектура с 128 embedding dim, 8 attn heads, 4 слоя, и размером контекста 256. Оно упирается как раз впритык - веса заняли 209 блоков, KV-кэш - ещё 128.
Из интересного - веса/активации находятся сразу в BRAM в int8, разбиваясь на отдельные файлы через extract_weights.py (LayerNorm’ы пихаются в один файл, так как они мелкие и тратить по блоку на каждый из них - слишком дорого). Попутно генерируется weight_scales.vh, чтобы в рантайме адекватно перевести все это в fp16 для активаций.
Для Softmax и sqrt(1/x) использовал статью, которую вкратце описал выше [тык]
GELU был реализован по этой статье [ссылка]: erf аппроксимируется кусочно-линейной функцией, используя нечётную симметрию erf(-x) = -erf(x), что вдвое сокращает область аппроксимации. Breakpoints ищутся через EPSS (Error Peak Search Strategy) - это итеративный алгоритм: на каждом шаге находит локальные максимумы ошибки аппроксимации внутри каждого сегмента через argrelextrema и вставляет туда новые breakpoints. В результате breakpoints концентрируются там где erf кривее (около нуля) и разреживаются на плоском хвосте.
На данном этапе проект доведен до полной RTL-симуляции: есть тесты сравнения идеальных операций в ideal_ops.py, RTL операций на питоне rtl_ops.py (pure-python fp16-примитивы, которые воспроизводят поведение RTL бит-в-бит, включая rounding и flush-to-zero) и сравнение полученных результатов из xsim. В принципе transformer_top.v выдает что-то похожее на когерентный текст в симуляциях, и осталось только дописать поддержку temperature, top-k и т.п., а также интерфейсы для самой железки. В дальнейшем, наверное, напишу еще один пост про результаты и оптимизации (потенциально можно улучшить скорость инференса, если распараллелить операции с плавающей точкой)
Поковырять исходники можно здесь [тык]
Вдохновился недавней новостью, о том, что LLM зашили в железо, и решил попробовать повторить в меньших масштабах, написав проект на verilog, где ~854K модель зашивается в Artix-7 (XC7A200T). Задачей было уложиться в бюджет 365 BRAM блоков (потому что я слишком нищий для более серьезной борды), поэтому была выбрана архитектура с 128 embedding dim, 8 attn heads, 4 слоя, и размером контекста 256. Оно упирается как раз впритык - веса заняли 209 блоков, KV-кэш - ещё 128.
Из интересного - веса/активации находятся сразу в BRAM в int8, разбиваясь на отдельные файлы через extract_weights.py (LayerNorm’ы пихаются в один файл, так как они мелкие и тратить по блоку на каждый из них - слишком дорого). Попутно генерируется weight_scales.vh, чтобы в рантайме адекватно перевести все это в fp16 для активаций.
Для Softmax и sqrt(1/x) использовал статью, которую вкратце описал выше [тык]
GELU был реализован по этой статье [ссылка]: erf аппроксимируется кусочно-линейной функцией, используя нечётную симметрию erf(-x) = -erf(x), что вдвое сокращает область аппроксимации. Breakpoints ищутся через EPSS (Error Peak Search Strategy) - это итеративный алгоритм: на каждом шаге находит локальные максимумы ошибки аппроксимации внутри каждого сегмента через argrelextrema и вставляет туда новые breakpoints. В результате breakpoints концентрируются там где erf кривее (около нуля) и разреживаются на плоском хвосте.
На данном этапе проект доведен до полной RTL-симуляции: есть тесты сравнения идеальных операций в ideal_ops.py, RTL операций на питоне rtl_ops.py (pure-python fp16-примитивы, которые воспроизводят поведение RTL бит-в-бит, включая rounding и flush-to-zero) и сравнение полученных результатов из xsim. В принципе transformer_top.v выдает что-то похожее на когерентный текст в симуляциях, и осталось только дописать поддержку temperature, top-k и т.п., а также интерфейсы для самой железки. В дальнейшем, наверное, напишу еще один пост про результаты и оптимизации (потенциально можно улучшить скорость инференса, если распараллелить операции с плавающей точкой)
Поковырять исходники можно здесь [тык]
🔥42 7👻1🗿1
Марков цепи пропил
Максимально проклято
С огромной долей вероятности оно все сведется к +/- этому
iCTS: Iterative and Hierarchical Clock Tree Synthesis With Skew-Latency-Load Tree [ссылка]
Прикольная статья, в которой китайцы предлагают интересный метод оптимизации тактового дерева.
Если кратко, то синхронная цифровая схема держится на одном допущении: каждый триггер видит фронт тактового сигнала одновременно (иначе данные защёлкнутся раньше, чем вычисление закончилось, и триггер захватит некорректный результат).
Но "одновременно" физически невозможно. Сигнал идёт от одного источника до тысяч триггеров по металлическим проводам с сопротивлением и емкостью, разные триггеры находятся на разном расстоянии, и сигнал приходит в разное время (эта разница называется skew).
Решение - буферы. Они восстанавливают деградировавший фронт и добавляют контролируемую задержку, и если вставить буферы в нужных точках сети, суммарная задержка по всем путям выравнивается и каждый триггер получает фронт в +/- одно и то же время. Собственно, автоматическое построение такого дерева буферов и называется Clock Tree Synthesis.
Звучит как инженерная задача с однозначным решением, но внутри три цели, которые конфликтуют между собой: минимальный skew, минимальная длина проводов и минимальная latency. Получить всё три одновременно - NP-hard.
Стандартный CTS flow разбит на два независимых шага без обратной связи: сначала DME (алгоритм балансировки топологии тактового дерева) фиксирует структуру дерева: где провода соединяются и как ветвятся. Потом отдельный алгоритм (как правило, ван Гинекен) вставляет буферы уже в готовую структуру: обходит дерево снизу вверх, в каждом узле оценивает варианты буферов с учётом ёмкостной нагрузки downstream и выбирает лучший. Топологию изменить уже нельзя, поэтому алгоритм подстраивается под то, что есть.
Чтобы выровнять задержки на коротких ветках, DME добавляет wire elongations - буквально удлиняет провода змейкой, из-за чего растёт ёмкость и мощность. Буферы потом сайзятся под худший случай нагрузки, часто избыточно.
И это бьёт не только по мощности. Буферы занимают площадь кристалла, тактовые провода съедают ресурсы роутинга, рядом с кластерами буферов нужны decap-ячейки. Место, которое могло быть вычислениями, занято инфраструктурой доставки сигнала.
Тактовое дерево переключается каждый такт, независимо от того что делает логика. На высокопроизводительных процессорах это может отъедать до 50% от TDP, то есть если чип греется на 100 Вт, то до 50 Вт уходит только на доставку тактового сигнала, а не на вычисления (пара статей про это - [тык], [тык], [тык])
Исследователи из CUHK и Pengcheng Lab предложили строить топологию и буферизацию одновременно с явным trade-off между skew, latency и wirelength. В классическом DME точка слияния двух поддеревьев единственная, где задержки выравниваются, и никакого выбора нет. iCTS расширяет это до отрезка допустимых точек слияния, где любая точка даёт skew в пределах заданной границы, и на этом отрезке алгоритм выбирает точку, минимизирующую сумму с явными весами для latency и wirelength.
Поверх этого иерархическая кластеризация группирует синки не по геометрической близости, а по ёмкостной нагрузке, потому что физически близкие синки могут нагружать дерево по-разному и ломать балансировку. Результат кластеризации дополнительно оптимизируется отжигом. Буферы оцениваются до вставки, что позволяет отсекать плохие варианты раньше. После буферизации ISCA итеративно перезапускает BST-DME на зафиксированной топологии, каждый раз уточняя оценки задержек, пока skew не достигает целевого значения.
На бенчмарках ISCAS'89, OpenLane, OpenCores и ysyx (от 1248 до 22810 flip-flop) при 28nm результат такой: коммерческий P&R инструмент проигрывает iCTS на 39.5% по skew, 13.0% по latency и 18.5% по capacitance, OpenROAD - на 101.6%, 50.7% и 25.5% соответственно. По clock power коммерческий инструмент потребляет в среднем на 32% больше, OpenROAD - на 81% больше.
Хоть в максимальном бенчмарке всего ~23к триггеров, иерархическая архитектура iCTS должна масштабироваться. Не знаю, применит ли это кто-нибудь в продакшене, но звучит многообещающе
Прикольная статья, в которой китайцы предлагают интересный метод оптимизации тактового дерева.
Если кратко, то синхронная цифровая схема держится на одном допущении: каждый триггер видит фронт тактового сигнала одновременно (иначе данные защёлкнутся раньше, чем вычисление закончилось, и триггер захватит некорректный результат).
Но "одновременно" физически невозможно. Сигнал идёт от одного источника до тысяч триггеров по металлическим проводам с сопротивлением и емкостью, разные триггеры находятся на разном расстоянии, и сигнал приходит в разное время (эта разница называется skew).
Решение - буферы. Они восстанавливают деградировавший фронт и добавляют контролируемую задержку, и если вставить буферы в нужных точках сети, суммарная задержка по всем путям выравнивается и каждый триггер получает фронт в +/- одно и то же время. Собственно, автоматическое построение такого дерева буферов и называется Clock Tree Synthesis.
Звучит как инженерная задача с однозначным решением, но внутри три цели, которые конфликтуют между собой: минимальный skew, минимальная длина проводов и минимальная latency. Получить всё три одновременно - NP-hard.
Стандартный CTS flow разбит на два независимых шага без обратной связи: сначала DME (алгоритм балансировки топологии тактового дерева) фиксирует структуру дерева: где провода соединяются и как ветвятся. Потом отдельный алгоритм (как правило, ван Гинекен) вставляет буферы уже в готовую структуру: обходит дерево снизу вверх, в каждом узле оценивает варианты буферов с учётом ёмкостной нагрузки downstream и выбирает лучший. Топологию изменить уже нельзя, поэтому алгоритм подстраивается под то, что есть.
Чтобы выровнять задержки на коротких ветках, DME добавляет wire elongations - буквально удлиняет провода змейкой, из-за чего растёт ёмкость и мощность. Буферы потом сайзятся под худший случай нагрузки, часто избыточно.
И это бьёт не только по мощности. Буферы занимают площадь кристалла, тактовые провода съедают ресурсы роутинга, рядом с кластерами буферов нужны decap-ячейки. Место, которое могло быть вычислениями, занято инфраструктурой доставки сигнала.
Тактовое дерево переключается каждый такт, независимо от того что делает логика. На высокопроизводительных процессорах это может отъедать до 50% от TDP, то есть если чип греется на 100 Вт, то до 50 Вт уходит только на доставку тактового сигнала, а не на вычисления (пара статей про это - [тык], [тык], [тык])
Исследователи из CUHK и Pengcheng Lab предложили строить топологию и буферизацию одновременно с явным trade-off между skew, latency и wirelength. В классическом DME точка слияния двух поддеревьев единственная, где задержки выравниваются, и никакого выбора нет. iCTS расширяет это до отрезка допустимых точек слияния, где любая точка даёт skew в пределах заданной границы, и на этом отрезке алгоритм выбирает точку, минимизирующую сумму с явными весами для latency и wirelength.
Поверх этого иерархическая кластеризация группирует синки не по геометрической близости, а по ёмкостной нагрузке, потому что физически близкие синки могут нагружать дерево по-разному и ломать балансировку. Результат кластеризации дополнительно оптимизируется отжигом. Буферы оцениваются до вставки, что позволяет отсекать плохие варианты раньше. После буферизации ISCA итеративно перезапускает BST-DME на зафиксированной топологии, каждый раз уточняя оценки задержек, пока skew не достигает целевого значения.
На бенчмарках ISCAS'89, OpenLane, OpenCores и ysyx (от 1248 до 22810 flip-flop) при 28nm результат такой: коммерческий P&R инструмент проигрывает iCTS на 39.5% по skew, 13.0% по latency и 18.5% по capacitance, OpenROAD - на 101.6%, 50.7% и 25.5% соответственно. По clock power коммерческий инструмент потребляет в среднем на 32% больше, OpenROAD - на 81% больше.
Хоть в максимальном бенчмарке всего ~23к триггеров, иерархическая архитектура iCTS должна масштабироваться. Не знаю, применит ли это кто-нибудь в продакшене, но звучит многообещающе
🔥10 6👌1👻1
Forwarded from Канал респекта и уважухи 2.0
разработчик мессенджера Max может добиться эрекции только когда его жена грязно шепчет ему на ухо, что выбрала его, только потому что все другие мужики, которые её реально возбуждали, оказались недоступны, а не потому что она типа его реально любит и хочет
😁48🤡4🙏3👻2 1 1
Simple Recipe Works: Vision-Language-Action Models are Natural Continual Learners with Reinforcement Learning [ссылка]
Tldr: челы решили проверить, есть ли смысл в существующих методах Continual RL для обучения роботов, или можно обойтись простым Sequential Fine-Tuning с LoRA и on-policy RL.
Большие VLA-модели вроде OpenVLA или Pi-0 хорошо обобщаются, но ломаются при деплое в меняющейся среде. Чтобы агент мог адаптироваться к новым задачам через взаимодействие со средой, его нужно дообучать с RL постепенно, не имея доступа к данным предыдущих задач. Но есть проблема: Sequential Fine-Tuning в такой постановке ведёт к катастрофическому забыванию, модель переобучается на текущей задаче и теряет навыки предыдущих.
Существующие методы Continual RL решают это тремя способами: регуляризацией весов (EWC штрафует обновления параметров, важных для прошлых задач), воспроизведением прошлого опыта (Expert Replay и Dark Experience Replay хранят демонстрации или логиты и перемешивают их с текущим обучением) и изоляцией параметров (Dynamic Weight Expansion выделяет отдельный LoRA-адаптер на каждую задачу). Для больших моделей есть и более свежие подходы: SLCA применяет разные learning rate для разных слоёв, RETAIN после каждой задачи вмёрживает веса обратно в базовую модель с коэффициентом дисконтирования.
Авторы берут простейший Seq. FT с LoRA и GRPO и гоняют его вместе со всеми перечисленными методами на трёх VLA-моделях (OpenVLA-OFT, OpenVLA, Pi-0) и пяти бенчмарках (LIBERO-Object, LIBERO-Spatial, LIBERO-Long, RoboCasa, ManiSkill). И в результате Seq. FT с LoRA и GRPO выигрывает или не уступает во всех сетапах, при этом падение успешности на прошлых задачах меньше 2%, нередко уходя в минус, то есть обучение на новых задачах ещё и улучшает старые. EWC, SLCA и RETAIN хуже учатся новым задачам - ограничения на обновления параметров мешают не только забывать старое, но и усваивать новое, поэтому итоговый avg ниже. DWE вообще не получает transfer между задачами. Replay требует хранить демонстрации и при этом ничего не выигрывает. Zero-shot на незнакомых задачах у Seq. FT в большинстве сетапов не уступает модели, обученной сразу на всех задачах одновременно, то есть верхней границе качества. А там, где Seq. FT всё же отстаёт, хватает просто подольше потренировать слабые задачи.
Авторы объясняют это синергией трёх вещей. On-policy RL неявно удерживает политику близко к базовой, потому что обновления не могут сдвинуть вероятностную массу туда, где базовая политика почти не имеет поддержки. Большой размер модели помогает потому, что в пространстве миллиардов параметров обновление по новой задаче почти не затрагивает направления, важные для предыдущих. LoRA не даёт отдельным слоям переписываться полностью, при этом пластичность не страдает: on-policy обучение при разреженном вознаграждении информационно дёшево, и ёмкости адаптера хватает.
Пока все эксперименты проводились в симуляции. Решат ли авторы главную проблему RL -он не работает плохую масштабируемость в реальных средах, пока неизвестно. Но понаблюдать за развитием можно здесь [тык]
Tldr: челы решили проверить, есть ли смысл в существующих методах Continual RL для обучения роботов, или можно обойтись простым Sequential Fine-Tuning с LoRA и on-policy RL.
Большие VLA-модели вроде OpenVLA или Pi-0 хорошо обобщаются, но ломаются при деплое в меняющейся среде. Чтобы агент мог адаптироваться к новым задачам через взаимодействие со средой, его нужно дообучать с RL постепенно, не имея доступа к данным предыдущих задач. Но есть проблема: Sequential Fine-Tuning в такой постановке ведёт к катастрофическому забыванию, модель переобучается на текущей задаче и теряет навыки предыдущих.
Существующие методы Continual RL решают это тремя способами: регуляризацией весов (EWC штрафует обновления параметров, важных для прошлых задач), воспроизведением прошлого опыта (Expert Replay и Dark Experience Replay хранят демонстрации или логиты и перемешивают их с текущим обучением) и изоляцией параметров (Dynamic Weight Expansion выделяет отдельный LoRA-адаптер на каждую задачу). Для больших моделей есть и более свежие подходы: SLCA применяет разные learning rate для разных слоёв, RETAIN после каждой задачи вмёрживает веса обратно в базовую модель с коэффициентом дисконтирования.
Авторы берут простейший Seq. FT с LoRA и GRPO и гоняют его вместе со всеми перечисленными методами на трёх VLA-моделях (OpenVLA-OFT, OpenVLA, Pi-0) и пяти бенчмарках (LIBERO-Object, LIBERO-Spatial, LIBERO-Long, RoboCasa, ManiSkill). И в результате Seq. FT с LoRA и GRPO выигрывает или не уступает во всех сетапах, при этом падение успешности на прошлых задачах меньше 2%, нередко уходя в минус, то есть обучение на новых задачах ещё и улучшает старые. EWC, SLCA и RETAIN хуже учатся новым задачам - ограничения на обновления параметров мешают не только забывать старое, но и усваивать новое, поэтому итоговый avg ниже. DWE вообще не получает transfer между задачами. Replay требует хранить демонстрации и при этом ничего не выигрывает. Zero-shot на незнакомых задачах у Seq. FT в большинстве сетапов не уступает модели, обученной сразу на всех задачах одновременно, то есть верхней границе качества. А там, где Seq. FT всё же отстаёт, хватает просто подольше потренировать слабые задачи.
Авторы объясняют это синергией трёх вещей. On-policy RL неявно удерживает политику близко к базовой, потому что обновления не могут сдвинуть вероятностную массу туда, где базовая политика почти не имеет поддержки. Большой размер модели помогает потому, что в пространстве миллиардов параметров обновление по новой задаче почти не затрагивает направления, важные для предыдущих. LoRA не даёт отдельным слоям переписываться полностью, при этом пластичность не страдает: on-policy обучение при разреженном вознаграждении информационно дёшево, и ёмкости адаптера хватает.
Пока все эксперименты проводились в симуляции. Решат ли авторы главную проблему RL -
Dial-up 2.0, или гоним трафик в обход белых списков через звонки в ВК
Пока железки в пути, решил заняться насущным вопросом, который в последнее время что-то обострился.
VK-звонки работают через WebRTC с Selective Forwarding Unit, который пробрасывает SCTP DataChannel между участниками, не заглядывая внутрь. Помимо своего animoji-канала (id:1) VK туда ничего больше не кладёт, поэтому можно создать рядом свой DataChannel (id:2) и использовать его как двунаправленный пайп для произвольных данных. Весь трафик при этом идёт через TURN-серверы VK, которые находятся в белых списках - для DPI это выглядит как обычный звонок.
На стороне Creator'а (тот, у кого есть доступ в интернет) запускается Go relay и hook.js сниппет в браузере. Сниппет хукает RTCPeerConnection, перехватывает ICE-конфигурацию прямо из конструктора, после чего создаётся туннельный DataChannel и бриджится с локальным WebSocket'ом, через который relay раздаёт трафик наружу.
На стороне Joiner'а (тот, кто в зоне белых списков) всё чуть интереснее. Приложение открывает VK-звонок в WebView, поднимает VpnService, который перехватывает весь IP-трафик устройства, прогоняет его через tun2socks, дальше в SOCKS5 прокси на Go, и уже оттуда через WebSocket в тот же DataChannel. Go-часть собирается через gomobile в .aar и линкуется прямо в APK, поэтому можно обойтись без рута/termux.
На speed test получилось добиться 9.57 Мбит/с на скачивание, 4.15 Мбит/с на загрузку с задержкой 14 мс до Брюсселя с мобильной сети.
Есть моменты, которые можно улучшить и автоматизировать, но в целом пока пойдет.
Код/билды здесь [тык]
Пока железки в пути, решил заняться насущным вопросом, который в последнее время что-то обострился.
VK-звонки работают через WebRTC с Selective Forwarding Unit, который пробрасывает SCTP DataChannel между участниками, не заглядывая внутрь. Помимо своего animoji-канала (id:1) VK туда ничего больше не кладёт, поэтому можно создать рядом свой DataChannel (id:2) и использовать его как двунаправленный пайп для произвольных данных. Весь трафик при этом идёт через TURN-серверы VK, которые находятся в белых списках - для DPI это выглядит как обычный звонок.
На стороне Creator'а (тот, у кого есть доступ в интернет) запускается Go relay и hook.js сниппет в браузере. Сниппет хукает RTCPeerConnection, перехватывает ICE-конфигурацию прямо из конструктора, после чего создаётся туннельный DataChannel и бриджится с локальным WebSocket'ом, через который relay раздаёт трафик наружу.
На стороне Joiner'а (тот, кто в зоне белых списков) всё чуть интереснее. Приложение открывает VK-звонок в WebView, поднимает VpnService, который перехватывает весь IP-трафик устройства, прогоняет его через tun2socks, дальше в SOCKS5 прокси на Go, и уже оттуда через WebSocket в тот же DataChannel. Go-часть собирается через gomobile в .aar и линкуется прямо в APK, поэтому можно обойтись без рута/termux.
На speed test получилось добиться 9.57 Мбит/с на скачивание, 4.15 Мбит/с на загрузку с задержкой 14 мс до Брюсселя с мобильной сети.
Есть моменты, которые можно улучшить и автоматизировать, но в целом пока пойдет.
Код/билды здесь [тык]
419🔥140 26😁9👻3🦄1