Разделение времени работы GPU для одновременной работы агентов LLM в Kubernetes
Прекратите доверять статусу пода. Комплексная платформа для измерения скрытых микроархитектурных затрат и конфликтов доступа к памяти при совместном использовании графических процессоров агентами ИИ в Kubernetes.
Делиться
В Python тестовые агенты запускаются по одному. Агенты, используемые в производственной среде, конкурируют за один и тот же графический процессор — и на одной из общих карт задержка p99 у чувствительного к задержке агента незаметно увеличилась на 66%, в то время как все остальные поды по-прежнему сообщали о работоспособности. Вот во сколько обойдется эта борьба, измеренная по показателю p99, а не упрощенная оценка.
Это вторая часть серии статей «Производственный уровень агентного вывода» . Каждая часть устраняет один из видов избыточной работы в конвейере агентного LLM. В первой части устраняется избыточное предварительное заполнение. Во второй части (этой части) рассматривается избыточное ожидание — как несколько микроагентов совместно используют один графический процессор посредством разделения по времени. В третьей части получение RAG-данных остается на графическом процессоре с помощью пользовательского ядра CUDA Top-K. В четвертой части состояние агента сохраняется при переключении между агентами, чтобы у следующего агента никогда не возникала проблема холодного запуска.
Основные выводы
- Совместное использование графического процессора не бесплатно, и ваш планировщик вам об этом не сообщит. Когда два агента используют один графический процессор с разделенным временем выполнения, Kubernetes спокойно сообщает, что оба пода
Running. Ущерб скрывается в задержке. - Медиана лжет, а хвост говорит правду. В моем эксперименте (всего с двумя агентами) у обоих значение p50 оставалось практически неизменным. Но у небольшого, чувствительного к задержке, значение p99 подскочило с 3,68 мс до 6,10 мс (≈1,66×) , а его дрожание (p99/p50) увеличилось с 1,02 до 1,70 .
- Агент, чувствительный к задержкам, начинает работать хуже первым. Небольшая, непостоянная рабочая нагрузка пострадала гораздо сильнее, чем тяжёлая, стабильная, даже несмотря на то, что обе получили графический процессор.
- Пропускная способность практически не изменилась, и в этом вся ловушка. Средний показатель пропускной способности снизился всего на несколько процентов — поэтому панель мониторинга, отслеживающая средние значения, посчитала бы это успехом, в то время как ваш чувствительный к хвостам распределения агент незаметно пропускает один из пятидесяти сроков.
- Он работает на видеокарте за 150 долларов. Все измерения ниже проводились на одной пятилетней GTX 1080 со стандартным плагином NVIDIA Kubernetes Device Plugin и функцией разделения времени CUDA. Никакого H100, никакого MIG, никакой магии. Это было сделано намеренно, не каждый может позволить себе H100 — некоторые до сих пор используют старое оборудование. И честно говоря, запуск агентного ИИ-производства на H100 не требует никакой магии; но на видеокарте за 150 долларов она, безусловно, необходима.
Вкратце: я поместил две совершенно разные рабочие нагрузки агента — небольшой, чувствительный к задержке рабочий процесс FFT и ресурсоемкий рабочий процесс GEMM в стиле трансформера — в отдельные поды Kubernetes, каждый из которых вежливо запросил nvidia.com/gpu: "1" , и позволил плагину устройства NVIDIA использовать функцию разделения времени CUDA для распределения их обоих на одной физической видеокарте GTX 1080. Затем я замерил время каждой итерации с помощью событий CUDA, объединил данные в p50/p95/p99, вычислил коэффициент деградации (общий хвост / одиночный хвост) и сравнил его со счетчиками использования GPU DCGM. Результат: медианные значения и пропускная способность практически не изменились, но задержка и дрожание в хвосте резко возросли — хуже всего для небольшого, критичного к задержке агента. Kubernetes сообщает: «два работоспособных пода». Аппаратное обеспечение сообщает: «один из вас голодает в очереди». Kubernetes сообщает: «два работоспособных пода». Кремниевый чип сообщает о драке на шине памяти, а хвостовая часть P99 указывает, кто поплатился за это.
Репозиторий на GitHub : https://github.com/AnubhabBanerjee/Kube-Timeslice-Profiler
(Небольшое признание, прежде чем мы начнем: я пришел к этому с инженерного опыта в области 5G/6G RAN. Как оказалось, это именно та проблема, с которой сейчас сталкивается AI RAN. На периферийных серверах операторы пытаются разместить критически важную с точки зрения задержки обработку базовой полосы частот вместе с ресурсоемким выводом LLM на одних и тех же графических процессорах. Это превращается в кошмар планирования, как только рабочая нагрузка ИИ начинает лишать критически важные с точки зрения задержки приложения пропускной способности памяти — и именно поэтому я написал этот пост.)
Архитектурная ментальная модель — держите это открытым во время чтения.
Two pods → each asks for nvidia.com/gpu: 1 → the device plugin cheerfully says "sure, here are 4 GPUs" (there is exactly 1) → CUDA time-slices the one real GPU → everybody takes turns → the tail pays the bill.
Всё, что ниже приведено, — это лишь комментарии к одной части этой строки.
1. Признание: «Запуск» — самая дорогая иллюзия в Kubernetes.
Как и в предыдущей статье этой серии, давайте начнём с драматического разговора, прежде чем постепенно перейдём к более скучным техническим вопросам.
Вы: «Kubernetes, пожалуйста, запустите моих двух агентов».
Kubernetes: «Готово. Оба пода
Running. ✅»Вы: «На одной и той же видеокарте?»
Kubernetes: «Да. Каждый запросил
nvidia.com/gpu: 1, поэтому я выделил каждому по одному графическому процессору».Вы: «Но у меня всего одна видеокарта».
Kubernetes: «Верно. И я выделил каждому из них графический процессор». 🫡
Ты: «Подожди, что?! Как?? Они не могут оба иметь…»
Kubernetes: «Тише. Не беспокойтесь. Посмотрите, какие они зелёные».
Ваша панель мониторинга Grafana: «Всё отлично, чувак. 🟢»
Тем временем…
Ваша физическая видеокарта: (кричит, переключая контекст)
Ваша задержка p99: (тихо удваивается в углу)
Ну, может, всё и не так уж драматично, но вы же поняли мою мысль, верно? Планировщик считает, что «здоровый» — это когда под жив и запущен процесс. Он не имеет никакого мнения о том, не перегружает ли ваш агент, критически важный для задержки, графический процессор сорок раз в секунду. Фаза пода показывает Running ». Агент ничего не говорит, потому что, ну, на самом деле, никто его об этом не спрашивал.
Это напрямую вытекает из того, на чём закончилась первая часть. В посте о SwarmKV я использовал двух агентов для чтения одного документа и хвастался тем, что один раз предварительно заполнил кэш ключ-значение и развернул его. Затем, в оговорках, я признался в неловкой ситуации: фактическая работа каждого ветвления на графическом процессоре по-прежнему выполнялась за одним глобальным мьютексом. Оркестрация развернулась; вычисления выстроились в один ряд. Два агента, два хода. Пятьдесят агентов, пятьдесят ходов. Я вручную создал блокировку и на этом закончил.
Для демонстрации это нормально. Но для производственной среды это катастрофа, поскольку «рой агентов» означает дюжину небольших специализированных моделей — маршрутизатор, сумматор, средство проверки безопасности, средство поиска, множество инструментов вызова — все они активны одновременно и все нуждаются в одном и том же ускорителе. Вы не можете купить каждому из них H100 (если только вас не зовут Дженсен Хуанг). Вы размещаете их на одном общем графическом процессоре и надеетесь, что планировщик с этим разберется.
Поэтому я хотел ответить на один прямой вопрос: когда два агента используют одну видеокарту, сколько на самом деле платит каждый из них — и сможет ли что-нибудь в моем кластере мне это показать?
Внимание, спойлер: это занимает реальные миллисекунды, почти все зависит от небольшого быстрого агента, и нет, ничто в вашем кластере вам об этом не сообщит. Поэтому я создал инструмент, который это делает.
2. Два агента с противоположными характерами.
В репозитории, лежащем в основе этой публикации, запущены два контейнеризированных рабочих процесса PyTorch, которые заменяют два типа агентов, встречающихся практически в каждом рое агентов:
- Небольшой, непостоянный, чувствительный к задержкам агент (
fft_worker.py). Он выполняет непрерывный цикл больших двумерных комплексных БПФ. Представьте его как класс маршрутизатора/ограничителя/вызывающего инструмента — агентов, которые должны ответить немедленно, иначе весь мир начнет рушиться. - Большой, стабильный, ресурсоемкий агент (
matmul_worker.py). Он выполняет непрерывный поток умножений больших квадратных матриц — GEMM, лежащий в основе прямого прохода трансформера. Это тот самый «тяжеловес», который фактически выполняет мыслительные процессы модели.
Вся их работа довольно проста. Процессор БПФ предварительно выделяет комплексный тензор размером 4096×4096 и обрабатывает его:
# ----- Pre-allocate tensors ----- # Single allocation keeps cuFFT plan creation and allocator traffic out of the per-iteration ``elapsed_time`` window on GPU. # ``complex64`` matches typical PHY IQ data width; real-only FFT would under-report memory traffic relevant to DRAM contention with GEMM tenants. data = torch.randn(MATRIX_SIZE, MATRIX_SIZE, device=device, dtype=torch.complex64) # First launches pay JIT/plan costs; five iterations is a small fixed count—formal steady-state trimming still happens in ``generate_results`` §1.4. # Throwaway ``fft2`` calls prime instruction and constant caches so timed iterations see repeatable SM occupancy, not driver one-shot spikes. for _ in range(5): # Assignment to ``_`` discards output tensor handle immediately; we only need kernel execution side effects on device resident ``data``. torch.fft.fft2(data) # Final sync guarantees no warmup kernel overlaps the first timed iteration's event pair—critical for CUDA event timing validity §3. sync()
Рабочий процесс GEMM предварительно выделяет две матрицы формата FP32 и бесконечно их умножает:
# Matmul needs two operands resident on device; allocating once keeps allocator and paging out of the timed cuBLAS path each iteration. # FP32 is the default training/inference dtype on Pascal-class GPUs without Tensor Cores; this matches the “GEMM on 1080” narrative in README. A = torch.randn(MATRIX_SIZE, MATRIX_SIZE, device=device) B = torch.randn(MATRIX_SIZE, MATRIX_SIZE, device=device) # cuBLAS autotuning can pick different algorithms across first launches; warmup iterations absorb that non-determinism before ``KTS_APP`` lines. # Five repeats mirror FFT worker so cross-tenant comparisons in papers do not confound different warmup depths with silicon interference effects. for _ in range(5): # Result discarded; peak memory stays flat because output tensor is freed each iteration before timed loop allocates nothing new per iter. torch.matmul(A, B) # Sync closes the warmup window so first ``_ev_start.record`` does not overlap trailing warmup kernels on the same default CUDA stream semantics. sync()
Суть заключалась вовсе не в создании хитрой модели — а в том, чтобы построить двух пользователей GPU с противоположными манерами и наблюдать, как они делят одну комнату. Один заканчивает примерно за 3,6 мс и хочет сразу же продолжить; другому требуется около 20 мс, и он просто хочет продолжать. Теперь поместите их на один и тот же GPU и задайте единственный интересный вопрос: кто моргнет первым?
Оба рабочих процесса настраиваются с помощью переменных окружения, поэтому спецификация пода может перенастроить их без пересборки образа:
# ----- Configuration (overridable via env vars so pod specs can tune per experiment) ----- # ``ITERATIONS`` default matches FFT worker so DF numerators/denominators use comparable sample counts without env overrides in YAML. # Raising iterations lengthens shared-GPU ``kubectl wait``; lowering spikes variance in p99 tails used for contention storytelling in ``results.md``. ITERATIONS = int(os.environ.get("ITERATIONS", 800)) # ``MATRIX_SIZE`` dominates FLOPs per iteration; env override lets you downshift VRAM when MatMul shares 8 GB with FFT co-tenant allocations. # Time-slicing does not partition memory—both pods' peak allocations must fit one physical card or the slower OOMKill path invalidates the experiment. MATRIX_SIZE = int(os.environ.get("MATRIX_SIZE", 4096)) # ``SLEEP_MS`` defaults slightly above FFT's 100 ms so two tenants rarely wake in lockstep, spreading scheduler quanta for more realistic interference. # Same caveat as FFT: sleep is between measured iterations and is excluded from ``latency_ms_device``—only GPU matmul time is in the sample list. SLEEP_MS = int(os.environ.get("SLEEP_MS", 150))
Думаю, к этому моменту вы уже поняли, что здесь нет ничего специфичного для конкретной области. Цифры получены из задачи обработки сигналов, расположенной рядом с умножением матриц, но если заменить их на ваши два агента — один лёгкий и ориентированный на дедлайн, другой тяжёлый и стабильный — картина остаётся прежней. Это пост о столкновении типов рабочих нагрузок на одном ускорителе , а не о каком-либо конкретном приложении.
Как правильно выбрать время, не питая иллюзий.
Существует классический способ тестирования производительности графического процессора, позволяющий получить красивый, но совершенно неверный результат: измерить время, необходимое Python для запуска ядра. CUDA асинхронна, поэтому torch.matmul(A, B) возвращает результат практически мгновенно, пока графический процессор ещё работает. Измерьте это время, и вы убедитесь, что ваш matmul занимает всего 50 микросекунд, а затем начнёте ломать голову, почему производительность так низкая.
Рабочие процессы этого не делают. Они оборачивают каждую операцию в события CUDA и принудительно вызывают torch.cuda.synchronize() , чтобы часы остановились после фактического завершения работы ядер на SM:
# Start epoch immediately before ``record`` minimizes gap between “intent to launch” and queue submission for join alignment studies. epoch_ns_start = time.time_ns() _ev_start.record() _ = torch.fft.fft2(data) _ev_end.record() torch.cuda.synchronize() epoch_ns_end = time.time_ns() latency_ms_device = float(_ev_start.elapsed_time(_ev_end))
elapsed_time считывает собственную временную шкалу графического процессора — с разрешением менее микросекунды, без дрожания на стороне хоста. Функция synchronize() — это разница между измерением «сколько времени работал графический процессор» и «сколько времени потребовалось Python для выполнения запроса». Затем каждая итерация выдает одну структурированную строку и сбрасывает ее, поэтому потоковая передача логов Kubernetes видит ее немедленно:
print( f"KTS_APP,v1,FFT,{i},{epoch_ns_start},{epoch_ns_end},{latency_ms_device:.6f},{phase_optional}" ) # ``flush`` forces line-buffered container stdout through CRI before the next sleep—without it, tail -f can batch lines and scramble join order. sys.stdout.flush()
В систему поступает исходное время выполнения на кремниевом кристалле; на выходе получается структурированный лог. Последующий парсер агрегирует эти данные в точные процентили, создавая строгий контракт измерения, который исключает весь шум на стороне хоста.
3. Как два модуля оказываются на одном графическом процессоре (объяснение)
Для новичков в Kubernetes этот раздел покажется настоящим волшебством. Остальные могут смело пропустить его и перейти к следующему.
По умолчанию Kubernetes рассматривает nvidia.com/gpu как единое целое, неделимое: один графический процессор, один пользователь, никакого совместного использования. Функция разделения по времени в плагине устройств NVIDIA меняет порядок учета. Вы передаете ему ConfigMap, который, по сути, говорит: «предположим, что каждый физический графический процессор — это несколько графических процессоров»:
apiVersion: v1 kind: ConfigMap metadata: name: time-slicing-config namespace: nvidia-device-plugin data: any: |- version: v1 flags: migStrategy: "none" failOnInitError: true sharing: timeSlicing: failRequestsGreaterThanOne: true renameByDefault: false resources: - name: nvidia.com/gpu replicas: 4
replicas: 4 в Kubernetes означает «обмануть планировщика четыре раза». После этого одна физическая видеокарта GTX 1080 объявляет API о наличии четырех выделяемых слотов nvidia.com/gpu . Четыре пода могут запросить по "1" , и все они будут успешно запланированы.
Вот в чём загвоздка, выделенная жирным шрифтом, потому что от неё зависит весь пост: здесь физически не разделяется аппаратное обеспечение. Это не MIG. Нет ни барьера памяти, ни барьера вычислений. Четыре «GPU» — это один и тот же кремниевый чип, и модули по очереди используют его с помощью временного разделения CUDA — контекст GPU переключается между ними, как один бариста, обслуживающий четыре линии, быстро переключаясь между регистрами. Больше планируемых слотов, абсолютно нулевая изоляция.

