#ifndef GRAPH_BACKEND_DNNL_PASSES_MEMORY_PLANNING_HPP
#define GRAPH_BACKEND_DNNL_PASSES_MEMORY_PLANNING_HPP
#include <algorithm>
#include <map>
#include <memory>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include <unordered_map>
#include "graph/interface/value.hpp"
#include "graph/utils/utils.hpp"
#include "graph/backend/dnnl/scratchpad.hpp"
#include "graph/backend/dnnl/subgraph.hpp"
#include "oneapi/dnnl/dnnl.hpp"
namespace dnnl {
namespace impl {
namespace graph {
namespace dnnl_impl {
class execution_args_set_t {
public:
execution_args_set_t() = default;
execution_args_set_t(const execution_args_set_t &) = delete;
execution_args_set_t(execution_args_set_t &&) = delete;
execution_args_set_t &operator=(const execution_args_set_t &) = delete;
execution_args_set_t &operator=(execution_args_set_t &&) = delete;
std::shared_ptr<execution_args_set_t> clone() const;
void clear();
const std::vector<exec_args> &get_exec_args() const {
return topo_ordered_exec_args_;
}
const std::unordered_map<value_t *, memory> &get_value_mem_map() const {
return value_mem_map_;
}
const std::vector<std::pair<dnnl::memory, size_t>> &
get_mems_use_external_inputs() const {
return mems_use_external_inputs_;
}
const std::vector<std::pair<dnnl::memory, size_t>> &
get_mems_use_external_outputs() const {
return mems_use_external_outputs_;
}
const std::vector<std::pair<dnnl::memory, size_t>> &
get_mems_use_internal_temporary() const {
return mems_use_internal_temporary_;
}
const std::vector<std::pair<dnnl::memory, size_t>> &
get_mems_use_internal_persistent() const {
return mems_use_internal_persistent_;
}
std::vector<dnnl::memory::desc> get_persistent_mem_desc_list() const {
std::vector<dnnl::memory::desc> mds;
mds.reserve(mems_use_internal_persistent_.size());
for (auto &mem_offkey : mems_use_internal_persistent_) {
auto md = mem_offkey.first.get_desc();
mds.emplace_back(md);
}
return mds;
}
void add_exec_args(const exec_args &args) {
topo_ordered_exec_args_.emplace_back(args);
}
void reset_exec_args(size_t idx, const exec_args &args) {
if (idx >= topo_ordered_exec_args_.size()) return;
topo_ordered_exec_args_[idx] = args;
}
void add_value_mem_map(const std::pair<value_t *, memory> &map) {
value_mem_map_.insert(map);
}
void add_mem_use_external_inputs(
const std::pair<dnnl::memory, size_t> &mem_idx) {
mems_use_external_inputs_.emplace_back(mem_idx);
}
void add_mem_use_external_outputs(
const std::pair<dnnl::memory, size_t> &mem_idx) {
mems_use_external_outputs_.emplace_back(mem_idx);
}
void add_mem_use_internal_temporary(
const std::pair<dnnl::memory, size_t> &mem_offkey) {
mems_use_internal_temporary_.emplace_back(mem_offkey);
}
void add_mem_use_internal_persistent(
const std::pair<dnnl::memory, size_t> &mem_offkey) {
mems_use_internal_persistent_.emplace_back(mem_offkey);
}
bool find_value_mem_map(value_t *key, memory &mem) const {
auto pos = value_mem_map_.find(key);
if (pos != value_mem_map_.end()) {
mem = pos->second;
return true;
}
return false;
}
private:
std::vector<std::pair<dnnl::memory, size_t>> mems_use_external_inputs_;
std::vector<std::pair<dnnl::memory, size_t>> mems_use_external_outputs_;
std::vector<std::pair<dnnl::memory, size_t>> mems_use_internal_temporary_;
std::vector<std::pair<dnnl::memory, size_t>> mems_use_internal_persistent_;
std::unordered_map<value_t *, memory> value_mem_map_;
std::vector<exec_args> topo_ordered_exec_args_;
};
class alias_analyzer_t {
public:
alias_analyzer_t() = default;
void clear();
status_t run(std::shared_ptr<subgraph_t> &sg);
std::vector<const value_t *> get_alias_outputs(const value_t *input) const;
const value_t *get_alias_input(const value_t *output) const;
std::vector<const value_t *> get_all_aliases(const value_t *val) const;
private:
std::unordered_map<const value_t *, const value_t *> alias_map_;
std::unordered_multimap<const value_t *, const value_t *>
reverse_alias_map_;
};
class buffer_assigner_t {
public:
explicit buffer_assigner_t(const size_t match_range)
: match_range_(match_range) {}
size_t request(size_t size) {
if (size == 0) return -1;
if (match_range_ == 0) return this->alloc(size);
auto begin = free_.lower_bound(size / match_range_);
auto mid = free_.lower_bound(size);
auto end = free_.upper_bound(size * match_range_);
auto it = mid;
if (it != end) {
buffer_info_t *e = it->second;
e->max_bytes_ = std::max(size, e->max_bytes_);
free_.erase(it);
return e->id_;
}
if (it != begin) {
--it;
buffer_info_t *e = it->second;
e->max_bytes_ = std::max(size, e->max_bytes_);
free_.erase(it);
return e->id_;
}
return this->alloc(size);
}
void release(size_t id) {
assertm(id < data_.size() || id == static_cast<size_t>(-1),
"invalid buffer id");
if (id == static_cast<size_t>(-1)) return;
buffer_info_t *e = data_[id].get();
free_.insert({e->max_bytes_, e});
}
size_t query_size(size_t id) const {
assertm(id < data_.size() || id == static_cast<size_t>(-1),
"invalid buffer id");
if (id == static_cast<size_t>(-1)) return 0;
return data_[id]->max_bytes_;
}
void clear() {
free_.clear();
data_.clear();
}
private:
size_t alloc(size_t size) {
size_t id = static_cast<size_t>(data_.size());
std::unique_ptr<buffer_info_t> ptr(new buffer_info_t(id, size));
data_.emplace_back(std::move(ptr));
return id;
}
struct buffer_info_t {
buffer_info_t(size_t id, size_t size) : id_(id), max_bytes_(size) {}
size_t id_;
size_t max_bytes_;
};
size_t match_range_;
std::multimap<size_t, buffer_info_t *> free_;
std::vector<std::unique_ptr<buffer_info_t>> data_;
};
class memory_planner_t {
public:
memory_planner_t()
: persistent_buffer_assigner_(16), temporary_buffer_assigner_(16) {}
memory_planner_t(memory_planner_t &&) = delete;
memory_planner_t(const memory_planner_t &other) = delete;
memory_planner_t &operator=(const memory_planner_t &) = delete;
memory_planner_t &operator=(memory_planner_t &&) = delete;
grantor_t internal_persistent_grantor(char *base_ptr) const {
return persistent_registry_.grantor(base_ptr);
}
grantor_t internal_temporary_grantor(char *base_ptr) const {
return temporary_registry_.grantor(base_ptr);
}
size_t total_internal_persistent_size() const {
return persistent_registry_.size();
}
size_t total_internal_temporary_size() const {
return temporary_registry_.size();
}
execution_args_set_t &get_exec_args_set() { return exec_args_set_; }
status_t run(std::shared_ptr<subgraph_t> &sg);
const std::vector<inplace_pair_t> &get_subgraph_inplace_pairs() const {
return inplace_pairs_;
}
std::string get_memory_info(const value_t *val) const {
std::string str;
auto pos = buffer_assignments_.find(val);
if (pos == buffer_assignments_.end()) return str;
assign_info_t info = pos->second;
if (info.kind_ == internal_persistent) {
str += "persistent_";
} else if (info.kind_ == internal_temporary) {
str += "temporary_";
} else if (info.kind_ == external_input) {
str += "external_in_";
} else if (info.kind_ == external_output) {
str += "external_out_";
} else {
}
str += std::to_string(info.index_);
return str;
}
private:
enum buffer_kind_t {
external_input = 0,
external_output,
internal_temporary,
internal_persistent,
};
class assign_info_t {
public:
assign_info_t(buffer_kind_t kind, size_t index)
: kind_(kind), index_(index) {}
assign_info_t() = default;
assign_info_t(const assign_info_t &other) = default;
assign_info_t &operator=(const assign_info_t &other) = default;
bool operator==(const assign_info_t &other) const {
return kind_ == other.kind_ && index_ == other.index_;
}
bool operator!=(const assign_info_t &other) const {
return !(*this == other);
}
buffer_kind_t kind_;
size_t index_; };
struct time_bound_t {
size_t start_;
size_t end_;
};
void clear() {
alias_analyzer_.clear();
buffer_assignments_.clear();
exec_args_set_.clear();
persistent_buffer_assigner_.clear();
temporary_buffer_assigner_.clear();
persistent_registry_.clear();
temporary_registry_.clear();
external_inputs_live_range_.clear();
inplace_pairs_.clear();
}
status_t assign_external_inputs_buffer(std::shared_ptr<subgraph_t> &sg,
const std::vector<logical_tensor_t> &inputs);
status_t assign_external_outputs_buffer(std::shared_ptr<subgraph_t> &sg,
const std::vector<logical_tensor_t> &outputs);
status_t assign_internal_persistent_buffer(std::shared_ptr<subgraph_t> &sg);
status_t assign_internal_temporary_buffer(std::shared_ptr<subgraph_t> &sg,
const std::unordered_map<value_t *, size_t> &edge_ref_count,
bool enable_standard_sharing);
status_t prepare_subgraph_inplace_pairs(
std::shared_ptr<subgraph_t> &sg, bool enable_standard_sharing);
status_t book_buffers(std::shared_ptr<subgraph_t> &sg);
status_t prepare_execution_args_set(
std::shared_ptr<subgraph_t> &sg, const dnnl::engine &p_engine);
execution_args_set_t exec_args_set_;
std::unordered_map<const value_t *, assign_info_t> buffer_assignments_;
buffer_assigner_t persistent_buffer_assigner_;
buffer_assigner_t temporary_buffer_assigner_;
registry_t persistent_registry_;
registry_t temporary_registry_;
alias_analyzer_t alias_analyzer_;
std::unordered_map<const assign_info_t *, time_bound_t>
external_inputs_live_range_;
std::vector<inplace_pair_t> inplace_pairs_;
};
} } } }
#endif