Skip to content

Commit cc98c05

Browse files
gbonikblinxt
andcommitted
Allow frozen dataclasses as kernel arguments
Co-authored-by: Boyan Li <boyanl@nvidia.com> Signed-off-by: Greg Bonik <gbonik@nvidia.com>
1 parent 490166d commit cc98c05

11 files changed

Lines changed: 908 additions & 63 deletions

File tree

cext/launch_helper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ struct LaunchHelper {
3838
Vec<PyTypeObject*> pyarg_types_breadth_first;
3939
Vec<PyObject*> pyarg_objs_breadth_first;
4040
Vec<PyObject*> leaf_pyarg_objs;
41+
Vec<PyPtr> pyarg_refs; // extra references to parsed arguments, e.g. unpacked dataclass fields
4142
Arena arena;
4243
Vec<ArenaOffset> cuarg_offsets; // offsets into `arena`
4344
Vec<ArenaOffset> array_ptr_arena_offsets;

cext/tile_kernel.cpp

Lines changed: 138 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ static PyObject* g_cooperative_pyunicode;
3737
static PyObject* g_block_in_cluster_count_pyunicode;
3838
static PyObject* g_preferred_block_in_cluster_count_pyunicode;
3939
static PyObject* g_programmatic_dependent_launch_pyunicode;
40+
static PyObject* g___dataclass_fields___pyunicode;
4041

4142
static PyTypeObject* g_torch_Tensor_type;
4243
static PyTypeObject* g_torch_cuda_Stream_type;
@@ -250,7 +251,6 @@ static PyMethodDef CallingConvention_methods[] = {
250251
"cutile_python_v3()\n"
251252
"--\n\n"
252253
"Returns the ``cutile_python_v3`` calling convention.\n\n"
253-
254254
},
255255
#endif
256256
{} // sentinel
@@ -430,6 +430,7 @@ static LaunchHelper* g_helper_freelist; // protected by the GIL or g_launch_mut
430430

431431
namespace { struct LaunchHelperDeleter {
432432
void operator() (LaunchHelper* helper) const {
433+
helper->pyarg_refs.clear();
433434
helper->next_free = g_helper_freelist;
434435
g_helper_freelist = helper;
435436
}
@@ -455,18 +456,37 @@ static LaunchHelperPtr launch_helper_get() {
455456
}
456457
}
457458

459+
460+
namespace { struct DataclassInfo : SimpleRefcount<DataclassInfo> {
461+
PyPtr dataclass;
462+
Vec<PyPtr> field_names;
463+
464+
DataclassInfo(PyPtr dataclass, Vec<PyPtr> field_names)
465+
: dataclass(std::move(dataclass)), field_names(std::move(field_names))
466+
{ }
467+
}; }
468+
469+
458470
struct AggregateArgType {
459471
enum Kind {
460-
Tuple
472+
Tuple,
473+
#ifdef ENABLE_CCONV_V3
474+
Dataclass
475+
#endif
461476
};
462477

463478
Kind kind;
479+
RefPtr<DataclassInfo> dataclass_info;
464480

465481
bool operator== (const AggregateArgType& other) const {
466482
if (kind != other.kind) return false;
467483
switch (kind) {
468484
case Kind::Tuple:
469485
return true;
486+
#ifdef ENABLE_CCONV_V3
487+
case Kind::Dataclass:
488+
return dataclass_info->dataclass == other.dataclass_info->dataclass;
489+
#endif
470490
}
471491
CHECK(false);
472492
}
@@ -713,6 +733,44 @@ namespace { struct ProfileMapKey {
713733
// We use the HashMap as a hash set by providing a dummy value type.
714734
using ProfileMap = HashMap<ProfileMapKey, int /*dummy*/>;
715735

736+
737+
738+
#ifdef ENABLE_CCONV_V3
739+
static Status get_dataclass_field_names(PyObject* cls, Vec<PyPtr>* field_names) {
740+
PyPtr fields = steal(PyObject_GetAttr(cls, g___dataclass_fields___pyunicode));
741+
if (!fields) return ErrorRaised;
742+
743+
PyPtr field_iter = steal(PyObject_GetIter(fields.get()));
744+
if (!field_iter) return ErrorRaised;
745+
746+
while (PyPtr name = steal(PyIter_Next(field_iter.get()))) {
747+
field_names->push_back(std::move(name));
748+
}
749+
if (PyErr_Occurred())
750+
return ErrorRaised;
751+
return OK;
752+
}
753+
754+
755+
static RefPtr<DataclassInfo> get_dataclass_info(PyTypeObject* ty) {
756+
static HashMap<PyPtr, RefPtr<DataclassInfo>>* cache;
757+
if (!cache) cache = new HashMap<PyPtr, RefPtr<DataclassInfo>>();
758+
759+
PyPtr cls_ref = newref(reinterpret_cast<PyObject*>(ty));
760+
HashMap<PyPtr, RefPtr<DataclassInfo>>::Item* cached = cache->find(cls_ref);
761+
if (cached) return cached->value;
762+
763+
Vec<PyPtr> field_names;
764+
if (!get_dataclass_field_names(cls_ref.get(), &field_names))
765+
return {};
766+
767+
RefPtr<DataclassInfo> info = newref(new DataclassInfo(cls_ref, std::move(field_names)));
768+
cache->insert(cls_ref, info);
769+
return info;
770+
}
771+
#endif // ENABLE_CCONV_V3
772+
773+
716774
namespace {struct AggregateArgInfo {
717775
size_t breadth_first_index;
718776
AggregateArgType type;
@@ -2151,10 +2209,21 @@ static PyPtr create_tuple_constraint(PyObject* items_list) {
21512209
return steal(PyObject_CallOneArg(constraint_class.get(), items_list));
21522210
}
21532211

2212+
#ifdef ENABLE_CCONV_V3
2213+
static PyPtr create_dataclass_constraint(PyObject* dataclass, PyObject* items_list) {
2214+
PyObject* signature_module = get_signature_module();
2215+
if (!signature_module) return {};
2216+
PyPtr constraint_class = getattr(signature_module, "DataclassConstraint");
2217+
if (!constraint_class) return {};
2218+
return steal(PyObject_CallFunctionObjArgs(constraint_class.get(),
2219+
dataclass, items_list, nullptr));
2220+
}
2221+
#endif
2222+
21542223
static PyPtr parse_param_constraint(ConstantCursor& cursor,
21552224
Cursor<ParameterKind>* param_cursor,
21562225
Cursor<RefPtr<LeafAnnotationNode>>* annotation_cursor) {
2157-
ParameterKind pk = param_cursor->next();
2226+
const ParameterKind& pk = param_cursor->next();
21582227
if (pk.category == ParameterKind::AggregateBegin) {
21592228
PyPtr items_list = steal(PyList_New(0));
21602229
if (!items_list) return {};
@@ -2169,6 +2238,11 @@ static PyPtr parse_param_constraint(ConstantCursor& cursor,
21692238
switch (pk.agg_type.kind) {
21702239
case AggregateArgType::Tuple:
21712240
return create_tuple_constraint(items_list.get());
2241+
#ifdef ENABLE_CCONV_V3
2242+
case AggregateArgType::Dataclass:
2243+
return create_dataclass_constraint(pk.agg_type.dataclass_info->dataclass.get(),
2244+
items_list.get());
2245+
#endif
21722246
}
21732247
CHECK(false);
21742248
}
@@ -2209,6 +2283,11 @@ static CallConvVersion minimum_calling_convention(
22092283
case AggregateArgType::Tuple:
22102284
require(CallConvVersion::CutilePython_V2);
22112285
break;
2286+
#ifdef ENABLE_CCONV_V3
2287+
case AggregateArgType::Dataclass:
2288+
require(CallConvVersion::CutilePython_V3);
2289+
break;
2290+
#endif
22122291
}
22132292
}
22142293
}
@@ -2832,9 +2911,24 @@ static Status stage_list_args_on_stream(const DriverApi* driver,
28322911
return OK;
28332912
}
28342913

2914+
#ifdef ENABLE_CCONV_V3
2915+
static bool is_dataclass(PyTypeObject* ty) {
2916+
return PyObject_HasAttr(reinterpret_cast<PyObject*>(ty),
2917+
g___dataclass_fields___pyunicode);
2918+
}
2919+
#endif
2920+
28352921
static Result<std::optional<AggregateArgType>> classify_aggregate_type(PyTypeObject* ty) {
2836-
if (ty == &PyTuple_Type) {
2837-
return {{{ AggregateArgType::Tuple }}};
2922+
if (ty == nullptr) {
2923+
return {std::nullopt};
2924+
} else if (ty == &PyTuple_Type) {
2925+
return {{{ AggregateArgType::Tuple, {} }}};
2926+
#ifdef ENABLE_CCONV_V3
2927+
} else if (is_dataclass(ty)) {
2928+
RefPtr<DataclassInfo> info = get_dataclass_info(ty);
2929+
if (!info) return ErrorRaised;
2930+
return {{{ AggregateArgType::Dataclass, std::move(info) }}};
2931+
#endif
28382932
} else {
28392933
return {std::nullopt};
28402934
}
@@ -2884,6 +2978,13 @@ static ErrorRaised_t raise_invalid_kernel_arg_type_impl(
28842978
case AggregateArgType::Tuple:
28852979
new_str = steal(PyUnicode_FromFormat("%Uitem #%zu of ", ret.get(), path[i].item_idx));
28862980
break;
2981+
#ifdef ENABLE_CCONV_V3
2982+
case AggregateArgType::Dataclass:
2983+
CHECK(path[i].item_idx < agg_type->dataclass_info->field_names.size());
2984+
new_str = steal(PyUnicode_FromFormat("%Ufield '%U' of ", ret.get(),
2985+
agg_type->dataclass_info->field_names[path[i].item_idx].get()));
2986+
break;
2987+
#endif
28872988
}
28882989
if (!new_str) return {};
28892990
ret = std::move(new_str);
@@ -3125,11 +3226,30 @@ static void gather_leaf_pyargs(const Vec<PyObject*>& pyarg_objs_breadth_first,
31253226
leaf_pyarg_objs->push_back(pyarg_objs_breadth_first[i]);
31263227
}
31273228

3229+
#ifdef ENABLE_CCONV_V3
3230+
static Status expand_dataclass_instance(PyObject* arg, const DataclassInfo& info,
3231+
Vec<PyObject*>* pyarg_objs_breadth_first,
3232+
Vec<PyTypeObject*>* pyarg_types_breadth_first,
3233+
Vec<PyPtr>* pyarg_refs) {
3234+
for (const PyPtr& name : info.field_names) {
3235+
PyPtr value = steal(PyObject_GetAttr(arg, name.get()));
3236+
if (!value) return ErrorRaised;
3237+
pyarg_objs_breadth_first->push_back(value.get());
3238+
pyarg_types_breadth_first->push_back(Py_TYPE(value.get()));
3239+
pyarg_refs->push_back(std::move(value));
3240+
}
3241+
pyarg_objs_breadth_first->push_back(nullptr);
3242+
pyarg_types_breadth_first->push_back(nullptr);
3243+
return OK;
3244+
}
3245+
#endif
3246+
31283247
static Status expand_aggregate_arg(
31293248
PyObject* arg,
31303249
const AggregateArgType& agg_type,
31313250
Vec<PyObject*>* pyarg_objs_breadth_first,
3132-
Vec<PyTypeObject*>* pyarg_types_breadth_first) {
3251+
Vec<PyTypeObject*>* pyarg_types_breadth_first,
3252+
Vec<PyPtr>* pyarg_refs) {
31333253
switch (agg_type.kind) {
31343254
case AggregateArgType::Tuple:
31353255
CHECK(PyTuple_CheckExact(arg));
@@ -3138,6 +3258,11 @@ static Status expand_aggregate_arg(
31383258
pyarg_objs_breadth_first,
31393259
pyarg_types_breadth_first);
31403260
return OK;
3261+
#ifdef ENABLE_CCONV_V3
3262+
case AggregateArgType::Dataclass:
3263+
return expand_dataclass_instance(arg, *agg_type.dataclass_info,
3264+
pyarg_objs_breadth_first, pyarg_types_breadth_first, pyarg_refs);
3265+
#endif
31413266
}
31423267
CHECK(false);
31433268
}
@@ -3150,7 +3275,8 @@ static PythonArgProfile* python_arg_profile_lookup_impl(
31503275
Vec<RefPtr<KernelFamily>>* kernel_families,
31513276
Vec<PyObject*>* pyarg_objs_breadth_first,
31523277
Vec<PyTypeObject*>* pyarg_types_breadth_first,
3153-
Vec<PyObject*>* leaf_pyarg_objs) {
3278+
Vec<PyObject*>* leaf_pyarg_objs,
3279+
Vec<PyPtr>* pyarg_refs) {
31543280
ProfileMapQuery query = {pyarg_types_breadth_first, 0, 0};
31553281
query.mark_start();
31563282
get_pyarg_objects_and_types(pyargs, num_pyargs,
@@ -3248,7 +3374,8 @@ static PythonArgProfile* python_arg_profile_lookup_impl(
32483374
for (const AggregateArgInfo& agg_info : next->aggregate_args) {
32493375
PyObject* arg = (*pyarg_objs_breadth_first)[agg_info.breadth_first_index];
32503376
if (!expand_aggregate_arg(arg, agg_info.type,
3251-
pyarg_objs_breadth_first, pyarg_types_breadth_first))
3377+
pyarg_objs_breadth_first, pyarg_types_breadth_first,
3378+
pyarg_refs))
32523379
return nullptr;
32533380
}
32543381
query.mark_end();
@@ -3275,7 +3402,8 @@ static PythonArgProfile* python_arg_profile_lookup(PyObject* const* pyargs,
32753402
&ctx_dispatcher->kernel_families,
32763403
&helper->pyarg_objs_breadth_first,
32773404
&helper->pyarg_types_breadth_first,
3278-
&helper->leaf_pyarg_objs);
3405+
&helper->leaf_pyarg_objs,
3406+
&helper->pyarg_refs);
32793407
}
32803408

32813409
static Result<PreparedLaunch> prepare_launch(
@@ -4537,6 +4665,7 @@ Status tile_kernel_init(PyObject* m) {
45374665
INIT_STRING_CONSTANT(block_in_cluster_count);
45384666
INIT_STRING_CONSTANT(preferred_block_in_cluster_count);
45394667
INIT_STRING_CONSTANT(programmatic_dependent_launch);
4668+
INIT_STRING_CONSTANT(__dataclass_fields__);
45404669

45414670
g_constant_kind_enum = define_constant_kind_enum().release();
45424671
if (!g_constant_kind_enum) return ErrorRaised;

experimental/cuda-lang/test/test_launch.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5+
from dataclasses import dataclass
6+
from typing import Any
7+
58
import cuda.lang as cl
69
import torch
710
import pytest
811

12+
from cuda.tile._cext import cconv_v3_enabled
13+
914

1015
def test_too_many_kwargs():
1116
@cl.kernel()
@@ -16,3 +21,21 @@ def kernel():
1621

1722
with pytest.raises(TypeError, match="unexpected keyword argument"):
1823
cl.launch(torch.cuda.current_stream(), (1,), (1,), kernel, (), **bad_kwargs)
24+
25+
26+
@pytest.mark.skipif(not cconv_v3_enabled(), reason="Requires cconv3 enabled")
27+
def test_dataclass_kernel_arg():
28+
@dataclass(frozen=True)
29+
class KernelArgs:
30+
out: Any
31+
scale: int
32+
bias: float
33+
34+
@cl.kernel()
35+
def kernel(a):
36+
i = cl.thread_index(0)
37+
a.out[i] = i * a.scale + a.bias
38+
39+
x = torch.zeros((4,), dtype=torch.float32, device="cuda")
40+
cl.launch(torch.cuda.current_stream(), (1,), (4,), kernel, (KernelArgs(x, 10, 0.5),))
41+
assert x.tolist() == [0.5, 10.5, 20.5, 30.5]

0 commit comments

Comments
 (0)