Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (C) 2026 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <map>
#include <memory>

#include "openvino/runtime/aligned_buffer.hpp"
#include "openvino/runtime/iremote_context.hpp"
#include "openvino/runtime/iremote_tensor.hpp"
#include "openvino/runtime/so_ptr.hpp"

namespace ov {

/// \brief Buffer descriptor that carries the set of remote contexts which co-own
/// (or should receive) the underlying allocation.
///
/// Extends the base IBufferDescriptor interface so that consumers can
/// retrieve the associated remote contexts via get_remote_contexts().

class OPENVINO_RUNTIME_API SharedContextBufferDescriptor : public ov::IBufferDescriptor {
public:
struct RemoteContextPtrLess {
bool operator()(const ov::SoPtr<ov::IRemoteContext>& lhs,
const ov::SoPtr<ov::IRemoteContext>& rhs) const noexcept {
return std::less<const ov::IRemoteContext*>{}(lhs._ptr.get(), rhs._ptr.get());
}
};

using RemoteContextsMap = std::map<ov::SoPtr<ov::IRemoteContext>,
ov::SoPtr<ov::IRemoteTensor>,
RemoteContextPtrLess>;

SharedContextBufferDescriptor(size_t id,
size_t offset,
size_t real_buffer_size,
const std::shared_ptr<ov::AlignedBuffer>& source_buffer,
RemoteContextsMap remote_contexts);

size_t get_id() const override;
size_t get_offset() const override;
size_t get_real_buffer_size() const;
std::shared_ptr<ov::AlignedBuffer> get_source_buffer() const override;

const RemoteContextsMap& get_remote_contexts() const;

~SharedContextBufferDescriptor() override;

private:
size_t m_id = 0;
size_t m_offset = 0;
size_t m_real_buffer_size = 0;
std::weak_ptr<ov::AlignedBuffer> m_source_buffer;
RemoteContextsMap m_remote_contexts;
};

} // namespace ov
45 changes: 45 additions & 0 deletions src/inference/src/dev/shared_context_buffer_descriptor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (C) 2026 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "openvino/runtime/shared_context_buffer_descriptor.hpp"

#include <utility>

namespace ov {

SharedContextBufferDescriptor::SharedContextBufferDescriptor(
size_t id,
size_t offset,
size_t real_buffer_size,
const std::shared_ptr<ov::AlignedBuffer>& source_buffer,
RemoteContextsMap remote_contexts)
: m_id(id),
m_offset(offset),
m_real_buffer_size(real_buffer_size),
m_source_buffer(source_buffer),
m_remote_contexts(std::move(remote_contexts)) {}

SharedContextBufferDescriptor::~SharedContextBufferDescriptor() = default;

size_t SharedContextBufferDescriptor::get_id() const {
return m_id;
}

size_t SharedContextBufferDescriptor::get_offset() const {
return m_offset;
}

size_t SharedContextBufferDescriptor::get_real_buffer_size() const {
return m_real_buffer_size;
}

std::shared_ptr<ov::AlignedBuffer> SharedContextBufferDescriptor::get_source_buffer() const {
return m_source_buffer.lock();
}

const SharedContextBufferDescriptor::RemoteContextsMap& SharedContextBufferDescriptor::get_remote_contexts() const {
return m_remote_contexts;
}

} // namespace ov
36 changes: 35 additions & 1 deletion src/plugins/intel_gpu/src/plugin/ops/constant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "intel_gpu/plugin/program_builder.hpp"
#include "intel_gpu/plugin/common_utils.hpp"
#include "intel_gpu/plugin/remote_tensor.hpp"
#include "intel_gpu/op/convolution.hpp"

#include "openvino/core/weight_sharing_util.hpp"
Expand All @@ -28,6 +29,7 @@
#include "openvino/op/matmul.hpp"
#include "openvino/op/moe.hpp"
#include "openvino/op/util/binary_elementwise_bitwise.hpp"
#include "openvino/runtime/shared_context_buffer_descriptor.hpp"