Эксперимент состоит из трех заданий Kubernetes: каждое задание выполняется отдельно (базовый вариант), а затем оба одновременно. Вариант «оба одновременно» — это вся суть: два задания, каждое из которых по умолчанию запрашивает один графический процессор, но намеренно использует одну и ту же карту.
containers: - name: worker image: localhost/kts-worker:v1 imagePullPolicy: Never resources: limits: nvidia.com/gpu: "1" requests: nvidia.com/gpu: "1"
Ни один из модулей не знает о существовании другого. Ни один из них не просил о совместном использовании. Планировщик поместил их в одну комнату, потому что, насколько ему известно, комнат было четыре. Базовые показатели показывают, насколько быстро работает каждый агент, когда он владеет графическим процессором; совместный запуск показывает, сколько он платит за компанию. Разница между ними — это вся история.
4. Буровая установка, одним предложением.
Всё нижеописанное работает на семилетней видеокарте NVIDIA GTX 1080 (8 ГБ, Pascal) на одноузловом сервере K3s со стандартным плагином устройства NVIDIA и поддержкой тайм-лиминга CUDA. Никакого H100, никакого MIG, никакой стойки для дата-центров — только та карта, которая до сих пор лежит под столом у половины читателей этого текста.
Я использую этот антиквариат намеренно. Плохое планирование не исчезает волшебным образом на H100; он просто выполняет свои узкие места на более высокой тактовой частоте. Если ваши агенты борются за шину памяти на карте за 150 долларов, вложение 30 000 долларов в решение проблемы не предотвратит затор — это только сделает сбой более дорогостоящим. Использование H100 для устранения ошибки оркестрации не решает проблему конкуренции; это просто позволяет выполнять задачи с плохой архитектурой за меньшее количество миллисекунд. Физике вытеснения из кэша всё равно, в каком году был выпущен ваш кремниевый чип.
(Версии драйвера, containerd и инструментария закреплены в репозитории для тех, кто пытается воспроизвести эту проблему; они намеренно скучные, и они не нужны для понимания сюжета.)
5. Чеки (т.е., цифры)
Теперь вся история в одном изображении:

