Skip to content

Commit 17c2be2

Browse files
leofangclaude
andcommitted
Add explicit green context model: ctx.create_stream and ctx.resources
Switch from the v1.0 push model (dev.set_current + dev.create_stream) to the v1.1 explicit model (ctx.create_stream + ctx.resources) as the primary way to use green contexts. Context.create_stream(options): - Only supported on green contexts (raises on primary contexts). - Delegates to Stream._init which now detects green contexts via get_context_green_ctx() and dispatches to cuGreenCtxStreamCreate — no duplication of stream creation logic. - New C++ create_stream_handle_for_green_ctx() with function pointer for cuGreenCtxStreamCreate. Context.resources: - Returns a DeviceResources namespace querying this context's resources (cuCtxGetDevResource / cuGreenCtxGetDevResource), not the full device. dev.set_current(green_ctx) still works but is not the recommended path. Tests rewritten to use the explicit model throughout. Push-model set_current kept as regression tests with _use_green_ctx helper. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent aee88c6 commit 17c2be2

9 files changed

Lines changed: 251 additions & 63 deletions

cuda_core/cuda/core/_context.pyx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ from collections.abc import Sequence
88
from dataclasses import dataclass
99

1010
from cuda.bindings cimport cydriver
11+
from cuda.core._device_resources cimport DeviceResources, SMResource, WorkqueueResource
1112
from cuda.core._device_resources import SMResource, WorkqueueResource
1213
from cuda.core._resource_handles cimport (
1314
ContextHandle,
@@ -19,6 +20,7 @@ from cuda.core._resource_handles cimport (
1920
as_intptr,
2021
as_py,
2122
)
23+
from cuda.core._stream import StreamOptions
2224
from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
2325

2426

@@ -75,6 +77,47 @@ cdef class Context:
7577
return False
7678
return get_context_green_ctx(self._h_context).get() != NULL
7779

80+
@property
81+
def resources(self) -> DeviceResources:
82+
"""Query the hardware resources provisioned for this context.
83+
84+
For green contexts, returns the resources this context was created
85+
with (SM partition, workqueue config). For primary contexts, returns
86+
the full device resources.
87+
88+
Raises :class:`RuntimeError` if the context has been closed.
89+
"""
90+
if not self._h_context:
91+
raise RuntimeError("Cannot query resources on a closed context")
92+
return DeviceResources._init_from_ctx(self._h_context, self._device_id)
93+
94+
def create_stream(self, options: StreamOptions | None = None):
95+
"""Create a new stream bound to this green context.
96+
97+
This method is only available on green contexts. For primary
98+
contexts, use :meth:`Device.create_stream` instead.
99+
100+
Parameters
101+
----------
102+
options : :obj:`~_stream.StreamOptions`, optional
103+
Customizable dataclass for stream creation options.
104+
105+
Returns
106+
-------
107+
:obj:`~_stream.Stream`
108+
Newly created stream object.
109+
"""
110+
if not self._h_context:
111+
raise RuntimeError("Cannot create a stream on a closed context")
112+
if not self.is_green:
113+
raise RuntimeError(
114+
"Context.create_stream() is only supported on green contexts. "
115+
"Use Device.create_stream() for primary contexts."
116+
)
117+
118+
from cuda.core._stream import Stream
119+
return Stream._init(options=options, device_id=self._device_id, ctx=self)
120+
78121
cpdef close(self):
79122
"""Release this context wrapper's underlying CUDA handles."""
80123
cdef cydriver.CUcontext current_ctx

cuda_core/cuda/core/_cpp/resource_handles.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ decltype(&cuGreenCtxDestroy) p_cuGreenCtxDestroy = nullptr;
3434
decltype(&cuCtxFromGreenCtx) p_cuCtxFromGreenCtx = nullptr;
3535
decltype(&cuDevResourceGenerateDesc) p_cuDevResourceGenerateDesc = nullptr;
3636

37+
decltype(&cuGreenCtxStreamCreate) p_cuGreenCtxStreamCreate = nullptr;
38+
3739
decltype(&cuStreamCreateWithPriority) p_cuStreamCreateWithPriority = nullptr;
3840
decltype(&cuStreamDestroy) p_cuStreamDestroy = nullptr;
3941

@@ -427,6 +429,34 @@ StreamHandle create_stream_handle(const ContextHandle& h_ctx, unsigned int flags
427429
return h;
428430
}
429431

432+
StreamHandle create_stream_handle_for_green_ctx(
433+
const GreenCtxHandle& h_green, const ContextHandle& h_ctx,
434+
unsigned int flags, int priority) {
435+
if (!p_cuGreenCtxStreamCreate) {
436+
err = CUDA_ERROR_NOT_SUPPORTED;
437+
return {};
438+
}
439+
440+
GILReleaseGuard gil;
441+
CUstream stream;
442+
if (CUDA_SUCCESS != (err = p_cuGreenCtxStreamCreate(&stream, as_cu(h_green), flags, priority))) {
443+
return {};
444+
}
445+
446+
auto box = std::shared_ptr<const StreamBox>(
447+
new StreamBox{stream, h_ctx},
448+
[](const StreamBox* b) {
449+
stream_registry.unregister_handle(b->resource);
450+
GILReleaseGuard gil;
451+
p_cuStreamDestroy(b->resource);
452+
delete b;
453+
}
454+
);
455+
StreamHandle h(box, &box->resource);
456+
stream_registry.register_handle(stream, h);
457+
return h;
458+
}
459+
430460
StreamHandle create_stream_handle_ref(CUstream stream) {
431461
if (auto h = stream_registry.lookup(stream)) {
432462
return h;

cuda_core/cuda/core/_cpp/resource_handles.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ extern decltype(&cuGreenCtxDestroy) p_cuGreenCtxDestroy;
6464
extern decltype(&cuCtxFromGreenCtx) p_cuCtxFromGreenCtx;
6565
extern decltype(&cuDevResourceGenerateDesc) p_cuDevResourceGenerateDesc;
6666

67+
extern decltype(&cuGreenCtxStreamCreate) p_cuGreenCtxStreamCreate;
68+
6769
extern decltype(&cuStreamCreateWithPriority) p_cuStreamCreateWithPriority;
6870
extern decltype(&cuStreamDestroy) p_cuStreamDestroy;
6971

@@ -213,6 +215,12 @@ StreamHandle create_stream_handle_ref(CUstream stream);
213215
// The owner is responsible for keeping the stream's context alive.
214216
StreamHandle create_stream_handle_with_owner(CUstream stream, PyObject* owner);
215217

218+
// Create an owning stream from a green context (calls cuGreenCtxStreamCreate).
219+
// The returned stream handle depends on the green ctx's ContextHandle.
220+
StreamHandle create_stream_handle_for_green_ctx(
221+
const GreenCtxHandle& h_green, const ContextHandle& h_ctx,
222+
unsigned int flags, int priority);
223+
216224
// Return the context dependency associated with a stream handle, if any.
217225
ContextHandle get_stream_context(const StreamHandle& h) noexcept;
218226

cuda_core/cuda/core/_device_resources.pxd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
from cuda.bindings cimport cydriver
6+
from cuda.core._resource_handles cimport ContextHandle, GreenCtxHandle
67

78

89
cdef class SMResource:
@@ -38,7 +39,13 @@ cdef class WorkqueueResource:
3839
cdef class DeviceResources:
3940
cdef:
4041
int _device_id
42+
ContextHandle _h_context # NULL for device-level queries
4143
object __weakref__
4244

4345
@staticmethod
4446
cdef DeviceResources _init(int device_id)
47+
48+
@staticmethod
49+
cdef DeviceResources _init_from_ctx(ContextHandle h_context, int device_id)
50+
51+
cdef inline int _query_sm(self, cydriver.CUdevResource* res) except?-1 nogil

cuda_core/cuda/core/_device_resources.pyx

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ from libc.stdlib cimport free, malloc
1212
from libc.string cimport memset
1313

1414
from cuda.bindings cimport cydriver
15+
from cuda.core._resource_handles cimport ContextHandle, GreenCtxHandle, as_cu, get_context_green_ctx
1516
from cuda.core._utils.cuda_utils cimport check_or_create_options, HANDLE_RETURN
1617
from cuda.core._utils.cuda_utils import is_sequence
1718
from cuda.core._utils.version cimport cy_binding_version, cy_driver_version
@@ -480,53 +481,99 @@ cdef class WorkqueueResource:
480481

481482

482483
cdef class DeviceResources:
483-
"""Namespace for hardware resource query. Not user-constructible."""
484+
"""Namespace for hardware resource query. Not user-constructible.
485+
486+
When obtained via ``dev.resources``, queries return full device resources.
487+
When obtained via ``ctx.resources``, queries return the resources
488+
provisioned for that context.
489+
"""
484490

485491
def __init__(self, *args, **kwargs):
486492
raise RuntimeError(
487493
"DeviceResources cannot be instantiated directly. "
488-
"Use dev.resources."
494+
"Use dev.resources or ctx.resources."
489495
)
490496

491497
@staticmethod
492498
cdef DeviceResources _init(int device_id):
493499
cdef DeviceResources self = DeviceResources.__new__(DeviceResources)
494500
self._device_id = device_id
501+
# _h_context is default empty — queries use cuDeviceGetDevResource
495502
return self
496503

504+
@staticmethod
505+
cdef DeviceResources _init_from_ctx(ContextHandle h_context, int device_id):
506+
cdef DeviceResources self = DeviceResources.__new__(DeviceResources)
507+
self._device_id = device_id
508+
self._h_context = h_context
509+
return self
510+
511+
cdef inline int _query_sm(self, cydriver.CUdevResource* res) except?-1 nogil:
512+
"""Query SM resource from either device or context."""
513+
cdef GreenCtxHandle h_green
514+
if self._h_context:
515+
h_green = get_context_green_ctx(self._h_context)
516+
if h_green:
517+
HANDLE_RETURN(cydriver.cuGreenCtxGetDevResource(
518+
as_cu(h_green), res,
519+
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_SM,
520+
))
521+
else:
522+
HANDLE_RETURN(cydriver.cuCtxGetDevResource(
523+
as_cu(self._h_context), res,
524+
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_SM,
525+
))
526+
else:
527+
HANDLE_RETURN(cydriver.cuDeviceGetDevResource(
528+
<cydriver.CUdevice>self._device_id, res,
529+
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_SM,
530+
))
531+
return 0
532+
497533
@property
498534
def sm(self) -> SMResource:
499-
"""Query SM resources from this device."""
535+
"""Query SM resources."""
500536
_check_green_ctx_support()
501537
cdef cydriver.CUdevResource res
502538
with nogil:
503-
HANDLE_RETURN(cydriver.cuDeviceGetDevResource(
504-
<cydriver.CUdevice>self._device_id,
505-
&res,
506-
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_SM,
507-
))
539+
self._query_sm(&res)
508540
return SMResource._from_dev_resource(res, self._device_id)
509541

510542
@property
511543
def workqueue(self) -> WorkqueueResource:
512-
"""Query workqueue resources from this device."""
544+
"""Query workqueue resources."""
513545
_check_green_ctx_support()
514546
_check_workqueue_support()
515547
cdef cydriver.CUdevResource _wq_config
516548
cdef cydriver.CUdevResource _wq
517549

518550
IF CUDA_CORE_BUILD_MAJOR >= 13:
519-
with nogil:
520-
HANDLE_RETURN(cydriver.cuDeviceGetDevResource(
521-
<cydriver.CUdevice>self._device_id,
522-
&_wq_config,
523-
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_WORKQUEUE_CONFIG,
524-
))
525-
HANDLE_RETURN(cydriver.cuDeviceGetDevResource(
526-
<cydriver.CUdevice>self._device_id,
527-
&_wq,
528-
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_WORKQUEUE,
529-
))
551+
if self._h_context:
552+
# Context-level query
553+
with nogil:
554+
HANDLE_RETURN(cydriver.cuCtxGetDevResource(
555+
as_cu(self._h_context),
556+
&_wq_config,
557+
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_WORKQUEUE_CONFIG,
558+
))
559+
HANDLE_RETURN(cydriver.cuCtxGetDevResource(
560+
as_cu(self._h_context),
561+
&_wq,
562+
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_WORKQUEUE,
563+
))
564+
else:
565+
# Device-level query
566+
with nogil:
567+
HANDLE_RETURN(cydriver.cuDeviceGetDevResource(
568+
<cydriver.CUdevice>self._device_id,
569+
&_wq_config,
570+
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_WORKQUEUE_CONFIG,
571+
))
572+
HANDLE_RETURN(cydriver.cuDeviceGetDevResource(
573+
<cydriver.CUdevice>self._device_id,
574+
&_wq,
575+
cydriver.CUdevResourceType.CU_DEV_RESOURCE_TYPE_WORKQUEUE,
576+
))
530577
return WorkqueueResource._from_dev_resources(_wq_config, _wq)
531578
ELSE:
532579
raise NotImplementedError(

cuda_core/cuda/core/_resource_handles.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ cdef ContextHandle get_current_context() except+ nogil
123123
# Stream handles
124124
cdef StreamHandle create_stream_handle(
125125
const ContextHandle& h_ctx, unsigned int flags, int priority) except+ nogil
126+
cdef StreamHandle create_stream_handle_for_green_ctx(
127+
const GreenCtxHandle& h_green, const ContextHandle& h_ctx,
128+
unsigned int flags, int priority) except+ nogil
126129
cdef StreamHandle create_stream_handle_ref(cydriver.CUstream stream) except+ nogil
127130
cdef StreamHandle create_stream_handle_with_owner(cydriver.CUstream stream, object owner) except+ nogil
128131
cdef ContextHandle get_stream_context(const StreamHandle& h) noexcept nogil

cuda_core/cuda/core/_resource_handles.pyx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ cdef extern from "_cpp/resource_handles.hpp" namespace "cuda_core":
7272
# Stream handles
7373
StreamHandle create_stream_handle "cuda_core::create_stream_handle" (
7474
const ContextHandle& h_ctx, unsigned int flags, int priority) except+ nogil
75+
StreamHandle create_stream_handle_for_green_ctx "cuda_core::create_stream_handle_for_green_ctx" (
76+
const GreenCtxHandle& h_green, const ContextHandle& h_ctx,
77+
unsigned int flags, int priority) except+ nogil
7578
StreamHandle create_stream_handle_ref "cuda_core::create_stream_handle_ref" (
7679
cydriver.CUstream stream) except+ nogil
7780
StreamHandle create_stream_handle_with_owner "cuda_core::create_stream_handle_with_owner" (
@@ -239,6 +242,7 @@ cdef extern from "_cpp/resource_handles.hpp" namespace "cuda_core":
239242
void* p_cuGreenCtxDestroy "reinterpret_cast<void*&>(cuda_core::p_cuGreenCtxDestroy)"
240243
void* p_cuCtxFromGreenCtx "reinterpret_cast<void*&>(cuda_core::p_cuCtxFromGreenCtx)"
241244
void* p_cuDevResourceGenerateDesc "reinterpret_cast<void*&>(cuda_core::p_cuDevResourceGenerateDesc)"
245+
void* p_cuGreenCtxStreamCreate "reinterpret_cast<void*&>(cuda_core::p_cuGreenCtxStreamCreate)"
242246

243247
# Stream
244248
void* p_cuStreamCreateWithPriority "reinterpret_cast<void*&>(cuda_core::p_cuStreamCreateWithPriority)"
@@ -320,6 +324,7 @@ p_cuGreenCtxCreate = _get_optional_driver_fn("cuGreenCtxCreate")
320324
p_cuGreenCtxDestroy = _get_optional_driver_fn("cuGreenCtxDestroy")
321325
p_cuCtxFromGreenCtx = _get_optional_driver_fn("cuCtxFromGreenCtx")
322326
p_cuDevResourceGenerateDesc = _get_optional_driver_fn("cuDevResourceGenerateDesc")
327+
p_cuGreenCtxStreamCreate = _get_optional_driver_fn("cuGreenCtxStreamCreate")
323328

324329
# Stream
325330
p_cuStreamCreateWithPriority = _get_driver_fn("cuStreamCreateWithPriority")

cuda_core/cuda/core/_stream.pyx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ from cuda.core._event import Event, EventOptions
2525
from cuda.core._resource_handles cimport (
2626
ContextHandle,
2727
EventHandle,
28+
GreenCtxHandle,
2829
StreamHandle,
2930
create_context_handle_ref,
3031
create_event_handle_noctx,
3132
create_stream_handle,
33+
create_stream_handle_for_green_ctx,
3234
create_stream_handle_with_owner,
35+
get_context_green_ctx,
3336
get_current_context,
3437
get_legacy_stream,
3538
get_per_thread_stream,
@@ -154,7 +157,13 @@ cdef class Stream:
154157
prio = high
155158

156159
# C++ creates the stream and returns owning handle with context dependency
157-
h_stream = create_stream_handle(h_context, flags, prio)
160+
cdef GreenCtxHandle h_green
161+
if h_context:
162+
h_green = get_context_green_ctx(h_context)
163+
if h_green:
164+
h_stream = create_stream_handle_for_green_ctx(h_green, h_context, flags, prio)
165+
else:
166+
h_stream = create_stream_handle(h_context, flags, prio)
158167
if not h_stream:
159168
raise RuntimeError("Failed to create CUDA stream")
160169
self = Stream._from_handle(cls, h_stream)

0 commit comments

Comments
 (0)