77 */
88
99#include < algorithm>
10+ #include < cinttypes>
1011#include < cstdio>
1112#include < iostream>
1213#include < memory>
3132#include < executorch/extension/threadpool/threadpool.h>
3233#include < executorch/runtime/backend/interface.h>
3334#include < executorch/runtime/core/data_loader.h>
35+ #include < executorch/runtime/core/device_memory_buffer.h>
3436#include < executorch/runtime/core/exec_aten/util/scalar_type_util.h>
3537#include < executorch/runtime/executor/method.h>
3638#include < executorch/runtime/executor/program.h>
@@ -88,6 +90,7 @@ using ::executorch::extension::MmapDataLoader;
8890using ::executorch::extension::ET_BUNDLED_MODULE_NAMESPACE ::BundledModule;
8991using ::executorch::extension::pybindings::PyDataLoader;
9092using ::executorch::runtime::DataLoader;
93+ using ::executorch::runtime::DeviceMemoryBuffer;
9194using ::executorch::runtime::Error;
9295using ::executorch::runtime::EValue;
9396using ::executorch::runtime::EventTracerDebugLogLevel;
@@ -1077,12 +1080,22 @@ inline std::shared_ptr<ProgramState> load_program(
10771080// / A wrapper/util class for executorch memory allocations/manager.
10781081class ProgramMemory {
10791082 public:
1080- explicit ProgramMemory (std::vector<std::vector<uint8_t >>&& non_const_buffers)
1083+ // / @param sizes Byte size of each memory-planned buffer.
1084+ // / @param devices Device each planned buffer must live on, same indexing as
1085+ // / `sizes`. A buffer tagged non-CPU is allocated through the device
1086+ // / allocator; handing the backend a host pointer for it is undefined.
1087+ ProgramMemory (
1088+ std::vector<int64_t >&& sizes,
1089+ std::vector<executorch::aten::Device>&& devices)
10811090 : runtime_allocator_(),
1082- non_const_buffers_(std::move(non_const_buffers)),
1091+ planned_sizes_ (std::move(sizes)),
1092+ planned_devices_(std::move(devices)),
1093+ non_const_buffers_(allocate_host_buffers()),
1094+ device_buffers_(allocate_device_buffers()),
10831095 non_const_spans_(create_non_const_spans()),
10841096 non_const_allocator_(
1085- {non_const_spans_.data (), non_const_spans_.size ()}),
1097+ {non_const_spans_.data (), non_const_spans_.size ()},
1098+ {planned_devices_.data (), planned_devices_.size ()}),
10861099 mem_manager_(
10871100 &const_allocator_,
10881101 &non_const_allocator_,
@@ -1105,19 +1118,71 @@ class ProgramMemory {
11051118
11061119 MallocMemoryAllocator temp_allocator_{};
11071120
1121+ std::vector<int64_t > planned_sizes_;
1122+
1123+ std::vector<executorch::aten::Device> planned_devices_;
1124+
1125+ // Backs CPU-tagged buffers; the entry is empty for a device-tagged buffer.
11081126 std::vector<std::vector<uint8_t >> non_const_buffers_;
11091127
1128+ // Backs device-tagged buffers; the entry is empty for a CPU-tagged buffer.
1129+ // Parallel to non_const_buffers_ so both index by planned buffer id.
1130+ std::vector<DeviceMemoryBuffer> device_buffers_;
1131+
11101132 std::vector<Span<uint8_t >> non_const_spans_;
11111133
11121134 HierarchicalAllocator non_const_allocator_;
11131135
11141136 MemoryManager mem_manager_;
11151137
1138+ std::vector<std::vector<uint8_t >> allocate_host_buffers () {
1139+ std::vector<std::vector<uint8_t >> result;
1140+ result.reserve (planned_sizes_.size ());
1141+ for (size_t i = 0 ; i < planned_sizes_.size (); i++) {
1142+ if (planned_devices_[i].is_cpu ()) {
1143+ result.emplace_back (planned_sizes_[i]);
1144+ } else {
1145+ result.emplace_back ();
1146+ }
1147+ }
1148+ return result;
1149+ }
1150+
1151+ std::vector<DeviceMemoryBuffer> allocate_device_buffers () {
1152+ std::vector<DeviceMemoryBuffer> result;
1153+ result.reserve (planned_sizes_.size ());
1154+ for (size_t i = 0 ; i < planned_sizes_.size (); i++) {
1155+ if (planned_devices_[i].is_cpu ()) {
1156+ result.emplace_back ();
1157+ continue ;
1158+ }
1159+ auto buffer = DeviceMemoryBuffer::create (
1160+ planned_sizes_[i],
1161+ planned_devices_[i].type (),
1162+ planned_devices_[i].index ());
1163+ THROW_IF_ERROR (
1164+ buffer.error (),
1165+ " Failed to allocate %" PRId64
1166+ " bytes for planned buffer %zu on device type %d index %d" ,
1167+ planned_sizes_[i],
1168+ i,
1169+ static_cast <int >(planned_devices_[i].type ()),
1170+ static_cast <int >(planned_devices_[i].index ()));
1171+ result.emplace_back (std::move (buffer.get ()));
1172+ }
1173+ return result;
1174+ }
1175+
11161176 std::vector<Span<uint8_t >> create_non_const_spans () {
11171177 std::vector<Span<uint8_t >> result;
1118- for (size_t i = 0 ; i < non_const_buffers_.size (); i++) {
1119- result.push_back (
1120- {non_const_buffers_[i].data (), non_const_buffers_[i].size ()});
1178+ result.reserve (planned_sizes_.size ());
1179+ for (size_t i = 0 ; i < planned_sizes_.size (); i++) {
1180+ if (planned_devices_[i].is_cpu ()) {
1181+ result.push_back (
1182+ {non_const_buffers_[i].data (), non_const_buffers_[i].size ()});
1183+ } else {
1184+ result.push_back (device_buffers_[i].as_span ());
1185+ }
11211186 }
11221187 return result;
11231188 }
@@ -1420,30 +1485,59 @@ struct PyProgram final {
14201485 // method in the program. Map will be easier to use than a list because we
14211486 // dont know how many non_const arenas there will be
14221487 std::map<size_t , int64_t > non_const_buffer_sizes;
1488+ // Device each arena must live on. Arenas are shared across methods, so all
1489+ // methods have to agree; disagreement is rejected below rather than
1490+ // silently resolved, because one arena cannot be both host and device.
1491+ std::map<size_t , executorch::aten::Device> non_const_buffer_devices;
14231492 for (size_t i = 0 ; i < state_->program_ ->num_methods (); ++i) {
14241493 auto name = state_->program_ ->get_method_name (i).get ();
14251494 auto method_meta = state_->program_ ->method_meta (name).get ();
1426- for (size_t j = 0 ; j < method_meta.num_non_const_buffers (); j++) {
1427- int64_t buffer_size = method_meta.non_const_buffer_size (j).get ();
1495+ for (size_t j = 0 ; j < method_meta.num_memory_planned_buffers (); j++) {
1496+ int64_t buffer_size = method_meta.memory_planned_buffer_size (j).get ();
1497+ auto device_result = method_meta.memory_planned_buffer_device (j);
1498+ THROW_IF_ERROR (
1499+ device_result.error (),
1500+ " Failed to get device of planned buffer %zu of method %s" ,
1501+ j,
1502+ name);
1503+ auto device = device_result.get ();
14281504 if (non_const_buffer_sizes.find (j) == non_const_buffer_sizes.end ()) {
14291505 non_const_buffer_sizes.insert ({j, buffer_size});
1506+ non_const_buffer_devices.insert ({j, device});
14301507 } else {
14311508 non_const_buffer_sizes[j] =
14321509 std::max (non_const_buffer_sizes[j], buffer_size);
1510+ const auto & seen = non_const_buffer_devices.at (j);
1511+ THROW_IF_ERROR (
1512+ (seen.type () == device.type () && seen.index () == device.index ())
1513+ ? Error::Ok
1514+ : Error::InvalidProgram,
1515+ " Planned buffer %zu is on device type %d index %d for method %s "
1516+ " but device type %d index %d for an earlier method; a shared "
1517+ " arena cannot satisfy both" ,
1518+ j,
1519+ static_cast <int >(device.type ()),
1520+ static_cast <int >(device.index ()),
1521+ name,
1522+ static_cast <int >(seen.type ()),
1523+ static_cast <int >(seen.index ()));
14331524 }
14341525 }
14351526 }
14361527
1437- // Allocate the arenas. Using vector because we need to remember the size as
1438- // well, so vector is easier then unique_ptr.
1439- std::vector<std::vector<uint8_t >> non_const_buffers_;
1440- for (std::map<size_t , int64_t >::iterator i = non_const_buffer_sizes.begin ();
1441- i != non_const_buffer_sizes.end ();
1442- i++) {
1443- non_const_buffers_.push_back (std::vector<uint8_t >(i->second ));
1528+ // Hand the sizes and devices to ProgramMemory, which allocates each arena
1529+ // on its tagged device.
1530+ std::vector<int64_t > planned_sizes;
1531+ std::vector<executorch::aten::Device> planned_devices;
1532+ planned_sizes.reserve (non_const_buffer_sizes.size ());
1533+ planned_devices.reserve (non_const_buffer_sizes.size ());
1534+ for (const auto & entry : non_const_buffer_sizes) {
1535+ planned_sizes.push_back (entry.second );
1536+ planned_devices.push_back (non_const_buffer_devices.at (entry.first ));
14441537 }
14451538
1446- memory_ = std::make_shared<ProgramMemory>(std::move (non_const_buffers_));
1539+ memory_ = std::make_shared<ProgramMemory>(
1540+ std::move (planned_sizes), std::move (planned_devices));
14471541 if (event_tracer_ && debug_buffer_size > 0 ) {
14481542 // If a debug buffer was requested for the ETDump, allocate it and make
14491543 // sure its lifetime is as long as the event_tracer.
0 commit comments