Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions include/cudnn_frontend/graph_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -1627,7 +1627,7 @@ class Graph : public ICudnn, public INode {
}

error_t
deserialize(cudnnHandle_t handle, std::vector<uint8_t> const &data) {
deserialize(cudnnHandle_t handle, std::vector<uint8_t> const &data, bool const enforce_precompiled = false) {
CUDNN_FE_LOG_BANNER(" DESERIALIZE PLAN WITH HANDLE ");

#ifndef CUDNN_FRONTEND_SKIP_JSON_LIB
Expand All @@ -1654,8 +1654,12 @@ class Graph : public ICudnn, public INode {
}
}

auto serialized_plan = j["cudnn_backend_data"];
RETURN_CUDNN_FRONTEND_ERROR_IF(
enforce_precompiled && !j.contains("cudnn_backend_data"),
error_code_t::GRAPH_EXECUTION_PLAN_CREATION_FAILED,
"enforce_precompiled requested, but serialized graph has no precompiled execution plan");

auto serialized_plan = j["cudnn_backend_data"];
CHECK_CUDNN_FRONTEND_ERROR(plans.build_plans(handle, serialized_plan));

plans.behavior_notes = j["behavior_notes"].get<std::vector<std::vector<BehaviorNote_t>>>();
Expand Down Expand Up @@ -1721,6 +1725,7 @@ class Graph : public ICudnn, public INode {
#else
CUDNN_FRONTEND_UNUSED(handle);
CUDNN_FRONTEND_UNUSED(data);
CUDNN_FRONTEND_UNUSED(enforce_precompiled);
return {error_code_t::GRAPH_NOT_SUPPORTED, "unavailable when compiled with CUDNN_FRONTEND_SKIP_JSON_LIB"};
#endif
}
Expand Down Expand Up @@ -2462,7 +2467,12 @@ class Graph : public ICudnn, public INode {
// TODO: temparorily placed in graphs class. This function needs to be a free standing function.
#ifndef CUDNN_FRONTEND_SKIP_JSON_LIB
error_t
deserialize(const json &j) {
deserialize(const json &j, bool const enforce_precompiled = false) {
RETURN_CUDNN_FRONTEND_ERROR_IF(
enforce_precompiled,
error_code_t::GRAPH_NOT_SUPPORTED,
"enforce_precompiled requires plan serialization; JSON deserialization reconstructs the graph");

if (j.contains("context")) {
const auto &j_context = j["context"];
if (j_context.contains("compute_data_type") && !j_context["compute_data_type"].is_null()) {
Expand Down
20 changes: 12 additions & 8 deletions python/pygraph/pygraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,11 @@ PyGraph::serialize() const {
}

void
PyGraph::deserialize(std::optional<std::intptr_t> handle_, py::object const& pyobj) {
PyGraph::deserialize(std::optional<std::intptr_t> handle_, py::object const& pyobj, bool const enforce_precompiled) {
if (py::isinstance<py::str>(pyobj)) {
json j = json::parse(pyobj.cast<std::string>());

auto status = graph->deserialize(j);
auto status = graph->deserialize(j, enforce_precompiled);

throw_if(status.is_bad(), status.get_code(), status.get_message());

Expand All @@ -634,16 +634,16 @@ PyGraph::deserialize(std::optional<std::intptr_t> handle_, py::object const& pyo
handle_.has_value() ? static_cast<cudnnHandle_t>((void*)(handle_.value())) : this->handle;

std::vector<uint8_t> data = pyobj.cast<std::vector<uint8_t>>();
auto status = graph->deserialize(handle, data);
auto status = graph->deserialize(handle, data, enforce_precompiled);

throw_if(status.is_bad(), status.get_code(), status.get_message());
}
}

void
PyGraph::deserialize(py::object const& pyobj) {
PyGraph::deserialize(py::object const& pyobj, bool const enforce_precompiled) {
// Call the overloaded version with default handle (nullopt)
deserialize(std::nullopt, pyobj);
deserialize(std::nullopt, pyobj, enforce_precompiled);
}

void
Expand Down Expand Up @@ -1378,10 +1378,14 @@ init_pygraph_submodule(py::module_& m) {
.def("update_cuda_graph", &PyGraph::update_cuda_graph)
.def("serialize", &PyGraph::serialize)
.def("deserialize",
(void (PyGraph::*)(std::optional<std::intptr_t>, py::object const&))&PyGraph::deserialize,
(void (PyGraph::*)(std::optional<std::intptr_t>, py::object const&, bool const))&PyGraph::deserialize,
py::arg("handle_"),
py::arg("pyobj"))
.def("deserialize", (void (PyGraph::*)(py::object const&))&PyGraph::deserialize, py::arg("pyobj"))
py::arg("pyobj"),
py::arg("enforce_precompiled") = false)
.def("deserialize",
(void (PyGraph::*)(py::object const&, bool const))&PyGraph::deserialize,
py::arg("pyobj"),
py::arg("enforce_precompiled") = false)
.def("_execute_plan_at_index",
&PyGraph::execute_plan_at_index,
py::arg("var_pack"),
Expand Down
4 changes: 2 additions & 2 deletions python/pygraph/pygraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -776,10 +776,10 @@ class PyGraph {
serialize() const;

void
deserialize(std::optional<std::intptr_t> handle_, py::object const& pyobj);
deserialize(std::optional<std::intptr_t> handle_, py::object const& pyobj, bool const enforce_precompiled = false);

void
deserialize(py::object const& pyobj);
deserialize(py::object const& pyobj, bool const enforce_precompiled = false);

int64_t
get_execution_plan_count() const {
Expand Down
6 changes: 5 additions & 1 deletion test/cpp/serialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ TEST_CASE("Context serialization", "[context][serialize]") {

REQUIRE(j == j2);

auto status = fe::graph::Graph().deserialize(j, true);
REQUIRE(status.is_bad());
REQUIRE(status.get_message().find("enforce_precompiled") != std::string::npos);

REQUIRE(graph.validate().is_good());
}

Expand Down Expand Up @@ -584,7 +588,7 @@ TEST_CASE("Plan deserialize prepares variant pack template", "[graph][serialize]
REQUIRE(graph.serialize(serialized_data).is_good());

fe::graph::Graph graph_deserialized;
REQUIRE(graph_deserialized.deserialize(handle, serialized_data).is_good());
REQUIRE(graph_deserialized.deserialize(handle, serialized_data, true).is_good());

// Variant pack template should already be populated; no execute needed.
auto const user_uids = graph_deserialized.get_variant_pack_uids_sorted();
Expand Down
8 changes: 7 additions & 1 deletion test/python/test_deviceless_aot_compilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
"""


@pytest.mark.L0
def test_deserialize_enforce_precompiled_rejects_json_graph():
with pytest.raises(Exception, match="enforce_precompiled requires plan serialization"):
cudnn.pygraph().deserialize("{}", enforce_precompiled=True)


@pytest.mark.skipif(
LooseVersion(cudnn.backend_version_string()) < "9.11",
reason="requires cudnn 9.11 or higher",
Expand Down Expand Up @@ -74,7 +80,7 @@ def test_device_properties():
cudnn.set_stream(handle=cudnn_handle, stream=stream)

graph_deserialized = cudnn.pygraph()
graph_deserialized.deserialize(cudnn_handle, json_str)
graph_deserialized.deserialize(cudnn_handle, json_str, enforce_precompiled=True)

Y_actual = torch.zeros_like(Y_ref)

Expand Down