TGTGInsightаналитика telegramLIVE / telegram public index
← AI-Driven Development. Родион Мостовой
AI-Driven Development. Родион Мостовой avatar

TGINSIGHT POST

Post #47

@ai_driven

AI-Driven Development. Родион Мостовой

Просмотры976Количество просмотров
Опубликован20 янв.20.01.2025, 11:19
Содержимое поста

Содержимое

o1-pro: Реализуем Parallel.ForEachAsync с регулируемым "на лету" параллелизмом Не все знают, но в .NET начиная с 6-й версии есть прекрасный метод Parallel.ForEachAsync, который позволяет распараллелить задачу на заданное количество воркеров. По умолчанию выполнение происходит на тредпуле, поэтому в случае использования асинхронных методов некоторые задачи можно решать в десятки, а то и в сотни раз быстрее. Одна из таких задач - это, например, генерация embeddings или любые другие обращения к облачной LLM или даже парсинг. Это здорово работает, но иногда возникает необходимость изменять количество воркеров (degree of parallelism) "на лету" - к примеру, в зависимости от пропускной способности LLM провайдера. Написать такую реализацию DynamicParallel.ForEachAsync самому, конечно, можно, но делать это нужно максимально внимательно с учетом всяких возможных дедлоков и race condition. Делегировать эту задачу LLMке я порываюсь еще с сентября, но тогда ни GPT-4o, ни Sonnet 3.5 ни в какую не могли с этим справиться, хотя бы даже частично. Шаги к решению 1. Итак, я попросил o1-pro сначала реализовать решение, позволяющее динамически регулировать параллелизм на лету. После примерно 5-ти минут раздумий, она выбрала решение на основе SemaphoreSlim (из цепочки рассуждений было видно, что так же в серьез рассматривалось решение с DataFlow, но в итоге было отметено ну и хорошо) 2. Затем, понимая комплексность задачи (многопоточный код жеж), я попросил Claude Sonnet 3.5 и обычную o1 поревьюить результат (ревью от Клода там во втором моем сообщении) - они нашли какие-то проблемы, на первый взгляд мне они показались адекватными и я правки принял. 3. Взял результат из шага 2, и попросил o1-pro на базе DynamicConcurrencyLimiter реализовать метод DynamicParallel.ForEachAsync. И тут важно отметить, что для упрощения нейросети задачи, я добавил ей в контекст исходник оригинального Parallel.ForEachAsync из актуальных сорсов .NET (оригинальный ForEachAsync НЕ поддерживает динамический параллелизм). Кстати, обратите внимание, что промпты в моих сообщениях довольно простые (для reasoning моделей это ок). 4. [важнейший шаг] Я попросил o1-pro написать тесты на получившиеся классы. Про валидацию решения Валидация сгенерированного кода тестами - это эдакий лайф-хак, особенно на случай когда код получается реально сложный и его трудно проверить глазами - тут уже важно, чтобы программист имел понимание возможных уязвимых мест разного сложного кода и всевозможных corner cases - в принципе, этот этап тоже можно практиковать совместно с LLM, но я бы все равно рекомендовал взять минутку и продумать какие-то сложные сценарии, которые должны быть покрыты. Возвращаюсь к нашему кейсу мне показалось важным убедиться в 3-х вещах: 1. В том, что при активной динамической регулировке параллелизма не возникает дедлоков (не хотелось бы где-то встрять навечно). 2. В том, что все элементы действительно обрабатываются. 3. В том, что каждый элемент списка обрабатывается ровно один раз. Поэтому, я сам спроектировал еще один дополнительный стресс-тест, который делает все эти вещи. В этом тесте сейчас 1M элементов - но когда речь идет о проверке потокобезопасности, этого может быть мало для надежного теста, больше того, по-хорошему подобные решения еще имеет смысл прогонять на разных архитектурах (x86, arm, ...), т. к. из-за различий в модели памяти поведение многопоточного кода может слегка отличаться. Кстати, здесь бывает полезным погонять тесты в цикле до провала (в течение дня, например) - в Rider для этого есть опция Run Unit Tests Until Failure. Что в итоге? После пары косметических правок (в тестах было 2 ошибки компиляции), к моему удивлению, все тесты прошли с первого раза. Код и тесты тут. Продолжение.