Четыре панели, одна изюминка. Медианы (левая пара столбцов на каждом графике задержки) практически не изменились. Пропускная способность (нижний ряд) снизилась всего на 7,3% и 1,4% — такие цифры, о которых вы бы сообщили вышестоящему руководству и получили бы одобрительный смайлик. А затем есть верхний правый угол верхнего левого графика: показатель p99 у небольшого агента подскочил на 66%. Та же панель мониторинга, те же Running модули, тот же скучный график пропускной способности — и один из ваших двух агентов теперь иногда, непредсказуемо, работает на 66% медленнее, чем вчера. Добро пожаловать в мир совместного использования графических процессоров.
Реальные цифры, чтобы никому не приходилось щуриться, глядя на барные стойки:
| Метрическая система | Соло | Общий | Изменять |
|---|---|---|---|
| FFT (чувствительный к задержке) p50 | 3,598 мс | 3,593 мс | незначительный |
| FFT p95 | 3,645 мс | 5,868 мс | 1,61× |
| FFT p99 | 3,679 мс | 6,101 мс | 1,66× |
| Дрожание БПФ (p99/p50) | 1.02 | 1.70 | хвост вылетел |
| GEMM (тяжелый) стр. 50 | 20,677 мс | 20,669 мс | незначительный |
| GEMM стр. 95 | 20,896 мс | 24,505 мс | 1,17× |
| GEMM стр. 99 | 20,985 мс | 24,690 мс | 1,18× |
| Джиттер GEMM (p99/p50) | 1.01 | 1.20 | небольшой |
| Пропускная способность БПФ (ит./с) | 278.1 | 257.9 | −7,3% |
| Пропускная способность GEMM (итер./с) | 49.1 | 48.3 | −1,4% |
Прочитайте эти строки FFT дважды. Медиана не изменилась. Если бы вы смотрели на панель мониторинга p50, вы бы поклялись, что ничего не произошло, вышли бы из системы и пошли на обед. Но теперь каждый сотый вызов FFT занимает на 66% больше времени, а разрыв между типичной и неудачной итерацией увеличился почти вдвое. В среднем вы не замедляли работу оператора — вы иногда, непредсказуемо, задерживались. Что еще хуже, потому что теперь это ненадежный оператор, и никто не может воспроизвести проблему в пятницу днем.
В этом и заключается ключевая асимметрия, и это не совпадение: маленький, чувствительный к задержке агент первым и сильнее всего деградирует. Большой GEMM — это бульдозер: он хватает свой квант и прокладывает себе путь. Маленький FFT постоянно получает подзатыльник, его отталкивают от SM и заставляют ждать следующей очереди. Когда две рабочие нагрузки используют одну линию, страдает та, которая должна работать быстро. Это имеет огромные последствия в телекоммуникационной сфере: если это будет продолжаться, звонки начнут обрываться, а в худшем случае даже номера экстренных служб могут перестать работать. Просто задумайтесь над этим!
Чтобы сделать эти данные сопоставимыми для любой пары агентов, инструмент вычисляет коэффициент деградации (DF) = shared_p99 / baseline_p99 . DF = 1,0 означает, что совместное использование было бесплатным. Чем выше значение, тем хуже. В этом случае оно составляет 1,66 для FFT и 1,18 для GEMM. Это значение 1,66 — это весь пост, сжатый в число, которое можно показать на слайде своему руководителю.
И вот что должно быть противозаконно: пропускная способность практически не изменилась. Если ваш SLO (целевой уровень обслуживания) сформулирован в терминах средней пропускной способности, вы бы посмотрели на «FFT снизился на 7%, GEMM снизился на 1%» и объявили бы о победе. Тем временем ваш чувствительный к хвосту агент молча пропускает один из пятидесяти сроков. Средние значения — это то, где прячутся разногласия. Среднее арифметическое — это добрая душа, которая сглаживает ваши худшие моменты. P99 — это друг, который помнит всё.
Проведем одну проверку на адекватность, а затем перейдем к следующему шагу. Профайлер также каждые 100 мс отслеживает счетчики использования графического процессора DCGM и объединяет их с каждой итерацией. В общем окне активность SM и DRAM рабочего процесса FFT резко возрастает (теперь циклы его выполнения перекрываются с циклом GEMM, работающим с той же системой памяти); в одиночном окне этого не происходит. Таким образом, конкуренция проявляется на двух совершенно независимых уровнях — задержка приложения и аппаратные счетчики — что и позволяет понять, что это реально, а не артефакт секундомера.
6. Речь идёт о роях агентов, а не о какой-либо одной рабочей нагрузке.
Раздел 5 легко можно было бы охарактеризовать как «борьбу между БПФ и умножением матриц на графическом процессоре, что совершенно никого не удивляет, кто когда-либо писал ядро CUDA», но это совершенно не по теме. Два рабочих процесса — это всего лишь удобные, измеримые заменители шаблона, который проявляется в тот момент, когда вы размещаете реальный рой агентов на общем оборудовании:
- Легкие, ориентированные на результат агенты — маршрутизаторы, ограничители, классификаторы, обработчики запросов, небольшие быстрые модели. Недорогие по отдельности, постоянно работающие, и весь конвейер ожидает их завершения. (Работник БПФ — один из конкретных примеров такого типа агентов.)
- Тяжелые, стабильные агенты — большие трансформерные прямые проходы, которые модель, основанная на GEMM, называет доминирующими вычислительными процессами. (Рабочий процесс GEMM — один из конкретных примеров этого.)
Если поместить любых двух агентов с такими формами на один графический процессор с временным разделением, вы получите именно то, что я измерил: медианные значения почти не меняются, но маленький, критически важный по задержке агент «съедает хвост». Неважно, что делают агенты; важно, как они ведут себя на SM — одному нужно быстро и часто заканчивать, а другой просто хочет работать на износ. Временное разделение распределяет ходы. Оно не устанавливает крайние сроки. Поэтому агент, который выживает или погибает к своему крайнему сроку, страдает, когда его ход постоянно прерывается.
Это системная тема, пронизывающая всю эту серию. В первой части речь шла о том, чтобы не повторять работу между агентами (совместное использование кэша ключ-значение). В этой части речь идет о том, чтобы не обманывать себя относительно того, во сколько обходится совместное использование графического процессора этим агентам. Разделение по времени дает вам дополнительные ресурсы — больше слотов планирования на одной карте — и не обеспечивает никакой изоляции . Если вы будете отслеживать только средние значения, ваш самый чувствительный к срокам агент сломается первым, незаметно, в p99, в то время как каждый под будет продолжать мигать Running .
7. «Итак… как же мне этим управлять?»
Конвейер обработки данных намеренно скучный, потому что в системной инженерии «захватывающий» обычно означает, что производство находится в самом разгаре. Это линейный граф сборки → кластера → логов → метрик , управляемый из корневого каталога репозитория:
-
run.pyсоздает образ рабочего процесса с помощью Podman, импортирует его в containerd K3s, создает пространство имен, при необходимости запускает поток сбора данных DCGM, применяет задания, ожидает и собирает логи вlogs/run-./ - Рабочие процессы генерируют строки
KTS_APPкоторые вы видели выше, для каждой итерации. -
generate_results.pyанализирует логи, удаляет фрагменты данных, необходимые для прогрева, вычисляет p50/p95/p99, прокси-объект пропускной способности, коэффициент деградации и объединение DCGM, а затем записывает данные в файлdata/summary.{csv,json}, графики и файлdocs/results.md.
На узле, где уже установлены K3s, драйвер NVIDIA, Container Toolkit, плагин устройства и nvidia RuntimeClass, всё это выполняется всего тремя командами:
# 1. Install the time-slicing ConfigMap and reload the device plugin kubectl apply -f time-slicing-config.yaml # 2. Build the worker image and run the full benchmark (build, import, Jobs, logs) python3 run.py # 3. Turn the logs into summaries, plots, and a results page python3 generate_results.py
Ссылка на репозиторий? Вы найдете её в самом верху статьи. И поздравляю вас с тем, что вы дочитали до этого места – я и представить не мог, что кто-то когда-либо это сделает!
8. Честные оговорки (потому что комментарии не заставят себя долго ждать)
Это небольшое, целенаправленное исследование, а не модель производительности центров обработки данных. Вот что это точно не то, прежде чем кто-нибудь опубликует это за меня:
- Речь идёт о двух агентах, а не о пятидесяти. Конфигурация предоставляет четыре логических слота; в выделенном примере один рабочий процесс FFT объединен с одним рабочим процессом GEMM. Это наименьший интересный случай конкуренции, выбранный для наглядности. Заполнение всех четырёх слотов (полной матрицы конкуренции) входит в планы, но не отражено в этих цифрах. Я не привожу результаты для пятидесяти агентов, потому что я не проводил измерения для пятидесяти агентов.
- Пропускная способность — это показатель средней скорости обработки запросов.
1000 / mean latency— это скорость итераций, а не пропускная способность обработки запросов в реальном процессе поступления. Его ценность заключается в том, что «средние значения скрывают хвост», и ничего больше. - Используемые рабочие нагрузки являются синтетическими. Циклическое БПФ и циклическое умножение матриц являются подходящими заменителями легковесного, чувствительного к задержке агента и тяжеловесного агента вывода, но они не представляют собой полноценную модель для работы с реальным трафиком. Форма помех обобщается; абсолютные значения в миллисекундах — нет.
- Активность DCGM — это малозначительный показатель. Рабочие процессы регулируются с помощью задержек, поэтому графический процессор часто простаивает, и значения SM/DRAM кажутся незначительными. Рассматривайте их как относительные сигналы внутри исследования — они подтверждают данные о задержках, а не утверждают о полной насыщенности.
- Разделение по времени — не единственный режим совместного использования ресурсов. Как указано в §7, в этом исследовании намеренно измеряется путь по умолчанию — тот, который большинство пользователей получают в момент включения совместного использования ресурсов графического процессора. Сравнение с MPS и MIG будет рассмотрено в отдельной статье.
- Один класс GPU, один запуск выделен. Данные получены с одной видеокарты Pascal GTX 1080. Более новые GPU быстрее переключают контекст, и абсолютные значения в хвостах распределения уменьшаются; направление — сначала деградирует небольшой чувствительный к задержкам агент — является наиболее устойчивым результатом.
Всё это не меняет сути вывода. Это лишь заставляет меня честно оценивать масштаб проблемы — а в тот момент, когда в обзоре результатов скрывают свои оговорки, его цифры перестают иметь какую-либо ценность.
9. Заключение (и подготовка к части 3)
Функция разделения времени в Kubernetes — это чудесная иллюзия. Она сообщает вашему планировщику, что один графический процессор равен четырём, позволяет четырём подам сообщать Running , а затем незаметно блокирует их в отдельном помещении, где они будут бороться за шину памяти. Для задач, требующих высокой пропускной способности и не ограниченных сроками, эта иллюзия безвредна и действительно полезна. Для чувствительных к задержкам участников роя агентов эта иллюзия скрывается именно там, где вы не смотрите: в области p99.
Решение заключается не в запрете совместного использования графических процессоров — оборудование необходимо использовать совместно, если только у вас нет неограниченного бюджета. Решение состоит в том, чтобы перестать использовать зеленую галочку в YAML-файле в качестве замены микроархитектурной реальности. Измерьте «хвост» деградации, определите причины снижения производительности и планируйте работу с учетом фактических ограничений кремниевого кристалла. Kube-TimeSlice-Profiler — это шаг в правильном направлении: он превращает смутное ощущение «сегодня графический процессор работает медленно» в измеримый коэффициент деградации с подтверждающими документами.
Если вы пришли сюда как новичок, который просто хотел узнать, почему «оба пода работают» не означает «оба агента довольны»: поздравляю, теперь вы понимаете совместное использование GPU лучше, чем зелёная галочка. Можете смело не доверять своим средним значениям, вы готовы!
Далее: Позорная прогулка PCIe

