11 мая 2026 года NVIDIA выпустила cuda-core v1.0.0 — первый стабильный релиз библиотеки, которая даёт Python-разработчикам прямой доступ к CUDA Runtime без тяжелых C++ обвязок.

Мы взяли 3 видеокарты (4090, 3090, A100 80Gb) и протестировали работу библиотеки на каждой.

cuda-core — это Pythonic-обёртка над CUDA Runtime. Она закрывает ту нишу, которую раньше занимали pycuda или ручные вызовы через ctypes (компиляция ядер прямо из Python, управление памятью на GPU, запуск ядер без C++ расширений). Версия 1.0.0 фиксирует публичный API — теперь можно применять библиотеку в продакшн-зависимостях.


В экосистеме CUDA Python два слоя:

Слой

Пакет

Что делает

Низкий

cuda-bindings

1:1 маппинг C-API (cudaMemcpy и пр.)

Высокий

cuda-core

Pythonic API: Device, Stream, Program, Buffer

Для кого полезно:

  • Исследователи, прототипирующие нестандартные операции для обучения моделей

  • Инференс-инженеры, оптимизирующие горячие пути под конкретное железо

  • Авторы ML-библиотек, которым нужен GPU-доступ без C++ build system

Что нового в v1.0.0

Кэш компиляции ядер

Компиляция CUDA-ядра через NVRTC занимает 10–50 мс. При каждом старте приложения это складывается в секунды. v1.0.0 добавляет два класса:

  • InMemoryProgramCache — in-process LRU-кэш на время жизни процесса

  • FileStreamProgramCache — персистентный кэш на диске между запусками

from cuda.core import Device, Program, ProgramOptions, ObjectCode
from cuda.core.utils import InMemoryProgramCache

d = Device(0)
d.set_current()

cuda_src = r"""
extern "C" __global__ void saxpy(
    float alpha, const float* __restrict__ x,
    const float* __restrict__ y, float* out, int n
) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < n) out[idx] = alpha * x[idx] + y[idx];
}
"""

arch = f'sm_{d.compute_capability.major}{d.compute_capability.minor}'
cache = InMemoryProgramCache()
cache_key = f'saxpy_{arch}'

# Первый запуск — компилируем и кладём в кэш (~15 мс)
if cache.get(cache_key) is None:
    prog = Program(cuda_src, 'c++', options=ProgramOptions(arch=arch))
    cache[cache_key] = prog.compile('cubin')

# Все последующие — мгновенно (~0.05 мс, 300× быстрее)
kernel = ObjectCode.from_cubin(cache.get(cache_key)).get_kernel('saxpy')

Green Contexts: делим GPU между задачами

Green Contexts — механизм разделения SM-ресурсов GPU между задачами без физической изоляции. RTX 4090 имеет 128 SM; можно нарезать их на партиции и запускать задачи параллельно с гарантированными ресурсами для каждой.

Типичный сценарий: два независимых инференс-запроса должны работать на одном GPU без взаимного вытеснения за SM. Без Green Contexts задачи конкурируют за ресурс неконтролируемо. С ними — каждая получает свой выделенный кусок.

import numpy as np
import ctypes
import threading
from cuda.core import (
    Device, SMResourceOptions, ContextOptions,
    Program, ProgramOptions, LaunchConfig, launch, ObjectCode,
    DeviceMemoryResource, DeviceMemoryResourceOptions
)
from cuda.core.utils import InMemoryProgramCache
from cuda.bindings import runtime as cudart

N = 5_000_000

d = Device(0)
d.set_current()
total_sm = d.resources.sm.sm_count
print(f"GPU: {d.name}, Total SMs: {total_sm}")  # Total SMs: 128

