From b8868c04d9614fcb2e1b0feb6209f3b4580abd02 Mon Sep 17 00:00:00 2001 From: Vince Han Date: Fri, 26 Jun 2026 15:13:57 -0700 Subject: [PATCH 1/2] Add run_warmup opt-out and reuse-parsed-json overload to Graph::deserialize --- include/cudnn_frontend/graph_interface.h | 37 ++++++++---- test/cpp/serialize.cpp | 77 ++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 11 deletions(-) diff --git a/include/cudnn_frontend/graph_interface.h b/include/cudnn_frontend/graph_interface.h index af51de962..71d1cfcf9 100644 --- a/include/cudnn_frontend/graph_interface.h +++ b/include/cudnn_frontend/graph_interface.h @@ -1626,12 +1626,29 @@ class Graph : public ICudnn, public INode { #endif } + // Parse the blob then delegate. Callers that already parsed it should call the + // json overload to skip this second parse. error_t - deserialize(cudnnHandle_t handle, std::vector const &data, bool const enforce_precompiled = false) { - CUDNN_FE_LOG_BANNER(" DESERIALIZE PLAN WITH HANDLE "); + deserialize(cudnnHandle_t handle, std::vector const &data, bool const enforce_precompiled = false, + bool run_warmup = true) { +#ifndef CUDNN_FRONTEND_SKIP_JSON_LIB + return deserialize(handle, json::from_ubjson(data), enforce_precompiled, run_warmup); +#else + CUDNN_FRONTEND_UNUSED(handle); + CUDNN_FRONTEND_UNUSED(data); + CUDNN_FRONTEND_UNUSED(enforce_precompiled); + CUDNN_FRONTEND_UNUSED(run_warmup); + return {error_code_t::GRAPH_NOT_SUPPORTED, "unavailable when compiled with CUDNN_FRONTEND_SKIP_JSON_LIB"}; +#endif + } #ifndef CUDNN_FRONTEND_SKIP_JSON_LIB - json j = json::from_ubjson(data); + // Plan deserialize from an already-parsed json (avoids a second from_ubjson). + // run_warmup=false skips the throwaway warmup capture and the tensor-properties + // build that only feeds it (and tensors_to_dump). + error_t + deserialize(cudnnHandle_t handle, json const &j, bool const enforce_precompiled = false, bool run_warmup = true) { + CUDNN_FE_LOG_BANNER(" DESERIALIZE PLAN WITH HANDLE "); // Clear deserialize-owned containers so a re-deserialize on the same Graph // does not feed prepare_variant_pack_template() with stale entries from a @@ -1645,7 +1662,8 @@ class Graph : public ICudnn, public INode { graph_uid = j["graph_uid"].get(); } - if (j.contains("tensors")) { + // deserialized_tensor_properties feeds warmup() and tensors_to_dump; skip when warmup is off. + if (run_warmup && j.contains("tensors")) { auto tensor_map = j["tensors"].get>(); for (const auto &tensor_info : tensor_map) { auto tensor_attributes = std::make_shared(); @@ -1717,18 +1735,15 @@ class Graph : public ICudnn, public INode { } } - CHECK_CUDNN_FRONTEND_ERROR(warmup(handle)); + if (run_warmup) { + CHECK_CUDNN_FRONTEND_ERROR(warmup(handle)); + } CUDNN_FE_LOG_BANNER(" DESERIALIZE PLAN WITH HANDLE (ALL OK) "); return {error_code_t::OK, ""}; -#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 } +#endif Type getType() override { diff --git a/test/cpp/serialize.cpp b/test/cpp/serialize.cpp index 0ecff5208..0e8090f94 100644 --- a/test/cpp/serialize.cpp +++ b/test/cpp/serialize.cpp @@ -596,3 +596,80 @@ TEST_CASE("Plan deserialize prepares variant pack template", "[graph][serialize] cudnnDestroy(handle); } + +// Exercises the run_warmup=false fast path of deserialize(handle, ...). Skipping +// the throwaway warmup capture must not change the resulting plan: the variant +// pack template is built by prepare_variant_pack_template(), which is independent +// of warmup, so the deserialized graph is still fully usable. +// +// Note the additive argument order: deserialize(handle, data, enforce_precompiled, run_warmup). +// Both bools are spelled explicitly below so the positional meaning is unambiguous. +TEST_CASE("Plan deserialize with run_warmup=false still prepares template", + "[graph][serialize][deserialize]") { + namespace fe = cudnn_frontend; + + constexpr int64_t a_uid = 1, b_uid = 2, c_uid = 3; + + fe::graph::Graph graph; + graph.set_io_data_type(fe::DataType_t::HALF) + .set_intermediate_data_type(fe::DataType_t::FLOAT) + .set_compute_data_type(fe::DataType_t::FLOAT); + + auto A = graph.tensor( + fe::graph::Tensor_attributes().set_name("A").set_dim({4, 16, 64}).set_stride({16 * 64, 64, 1}).set_uid(a_uid)); + auto B = graph.tensor( + fe::graph::Tensor_attributes().set_name("B").set_dim({4, 64, 32}).set_stride({64 * 32, 32, 1}).set_uid(b_uid)); + + auto C = graph.matmul(A, B, fe::graph::Matmul_attributes().set_name("matmul")); + C->set_output(true).set_uid(c_uid); + + cudnnHandle_t handle; + cudnnCreate(&handle); + + REQUIRE(graph.build(handle, {fe::HeurMode_t::A}).is_good()); + + // serialize the graph + std::vector serialized_data; + REQUIRE(graph.serialize(serialized_data).is_good()); + + // expected uids + std::vector const expected_uids{a_uid, b_uid, c_uid}; + + // test the blob overload, run_warmup=false. + // Check for success and variant pack uids are the same as the expected uids + SECTION("blob overload, run_warmup=false") { + fe::graph::Graph graph_deserialized; + REQUIRE(graph_deserialized + .deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/false) + .is_good()); + // check the variant pack uids are the same as the expected uids + REQUIRE(graph_deserialized.get_variant_pack_uids_sorted() == expected_uids); + } + + // test the json overload, run_warmup=false. + // same assertion via the pre‑parsed‑json overload, covering the path that avoids a second from_ubjson. + SECTION("json overload, run_warmup=false") { + json const j = json::from_ubjson(serialized_data); + fe::graph::Graph graph_deserialized; + REQUIRE(graph_deserialized.deserialize(handle, j, /*enforce_precompiled=*/false, /*run_warmup=*/false).is_good()); + REQUIRE(graph_deserialized.get_variant_pack_uids_sorted() == expected_uids); + } + + // test the run_warmup=false matches the default warmup=true template + // Confirms warmup is purely a priming step with no effect on the executable plan. + SECTION("run_warmup=false matches the default warmup=true template") { + fe::graph::Graph warmed; + REQUIRE( + warmed.deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/true).is_good()); + + fe::graph::Graph skipped; + REQUIRE( + skipped.deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/false).is_good()); + + // The plan-side state the fast execute path relies on is identical either way. + REQUIRE(warmed.get_variant_pack_uids_sorted() == skipped.get_variant_pack_uids_sorted()); + REQUIRE(skipped.get_variant_pack_uids_sorted() == expected_uids); + } + + cudnnDestroy(handle); +} From 0c03e0249be6c830765ca3b45190480c0dcbd04a Mon Sep 17 00:00:00 2001 From: Vince Han Date: Mon, 29 Jun 2026 08:59:17 -0700 Subject: [PATCH 2/2] docstring, clang, warmup level fixes --- include/cudnn_frontend/graph_interface.h | 39 ++++++++++++++++++------ test/cpp/serialize.cpp | 27 +++++++++------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/include/cudnn_frontend/graph_interface.h b/include/cudnn_frontend/graph_interface.h index 71d1cfcf9..7cac72beb 100644 --- a/include/cudnn_frontend/graph_interface.h +++ b/include/cudnn_frontend/graph_interface.h @@ -1626,11 +1626,24 @@ class Graph : public ICudnn, public INode { #endif } - // Parse the blob then delegate. Callers that already parsed it should call the - // json overload to skip this second parse. + /** + * @brief Deserialize an execution plan from a serialized byte blob. + * + * Parses @p data with from_ubjson and delegates to the json overload. Callers that + * have already parsed the blob should call the json overload directly to avoid a + * second parse. + * + * @param handle cuDNN handle used to rebuild the execution plan. + * @param data UBJSON blob previously produced by serialize(). + * @param enforce_precompiled When true, fail unless the blob carries a precompiled plan. + * @param run_warmup When false, skip the throwaway warmup capture. + * @return error_t OK on success, otherwise an error code describing the failure. + */ error_t - deserialize(cudnnHandle_t handle, std::vector const &data, bool const enforce_precompiled = false, - bool run_warmup = true) { + deserialize(cudnnHandle_t handle, + std::vector const &data, + bool const enforce_precompiled = false, + bool run_warmup = true) { #ifndef CUDNN_FRONTEND_SKIP_JSON_LIB return deserialize(handle, json::from_ubjson(data), enforce_precompiled, run_warmup); #else @@ -1643,9 +1656,17 @@ class Graph : public ICudnn, public INode { } #ifndef CUDNN_FRONTEND_SKIP_JSON_LIB - // Plan deserialize from an already-parsed json (avoids a second from_ubjson). - // run_warmup=false skips the throwaway warmup capture and the tensor-properties - // build that only feeds it (and tensors_to_dump). + /** + * @brief Deserialize an execution plan from an already-parsed json. + * + * Avoids a second from_ubjson parse. run_warmup=false skips the throwaway warmup capture. + * + * @param handle cuDNN handle used to rebuild the execution plan. + * @param j Parsed json graph, as produced by serialize(). + * @param enforce_precompiled When true, fail unless the json carries a precompiled plan. + * @param run_warmup When false, skip the throwaway warmup capture. + * @return error_t OK on success, otherwise an error code describing the failure. + */ error_t deserialize(cudnnHandle_t handle, json const &j, bool const enforce_precompiled = false, bool run_warmup = true) { CUDNN_FE_LOG_BANNER(" DESERIALIZE PLAN WITH HANDLE "); @@ -1662,8 +1683,8 @@ class Graph : public ICudnn, public INode { graph_uid = j["graph_uid"].get(); } - // deserialized_tensor_properties feeds warmup() and tensors_to_dump; skip when warmup is off. - if (run_warmup && j.contains("tensors")) { + // Resolve tensor UIDs with deserialized_tensor_properties. + if (j.contains("tensors")) { auto tensor_map = j["tensors"].get>(); for (const auto &tensor_info : tensor_map) { auto tensor_attributes = std::make_shared(); diff --git a/test/cpp/serialize.cpp b/test/cpp/serialize.cpp index 0e8090f94..24b1e74b8 100644 --- a/test/cpp/serialize.cpp +++ b/test/cpp/serialize.cpp @@ -604,8 +604,7 @@ TEST_CASE("Plan deserialize prepares variant pack template", "[graph][serialize] // // Note the additive argument order: deserialize(handle, data, enforce_precompiled, run_warmup). // Both bools are spelled explicitly below so the positional meaning is unambiguous. -TEST_CASE("Plan deserialize with run_warmup=false still prepares template", - "[graph][serialize][deserialize]") { +TEST_CASE("Plan deserialize with run_warmup=false still prepares template", "[graph][serialize][deserialize]") { namespace fe = cudnn_frontend; constexpr int64_t a_uid = 1, b_uid = 2, c_uid = 3; @@ -635,23 +634,28 @@ TEST_CASE("Plan deserialize with run_warmup=false still prepares template", // expected uids std::vector const expected_uids{a_uid, b_uid, c_uid}; - // test the blob overload, run_warmup=false. + // test the blob overload, run_warmup=false. // Check for success and variant pack uids are the same as the expected uids SECTION("blob overload, run_warmup=false") { fe::graph::Graph graph_deserialized; - REQUIRE(graph_deserialized - .deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/false) - .is_good()); + REQUIRE( + graph_deserialized.deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/false) + .is_good()); // check the variant pack uids are the same as the expected uids REQUIRE(graph_deserialized.get_variant_pack_uids_sorted() == expected_uids); + // tensor metadata must still resolve with warmup skipped (deserialized_tensor_properties) + fe::graph::Tensor_attributes queried; + REQUIRE(graph_deserialized.query_tensor_attributes_of_uid(a_uid, queried).is_good()); } - // test the json overload, run_warmup=false. - // same assertion via the pre‑parsed‑json overload, covering the path that avoids a second from_ubjson. + // test the json overload, run_warmup=false. + // same assertion via the pre-parsed-json overload, covering the path that avoids a second from_ubjson. SECTION("json overload, run_warmup=false") { json const j = json::from_ubjson(serialized_data); fe::graph::Graph graph_deserialized; - REQUIRE(graph_deserialized.deserialize(handle, j, /*enforce_precompiled=*/false, /*run_warmup=*/false).is_good()); + auto const status = + graph_deserialized.deserialize(handle, j, /*enforce_precompiled=*/false, /*run_warmup=*/false); + REQUIRE(status.is_good()); REQUIRE(graph_deserialized.get_variant_pack_uids_sorted() == expected_uids); } @@ -663,8 +667,9 @@ TEST_CASE("Plan deserialize with run_warmup=false still prepares template", warmed.deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/true).is_good()); fe::graph::Graph skipped; - REQUIRE( - skipped.deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/false).is_good()); + auto const skipped_status = + skipped.deserialize(handle, serialized_data, /*enforce_precompiled=*/false, /*run_warmup=*/false); + REQUIRE(skipped_status.is_good()); // The plan-side state the fast execute path relies on is identical either way. REQUIRE(warmed.get_variant_pack_uids_sorted() == skipped.get_variant_pack_uids_sorted());