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
1223using 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;
3345using executorch::runtime::MemoryAllocator;
3446using executorch::runtime::Result;
3547using executorch::runtime::Span;
48+ using executorch::runtime::toString;
3649
3750#ifdef ET_EVENT_TRACER_ENABLED
3851using 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+
104412class 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