# Делим 128 SM на два раздела по 64
sm_opts = SMResourceOptions(count=[total_sm // 2, total_sm // 2])
groups, _ = d.resources.sm.split(sm_opts)

ctx_a = d.create_context(ContextOptions([groups[0]]))
ctx_b = d.create_context(ContextOptions([groups[1]]))
print(f"Context A: {ctx_a.resources.sm.sm_count} SMs, is_green={ctx_a.is_green}")
print(f"Context B: {ctx_b.resources.sm.sm_count} SMs, is_green={ctx_b.is_green}")
# Context A: 64 SMs, is_green=True
# Context B: 64 SMs, is_green=True

cuda_src = r"""
extern "C" __global__ void saxpy(
    float alpha, const float* __restrict__ x,
    const float* __restrict__ y, float* out, int n
) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < n) out[idx] = alpha * x[idx] + y[idx];
}
"""
arch = f"sm_{d.compute_capability.major}{d.compute_capability.minor}"
cache = InMemoryProgramCache()
cache_key = f"saxpy_{arch}"
if cache.get(cache_key) is None:
    prog = Program(cuda_src, "c++", options=ProgramOptions(arch=arch))
    cache[cache_key] = prog.compile("cubin")

def run_task(ctx, task_name, alpha, fill_x, fill_y):
    d.set_current()  # нужен primary context в каждом потоке
    # stream, созданный через green context, привязан к его SM-партиции
    stream = ctx.create_stream()
    mr = DeviceMemoryResource(0, DeviceMemoryResourceOptions())
    kernel = ObjectCode.from_cubin(cache.get(cache_key)).get_kernel("saxpy")

    size = N * 4  # float32
    buf_x = mr.allocate(size, stream=stream)
    buf_y = mr.allocate(size, stream=stream)
    buf_o = mr.allocate(size, stream=stream)

    x_h = np.full(N, fill_x, dtype=np.float32)
    y_h = np.full(N, fill_y, dtype=np.float32)
    cudart.cudaMemcpy(buf_x.handle, x_h.ctypes.data, size, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)
    cudart.cudaMemcpy(buf_y.handle, y_h.ctypes.data, size, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)

    cfg = LaunchConfig(grid=(N + 255) // 256, block=256)
    launch(stream, cfg, kernel,
           np.float32(alpha), buf_x.handle, buf_y.handle, buf_o.handle, ctypes.c_int(N))
    stream.sync()

    out_h = np.empty(N, dtype=np.float32)
    cudart.cudaMemcpy(out_h.ctypes.data, buf_o.handle, size, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)
    print(f"  {task_name}: result={out_h[0]:.1f}")
    buf_x.close(); buf_y.close(); buf_o.close()
    stream.close()

# Запускаем два ядра параллельно — каждое на своих 64 SM
t_a = threading.Thread(target=run_task, args=(ctx_a, "Task-A (64 SM)", 3.0, 2.0, 1.0))
t_b = threading.Thread(target=run_task, args=(ctx_b, "Task-B (64 SM)", 5.0, 4.0, 2.0))
t_a.start(); t_b.start()
t_a.join(); t_b.join()
# Task-A (64 SM): result=7.0
# Task-B (64 SM): result=22.0

ctx_a.close(); ctx_b.close()

Где доступно: Ada Lovelace (RTX 40xx, sm_89) и новее, CUDA 12.4+.

Расширенный NVML

Модуль system получил GPU-мониторинг в реальном времени:

from cuda.core import system, Device

print(f"Devices: {system.get_num_devices()}")

d = Device(0)
d.set_current()
sd = d.to_system_device()

util = sd.utilization
mem  = sd.memory_info
print(f"{sd.name}: GPU {util.gpu}%, MEM {util.memory}%")
print(f"Memory: {mem.used // 10242} MB / {mem.total // 10242} MB")

Также добавлены: MIG-режим, NVLink (версия, состояние), список запущенных compute-процессов с потреблением памяти.

Ускорение StridedMemoryView для PyTorch

StridedMemoryView получил fast path через AOT Inductor — 7–20× более быстрое построение view для PyTorch-тензоров без копирования данных.

Полный рабочий пример: SAXPY на GPU

Полный цикл: инициализация → компиляция → память → запуск → проверка. Протестировано на RTX 4090 / CUDA 13.0.

import numpy as np
import ctypes
from cuda.core import (
    Device, DeviceMemoryResource, DeviceMemoryResourceOptions,
    Program, ProgramOptions, LaunchConfig, launch, ObjectCode
)
from cuda.core.utils import InMemoryProgramCache
from cuda.bindings import runtime as cudart

N = 10_000_000
FLOAT_BYTES = 4

# 1. Устройство и стрим
d = Device(0)
d.set_current()
stream = d.create_stream()
print(f"GPU: {d.name}, SM: {d.properties.multiprocessor_count}")

# 2. Memory pool
mr = DeviceMemoryResource(0, DeviceMemoryResourceOptions())

# 3. Kernel: SAXPY = alpha * X + Y
cuda_src = r"""
extern "C" __global__ void saxpy(
    float alpha, const float* __restrict__ x,
    const float* __restrict__ y, float* out, int n
) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < n) out[idx] = alpha * x[idx] + y[idx];
}
"""

# 4. Компиляция с кэшем
arch = f'sm_{d.compute_capability.major}{d.compute_capability.minor}'
cache = InMemoryProgramCache()
cache_key = f'saxpy_{arch}'
if cache.get(cache_key) is None:
    prog = Program(cuda_src, 'c++', options=ProgramOptions(arch=arch))
    cache[cache_key] = prog.compile('cubin')
kernel = ObjectCode.from_cubin(cache.get(cache_key)).get_kernel('saxpy')

# 5. GPU-память
size_bytes = N * FLOAT_BYTES
buf_x   = mr.allocate(size_bytes, stream=stream)
buf_y   = mr.allocate(size_bytes, stream=stream)
buf_out = mr.allocate(size_bytes, stream=stream)

# 6. H→D
x_host = np.ones(N, dtype=np.float32) * 2.0
y_host = np.ones(N, dtype=np.float32) * 1.0
alpha  = np.float32(3.0)
cudart.cudaMemcpy(buf_x.handle, x_host.ctypes.data, size_bytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)
cudart.cudaMemcpy(buf_y.handle, y_host.ctypes.data, size_bytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)

# 7. Запуск
cfg = LaunchConfig(grid=(N + 255) // 256, block=256)
launch(stream, cfg, kernel, alpha, buf_x.handle, buf_y.handle, buf_out.handle, ctypes.c_int(N))

# 8. D→H + проверка
stream.sync()
out_host = np.empty(N, dtype=np.float32)
cudart.cudaMemcpy(out_host.ctypes.data, buf_out.handle, size_bytes, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)
assert np.allclose(out_host, 3.0 * x_host + y_host)
print(f"SAXPY OK: {out_host[0]:.1f}")  # 7.0

buf_x.close(); buf_y.close(); buf_out.close()
stream.close()

Время ядра на 10M элементов — ~0.13 мс, эффективная полоса пропускания ~920 GB/s (91% от пика RTX 4090).

Полный код примеров и бенчмарков: github.com/IntelionCloud/research-examples/cuda-core/

Benchmark: пропускная способность памяти

Реальные измерения SAXPY на cuda-core v1.0.0 (100M float32 элементов, 5 прогонов, warmup):

GPU

Теор. BW

Замеренный BW

Эффективность

A100 80GB PCIe

1935 GB/s

1584 GB/s

82%

RTX 4090 (24 GB)

1008 GB/s

929 GB/s

92%

RTX 3090

936 GB/s

848 GB/s

91%

Все три GPU использовали 100% своих SM: при 100M элементах и block=256 получается 390 625 блоков — с избытком для загрузки 108 SM (A100), 128 SM (RTX 4090) и 82 SM (RTX 3090). Разные проценты эффективности отражают архитектуру памяти, а не SM-утилизацию: GDDR6X (RTX 4090, 3090) хорошо насыщается простым последовательным стримингом, HBM2e (A100) оптимизирован под массивно-параллельный доступ и матричные операции — SAXPY его не раскрывает (SAXPY = Single-precision A·X Plus Y — операция из стандарта Basic Linear Algebra Subprograms, 1970-е). На transformer attention и крупных matmul картина была бы другой.

cuda-core vs pycuda

Характеристика

pycuda

cuda-core v1.0.0

Статус

Сторонний, неофициальный

Официальный NVIDIA

Стабильный API

Нет

Да, с 1.0.0

CUDA 12/13

Частичная поддержка

Полная

Кэш компиляции

Нет

InMemory + File

Green Contexts / SM partition

Нет

Есть (sm_89+)

PyTorch StridedMemoryView

Нет

7–20× быстрее

Python 3.12/3.13

Проблемы

Поддерживается

Ограничения

  • CUDA 12+ обязателен

  • Green Contexts — Ada Lovelace (sm_89) и новее, CUDA 12.4+

  • Process checkpointing — только Linux

Итог

cuda-core v1.0.0 — официальный и теперь стабильный Python-интерфейс к CUDA. Кэш компиляции убирает latency cold start, Green Contexts дают детерминированное разделение GPU между задачами, NVML — мониторинг из той же библиотеки. Для тех, кто писал кастомные ядра через pycuda или ctypes — время мигрировать.

# CUDA 12 (драйвер 525+)
pip install "cuda-core[cu12]"

# CUDA 13 (драйвер 570+)
pip install "cuda-core[cu13]"

Комментарии (0)