Мы едва пережили борьбу двух агентов за один графический процессор, не обманывая себя относительно задержки. Но в каждом конвейере RAG есть еще один скрытый недостаток: обмен данными через PCIe.
В настоящий момент каждый раз, когда агенту необходимо получить контекст, он делает паузу, отключается от ускорителя, медленно перемещается по шине PCIe обратно к Python, выполняет векторный поиск на ЦП и снова возвращается обратно.
В третьей части мы избавимся от этой проблемы с поездками на работу. Мы создадим собственное ядро CUDA Top-K , чтобы весь цикл получения данных оставался на аппаратном уровне графического процессора — никаких обменов данными через Python, никаких задержек на стороне хоста. Тот же бюджетный графический процессор. Та же философия «прекратите тратить оборудование впустую».
Увидимся в третьей части.
Примечание: Иллюстрации в этой статье созданы с помощью ИИ (Claude Opus 4.8). Они носят иллюстративный, а не фотографический характер, и любые подписи, видимые на изображениях, являются стилизованными, а не авторитетными — для получения точных названий функций, значений метрик и сведений об архитектуре обратитесь к тексту статьи и самому коду.
Анубхаб Банерджи Посмотреть все в Анубхаб Банерджи
Источник: towardsdatascience.com
Похожие записи
- Пока компании, занимающиеся искусственным интеллектом, стремятся выйти на биржу, кто еще готов присоединиться к этому процессу?
- ИИ по паспорту: как Fable 5 знаменует конец эпохи свободного доступа к frontier-моделям
- Пока партия One Nation собирает пожертвования, чтобы «уволить лжеца», News Corp размещает статью на первой полосе | Weekly Beast
Оцените материал:
Похожие записи
Какая способность является самой развитой формой интеллекта?
26.12.2025
Исследователи сообщают о более значительном ухудшении когнитивных функций через 78 недель при приеме валацикловира, чем при приеме плацебо, у взрослых с ранними симптомами болезни Альцгеймера и
28.12.2025
