Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
H
hpvm-release
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
llvm
hpvm-release
Commits
5073d2e8
Commit
5073d2e8
authored
5 years ago
by
kotsifa2
Browse files
Options
Downloads
Plain Diff
Merge branch 'approx_hpvm' of gitlab.engr.illinois.edu:llvm/hpvm into approx_hpvm
parents
b1109923
516e468a
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
llvm/projects/hpvm-tensor-rt/build_pldi/table_generator.py
+198
-97
198 additions, 97 deletions
llvm/projects/hpvm-tensor-rt/build_pldi/table_generator.py
with
198 additions
and
97 deletions
llvm/projects/hpvm-tensor-rt/build_pldi/table_generator.py
+
198
−
97
View file @
5073d2e8
...
@@ -6,111 +6,117 @@ import shutil
...
@@ -6,111 +6,117 @@ import shutil
from
collections
import
defaultdict
from
collections
import
defaultdict
class
TableGenerator
:
class
TableGenerator
:
'''
Stores all precision conversions used.
'''
precision_conversions
=
frozenset
([
"
h2f
"
,
"
f2h
"
])
precision_conversions
=
frozenset
([
"
h2f
"
,
"
f2h
"
])
def
__init__
(
self
,
dir_name
,
iters
,
profiler_binary_name
):
def
__init__
(
self
,
dir_path
,
iters
,
profiler_binary_name
):
self
.
__dir_name
=
dir_name
'''
Args:
dir_path: Path of directory containing network binaries
iters: Number of iterations to run each binary for
profiler_binary_name: Name of offline profiler binary to run
'''
self
.
__dir_path
=
dir_path
#
directory/path/network_name (last item in path)
#
Name of the actual directory
self
.
__network_name
=
os
.
path
.
split
(
dir_
name
)[
-
1
]
self
.
__network_name
=
os
.
path
.
split
(
dir_
path
)[
-
1
]
self
.
__iters
=
iters
self
.
__iters
=
iters
self
.
__profiler_binary_name
=
profiler_binary_name
self
.
__profiler_binary_name
=
profiler_binary_name
self
.
__results_dir_name
=
"
%s_results
"
%
self
.
__dir_name
self
.
__table_filename
=
"
%s_tensors.txt
"
%
self
.
__network_name
# Path to results directory
self
.
__results_dir_path
=
"
%s_results
"
%
self
.
__dir_path
# Outputted table file
self
.
__table_filename
=
"
%s_tensors.txt
"
%
self
.
__network_name
def
__is_binary
(
self
,
file_path
):
# Nested default dictionary of default dicts
# Binary name must start with the network name as per our naming standards
self
.
__table
=
self
.
__build_nested_default_dict
()
return
os
.
path
.
isfile
(
file_path
)
and
os
.
access
(
file_path
,
os
.
X_OK
)
and
\
file_path
.
find
(
self
.
__network_name
)
!=
-
1
def
run_binaries_in_input_dir
(
self
):
def
generate_table
(
self
):
if
not
os
.
path
.
isdir
(
self
.
__dir_name
):
'''
print
(
"
ERROR: Directory %s not found
"
%
self
.
__dir_name
)
Generates a table file called <network_name>_tensors.txt in the following
steps:
1. Runs the offline profiler against the inputted binaries to generate
results files
2. Builds an internal table storing all data from the parsed results files
the offline profiler generated
3. Writes the internal table to <network_name>_tensors.txt file and uses the
<network_name>_ops.txt file as a guideline in terms of row order
'''
#self.__run_inputted_binaries()
self
.
__build_internal_table
()
self
.
__output_table_to_file
()
def
__run_inputted_binaries
(
self
):
'''
Invokes the profiler to run all appropriate binaries (must start with the network
name) in the inputted directory. Result files generated by the profiler are
stored in the results file directory and are named <binary_name>.txt. These results
files are then parsed in a later step to generate the table
'''
if
not
os
.
path
.
isdir
(
self
.
__dir_path
):
print
(
"
ERROR: Directory %s not found
"
%
self
.
__dir_path
)
exit
(
1
)
exit
(
1
)
try
:
try
:
os
.
mkdir
(
self
.
__results_dir_
name
)
os
.
mkdir
(
self
.
__results_dir_
path
)
except
OSError
:
except
OSError
:
if
os
.
path
.
isdir
(
self
.
__results_dir_
name
):
if
os
.
path
.
isdir
(
self
.
__results_dir_
path
):
print
(
"
Directory already exists. Clearing directory.
"
)
print
(
"
Directory already exists. Clearing directory.
"
)
for
old_file
in
glob
.
glob
(
os
.
path
.
join
(
self
.
__results_dir_
name
,
"
*
"
)):
for
old_file
in
glob
.
glob
(
os
.
path
.
join
(
self
.
__results_dir_
path
,
"
*
"
)):
os
.
remove
(
old_file
)
os
.
remove
(
old_file
)
else
:
else
:
print
(
"
ERROR: Directory doesn
'
t exist but failed to create dir
"
)
print
(
"
ERROR: Directory doesn
'
t exist but failed to create dir
"
)
for
binary_name
in
os
.
listdir
(
self
.
__dir_
name
):
for
binary_name
in
os
.
listdir
(
self
.
__dir_
path
):
binary_path
=
os
.
path
.
join
(
self
.
__dir_
name
,
binary_name
)
binary_path
=
os
.
path
.
join
(
self
.
__dir_
path
,
binary_name
)
if
not
self
.
__
is_binary
(
binary_path
):
if
not
self
.
__
should_execute_file
(
binary_path
):
continue
continue
if
not
os
.
path
.
isfile
(
binary_path
):
output_file
=
os
.
path
.
join
(
self
.
__results_dir_path
,
binary_name
+
"
.txt
"
)
print
(
"
ERROR: Binary %s not found
"
%
binary_path
)
exit
(
1
)
output_file
=
os
.
path
.
join
(
self
.
__results_dir_name
,
binary_name
+
"
.txt
"
)
# No stdout/stderr piping needed for now
# No stdout/stderr piping needed for now
subprocess
.
Popen
([
profiler_binary_name
,
binary_path
,
str
(
self
.
__iters
),
\
subprocess
.
Popen
([
profiler_binary_name
,
binary_path
,
str
(
self
.
__iters
),
\
output_file
]).
communicate
()
output_file
]).
communicate
()
def
__get_approximation_type
(
self
,
results_filename
):
approx_type_start_ind
=
results_filename
.
find
(
self
.
__network_name
)
\
+
len
(
self
.
__network_name
)
+
1
# + 1 to account for _ delimiter
approx_type_end_ind
=
results_filename
.
find
(
"
.txt
"
)
return
results_filename
[
approx_type_start_ind
:
approx_type_end_ind
]
def
__parse_tensor_operation_line
(
self
,
tensor_op_line
):
print
(
tensor_op_line
)
line_as_list
=
tensor_op_line
.
split
(
"
,
"
)
return
line_as_list
[
0
],
line_as_list
[
1
],
line_as_list
[
2
]
def
__build_nested_default_dict
(
self
):
return
defaultdict
(
self
.
__build_nested_default_dict
)
# h2f or f2h
def
__get_original_operation_name
(
self
,
op_name
):
underscore_ind
=
op_name
.
find
(
"
_
"
)
return
op_name
[
:
underscore_ind
],
op_name
[
underscore_ind
+
1
:
]
def
generate_table
(
self
):
self
.
__table
=
self
.
__build_nested_default_dict
()
self
.
__build_internal_table
()
self
.
__output_table
()
def
__build_internal_table
(
self
):
def
__build_internal_table
(
self
):
for
results_file_name
in
os
.
listdir
(
self
.
__results_dir_name
):
'''
Iterates through each results file generated by the runs of the offline
profiler and stores the data in a dictionary in the following format:
[operation name][approximation type OR conversion type][time/energy]
'''
for
results_file_name
in
os
.
listdir
(
self
.
__results_dir_path
):
# Ignore if it's not a results file
# Ignore if it's not a results file
if
results_file_name
==
self
.
__table_filename
or
\
if
results_file_name
==
self
.
__table_filename
or
\
not
results_file_name
.
startswith
(
self
.
__network_name
):
not
results_file_name
.
startswith
(
self
.
__network_name
):
continue
continue
approx_type
=
self
.
__get_approximation_type
(
results_file_name
)
approx_type
=
self
.
__get_approximation_type
(
results_file_name
)
results_file
=
open
(
os
.
path
.
join
(
self
.
__results_dir_
name
,
results_file_name
),
"
r
"
)
results_file
=
open
(
os
.
path
.
join
(
self
.
__results_dir_
path
,
results_file_name
),
"
r
"
)
for
line
in
results_file
:
for
line
in
results_file
:
line
=
line
.
strip
()
line
=
line
.
strip
()
op_name
,
total_time
,
total_energy
=
self
.
__parse_tensor_operation_line
(
line
)
op_name
,
total_time
,
total_energy
=
self
.
__parse_tensor_operation_line
(
line
)
# Handle _f2h and _h2f output for tensor operation
# If the current operation is f2h or h2f
# Store as columns of original operation rather than independent rows
if
any
(
op_name
.
endswith
(
prec_conv
)
for
prec_conv
in
TableGenerator
.
precision_conversions
):
if
any
(
op_name
.
endswith
(
prec_conv
)
for
prec_conv
in
TableGenerator
.
precision_conversions
):
# Get the original operation name (without the f2h/h2f) and the conversion type
orig_op_name
,
conversion_type
=
self
.
__get_original_operation_name
(
op_name
)
orig_op_name
,
conversion_type
=
self
.
__get_original_operation_name
(
op_name
)
# Error bc original op name should ALWAYS be in the table
if
orig_op_name
not
in
self
.
__table
:
if
orig_op_name
not
in
self
.
__table
:
print
(
"
ERROR: Conversion found but original %s is not in the table
"
%
orig_op_name
)
print
(
"
ERROR: Conversion found but original %s is not in the table
"
%
orig_op_name
)
exit
(
1
)
exit
(
1
)
self
.
__table
[
orig_op_name
][
conversion_type
][
"
time
"
]
=
total_time
self
.
__table
[
orig_op_name
][
conversion_type
][
"
energy
"
]
=
total_energy
# Store f2h and h2f as columns in the row belonging to the original operation
self
.
__table
[
orig_op_name
][
approx_type
][
"
time
"
]
=
total_time
self
.
__table
[
orig_op_name
][
approx_type
][
"
energy
"
]
=
total_energy
# Create a new row in the dictionary
# Create a new row in the dictionary
else
:
else
:
...
@@ -119,67 +125,162 @@ class TableGenerator:
...
@@ -119,67 +125,162 @@ class TableGenerator:
results_file
.
close
()
results_file
.
close
()
def
__output_table
(
self
):
table_file_path
=
os
.
path
.
join
(
self
.
__results_dir_name
,
self
.
__table_filename
)
# TODO un hard code this
soc_operations_file_name
=
os
.
path
.
join
(
"
/home/nvidia/soc_simulator
"
,
"
%s_cifar10
"
%
self
.
__network_name
,
"
%s_ops.txt
"
%
self
.
__network_name
)
# Don't need to copy the file over --> can use the original file as a reference
def
__output_table_to_file
(
self
):
'''
Outputs the internally stored table to a file using the <network_name>_ops.txt file as
a guideline in the following steps:
1. Opens the ops file and the file to output the table to
2. Reads a line from the ops file (guaranteed to be the layers/NML header)
3. For each operation in the layer (or 1 operation if the
"
layer
"
is a NML), we store the
time and the energy
'''
table_file_path
=
os
.
path
.
join
(
self
.
__results_dir_path
,
self
.
__table_filename
)
soc_operations_file_name
=
os
.
path
.
join
(
"
/
"
,
"
home
"
,
"
nvidia
"
,
"
soc_simulator
"
,
\
"
%s_cifar10
"
%
self
.
__network_name
,
"
%s_ops.txt
"
%
self
.
__network_name
)
soc_operations_file
=
open
(
soc_operations_file_name
,
"
r
"
)
soc_operations_file
=
open
(
soc_operations_file_name
,
"
r
"
)
table_file
=
open
(
table_file_path
,
"
w
"
)
table_file
=
open
(
table_file_path
,
"
w
"
)
# TODO possible for operations in the same layer to not have the same # of cols?
curr_line
=
soc_operations_file
.
readline
().
strip
()
curr_line
=
soc_operations_file
.
readline
().
strip
()
while
curr_line
:
while
curr_line
:
# First line is always the layers line (#layer_name,num_ops)
# First line is always the layers line (#layer_name,num_ops)
layer_name
,
num_ops
=
self
.
__parse_layer_info_line
(
curr_line
)
layer_name
,
num_ops
=
self
.
__parse_layer_info_line
(
curr_line
)
print
(
"
FIRST LINE
"
,
layer_name
,
num_ops
)
# List of strings, where each string is a row corresponding to an operation
#
Get each operation
in the layer
# in the layer
ops_in_layer
=
[]
ops_in_layer
=
[]
header
=
[
"
**
"
,
layer_name
,
str
(
num_ops
),
"
_
"
]
# Stores a list of elements in the header, which will be joined into a string
for
op_in_layer_count
in
range
(
num_ops
):
# The header is only generated for the first operation in the layer
# Each line consists of operation name
# CRITICAL ASSUMPTION: All operations within a layer have the same # columns
# or everything breaks bc the header is per layer, not per operation
header
=
[
"
**
"
,
layer_name
,
str
(
num_ops
)]
# Iterate through all operations within the layer
for
op_in_layer_count
in
range
(
num_ops
):
# Contains the operation name
curr_line
=
soc_operations_file
.
readline
().
strip
()
curr_line
=
soc_operations_file
.
readline
().
strip
()
curr_op
=
[
curr_line
]
# Join into a string later
# Stores a list of elements that will be joined to make up a row
curr_op
=
[
curr_line
]
operation_data
=
self
.
__table
[
curr_line
]
operation_data
=
self
.
__table
[
curr_line
]
# Iterate through time/energy data for each approx type
# Iterate through time/energy data for each approximation type corresponding
# to the current operation
for
approx_type
in
operation_data
:
for
approx_type
in
operation_data
:
curr_op
.
append
(
operation_data
[
approx_type
][
"
time
"
])
op_time
=
operation_data
[
approx_type
][
"
time
"
]
curr_op
.
append
(
operation_data
[
approx_type
][
"
energy
"
])
op_energy
=
operation_data
[
approx_type
][
"
energy
"
]
curr_op
.
append
(
op_time
)
curr_op
.
append
(
op_energy
)
# CRITICAL ASSUMPTION: All ops within a layer have the same # cols
# Only fill out the header once for the layer
if
op_in_layer_count
==
0
:
if
op_in_layer_count
==
0
:
header
.
append
(
approx_type
)
header
.
append
(
"
%s_time
"
%
approx_type
)
header
.
append
(
"
%s_energy
"
%
approx_type
)
ops_in_layer
.
append
(
'
'
.
join
(
curr_op
))
ops_in_layer
.
append
(
'
'
.
join
(
curr_op
))
# Getting all operation rows and then writing everything because
# Getting all operation rows and then writing everything because
# calls to write() are slow (memory vs time tradeoff)
# calls to write() are slow (memory vs time tradeoff)
print
(
"
%s
"
%
'
'
.
join
(
header
))
print
(
"
%s
"
%
'
\n
'
.
join
(
ops_in_layer
))
table_file
.
write
(
"
%s
\n
%s
\n
"
%
(
'
'
.
join
(
header
),
'
\n
'
.
join
(
ops_in_layer
)))
table_file
.
write
(
"
%s
\n
%s
\n
"
%
(
'
'
.
join
(
header
),
'
\n
'
.
join
(
ops_in_layer
)))
curr_line
=
soc_operations_file
.
readline
().
strip
()
curr_line
=
soc_operations_file
.
readline
().
strip
()
def
__should_execute_file
(
self
,
file_path
):
'''
Checks if the file at the given file path is a binary that should be run
by the profiler. Must exist, be a binary, and must start with the network
name as per our naming standards.
Args:
file_path: Path of the file to check
'''
return
os
.
path
.
isfile
(
file_path
)
and
os
.
access
(
file_path
,
os
.
X_OK
)
and
\
file_path
.
find
(
self
.
__network_name
)
!=
-
1
def
__get_approximation_type
(
self
,
results_filename
):
'''
Parses a given results filename for the approximation type.
Format assumption: <network_name>_<approx_type>.txt
Args:
results_filename: Name of results file
Returns:
the approximation technique (ex: fp16)
'''
approx_type_start_ind
=
results_filename
.
find
(
self
.
__network_name
)
\
+
len
(
self
.
__network_name
)
+
1
# + 1 to account for _ delimiter
approx_type_end_ind
=
results_filename
.
find
(
"
.txt
"
)
return
results_filename
[
approx_type_start_ind
:
approx_type_end_ind
]
def
__parse_tensor_operation_line
(
self
,
tensor_op_line
):
'''
Parses a tensor operation line (within a output file from the offline
profiler for the operation name, the total time used, and the total
energy used
Args:
tensor_op_line: Tensor operation line from output file
Returns:
operation name
total time used
total energy used
'''
line_as_list
=
tensor_op_line
.
split
(
"
,
"
)
return
line_as_list
[
0
],
line_as_list
[
1
],
line_as_list
[
2
]
def
__build_nested_default_dict
(
self
):
'''
Builds a nested default dictionary with an arbitrary number of levels
'''
return
defaultdict
(
self
.
__build_nested_default_dict
)
def
__get_original_operation_name
(
self
,
op_name
):
'''
Parses an operation name containing _<conversion type> for the original
operation name.
Format assumption: <original_op_name>_<conversion type>
Args:
op_name: Name of the operation
Returns:
the original operation name
'''
underscore_ind
=
op_name
.
find
(
"
_
"
)
return
op_name
[
:
underscore_ind
],
op_name
[
underscore_ind
+
1
:
]
def
__parse_layer_info_line
(
self
,
layer_info_line
):
#layer_name,num_ops
def
__parse_layer_info_line
(
self
,
layer_info_line
):
#layer_name,num_ops
'''
Parses a layer header (from the original ops.txt file) into the layer name
and the number of operations
Assumed format: #layer_name,num_ops
Args:
layer_info_line: Line at the beginning of each layer in the ops file
Returns:
layer name
number of ops in the layer
'''
comma_ind
=
layer_info_line
.
find
(
"
,
"
)
comma_ind
=
layer_info_line
.
find
(
"
,
"
)
return
layer_info_line
[
layer_info_line
.
find
(
"
#
"
)
+
1
:
comma_ind
],
\
return
layer_info_line
[
layer_info_line
.
find
(
"
#
"
)
+
1
:
comma_ind
],
\
int
(
layer_info_line
[
comma_ind
+
1
:
])
int
(
layer_info_line
[
comma_ind
+
1
:
])
def
__generate_header
(
self
,
table
):
# <approx type time/energy> <conversion type at very end>
if
__name__
==
"
__main__
"
:
# should the header be per tensor op or per layer?
binary_dir_path
=
"
/home/nvidia/Gitlab/hpvm/llvm/projects/hpvm-tensor-rt/build_pldi/mobilenet
"
# Try doing this per layer first
num_iters
=
1
pass
profiler_binary_name
=
"
/home/nvidia/awesome_profiler/pp
"
table_gen
=
TableGenerator
(
binary_dir_path
,
num_iters
,
profiler_binary_name
)
binary_dir_name
=
"
/home/nvidia/Gitlab/hpvm/llvm/projects/hpvm-tensor-rt/build_pldi/mobilenet
"
#table_gen.run_inputted_binaries()
num_iters
=
1
table_gen
.
generate_table
()
profiler_binary_name
=
"
/home/nvidia/awesome_profiler/pp
"
table_gen
=
TableGenerator
(
binary_dir_name
,
num_iters
,
profiler_binary_name
)
#table_gen.run_binaries_in_input_dir()
table_gen
.
generate_table
()
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment