Skip to content

Commit 5a1d9fe

Browse files
authored
Arm backend: Dump Arm VGF Delegate Boundary Inputs (pytorch#20739)
Dump Arm VGF Delegate Boundary Inputs for testing cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Elena Zhelezina <elena.zhelezina@arm.com>
1 parent 4eda94e commit 5a1d9fe

3 files changed

Lines changed: 418 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ if(EXECUTORCH_BUILD_CUDA)
236236
add_definitions(-DCUDA_AVAILABLE=1)
237237
endif()
238238

239+
option(ET_ENABLE_VGF_INPUT_DUMP
240+
"Enable Arm/VGF delegate-boundary input dump flags in executor_runner"
241+
OFF
242+
)
243+
announce_configured_options(ET_ENABLE_VGF_INPUT_DUMP)
244+
239245
# -ffunction-sections -fdata-sections: breaks function and data into sections so
240246
# they can be properly gc'd. -s: strip symbol.
241247
if(WIN32)
@@ -1453,6 +1459,9 @@ if(EXECUTORCH_BUILD_EXECUTOR_RUNNER)
14531459
endif()
14541460
target_link_libraries(executor_runner PRIVATE ${_executor_runner_libs})
14551461
target_compile_options(executor_runner PUBLIC ${_common_compile_options})
1462+
if(ET_ENABLE_VGF_INPUT_DUMP)
1463+
target_compile_definitions(executor_runner PRIVATE ET_ENABLE_VGF_INPUT_DUMP)
1464+
endif()
14561465
if(EXECUTORCH_BUILD_ARM_ETHOSU_LINUX)
14571466
target_compile_definitions(
14581467
executor_runner PRIVATE EXECUTORCH_BUILD_ARM_ETHOSU_LINUX=1

backends/arm/runtime/VGFBackend.cpp

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,28 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
#include <atomic>
9+
#include <cerrno>
10+
#include <chrono>
811
#include <cinttypes>
12+
#include <cstdio>
13+
#include <cstdlib>
14+
#include <cstring>
15+
#include <filesystem>
16+
#include <fstream>
17+
#include <iomanip>
918
#include <list>
1019
#include <numeric>
20+
#include <sstream>
21+
#include <string>
1122

1223
using namespace std;
1324

1425
#include <c10/util/safe_numerics.h>
1526
#include <executorch/runtime/backend/interface.h>
1627
#include <executorch/runtime/core/error.h>
1728
#include <executorch/runtime/core/evalue.h>
29+
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
1830

1931
#ifdef ET_EVENT_TRACER_ENABLED
2032
#include <executorch/runtime/core/event_tracer_hooks_delegate.h>
@@ -33,6 +45,7 @@ using executorch::runtime::FreeableBuffer;
3345
using executorch::runtime::MemoryAllocator;
3446
using executorch::runtime::Result;
3547
using executorch::runtime::Span;
48+
using executorch::runtime::toString;
3649

3750
#ifdef ET_EVENT_TRACER_ENABLED
3851
using executorch::runtime::event_tracer_end_profiling_delegate;
@@ -101,6 +114,301 @@ void vkml_free_basics(
101114
// vkDestroyInstance(*instance, nullptr);
102115
}
103116

117+
// Helper functions to dump VGF Delegate Boundary Inputs
118+
constexpr const char* kVgfDumpInputsDirEnv = "EXECUTORCH_VGF_DUMP_INPUTS_DIR";
119+
constexpr const char* kVgfDumpInputsAndExitEnv =
120+
"EXECUTORCH_VGF_DUMP_INPUTS_AND_EXIT";
121+
122+
std::atomic<uint64_t> g_vgf_dump_invocation{0};
123+
124+
bool env_flag_enabled(const char* name) {
125+
const char* value = std::getenv(name);
126+
return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0 &&
127+
std::strcmp(value, "false") != 0 && std::strcmp(value, "False") != 0;
128+
}
129+
130+
const char* descriptor_type_to_string(VkDescriptorType type) {
131+
switch (type) {
132+
case VK_DESCRIPTOR_TYPE_TENSOR_ARM:
133+
return "VK_DESCRIPTOR_TYPE_TENSOR_ARM";
134+
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
135+
return "VK_DESCRIPTOR_TYPE_STORAGE_BUFFER";
136+
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
137+
return "VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER";
138+
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
139+
return "VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE";
140+
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
141+
return "VK_DESCRIPTOR_TYPE_STORAGE_IMAGE";
142+
default:
143+
return "VK_DESCRIPTOR_TYPE_UNKNOWN";
144+
}
145+
}
146+
147+
template <typename T>
148+
std::string array_ref_to_json(ArrayRef<T> values) {
149+
std::ostringstream out;
150+
out << "[";
151+
for (size_t i = 0; i < values.size(); ++i) {
152+
if (i != 0) {
153+
out << ", ";
154+
}
155+
out << static_cast<int64_t>(values[i]);
156+
}
157+
out << "]";
158+
return out.str();
159+
}
160+
161+
template <typename T>
162+
std::string vector_to_json(const std::vector<T>& values) {
163+
std::ostringstream out;
164+
out << "[";
165+
for (size_t i = 0; i < values.size(); ++i) {
166+
if (i != 0) {
167+
out << ", ";
168+
}
169+
out << static_cast<int64_t>(values[i]);
170+
}
171+
out << "]";
172+
return out.str();
173+
}
174+
175+
bool write_binary_file(
176+
const std::filesystem::path& path,
177+
const void* data,
178+
size_t nbytes) {
179+
std::ofstream file(path, std::ios::binary | std::ios::trunc);
180+
if (!file) {
181+
return false;
182+
}
183+
184+
file.write(
185+
static_cast<const char*>(data), static_cast<std::streamsize>(nbytes));
186+
return file.good();
187+
}
188+
189+
std::string make_vgf_call_dir_name(uint64_t invocation) {
190+
std::ostringstream call_name;
191+
call_name << "call_" << std::setw(6) << std::setfill('0') << invocation;
192+
return call_name.str();
193+
}
194+
195+
std::filesystem::path make_vgf_tmp_call_dir(
196+
const std::filesystem::path& dump_root,
197+
const std::string& call_dir_name) {
198+
const auto nonce =
199+
std::chrono::steady_clock::now().time_since_epoch().count();
200+
201+
std::ostringstream tmp_name;
202+
tmp_name << "." << call_dir_name << ".tmp." << nonce;
203+
204+
return dump_root / tmp_name.str();
205+
}
206+
207+
class ScopedTmpDir {
208+
public:
209+
explicit ScopedTmpDir(std::filesystem::path path)
210+
: path_(std::move(path)), active_(true) {}
211+
212+
~ScopedTmpDir() {
213+
if (active_) {
214+
std::error_code ec;
215+
std::filesystem::remove_all(path_, ec);
216+
}
217+
}
218+
219+
ScopedTmpDir(const ScopedTmpDir&) = delete;
220+
ScopedTmpDir& operator=(const ScopedTmpDir&) = delete;
221+
222+
void release() {
223+
active_ = false;
224+
}
225+
226+
private:
227+
std::filesystem::path path_;
228+
bool active_;
229+
};
230+
231+
Error dump_vgf_delegate_inputs(
232+
const VgfRepr& repr,
233+
Span<EValue*> args,
234+
const char* dump_root) {
235+
const uint64_t invocation = g_vgf_dump_invocation.fetch_add(1);
236+
237+
const std::filesystem::path dump_root_path(dump_root);
238+
const std::string call_dir_name = make_vgf_call_dir_name(invocation);
239+
const std::filesystem::path final_call_dir = dump_root_path / call_dir_name;
240+
const std::filesystem::path tmp_call_dir =
241+
make_vgf_tmp_call_dir(dump_root_path, call_dir_name);
242+
243+
std::error_code ec;
244+
245+
std::filesystem::create_directories(dump_root_path, ec);
246+
if (ec) {
247+
ET_LOG(
248+
Error,
249+
"Failed to create VGF dump root %s: %s",
250+
dump_root_path.string().c_str(),
251+
ec.message().c_str());
252+
return Error::Internal;
253+
}
254+
255+
if (std::filesystem::exists(final_call_dir, ec)) {
256+
ET_LOG(
257+
Error,
258+
"VGF dump output directory already exists: %s",
259+
final_call_dir.string().c_str());
260+
return Error::Internal;
261+
}
262+
263+
std::filesystem::create_directory(tmp_call_dir, ec);
264+
if (ec) {
265+
ET_LOG(
266+
Error,
267+
"Failed to create temporary VGF dump directory %s: %s",
268+
tmp_call_dir.string().c_str(),
269+
ec.message().c_str());
270+
return Error::Internal;
271+
}
272+
273+
ScopedTmpDir tmp_dir_guard(tmp_call_dir);
274+
275+
std::vector<std::string> input_file_names;
276+
input_file_names.reserve(repr.model_input_count);
277+
278+
for (size_t input_arg_idx = 0; input_arg_idx < repr.model_input_count;
279+
++input_arg_idx) {
280+
const int io_idx = repr.model_input_io_index[input_arg_idx];
281+
282+
if (io_idx < 0 || static_cast<size_t>(io_idx) >= repr.IOs.size()) {
283+
ET_LOG(
284+
Error,
285+
"Invalid VGF input IO index %d for input arg %zu",
286+
io_idx,
287+
input_arg_idx);
288+
return Error::InvalidArgument;
289+
}
290+
291+
if (args[input_arg_idx] == nullptr || !args[input_arg_idx]->isTensor()) {
292+
ET_LOG(Error, "VGF input arg %zu is not a tensor", input_arg_idx);
293+
return Error::InvalidArgument;
294+
}
295+
296+
const Tensor& tensor = args[input_arg_idx]->toTensor();
297+
const IO& io = repr.IOs[io_idx];
298+
299+
if (tensor.nbytes() != io.allocation_size) {
300+
ET_LOG(
301+
Error,
302+
"VGF input arg %zu size mismatch: tensor nbytes=%zu, IO allocation_size=%zu",
303+
input_arg_idx,
304+
tensor.nbytes(),
305+
io.allocation_size);
306+
return Error::InvalidArgument;
307+
}
308+
309+
std::ostringstream file_name;
310+
file_name << "input_" << std::setw(3) << std::setfill('0') << input_arg_idx
311+
<< "_io_" << io_idx << ".bin";
312+
313+
input_file_names.push_back(file_name.str());
314+
315+
const std::filesystem::path input_path = tmp_call_dir / file_name.str();
316+
317+
if (!write_binary_file(
318+
input_path, tensor.const_data_ptr(), tensor.nbytes())) {
319+
ET_LOG(
320+
Error,
321+
"Failed to write VGF input dump file %s",
322+
input_path.string().c_str());
323+
return Error::Internal;
324+
}
325+
}
326+
327+
const std::filesystem::path metadata_path = tmp_call_dir / "metadata.json";
328+
{
329+
std::ofstream metadata(metadata_path, std::ios::out | std::ios::trunc);
330+
if (!metadata) {
331+
ET_LOG(
332+
Error,
333+
"Failed to open VGF metadata file %s",
334+
metadata_path.string().c_str());
335+
return Error::Internal;
336+
}
337+
338+
metadata << "{\n";
339+
metadata << " \"format_version\": 1,\n";
340+
metadata << " \"invocation\": " << invocation << ",\n";
341+
metadata << " \"input_count\": " << repr.model_input_count << ",\n";
342+
metadata << " \"inputs\": [\n";
343+
344+
for (size_t input_arg_idx = 0; input_arg_idx < repr.model_input_count;
345+
++input_arg_idx) {
346+
const int io_idx = repr.model_input_io_index[input_arg_idx];
347+
const Tensor& tensor = args[input_arg_idx]->toTensor();
348+
const IO& io = repr.IOs[io_idx];
349+
350+
metadata << " {\n";
351+
metadata << " \"arg_index\": " << input_arg_idx << ",\n";
352+
metadata << " \"io_index\": " << io_idx << ",\n";
353+
metadata << " \"file\": \"" << input_file_names[input_arg_idx]
354+
<< "\",\n";
355+
metadata << " \"nbytes\": " << tensor.nbytes() << ",\n";
356+
metadata << " \"scalar_type\": \"" << toString(tensor.scalar_type())
357+
<< "\",\n";
358+
metadata << " \"tensor_shape\": "
359+
<< array_ref_to_json(tensor.sizes()) << ",\n";
360+
metadata << " \"tensor_strides\": "
361+
<< array_ref_to_json(tensor.strides()) << ",\n";
362+
metadata << " \"io_shape\": " << vector_to_json(io.size) << ",\n";
363+
metadata << " \"io_strides\": " << vector_to_json(io.stride)
364+
<< ",\n";
365+
metadata << " \"io_element_size\": " << io.elt_size << ",\n";
366+
metadata << " \"io_allocation_size\": " << io.allocation_size
367+
<< ",\n";
368+
metadata << " \"io_descriptor_type\": \""
369+
<< descriptor_type_to_string(io.descriptor_type) << "\"\n";
370+
metadata << " }";
371+
372+
if (input_arg_idx + 1 < repr.model_input_count) {
373+
metadata << ",";
374+
}
375+
metadata << "\n";
376+
}
377+
378+
metadata << " ]\n";
379+
metadata << "}\n";
380+
381+
metadata.close();
382+
if (!metadata) {
383+
ET_LOG(
384+
Error,
385+
"Failed to write VGF metadata file %s",
386+
metadata_path.string().c_str());
387+
return Error::Internal;
388+
}
389+
}
390+
391+
std::filesystem::rename(tmp_call_dir, final_call_dir, ec);
392+
if (ec) {
393+
ET_LOG(
394+
Error,
395+
"Failed to publish VGF dump directory %s -> %s: %s",
396+
tmp_call_dir.string().c_str(),
397+
final_call_dir.string().c_str(),
398+
ec.message().c_str());
399+
return Error::Internal;
400+
}
401+
402+
tmp_dir_guard.release();
403+
404+
ET_LOG(
405+
Info,
406+
"Wrote VGF delegate input dump to %s",
407+
final_call_dir.string().c_str());
408+
409+
return Error::Ok;
410+
}
411+
104412
class VGFBackend final : public ::executorch::runtime::BackendInterface {
105413
public:
106414
VGFBackend() = default;
@@ -264,6 +572,25 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
264572
return Error::InvalidArgument;
265573
}
266574

575+
// Helper block to dump VGF delegate boundary inputs for testing
576+
// with scenari0 runner
577+
const char* dump_inputs_dir = std::getenv(kVgfDumpInputsDirEnv);
578+
if (dump_inputs_dir != nullptr && dump_inputs_dir[0] != '\0') {
579+
Error dump_status =
580+
dump_vgf_delegate_inputs(*repr, args, dump_inputs_dir);
581+
if (dump_status != Error::Ok) {
582+
return dump_status;
583+
}
584+
585+
if (env_flag_enabled(kVgfDumpInputsAndExitEnv)) {
586+
ET_LOG(
587+
Info,
588+
"Exiting after VGF delegate input dump because %s is set",
589+
kVgfDumpInputsAndExitEnv);
590+
return Error::EndOfMethod;
591+
}
592+
}
593+
267594
#ifdef ET_EVENT_TRACER_ENABLED
268595
EventTracer* event_tracer = context.event_tracer();
269596

0 commit comments

Comments
 (0)