1919"""
2020import logging
2121import os .path
22+ from copy import deepcopy
2223from typing import Any , Optional , Dict , List , Union , Callable , Sequence
2324from pathlib import Path
25+ import re
2426
2527import tvm
2628from tvm import autotvm , auto_scheduler
3032from tvm .ir .memory_pools import WorkspaceMemoryPools
3133from tvm .target import Target
3234from tvm .relay .backend import Executor , Runtime
35+ from tvm .relay .analysis .operations_distribution import analyze_operations_distribution
3336
3437from . import composite_target , frontends , TVMCException
3538from .model import TVMCModel , TVMCPackage
@@ -73,6 +76,16 @@ def add_compile_parser(subparsers, _, json_params):
7376 default = "" ,
7477 help = "comma separated list of formats to export the input model, e.g. 'asm,ll,relay'." ,
7578 )
79+ parser .add_argument (
80+ "--dump-offloads" ,
81+ default = "" ,
82+ help = "output a mapping of which operations of the initial Relay "
83+ "will be transferred to which backend, indicating the composite "
84+ "that includes those operations, "
85+ "e.g. '--dump-offloads -' to dump to the console, "
86+ "e.g. '--dump-offloads <path_to_file>' to dump to the file. "
87+ "If not presented, no output is done. " ,
88+ )
7689 parser .add_argument (
7790 "--model-format" ,
7891 choices = frontends .get_frontend_names (),
@@ -175,6 +188,8 @@ def drive_compile(args):
175188
176189 dump_code = [x .strip () for x in args .dump_code .split ("," )] if args .dump_code else None
177190
191+ dump_offloads = args .dump_offloads if args .dump_offloads else ""
192+
178193 additional_targets = reconstruct_target_args (args )
179194 workspace_pools_target , extra_targets = target_from_cli (args .target , additional_targets )
180195
@@ -190,6 +205,7 @@ def drive_compile(args):
190205 cross_options = args .cross_compiler_options ,
191206 output_format = args .output_format ,
192207 dump_code = dump_code ,
208+ dump_offloads = dump_offloads ,
193209 target_host = None ,
194210 desired_layout = args .desired_layout ,
195211 disabled_pass = args .disabled_pass ,
@@ -216,6 +232,7 @@ def compile_model(
216232 cross_options : Optional [str ] = None ,
217233 output_format : str = "so" ,
218234 dump_code : Optional [List [str ]] = None ,
235+ dump_offloads : str = "" ,
219236 target_host : Optional [str ] = None ,
220237 desired_layout : Optional [str ] = None ,
221238 disabled_pass : Optional [str ] = None ,
@@ -257,6 +274,10 @@ def compile_model(
257274 dump_code : list, optional
258275 Dump the generated code for the specified source types, on
259276 the requested target.
277+ dump_offloads : str
278+ Dump the information about the partition of input model's layers by external codegen.
279+ Can be '' to not dump at all, '-' to dump to the console
280+ or '<path_to_file>' to dump to the specified file.
260281 target_host : str, optional
261282 The target of the host machine if host-side code
262283 needs to be generated.
@@ -292,6 +313,11 @@ def compile_model(
292313
293314 config = parse_configs (pass_context_configs )
294315
316+ initial_relay = None
317+ if dump_offloads != "" :
318+ # remember initial relay
319+ initial_relay = deepcopy (tvmc_model .mod )
320+
295321 tvm_target , extra_targets = target_from_cli (target , additional_target_options )
296322 tvm_target , target_host = Target .canon_target_and_host (tvm_target , target_host )
297323
@@ -316,6 +342,10 @@ def compile_model(
316342 for partition_function , opts in zip (partition_functions , partition_opts ):
317343 mod = partition_function (mod , params , mod_name = mod_name , ** opts )
318344
345+ if initial_relay :
346+ # dump which operations are offloaded to which backend
347+ dump_operation_offloads (mod , initial_relay , dump_offloads )
348+
319349 if tuning_records and os .path .exists (tuning_records ):
320350 logger .debug ("tuning records file provided: %s" , tuning_records )
321351
@@ -459,3 +489,79 @@ def save_dumps(module_name: str, dumps: Dict[str, str], dump_root: str = "."):
459489 dump_name = module_name + "." + dump_format
460490 with open (Path (dump_root , dump_name ), "w" ) as f :
461491 f .write (dumps [dump_format ])
492+
493+
494+ def dump_operation_offloads (mod : tvm .ir .IRModule , initial_mod : tvm .ir .IRModule , dump_path : str ):
495+ """This helper function forms a line-by-line output of the initial Relay lines,
496+ indicating which operations are ported to which target,
497+ and indicating the composite that includes those operations;
498+ the 'generic' target refers to operations uploaded to the host, e.g
499+ 'target1 <- target2.qnn_conv2d'
500+ 'target1 <- %0 = qnn.conv2d(%tfl.quantize, %v_param_1, ...'
501+ 'target1 <- %1 = nn.bias_add(%0, %v_param_2, axis=3);'
502+ 'target1 <- %2 = qnn.requantize(%1, meta[relay.Constant]...'
503+ 'target2 <- target2.reshape'
504+ 'target2 <- %3 = reshape(%2, newshape=[1, 1001]);'
505+ 'generic <- %4 = nn.pad(%3, -128f, pad_width=[[0, 0], [1, 1]...'
506+
507+ Parameters
508+ ----------
509+ mod : tvm.ir.IRModule
510+ The IRModule that gets generated from a relay frontend.
511+ initial_relay_astext : list
512+ List of input model IR strings.
513+ dump_path: str
514+ Value of the "dump_offloads" compiler atribute.
515+ Could be dash ("-") or file path or empty string for
516+ printing to console, file or doing nothing respectively.
517+ """
518+ print_to_console = dump_path == "-"
519+ save_to_file = all ([dump_path != "-" , dump_path != "" ])
520+
521+ if print_to_console or save_to_file :
522+
523+ operations_distribution = analyze_operations_distribution (mod )
524+
525+ def annotate_f (x ):
526+ ret = ""
527+ if isinstance (x , relay .Call ):
528+ assert (
529+ x .span .source_name .name in operations_distribution
530+ ), f"Initial relay op { x .span .source_name .name } is not present \
531+ in relay after partition"
532+ compiler_name , op_name , func_id = operations_distribution [x .span .source_name .name ]
533+ ret = f", func_id: { func_id } , compiler_name: { compiler_name } , op_name: { op_name } "
534+ return ret
535+
536+ initial_relay_astext = initial_mod .astext (show_meta_data = False , annotate = annotate_f ).split (
537+ "\n "
538+ )
539+
540+ output = []
541+ prev_func_id = - 1
542+ for s in initial_relay_astext :
543+ result = re .search (
544+ r"(func_id: )(.*)(, compiler_name: )(.*)(, op_name: )((.*)(?=;)|(.*))" , s
545+ )
546+ if result :
547+ func_id = result .group (2 )
548+ target_name = result .group (4 )
549+ op_name = result .group (6 )
550+ if prev_func_id != func_id :
551+ # assume that operations of one composite go one after another in the relay
552+ prev_func_id = func_id
553+ # add the name of a composite
554+ if op_name != "generic" :
555+ output .append (f"{ target_name :10} <- { op_name } " )
556+ sub = re .sub (r", compiler_name: (.*)" , "" , s ).lstrip ()
557+ if target_name == "generic" :
558+ output .append (f"{ target_name :10} <- { sub } " )
559+ else :
560+ output .append (f"{ target_name :10} <- { sub } " )
561+ if print_to_console :
562+ print ("\n " + "\n " .join (output ))
563+ if save_to_file :
564+ file_path = os .path .abspath (dump_path )
565+ os .makedirs (os .path .dirname (file_path ), exist_ok = True )
566+ with open (file_path , "w" ) as f :
567+ f .write ("\n " .join (output ))
0 commit comments