8(800) 222 32 56
Панель управления
GPU

NUMA-locality для GPU-инференса: CPU, память и PCIe без удалённого пути

NUMA-locality для GPU-инференса: CPU, память и PCIe без удалённого пути
Подберите идеальное решение для ваших задач:
в России, США и Нидерландах обеспечат максимальную скорость. Воспользуйтесь всеми преимуществами надежного оборудования. Базовая помощь и техническое обслуживание входят в пакет услуг.
GPU недогружен, а p99 первого токена растёт после каждого всплеска CPU-нагрузки. На двухсокетном сервере причина может быть не в модели, а в удалённом пути между worker, памятью и PCIe-root GPU. Разберём, как доказать эту гипотезу через topology, effective state и одинаковый canary, не выдавая одну удачную метрику за ускорение. В финале получится runbook с stop conditions и симметричным rollback.

Готовы перейти на современную серверную инфраструктуру?

В King Servers мы предлагаем серверы как на AMD EPYC, так и на Intel Xeon, с гибкими конфигурациями под любые задачи — от виртуализации и веб-хостинга до S3-хранилищ и кластеров хранения данных.

  • S3-совместимое хранилище для резервных копий
  • Панель управления, API, масштабируемость
  • Поддержку 24/7 и помощь в выборе конфигурации

Создайте аккаунт

Быстрая регистрация для доступа к инфраструктуре

Когда NUMA становится проблемой для LLM-инференса

GPU загружен не полностью, но p99 первого токена ползёт вверх, CPU-потоки сервера мечутся между сокетами, а после рестарта картина иногда меняется сама собой. Такой набор симптомов часто списывают на batching, Python, драйвер или соседний workload. На двухсокетной машине стоит проверить ещё одну гипотезу: процесс обслуживает GPU через CPU и память, находящиеся по другую сторону межсокетной шины.

NUMA — это не отдельный «ускоритель». Память локального узла ближе к своим CPU-ядрам, а обращение к удалённой памяти проходит через межсокетный interconnect. Для GPU важна и топология PCIe: ускоритель физически висит за конкретным root complex. Если tokenizer, scheduler, network thread, pinned host buffers и GPU feeder оказались на разных узлах, каждый отдельный переход может выглядеть терпимо, но хвост latency собирает их вместе.

Disqualifier нужно назвать сразу: если сервер односокетный, приложение почти полностью GPU-bound, host-to-device трафик мал, а CPU и RAM не насыщены, ручная NUMA-привязка может ничего не изменить. Она также не лечит недостаток VRAM, Xid, ECC, плохой batching или медленную модель. Для аппаратных инцидентов используйте отдельный runbook по NVIDIA Xid, а не маскируйте ошибку affinity.

Практический критерий здесь простой: NUMA-locality интересна только тогда, когда вы можете связать наблюдаемый путь «CPU → память → PCIe → GPU» с application metric. Типичный пример — два одинаковых LLM-worker на одном сервере: один стабилен, второй даёт длинный хвост при одинаковом prompt mix. До изменения конфигурации сравните их topology, effective affinity и распределение страниц; одной строки из nvidia-smi недостаточно.

Удалённый NUMA-доступ между CPU и GPU под нагрузкой LLM

Снимите topology и baseline до первого изменения

Начинайте с evidence bundle, который можно повторить после rollout и rollback. Зафиксируйте модель GPU, PCI bus ID, версию драйвера, карту CPU/NUMA, память по узлам, текущие cpuset и mems процесса, а также неизменяемый canary-набор запросов. Если после тюнинга нельзя воспроизвести исходное состояние, вы получите красивую корреляцию без доказанной причины.

Официальная документация NVIDIA SMI описывает nvidia-smi topo -m: матрица показывает GPU, NIC, CPU affinity и классы пути PIX/PXB/PHB/NODE/SYS. Значение SYS особенно важно: путь пересекает PCIe и межсокетный interconnect. Но topology описывает физическую близость; она не резервирует ядра и не доказывает, где реально работает процесс.

