From 171c0972a5b0ece2264f0b81eddba4ce0d38d7ea Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Mon, 14 Aug 2023 15:45:19 -0700 Subject: [PATCH 1/8] Add in-memory buffer serialize/deserialize support. --- src/Deserialization.cpp | 22 ++++++++++++++++++++-- src/Deserialization.h | 12 ++++++++++++ src/Serialization.cpp | 30 ++++++++++++++++++++++++++++-- src/Serialization.h | 10 ++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/Deserialization.cpp b/src/Deserialization.cpp index d40c3d746893..27d5d5f2f8af 100644 --- a/src/Deserialization.cpp +++ b/src/Deserialization.cpp @@ -27,10 +27,15 @@ class Deserializer { : external_params(external_params) { } + // Deserialize a pipeline from the given filename Pipeline deserialize(const std::string &filename); + // Deserialize a pipeline from the given input stream Pipeline deserialize(std::istream &in); + // Deserialize a pipeline from the given buffer of bytes + Pipeline deserialize(const std::vector &data); + private: // Helper function to deserialize a homogenous vector from a flatbuffer vector, // does not apply to union types like Stmt and Expr or enum types like MemoryType @@ -1294,9 +1299,12 @@ Pipeline Deserializer::deserialize(std::istream &in) { in.seekg(0, std::ios::end); int size = in.tellg(); in.seekg(0, std::ios::beg); - std::vector data(size); - in.read(data.data(), size); + std::vector data(size); + in.read((char*)data.data(), size); + return deserialize(data); +} +Pipeline Deserializer::deserialize(const std::vector &data) { const auto *pipeline_obj = Serialize::GetPipeline(data.data()); if (pipeline_obj == nullptr) { user_warning << "deserialized pipeline is empty\n"; @@ -1375,6 +1383,11 @@ Pipeline deserialize_pipeline(std::istream &in, const std::map &buffer, const std::map &external_params) { + Internal::Deserializer deserializer(external_params); + return deserializer.deserialize(buffer); +} + } // namespace Halide #else // WITH_SERIALIZATION @@ -1391,6 +1404,11 @@ Pipeline deserialize_pipeline(std::istream &in, const std::map &buffer, const std::map &external_params) { + user_error << "Deserialization is not supported in this build of Halide; try rebuilding with WITH_SERIALIZATION=ON."; + return Pipeline(); +} + } // namespace Halide #endif // WITH_SERIALIZATION diff --git a/src/Deserialization.h b/src/Deserialization.h index 4ef48b60bdb7..d2ce026bfabe 100644 --- a/src/Deserialization.h +++ b/src/Deserialization.h @@ -15,8 +15,20 @@ namespace Halide { */ Pipeline deserialize_pipeline(const std::string &filename, const std::map &external_params); +/** + * Deserialize a Halide pipeline from an input stream. + * external_params is an optional map, all parameters in the map + * will be treated as external parameters so won't be deserialized. + */ Pipeline deserialize_pipeline(std::istream &in, const std::map &external_params); +/** + * Deserialize a Halide pipeline from a byte buffer containing a serizalized pipeline in binary format + * external_params is an optional map, all parameters in the map + * will be treated as external parameters so won't be deserialized. + */ +Pipeline deserialize_pipeline(const std::vector &data, const std::map &external_params); + } // namespace Halide #endif diff --git a/src/Serialization.cpp b/src/Serialization.cpp index c2cde6818884..f6e8a52837d0 100644 --- a/src/Serialization.cpp +++ b/src/Serialization.cpp @@ -28,8 +28,12 @@ class Serializer { public: Serializer() = default; + // Serialize the given pipeline into the given filename void serialize(const Pipeline &pipeline, const std::string &filename); + // Serialize the given pipeline into given the data buffer + void serialize(const Pipeline &pipeline, std::vector &data); + const std::map &get_external_parameters() const { return external_parameters; } @@ -1388,7 +1392,7 @@ void Serializer::build_function_mappings(const std::map & } } -void Serializer::serialize(const Pipeline &pipeline, const std::string &filename) { +void Serializer::serialize(const Pipeline &pipeline, std::vector &result) { FlatBufferBuilder builder(1024); // extract the DAG, unwrap function from Funcs @@ -1453,17 +1457,35 @@ void Serializer::serialize(const Pipeline &pipeline, const std::string &filename uint8_t *buf = builder.GetBufferPointer(); int size = builder.GetSize(); + + if(buf != nullptr && size > 0) { + result.reserve(size); + result.insert(result.begin(), buf, buf + size); + } else { + user_error << "failed to serialize pipeline!\n"; + } +} + +void Serializer::serialize(const Pipeline &pipeline, const std::string &filename) { + std::vector data; + serialize(pipeline, data); std::ofstream out(filename, std::ios::out | std::ios::binary); if (!out) { user_error << "failed to open file " << filename << "\n"; exit(1); } - out.write((char *)(buf), size); + out.write((char *)(data.data()), data.size()); out.close(); } } // namespace Internal +void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms) { + Internal::Serializer serializer; + serializer.serialize(pipeline, data); + params = serializer.get_external_parameters(); +} + void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, std::map ¶ms) { Internal::Serializer serializer; serializer.serialize(pipeline, filename); @@ -1476,6 +1498,10 @@ void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, s namespace Halide { +void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms) { + user_error << "Serialization is not supported in this build of Halide; try rebuilding with WITH_SERIALIZATION=ON."; +} + void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, std::map ¶ms) { user_error << "Serialization is not supported in this build of Halide; try rebuilding with WITH_SERIALIZATION=ON."; } diff --git a/src/Serialization.h b/src/Serialization.h index 85d2c86dae47..1f60f86f68ab 100644 --- a/src/Serialization.h +++ b/src/Serialization.h @@ -5,6 +5,16 @@ namespace Halide { +/** + * Serialize a Halide pipeline into the given data buffer. + * params is an optional map which can be used to bind external parameters to objects in the pipeline by name + */ +void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms); + +/** + * Serialize a Halide pipeline into the given filename. + * params is an optional map which can be used to bind external parameters to objects in the pipeline by name + */ void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, std::map ¶ms); } // namespace Halide From 8100b93de18a2160f0c16c86b3a8ec09c879213e Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Mon, 14 Aug 2023 15:45:50 -0700 Subject: [PATCH 2/8] Add basic serialization tutorial --- tutorial/CMakeLists.txt | 3 + tutorial/lesson_22_serialization.cpp | 169 +++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 tutorial/lesson_22_serialization.cpp diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt index 19b72d58c09c..44d81d9bb320 100644 --- a/tutorial/CMakeLists.txt +++ b/tutorial/CMakeLists.txt @@ -209,3 +209,6 @@ if (TARGET Halide::Mullapudi2016) add_test(NAME tutorial_lesson_21_auto_scheduler_run COMMAND lesson_21_auto_scheduler_run) set_tests_properties(tutorial_lesson_21_auto_scheduler_run PROPERTIES LABELS "tutorial;multithreaded") endif () + +# Lesson 22 +add_tutorial(lesson_22_serialization.cpp) diff --git a/tutorial/lesson_22_serialization.cpp b/tutorial/lesson_22_serialization.cpp new file mode 100644 index 000000000000..fbe0f915bb09 --- /dev/null +++ b/tutorial/lesson_22_serialization.cpp @@ -0,0 +1,169 @@ +// Halide tutorial lesson 22: Serialization + +// This lesson describes how to serialize pipelines into a binary format +// which can be saved on disk, and later deserialized and loaded for +// evaluation. + +// Note that you'll need to be using a build of Halide that was configured +// using the WITH_SERIALIZATION=ON macro defined in order for this tutorial +// to work. + +// On linux, you can compile this tutorial and run it like so: +// g++ lesson_22*.cpp -g -I -L -lHalide -lpthread -ldl -o lesson_22 -std=c++17 +// LD_LIBRARY_PATH= ./lesson_22 + +// On os x: +// g++ lesson_22*.cpp -g -I -L -lHalide -o lesson_22 -std=c++17 +// DYLD_LIBRARY_PATH= ./lesson_22 + +// If you have the entire Halide source tree, you can also build it by +// running: +// make tutorial_lesson_22_serialization +// in a shell with the current directory at the top of the halide +// source tree. + +#include "Halide.h" +#include +#include +using namespace Halide; + +void print_ascii(Buffer result) { + const char code[] = " .:-~*={}&%#@"; + const size_t code_count = sizeof(code) / sizeof(char); + for (int y = 0; y < result.height(); y++) { + for (int x = 0; x < result.width(); x++) { + int value = result(x, y); + if(value < code_count) { + printf("%c", code[value]); + } else { + printf("X"); + } + } + printf("\n"); + } +} + +int main(int argc, char **argv) { + + int width = 64; + int height = 32; + + // Let's create a reasonably complicated Pipeline that computes a Julia Set fractal + { + struct Complex { + Expr real, imag; + + // Construct from a Tuple + Complex(Tuple t) + : real(t[0]), imag(t[1]) { + } + + // Construct from a pair of Exprs + Complex(Expr r, Expr i) + : real(r), imag(i) { + } + + // Construct from a call to a Func by treating it as a Tuple + Complex(FuncRef t) + : Complex(Tuple(t)) { + } + + // Convert to a Tuple + operator Tuple() const { + return {real, imag}; + } + + // Complex addition + Complex operator+(const Complex &other) const { + return {real + other.real, imag + other.imag}; + } + + // Complex multiplication + Complex operator*(const Complex &other) const { + return {real * other.real - imag * other.imag, + real * other.imag + imag * other.real}; + } + + // Complex magnitude, squared for efficiency + Expr magnitude_squared() const { + return real * real + imag * imag; + } + + // Other complex operators would go here. The above are + // sufficient for this example. + }; + + // Let's use the Complex struct to compute a Julia set. + Func julia; + Var x, y; + + // Lets define the coordinate mapping from pixel coordinates to values in the complex plane + Complex extent(2.0f, 2.0f); + Expr scale = max(extent.real / width, extent.imag / height); + Complex position(scale * (x - cast(width) / 2.0f), scale * (y - cast(height) / 2.0f)); + + // Let's center the fractal around a pretty position in the complex plane + Complex initial(-0.79f, 0.15f); + + // Pure definition. + Var t; + julia(x, y, t) = position; + + // We'll use an update definition to take 12 steps. + RDom r(1, 12); + Complex current = julia(x, y, r - 1); + + // The following line uses the complex multiplication and + // addition we defined above. + julia(x, y, r) = current * current + initial; + + // We'll use another tuple reduction to compute the iteration + // number where the value first escapes a circle of radius 2. + // This can be expressed as an argmin of a boolean - we want + // the index of the first time the given boolean expression is + // false (we consider false to be less than true). The argmax + // would return the index of the first time the expression is + // true. + Expr escape_condition = Complex(julia(x, y, r)).magnitude_squared() < 4.0f; + Tuple first_escape = argmin(escape_condition); + + // We only want the index, not the value, but argmin returns + // both, so we'll index the argmin Tuple expression using + // square brackets to get the Expr representing the index. + Func escape; + escape(x, y) = first_escape[0]; + + // Now lets serialize the pipeline to disk (must use the .hlpipe file extension) + std::map params; // params are not used in this example + serialize_pipeline(escape, "julias.hlpipe", params); + } + + // new scope ... everything above is now destroyed! + { + // Lets construct a new pipeline from scratch by deserializing the file we wrote to disk + std::map params; // params are not used in this example + Pipeline deserialized = deserialize_pipeline("julias.hlpipe", params); + + // Now lets realize it ... and print the results as ascii art + Buffer result = deserialized.realize({width, height}); + print_ascii(result); + } + + // new scope ... everything above is now destroyed! + { + // Lets do the same thing again ... construct a new pipeline from scratch by deserializing the file we wrote to disk + std::map params; // params are not used in this example + Pipeline julia = deserialize_pipeline("julias.hlpipe", params); + + // Now, lets serialize it to an in memory buffer ... rather than writing it to disk + std::vector data; + serialize_pipeline(julia, data, params); + + // Now lets deserialize it ... and run it! + Pipeline deserialized = deserialize_pipeline(data, params); + Buffer result = deserialized.realize({width, height}); + } + + printf("Success!\n"); + return 0; +} From a90e94011508bd698925ecfdc5530a30b49ae8b9 Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Mon, 14 Aug 2023 15:51:39 -0700 Subject: [PATCH 3/8] Clang format pass --- src/Deserialization.cpp | 2 +- src/Serialization.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Deserialization.cpp b/src/Deserialization.cpp index 27d5d5f2f8af..57c624dd88ff 100644 --- a/src/Deserialization.cpp +++ b/src/Deserialization.cpp @@ -1300,7 +1300,7 @@ Pipeline Deserializer::deserialize(std::istream &in) { int size = in.tellg(); in.seekg(0, std::ios::beg); std::vector data(size); - in.read((char*)data.data(), size); + in.read((char *)data.data(), size); return deserialize(data); } diff --git a/src/Serialization.cpp b/src/Serialization.cpp index f6e8a52837d0..4a733f349511 100644 --- a/src/Serialization.cpp +++ b/src/Serialization.cpp @@ -1458,7 +1458,7 @@ void Serializer::serialize(const Pipeline &pipeline, std::vector &resul uint8_t *buf = builder.GetBufferPointer(); int size = builder.GetSize(); - if(buf != nullptr && size > 0) { + if (buf != nullptr && size > 0) { result.reserve(size); result.insert(result.begin(), buf, buf + size); } else { From e19c712d080856850cc56e9a6103c017ea56159f Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Tue, 15 Aug 2023 14:23:04 -0700 Subject: [PATCH 4/8] Update doc strings to use Doxygen formatted args --- src/Deserialization.h | 28 ++++++++++++---------------- src/Serialization.h | 16 ++++++++-------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/Deserialization.h b/src/Deserialization.h index d2ce026bfabe..29ef4c0de665 100644 --- a/src/Deserialization.h +++ b/src/Deserialization.h @@ -7,26 +7,22 @@ namespace Halide { -/** - * Deserialize a Halide pipeline from a file. - * filename should always end in .hlpipe suffix. - * external_params is an optional map, all parameters in the map - * will be treated as external parameters so won't be deserialized. - */ +/// @brief Deserialize a Halide pipeline from a file. +/// @param filename The location of the file to deserialize. Must use .hlpipe extension. +/// @param external_params Optional map of named external parameters (used to avoid deserializing specific objects and enable the use of externally defined ones instead). +/// @return Returns a newly constructed deserialized Pipeline object/ Pipeline deserialize_pipeline(const std::string &filename, const std::map &external_params); -/** - * Deserialize a Halide pipeline from an input stream. - * external_params is an optional map, all parameters in the map - * will be treated as external parameters so won't be deserialized. - */ +/// @brief Deserialize a Halide pipeline from an input stream. +/// @param in The input stream to read from containing a serialized Halide pipeline +/// @param external_params Optional map of named external parameters (used to avoid deserializing specific objects and enable the use of externally defined ones instead). +/// @return Returns a newly constructed deserialized Pipeline object/ Pipeline deserialize_pipeline(std::istream &in, const std::map &external_params); -/** - * Deserialize a Halide pipeline from a byte buffer containing a serizalized pipeline in binary format - * external_params is an optional map, all parameters in the map - * will be treated as external parameters so won't be deserialized. - */ +/// @brief Deserialize a Halide pipeline from a byte buffer containing a serizalized pipeline in binary format +/// @param data The data buffer containing a serialized Halide pipeline +/// @param external_params Optional map of named external parameters (used to avoid deserializing specific objects and enable the use of externally defined ones instead). +/// @return Returns a newly constructed deserialized Pipeline object/ Pipeline deserialize_pipeline(const std::vector &data, const std::map &external_params); } // namespace Halide diff --git a/src/Serialization.h b/src/Serialization.h index 1f60f86f68ab..43c102a01bc6 100644 --- a/src/Serialization.h +++ b/src/Serialization.h @@ -5,16 +5,16 @@ namespace Halide { -/** - * Serialize a Halide pipeline into the given data buffer. - * params is an optional map which can be used to bind external parameters to objects in the pipeline by name - */ +/// @brief Serialize a Halide pipeline into the given data buffer. +/// @param pipeline The Halide pipeline to serialize. +/// @param data The data buffer to store the serialized Halide pipeline into. Any existing contents will be destroyed. +/// @param params Optional map of named parameters (used to bind external parameters to objects in the pipeline by name). void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms); -/** - * Serialize a Halide pipeline into the given filename. - * params is an optional map which can be used to bind external parameters to objects in the pipeline by name - */ +/// @brief Serialize a Halide pipeline into the given filename. +/// @param pipeline The Halide pipeline to serialize. +/// @param filename The location of the file to write into to store the serialized pipeline. Any existing contents will be destroyed. +/// @param params Optional map of named parameters (used to bind external parameters to objects in the pipeline by name). void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, std::map ¶ms); } // namespace Halide From a368ee2de4d357745322181cb233038d8a4c5c76 Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Tue, 15 Aug 2023 15:49:25 -0700 Subject: [PATCH 5/8] Clear out data buffer during serialization --- src/Serialization.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Serialization.cpp b/src/Serialization.cpp index 4a733f349511..a5194bb720df 100644 --- a/src/Serialization.cpp +++ b/src/Serialization.cpp @@ -1459,6 +1459,7 @@ void Serializer::serialize(const Pipeline &pipeline, std::vector &resul int size = builder.GetSize(); if (buf != nullptr && size > 0) { + result.clear(); result.reserve(size); result.insert(result.begin(), buf, buf + size); } else { From 723d121130b4a9584ec0d2aaf2b4d4f104ffff77 Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Tue, 15 Aug 2023 15:50:50 -0700 Subject: [PATCH 6/8] Update serialization tutorial to use simple blur example with ImageParam --- src/Deserialization.h | 6 +- src/Serialization.h | 4 +- tutorial/CMakeLists.txt | 2 +- tutorial/lesson_22_serialization.cpp | 190 +++++++++++---------------- 4 files changed, 81 insertions(+), 121 deletions(-) diff --git a/src/Deserialization.h b/src/Deserialization.h index 29ef4c0de665..d4d8f3ea292e 100644 --- a/src/Deserialization.h +++ b/src/Deserialization.h @@ -9,19 +9,19 @@ namespace Halide { /// @brief Deserialize a Halide pipeline from a file. /// @param filename The location of the file to deserialize. Must use .hlpipe extension. -/// @param external_params Optional map of named external parameters (used to avoid deserializing specific objects and enable the use of externally defined ones instead). +/// @param external_params Map of named input/output parameters to bind with the resulting pipeline (used to avoid deserializing specific objects and enable the use of externally defined ones instead). /// @return Returns a newly constructed deserialized Pipeline object/ Pipeline deserialize_pipeline(const std::string &filename, const std::map &external_params); /// @brief Deserialize a Halide pipeline from an input stream. /// @param in The input stream to read from containing a serialized Halide pipeline -/// @param external_params Optional map of named external parameters (used to avoid deserializing specific objects and enable the use of externally defined ones instead). +/// @param external_params Map of named input/output parameters to bind with the resulting pipeline (used to avoid deserializing specific objects and enable the use of externally defined ones instead). /// @return Returns a newly constructed deserialized Pipeline object/ Pipeline deserialize_pipeline(std::istream &in, const std::map &external_params); /// @brief Deserialize a Halide pipeline from a byte buffer containing a serizalized pipeline in binary format /// @param data The data buffer containing a serialized Halide pipeline -/// @param external_params Optional map of named external parameters (used to avoid deserializing specific objects and enable the use of externally defined ones instead). +/// @param external_params Map of named input/output parameters to bind with the resulting pipeline (used to avoid deserializing specific objects and enable the use of externally defined ones instead). /// @return Returns a newly constructed deserialized Pipeline object/ Pipeline deserialize_pipeline(const std::vector &data, const std::map &external_params); diff --git a/src/Serialization.h b/src/Serialization.h index 43c102a01bc6..98b9476302a0 100644 --- a/src/Serialization.h +++ b/src/Serialization.h @@ -8,13 +8,13 @@ namespace Halide { /// @brief Serialize a Halide pipeline into the given data buffer. /// @param pipeline The Halide pipeline to serialize. /// @param data The data buffer to store the serialized Halide pipeline into. Any existing contents will be destroyed. -/// @param params Optional map of named parameters (used to bind external parameters to objects in the pipeline by name). +/// @param params Map of named parameters which will get populated during serialization (can be used to bind external parameters to objects in the pipeline by name). void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms); /// @brief Serialize a Halide pipeline into the given filename. /// @param pipeline The Halide pipeline to serialize. /// @param filename The location of the file to write into to store the serialized pipeline. Any existing contents will be destroyed. -/// @param params Optional map of named parameters (used to bind external parameters to objects in the pipeline by name). +/// @param params Map of named parameters which will get populated during serialization (can be used to bind external parameters to objects in the pipeline by name). void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, std::map ¶ms); } // namespace Halide diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt index 44d81d9bb320..15a1ce884161 100644 --- a/tutorial/CMakeLists.txt +++ b/tutorial/CMakeLists.txt @@ -211,4 +211,4 @@ if (TARGET Halide::Mullapudi2016) endif () # Lesson 22 -add_tutorial(lesson_22_serialization.cpp) +add_tutorial(lesson_22_serialization.cpp WITH_IMAGE_IO) diff --git a/tutorial/lesson_22_serialization.cpp b/tutorial/lesson_22_serialization.cpp index fbe0f915bb09..a3257c18f84e 100644 --- a/tutorial/lesson_22_serialization.cpp +++ b/tutorial/lesson_22_serialization.cpp @@ -8,12 +8,15 @@ // using the WITH_SERIALIZATION=ON macro defined in order for this tutorial // to work. +// Disclaimer: Serialization is experimental in Halide 17 and is subject to +// change; we recommend that you avoid relying on it for production work at this time. + // On linux, you can compile this tutorial and run it like so: -// g++ lesson_22*.cpp -g -I -L -lHalide -lpthread -ldl -o lesson_22 -std=c++17 +// g++ lesson_22*.cpp -g -I -I -L -lHalide -lpthread -ldl -o lesson_22 -std=c++17 // LD_LIBRARY_PATH= ./lesson_22 // On os x: -// g++ lesson_22*.cpp -g -I -L -lHalide -o lesson_22 -std=c++17 +// g++ lesson_22*.cpp -g -I -I -L -lHalide -o lesson_22 -std=c++17 // DYLD_LIBRARY_PATH= ./lesson_22 // If you have the entire Halide source tree, you can also build it by @@ -27,141 +30,98 @@ #include using namespace Halide; -void print_ascii(Buffer result) { - const char code[] = " .:-~*={}&%#@"; - const size_t code_count = sizeof(code) / sizeof(char); - for (int y = 0; y < result.height(); y++) { - for (int x = 0; x < result.width(); x++) { - int value = result(x, y); - if(value < code_count) { - printf("%c", code[value]); - } else { - printf("X"); - } - } - printf("\n"); - } -} +// Support code for loading pngs. +#include "halide_image_io.h" int main(int argc, char **argv) { - int width = 64; - int height = 32; + // First we'll declare some Vars to use below. + Var x("x"), y("y"), c("c"); - // Let's create a reasonably complicated Pipeline that computes a Julia Set fractal + // Let's start with the same separable blur pipeline that we used in Tutorial 7, + // with the clamped boundary condition { - struct Complex { - Expr real, imag; - - // Construct from a Tuple - Complex(Tuple t) - : real(t[0]), imag(t[1]) { - } - - // Construct from a pair of Exprs - Complex(Expr r, Expr i) - : real(r), imag(i) { - } - - // Construct from a call to a Func by treating it as a Tuple - Complex(FuncRef t) - : Complex(Tuple(t)) { - } - - // Convert to a Tuple - operator Tuple() const { - return {real, imag}; - } - - // Complex addition - Complex operator+(const Complex &other) const { - return {real + other.real, imag + other.imag}; - } - - // Complex multiplication - Complex operator*(const Complex &other) const { - return {real * other.real - imag * other.imag, - real * other.imag + imag * other.real}; - } - - // Complex magnitude, squared for efficiency - Expr magnitude_squared() const { - return real * real + imag * imag; - } - - // Other complex operators would go here. The above are - // sufficient for this example. - }; - - // Let's use the Complex struct to compute a Julia set. - Func julia; - Var x, y; - - // Lets define the coordinate mapping from pixel coordinates to values in the complex plane - Complex extent(2.0f, 2.0f); - Expr scale = max(extent.real / width, extent.imag / height); - Complex position(scale * (x - cast(width) / 2.0f), scale * (y - cast(height) / 2.0f)); - - // Let's center the fractal around a pretty position in the complex plane - Complex initial(-0.79f, 0.15f); - - // Pure definition. - Var t; - julia(x, y, t) = position; - - // We'll use an update definition to take 12 steps. - RDom r(1, 12); - Complex current = julia(x, y, r - 1); - - // The following line uses the complex multiplication and - // addition we defined above. - julia(x, y, r) = current * current + initial; - - // We'll use another tuple reduction to compute the iteration - // number where the value first escapes a circle of radius 2. - // This can be expressed as an argmin of a boolean - we want - // the index of the first time the given boolean expression is - // false (we consider false to be less than true). The argmax - // would return the index of the first time the expression is - // true. - Expr escape_condition = Complex(julia(x, y, r)).magnitude_squared() < 4.0f; - Tuple first_escape = argmin(escape_condition); - - // We only want the index, not the value, but argmin returns - // both, so we'll index the argmin Tuple expression using - // square brackets to get the Expr representing the index. - Func escape; - escape(x, y) = first_escape[0]; + // Let's create an ImageParam for an 8-bit RGB image that we'll use for input. + ImageParam input(UInt(8), 3, "input"); + + // Wrap the input in a Func that prevents reading out of bounds: + Func clamped("clamped"); + Expr clamped_x = clamp(x, 0, input.width() - 1); + Expr clamped_y = clamp(y, 0, input.height() - 1); + clamped(x, y, c) = input(clamped_x, clamped_y, c); + + // Upgrade it to 16-bit, so we can do math without it overflowing. + Func input_16("input_16"); + input_16(x, y, c) = cast(clamped(x, y, c)); + + // Blur it horizontally: + Func blur_x("blur_x"); + blur_x(x, y, c) = (input_16(x - 1, y, c) + + 2 * input_16(x, y, c) + + input_16(x + 1, y, c)) / 4; + + // Blur it vertically: + Func blur_y("blur_y"); + blur_y(x, y, c) = (blur_x(x, y - 1, c) + + 2 * blur_x(x, y, c) + + blur_x(x, y + 1, c)) / 4; + + // Convert back to 8-bit. + Func output("output"); + output(x, y, c) = cast(blur_y(x, y, c)); // Now lets serialize the pipeline to disk (must use the .hlpipe file extension) - std::map params; // params are not used in this example - serialize_pipeline(escape, "julias.hlpipe", params); + Pipeline blur_pipeline(output); + std::map params; + serialize_pipeline(blur_pipeline, "blur.hlpipe", params); + + // The call to serialize_pipeline populates the params map with any input or output parameters + // that were found ... object's we'll need to attach to buffers if we wish to execute the pipeline + for(auto named_param: params) { + std::cout << "Found Param: " << named_param.first << std::endl; + } } - // new scope ... everything above is now destroyed! + // new scope ... everything above is now destroyed! Now lets reconstruct the entire pipeline + // from scratch by deserializing it from a file { + // Lets load a color 8-bit input and connect it to an ImageParam + Buffer rgb_image = Halide::Tools::load_image("images/rgb.png"); + ImageParam input(UInt(8), 3, "input"); + input.set(rgb_image); + + // Now lets populate the params map so we can override the input + std::map params; + params.insert({"input", input.parameter()}); + // Lets construct a new pipeline from scratch by deserializing the file we wrote to disk - std::map params; // params are not used in this example - Pipeline deserialized = deserialize_pipeline("julias.hlpipe", params); + Pipeline blur_pipeline = deserialize_pipeline("blur.hlpipe", params); + + // Now realize the pipeline and blur out input image + Buffer result = blur_pipeline.realize({rgb_image.width(), rgb_image.height(), 3}); - // Now lets realize it ... and print the results as ascii art - Buffer result = deserialized.realize({width, height}); - print_ascii(result); + // Now lets save the result ... we should have another blurry parrot! + Halide::Tools::save_image(result, "another_blurry_parrot.png"); } // new scope ... everything above is now destroyed! { // Lets do the same thing again ... construct a new pipeline from scratch by deserializing the file we wrote to disk - std::map params; // params are not used in this example - Pipeline julia = deserialize_pipeline("julias.hlpipe", params); + + // FIXME: We shouldn't have to populate the params ... but passing an empty map triggers an error in deserialize? + std::map params; + ImageParam input(UInt(8), 3, "input"); + params.insert({"input", input.parameter()}); + + // Now deserialize the pipeline from file + Pipeline blur_pipeline = deserialize_pipeline("blur.hlpipe", params); // Now, lets serialize it to an in memory buffer ... rather than writing it to disk std::vector data; - serialize_pipeline(julia, data, params); + serialize_pipeline(blur_pipeline, data, params); - // Now lets deserialize it ... and run it! + // Now lets deserialize it from memory Pipeline deserialized = deserialize_pipeline(data, params); - Buffer result = deserialized.realize({width, height}); } printf("Success!\n"); From 33c829a64130a192d82b2e471fa0941460b13ee4 Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Fri, 22 Sep 2023 15:13:36 -0700 Subject: [PATCH 7/8] Make parameter map optional for serialize #7849 Add error messages to deserializer for missing params Update tutorial --- src/Deserialization.cpp | 14 ++++++++++++++ src/Serialization.cpp | 18 ++++++++++++++++++ src/Serialization.h | 11 +++++++++++ tutorial/lesson_23_serialization.cpp | 3 ++- 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/Deserialization.cpp b/src/Deserialization.cpp index ba4b26c4434c..7f2e1956aefe 100644 --- a/src/Deserialization.cpp +++ b/src/Deserialization.cpp @@ -450,6 +450,8 @@ void Deserializer::deserialize_function(const Serialize::Func *function, Functio output_buffer = it->second; } else if (auto it = parameters_in_pipeline.find(output_buffer_name); it != parameters_in_pipeline.end()) { output_buffer = it->second; + } else if (!output_buffer_name.empty()){ + user_error << "unknown output buffer used in pipeline '" << output_buffer_name << "'\n"; } output_buffers.push_back(output_buffer); } @@ -519,6 +521,8 @@ Stmt Deserializer::deserialize_stmt(Serialize::Stmt type_code, const void *stmt) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; + } else if (!param_name.empty()){ + user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } const auto alignment = deserialize_modulus_remainder(store_stmt->alignment()); return Store::make(name, value, index, param, predicate, alignment); @@ -776,6 +780,8 @@ Expr Deserializer::deserialize_expr(Serialize::Expr type_code, const void *expr) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; + } else if (!param_name.empty()){ + user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } const auto alignment = deserialize_modulus_remainder(load_expr->alignment()); const auto type = deserialize_type(load_expr->type()); @@ -825,6 +831,8 @@ Expr Deserializer::deserialize_expr(Serialize::Expr type_code, const void *expr) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; + } else if (!param_name.empty()){ + user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } const auto type = deserialize_type(call_expr->type()); return Call::make(type, name, args, call_type, func_ptr, value_index, image, param); @@ -839,6 +847,8 @@ Expr Deserializer::deserialize_expr(Serialize::Expr type_code, const void *expr) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; + } else if (!param_name.empty()){ + user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } auto image_name = deserialize_string(variable_expr->image_name()); Buffer<> image; @@ -1036,6 +1046,8 @@ PrefetchDirective Deserializer::deserialize_prefetch_directive(const Serialize:: Parameter param; if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; + } else if (!param_name.empty()){ + user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } auto hl_prefetch_directive = PrefetchDirective(); hl_prefetch_directive.name = name; @@ -1204,6 +1216,8 @@ ExternFuncArgument Deserializer::deserialize_extern_func_argument(const Serializ image_param = it->second; } else if (auto it = parameters_in_pipeline.find(image_param_name); it != parameters_in_pipeline.end()) { image_param = it->second; + } else if (!image_param_name.empty()){ + user_error << "unknown image parameter used in pipeline '" << image_param_name << "'\n"; } return ExternFuncArgument(image_param); } diff --git a/src/Serialization.cpp b/src/Serialization.cpp index e2444d56e5fd..1ccc55698915 100644 --- a/src/Serialization.cpp +++ b/src/Serialization.cpp @@ -1481,12 +1481,22 @@ void Serializer::serialize(const Pipeline &pipeline, const std::string &filename } // namespace Internal +void serialize_pipeline(const Pipeline &pipeline, std::vector &data) { + Internal::Serializer serializer; + serializer.serialize(pipeline, data); +} + void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms) { Internal::Serializer serializer; serializer.serialize(pipeline, data); params = serializer.get_external_parameters(); } +void serialize_pipeline(const Pipeline &pipeline, const std::string &filename) { + Internal::Serializer serializer; + serializer.serialize(pipeline, filename); +} + void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, std::map ¶ms) { Internal::Serializer serializer; serializer.serialize(pipeline, filename); @@ -1499,10 +1509,18 @@ void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, s namespace Halide { +void serialize_pipeline(const Pipeline &pipeline, std::vector &data) { + user_error << "Serialization is not supported in this build of Halide; try rebuilding with WITH_SERIALIZATION=ON."; +} + void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms) { user_error << "Serialization is not supported in this build of Halide; try rebuilding with WITH_SERIALIZATION=ON."; } +void serialize_pipeline(const Pipeline &pipeline, const std::string &filename) { + user_error << "Serialization is not supported in this build of Halide; try rebuilding with WITH_SERIALIZATION=ON."; +} + void serialize_pipeline(const Pipeline &pipeline, const std::string &filename, std::map ¶ms) { user_error << "Serialization is not supported in this build of Halide; try rebuilding with WITH_SERIALIZATION=ON."; } diff --git a/src/Serialization.h b/src/Serialization.h index f07e217415cb..9eb7f71c33cc 100644 --- a/src/Serialization.h +++ b/src/Serialization.h @@ -5,12 +5,23 @@ namespace Halide { +/// @brief Serialize a Halide pipeline into the given data buffer. +/// @param pipeline The Halide pipeline to serialize. +/// @param data The data buffer to store the serialized Halide pipeline into. Any existing contents will be destroyed. +/// @param params Map of named parameters which will get populated during serialization (can be used to bind external parameters to objects in the pipeline by name). +void serialize_pipeline(const Pipeline &pipeline, std::vector &data); + /// @brief Serialize a Halide pipeline into the given data buffer. /// @param pipeline The Halide pipeline to serialize. /// @param data The data buffer to store the serialized Halide pipeline into. Any existing contents will be destroyed. /// @param params Map of named parameters which will get populated during serialization (can be used to bind external parameters to objects in the pipeline by name). void serialize_pipeline(const Pipeline &pipeline, std::vector &data, std::map ¶ms); +/// @brief Serialize a Halide pipeline into the given filename. +/// @param pipeline The Halide pipeline to serialize. +/// @param filename The location of the file to write into to store the serialized pipeline. Any existing contents will be destroyed. +void serialize_pipeline(const Pipeline &pipeline, const std::string &filename); + /// @brief Serialize a Halide pipeline into the given filename. /// @param pipeline The Halide pipeline to serialize. /// @param filename The location of the file to write into to store the serialized pipeline. Any existing contents will be destroyed. diff --git a/tutorial/lesson_23_serialization.cpp b/tutorial/lesson_23_serialization.cpp index 70a314449ed5..a01de5f916fd 100644 --- a/tutorial/lesson_23_serialization.cpp +++ b/tutorial/lesson_23_serialization.cpp @@ -108,7 +108,8 @@ int main(int argc, char **argv) { { // Lets do the same thing again ... construct a new pipeline from scratch by deserializing the file we wrote to disk - // FIXME: We shouldn't have to populate the params ... but passing an empty map triggers an error in deserialize? + // FIXME: We shouldn't have to populate the params ... but passing an empty map triggers an error in deserialize + // for a missing input param std::map params; ImageParam input(UInt(8), 3, "input"); params.insert({"input", input.parameter()}); From 915b1e6bee07776f52185c2909728d1176962749 Mon Sep 17 00:00:00 2001 From: Derek Gerstmann Date: Tue, 26 Sep 2023 13:48:11 -0700 Subject: [PATCH 8/8] Clang format pass --- src/Deserialization.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Deserialization.cpp b/src/Deserialization.cpp index 7f2e1956aefe..71eca2d269e6 100644 --- a/src/Deserialization.cpp +++ b/src/Deserialization.cpp @@ -450,7 +450,7 @@ void Deserializer::deserialize_function(const Serialize::Func *function, Functio output_buffer = it->second; } else if (auto it = parameters_in_pipeline.find(output_buffer_name); it != parameters_in_pipeline.end()) { output_buffer = it->second; - } else if (!output_buffer_name.empty()){ + } else if (!output_buffer_name.empty()) { user_error << "unknown output buffer used in pipeline '" << output_buffer_name << "'\n"; } output_buffers.push_back(output_buffer); @@ -521,7 +521,7 @@ Stmt Deserializer::deserialize_stmt(Serialize::Stmt type_code, const void *stmt) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; - } else if (!param_name.empty()){ + } else if (!param_name.empty()) { user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } const auto alignment = deserialize_modulus_remainder(store_stmt->alignment()); @@ -780,7 +780,7 @@ Expr Deserializer::deserialize_expr(Serialize::Expr type_code, const void *expr) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; - } else if (!param_name.empty()){ + } else if (!param_name.empty()) { user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } const auto alignment = deserialize_modulus_remainder(load_expr->alignment()); @@ -831,7 +831,7 @@ Expr Deserializer::deserialize_expr(Serialize::Expr type_code, const void *expr) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; - } else if (!param_name.empty()){ + } else if (!param_name.empty()) { user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } const auto type = deserialize_type(call_expr->type()); @@ -847,7 +847,7 @@ Expr Deserializer::deserialize_expr(Serialize::Expr type_code, const void *expr) param = it->second; } else if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; - } else if (!param_name.empty()){ + } else if (!param_name.empty()) { user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } auto image_name = deserialize_string(variable_expr->image_name()); @@ -1046,7 +1046,7 @@ PrefetchDirective Deserializer::deserialize_prefetch_directive(const Serialize:: Parameter param; if (auto it = parameters_in_pipeline.find(param_name); it != parameters_in_pipeline.end()) { param = it->second; - } else if (!param_name.empty()){ + } else if (!param_name.empty()) { user_error << "unknown parameter used in pipeline '" << param_name << "'\n"; } auto hl_prefetch_directive = PrefetchDirective(); @@ -1216,7 +1216,7 @@ ExternFuncArgument Deserializer::deserialize_extern_func_argument(const Serializ image_param = it->second; } else if (auto it = parameters_in_pipeline.find(image_param_name); it != parameters_in_pipeline.end()) { image_param = it->second; - } else if (!image_param_name.empty()){ + } else if (!image_param_name.empty()) { user_error << "unknown image parameter used in pipeline '" << image_param_name << "'\n"; } return ExternFuncArgument(image_param);