1818Provides support to compile networks both AOT and JIT.
1919"""
2020import logging
21+ import os
2122import os .path
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 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,15 @@ def compile_model(
292313
293314 config = parse_configs (pass_context_configs )
294315
316+ # remember initial relay
317+ initial_relay_astext = (
318+ mod .astext (
319+ show_meta_data = False ,
320+ ).split ("\n " )
321+ if dump_offloads != ""
322+ else ""
323+ )
324+
295325 tvm_target , extra_targets = target_from_cli (target , additional_target_options )
296326 tvm_target , target_host = Target .canon_target_and_host (tvm_target , target_host )
297327
@@ -316,6 +346,10 @@ def compile_model(
316346 for partition_function , opts in zip (partition_functions , partition_opts ):
317347 mod = partition_function (mod , params , mod_name = mod_name , ** opts )
318348
349+ if initial_relay_astext != "" :
350+ # dump which operations are offloaded to which backend
351+ dump_operation_offloads (mod , initial_relay_astext , dump_offloads )
352+
319353 if tuning_records and os .path .exists (tuning_records ):
320354 logger .debug ("tuning records file provided: %s" , tuning_records )
321355
@@ -459,3 +493,65 @@ def save_dumps(module_name: str, dumps: Dict[str, str], dump_root: str = "."):
459493 dump_name = module_name + "." + dump_format
460494 with open (Path (dump_root , dump_name ), "w" ) as f :
461495 f .write (dumps [dump_format ])
496+
497+
498+ def dump_operation_offloads (mod : tvm .ir .IRModule , initial_relay_astext : list , dump_path : str ):
499+ """This helper function forms a line-by-line output of the initial Relay lines,
500+ indicating which operations are ported to which backend,
501+ indicating the composite that includes those operations e.g
502+ 'device1 <- device2.qnn_conv2d'
503+ 'device1 <- %0 = qnn.conv2d(%tfl.quantize, %v_param_1, ...'
504+ 'device1 <- %1 = nn.bias_add(%0, %v_param_2, axis=3);'
505+ 'device1 <- %2 = qnn.requantize(%1, meta[relay.Constant]...'
506+ 'device2 <- device2.reshape'
507+ 'device2 <- %3 = reshape(%206, newshape=[1, 1001]);'
508+
509+ Parameters
510+ ----------
511+ mod : tvm.ir.IRModule
512+ The IRModule that gets generated from a relay frontend.
513+ initial_relay_astext : list
514+ List of input model IR strings.
515+ dump_path: str
516+ Value of the "dump_offloads" compiler atribute.
517+ Could be dash ("-") or file path or empty string for
518+ printing to console, file or doing nothing respectively.
519+ """
520+ print_to_console = dump_path == "-"
521+ save_to_file = all ([dump_path != "-" , dump_path != "" ])
522+
523+ if print_to_console or save_to_file :
524+
525+ operations_distribution = analyze_operations_distribution (mod )
526+ output = []
527+ prev_op_name = ""
528+ for s in initial_relay_astext :
529+ result = re .search (r"(output_name: )(.*)(:0:0)(.*)\*/" , s )
530+ if result :
531+ op = result .group (2 )
532+ hardw_id = ""
533+ if op in operations_distribution :
534+ hardw_id = operations_distribution [op ][0 ]
535+ result = re .search (r"(.*)(_PART_)" , op )
536+ op_name = result .group (1 ) if result else op
537+ if prev_op_name != op_name :
538+ # assume that operations of one composite go one after another in the relay
539+ prev_op_name = op_name
540+ # add the name of a composite
541+ output .append (
542+ f"{ hardw_id :10} <- { (operations_distribution [op ][1 ])} "
543+ )
544+ if "span" in s :
545+ sub = re .sub (r" /\* span(.*)\*/" , "" , s ).lstrip ()
546+ if hardw_id == "" :
547+ hardw_id = "generic"
548+ output .append (f"{ hardw_id :10} <- { sub } " )
549+ else :
550+ output .append (f"{ hardw_id :10} <- { sub } " )
551+ if print_to_console :
552+ print ("\n " + "\n " .join (output ))
553+ if save_to_file :
554+ file_path = os .path .abspath (dump_path )
555+ os .makedirs (os .path .dirname (file_path ), exist_ok = True )
556+ with open (file_path , "w" ) as f :
557+ f .write ("\n " .join (output ))
0 commit comments