Сохраните не только среднее время ответа. Для инференса полезны TTFT, inter-token latency, tokens/s, request queue time, CPU utilization по сокетам, memory bandwidth и число remote NUMA accesses, если платформа позволяет их получить. Canary должен сохранять модель, длины prompt/output, concurrency, batching policy, warmup и версию контейнера. Иначе изменение очереди легко выдать за эффект locality.

Проверяли ли вы, что «быстрый» worker не просто получил более короткие prompts? Возьмите один и тот же набор, прогрейте модель одинаково и разделите окна warmup, steady state и cooldown. Материал о том, как выбирать саму конфигурацию ускорителя, уже есть в гайде про GPU-серверы для машинного обучения; здесь мы намеренно сужаем задачу до размещения на готовом узле.

bashcollect-numa-baseline.sh
#!/usr/bin/env bash
set -euo pipefail
date --iso-8601=seconds
uname -a
nvidia-smi --query-gpu=index,pci.bus_id,name,driver_version --format=csv
nvidia-smi topo -m
lscpu --extended=CPU,NODE,SOCKET,CORE,ONLINE
numactl --hardware
PID="$(pgrep -n -f 'vllm|tritonserver|text-generation')"
taskset -pc "$PID"
grep -E 'Cpus_allowed_list|Mems_allowed_list' "/proc/$PID/status"
head -n 40 "/proc/$PID/numa_maps"
Topology и effective state перед изменениями

Стоит ли начинать NUMA-эксперимент

Схема локального и удалённого пути в двухсокетной NUMA-системе с GPU

Как читать карту NUMA без ложных выводов

В nvidia-smi topo -m сначала найдите строку нужного GPU и колонку CPU Affinity. Затем сопоставьте ядра с lscpu -e и PCI bus ID с lspci -D. У двух GPU одинаковой модели affinity может различаться, потому что карты стоят за разными root complex. Название устройства и номер CUDA не заменяют stable identity: после перезагрузки ordinal способен измениться, а PCI address обычно лучше подходит для инвентаризации.

Матрица GPU↔GPU отвечает на другой вопрос. NVLink, PIX или PHB важны для tensor/pipeline parallel, но worker с одним GPU всё равно зависит от CPU и host memory. Например, модель целиком помещается в VRAM, однако tokenizer выдаёт много коротких запросов, scheduler постоянно формирует batches, а pinned buffers обновляются на каждом шаге. В этом случае удалённый CPU path может проявиться именно в p99, а не в среднем tokens/s.

Сверьте вывод с DCGM topology. DCGM сообщает CPU core affinity и NUMA optimal для группы, но документация отдельно предупреждает: topology не выполняет bind процесса. Это важная граница. Зелёный topology report означает, что удачное размещение возможно, а не то, что kubelet, systemd или shell его уже применили.

Для multi-GPU workload не пытайтесь любой ценой втиснуть все worker threads в один узел. Если GPU распределены по сокетам, правильнее разделить процессы по устройствам и дать каждому локальные CPU/память, чем создать один огромный cpuset. Иначе вы «исправите» локальность одного GPU ценой удалённого пути для другого. Сначала нарисуйте ownership: какой launcher создаёт процессы, кто выбирает GPU и кто управляет cgroup.

Baseline на bare metal: numactl, cpuset и проверка изнутри процесса

Для первого canary используйте явную привязку через numactl, а не сразу меняйте kubelet или системный unit. В актуальной man page numactl различает --cpunodebind, который ограничивает выполнение CPU узлами, и --membind, который разрешает выделение памяти только из заданных узлов. Жёсткий membind может завершить allocation ошибкой при нехватке локальной памяти — это полезный fail-closed режим для теста, но опасный универсальный default.

Начните с одного worker, одного GPU и конечного CPU budget. Не используйте «все ядра узла» просто потому, что команда короткая: там могут жить IRQ, storage, network и системные службы. Выделите конкретный диапазон, сохраните резерв для OS, затем запустите тот же canary. В отдельном окне проверьте /proc/PID/status, numactl --show из launcher context и /proc/PID/numa_maps. Конфигурация без effective-state проверки остаётся намерением.

Linux kernel documentation о NUMA memory policy подчёркивает, что cpuset restrictions имеют приоритет над policy процесса. Поэтому numactl --membind=0 внутри контейнера не расширит cpuset.mems, разрешённый родительским cgroup. Типичная ловушка: команда выглядит правильной, но контейнер видит только другой node или оба узла, а страницы уже были выделены до применения policy.

Практический пример: worker загружает weights до того, как launcher применил affinity к дочернему процессу. GPU выбран верно, CPU mask локален, но большая часть page cache и pinned buffers уже появилась удалённо. Полностью перезапустите canary после изменения и не сравнивайте живой процесс с новой маской со свежим baseline — это разные состояния памяти.

bashrun-local-worker.sh
#!/usr/bin/env bash
set -euo pipefail
GPU=0
CPU_LIST=0-15
NUMA_NODE=0
export CUDA_VISIBLE_DEVICES="$GPU"
exec numactl   --physcpubind="$CPU_LIST"   --membind="$NUMA_NODE"   /opt/llm/bin/start-worker --config /etc/llm/canary.yaml
Canary с конечным CPU и memory budget

Какую memory policy выбрать для canary

Жёсткое доказательство локального allocation: при нехватке памяти запуск падает. Подходит для контролируемого теста.
Мягкое предпочтение с fallback. Удобнее в эксплуатации, но remote allocation нужно наблюдать отдельно.
Распределяет страницы между узлами. Это отдельная гипотеза, а не способ доказать locality одного GPU.
Локальная привязка процесса инференса к CPU, RAM и GPU одного NUMA-узла

Kubernetes: когда Topology Manager действительно выравнивает ресурсы

В Kubernetes CPU Manager, Device Manager, Memory Manager и scheduler решают разные части задачи. Без координации pod может получить GPU с одного NUMA node, а CPU — с другого. Topology Manager собирает topology hints и выбирает согласованное размещение. Функция stable начиная с Kubernetes 1.27, но наличие компонента не означает, что строгая policy включена на узле.

Для latency-sensitive GPU-pod практичной исходной точкой служат CPU Manager static, Topology Manager single-numa-node или restricted и scope, соответствующий структуре pod. Официальная документация отмечает особую ценность сочетания pod scope и single-numa-node для workload с IPC и требованиями к latency. Однако admission зависит от hints, которые дают resource managers и device plugin; произвольный extended resource без topology information не станет локальным от одного названия policy.

CPU Manager выдаёт эксклюзивные CPU только Guaranteed pod с целочисленным CPU request. Burstable pod или fractional request останется в shared pool, и ожидаемая изоляция не появится. Проверьте фактический QoS, kubelet config, state file CPU Manager и cpuset контейнера. Если pod admitted, это доказывает совместимость hints на момент allocation; application readiness всё равно нужно проверять запросом к модели.

Не меняйте policy сразу на всём кластере. Выделите один canary node, drain его, сохраните kubelet config и resource inventory, примените изменение, затем верните только тестовый pod. У Kubernetes restart kubelet и удаление CPU Manager state имеют операционные последствия; следуйте процедурам вашей версии и не удаляйте state на работающем узле вслепую. Более широкий контекст платформы описан в материале Kubernetes для AI inference.

yamlkubelet-config.yaml
cpuManagerPolicy: static
topologyManagerPolicy: single-numa-node
topologyManagerScope: pod
reservedSystemCPUs: "0-3"
Topology-aware baseline для выделенного canary node

Проверка GPU-пода перед canary-трафиком

Topology-aware размещение GPU-пода на одном NUMA-узле Kubernetes

Как доказать эффект: canary, отрицательный контроль и application gate

После привязки не ищите одну «правильную» цифру. Сравнивайте paired windows: тот же узел, модель, контейнер, prompt set, concurrency, batch settings и длительность. Сначала baseline без ручной policy, затем local bind, потом безопасный отрицательный контроль — привязка CPU или памяти к заведомо удалённому разрешённому node. Завершите rollback к baseline. Такая последовательность отделяет эффект topology от случайного изменения нагрузки.

