-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[Core][Ref][CPU] Multinomial with RandomUniform samples #31034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[Core][Ref][CPU] Multinomial with RandomUniform samples #31034
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Goal:
The goal of this PR is to reuse RandomUniform logic as a source of random samples of Multinomial operation to align with frameworks random generators.
Changes Review:
This PR extends existing Multinomial-13 op with new attribute (for PhiloxAlignment) and input (for random_samples). As long as it's backward compatible update, this approach can be reviewed within the same op version, but there should be a common agreement with plugins on that.
@maxnick @sshlyapn @birozsolt
From my understanding this new input is not supposed to be filled by user or by the values from an original model, but by RandomUniform operation, which is inserted during the proposed common transformation, replacing every Multinomial of originally two inputs, with Multinomial(..., num_samples=RandomUniform)
, where the output of RU is the new third random_samples
.
Risks:
Introducing the MultinomialRandomUniformFusion
as a common transformation is an interesting idea, allowing for easy update of the Multinomial logic, without significant changes in the plugin code. But it will affect all already supported models with Multinomial, and not only CPU plugin. Currently it looks a bit like work-around, than a standard approach.
Question: (@PiotrKrzem @nshchego @itikhono)
Can we eloborate more on the right direction here, and review the proposed transformation vs usage of RandomUniform kernel inside the plugin Multinomial kernel explicitly?
#include "openvino/op/reshape.hpp" | ||
#include "openvino/op/shape_of.hpp" | ||
#include "openvino/pass/pattern/op/wrap_type.hpp" | ||
#include "transformations/common_optimizations/mul_fake_quantize_fusion.hpp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove redundant includes:
#include "transformations/common_optimizations/mul_fake_quantize_fusion.hpp" |
|
||
new_multinomial->set_friendly_name(multinomial->get_friendly_name()); | ||
ov::copy_runtime_info(multinomial, {random_uniform, new_multinomial}); | ||
ov::replace_node(multinomial, new_multinomial); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Transformation should be covered with transformation tests like:
https://github.com/openvinotoolkit/openvino/tree/master/src/common/transformations/tests/common_optimizations
const uint64_t op_seed = 0, | ||
const PhiloxAlignment alignment = PhiloxAlignment::TENSORFLOW); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this alignment
attribute has effect only if the replacement transformation is applied and random_samples
input is provided.
if (m_provided_random_samples) { | ||
const auto& random_samples_shape = getParentEdgeAt(RANDOM_SAMPLES_PORT)->getMemory().getStaticDims(); | ||
|
||
if (random_samples_shape.size() != 1) { | ||
THROW_CPU_NODE_ERR("has incompatible 'random_samples' shape ", | ||
PartialShape(random_samples_shape), | ||
". Only 1D tensors are allowed."); | ||
} | ||
|
||
if (random_samples_shape[0] != m_output_elements_count) { | ||
THROW_CPU_NODE_ERR( | ||
"has incompatible 'random_samples' shape ", | ||
PartialShape(random_samples_shape), | ||
". The total elements of this input should be equal to the total number of elements in the output."); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those checks covered by Multinomial shape_infer. Is there a need to validate it again here?
@@ -206,20 +242,23 @@ void Multinomial::execute_convert_type() { | |||
}); | |||
} | |||
|
|||
// TODO RandomUniform - should use RandomUniform kernel to match other frameworks' seed results |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible and more efficient to use the RandomUniform cpu kernel explicitly by Multinomial, instead of providing it as input?
if (random_samples == nullptr) { | ||
const T zero = 0; | ||
const T one = 1; | ||
const ov::Shape output_shape_shape{output_shape.size()}; | ||
const std::vector<uint64_t> output_shape_u64(output_shape.begin(), output_shape.end()); | ||
const std::pair<uint64_t, uint64_t> initial_state(0, 0); | ||
random_uniform(output_shape_u64.data(), | ||
reinterpret_cast<const char*>(&zero), | ||
reinterpret_cast<const char*>(&one), | ||
reinterpret_cast<char*>(uniform_samples.data()), | ||
output_shape_shape, | ||
ov::element::from<T>(), | ||
global_seed, | ||
op_seed, | ||
initial_state); | ||
} else { | ||
const auto random_samples_count = shape_size<Shape>(random_samples_shape); | ||
OPENVINO_ASSERT(random_samples_count == total_output_elements_count, | ||
"Multinomial random_samples count is not equal to output_shape size"); | ||
uniform_samples.assign(random_samples, random_samples + random_samples_count); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the reference implementation (Template plugin) tests perspective, if the replacement transformation is always applied, the random_samples == nullptr
path is not tested at all.
if (!m_provided_random_samples) { | ||
std::mt19937 gen; | ||
if (m_global_seed == 0 && m_op_seed == 0) { | ||
gen.seed(std::time(nullptr)); | ||
} else { | ||
std::seed_seq seed{m_global_seed, m_op_seed}; | ||
gen.seed(seed); | ||
} | ||
const auto gen_max = static_cast<float>(std::mt19937::max()); | ||
std::generate(m_random_samples.begin(), m_random_samples.end(), [&]() { | ||
return static_cast<P>(static_cast<float>(gen()) / gen_max); | ||
}); | ||
} else { | ||
std::seed_seq seed{m_global_seed, m_op_seed}; | ||
gen.seed(seed); | ||
const auto* samples = getSrcDataAtPortAs<const P>(RANDOM_SAMPLES_PORT); | ||
m_random_samples.assign(samples, samples + m_output_elements_count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the Multinomial replacement transformation is always applied as common transformation, the if (!m_provided_random_samples)
path is not tested for CPU by any tests.
|
||
private: | ||
ov::element::Type_t m_convert_type; | ||
bool m_with_replacement; | ||
bool m_log_probs; | ||
uint64_t m_global_seed; | ||
uint64_t m_op_seed; | ||
op::PhiloxAlignment m_alignment; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new attribute is actually used only by the introduced transformation, but not inside the Multinomial logic (ref or CPU).
Co-authored-by: Katarzyna Mitrus <[email protected]>
STATUS:
ON HOLD, waiting for info about opset version (discussion below) + priority shift
Details:
Tickets: