#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include "common/engine.hpp"
#include "common/utils.hpp"
#if DNNL_CPU_RUNTIME != DNNL_RUNTIME_NONE
#include "cpu/cpu_engine.hpp"
#endif
#if DNNL_GPU_RUNTIME == DNNL_RUNTIME_OCL
#include "xpu/ocl/engine_factory.hpp"
#endif
#ifdef DNNL_WITH_SYCL
#include "xpu/sycl/engine_factory.hpp"
#endif
#include "graph/interface/backend.hpp"
#include "graph/interface/constant_tensor_cache.hpp"
#include "graph/utils/utils.hpp"
#ifdef _WIN32
#include <windows.h>
#endif
namespace std {
template <>
struct hash<dnnl::impl::engine_kind_t> {
using argument_type = dnnl::impl::engine_kind_t;
using result_type = std::size_t;
result_type operator()(const argument_type &eng_kind) const {
return static_cast<result_type>(eng_kind);
}
};
}
namespace dnnl {
namespace impl {
namespace graph {
using c_key_t = constant_tensor_cache_t::key_t;
using c_value_t = constant_tensor_cache_t::value_t;
static size_t get_timestamp() {
return std::chrono::steady_clock::now().time_since_epoch().count();
}
constant_tensor_cache_t::constant_tensor_cache_t(
size_t capacity_in_bytes, const std::string &name)
: name_(name), capacity_in_bytes_(capacity_in_bytes), counter_(1) {
constant_map_ = impl::utils::make_unique<
std::unordered_map<c_key_t, timed_entry_t>>();
}
constant_tensor_cache_t::~constant_tensor_cache_t() {
if (constant_map().empty()) return;
#if defined(_WIN32) && defined(DNNL_WITH_SYCL)
HMODULE handle = LoadLibraryExA(
"ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!handle) {
constant_map_.release();
return;
}
auto f = reinterpret_cast<BOOLEAN (*)(void)>(
GetProcAddress(handle, "RtlDllShutdownInProgress"));
if (!f) {
auto ret = FreeLibrary(handle);
assert(ret);
MAYBE_UNUSED(ret);
constant_map_.release();
return;
}
bool is_process_termination_in_progress = f();
auto ret = FreeLibrary(handle);
assert(ret);
MAYBE_UNUSED(ret);
if (is_process_termination_in_progress) {
for (auto it = constant_map().begin(); it != constant_map().end();) {
#ifdef DNNL_WITH_SYCL
++it;
#else
it = constant_map().erase(it);
#endif
}
constant_map_.release();
} else {
constant_map_.reset();
}
#else
constant_map_.reset();
#endif
}
status_t constant_tensor_cache_t::set_capacity(size_t capacity) {
lock_write();
capacity_in_bytes_ = capacity;
evict(get_size()); unlock_write();
return status::success;
}
size_t constant_tensor_cache_t::get_capacity() {
return capacity_in_bytes_.load();
}
c_key_t constant_tensor_cache_t::combine_key(
c_key_t backend_id, c_key_t backend_specific_key) {
size_t key = (backend_specific_key << BACKEND_ID_LENGTH)
| (backend_id & (size_t)((1 << BACKEND_ID_LENGTH) - 1));
return key;
}
c_value_t constant_tensor_cache_t::get_or_add(c_key_t backend_id,
c_key_t backend_specific_key, size_t size, const c_value_t &value) {
if (!size) { return c_value_t(); }
c_key_t key = combine_key(backend_id, backend_specific_key);
lock_read();
if (capacity_in_bytes_ == 0) {
unlock_read();
return c_value_t();
}
auto e = get(key);
if (e.valid()) {
unlock_read();
return e;
}
unlock_read();
lock_write();
if (capacity_in_bytes_ == 0) {
unlock_write();
return c_value_t();
}
e = get(key);
if (!e.valid()) {
add(key, size, value);
}
unlock_write();
return e;
}
void constant_tensor_cache_t::remove_if_exist(
c_key_t backend_id, c_key_t backend_specific_key) {
c_key_t key = combine_key(backend_id, backend_specific_key);
lock_write();
if (constant_map().count(key) == 0) {
unlock_write();
} else {
auto &item = constant_map().at(key);
item.value_.get()->notify_evict();
constant_map().erase(key);
unlock_write();
}
}
size_t constant_tensor_cache_t::get_size() const {
size_t total_size = 0;
for (const auto &pair : constant_map()) {
total_size += pair.second.value_.get()->size();
}
return total_size;
}
void constant_tensor_cache_t::add(
const c_key_t &key, size_t size, const c_value_t &constant) {
size_t current_size = get_size();
if (current_size + size > capacity_in_bytes_) { return; }
size_t timestamp = get_timestamp();
auto res = constant_map().emplace(std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(constant, timestamp));
UNUSED(res);
assert(res.second);
}
c_value_t constant_tensor_cache_t::get(const c_key_t &key) {
auto it = constant_map().find(key);
if (it == constant_map().end()) return c_value_t();
size_t timestamp = get_timestamp();
it->second.timestamp_.store(timestamp);
return it->second.value_;
}
void constant_tensor_cache_t::evict(size_t n) {
using v_t = std::unordered_map<c_key_t, timed_entry_t>::value_type;
if (n == get_size()) {
constant_map().clear();
return;
}
size_t evicted_size = 0;
while (evicted_size < n) {
auto it = std::min_element(constant_map().begin(), constant_map().end(),
[&](const v_t &left, const v_t &right) {
return left.second.timestamp_.load(std::memory_order_relaxed)
< right.second.timestamp_.load(std::memory_order_relaxed);
});
evicted_size += it->second.value_.get()->size();
auto res = constant_map().erase(it->first);
UNUSED(res);
assert(res);
}
}
static std::unique_ptr<impl::engine_factory_t> get_engine_factory(
impl::engine_kind_t kind, impl::runtime_kind_t runtime_kind) {
#if DNNL_CPU_RUNTIME != DNNL_RUNTIME_NONE
if (kind == impl::engine_kind::cpu && is_native_runtime(runtime_kind)) {
return std::unique_ptr<impl::engine_factory_t>(
new impl::cpu::cpu_engine_factory_t());
}
#endif
#if DNNL_GPU_RUNTIME == DNNL_RUNTIME_OCL
if (kind == engine_kind::gpu && runtime_kind == runtime_kind::ocl) {
return std::unique_ptr<engine_factory_t>(
new xpu::ocl::engine_factory_t(kind));
}
#endif
#ifdef DNNL_WITH_SYCL
if (runtime_kind == impl::runtime_kind::sycl)
return xpu::sycl::get_engine_factory(kind);
#endif
return nullptr;
}
using cache_ptr = std::shared_ptr<constant_tensor_cache_t>;
struct global_cache_manager_t {
public:
global_cache_manager_t(const global_cache_manager_t &) = delete;
global_cache_manager_t(global_cache_manager_t &&) = delete;
global_cache_manager_t &operator=(const global_cache_manager_t &) = delete;
global_cache_manager_t &operator=(global_cache_manager_t &&) = delete;
static global_cache_manager_t &get_instance() {
static global_cache_manager_t instance;
return instance;
}
std::unordered_map<impl::engine_kind_t, std::vector<cache_ptr>> &
get_caches() {
return caches;
}
std::unordered_map<impl::engine_kind_t, size_t> &get_default_capacities() {
return default_capacities;
}
std::unordered_map<impl::engine_kind_t, size_t> &get_user_capacities() {
return user_capacities;
}
private:
global_cache_manager_t() {
std::string str = impl::getenv_string_user(
"GRAPH_CONSTANT_TENSOR_CACHE_CAPACITY");
auto configs = graph::utils::split(str, ';');
for (const auto &config : configs) {
auto fields = graph::utils::split(config, ':');
impl::engine_kind_t env_eng_kind = impl::engine_kind::any_engine;
if (!fields.empty() && !fields[0].empty()) {
const std::string &eng_kind = fields[0];
assertm(eng_kind == "cpu" || eng_kind == "gpu",
"engine kind must be cpu or gpu");
env_eng_kind = eng_kind == "cpu" ? impl::engine_kind::cpu
: impl::engine_kind::gpu;
}
if (env_eng_kind == impl::engine_kind::any_engine) continue;
if (fields.size() > 1 && !fields[1].empty()) {
try {
size_t capacity = std::stoll(fields[1]);
static constexpr size_t converter = 1024 * 1024;
size_t capacity_byte
= capacity < std::numeric_limits<size_t>::max()
/ converter
? capacity * converter
: std::numeric_limits<size_t>::max();
user_capacities[env_eng_kind] = capacity_byte;
} catch (const std::invalid_argument &e) {
VERROR(graph, constant_tensor_cache,
"'%s': capacity setting is invalid ", e.what());
} catch (const std::out_of_range &e) {
VERROR(graph, constant_tensor_cache,
"'%s': capacity setting exceeds numerical "
"representation limit ",
e.what());
}
}
}
std::vector<impl::engine_kind_t> eng_kinds {
impl::engine_kind::cpu, impl::engine_kind::gpu};
for (auto &kind : eng_kinds) {
auto ef = get_engine_factory(kind, impl::get_default_runtime(kind));
if (!ef) continue;
auto device_count = ef->count();
default_capacities[kind] = 0;
size_t capacity_in_bytes = user_capacities.count(kind)
? user_capacities[kind]
: default_capacities[kind];
std::vector<cache_ptr> cache_list(device_count);
for (size_t id = 0; id < device_count; id++) {
cache_ptr &cache = cache_list[id];
cache.reset(new constant_tensor_cache_t(capacity_in_bytes),
[](constant_tensor_cache_t *ptr) {
return ptr->release();
});
}
caches.insert({kind, std::move(cache_list)});
}
}
std::unordered_map<impl::engine_kind_t, std::vector<cache_ptr>> caches;
std::unordered_map<impl::engine_kind_t, size_t> default_capacities;
std::unordered_map<impl::engine_kind_t, size_t> user_capacities;
};
constant_tensor_cache_t *get_constant_tensor_cache(
impl::engine_kind_t eng_kind, size_t index) {
std::vector<cache_ptr> &cache_list
= global_cache_manager_t::get_instance().get_caches().at(eng_kind);
if (index >= cache_list.size()) {
assertm(false, "given device index exceeds the detected device number");
return nullptr;
}
cache_ptr &cache = cache_list[index];
return cache.get();
}
} } }
dnnl::impl::graph::status_t dnnl_graph_set_constant_tensor_cache(int flag) {
if (flag < 0) return dnnl::impl::graph::status::invalid_arguments;
size_t capacity = flag == 0 ? 0 : std::numeric_limits<size_t>::max();
dnnl::impl::graph::status_t ret;
ret = dnnl_graph_set_constant_tensor_cache_capacity(
dnnl::impl::engine_kind::cpu, capacity);
if (ret != dnnl::impl::graph::status::success) return ret;
ret = dnnl_graph_set_constant_tensor_cache_capacity(
dnnl::impl::engine_kind::gpu, capacity);
if (ret != dnnl::impl::graph::status::success) return ret;
return dnnl::impl::graph::status::success;
}
dnnl::impl::graph::status_t dnnl_graph_get_constant_tensor_cache(int *flag) {
if (flag == nullptr) return dnnl::impl::graph::status::invalid_arguments;
size_t cpu_flag, gpu_flag;
dnnl::impl::graph::status_t ret;
ret = dnnl_graph_get_constant_tensor_cache_capacity(
dnnl::impl::engine_kind::cpu, &cpu_flag);
if (ret != dnnl::impl::graph::status::success) return ret;
ret = dnnl_graph_get_constant_tensor_cache_capacity(
dnnl::impl::engine_kind::gpu, &gpu_flag);
if (ret != dnnl::impl::graph::status::success) return ret;
*flag = static_cast<int>((cpu_flag != 0) || (gpu_flag != 0));
return dnnl::impl::graph::status::success;
}
dnnl::impl::graph::status_t dnnl_graph_set_constant_tensor_cache_capacity(
dnnl_engine_kind_t eng_kind, size_t size) {
static constexpr size_t converter = 1024 * 1024;
size_t size_byte = size < std::numeric_limits<size_t>::max() / converter
? size * converter
: std::numeric_limits<size_t>::max();
dnnl::impl::graph::global_cache_manager_t::get_instance()
.get_user_capacities()[eng_kind]
= size_byte;
if (dnnl::impl::graph::global_cache_manager_t::get_instance()
.get_caches()
.count(eng_kind)) {
for (auto &cache :
dnnl::impl::graph::global_cache_manager_t::get_instance()
.get_caches()
.at(eng_kind)) {
if (cache) {
cache->set_capacity(
dnnl::impl::graph::global_cache_manager_t::
get_instance()
.get_user_capacities()[eng_kind]);
}
}
}
return dnnl::impl::graph::status::success;
}
dnnl::impl::graph::status_t dnnl_graph_get_constant_tensor_cache_capacity(
dnnl_engine_kind_t eng_kind, size_t *size) {
static constexpr size_t converter = 1024 * 1024;
if (dnnl::impl::graph::global_cache_manager_t::get_instance()
.get_user_capacities()
.count(eng_kind)) {
*size = dnnl::impl::graph::global_cache_manager_t::get_instance()
.get_user_capacities()
.at(eng_kind)
/ converter;
} else {
*size = dnnl::impl::graph::global_cache_manager_t::get_instance()
.get_default_capacities()[eng_kind]
/ converter;
}
return dnnl::impl::graph::status::success;
}