Application gate должен отвечать на вопрос пользователя сервиса. Сохранился ли error rate? Не вышли ли TTFT и inter-token latency за SLO? Не упал ли goodput при улучшении p99? Инфраструктурный счётчик remote access — полезное доказательство механизма, но не успех сам по себе. Если remote accesses снизились, а latency не изменилась, узкое место, вероятно, уже мигрировало на GPU kernels, очередь, сеть или storage.

Пример: local bind снижает разброс queue time, но tokens/s остаётся прежним. Это нормальный результат для workload с короткими запросами: locality могла убрать jitter на host-side scheduler, не ускорив матричные операции. Другой пример — средний TTFT улучшается, а p99 ухудшается из-за нехватки локальной памяти и reclaim. Тогда жёсткий membind не готов к production, даже если один короткий benchmark выглядит лучше.

Заранее задайте stop conditions: OOM или allocation failure, рост error rate, нарушение SLO, исчезновение GPU из inventory, Xid, нестабильность canary либо невозможность восстановить исходный cpuset. Не продолжайте «ещё пять минут», когда gate уже красный. Сохраните результаты как evidence bundle: config digest, topology, effective masks и application summary с timestamp.

Схема проверки NUMA-locality через baseline, canary, отрицательный контроль и rollback

Типичные ошибки: affinity есть, а локальности нет

Первая ошибка — привязать только CPU. Процесс исполняется на локальных ядрах, но его страницы остались удалёнными или policy допускает fallback. Вторая — привязать память, но оставить network и storage IRQ на другом сокете; bottleneck просто переезжает. Третья — применить taskset к launcher, который затем создаёт отдельный runtime с другой cgroup policy. Каждый слой нужно читать из effective state конечного PID.

Четвёртая ошибка особенно неприятна: команда использует CUDA ordinal, а после reboot порядок GPU меняется. Привязка остаётся к node 0, но worker получает физически другой GPU. Храните PCI bus ID или UUID в evidence и сопоставляйте его с выбранным устройством при старте. Automation должна завершаться fail-closed, если inventory не совпал с ожидаемым, а не молча запускать модель на «GPU 0».

Пятая — считать automatic NUMA balancing универсальным лекарством. Kernel может мигрировать страницы CPU workload, но pinned memory, device DMA и жизненный цикл LLM-worker создают ограничения. Кроме того, cpuset ограничивает допустимые nodes. Не меняйте глобальный kernel.numa_balancing ради одного canary без отдельного плана: вы затронете другие workloads и потеряете чистоту эксперимента.

Шестая — переоценить synthetic copy test. Он подтверждает путь памяти или PCIe, но не моделирует tokenizer, batching и request scheduler. Держите три независимых gate: topology identity, effective placement и application request. Именно эта структура отличает production runbook от набора тюнинговых флагов.

Что проверять при расхождении метрик

Диагностика типичных ошибок NUMA-размещения на GPU-сервере

Rollout и rollback: переносим canary в эксплуатацию

Production rollout начинайте не с процента трафика, а с ownership. Platform team отвечает за topology inventory и kubelet/cgroup policy; владелец модели — за одинаковый canary и application SLO; on-call — за stop condition и rollback. Если эти роли не названы, первая ночная деградация превратит affinity в загадочную настройку, которую никто не решится снять.

Расширяйте охват ступенчато: один worker, один GPU, один node; затем несколько worker на том же topology class; только после этого — другие модели или типы серверов. Не переносите маски CPU между разными SKU. Даже одинаковые процессоры не гарантируют одинаковое расположение PCIe slots, NIC и NVMe. Для каждого hardware class нужен собственный topology manifest.

Rollback должен быть симметричным. Верните прежний launcher или kubelet config, перезапустите worker так, чтобы память действительно выделилась заново, подтвердите исходные masks и повторите тот же application probe. Если baseline не возвращается, причинность не доказана: возможно, одновременно изменились cache, модель, нагрузка или thermal state. Зафиксируйте это честно вместо вывода «NUMA ускорила сервис».