#include "ov_ops/moe_compressed.hpp"

Expand Down Expand Up @@ -110,8 +112,40 @@ static void create_data(ProgramBuilder& p, const ov::Shape& const_shape, const s
p.primitive_ids[initialconstPrimID] = constPrimID;
p.profiling_ids.push_back(initialconstPrimID);
} else {
GPU_DEBUG_LOG << "[" << initialconstPrimID << ": constant] check whether the constant is a shared weight or not" << std::endl;
const auto shared_buffer = weight_sharing::Extension::get_constant_source_buffer(*op);
if (shared_buffer) {
GPU_DEBUG_LOG << "[" << initialconstPrimID << ": constant] check whether the constant memory has been already allocated" << std::endl;
auto shared_context_buffer = std::dynamic_pointer_cast<const ov::SharedContextBufferDescriptor>(shared_buffer->get_descriptor());
if (shared_context_buffer) {
const auto& remote_contexts = shared_context_buffer->get_remote_contexts();
GPU_DEBUG_LOG << "[" << initialconstPrimID << ": constant] shared between contexts:" << remote_contexts.size() << std::endl;
std::shared_ptr<IRemoteTensor> gpu_remote_tensor;
for (const auto& [remote_context, remote_tensor_ptr] : remote_contexts) {
GPU_DEBUG_LOG << "[" << initialconstPrimID << ": constant] check whether weight from the remote context: " << remote_context->get_device_name() << " is available on device: " << p.get_engine().get_device_info().dev_name << std::endl;
if (remote_context->get_device_name().find("GPU") != std::string::npos) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michal-miotk Do you know what might be the right way to check for GPU device?
My concerns are:

  1. dGPU vs iGPU
  2. Can there be multiple contexts for one device?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. maybe get_engine().get_device_info().dev_type == device_type::integrated_gpu

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

gpu_remote_tensor = remote_tensor_ptr._ptr;
break;
}
}
if (gpu_remote_tensor) {
GPU_DEBUG_LOG << "[" << initialconstPrimID << ": constant] layout "
<< constLayout.to_short_string() << ", weight_sharing(" << static_cast<const void*>(data) << ", " << constLayout.bytes_count() <<" bytes)" << std::endl;
auto iremote_tensor = std::dynamic_pointer_cast<ov::intel_gpu::RemoteTensorImpl>(gpu_remote_tensor);
auto mem = iremote_tensor->get_original_memory();
p.add_primitive(*op, cldnn::data(initialconstPrimID, mem));
p.blobMemCache[cache_key] = initialconstPrimID;
constPrimID = initialconstPrimID;
return;
}
} else {
GPU_DEBUG_LOG << "[" << initialconstPrimID << ": constant] layout "
<< constLayout.to_short_string() << ", ignore weight sharing, UNEXPECTED buffer type (" << static_cast<const void*>(data) << ", " << constLayout.bytes_count() <<" bytes)" << std::endl;
std::cout << "[" << initialconstPrimID << ": constant] layout "
<< constLayout.to_short_string() << ", ignore weight sharing, UNEXPECTED buffer type (" << static_cast<const void*>(data) << ", " << constLayout.bytes_count() <<" bytes)" << std::endl;
}
}
auto partial_upload = try_prepare_partial_upload(p, op, const_shape, out_dtype, constFormat, constLayout);

cldnn::memory::ptr mem = nullptr;

