From 0ce0ff98848adb9a938931ccb052a01acb428aa7 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Fri, 8 Nov 2024 17:28:04 +0100 Subject: [PATCH 01/12] Fixed passing of generation config params to VLM generate. --- src/python/py_utils.cpp | 79 ++++++++++++++++++++++++++++++++++ src/python/py_utils.hpp | 2 + src/python/py_vlm_pipeline.cpp | 5 ++- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index a2e8630059..f3b631a8b9 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -242,6 +242,14 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O } if (key == "max_new_tokens") { res_config.max_new_tokens = py::cast(item.second); + } else if (key == "min_new_tokens") { + res_config.min_new_tokens = py::cast(value); + } else if (key == "stop_strings") { + res_config.stop_strings = py::cast>(value); + } else if (key == "include_stop_str_in_output") { + res_config.include_stop_str_in_output = py::cast(value); + } else if (key == "include_stop_str_in_output") { + res_config.stop_token_ids = py::cast>(value); } else if (key == "max_length") { res_config.max_length = py::cast(item.second); } else if (key == "ignore_eos") { @@ -270,6 +278,16 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O res_config.do_sample = py::cast(item.second); } else if (key == "repetition_penalty") { res_config.repetition_penalty = py::cast(item.second); + } else if (key == "presence_penalty") { + res_config.presence_penalty = py::cast(value); + } else if (key == "frequency_penalty") { + res_config.frequency_penalty = py::cast(value); + } else if (key == "rng_seed") { + res_config.rng_seed = py::cast(value); + } else if (key == "assistant_confidence_threshold") { + res_config.assistant_confidence_threshold = py::cast(value); + } else if (key == "num_assistant_tokens") { + res_config.num_assistant_tokens = py::cast(value); } else if (key == "eos_token_id") { res_config.set_eos_token_id(py::cast(item.second)); } else if (key == "adapters") { @@ -283,4 +301,65 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O return res_config; } + +bool generation_config_param_to_property(std::string key, py::object value, ov::AnyMap& map) { + if (key == "max_new_tokens") { + map.insert(ov::genai::max_new_tokens(py::cast(value))); + } else if (key == "max_length") { + map.insert(ov::genai::max_length(py::cast(value))); + } else if (key == "ignore_eos") { + map.insert(ov::genai::ignore_eos(py::cast(value))); + } else if (key == "min_new_tokens") { + map.insert(ov::genai::min_new_tokens(py::cast(value))); + } else if (key == "stop_strings") { + map.insert(ov::genai::stop_strings(py::cast>(value))); + } else if (key == "include_stop_str_in_output") { + map.insert(ov::genai::include_stop_str_in_output(py::cast(value))); + } else if (key == "include_stop_str_in_output") { + map.insert(ov::genai::stop_token_ids(py::cast>>(value))); + } else if (key == "num_beam_groups") { + map.insert(ov::genai::num_beam_groups(py::cast(value))); + } else if (key == "num_beams") { + map.insert(ov::genai::num_beams(py::cast(value))); + } else if (key == "diversity_penalty") { + map.insert(ov::genai::diversity_penalty(py::cast(value))); + } else if (key == "length_penalty") { + map.insert(ov::genai::length_penalty(py::cast(value))); + } else if (key == "num_return_sequences") { + map.insert(ov::genai::num_return_sequences(py::cast(value))); + } else if (key == "no_repeat_ngram_size") { + map.insert(ov::genai::no_repeat_ngram_size(py::cast(value))); + } else if (key == "stop_criteria") { + map.insert(ov::genai::stop_criteria(py::cast(value))); + } else if (key == "temperature") { + map.insert(ov::genai::temperature(py::cast(value))); + } else if (key == "top_p") { + map.insert(ov::genai::top_p(py::cast(value))); + } else if (key == "top_k") { + map.insert(ov::genai::top_k(py::cast(value))); + } else if (key == "do_sample") { + map.insert(ov::genai::do_sample(py::cast(value))); + } else if (key == "repetition_penalty") { + map.insert(ov::genai::repetition_penalty(py::cast(value))); + } else if (key == "presence_penalty") { + map.insert(ov::genai::presence_penalty(py::cast(value))); + } else if (key == "frequency_penalty") { + map.insert(ov::genai::frequency_penalty(py::cast(value))); + } else if (key == "rng_seed") { + map.insert(ov::genai::rng_seed(py::cast(value))); + } else if (key == "eos_token_id") { + map.insert(ov::genai::eos_token_id(py::cast(value))); + } else if (key == "assistant_confidence_threshold") { + map.insert(ov::genai::assistant_confidence_threshold(py::cast(value))); + } else if (key == "num_assistant_tokens") { + map.insert(ov::genai::num_assistant_tokens(py::cast(value))); + } else if (key == "adapters") { + map.insert(ov::genai::adapters(py::cast(value))); + } else { + return false; + } + return true; +} + + } // namespace ov::genai::pybind::utils diff --git a/src/python/py_utils.hpp b/src/python/py_utils.hpp index 2464904ca7..a16bc7eaf9 100644 --- a/src/python/py_utils.hpp +++ b/src/python/py_utils.hpp @@ -44,4 +44,6 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O ov::genai::StreamerVariant pystreamer_to_streamer(const PyBindStreamerVariant& py_streamer); +bool generation_config_param_to_property(std::string key, py::object value, ov::AnyMap& map); + } // namespace ov::genai::pybind::utils diff --git a/src/python/py_vlm_pipeline.cpp b/src/python/py_vlm_pipeline.cpp index 49be2567a9..057d9136eb 100644 --- a/src/python/py_vlm_pipeline.cpp +++ b/src/python/py_vlm_pipeline.cpp @@ -88,8 +88,9 @@ ov::AnyMap vlm_kwargs_to_any_map(const py::kwargs& kwargs, bool allow_compile_pr } else if (key == "streamer") { auto py_streamer = py::cast(value); params.insert({ov::genai::streamer(std::move(pyutils::pystreamer_to_streamer(py_streamer)))}); - - } + } else if (pyutils::generation_config_param_to_property(key, value, params)) { + continue; + } else { if (allow_compile_properties) { // convert arbitrary objects to ov::Any From 104281e95b017e6198b5f2d75b7826f8e874b3dc Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Mon, 11 Nov 2024 11:19:46 +0100 Subject: [PATCH 02/12] Code corrections. --- src/python/py_utils.cpp | 58 ++-------------------------------- src/python/py_vlm_pipeline.cpp | 32 ++++++++++--------- 2 files changed, 20 insertions(+), 70 deletions(-) diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index f3b631a8b9..39d3f4440f 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -227,7 +227,7 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O ov::genai::GenerationConfig res_config; if(config.has_value()) res_config = *config; - + ov::AnyMap map; for (const auto& item : kwargs) { std::string key = py::cast(item.first); py::object value = py::cast(item.second); @@ -240,64 +240,12 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O // us from reading such configs, e.g. {"typical_p": None, 'top_p': 1.0,...} return res_config; } - if (key == "max_new_tokens") { - res_config.max_new_tokens = py::cast(item.second); - } else if (key == "min_new_tokens") { - res_config.min_new_tokens = py::cast(value); - } else if (key == "stop_strings") { - res_config.stop_strings = py::cast>(value); - } else if (key == "include_stop_str_in_output") { - res_config.include_stop_str_in_output = py::cast(value); - } else if (key == "include_stop_str_in_output") { - res_config.stop_token_ids = py::cast>(value); - } else if (key == "max_length") { - res_config.max_length = py::cast(item.second); - } else if (key == "ignore_eos") { - res_config.ignore_eos = py::cast(item.second); - } else if (key == "num_beam_groups") { - res_config.num_beam_groups = py::cast(item.second); - } else if (key == "num_beams") { - res_config.num_beams = py::cast(item.second); - } else if (key == "diversity_penalty") { - res_config.diversity_penalty = py::cast(item.second); - } else if (key == "length_penalty") { - res_config.length_penalty = py::cast(item.second); - } else if (key == "num_return_sequences") { - res_config.num_return_sequences = py::cast(item.second); - } else if (key == "no_repeat_ngram_size") { - res_config.no_repeat_ngram_size = py::cast(item.second); - } else if (key == "stop_criteria") { - res_config.stop_criteria = py::cast(item.second); - } else if (key == "temperature") { - res_config.temperature = py::cast(item.second); - } else if (key == "top_p") { - res_config.top_p = py::cast(item.second); - } else if (key == "top_k") { - res_config.top_k = py::cast(item.second); - } else if (key == "do_sample") { - res_config.do_sample = py::cast(item.second); - } else if (key == "repetition_penalty") { - res_config.repetition_penalty = py::cast(item.second); - } else if (key == "presence_penalty") { - res_config.presence_penalty = py::cast(value); - } else if (key == "frequency_penalty") { - res_config.frequency_penalty = py::cast(value); - } else if (key == "rng_seed") { - res_config.rng_seed = py::cast(value); - } else if (key == "assistant_confidence_threshold") { - res_config.assistant_confidence_threshold = py::cast(value); - } else if (key == "num_assistant_tokens") { - res_config.num_assistant_tokens = py::cast(value); - } else if (key == "eos_token_id") { - res_config.set_eos_token_id(py::cast(item.second)); - } else if (key == "adapters") { - res_config.adapters = py::cast(item.second); - } else { + if (!generation_config_param_to_property(key, value, map)) { throw(std::invalid_argument("'" + key + "' is incorrect GenerationConfig parameter name. " "Use help(openvino_genai.GenerationConfig) to get list of acceptable parameters.")); } } - + res_config.update_generation_config(map); return res_config; } diff --git a/src/python/py_vlm_pipeline.cpp b/src/python/py_vlm_pipeline.cpp index 057d9136eb..21b1745d91 100644 --- a/src/python/py_vlm_pipeline.cpp +++ b/src/python/py_vlm_pipeline.cpp @@ -72,27 +72,18 @@ py::object call_vlm_generate( return py::cast(pipe.generate(prompt, images, updated_config, streamer)); } -ov::AnyMap vlm_kwargs_to_any_map(const py::kwargs& kwargs, bool allow_compile_properties=true) { +ov::AnyMap vlm_kwargs_to_any_map(const py::kwargs& kwargs, bool called_from_init=true) { ov::AnyMap params = {}; for (const auto& item : kwargs) { std::string key = py::cast(item.first); py::object value = py::cast(item.second); - if (key == "images") { - params.insert({ov::genai::images(std::move(py::cast>(value)))}); - } else if (key == "image") { - params.insert({ov::genai::image(std::move(py::cast(value)))}); - } else if (key == "generation_config") { - params.insert({ov::genai::generation_config(std::move(py::cast(value)))}); - } else if (key == "streamer") { - auto py_streamer = py::cast(value); - params.insert({ov::genai::streamer(std::move(pyutils::pystreamer_to_streamer(py_streamer)))}); - } else if (pyutils::generation_config_param_to_property(key, value, params)) { + if (pyutils::generation_config_param_to_property(key, value, params)) { continue; } else { - if (allow_compile_properties) { + if (called_from_init) { // convert arbitrary objects to ov::Any // not supported properties are not checked, as these properties are passed to compile(), which will throw exception in case of unsupported property if (pyutils::py_object_is_any_map(value)) { @@ -103,9 +94,20 @@ ov::AnyMap vlm_kwargs_to_any_map(const py::kwargs& kwargs, bool allow_compile_pr } } else { - // generate doesn't run compile(), so only VLMPipeline specific properties are allowed - throw(std::invalid_argument("'" + key + "' is unexpected parameter name. " - "Use help(openvino_genai.VLMPipeline.generate) to get list of acceptable parameters.")); + if (key == "images") { + params.insert({ov::genai::images(std::move(py::cast>(value)))}); + } else if (key == "image") { + params.insert({ov::genai::image(std::move(py::cast(value)))}); + } else if (key == "generation_config") { + params.insert({ov::genai::generation_config(std::move(py::cast(value)))}); + } else if (key == "streamer") { + auto py_streamer = py::cast(value); + params.insert({ov::genai::streamer(std::move(pyutils::pystreamer_to_streamer(py_streamer)))}); + } else { + // generate doesn't run compile(), so only VLMPipeline specific properties are allowed + throw(std::invalid_argument("'" + key + "' is unexpected parameter name. " + "Use help(openvino_genai.VLMPipeline.generate) to get list of acceptable parameters.")); + } } } } From 383b66115f24cb3dfc5044de06d640eab3815b24 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Sat, 16 Nov 2024 13:42:32 +0100 Subject: [PATCH 03/12] Moved properties processing to common place. --- .../openvino/genai/generation_config.hpp | 2 +- src/python/py_image_generation_pipelines.cpp | 104 +-------- src/python/py_utils.cpp | 213 ++++++++++-------- src/python/py_utils.hpp | 4 +- src/python/py_vlm_pipeline.cpp | 47 +--- src/python/py_whisper_pipeline.cpp | 55 +---- 6 files changed, 132 insertions(+), 293 deletions(-) diff --git a/src/cpp/include/openvino/genai/generation_config.hpp b/src/cpp/include/openvino/genai/generation_config.hpp index 22edcb98c0..8d23b298ba 100644 --- a/src/cpp/include/openvino/genai/generation_config.hpp +++ b/src/cpp/include/openvino/genai/generation_config.hpp @@ -156,7 +156,7 @@ static constexpr ov::Property ignore_eos{"ignore_eos"}; static constexpr ov::Property min_new_tokens{"min_new_tokens"}; static constexpr ov::Property> stop_strings{"stop_strings"}; static constexpr ov::Property include_stop_str_in_output{"include_stop_str_in_output"}; -static constexpr ov::Property>> stop_token_ids{"stop_token_ids"}; +static constexpr ov::Property> stop_token_ids{"stop_token_ids"}; static constexpr ov::Property num_beam_groups{"num_beam_groups"}; static constexpr ov::Property num_beams{"num_beams"}; diff --git a/src/python/py_image_generation_pipelines.cpp b/src/python/py_image_generation_pipelines.cpp index d99f91ed0a..907fc7405a 100644 --- a/src/python/py_image_generation_pipelines.cpp +++ b/src/python/py_image_generation_pipelines.cpp @@ -65,104 +65,6 @@ auto text2image_generate_docstring = R"( :rtype: ov.Tensor )"; - -void update_image_generation_config_from_kwargs( - ov::genai::ImageGenerationConfig& config, - const py::kwargs& kwargs) { - for (const auto& item : kwargs) { - std::string key = py::cast(item.first); - py::object value = py::cast(item.second); - - if (key == "prompt_2") { - config.prompt_2 = py::cast(value); - } else if (key == "prompt_3") { - config.prompt_3 = py::cast(value); - } else if (key == "negative_prompt") { - config.negative_prompt = py::cast(value); - } else if (key == "negative_prompt_2") { - config.negative_prompt_2 = py::cast(value); - } else if (key == "negative_prompt_3") { - config.negative_prompt_3 = py::cast(value); - } else if (key == "num_images_per_prompt") { - config.num_images_per_prompt = py::cast(value); - } else if (key == "guidance_scale") { - config.guidance_scale = py::cast(value); - } else if (key == "height") { - config.height = py::cast(value); - } else if (key == "width") { - config.width = py::cast(value); - } else if (key == "num_inference_steps") { - config.num_inference_steps = py::cast(value); - } else if (key == "generator") { - auto py_generator = py::cast>(value); - config.generator = py_generator; - } else if (key == "adapters") { - config.adapters = py::cast(value); - } else if (key == "strength") { - config.strength = py::cast(value); - } else { - throw(std::invalid_argument("'" + key + "' is unexpected parameter name. " - "Use help(openvino_genai.ImageGenerationConfig) to get list of acceptable parameters.")); - } - } -} - -ov::AnyMap text2image_kwargs_to_any_map(const py::kwargs& kwargs, bool allow_compile_properties=true) { - ov::AnyMap params = {}; - - for (const auto& item : kwargs) { - std::string key = py::cast(item.first); - py::object value = py::cast(item.second); - - if (key == "prompt_2") { - params.insert({ov::genai::prompt_2(std::move(py::cast(value)))}); - } else if (key == "prompt_3") { - params.insert({ov::genai::prompt_3(std::move(py::cast(value)))}); - } else if (key == "negative_prompt") { - params.insert({ov::genai::negative_prompt(std::move(py::cast(value)))}); - } else if (key == "negative_prompt_2") { - params.insert({ov::genai::negative_prompt_2(std::move(py::cast(value)))}); - } else if (key == "negative_prompt_3") { - params.insert({ov::genai::negative_prompt_3(std::move(py::cast(value)))}); - } else if (key == "num_images_per_prompt") { - params.insert({ov::genai::num_images_per_prompt(std::move(py::cast(value)))}); - } else if (key == "guidance_scale") { - params.insert({ov::genai::guidance_scale(std::move(py::cast(value)))}); - } else if (key == "height") { - params.insert({ov::genai::height(std::move(py::cast(value)))}); - } else if (key == "width") { - params.insert({ov::genai::width(std::move(py::cast(value)))}); - } else if (key == "num_inference_steps") { - params.insert({ov::genai::num_inference_steps(std::move(py::cast(value)))}); - } else if (key == "generator") { - auto py_generator =py::cast>(value); - params.insert({ov::genai::generator(std::move(py_generator))}); - } else if (key == "adapters") { - params.insert({ov::genai::adapters(std::move(py::cast(value)))}); - } else if (key == "strength") { - params.insert({ov::genai::strength(std::move(py::cast(value)))}); - } - else { - if (allow_compile_properties) { - // convert arbitrary objects to ov::Any - // not supported properties are not checked, as these properties are passed to compile(), which will throw exception in case of unsupported property - if (pyutils::py_object_is_any_map(value)) { - auto map = pyutils::py_object_to_any_map(value); - params.insert(map.begin(), map.end()); - } else { - params[key] = pyutils::py_object_to_any(value); - } - } - else { - // generate doesn't run compile(), so only Text2ImagePipeline specific properties are allowed - throw(std::invalid_argument("'" + key + "' is unexpected parameter name. " - "Use help(openvino_genai.Text2ImagePipeline.generate) to get list of acceptable parameters.")); - } - } - } - return params; -} - } // namespace void init_clip_text_model(py::module_& m); @@ -222,7 +124,7 @@ void init_image_generation_pipelines(py::module_& m) { .def("update_generation_config", []( ov::genai::ImageGenerationConfig config, const py::kwargs& kwargs) { - update_image_generation_config_from_kwargs(config, kwargs); + config.update_generation_config(pyutils::kwargs_to_any_map(kwargs)); }); auto text2image_pipeline = py::class_(m, "Text2ImagePipeline", "This class is used for generation with text-to-image models.") @@ -244,7 +146,7 @@ void init_image_generation_pipelines(py::module_& m) { const py::kwargs& kwargs ) { ScopedVar env_manager(pyutils::ov_tokenizers_module_path()); - return std::make_unique(models_path, device, text2image_kwargs_to_any_map(kwargs, true)); + return std::make_unique(models_path, device, pyutils::kwargs_to_any_map(kwargs)); }), py::arg("models_path"), "folder with exported model files.", py::arg("device"), "device on which inference will be done", @@ -281,7 +183,7 @@ void init_image_generation_pipelines(py::module_& m) { const std::string& prompt, const py::kwargs& kwargs ) -> py::typing::Union { - ov::AnyMap params = text2image_kwargs_to_any_map(kwargs, false); + ov::AnyMap params = pyutils::kwargs_to_any_map(kwargs); return py::cast(pipe.generate(prompt, params)); }, py::arg("prompt"), "Input string", diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index 39d3f4440f..5276f23e04 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -11,6 +11,8 @@ #include "tokenizers_path.hpp" #include "openvino/genai/llm_pipeline.hpp" +#include "openvino/genai/visual_language/pipeline.hpp" +#include "openvino/genai/image_generation/text2image_pipeline.hpp" namespace py = pybind11; namespace ov::genai::pybind::utils { @@ -43,7 +45,7 @@ bool py_object_is_any_map(const py::object& py_obj) { }); } -ov::Any py_object_to_any(const py::object& py_obj); +ov::Any py_object_to_any(const py::object& py_obj, std::string property_name); ov::AnyMap py_object_to_any_map(const py::object& py_obj) { OPENVINO_ASSERT(py_object_is_any_map(py_obj), "Unsupported attribute type."); @@ -54,16 +56,33 @@ ov::AnyMap py_object_to_any_map(const py::object& py_obj) { if (py_object_is_any_map(value)) { return_value[key] = py_object_to_any_map(value); } else { - return_value[key] = py_object_to_any(value); + return_value[key] = py_object_to_any(value, key); } } return return_value; } -ov::Any py_object_to_any(const py::object& py_obj) { +ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { // Python types + // TODO: Remove this after ov::Any is fixed to allow pass types, that can be casted to target type. + std::set size_t_properties = { + "max_new_tokens", + "max_length", + "min_new_tokens", + "logprobs", + "num_beam_groups", + "num_beams", + "num_return_sequences", + "no_repeat_ngram_size", + "top_k", + "rng_seed", + "num_assistant_tokens", + "max_initial_timestamp_index", + "num_images_per_prompt", + "num_inference_steps" + }; + py::object float_32_type = py::module_::import("numpy").attr("float32"); - if (py::isinstance(py_obj)) { return py_obj.cast(); } else if (py::isinstance(py_obj)) { @@ -71,16 +90,19 @@ ov::Any py_object_to_any(const py::object& py_obj) { } else if (py::isinstance(py_obj)) { return py_obj.cast(); } else if (py::isinstance(py_obj)) { - return py_obj.cast(); + return py_obj.cast(); } else if (py::isinstance(py_obj, float_32_type)) { return py_obj.cast(); } else if (py::isinstance(py_obj)) { + if (size_t_properties.find(property_name) != size_t_properties.end()) { + return py_obj.cast(); + } return py_obj.cast(); } else if (py::isinstance(py_obj)) { return {}; } else if (py::isinstance(py_obj)) { auto _list = py_obj.cast(); - enum class PY_TYPE : int { UNKNOWN = 0, STR, INT, FLOAT, BOOL, PARTIAL_SHAPE }; + enum class PY_TYPE : int { UNKNOWN = 0, STR, INT, FLOAT, BOOL, PARTIAL_SHAPE, TENSOR}; PY_TYPE detected_type = PY_TYPE::UNKNOWN; for (const auto& it : _list) { auto check_type = [&](PY_TYPE type) { @@ -100,6 +122,8 @@ ov::Any py_object_to_any(const py::object& py_obj) { check_type(PY_TYPE::BOOL); } else if (py::isinstance(it)) { check_type(PY_TYPE::PARTIAL_SHAPE); + } else if (py::isinstance(it)) { + check_type(PY_TYPE::TENSOR); } } @@ -117,10 +141,89 @@ ov::Any py_object_to_any(const py::object& py_obj) { return _list.cast>(); case PY_TYPE::PARTIAL_SHAPE: return _list.cast>(); + case PY_TYPE::TENSOR: + return _list.cast>(); + default: + OPENVINO_ASSERT(false, "Unsupported attribute type."); + } + + } else if (py::isinstance(py_obj)) { + auto _dict = py_obj.cast(); + enum class PY_TYPE : int { UNKNOWN = 0, STR, INT}; + PY_TYPE detected_key_type = PY_TYPE::UNKNOWN; + PY_TYPE detected_value_type = PY_TYPE::UNKNOWN; + for (const auto& it : _dict) { + auto check_type = [&](PY_TYPE type, PY_TYPE detected_type) { + if (detected_type == PY_TYPE::UNKNOWN || detected_type == type) { + detected_type = type; + return; + } + OPENVINO_THROW("Incorrect attribute. Mixed types in the dict are not allowed."); + }; + // check key type + if (py::isinstance(it.first)) { + check_type(PY_TYPE::STR, detected_key_type); + } + + // check value type + if (py::isinstance(it.second)) { + check_type(PY_TYPE::INT, detected_value_type); + } + } + + if (_dict.empty()) + return ov::Any(); + + switch (detected_key_type) { + case PY_TYPE::STR: + switch (detected_value_type) { + case PY_TYPE::INT: + return _dict.cast>(); + default: + OPENVINO_ASSERT(false, "Unsupported attribute type."); + } + default: + OPENVINO_ASSERT(false, "Unsupported attribute type."); + } + } else if (py::isinstance(py_obj)) { + auto _set = py_obj.cast(); + enum class PY_TYPE : int { UNKNOWN = 0, STR, INT, FLOAT, BOOL}; + PY_TYPE detected_type = PY_TYPE::UNKNOWN; + for (const auto& it : _set) { + auto check_type = [&](PY_TYPE type) { + if (detected_type == PY_TYPE::UNKNOWN || detected_type == type) { + detected_type = type; + return; + } + OPENVINO_THROW("Incorrect attribute. Mixed types in the set are not allowed."); + }; + if (py::isinstance(it)) { + check_type(PY_TYPE::STR); + } else if (py::isinstance(it)) { + check_type(PY_TYPE::INT); + } else if (py::isinstance(it)) { + check_type(PY_TYPE::FLOAT); + } else if (py::isinstance(it)) { + check_type(PY_TYPE::BOOL); + } + } + + if (_set.empty()) + return ov::Any(); + + switch (detected_type) { + case PY_TYPE::STR: + return _set.cast>(); + case PY_TYPE::FLOAT: + return _set.cast>(); + case PY_TYPE::INT: + return _set.cast>(); + case PY_TYPE::BOOL: + return _set.cast>(); default: OPENVINO_ASSERT(false, "Unsupported attribute type."); } - + // OV types } else if (py_object_is_any_map(py_obj)) { return py_object_to_any_map(py_obj); @@ -156,8 +259,18 @@ ov::Any py_object_to_any(const py::object& py_obj) { return py::cast>(py_obj); } else if (py::isinstance(py_obj)) { return py::cast(py_obj); - } else if (py::isinstance(py_obj)) { + } else if (py::isinstance(py_obj)) { return py::cast(py_obj); + } else if (py::isinstance(py_obj)) { + return py::cast(py_obj); + } else if (py::isinstance(py_obj)) { + return py::cast(py_obj); + } else if (py::isinstance(py_obj)) { + return py::cast>(py_obj); + // TODO check function signature? + } else if (py::isinstance(py_obj) || py::isinstance(py_obj) || py::isinstance(py_obj)) { + auto streamer = py::cast(py_obj); + return ov::genai::streamer(pystreamer_to_streamer(streamer)).second; } else if (py::isinstance(py_obj)) { return py_obj; } @@ -167,7 +280,7 @@ ov::Any py_object_to_any(const py::object& py_obj) { std::map properties_to_any_map(const std::map& properties) { std::map properties_to_cpp; for (const auto& property : properties) { - properties_to_cpp[property.first] = py_object_to_any(property.second); + properties_to_cpp[property.first] = py_object_to_any(property.second, property.first); } return properties_to_cpp; } @@ -183,7 +296,7 @@ ov::AnyMap kwargs_to_any_map(const py::kwargs& kwargs) { auto map = utils::py_object_to_any_map(value); params.insert(map.begin(), map.end()); } else { - params[key] = utils::py_object_to_any(value); + params[key] = utils::py_object_to_any(value, key); } } @@ -227,87 +340,9 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O ov::genai::GenerationConfig res_config; if(config.has_value()) res_config = *config; - ov::AnyMap map; - for (const auto& item : kwargs) { - std::string key = py::cast(item.first); - py::object value = py::cast(item.second); - - if (item.second.is_none()) { - // Even if argument key name does not fit GenerationConfig name - // it's not an error if it's not defined. - // Some HF configs can have parameters for methods currently unsupported in ov_genai - // but if their values are not set / None, then this should not block - // us from reading such configs, e.g. {"typical_p": None, 'top_p': 1.0,...} - return res_config; - } - if (!generation_config_param_to_property(key, value, map)) { - throw(std::invalid_argument("'" + key + "' is incorrect GenerationConfig parameter name. " - "Use help(openvino_genai.GenerationConfig) to get list of acceptable parameters.")); - } - } - res_config.update_generation_config(map); + res_config.update_generation_config(kwargs_to_any_map(kwargs)); return res_config; } - -bool generation_config_param_to_property(std::string key, py::object value, ov::AnyMap& map) { - if (key == "max_new_tokens") { - map.insert(ov::genai::max_new_tokens(py::cast(value))); - } else if (key == "max_length") { - map.insert(ov::genai::max_length(py::cast(value))); - } else if (key == "ignore_eos") { - map.insert(ov::genai::ignore_eos(py::cast(value))); - } else if (key == "min_new_tokens") { - map.insert(ov::genai::min_new_tokens(py::cast(value))); - } else if (key == "stop_strings") { - map.insert(ov::genai::stop_strings(py::cast>(value))); - } else if (key == "include_stop_str_in_output") { - map.insert(ov::genai::include_stop_str_in_output(py::cast(value))); - } else if (key == "include_stop_str_in_output") { - map.insert(ov::genai::stop_token_ids(py::cast>>(value))); - } else if (key == "num_beam_groups") { - map.insert(ov::genai::num_beam_groups(py::cast(value))); - } else if (key == "num_beams") { - map.insert(ov::genai::num_beams(py::cast(value))); - } else if (key == "diversity_penalty") { - map.insert(ov::genai::diversity_penalty(py::cast(value))); - } else if (key == "length_penalty") { - map.insert(ov::genai::length_penalty(py::cast(value))); - } else if (key == "num_return_sequences") { - map.insert(ov::genai::num_return_sequences(py::cast(value))); - } else if (key == "no_repeat_ngram_size") { - map.insert(ov::genai::no_repeat_ngram_size(py::cast(value))); - } else if (key == "stop_criteria") { - map.insert(ov::genai::stop_criteria(py::cast(value))); - } else if (key == "temperature") { - map.insert(ov::genai::temperature(py::cast(value))); - } else if (key == "top_p") { - map.insert(ov::genai::top_p(py::cast(value))); - } else if (key == "top_k") { - map.insert(ov::genai::top_k(py::cast(value))); - } else if (key == "do_sample") { - map.insert(ov::genai::do_sample(py::cast(value))); - } else if (key == "repetition_penalty") { - map.insert(ov::genai::repetition_penalty(py::cast(value))); - } else if (key == "presence_penalty") { - map.insert(ov::genai::presence_penalty(py::cast(value))); - } else if (key == "frequency_penalty") { - map.insert(ov::genai::frequency_penalty(py::cast(value))); - } else if (key == "rng_seed") { - map.insert(ov::genai::rng_seed(py::cast(value))); - } else if (key == "eos_token_id") { - map.insert(ov::genai::eos_token_id(py::cast(value))); - } else if (key == "assistant_confidence_threshold") { - map.insert(ov::genai::assistant_confidence_threshold(py::cast(value))); - } else if (key == "num_assistant_tokens") { - map.insert(ov::genai::num_assistant_tokens(py::cast(value))); - } else if (key == "adapters") { - map.insert(ov::genai::adapters(py::cast(value))); - } else { - return false; - } - return true; -} - } // namespace ov::genai::pybind::utils diff --git a/src/python/py_utils.hpp b/src/python/py_utils.hpp index a16bc7eaf9..027f25e887 100644 --- a/src/python/py_utils.hpp +++ b/src/python/py_utils.hpp @@ -28,7 +28,7 @@ py::list handle_utf8(const std::vector& decoded_res); py::str handle_utf8(const std::string& text); -ov::Any py_object_to_any(const py::object& py_obj); +ov::Any py_object_to_any(const py::object& py_obj, std::string property_name); bool py_object_is_any_map(const py::object& py_obj); @@ -44,6 +44,4 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O ov::genai::StreamerVariant pystreamer_to_streamer(const PyBindStreamerVariant& py_streamer); -bool generation_config_param_to_property(std::string key, py::object value, ov::AnyMap& map); - } // namespace ov::genai::pybind::utils diff --git a/src/python/py_vlm_pipeline.cpp b/src/python/py_vlm_pipeline.cpp index 0d729accec..9572652204 100644 --- a/src/python/py_vlm_pipeline.cpp +++ b/src/python/py_vlm_pipeline.cpp @@ -72,49 +72,6 @@ py::object call_vlm_generate( return py::cast(pipe.generate(prompt, images, updated_config, streamer)); } -ov::AnyMap vlm_kwargs_to_any_map(const py::kwargs& kwargs, bool called_from_init=true) { - ov::AnyMap params = {}; - - for (const auto& item : kwargs) { - std::string key = py::cast(item.first); - py::object value = py::cast(item.second); - - if (pyutils::generation_config_param_to_property(key, value, params)) { - continue; - } - else { - if (called_from_init) { - // convert arbitrary objects to ov::Any - // not supported properties are not checked, as these properties are passed to compile(), which will throw exception in case of unsupported property - if (pyutils::py_object_is_any_map(value)) { - auto map = pyutils::py_object_to_any_map(value); - params.insert(map.begin(), map.end()); - } else { - params[key] = pyutils::py_object_to_any(value); - } - } - else { - if (key == "images") { - params.insert({ov::genai::images(std::move(py::cast>(value)))}); - } else if (key == "image") { - params.insert({ov::genai::image(std::move(py::cast(value)))}); - } else if (key == "generation_config") { - params.insert({ov::genai::generation_config(std::move(py::cast(value)))}); - } else if (key == "streamer") { - auto py_streamer = py::cast(value); - params.insert({ov::genai::streamer(std::move(pyutils::pystreamer_to_streamer(py_streamer)))}); - } else { - // generate doesn't run compile(), so only VLMPipeline specific properties are allowed - throw(std::invalid_argument("'" + key + "' is unexpected parameter name. " - "Use help(openvino_genai.VLMPipeline.generate) to get list of acceptable parameters.")); - } - } - } - } - - return params; -} - void init_vlm_pipeline(py::module_& m) { py::class_(m, "VLMPipeline", "This class is used for generation with VLMs") .def(py::init([]( @@ -123,7 +80,7 @@ void init_vlm_pipeline(py::module_& m) { const py::kwargs& kwargs ) { ScopedVar env_manager(pyutils::ov_tokenizers_module_path()); - return std::make_unique(models_path, device, vlm_kwargs_to_any_map(kwargs, true)); + return std::make_unique(models_path, device, pyutils::kwargs_to_any_map(kwargs)); }), py::arg("models_path"), "folder with exported model files", py::arg("device"), "device on which inference will be done" @@ -180,7 +137,7 @@ void init_vlm_pipeline(py::module_& m) { const std::string& prompt, const py::kwargs& kwargs ) -> py::typing::Union { - return py::cast(pipe.generate(prompt, vlm_kwargs_to_any_map(kwargs, false))); + return py::cast(pipe.generate(prompt, pyutils::kwargs_to_any_map(kwargs))); }, py::arg("prompt"), "Input string", (vlm_generate_kwargs_docstring + std::string(" \n ")).c_str() diff --git a/src/python/py_whisper_pipeline.cpp b/src/python/py_whisper_pipeline.cpp index 32680d283a..62041b051f 100644 --- a/src/python/py_whisper_pipeline.cpp +++ b/src/python/py_whisper_pipeline.cpp @@ -142,60 +142,7 @@ OptionalWhisperGenerationConfig update_whisper_config_from_kwargs(const Optional WhisperGenerationConfig res_config; if (config.has_value()) res_config = *config; - - for (const auto& item : kwargs) { - std::string key = py::cast(item.first); - py::object value = py::cast(item.second); - - if (item.second.is_none()) { - // Even if argument key name does not fit GenerationConfig name - // it's not an error if it's not defined. - // Some HF configs can have parameters for methods currently unsupported in ov_genai - // but if their values are not set / None, then this should not block - // us from reading such configs, e.g. {"typical_p": None, 'top_p': 1.0,...} - return res_config; - } - - if (key == "max_new_tokens") { - res_config.max_new_tokens = py::cast(item.second); - } else if (key == "max_length") { - res_config.max_length = py::cast(item.second); - } else if (key == "decoder_start_token_id") { - res_config.decoder_start_token_id = py::cast(item.second); - } else if (key == "pad_token_id") { - res_config.pad_token_id = py::cast(item.second); - } else if (key == "translate_token_id") { - res_config.translate_token_id = py::cast(item.second); - } else if (key == "transcribe_token_id") { - res_config.transcribe_token_id = py::cast(item.second); - } else if (key == "no_timestamps_token_id") { - res_config.no_timestamps_token_id = py::cast(item.second); - } else if (key == "max_initial_timestamp_index") { - res_config.max_initial_timestamp_index = py::cast(item.second); - } else if (key == "begin_suppress_tokens") { - res_config.begin_suppress_tokens = py::cast>(item.second); - } else if (key == "suppress_tokens") { - res_config.suppress_tokens = py::cast>(item.second); - } else if (key == "is_multilingual") { - res_config.is_multilingual = py::cast(item.second); - } else if (key == "language") { - res_config.language = py::cast(item.second); - } else if (key == "lang_to_id") { - res_config.lang_to_id = py::cast>(item.second); - } else if (key == "task") { - res_config.task = py::cast(item.second); - } else if (key == "return_timestamps") { - res_config.return_timestamps = py::cast(item.second); - } else if (key == "eos_token_id") { - res_config.set_eos_token_id(py::cast(item.second)); - } else { - throw(std::invalid_argument( - "'" + key + - "' is incorrect WhisperGenerationConfig parameter name. " - "Use help(openvino_genai.WhisperGenerationConfig) to get list of acceptable parameters.")); - } - } - + res_config.update_generation_config(pyutils::kwargs_to_any_map(kwargs)); return res_config; } From 856db38fa4b87347562401926d593d25cbd2f824 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Sat, 16 Nov 2024 13:45:29 +0100 Subject: [PATCH 04/12] Minor correction. --- src/python/py_utils.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index 5276f23e04..52f5228717 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -267,7 +267,6 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { return py::cast(py_obj); } else if (py::isinstance(py_obj)) { return py::cast>(py_obj); - // TODO check function signature? } else if (py::isinstance(py_obj) || py::isinstance(py_obj) || py::isinstance(py_obj)) { auto streamer = py::cast(py_obj); return ov::genai::streamer(pystreamer_to_streamer(streamer)).second; From 26001235f755d206e24bbdb908d4bb9d317e7db3 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Sat, 16 Nov 2024 18:11:57 +0100 Subject: [PATCH 05/12] Fixed errors. --- src/python/py_utils.cpp | 9 +++++++-- tests/python_tests/test_generate_api.py | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index 52f5228717..3ddcca5041 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -153,7 +153,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { PY_TYPE detected_key_type = PY_TYPE::UNKNOWN; PY_TYPE detected_value_type = PY_TYPE::UNKNOWN; for (const auto& it : _dict) { - auto check_type = [&](PY_TYPE type, PY_TYPE detected_type) { + auto check_type = [&](PY_TYPE type, PY_TYPE& detected_type) { if (detected_type == PY_TYPE::UNKNOWN || detected_type == type) { detected_type = type; return; @@ -291,10 +291,15 @@ ov::AnyMap kwargs_to_any_map(const py::kwargs& kwargs) { for (const auto& item : kwargs) { std::string key = py::cast(item.first); py::object value = py::cast(item.second); - if (utils::py_object_is_any_map(value)) { + // we need to unpack only dictionaries, which are passed with "config" name, + // because there are dictionary properties that should not be unpacked + if (utils::py_object_is_any_map(value) && key == "config") { auto map = utils::py_object_to_any_map(value); params.insert(map.begin(), map.end()); } else { + if (py::isinstance(value)) { + continue; + } params[key] = utils::py_object_to_any(value, key); } diff --git a/tests/python_tests/test_generate_api.py b/tests/python_tests/test_generate_api.py index ba934e3bda..ac42d2612d 100644 --- a/tests/python_tests/test_generate_api.py +++ b/tests/python_tests/test_generate_api.py @@ -635,7 +635,8 @@ def test_valid_configs(model_tmp_path): invalid_py_configs = [ dict(num_beam_groups=3, num_beams=15, do_sample=True), - dict(unexisting_key_name=True), # no eos_token_id no max_new_tokens, no max_len + # TODO: Currently unexpected params do not cause exceptions. Need to implement it in c++ and return this test + # dict(unexisting_key_name=True), # no eos_token_id no max_new_tokens, no max_len dict(eos_token_id=42, ignore_eos=True), # no max_new_tokens, no max_len with ignore_eos dict(repetition_penalty=-1.0, eos_token_id=42, max_new_tokens=20), # invalid penalty dict(temperature=-1.0, do_sample=True, eos_token_id=42, max_new_tokens=20), # invalid temp From 3cb61b471630921ed339c76be997296f93ad0e98 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Mon, 18 Nov 2024 17:36:25 +0100 Subject: [PATCH 06/12] Applied comments. --- src/python/py_utils.cpp | 42 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index 3ddcca5041..374f02fc1a 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -12,7 +12,8 @@ #include "tokenizers_path.hpp" #include "openvino/genai/llm_pipeline.hpp" #include "openvino/genai/visual_language/pipeline.hpp" -#include "openvino/genai/image_generation/text2image_pipeline.hpp" +#include "openvino/genai/image_generation/generation_config.hpp" +#include "openvino/genai/whisper_generation_config.hpp" namespace py = pybind11; namespace ov::genai::pybind::utils { @@ -81,6 +82,12 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { "num_images_per_prompt", "num_inference_steps" }; + + // Properties, that can be empty sets + std::set allow_empty_dict_properties { + "PREFILL_CONFIG", + "GENERATE_CONFIG" + }; py::object float_32_type = py::module_::import("numpy").attr("float32"); if (py::isinstance(py_obj)) { @@ -110,7 +117,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { detected_type = type; return; } - OPENVINO_THROW("Incorrect attribute. Mixed types in the list are not allowed."); + OPENVINO_THROW("Incorrect value in \"" + property_name + "\". Mixed types in the list are not allowed."); }; if (py::isinstance(it)) { check_type(PY_TYPE::STR); @@ -128,7 +135,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { } if (_list.empty()) - return ov::Any(); + OPENVINO_THROW("The property " + property_name +" can't be empty."); switch (detected_type) { case PY_TYPE::STR: @@ -144,7 +151,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { case PY_TYPE::TENSOR: return _list.cast>(); default: - OPENVINO_ASSERT(false, "Unsupported attribute type."); + OPENVINO_THROW("Property \"" + property_name + "\" got unsupported type."); } } else if (py::isinstance(py_obj)) { @@ -158,7 +165,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { detected_type = type; return; } - OPENVINO_THROW("Incorrect attribute. Mixed types in the dict are not allowed."); + OPENVINO_THROW("Incorrect value in \"" + property_name + "\". Mixed types in the dict are not allowed."); }; // check key type if (py::isinstance(it.first)) { @@ -170,9 +177,12 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { check_type(PY_TYPE::INT, detected_value_type); } } - - if (_dict.empty()) - return ov::Any(); + if (_dict.empty()) { + if (allow_empty_dict_properties.find(property_name) != allow_empty_dict_properties.end()) { + return ov::AnyMap({}); + } + OPENVINO_THROW("The property " + property_name + " can't be empty."); + } switch (detected_key_type) { case PY_TYPE::STR: @@ -180,10 +190,10 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { case PY_TYPE::INT: return _dict.cast>(); default: - OPENVINO_ASSERT(false, "Unsupported attribute type."); + OPENVINO_THROW("Property \"" + property_name + "\" got unsupported type."); } default: - OPENVINO_ASSERT(false, "Unsupported attribute type."); + OPENVINO_THROW("Property \"" + property_name + "\" got unsupported type."); } } else if (py::isinstance(py_obj)) { auto _set = py_obj.cast(); @@ -195,7 +205,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { detected_type = type; return; } - OPENVINO_THROW("Incorrect attribute. Mixed types in the set are not allowed."); + OPENVINO_THROW("Incorrect value in \"" + property_name + "\". Mixed types in the set are not allowed."); }; if (py::isinstance(it)) { check_type(PY_TYPE::STR); @@ -209,7 +219,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { } if (_set.empty()) - return ov::Any(); + OPENVINO_THROW("The property " + property_name + " can't be empty."); switch (detected_type) { case PY_TYPE::STR: @@ -221,7 +231,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { case PY_TYPE::BOOL: return _set.cast>(); default: - OPENVINO_ASSERT(false, "Unsupported attribute type."); + OPENVINO_THROW("Property \"" + property_name + "\" got unsupported type."); } // OV types @@ -263,6 +273,10 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { return py::cast(py_obj); } else if (py::isinstance(py_obj)) { return py::cast(py_obj); + } else if (py::isinstance(py_obj)) { + return py::cast(py_obj); + } else if (py::isinstance(py_obj)) { + return py::cast(py_obj); } else if (py::isinstance(py_obj)) { return py::cast(py_obj); } else if (py::isinstance(py_obj)) { @@ -273,7 +287,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { } else if (py::isinstance(py_obj)) { return py_obj; } - OPENVINO_ASSERT(false, "Unsupported attribute type."); + OPENVINO_THROW("Property \"" + property_name + "\" got unsupported type."); } std::map properties_to_any_map(const std::map& properties) { From dc19f89161185a03edf3c9339eadc92eb736e593 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Tue, 19 Nov 2024 15:34:12 +0100 Subject: [PATCH 07/12] Applied comments. --- src/cpp/src/llm_pipeline_static.cpp | 3 +++ src/cpp/src/utils.hpp | 7 ++++++- .../openvino_genai/py_openvino_genai.pyi | 2 +- src/python/py_tokenizer.cpp | 6 +++--- src/python/py_utils.cpp | 19 +++++-------------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/cpp/src/llm_pipeline_static.cpp b/src/cpp/src/llm_pipeline_static.cpp index 7174321ff5..36e1a82808 100644 --- a/src/cpp/src/llm_pipeline_static.cpp +++ b/src/cpp/src/llm_pipeline_static.cpp @@ -313,6 +313,9 @@ template T pop_or_default(ov::AnyMap& config, const std::string& key, const T& default_value) { auto anyopt = pop_option(config, key); if (anyopt.has_value()) { + if (anyopt.value().empty()) { + return T{}; + } return anyopt.value().as(); } return default_value; diff --git a/src/cpp/src/utils.hpp b/src/cpp/src/utils.hpp index 9adc46c87a..b0d62f8dfe 100644 --- a/src/cpp/src/utils.hpp +++ b/src/cpp/src/utils.hpp @@ -31,7 +31,12 @@ template void read_anymap_param(const ov::AnyMap& config_map, const std::string& name, T& param) { auto it = config_map.find(name); if (it != config_map.end()) { - param = it->second.as::value>(); + if (it->second.empty()) { + param = T{}; + } + else { + param = it->second.as::value>(); + } } } diff --git a/src/python/openvino_genai/py_openvino_genai.pyi b/src/python/openvino_genai/py_openvino_genai.pyi index 61ab25a954..c81b0a8393 100644 --- a/src/python/openvino_genai/py_openvino_genai.pyi +++ b/src/python/openvino_genai/py_openvino_genai.pyi @@ -1294,7 +1294,7 @@ class Tokenizer: openvino_genai.Tokenizer object is used to initialize Tokenizer if it's located in a different path than the main model. """ - def __init__(self, tokenizer_path: os.PathLike, properties: dict[str, typing.Any] = {}) -> None: + def __init__(self, tokenizer_path: os.PathLike, **kwargs) -> None: ... def apply_chat_template(self, history: list[dict[str, str]], add_generation_prompt: bool, chat_template: str = '') -> str: """ diff --git a/src/python/py_tokenizer.cpp b/src/python/py_tokenizer.cpp index b3c52cd28b..57263adad9 100644 --- a/src/python/py_tokenizer.cpp +++ b/src/python/py_tokenizer.cpp @@ -30,10 +30,10 @@ void init_tokenizer(py::module_& m) { R"(openvino_genai.Tokenizer object is used to initialize Tokenizer if it's located in a different path than the main model.)") - .def(py::init([](const std::filesystem::path& tokenizer_path, const std::map& properties) { + .def(py::init([](const std::filesystem::path& tokenizer_path, const py::kwargs& kwargs) { ScopedVar env_manager(pyutils::ov_tokenizers_module_path()); - return std::make_unique(tokenizer_path, pyutils::properties_to_any_map(properties)); - }), py::arg("tokenizer_path"), py::arg("properties") = ov::AnyMap({})) + return std::make_unique(tokenizer_path, pyutils::kwargs_to_any_map(kwargs)); + }), py::arg("tokenizer_path")) .def("encode", [](Tokenizer& tok, std::vector& prompts, bool add_special_tokens) { ov::AnyMap tokenization_params; diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index 374f02fc1a..f53947d5d6 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -65,7 +65,7 @@ ov::AnyMap py_object_to_any_map(const py::object& py_obj) { ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { // Python types - // TODO: Remove this after ov::Any is fixed to allow pass types, that can be casted to target type. + // TODO: Remove this after ov::Any is fixed to allow pass types, that can be casted to target type. Ticket: 157622 std::set size_t_properties = { "max_new_tokens", "max_length", @@ -82,12 +82,6 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { "num_images_per_prompt", "num_inference_steps" }; - - // Properties, that can be empty sets - std::set allow_empty_dict_properties { - "PREFILL_CONFIG", - "GENERATE_CONFIG" - }; py::object float_32_type = py::module_::import("numpy").attr("float32"); if (py::isinstance(py_obj)) { @@ -135,7 +129,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { } if (_list.empty()) - OPENVINO_THROW("The property " + property_name +" can't be empty."); + return ov::Any(); switch (detected_type) { case PY_TYPE::STR: @@ -178,10 +172,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { } } if (_dict.empty()) { - if (allow_empty_dict_properties.find(property_name) != allow_empty_dict_properties.end()) { - return ov::AnyMap({}); - } - OPENVINO_THROW("The property " + property_name + " can't be empty."); + return ov::Any(); } switch (detected_key_type) { @@ -219,7 +210,7 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { } if (_set.empty()) - OPENVINO_THROW("The property " + property_name + " can't be empty."); + return ov::Any(); switch (detected_type) { case PY_TYPE::STR: @@ -312,7 +303,7 @@ ov::AnyMap kwargs_to_any_map(const py::kwargs& kwargs) { params.insert(map.begin(), map.end()); } else { if (py::isinstance(value)) { - continue; + OPENVINO_THROW("Property \"" + key + "\" can't be None."); } params[key] = utils::py_object_to_any(value, key); } From c928271f60028599ec3711ea43e90b1f1e13afe9 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Tue, 19 Nov 2024 18:07:08 +0100 Subject: [PATCH 08/12] Fixed tests. --- tests/python_tests/test_generate_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/python_tests/test_generate_api.py b/tests/python_tests/test_generate_api.py index ac42d2612d..80df79f31b 100644 --- a/tests/python_tests/test_generate_api.py +++ b/tests/python_tests/test_generate_api.py @@ -38,7 +38,7 @@ def run_hf_ov_genai_comparison_batched(model_descr, generation_config: Dict, pro # Need to set explicitly to False, but only if test arguments omitted this arg. # Do not apply 'repetition_penalty' if sampling is not used. config['do_sample'] = False - config['repetition_penalty'] = None + config['repetition_penalty'] = 1.0 # 1.0 means no penalty generation_config_hf = config.copy() if generation_config_hf.get('stop_criteria'): @@ -78,7 +78,7 @@ def run_hf_ov_genai_comparison(model_descr, generation_config: Dict, prompt: str # Need to set explicitly to False, but only if test arguments omitted this arg. # Do not apply 'repetition_penalty' if sampling is not used. config['do_sample'] = False - config['repetition_penalty'] = None + config['repetition_penalty'] = 1.0 # 1.0 means no penalty generation_config_hf = config.copy() if generation_config_hf.get('stop_criteria'): @@ -117,7 +117,7 @@ def hf_ov_genai_tensors_comparison( # Need to set explicitly to False, but only if test arguments omitted this arg. # Do not apply 'repetition_penalty' if sampling is not used. config['do_sample'] = False - config['repetition_penalty'] = None + config['repetition_penalty'] = 1.0 # 1.0 means no penalty generation_config_hf = config.copy() if generation_config_hf.get('stop_criteria'): @@ -764,7 +764,7 @@ def run_perf_metrics_collection(model_descr, generation_config: Dict, prompt: st # Need to set explicitly to False, but only if test arguments omitted this arg. # Do not apply 'repetition_penalty' if sampling is not used. config['do_sample'] = False - config['repetition_penalty'] = None + config['repetition_penalty'] = 1.0 # 1.0 means no penalty return pipe.generate([prompt], **config).perf_metrics From 1978b676245e77a7825dbf1ad5826b33ef400c9e Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Wed, 20 Nov 2024 10:30:36 +0100 Subject: [PATCH 09/12] Update src/python/py_utils.cpp Co-authored-by: Ilya Lavrenov --- src/python/py_utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index f53947d5d6..aaed10a032 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -303,7 +303,7 @@ ov::AnyMap kwargs_to_any_map(const py::kwargs& kwargs) { params.insert(map.begin(), map.end()); } else { if (py::isinstance(value)) { - OPENVINO_THROW("Property \"" + key + "\" can't be None."); + OPENVINO_ASSERT(!py::isinstance(value), "Property \"", key, "\" can't be None."); } params[key] = utils::py_object_to_any(value, key); } From 446dcb657afbf3e397456a588f173e4627e5853c Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Wed, 20 Nov 2024 16:19:10 +0100 Subject: [PATCH 10/12] Applied comments. --- src/cpp/src/llm_pipeline_static.cpp | 6 +++++- src/cpp/src/utils.hpp | 17 ++++++++++++++++- src/python/py_tokenizer.cpp | 15 ++++++++++++--- tests/cpp/utils.cpp | 21 +++++++++++++++++++++ tests/python_tests/test_sampling.py | 6 +++--- 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 tests/cpp/utils.cpp diff --git a/src/cpp/src/llm_pipeline_static.cpp b/src/cpp/src/llm_pipeline_static.cpp index 36e1a82808..4bae1601d8 100644 --- a/src/cpp/src/llm_pipeline_static.cpp +++ b/src/cpp/src/llm_pipeline_static.cpp @@ -314,7 +314,11 @@ T pop_or_default(ov::AnyMap& config, const std::string& key, const T& default_va auto anyopt = pop_option(config, key); if (anyopt.has_value()) { if (anyopt.value().empty()) { - return T{}; + if (ov::genai::utils::is_container) + return T{}; + else { + OPENVINO_THROW("Got empty ov::Any for key: " + key); + } } return anyopt.value().as(); } diff --git a/src/cpp/src/utils.hpp b/src/cpp/src/utils.hpp index b0d62f8dfe..3487fccb81 100644 --- a/src/cpp/src/utils.hpp +++ b/src/cpp/src/utils.hpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#include #include "openvino/genai/llm_pipeline.hpp" #include "openvino/runtime/core.hpp" @@ -12,6 +13,16 @@ namespace ov { namespace genai { namespace utils { +// Variable template that checks if a type has begin() and end() member functions +template +constexpr bool is_container = false; + +template +constexpr bool is_container().begin()), + decltype(std::declval().end())>> = true; + + Tensor init_attention_mask(const Tensor& position_ids); void print_tensor(const ov::Tensor& tensor); @@ -32,7 +43,11 @@ void read_anymap_param(const ov::AnyMap& config_map, const std::string& name, T& auto it = config_map.find(name); if (it != config_map.end()) { if (it->second.empty()) { - param = T{}; + if (ov::genai::utils::is_container) + param = T{}; + else { + OPENVINO_THROW("Got empty ov::Any for parameter name: " + name); + } } else { param = it->second.as::value>(); diff --git a/src/python/py_tokenizer.cpp b/src/python/py_tokenizer.cpp index 57263adad9..2ccccff4c0 100644 --- a/src/python/py_tokenizer.cpp +++ b/src/python/py_tokenizer.cpp @@ -30,10 +30,19 @@ void init_tokenizer(py::module_& m) { R"(openvino_genai.Tokenizer object is used to initialize Tokenizer if it's located in a different path than the main model.)") - .def(py::init([](const std::filesystem::path& tokenizer_path, const py::kwargs& kwargs) { + .def(py::init([](const std::filesystem::path& tokenizer_path, const std::map& properties, const py::kwargs& kwargs) { ScopedVar env_manager(pyutils::ov_tokenizers_module_path()); - return std::make_unique(tokenizer_path, pyutils::kwargs_to_any_map(kwargs)); - }), py::arg("tokenizer_path")) + auto kwargs_properties = pyutils::kwargs_to_any_map(kwargs); + if (properties.size()) { + PyErr_WarnEx(PyExc_DeprecationWarning, + "'properties' parameters is deprecated, please use kwargs to pass config properties instead.", + 1); + auto map_properties = pyutils::properties_to_any_map(properties); + kwargs_properties.insert(map_properties.begin(), map_properties.end()); + } + + return std::make_unique(tokenizer_path, kwargs_properties); + }), py::arg("tokenizer_path"), py::arg("properties") = ov::AnyMap({})) .def("encode", [](Tokenizer& tok, std::vector& prompts, bool add_special_tokens) { ov::AnyMap tokenization_params; diff --git a/tests/cpp/utils.cpp b/tests/cpp/utils.cpp new file mode 100644 index 0000000000..d00edae6fb --- /dev/null +++ b/tests/cpp/utils.cpp @@ -0,0 +1,21 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include "utils.hpp" + + +using namespace ov::genai::utils; +using map_type = std::map; + +TEST(TestIsContainer, test_is_container) { + EXPECT_EQ(is_container, false); + EXPECT_EQ(is_container, false); + EXPECT_EQ(is_container, false); + EXPECT_EQ(is_container, false); + EXPECT_EQ(is_container, true); + EXPECT_EQ(is_container>, true); + EXPECT_EQ(is_container, true); + EXPECT_EQ(is_container>, true); +} \ No newline at end of file diff --git a/tests/python_tests/test_sampling.py b/tests/python_tests/test_sampling.py index 9973e20e1d..9aa6931d85 100644 --- a/tests/python_tests/test_sampling.py +++ b/tests/python_tests/test_sampling.py @@ -334,7 +334,7 @@ def test_echo_without_completion(tmp_path, get_generation_config, max_num_batche model_path : Path = tmp_path / model_id save_ov_model_from_optimum(model, hf_tokenizer, model_path) - pipe = ContinuousBatchingPipeline(model_path.absolute().as_posix(), Tokenizer(model_path.absolute().as_posix(), {}), scheduler_config, "CPU", {}) + pipe = ContinuousBatchingPipeline(model_path.absolute().as_posix(), Tokenizer(model_path.absolute().as_posix()), scheduler_config, "CPU", {}) outputs = pipe.generate(["What is OpenVINO?"], generation_configs) assert(len(outputs)) @@ -361,7 +361,7 @@ def test_echo_with_completion(tmp_path, get_generation_config, max_num_batched_t model_path : Path = tmp_path / model_id save_ov_model_from_optimum(model, hf_tokenizer, model_path) - pipe = ContinuousBatchingPipeline(model_path.absolute().as_posix(), Tokenizer(model_path.absolute().as_posix(), {}), scheduler_config, "CPU", {}) + pipe = ContinuousBatchingPipeline(model_path.absolute().as_posix(), Tokenizer(model_path.absolute().as_posix()), scheduler_config, "CPU", {}) outputs = pipe.generate(["What is OpenVINO?"], generation_configs) assert(len(outputs)) @@ -389,7 +389,7 @@ def test_post_oom_health(tmp_path, sampling_config): models_path : Path = tmp_path / model_id save_ov_model_from_optimum(model, hf_tokenizer, models_path) - pipe = ContinuousBatchingPipeline(models_path.absolute().as_posix(), Tokenizer(models_path.absolute().as_posix(), {}), scheduler_config, "CPU", {}) + pipe = ContinuousBatchingPipeline(models_path.absolute().as_posix(), Tokenizer(models_path.absolute().as_posix()), scheduler_config, "CPU", {}) # First run should return incomplete response output = pipe.generate(["What is OpenVINO?"], generation_configs) assert (len(output)) From 4509e8e0feaa9f82a74038a2a81029f3b5f26b17 Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Wed, 20 Nov 2024 18:20:05 +0100 Subject: [PATCH 11/12] Minor corrections. --- src/python/py_utils.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index dfbd3a4c75..579fe6b789 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -273,9 +274,9 @@ ov::Any py_object_to_any(const py::object& py_obj, std::string property_name) { return py::cast(py_obj); } else if (py::isinstance(py_obj)) { return py::cast>(py_obj); - } else if (py::cast(py_obj) && property_name == "callback") { - return ov::genai::callback(py::cast>(py_obj)).second; - } else if (py::isinstance(py_obj) || py::isinstance(py_obj) || py::isinstance(py_obj)) { + } else if (py::isinstance(py_obj) && property_name == "callback") { + return py::cast>(py_obj); + } else if ((py::isinstance(py_obj) || py::isinstance(py_obj) || py::isinstance(py_obj)) && property_name == "streamer") { auto streamer = py::cast(py_obj); return ov::genai::streamer(pystreamer_to_streamer(streamer)).second; } else if (py::isinstance(py_obj)) { From f456311511c4fc872a4787379ad96d429251c66c Mon Sep 17 00:00:00 2001 From: Anastasiia Pnevskaia Date: Wed, 20 Nov 2024 18:25:44 +0100 Subject: [PATCH 12/12] Corrected pyi file. --- src/python/openvino_genai/py_openvino_genai.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/openvino_genai/py_openvino_genai.pyi b/src/python/openvino_genai/py_openvino_genai.pyi index 9021957a5f..df290a9744 100644 --- a/src/python/openvino_genai/py_openvino_genai.pyi +++ b/src/python/openvino_genai/py_openvino_genai.pyi @@ -1296,7 +1296,7 @@ class Tokenizer: openvino_genai.Tokenizer object is used to initialize Tokenizer if it's located in a different path than the main model. """ - def __init__(self, tokenizer_path: os.PathLike, **kwargs) -> None: + def __init__(self, tokenizer_path: os.PathLike, properties: dict[str, typing.Any] = {}, **kwargs) -> None: ... def apply_chat_template(self, history: list[dict[str, str]], add_generation_prompt: bool, chat_template: str = '') -> str: """