На выделенном сервере такой эксперимент проще контролировать: у команды есть ownership над BIOS, PCIe placement, CPU reservations и restart window. Это естественный bridge к инфраструктуре KingServers, но не обещание универсального прироста. Выделенное железо даёт воспроизводимый baseline; эффект всё равно должен пройти canary и SLO gate. Соседние вопросы распределения workloads раскрыты в статье про GPU scheduling.

Gate перед расширением rollout

Короткий production-чеклист для NUMA-locality

Сведите процедуру к повторяемой последовательности. Сначала identity: модель GPU, UUID и PCI bus ID, CPU sockets, NUMA nodes, версия драйвера и container digest. Затем topology: nvidia-smi topo -m, lscpu, numactl --hardware, при наличии DCGM. После этого effective state конечного worker: CPU mask, memory mask, cgroup, numa_maps и момент выделения памяти.

Третий слой — ресурсный budget. Зарезервируйте CPU для OS и IRQ, задайте конечный набор worker cores, оцените доступную локальную память и оставьте запас. Жёсткий membind без capacity check превращает тюнинг latency в новый failure mode. В Kubernetes подтвердите Guaranteed QoS, integer CPU request, topology policy и hints device plugin.

Четвёртый слой — application proof. Один и тот же warmup и canary, одинаковый prompt mix, concurrency и batching. Смотрите среднее и хвосты, goodput и ошибки. Добавьте удалённый отрицательный контроль только на изолированном canary; затем выполните симметричный rollback. Не допускайте выводов по одному удачному прогону.

Наконец, запишите owner и stop condition. Кто имеет право менять affinity? Кто смотрит application SLO? Какой сигнал немедленно возвращает baseline? Практика кажется бюрократичной ровно до первого инцидента. Зато после неё NUMA перестаёт быть «магией железа» и становится обычной проверяемой policy.

Проверенный GPU-сервер с локальным путём CPU и памяти для LLM-инференса

Вывод: локальность полезна только как доказанная policy

NUMA-locality может убрать удалённый путь между CPU, памятью и PCIe GPU, снизить jitter и сделать LLM-инференс предсказуемее. Но строка affinity, успешный pod admission или меньше remote accesses ещё не означают, что пользователь получил более стабильный ответ. Надёжный результат строится из трёх независимых доказательств: topology identity, effective placement и application SLO.

Начните с одного canary-worker. Сохраните baseline, примените конечный CPU/memory budget, повторите тот же запросный профиль, добавьте безопасный удалённый контроль и верните исходную конфигурацию. Если эффект переживает эту симметрию, policy можно расширять на topology class. Если нет — вы всё равно выиграли: исключили одну гипотезу без риска для всего кластера.

Следующий практический шаг — снять карту вашего двухсокетного GPU-узла и проверить, совпадает ли реальный cpuset worker с ближайшим PCIe-root. Для нового выделенного сервера заложите topology inventory и rollback ещё до первого production-трафика. Тогда разговор о «правильной NUMA» заканчивается не верой в флаг, а воспроизводимым решением, которое команда умеет объяснить и отменить.

Memory pressure в LLM-инференсе: cgroup v2, PSI и systemd-oomd
AI

Memory pressure в LLM-инференсе: cgroup v2, PSI и systemd-oomd

Практический runbook для защиты LLM-инференса от дефицита host RAM: PSI, cgroup v2, systemd-oomd, лимиты vLLM, алерты и аварийное восстановление.

NVIDIA CDI для rootless GPU-контейнеров: безопасный запуск LLM inference
AI

NVIDIA CDI для rootless GPU-контейнеров: безопасный запуск LLM inference

Практический runbook: NVIDIA Container Toolkit CDI, rootless Podman и Docker, выбор GPU/MIG, запуск vLLM, hardening, обновления и rollback.

Power cap для GPU в LLM inference: production-runbook без гадания по ваттам
AI

Power cap для GPU в LLM inference: production-runbook без гадания по ваттам

Практический runbook для безопасного ограничения мощности GPU: baseline, canary, DCGM и vLLM-метрики, energy-per-token, rollout и rollback.