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/cpp/src/llm_pipeline_static.cpp b/src/cpp/src/llm_pipeline_static.cpp index 40089384a8..2beb7d64be 100644 --- a/src/cpp/src/llm_pipeline_static.cpp +++ b/src/cpp/src/llm_pipeline_static.cpp @@ -530,6 +530,13 @@ 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()) { + if (ov::genai::utils::is_container) + return T{}; + else { + OPENVINO_THROW("Got empty ov::Any for key: " + key); + } + } return anyopt.value().as(); } return default_value; diff --git a/src/cpp/src/utils.hpp b/src/cpp/src/utils.hpp index 9adc46c87a..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); @@ -31,7 +42,16 @@ 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()) { + 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/openvino_genai/py_openvino_genai.pyi b/src/python/openvino_genai/py_openvino_genai.pyi index a16b74b703..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, properties: dict[str, typing.Any] = {}) -> 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: """ diff --git a/src/python/py_image_generation_pipelines.cpp b/src/python/py_image_generation_pipelines.cpp index f70faaca61..dade8a170e 100644 --- a/src/python/py_image_generation_pipelines.cpp +++ b/src/python/py_image_generation_pipelines.cpp @@ -67,108 +67,6 @@ auto text2image_generate_docstring = R"( )"; -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 if (key == "max_sequence_length") { - config.max_sequence_length = 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 (key == "max_sequence_length") { - params.insert({ov::genai::max_sequence_length(std::move(py::cast(value)))}); - } else if (key == "callback") { - params.insert({ov::genai::callback(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 @@ -230,7 +128,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.") @@ -252,7 +150,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", @@ -289,7 +187,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_tokenizer.cpp b/src/python/py_tokenizer.cpp index b3c52cd28b..2ccccff4c0 100644 --- a/src/python/py_tokenizer.cpp +++ b/src/python/py_tokenizer.cpp @@ -30,9 +30,18 @@ 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 std::map& properties, const py::kwargs& kwargs) { ScopedVar env_manager(pyutils::ov_tokenizers_module_path()); - return std::make_unique(tokenizer_path, pyutils::properties_to_any_map(properties)); + 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) { diff --git a/src/python/py_utils.cpp b/src/python/py_utils.cpp index a2e8630059..579fe6b789 100644 --- a/src/python/py_utils.cpp +++ b/src/python/py_utils.cpp @@ -6,11 +6,15 @@ #include #include #include +#include #include #include "tokenizers_path.hpp" #include "openvino/genai/llm_pipeline.hpp" +#include "openvino/genai/visual_language/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 { @@ -43,7 +47,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 +58,34 @@ 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. Ticket: 157622 + 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", + "max_sequence_length" + }; + 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 +93,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) { @@ -88,7 +113,7 @@ ov::Any py_object_to_any(const py::object& py_obj) { 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); @@ -100,6 +125,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 +144,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_THROW("Property \"" + property_name + "\" got unsupported 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 value in \"" + property_name + "\". 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_THROW("Property \"" + property_name + "\" got unsupported type."); + } + default: + OPENVINO_THROW("Property \"" + property_name + "\" got unsupported 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 value in \"" + property_name + "\". 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."); + OPENVINO_THROW("Property \"" + property_name + "\" got unsupported type."); } - + // OV types } else if (py_object_is_any_map(py_obj)) { return py_object_to_any_map(py_obj); @@ -156,18 +262,33 @@ 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); + } 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) && 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)) { 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) { 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; } @@ -179,11 +300,16 @@ 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 { - params[key] = utils::py_object_to_any(value); + if (py::isinstance(value)) { + OPENVINO_ASSERT(!py::isinstance(value), "Property \"", key, "\" can't be None."); + } + params[key] = utils::py_object_to_any(value, key); } } @@ -227,60 +353,9 @@ ov::genai::OptionalGenerationConfig update_config_from_kwargs(const ov::genai::O ov::genai::GenerationConfig 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 == "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 == "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 { - 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(kwargs_to_any_map(kwargs)); return res_config; } + } // namespace ov::genai::pybind::utils diff --git a/src/python/py_utils.hpp b/src/python/py_utils.hpp index 9213060660..20094196a6 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); diff --git a/src/python/py_vlm_pipeline.cpp b/src/python/py_vlm_pipeline.cpp index 30e2e04a14..9572652204 100644 --- a/src/python/py_vlm_pipeline.cpp +++ b/src/python/py_vlm_pipeline.cpp @@ -72,46 +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 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 == "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 (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 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([]( @@ -120,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" @@ -177,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 3bf777f739..d34bd5f3b6 100644 --- a/src/python/py_whisper_pipeline.cpp +++ b/src/python/py_whisper_pipeline.cpp @@ -162,60 +162,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; } 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_generate_api.py b/tests/python_tests/test_generate_api.py index ba934e3bda..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'): @@ -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 @@ -763,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 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))