if (partial_upload.enabled) {
Expand Down
81 changes: 81 additions & 0 deletions src/plugins/intel_npu/src/plugin/npuw/llm_compiled_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "llm_compiled_model_utils.hpp"
#include "llm_infer_request.hpp"
#include "logging.hpp"
#include "shared_context_buffer.hpp"
#include "moe_transformations/apply_moe_device_routed_transforms.hpp"
#include "npuw_transformations/add_position_ids_param.hpp"
#include "npuw_transformations/convert_kvcache_to_precision.hpp"
Expand All @@ -22,6 +23,8 @@
#include "npuw_transformations/reshape_to_static.hpp"
#include "npuw_transformations/slice_out_embeds.hpp"
#include "npuw_transformations/split_kvcache_into_blocks.hpp"
#include "openvino/core/weight_sharing_util.hpp"
#include "openvino/core/rt_info/weightless_caching_attributes.hpp"
#include "openvino/op/convert.hpp"
#include "openvino/op/greater.hpp"
#include "openvino/op/ops.hpp"
Expand All @@ -36,8 +39,11 @@
#include "openvino/pass/pattern/op/wrap_type.hpp"
#include "openvino/pass/stateful_to_stateless.hpp"
#include "openvino/pass/validate.hpp"
#include "openvino/runtime/device_id_parser.hpp"
#include "openvino/runtime/iasync_infer_request.hpp"
#include "openvino/runtime/properties.hpp"
#include "openvino/runtime/shared_context_buffer_descriptor.hpp"
#include "openvino/util/mmap_object.hpp"
#include "partitioning/patterns/fold_const.hpp"
#include "partitioning/patterns/moe.hpp"
#include "partitioning/patterns/pre_compute.hpp"
Expand Down Expand Up @@ -668,6 +674,78 @@ void ov::npuw::LLMCompiledModel::compile_generate_model_variants(
m_kvcache_compiled = m_generate_compiled_variants.back();
}

std::string dump_shared_ctx(const ov::weight_sharing::Context& shared_ctx) {
std::ostringstream oss;
oss << "TODO print me" << std::endl;
(void)shared_ctx;
return oss.str();
Comment on lines +678 to +681
}

void ov::npuw::LLMCompiledModel::assign_shared_weight_to_model_if_possible(const std::shared_ptr<ov::Model> model, const std::shared_ptr<const ov::IPlugin>& plugin,
const ov::AnyMap& properties) {
NPUW_ASSERT(model && "Model for assigning shared weights must not be null");
NPUW_ASSERT(plugin && "Plugin for assigning shared weights must not be null");
auto shared_weight_property_it = properties.find("SHARED_WEIGHTS");
if (shared_weight_property_it == properties.end()) {
return;
}
std::vector<ov::SoPtr<ov::IRemoteContext>> remote_ctxs;
auto shared_device_contexts = ov::DeviceIDParser::get_hetero_devices(shared_weight_property_it->second.as<std::string>());
LOG_DEBUG("Create shared context over devices: " << shared_device_contexts.size());
for (const auto& ctx_name : shared_device_contexts) {
try {
LOG_DEBUG("trying to get context '" << ctx_name << "'");
remote_ctxs.push_back(plugin->get_core()->get_default_context(ctx_name));
} catch (const std::exception& e) {
LOG_WARN("SHARED_WEIGHTS: context '" << ctx_name << "' unavailable (" << e.what()
<< "); weights will NOT be shared for this context.");
}
}
LOG_INFO("Created shared contexts: " << remote_ctxs.size() << " out of " << shared_device_contexts.size());
if (remote_ctxs.empty() || remote_ctxs.size() == 1) {
LOG_INFO("Not enough shared contexts available, skipping shared weights assignment.");
return;
}
// TODO
// think about allocating a one large buffer with individual weights located sequentially
// and distinguished by a pair of offset and size.
// It may reduce fragmentation and improve locality.
// On the other hand, allocation of individual weights can be done in parallel and may be faster.
const size_t kMinRelocateBytes = ov::util::get_system_page_size(); // page-sized+; skip tiny scalar constants
NPUW_ASSERT(!m_shared_ctx_ptr && "NPU shared context must be empty before shared weight assignment.");
m_shared_ctx_ptr = std::make_unique<ov::weight_sharing::Context>();
for (const auto& op : model->get_ops()) {
auto not_shared_constant = std::dynamic_pointer_cast<ov::op::v0::Constant>(op);
// TODO devise better conditions to determine whether shared weights applicable or not
if (!not_shared_constant || not_shared_constant->get_byte_size() < kMinRelocateBytes) {
continue;
}
const size_t bytes = not_shared_constant->get_byte_size();
LOG_DEBUG("Allocating shared buffer for weight" << not_shared_constant->get_friendly_name() << ", size: " << bytes);
auto buffer = std::make_shared<ov::npuw::SharedContextBuffer>(bytes, remote_ctxs, kMinRelocateBytes);
ov::weight_sharing::set_weight_source(*m_shared_ctx_ptr, buffer);
auto shared_constant =
std::make_shared<ov::op::v0::Constant>(not_shared_constant->get_element_type(),
not_shared_constant->get_shape(), buffer);
ov::weight_sharing::set_constant(*m_shared_ctx_ptr, *shared_constant);
shared_constant->set_friendly_name(not_shared_constant->get_friendly_name());
ov::copy_runtime_info(not_shared_constant, shared_constant);
std::memcpy(buffer->get_ptr(), not_shared_constant->get_data_ptr(), bytes);

// Preserve the weightless-cache attribute: copy_runtime_info drops it (is_copyable()==false),
// and without it NPUW's Const wrapper treats every relocated weight as a brand-new Constant
// and eagerly copies it to host (a full second ~2 GB copy at partition time).
// CAVEAT (under investigation): keeping the weightless attr may make NPUW reconstruct the GPU
// prefill submodel's constants from the original .bin mmap (off our malloc) -> GPU share_usm
// sees in_range=0. Toggle via NO_WEIGHTLESS_ATTR=1 to test the GPU-share path.
if (!std::getenv("NO_WEIGHTLESS_ATTR")) {
ov::copy_weightless_cache_attr(not_shared_constant, shared_constant);
}
ov::replace_node(not_shared_constant, shared_constant);
LOG_INFO("[NPUW] SHARED_WEIGHTS: " << dump_shared_ctx(*m_shared_ctx_ptr));
}
}

ov::npuw::LLMCompiledModel::LLMCompiledModel(const std::shared_ptr<ov::Model>& model,
const std::shared_ptr<const ov::IPlugin>& plugin,
const ov::AnyMap& properties,
Expand Down Expand Up @@ -809,6 +887,9 @@ ov::npuw::LLMCompiledModel::LLMCompiledModel(const std::shared_ptr<ov::Model>& m
const uint32_t seq_len_dim = m_cfg.get<::intel_npu::NPUW_LLM_SEQ_LEN_DIM>();
KVAxesPosition axes{batch_dim, seq_len_dim};

// Assign shared buffers
assign_shared_weight_to_model_if_possible(model, plugin, properties);

LOG_DEBUG("Creating kvcache model as clone of passed one.");
auto kvcache_model = model->clone();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
#include "npuw_transformations/kv_axes_position.hpp"

namespace ov {
namespace weight_sharing {
struct Context;
}
namespace test {
namespace npuw {
struct LLMVariantSwitchTestAccess;
Expand Down Expand Up @@ -153,8 +156,10 @@ class LLMCompiledModel : public ov::npuw::ICompiledModel {
void compile_generate_model_variants(const std::vector<std::shared_ptr<ov::Model>>& generate_model_variants,
const std::shared_ptr<const ov::IPlugin>& plugin,
const ov::AnyMap& generate_config);

void assign_shared_weight_to_model_if_possible(const std::shared_ptr<ov::Model> model, const std::shared_ptr<const ov::IPlugin>& plugin,
const ov::AnyMap& properties);
bool m_is_eagle = false;
std::unique_ptr<ov::weight_sharing::Context> m_shared_ctx_ptr;
};

} // namespace npuw
Expand Down
Loading
Loading