-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
[NPUW] L0 allocation improvements #27011
[NPUW] L0 allocation improvements #27011
Conversation
@@ -372,7 +373,7 @@ void ov::npuw::JustInferRequest::bind_global_parameters(std::size_t idx) { | |||
auto& comp_model_desc = m_npuw_model->m_compiled_submodels[idx]; | |||
const auto real_idx = comp_model_desc.replaced_by.value_or(idx); | |||
|
|||
const bool do_copy = needs_copy(idx); | |||
const bool do_copy = !m_alloc_required && needs_copy(idx); |
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.
Not sure if it's correct
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.
Here we check global parameters. The idea is that global parameters allocation solely depends on m_alloc_required
- if it's set, params will be allocated on NPU
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.
haven't checked on this yet
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.
I think m_alloc_required
is overall misleading. It is always required.
Also, you've missed this in my past L0 PR: dmatveev#5
More precisely, this part: https://github.com/dmatveev/openvino/blob/e7d62f1a4412f639d0fb112e4f5647eeff9a1b8e/src/plugins/intel_npu/src/plugin/npuw/just_sync_infer_request.cpp#L117
And then this part: https://github.com/dmatveev/openvino/blob/e7d62f1a4412f639d0fb112e4f5647eeff9a1b8e/src/plugins/intel_npu/src/plugin/npuw/just_sync_infer_request.cpp#L370
The backstory here is that, even if you've allocated your model-global input tensors yourself, they may be overwritten. Even our scripts carelessly do this, unfortunately. So what you need to do is to keep track of the tensors you allocate (maybe you can just memorize the pointers in your new allocTensor
method) and check if the tensors' your working with are still "known" to you.
Once there's a set_tensor
call - you loose it and your m_alloc_required
flag doesn't tell truth.
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.
Good point, thanks
src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.cpp
Outdated
Show resolved
Hide resolved
src/plugins/intel_npu/src/plugin/npuw/just_sync_infer_request.cpp
Outdated
Show resolved
Hide resolved
@@ -372,7 +373,7 @@ void ov::npuw::JustInferRequest::bind_global_parameters(std::size_t idx) { | |||
auto& comp_model_desc = m_npuw_model->m_compiled_submodels[idx]; | |||
const auto real_idx = comp_model_desc.replaced_by.value_or(idx); | |||
|
|||
const bool do_copy = needs_copy(idx); | |||
const bool do_copy = !m_alloc_required && needs_copy(idx); |
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.
haven't checked on this yet
src/plugins/intel_npu/src/plugin/npuw/just_sync_infer_request.cpp
Outdated
Show resolved
Hide resolved
{ | ||
std::lock_guard<std::mutex> guard(m_alloc_mutex); | ||
m_remote_ctx = m_npuw_model->get_plugin()->get_core()->get_default_context(device)._ptr; | ||
remote_tensor = m_remote_ctx->create_host_tensor(type, shape); | ||
allocated_tensor = ov::make_tensor(remote_tensor); |
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.
why do you need a mutex here? you call allocTensor
from multiple threads?
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.
Just in case since we do guard allocation in banks. Less error prone in the future
src/plugins/intel_npu/src/plugin/npuw/just_sync_infer_request.hpp
Outdated
Show resolved
Hide resolved
m_spatial_io[real_idx].input_tails[p.idx] = ov::get_tensor_impl( | ||
allocTensor(iport.get_element_type(), iport.get_shape(), *proto_comp_model_desc.device_it)); |
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.
just thinking.. if you're using allocTensor only where we store ITensors, why can't allocTensor
return the ITensor
so you don't need to call get_tensor_impl
everywhere?
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.
Done
@@ -153,7 +154,7 @@ ov::npuw::JustInferRequest::JustInferRequest(const std::shared_ptr<ov::npuw::Com | |||
LOG_INFO("Preallocating input tensors..."); | |||
for (size_t i = 0; i < m_npuw_model->inputs().size(); i++) { | |||
const auto& port = m_npuw_model->inputs()[i]; | |||
m_input_tensors.push_back(ov::get_tensor_impl(ov::Tensor(port.get_element_type(), port.get_shape()))); | |||
m_input_tensors.push_back(ov::get_tensor_impl(allocTensor(port.get_element_type(), port.get_shape()))); |
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.
maybe we need an overload for allocTensor
which takes ov::Input<ov::Node>
/ ov::Output<ov::Node>
.
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.
note - you're not passing any device here. And you have ="NPU"
as the default parameter..
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.
About the default device - that was the idea
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.
Done
@@ -372,7 +373,7 @@ void ov::npuw::JustInferRequest::bind_global_parameters(std::size_t idx) { | |||
auto& comp_model_desc = m_npuw_model->m_compiled_submodels[idx]; | |||
const auto real_idx = comp_model_desc.replaced_by.value_or(idx); | |||
|
|||
const bool do_copy = needs_copy(idx); | |||
const bool do_copy = !m_alloc_required && needs_copy(idx); |
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.
I think m_alloc_required
is overall misleading. It is always required.
Also, you've missed this in my past L0 PR: dmatveev#5
More precisely, this part: https://github.com/dmatveev/openvino/blob/e7d62f1a4412f639d0fb112e4f5647eeff9a1b8e/src/plugins/intel_npu/src/plugin/npuw/just_sync_infer_request.cpp#L117
And then this part: https://github.com/dmatveev/openvino/blob/e7d62f1a4412f639d0fb112e4f5647eeff9a1b8e/src/plugins/intel_npu/src/plugin/npuw/just_sync_infer_request.cpp#L370
The backstory here is that, even if you've allocated your model-global input tensors yourself, they may be overwritten. Even our scripts carelessly do this, unfortunately. So what you need to do is to keep track of the tensors you allocate (maybe you can just memorize the pointers in your new allocTensor
method) and check if the tensors' your working with are still "known" to you.
Once there's a set_tensor
call - you loose it and your m_alloc_required
flag doesn't tell truth.
if (!m_alloc_required || device == "CPU") { | ||
return ov::Tensor(type, shape); | ||
} |
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 and having device="NPU"
by default is looks a very obscure way to allocate the tensors in the right region.
So you're using this method in tree contexts:
- For function results - where
device
is taken into account - For global inputs, where
device
is not passed and defaults to"NPU"
(meh) - For global results, same?
Also the logic completely discards the WEIGHTS_BANK_ALLOC
setting we have here. OK, this is not weights, but that flag exists for a reason you know. Why do we think we bypass that problem here?
A clearer way how it could've been done:
- No default arguments here - just always pass the device for yourself
- Add two methods to the
CompiledModel
so frame the decision making logic:std::string global_mem_device()
std::string funcall_mem_device(idx)
The first only takes device distribution into account.
The second takes the subgraph affinity into account (you can access it).
BOTH should also take the BANK_ALLOC into account if that's set.
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.
Done
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.
Minor comments here
for (std::size_t idx = 0; idx < m_compiled_submodels.size(); ++idx) { | ||
auto& comp_model_desc = m_compiled_submodels[idx]; | ||
if (!comp_model_desc.compiled_model) { | ||
continue; | ||
} | ||
if (ov::npuw::util::starts_with(*comp_model_desc.device_it, "NPU")) { | ||
return "NPU"; | ||
} | ||
} |
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.
Device distribution would be a simpler check
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.
There is only log_device_distribution
which goes over the submodels and prints info in-place. It's not stored anywhere
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.
ohh...
auto& comp_model_desc = m_compiled_submodels[idx]; | ||
if (ov::npuw::util::starts_with(*comp_model_desc.device_it, "NPU")) { | ||
return "NPU"; | ||
} | ||
|
||
return "CPU"; |
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 is strange, what it is supposed to be is
auto& comp_model_desc = m_compiled_submodels[idx]; | |
if (ov::npuw::util::starts_with(*comp_model_desc.device_it, "NPU")) { | |
return "NPU"; | |
} | |
return "CPU"; | |
return *comp_model_desc.device_it; |
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.
So the only point was to take BANK_ALLOC into account if it is set - and do it in just one place
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.
Done
for (std::size_t idx = 0; idx < m_compiled_submodels.size(); ++idx) { | ||
auto& comp_model_desc = m_compiled_submodels[idx]; | ||
if (!comp_model_desc.compiled_model) { | ||
continue; | ||
} | ||
if (ov::npuw::util::starts_with(*comp_model_desc.device_it, "NPU")) { | ||
return "NPU"; | ||
} | ||
} |
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.
ohh...
return "CPU"; | ||
|
||
// Force globally set device if set | ||
const std::string device_alloc = m_cfg.get<::intel_npu::NPUW_WEIGHTS_BANK_ALLOC>(); | ||
if (!device_alloc.empty()) { | ||
return device_alloc; | ||
} | ||
|
||
auto& comp_model_desc = m_compiled_submodels[idx]; | ||
return *comp_model_desc.device_it; |
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.
Static analysis will for sure fire unreachable code here, so the past body should've stayed under #if 0
. But let's see if it works
EISW-142611