Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
nni
Commits
403195f0
Unverified
Commit
403195f0
authored
Jul 15, 2021
by
Yuge Zhang
Committed by
GitHub
Jul 15, 2021
Browse files
Merge branch 'master' into nn-meter
parents
99aa8226
a7278d2d
Changes
207
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
927 additions
and
57 deletions
+927
-57
nni/retiarii/nn/pytorch/api.py
nni/retiarii/nn/pytorch/api.py
+33
-12
nni/retiarii/nn/pytorch/component.py
nni/retiarii/nn/pytorch/component.py
+79
-2
nni/retiarii/nn/pytorch/hypermodule.py
nni/retiarii/nn/pytorch/hypermodule.py
+249
-0
nni/retiarii/nn/pytorch/mutator.py
nni/retiarii/nn/pytorch/mutator.py
+27
-6
nni/retiarii/nn/pytorch/nasbench101.py
nni/retiarii/nn/pytorch/nasbench101.py
+390
-0
nni/retiarii/nn/pytorch/utils.py
nni/retiarii/nn/pytorch/utils.py
+14
-2
nni/retiarii/operation_def/torch_op_def.py
nni/retiarii/operation_def/torch_op_def.py
+18
-5
nni/retiarii/strategy/bruteforce.py
nni/retiarii/strategy/bruteforce.py
+7
-4
nni/retiarii/utils.py
nni/retiarii/utils.py
+6
-1
nni/tools/nnictl/config_schema.py
nni/tools/nnictl/config_schema.py
+1
-0
nni/tools/nnictl/launcher.py
nni/tools/nnictl/launcher.py
+46
-3
nni/tools/nnictl/launcher_utils.py
nni/tools/nnictl/launcher_utils.py
+4
-0
nni/tools/nnictl/nnictl.py
nni/tools/nnictl/nnictl.py
+4
-0
nni/tools/nnictl/nnictl_utils.py
nni/tools/nnictl/nnictl_utils.py
+1
-1
pipelines/full-test-linux.yml
pipelines/full-test-linux.yml
+4
-11
pipelines/full-test-windows.yml
pipelines/full-test-windows.yml
+4
-9
pipelines/integration-test-trt.yml
pipelines/integration-test-trt.yml
+37
-0
test/.gitignore
test/.gitignore
+1
-0
test/config/integration_tests.yml
test/config/integration_tests.yml
+1
-0
test/ut/retiarii/test_convert_pytorch.py
test/ut/retiarii/test_convert_pytorch.py
+1
-1
No files found.
nni/retiarii/nn/pytorch/api.py
View file @
403195f0
...
...
@@ -3,13 +3,13 @@
import
copy
import
warnings
from
collections
import
OrderedDict
from
typing
import
Any
,
List
,
Union
,
Dict
,
Optional
import
torch
import
torch.nn
as
nn
from
...serializer
import
Translatable
,
basic_unit
from
...utils
import
NoContextError
from
.utils
import
generate_new_label
,
get_fixed_value
...
...
@@ -26,6 +26,8 @@ class LayerChoice(nn.Module):
----------
candidates : list of nn.Module or OrderedDict
A module list to be selected from.
prior : list of float
Prior distribution used in random sampling.
label : str
Identifier of the layer choice.
...
...
@@ -55,17 +57,21 @@ class LayerChoice(nn.Module):
``self.op_choice[1] = nn.Conv3d(...)``. Adding more choices is not supported yet.
"""
def
__new__
(
cls
,
candidates
:
Union
[
Dict
[
str
,
nn
.
Module
],
List
[
nn
.
Module
]],
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
# FIXME: prior is designed but not supported yet
def
__new__
(
cls
,
candidates
:
Union
[
Dict
[
str
,
nn
.
Module
],
List
[
nn
.
Module
]],
*
,
prior
:
Optional
[
List
[
float
]]
=
None
,
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
try
:
chosen
=
get_fixed_value
(
label
)
if
isinstance
(
candidates
,
list
):
return
candidates
[
int
(
chosen
)]
else
:
return
candidates
[
chosen
]
except
Assertion
Error
:
except
NoContext
Error
:
return
super
().
__new__
(
cls
)
def
__init__
(
self
,
candidates
:
Union
[
Dict
[
str
,
nn
.
Module
],
List
[
nn
.
Module
]],
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
def
__init__
(
self
,
candidates
:
Union
[
Dict
[
str
,
nn
.
Module
],
List
[
nn
.
Module
]],
*
,
prior
:
Optional
[
List
[
float
]]
=
None
,
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
super
(
LayerChoice
,
self
).
__init__
()
if
'key'
in
kwargs
:
warnings
.
warn
(
f
'"key" is deprecated. Assuming label.'
)
...
...
@@ -75,10 +81,12 @@ class LayerChoice(nn.Module):
if
'reduction'
in
kwargs
:
warnings
.
warn
(
f
'"reduction" is deprecated. Ignoring...'
)
self
.
candidates
=
candidates
self
.
prior
=
prior
or
[
1
/
len
(
candidates
)
for
_
in
range
(
len
(
candidates
))]
assert
abs
(
sum
(
self
.
prior
)
-
1
)
<
1e-5
,
'Sum of prior distribution is not 1.'
self
.
_label
=
generate_new_label
(
label
)
self
.
names
=
[]
if
isinstance
(
candidates
,
OrderedD
ict
):
if
isinstance
(
candidates
,
d
ict
):
for
name
,
module
in
candidates
.
items
():
assert
name
not
in
[
"length"
,
"reduction"
,
"return_mask"
,
"_key"
,
"key"
,
"names"
],
\
"Please don't use a reserved name '{}' for your module."
.
format
(
name
)
...
...
@@ -170,17 +178,23 @@ class InputChoice(nn.Module):
Recommended inputs to choose. If None, mutator is instructed to select any.
reduction : str
``mean``, ``concat``, ``sum`` or ``none``.
prior : list of float
Prior distribution used in random sampling.
label : str
Identifier of the input choice.
"""
def
__new__
(
cls
,
n_candidates
:
int
,
n_chosen
:
int
=
1
,
reduction
:
str
=
'sum'
,
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
def
__new__
(
cls
,
n_candidates
:
int
,
n_chosen
:
Optional
[
int
]
=
1
,
reduction
:
str
=
'sum'
,
*
,
prior
:
Optional
[
List
[
float
]]
=
None
,
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
try
:
return
ChosenInputs
(
get_fixed_value
(
label
),
reduction
=
reduction
)
except
Assertion
Error
:
except
NoContext
Error
:
return
super
().
__new__
(
cls
)
def
__init__
(
self
,
n_candidates
:
int
,
n_chosen
:
int
=
1
,
reduction
:
str
=
'sum'
,
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
def
__init__
(
self
,
n_candidates
:
int
,
n_chosen
:
Optional
[
int
]
=
1
,
reduction
:
str
=
'sum'
,
*
,
prior
:
Optional
[
List
[
float
]]
=
None
,
label
:
Optional
[
str
]
=
None
,
**
kwargs
):
super
(
InputChoice
,
self
).
__init__
()
if
'key'
in
kwargs
:
warnings
.
warn
(
f
'"key" is deprecated. Assuming label.'
)
...
...
@@ -192,6 +206,7 @@ class InputChoice(nn.Module):
self
.
n_candidates
=
n_candidates
self
.
n_chosen
=
n_chosen
self
.
reduction
=
reduction
self
.
prior
=
prior
or
[
1
/
n_candidates
for
_
in
range
(
n_candidates
)]
assert
self
.
reduction
in
[
'mean'
,
'concat'
,
'sum'
,
'none'
]
self
.
_label
=
generate_new_label
(
label
)
...
...
@@ -278,19 +293,25 @@ class ValueChoice(Translatable, nn.Module):
----------
candidates : list
List of values to choose from.
prior : list of float
Prior distribution to sample from.
label : str
Identifier of the value choice.
"""
def
__new__
(
cls
,
candidates
:
List
[
Any
],
label
:
Optional
[
str
]
=
None
):
# FIXME: prior is designed but not supported yet
def
__new__
(
cls
,
candidates
:
List
[
Any
],
*
,
prior
:
Optional
[
List
[
float
]]
=
None
,
label
:
Optional
[
str
]
=
None
):
try
:
return
get_fixed_value
(
label
)
except
Assertion
Error
:
except
NoContext
Error
:
return
super
().
__new__
(
cls
)
def
__init__
(
self
,
candidates
:
List
[
Any
],
label
:
Optional
[
str
]
=
None
):
def
__init__
(
self
,
candidates
:
List
[
Any
],
*
,
prior
:
Optional
[
List
[
float
]]
=
None
,
label
:
Optional
[
str
]
=
None
):
super
().
__init__
()
self
.
candidates
=
candidates
self
.
prior
=
prior
or
[
1
/
len
(
candidates
)
for
_
in
range
(
len
(
candidates
))]
assert
abs
(
sum
(
self
.
prior
)
-
1
)
<
1e-5
,
'Sum of prior distribution is not 1.'
self
.
_label
=
generate_new_label
(
label
)
self
.
_accessor
=
[]
...
...
@@ -324,7 +345,7 @@ class ValueChoice(Translatable, nn.Module):
return
self
def
__deepcopy__
(
self
,
memo
):
new_item
=
ValueChoice
(
self
.
candidates
,
self
.
label
)
new_item
=
ValueChoice
(
self
.
candidates
,
label
=
self
.
label
)
new_item
.
_accessor
=
[
*
self
.
_accessor
]
return
new_item
...
...
nni/retiarii/nn/pytorch/component.py
View file @
403195f0
import
copy
from
collections
import
OrderedDict
from
typing
import
Callable
,
List
,
Union
,
Tuple
,
Optional
import
torch
...
...
@@ -7,10 +8,12 @@ import torch.nn as nn
from
.api
import
LayerChoice
,
InputChoice
from
.nn
import
ModuleList
from
.nasbench101
import
NasBench101Cell
,
NasBench101Mutator
from
.utils
import
generate_new_label
,
get_fixed_value
from
...utils
import
NoContextError
__all__
=
[
'Repeat'
,
'Cell'
]
__all__
=
[
'Repeat'
,
'Cell'
,
'NasBench101Cell'
,
'NasBench101Mutator'
,
'NasBench201Cell'
]
class
Repeat
(
nn
.
Module
):
...
...
@@ -33,7 +36,7 @@ class Repeat(nn.Module):
try
:
repeat
=
get_fixed_value
(
label
)
return
nn
.
Sequential
(
*
cls
.
_replicate_and_instantiate
(
blocks
,
repeat
))
except
Assertion
Error
:
except
NoContext
Error
:
return
super
().
__new__
(
cls
)
def
__init__
(
self
,
...
...
@@ -145,3 +148,77 @@ class Cell(nn.Module):
current_state
=
torch
.
sum
(
torch
.
stack
(
current_state
),
0
)
states
.
append
(
current_state
)
return
torch
.
cat
(
states
[
self
.
num_predecessors
:],
1
)
class
NasBench201Cell
(
nn
.
Module
):
"""
Cell structure that is proposed in NAS-Bench-201 [nasbench201]_ .
This cell is a densely connected DAG with ``num_tensors`` nodes, where each node is tensor.
For every i < j, there is an edge from i-th node to j-th node.
Each edge in this DAG is associated with an operation transforming the hidden state from the source node
to the target node. All possible operations are selected from a predefined operation set, defined in ``op_candidates``.
Each of the ``op_candidates`` should be a callable that accepts input dimension and output dimension,
and returns a ``Module``.
Input of this cell should be of shape :math:`[N, C_{in}, *]`, while output should be :math:`[N, C_{out}, *]`. For example,
The space size of this cell would be :math:`|op|^{N(N-1)/2}`, where :math:`|op|` is the number of operation candidates,
and :math:`N` is defined by ``num_tensors``.
Parameters
----------
op_candidates : list of callable
Operation candidates. Each should be a function accepts input feature and output feature, returning nn.Module.
in_features : int
Input dimension of cell.
out_features : int
Output dimension of cell.
num_tensors : int
Number of tensors in the cell (input included). Default: 4
label : str
Identifier of the cell. Cell sharing the same label will semantically share the same choice.
References
----------
.. [nasbench201] Dong, X. and Yang, Y., 2020. Nas-bench-201: Extending the scope of reproducible neural architecture search.
arXiv preprint arXiv:2001.00326.
"""
@
staticmethod
def
_make_dict
(
x
):
if
isinstance
(
x
,
list
):
return
OrderedDict
([(
str
(
i
),
t
)
for
i
,
t
in
enumerate
(
x
)])
return
OrderedDict
(
x
)
def
__init__
(
self
,
op_candidates
:
List
[
Callable
[[
int
,
int
],
nn
.
Module
]],
in_features
:
int
,
out_features
:
int
,
num_tensors
:
int
=
4
,
label
:
Optional
[
str
]
=
None
):
super
().
__init__
()
self
.
_label
=
generate_new_label
(
label
)
self
.
layers
=
nn
.
ModuleList
()
self
.
in_features
=
in_features
self
.
out_features
=
out_features
self
.
num_tensors
=
num_tensors
op_candidates
=
self
.
_make_dict
(
op_candidates
)
for
tid
in
range
(
1
,
num_tensors
):
node_ops
=
nn
.
ModuleList
()
for
j
in
range
(
tid
):
inp
=
in_features
if
j
==
0
else
out_features
op_choices
=
OrderedDict
([(
key
,
cls
(
inp
,
out_features
))
for
key
,
cls
in
op_candidates
.
items
()])
node_ops
.
append
(
LayerChoice
(
op_choices
,
label
=
f
'
{
self
.
_label
}
__
{
j
}
_
{
tid
}
'
))
self
.
layers
.
append
(
node_ops
)
def
forward
(
self
,
inputs
):
tensors
=
[
inputs
]
for
layer
in
self
.
layers
:
current_tensor
=
[]
for
i
,
op
in
enumerate
(
layer
):
current_tensor
.
append
(
op
(
tensors
[
i
]))
current_tensor
=
torch
.
sum
(
torch
.
stack
(
current_tensor
),
0
)
tensors
.
append
(
current_tensor
)
return
tensors
[
-
1
]
nni/retiarii/nn/pytorch/hypermodule.py
0 → 100644
View file @
403195f0
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import
torch
import
torch.nn
as
nn
from
nni.retiarii.serializer
import
basic_unit
from
.api
import
LayerChoice
from
...utils
import
version_larger_equal
__all__
=
[
'AutoActivation'
]
TorchVersion
=
'1.5.0'
# ============== unary function modules ==============
@
basic_unit
class
UnaryIdentity
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
x
@
basic_unit
class
UnaryNegative
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
-
x
@
basic_unit
class
UnaryAbs
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
abs
(
x
)
@
basic_unit
class
UnarySquare
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
square
(
x
)
@
basic_unit
class
UnaryPow
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
pow
(
x
,
3
)
@
basic_unit
class
UnarySqrt
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
sqrt
(
x
)
@
basic_unit
class
UnaryMul
(
nn
.
Module
):
def
__init__
(
self
):
super
().
__init__
()
# element-wise for now, will change to per-channel trainable parameter
self
.
beta
=
torch
.
nn
.
Parameter
(
torch
.
tensor
(
1
,
dtype
=
torch
.
float32
))
# pylint: disable=not-callable
def
forward
(
self
,
x
):
return
x
*
self
.
beta
@
basic_unit
class
UnaryAdd
(
nn
.
Module
):
def
__init__
(
self
):
super
().
__init__
()
# element-wise for now, will change to per-channel trainable parameter
self
.
beta
=
torch
.
nn
.
Parameter
(
torch
.
tensor
(
1
,
dtype
=
torch
.
float32
))
# pylint: disable=not-callable
def
forward
(
self
,
x
):
return
x
+
self
.
beta
@
basic_unit
class
UnaryLogAbs
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
log
(
torch
.
abs
(
x
)
+
1e-7
)
@
basic_unit
class
UnaryExp
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
exp
(
x
)
@
basic_unit
class
UnarySin
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
sin
(
x
)
@
basic_unit
class
UnaryCos
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
cos
(
x
)
@
basic_unit
class
UnarySinh
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
sinh
(
x
)
@
basic_unit
class
UnaryCosh
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
cosh
(
x
)
@
basic_unit
class
UnaryTanh
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
tanh
(
x
)
if
not
version_larger_equal
(
torch
.
__version__
,
TorchVersion
):
@
basic_unit
class
UnaryAsinh
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
asinh
(
x
)
@
basic_unit
class
UnaryAtan
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
atan
(
x
)
if
not
version_larger_equal
(
torch
.
__version__
,
TorchVersion
):
@
basic_unit
class
UnarySinc
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
sinc
(
x
)
@
basic_unit
class
UnaryMax
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
max
(
x
,
torch
.
zeros_like
(
x
))
@
basic_unit
class
UnaryMin
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
min
(
x
,
torch
.
zeros_like
(
x
))
@
basic_unit
class
UnarySigmoid
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
sigmoid
(
x
)
@
basic_unit
class
UnaryLogExp
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
log
(
1
+
torch
.
exp
(
x
))
@
basic_unit
class
UnaryExpSquare
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
exp
(
-
torch
.
square
(
x
))
@
basic_unit
class
UnaryErf
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
erf
(
x
)
unary_modules
=
[
'UnaryIdentity'
,
'UnaryNegative'
,
'UnaryAbs'
,
'UnarySquare'
,
'UnaryPow'
,
'UnarySqrt'
,
'UnaryMul'
,
'UnaryAdd'
,
'UnaryLogAbs'
,
'UnaryExp'
,
'UnarySin'
,
'UnaryCos'
,
'UnarySinh'
,
'UnaryCosh'
,
'UnaryTanh'
,
'UnaryAtan'
,
'UnaryMax'
,
'UnaryMin'
,
'UnarySigmoid'
,
'UnaryLogExp'
,
'UnaryExpSquare'
,
'UnaryErf'
]
if
not
version_larger_equal
(
torch
.
__version__
,
TorchVersion
):
unary_modules
.
append
(
'UnaryAsinh'
)
unary_modules
.
append
(
'UnarySinc'
)
# ============== binary function modules ==============
@
basic_unit
class
BinaryAdd
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
x
[
0
]
+
x
[
1
]
@
basic_unit
class
BinaryMul
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
x
[
0
]
*
x
[
1
]
@
basic_unit
class
BinaryMinus
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
x
[
0
]
-
x
[
1
]
@
basic_unit
class
BinaryDivide
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
x
[
0
]
/
(
x
[
1
]
+
1e-7
)
@
basic_unit
class
BinaryMax
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
max
(
x
[
0
],
x
[
1
])
@
basic_unit
class
BinaryMin
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
min
(
x
[
0
],
x
[
1
])
@
basic_unit
class
BinarySigmoid
(
nn
.
Module
):
def
forward
(
self
,
x
):
return
torch
.
sigmoid
(
x
[
0
])
*
x
[
1
]
@
basic_unit
class
BinaryExpSquare
(
nn
.
Module
):
def
__init__
(
self
):
super
().
__init__
()
self
.
beta
=
torch
.
nn
.
Parameter
(
torch
.
tensor
(
1
,
dtype
=
torch
.
float32
))
# pylint: disable=not-callable
def
forward
(
self
,
x
):
return
torch
.
exp
(
-
self
.
beta
*
torch
.
square
(
x
[
0
]
-
x
[
1
]))
@
basic_unit
class
BinaryExpAbs
(
nn
.
Module
):
def
__init__
(
self
):
super
().
__init__
()
self
.
beta
=
torch
.
nn
.
Parameter
(
torch
.
tensor
(
1
,
dtype
=
torch
.
float32
))
# pylint: disable=not-callable
def
forward
(
self
,
x
):
return
torch
.
exp
(
-
self
.
beta
*
torch
.
abs
(
x
[
0
]
-
x
[
1
]))
@
basic_unit
class
BinaryParamAdd
(
nn
.
Module
):
def
__init__
(
self
):
super
().
__init__
()
self
.
beta
=
torch
.
nn
.
Parameter
(
torch
.
tensor
(
1
,
dtype
=
torch
.
float32
))
# pylint: disable=not-callable
def
forward
(
self
,
x
):
return
self
.
beta
*
x
[
0
]
+
(
1
-
self
.
beta
)
*
x
[
1
]
binary_modules
=
[
'BinaryAdd'
,
'BinaryMul'
,
'BinaryMinus'
,
'BinaryDivide'
,
'BinaryMax'
,
'BinaryMin'
,
'BinarySigmoid'
,
'BinaryExpSquare'
,
'BinaryExpAbs'
,
'BinaryParamAdd'
]
class
AutoActivation
(
nn
.
Module
):
"""
This module is an implementation of the paper "Searching for Activation Functions"
(https://arxiv.org/abs/1710.05941).
NOTE: current `beta` is not per-channel parameter
Parameters
----------
unit_num : int
the number of core units
"""
def
__init__
(
self
,
unit_num
=
1
):
super
().
__init__
()
self
.
unaries
=
nn
.
ModuleList
()
self
.
binaries
=
nn
.
ModuleList
()
self
.
first_unary
=
LayerChoice
([
eval
(
'{}()'
.
format
(
unary
))
for
unary
in
unary_modules
])
for
_
in
range
(
unit_num
):
one_unary
=
LayerChoice
([
eval
(
'{}()'
.
format
(
unary
))
for
unary
in
unary_modules
])
self
.
unaries
.
append
(
one_unary
)
for
_
in
range
(
unit_num
):
one_binary
=
LayerChoice
([
eval
(
'{}()'
.
format
(
binary
))
for
binary
in
binary_modules
])
self
.
binaries
.
append
(
one_binary
)
def
forward
(
self
,
x
):
out
=
self
.
first_unary
(
x
)
for
unary
,
binary
in
zip
(
self
.
unaries
,
self
.
binaries
):
out
=
binary
(
torch
.
stack
([
out
,
unary
(
x
)]))
return
out
nni/retiarii/nn/pytorch/mutator.py
View file @
403195f0
...
...
@@ -9,7 +9,7 @@ import torch.nn as nn
from
...mutator
import
Mutator
from
...graph
import
Cell
,
Graph
,
Model
,
ModelStatus
,
Node
from
.api
import
LayerChoice
,
InputChoice
,
ValueChoice
,
Placeholder
from
.component
import
Repeat
from
.component
import
Repeat
,
NasBench101Cell
,
NasBench101Mutator
from
...utils
import
uid
...
...
@@ -47,7 +47,12 @@ class InputChoiceMutator(Mutator):
n_candidates
=
self
.
nodes
[
0
].
operation
.
parameters
[
'n_candidates'
]
n_chosen
=
self
.
nodes
[
0
].
operation
.
parameters
[
'n_chosen'
]
candidates
=
list
(
range
(
n_candidates
))
chosen
=
[
self
.
choice
(
candidates
)
for
_
in
range
(
n_chosen
)]
if
n_chosen
is
None
:
chosen
=
[
i
for
i
in
candidates
if
self
.
choice
([
False
,
True
])]
# FIXME This is a hack to make choice align with the previous format
self
.
_cur_samples
=
chosen
else
:
chosen
=
[
self
.
choice
(
candidates
)
for
_
in
range
(
n_chosen
)]
for
node
in
self
.
nodes
:
target
=
model
.
get_node_by_name
(
node
.
name
)
target
.
update_operation
(
'__torch__.nni.retiarii.nn.pytorch.ChosenInputs'
,
...
...
@@ -199,8 +204,15 @@ class ManyChooseManyMutator(Mutator):
def
mutate
(
self
,
model
:
Model
):
# this mutate does not have any effect, but it is recorded in the mutation history
for
node
in
model
.
get_nodes_by_label
(
self
.
label
):
for
_
in
range
(
self
.
number_of_chosen
(
node
)):
self
.
choice
(
self
.
candidates
(
node
))
n_chosen
=
self
.
number_of_chosen
(
node
)
if
n_chosen
is
None
:
candidates
=
[
i
for
i
in
self
.
candidates
(
node
)
if
self
.
choice
([
False
,
True
])]
# FIXME This is a hack to make choice align with the previous format
# For example, it will convert [False, True, True] into [1, 2].
self
.
_cur_samples
=
candidates
else
:
for
_
in
range
(
n_chosen
):
self
.
choice
(
self
.
candidates
(
node
))
break
...
...
@@ -242,6 +254,11 @@ def extract_mutation_from_pt_module(pytorch_model: nn.Module) -> Tuple[Model, Op
'candidates'
:
list
(
range
(
module
.
min_depth
,
module
.
max_depth
+
1
))
})
node
.
label
=
module
.
label
if
isinstance
(
module
,
NasBench101Cell
):
node
=
graph
.
add_node
(
name
,
'NasBench101Cell'
,
{
'max_num_edges'
:
module
.
max_num_edges
})
node
.
label
=
module
.
label
if
isinstance
(
module
,
Placeholder
):
raise
NotImplementedError
(
'Placeholder is not supported in python execution mode.'
)
...
...
@@ -250,13 +267,17 @@ def extract_mutation_from_pt_module(pytorch_model: nn.Module) -> Tuple[Model, Op
return
model
,
None
mutators
=
[]
mutators_final
=
[]
for
nodes
in
_group_by_label_and_type
(
graph
.
hidden_nodes
):
assert
_is_all_equal
(
map
(
lambda
n
:
n
.
operation
.
type
,
nodes
)),
\
f
'Node with label "
{
nodes
[
0
].
label
}
" does not all have the same type.'
assert
_is_all_equal
(
map
(
lambda
n
:
n
.
operation
.
parameters
,
nodes
)),
\
f
'Node with label "
{
nodes
[
0
].
label
}
" does not agree on parameters.'
mutators
.
append
(
ManyChooseManyMutator
(
nodes
[
0
].
label
))
return
model
,
mutators
if
nodes
[
0
].
operation
.
type
==
'NasBench101Cell'
:
mutators_final
.
append
(
NasBench101Mutator
(
nodes
[
0
].
label
))
else
:
mutators
.
append
(
ManyChooseManyMutator
(
nodes
[
0
].
label
))
return
model
,
mutators
+
mutators_final
# utility functions
...
...
nni/retiarii/nn/pytorch/nasbench101.py
0 → 100644
View file @
403195f0
import
logging
from
collections
import
OrderedDict
from
typing
import
Callable
,
List
,
Optional
,
Union
,
Dict
import
numpy
as
np
import
torch
import
torch.nn
as
nn
from
.api
import
InputChoice
,
ValueChoice
,
LayerChoice
from
.utils
import
generate_new_label
,
get_fixed_dict
from
...mutator
import
InvalidMutation
,
Mutator
from
...graph
import
Model
from
...utils
import
NoContextError
_logger
=
logging
.
getLogger
(
__name__
)
def
compute_vertex_channels
(
input_channels
,
output_channels
,
matrix
):
"""
This is (almost) copied from the original NAS-Bench-101 implementation.
Computes the number of channels at every vertex.
Given the input channels and output channels, this calculates the number of channels at each interior vertex.
Interior vertices have the same number of channels as the max of the channels of the vertices it feeds into.
The output channels are divided amongst the vertices that are directly connected to it.
When the division is not even, some vertices may receive an extra channel to compensate.
Parameters
----------
in_channels : int
input channels count.
output_channels : int
output channel count.
matrix : np.ndarray
adjacency matrix for the module (pruned by model_spec).
Returns
-------
list of int
list of channel counts, in order of the vertices.
"""
num_vertices
=
np
.
shape
(
matrix
)[
0
]
vertex_channels
=
[
0
]
*
num_vertices
vertex_channels
[
0
]
=
input_channels
vertex_channels
[
num_vertices
-
1
]
=
output_channels
if
num_vertices
==
2
:
# Edge case where module only has input and output vertices
return
vertex_channels
# Compute the in-degree ignoring input, axis 0 is the src vertex and axis 1 is
# the dst vertex. Summing over 0 gives the in-degree count of each vertex.
in_degree
=
np
.
sum
(
matrix
[
1
:],
axis
=
0
)
interior_channels
=
output_channels
//
in_degree
[
num_vertices
-
1
]
correction
=
output_channels
%
in_degree
[
num_vertices
-
1
]
# Remainder to add
# Set channels of vertices that flow directly to output
for
v
in
range
(
1
,
num_vertices
-
1
):
if
matrix
[
v
,
num_vertices
-
1
]:
vertex_channels
[
v
]
=
interior_channels
if
correction
:
vertex_channels
[
v
]
+=
1
correction
-=
1
# Set channels for all other vertices to the max of the out edges, going backwards.
# (num_vertices - 2) index skipped because it only connects to output.
for
v
in
range
(
num_vertices
-
3
,
0
,
-
1
):
if
not
matrix
[
v
,
num_vertices
-
1
]:
for
dst
in
range
(
v
+
1
,
num_vertices
-
1
):
if
matrix
[
v
,
dst
]:
vertex_channels
[
v
]
=
max
(
vertex_channels
[
v
],
vertex_channels
[
dst
])
assert
vertex_channels
[
v
]
>
0
_logger
.
debug
(
'vertex_channels: %s'
,
str
(
vertex_channels
))
# Sanity check, verify that channels never increase and final channels add up.
final_fan_in
=
0
for
v
in
range
(
1
,
num_vertices
-
1
):
if
matrix
[
v
,
num_vertices
-
1
]:
final_fan_in
+=
vertex_channels
[
v
]
for
dst
in
range
(
v
+
1
,
num_vertices
-
1
):
if
matrix
[
v
,
dst
]:
assert
vertex_channels
[
v
]
>=
vertex_channels
[
dst
]
assert
final_fan_in
==
output_channels
or
num_vertices
==
2
# num_vertices == 2 means only input/output nodes, so 0 fan-in
return
vertex_channels
def
prune
(
matrix
,
ops
):
"""
Prune the extraneous parts of the graph.
General procedure:
1. Remove parts of graph not connected to input.
2. Remove parts of graph not connected to output.
3. Reorder the vertices so that they are consecutive after steps 1 and 2.
These 3 steps can be combined by deleting the rows and columns of the
vertices that are not reachable from both the input and output (in reverse).
"""
num_vertices
=
np
.
shape
(
matrix
)[
0
]
# calculate the connection matrix within V number of steps.
connections
=
np
.
linalg
.
matrix_power
(
matrix
+
np
.
eye
(
num_vertices
),
num_vertices
)
visited_from_input
=
set
([
i
for
i
in
range
(
num_vertices
)
if
connections
[
0
,
i
]])
visited_from_output
=
set
([
i
for
i
in
range
(
num_vertices
)
if
connections
[
i
,
-
1
]])
# Any vertex that isn't connected to both input and output is extraneous to the computation graph.
extraneous
=
set
(
range
(
num_vertices
)).
difference
(
visited_from_input
.
intersection
(
visited_from_output
))
if
len
(
extraneous
)
>
num_vertices
-
2
:
raise
InvalidMutation
(
'Non-extraneous graph is less than 2 vertices, '
'the input is not connected to the output and the spec is invalid.'
)
matrix
=
np
.
delete
(
matrix
,
list
(
extraneous
),
axis
=
0
)
matrix
=
np
.
delete
(
matrix
,
list
(
extraneous
),
axis
=
1
)
for
index
in
sorted
(
extraneous
,
reverse
=
True
):
del
ops
[
index
]
return
matrix
,
ops
def
truncate
(
inputs
,
channels
):
input_channels
=
inputs
.
size
(
1
)
if
input_channels
<
channels
:
raise
ValueError
(
'input channel < output channels for truncate'
)
elif
input_channels
==
channels
:
return
inputs
# No truncation necessary
else
:
# Truncation should only be necessary when channel division leads to
# vertices with +1 channels. The input vertex should always be projected to
# the minimum channel count.
assert
input_channels
-
channels
==
1
return
inputs
[:,
:
channels
]
class
_NasBench101CellFixed
(
nn
.
Module
):
"""
The fixed version of NAS-Bench-101 Cell, used in python-version execution engine.
"""
def
__init__
(
self
,
operations
:
List
[
Callable
[[
int
],
nn
.
Module
]],
adjacency_list
:
List
[
List
[
int
]],
in_features
:
int
,
out_features
:
int
,
num_nodes
:
int
,
projection
:
Callable
[[
int
,
int
],
nn
.
Module
]):
super
().
__init__
()
assert
num_nodes
==
len
(
operations
)
+
2
==
len
(
adjacency_list
)
+
1
self
.
operations
=
[
'IN'
]
+
operations
+
[
'OUT'
]
# add psuedo nodes
self
.
connection_matrix
=
self
.
build_connection_matrix
(
adjacency_list
,
num_nodes
)
del
num_nodes
# raw number of nodes is no longer used
self
.
connection_matrix
,
self
.
operations
=
prune
(
self
.
connection_matrix
,
self
.
operations
)
self
.
hidden_features
=
compute_vertex_channels
(
in_features
,
out_features
,
self
.
connection_matrix
)
self
.
num_nodes
=
len
(
self
.
connection_matrix
)
self
.
in_features
=
in_features
self
.
out_features
=
out_features
_logger
.
info
(
'Prund number of nodes: %d'
,
self
.
num_nodes
)
_logger
.
info
(
'Pruned connection matrix: %s'
,
str
(
self
.
connection_matrix
))
self
.
projections
=
nn
.
ModuleList
([
nn
.
Identity
()])
self
.
ops
=
nn
.
ModuleList
([
nn
.
Identity
()])
for
i
in
range
(
1
,
self
.
num_nodes
):
self
.
projections
.
append
(
projection
(
in_features
,
self
.
hidden_features
[
i
]))
for
i
in
range
(
1
,
self
.
num_nodes
-
1
):
self
.
ops
.
append
(
operations
[
i
-
1
](
self
.
hidden_features
[
i
]))
@
staticmethod
def
build_connection_matrix
(
adjacency_list
,
num_nodes
):
adjacency_list
=
[[]]
+
adjacency_list
# add adjacency for first node
connections
=
np
.
zeros
((
num_nodes
,
num_nodes
),
dtype
=
'int'
)
for
i
,
lst
in
enumerate
(
adjacency_list
):
assert
all
([
0
<=
k
<
i
for
k
in
lst
])
for
k
in
lst
:
connections
[
k
,
i
]
=
1
return
connections
def
forward
(
self
,
inputs
):
tensors
=
[
inputs
]
for
t
in
range
(
1
,
self
.
num_nodes
-
1
):
# Create interior connections, truncating if necessary
add_in
=
[
truncate
(
tensors
[
src
],
self
.
hidden_features
[
t
])
for
src
in
range
(
1
,
t
)
if
self
.
connection_matrix
[
src
,
t
]]
# Create add connection from projected input
if
self
.
connection_matrix
[
0
,
t
]:
add_in
.
append
(
self
.
projections
[
t
](
tensors
[
0
]))
if
len
(
add_in
)
==
1
:
vertex_input
=
add_in
[
0
]
else
:
vertex_input
=
sum
(
add_in
)
# Perform op at vertex t
vertex_out
=
self
.
ops
[
t
](
vertex_input
)
tensors
.
append
(
vertex_out
)
# Construct final output tensor by concating all fan-in and adding input.
if
np
.
sum
(
self
.
connection_matrix
[:,
-
1
])
==
1
:
src
=
np
.
where
(
self
.
connection_matrix
[:,
-
1
]
==
1
)[
0
][
0
]
return
self
.
projections
[
-
1
](
tensors
[
0
])
if
src
==
0
else
tensors
[
src
]
outputs
=
torch
.
cat
([
tensors
[
src
]
for
src
in
range
(
1
,
self
.
num_nodes
-
1
)
if
self
.
connection_matrix
[
src
,
-
1
]],
1
)
if
self
.
connection_matrix
[
0
,
-
1
]:
outputs
+=
self
.
projections
[
-
1
](
tensors
[
0
])
assert
outputs
.
size
(
1
)
==
self
.
out_features
return
outputs
class
NasBench101Cell
(
nn
.
Module
):
"""
Cell structure that is proposed in NAS-Bench-101 [nasbench101]_ .
This cell is usually used in evaluation of NAS algorithms because there is a ``comprehensive analysis'' of this search space
available, which includes a full architecture-dataset that ``maps 423k unique architectures to metrics
including run time and accuracy''. You can also use the space in your own space design, in which scenario it should be possible
to leverage results in the benchmark to narrow the huge space down to a few efficient architectures.
The space of this cell architecture consists of all possible directed acyclic graphs on no more than ``max_num_nodes`` nodes,
where each possible node (other than IN and OUT) has one of ``op_candidates``, representing the corresponding operation.
Edges connecting the nodes can be no more than ``max_num_edges``.
To align with the paper settings, two vertices specially labeled as operation IN and OUT, are also counted into
``max_num_nodes`` in our implementaion, the default value of ``max_num_nodes`` is 7 and ``max_num_edges`` is 9.
Input of this cell should be of shape :math:`[N, C_{in}, *]`, while output should be `[N, C_{out}, *]`. The shape
of each hidden nodes will be first automatically computed, depending on the cell structure. Each of the ``op_candidates``
should be a callable that accepts computed ``num_features`` and returns a ``Module``. For example,
.. code-block:: python
def conv_bn_relu(num_features):
return nn.Sequential(
nn.Conv2d(num_features, num_features, 1),
nn.BatchNorm2d(num_features),
nn.ReLU()
)
The output of each node is the sum of its input node feed into its operation, except for the last node (output node),
which is the concatenation of its input *hidden* nodes, adding the *IN* node (if IN and OUT are connected).
When input tensor is added with any other tensor, there could be shape mismatch. Therefore, a projection transformation
is needed to transform the input tensor. In paper, this is simply a Conv1x1 followed by BN and ReLU. The ``projection``
parameters accepts ``in_features`` and ``out_features``, returns a ``Module``. This parameter has no default value,
as we hold no assumption that users are dealing with images. An example for this parameter is,
.. code-block:: python
def projection_fn(in_features, out_features):
return nn.Conv2d(in_features, out_features, 1)
Parameters
----------
op_candidates : list of callable
Operation candidates. Each should be a function accepts number of feature, returning nn.Module.
in_features : int
Input dimension of cell.
out_features : int
Output dimension of cell.
projection : callable
Projection module that is used to preprocess the input tensor of the whole cell.
A callable that accept input feature and output feature, returning nn.Module.
max_num_nodes : int
Maximum number of nodes in the cell, input and output included. At least 2. Default: 7.
max_num_edges : int
Maximum number of edges in the cell. Default: 9.
label : str
Identifier of the cell. Cell sharing the same label will semantically share the same choice.
References
----------
.. [nasbench101] Ying, Chris, et al. "Nas-bench-101: Towards reproducible neural architecture search."
International Conference on Machine Learning. PMLR, 2019.
"""
@
staticmethod
def
_make_dict
(
x
):
if
isinstance
(
x
,
list
):
return
OrderedDict
([(
str
(
i
),
t
)
for
i
,
t
in
enumerate
(
x
)])
return
OrderedDict
(
x
)
def
__new__
(
cls
,
op_candidates
:
Union
[
Dict
[
str
,
Callable
[[
int
],
nn
.
Module
]],
List
[
Callable
[[
int
],
nn
.
Module
]]],
in_features
:
int
,
out_features
:
int
,
projection
:
Callable
[[
int
,
int
],
nn
.
Module
],
max_num_nodes
:
int
=
7
,
max_num_edges
:
int
=
9
,
label
:
Optional
[
str
]
=
None
):
def
make_list
(
x
):
return
x
if
isinstance
(
x
,
list
)
else
[
x
]
try
:
label
,
selected
=
get_fixed_dict
(
label
)
op_candidates
=
cls
.
_make_dict
(
op_candidates
)
num_nodes
=
selected
[
f
'
{
label
}
/num_nodes'
]
adjacency_list
=
[
make_list
(
selected
[
f
'
{
label
}
/input_
{
i
}
'
])
for
i
in
range
(
1
,
num_nodes
)]
if
sum
([
len
(
e
)
for
e
in
adjacency_list
])
>
max_num_edges
:
raise
InvalidMutation
(
f
'Expected
{
max_num_edges
}
edges, found:
{
adjacency_list
}
'
)
return
_NasBench101CellFixed
(
[
op_candidates
[
selected
[
f
'
{
label
}
/op_
{
i
}
'
]]
for
i
in
range
(
1
,
num_nodes
-
1
)],
adjacency_list
,
in_features
,
out_features
,
num_nodes
,
projection
)
except
NoContextError
:
return
super
().
__new__
(
cls
)
def
__init__
(
self
,
op_candidates
:
Union
[
Dict
[
str
,
Callable
[[
int
],
nn
.
Module
]],
List
[
Callable
[[
int
],
nn
.
Module
]]],
in_features
:
int
,
out_features
:
int
,
projection
:
Callable
[[
int
,
int
],
nn
.
Module
],
max_num_nodes
:
int
=
7
,
max_num_edges
:
int
=
9
,
label
:
Optional
[
str
]
=
None
):
super
().
__init__
()
self
.
_label
=
generate_new_label
(
label
)
num_vertices_prior
=
[
2
**
i
for
i
in
range
(
2
,
max_num_nodes
+
1
)]
num_vertices_prior
=
(
np
.
array
(
num_vertices_prior
)
/
sum
(
num_vertices_prior
)).
tolist
()
self
.
num_nodes
=
ValueChoice
(
list
(
range
(
2
,
max_num_nodes
+
1
)),
prior
=
num_vertices_prior
,
label
=
f
'
{
self
.
_label
}
/num_nodes'
)
self
.
max_num_nodes
=
max_num_nodes
self
.
max_num_edges
=
max_num_edges
op_candidates
=
self
.
_make_dict
(
op_candidates
)
# this is only for input validation and instantiating enough layer choice and input choice
self
.
hidden_features
=
out_features
self
.
projections
=
nn
.
ModuleList
([
nn
.
Identity
()])
self
.
ops
=
nn
.
ModuleList
([
nn
.
Identity
()])
self
.
inputs
=
nn
.
ModuleList
([
nn
.
Identity
()])
for
_
in
range
(
1
,
max_num_nodes
):
self
.
projections
.
append
(
projection
(
in_features
,
self
.
hidden_features
))
for
i
in
range
(
1
,
max_num_nodes
):
if
i
<
max_num_nodes
-
1
:
self
.
ops
.
append
(
LayerChoice
(
OrderedDict
([(
k
,
op
(
self
.
hidden_features
))
for
k
,
op
in
op_candidates
.
items
()]),
label
=
f
'
{
self
.
_label
}
/op_
{
i
}
'
))
self
.
inputs
.
append
(
InputChoice
(
i
,
None
,
label
=
f
'
{
self
.
_label
}
/input_
{
i
}
'
))
@
property
def
label
(
self
):
return
self
.
_label
def
forward
(
self
,
x
):
# This is a dummy forward and actually not used
tensors
=
[
x
]
for
i
in
range
(
1
,
self
.
max_num_nodes
):
node_input
=
self
.
inputs
[
i
]([
self
.
projections
[
i
](
tensors
[
0
])]
+
[
t
for
t
in
tensors
[
1
:]])
if
i
<
self
.
max_num_nodes
-
1
:
node_output
=
self
.
ops
[
i
](
node_input
)
else
:
node_output
=
node_input
tensors
.
append
(
node_output
)
return
tensors
[
-
1
]
class
NasBench101Mutator
(
Mutator
):
# for validation purposes
# for python execution engine
def
__init__
(
self
,
label
:
Optional
[
str
]):
super
().
__init__
(
label
=
label
)
@
staticmethod
def
candidates
(
node
):
if
'n_candidates'
in
node
.
operation
.
parameters
:
return
list
(
range
(
node
.
operation
.
parameters
[
'n_candidates'
]))
else
:
return
node
.
operation
.
parameters
[
'candidates'
]
@
staticmethod
def
number_of_chosen
(
node
):
if
'n_chosen'
in
node
.
operation
.
parameters
:
return
node
.
operation
.
parameters
[
'n_chosen'
]
return
1
def
mutate
(
self
,
model
:
Model
):
for
node
in
model
.
get_nodes_by_label
(
self
.
label
):
max_num_edges
=
node
.
operation
.
parameters
[
'max_num_edges'
]
break
mutation_dict
=
{
mut
.
mutator
.
label
:
mut
.
samples
for
mut
in
model
.
history
}
num_nodes
=
mutation_dict
[
f
'
{
self
.
label
}
/num_nodes'
][
0
]
adjacency_list
=
[
mutation_dict
[
f
'
{
self
.
label
}
/input_
{
i
}
'
]
for
i
in
range
(
1
,
num_nodes
)]
if
sum
([
len
(
e
)
for
e
in
adjacency_list
])
>
max_num_edges
:
raise
InvalidMutation
(
f
'Expected
{
max_num_edges
}
edges, found:
{
adjacency_list
}
'
)
matrix
=
_NasBench101CellFixed
.
build_connection_matrix
(
adjacency_list
,
num_nodes
)
prune
(
matrix
,
[
None
]
*
len
(
matrix
))
# dummy ops, possible to raise InvalidMutation inside
def
dry_run
(
self
,
model
):
return
[],
model
nni/retiarii/nn/pytorch/utils.py
View file @
403195f0
from
typing
import
Optional
from
typing
import
Any
,
Optional
,
Tuple
from
...utils
import
uid
,
get_current_context
...
...
@@ -9,9 +9,21 @@ def generate_new_label(label: Optional[str]):
return
label
def
get_fixed_value
(
label
:
str
):
def
get_fixed_value
(
label
:
str
)
->
Any
:
ret
=
get_current_context
(
'fixed'
)
try
:
return
ret
[
generate_new_label
(
label
)]
except
KeyError
:
raise
KeyError
(
f
'Fixed context with
{
label
}
not found. Existing values are:
{
ret
}
'
)
def
get_fixed_dict
(
label_prefix
:
str
)
->
Tuple
[
str
,
Any
]:
ret
=
get_current_context
(
'fixed'
)
try
:
label_prefix
=
generate_new_label
(
label_prefix
)
ret
=
{
k
:
v
for
k
,
v
in
ret
.
items
()
if
k
.
startswith
(
label_prefix
+
'/'
)}
if
not
ret
:
raise
KeyError
return
label_prefix
,
ret
except
KeyError
:
raise
KeyError
(
f
'Fixed context with prefix
{
label_prefix
}
not found. Existing values are:
{
ret
}
'
)
nni/retiarii/operation_def/torch_op_def.py
View file @
403195f0
...
...
@@ -59,9 +59,9 @@ class PrimConstant(PyTorchOperation):
def
to_forward_code
(
self
,
field
:
str
,
output
:
str
,
inputs
:
List
[
str
],
inputs_value
:
List
[
Any
]
=
None
)
->
str
:
# TODO: refactor this part, maybe we can remove the code gen of prim::Constant
# TODO: deal with all the types
if
self
.
parameters
[
'type'
]
==
'None'
:
if
self
.
parameters
[
'type'
]
in
[
'None'
,
'NoneType'
]
:
return
f
'
{
output
}
= None'
elif
self
.
parameters
[
'type'
]
in
(
'int'
,
'float'
,
'bool'
,
'int[]'
):
elif
self
.
parameters
[
'type'
]
in
(
'int'
,
'float'
,
'bool'
,
'int[]'
):
# 'Long()' ???
return
f
'
{
output
}
=
{
self
.
parameters
[
"value"
]
}
'
elif
self
.
parameters
[
'type'
]
==
'str'
:
str_val
=
self
.
parameters
[
"value"
]
...
...
@@ -171,7 +171,7 @@ class AtenTensors(PyTorchOperation):
'aten::ones_like'
,
'aten::zeros_like'
,
'aten::rand'
,
'aten::randn'
,
'aten::scalar_tensor'
,
'aten::new_full'
,
'aten::new_empty'
,
'aten::new_zeros'
,
'aten::arange'
,
'aten::tensor'
,
'aten::ones'
,
'aten::zeros'
]
'aten::tensor'
,
'aten::ones'
,
'aten::zeros'
,
'aten::as_tensor'
]
def
to_forward_code
(
self
,
field
:
str
,
output
:
str
,
inputs
:
List
[
str
],
inputs_value
:
List
[
Any
]
=
None
)
->
str
:
schemas
=
torch
.
_C
.
_jit_get_schemas_for_operator
(
self
.
type
)
...
...
@@ -238,7 +238,13 @@ class AtenIndex(PyTorchOperation):
ManuallyChooseDef
=
{
'aten::flatten'
:
[(
'start_dim'
,
'int'
,
'0'
),
(
'end_dim'
,
'int'
,
'-1'
)],
'aten::split'
:
[(
'split_size'
,
'int'
,
'None'
),
(
'dim'
,
'int'
,
'0'
)]
'aten::split'
:
[(
'split_size'
,
'int'
,
'None'
),
(
'dim'
,
'int'
,
'0'
)],
# in v1.9 dtype is supported as input argument for view, but torch script does not support it
'aten::view'
:
[(
'size'
,
'List[int]'
,
'None'
)],
# NOTE: dim supports different types: List[int], List[str], Optional[List[int]], now we only support the first two, refactor needed
# torch.std(input, dim, unbiased, keepdim=False, *, out=None) Tensor
# torch.std(input, unbiased) Tensor
'aten::std'
:
[(
'dim'
,
'List[int]'
,
'None'
),
(
'unbiased'
,
'bool'
,
'True'
),
(
'keepdim'
,
'bool'
,
'False'
)]
}
TensorOpExceptions
=
{
...
...
@@ -426,4 +432,11 @@ class AtenAvgpool2d(PyTorchOperation):
# NOTE: it is not included in the above aten ops for unkown reason
_ori_type_name
=
[
'aten::avg_pool2d'
]
def
to_forward_code
(
self
,
field
:
str
,
output
:
str
,
inputs
:
List
[
str
],
inputs_value
:
List
[
Any
]
=
None
)
->
str
:
return
f
'
{
output
}
= F.avg_pool2d(
{
", "
.
join
(
inputs
)
}
)'
\ No newline at end of file
return
f
'
{
output
}
= F.avg_pool2d(
{
", "
.
join
(
inputs
)
}
)'
class
AtenDet
(
PyTorchOperation
):
# for torch 1.9
# NOTE: it is not included in the above aten ops, maybe because torch.det is alias for torch.linalg.det
_ori_type_name
=
[
'aten::linalg_det'
]
def
to_forward_code
(
self
,
field
:
str
,
output
:
str
,
inputs
:
List
[
str
],
inputs_value
:
List
[
Any
]
=
None
)
->
str
:
return
f
'
{
output
}
= torch.det(
{
inputs
[
0
]
}
)'
\ No newline at end of file
nni/retiarii/strategy/bruteforce.py
View file @
403195f0
...
...
@@ -8,7 +8,7 @@ import random
import
time
from
typing
import
Any
,
Dict
,
List
from
..
import
Sampler
,
submit_models
,
query_available_resources
,
budget_exhausted
from
..
import
InvalidMutation
,
Sampler
,
submit_models
,
query_available_resources
,
budget_exhausted
from
.base
import
BaseStrategy
from
.utils
import
dry_run_for_search_space
,
get_targeted_model
,
filter_model
...
...
@@ -125,6 +125,9 @@ class Random(BaseStrategy):
if
budget_exhausted
():
return
time
.
sleep
(
self
.
_polling_interval
)
model
=
get_targeted_model
(
base_model
,
applied_mutators
,
sample
)
if
filter_model
(
self
.
filter
,
model
):
submit_models
(
model
)
try
:
model
=
get_targeted_model
(
base_model
,
applied_mutators
,
sample
)
if
filter_model
(
self
.
filter
,
model
):
submit_models
(
model
)
except
InvalidMutation
as
e
:
_logger
.
warning
(
f
'Invalid mutation:
{
e
}
. Skip.'
)
nni/retiarii/utils.py
View file @
403195f0
...
...
@@ -67,6 +67,10 @@ def get_importable_name(cls, relocate_module=False):
return
module_name
+
'.'
+
cls
.
__name__
class
NoContextError
(
Exception
):
pass
class
ContextStack
:
"""
This is to maintain a globally-accessible context envinronment that is visible to everywhere.
...
...
@@ -98,7 +102,8 @@ class ContextStack:
@
classmethod
def
top
(
cls
,
key
:
str
)
->
Any
:
assert
cls
.
_stack
[
key
],
'Context is empty.'
if
not
cls
.
_stack
[
key
]:
raise
NoContextError
(
'Context is empty.'
)
return
cls
.
_stack
[
key
][
-
1
]
...
...
nni/tools/nnictl/config_schema.py
View file @
403195f0
...
...
@@ -127,6 +127,7 @@ common_schema = {
Optional
(
'description'
):
setType
(
'description'
,
str
),
'trialConcurrency'
:
setNumberRange
(
'trialConcurrency'
,
int
,
1
,
99999
),
Optional
(
'maxExecDuration'
):
And
(
Regex
(
r
'^[1-9][0-9]*[s|m|h|d]$'
,
error
=
'ERROR: maxExecDuration format is [digit]{s,m,h,d}'
)),
Optional
(
'maxTrialDuration'
):
And
(
Regex
(
r
'^[1-9][0-9]*[s|m|h|d]$'
,
error
=
'ERROR: maxTrialDuration format is [digit]{s,m,h,d}'
)),
Optional
(
'maxTrialNum'
):
setNumberRange
(
'maxTrialNum'
,
int
,
1
,
99999
),
'trainingServicePlatform'
:
setChoice
(
'trainingServicePlatform'
,
'remote'
,
'local'
,
'pai'
,
'kubeflow'
,
'frameworkcontroller'
,
'dlts'
,
'aml'
,
'adl'
,
'hybrid'
),
...
...
nni/tools/nnictl/launcher.py
View file @
403195f0
...
...
@@ -250,6 +250,7 @@ def set_experiment_v1(experiment_config, mode, port, config_file_name):
request_data
[
'maxExecDuration'
]
=
experiment_config
[
'maxExecDuration'
]
request_data
[
'maxExperimentDuration'
]
=
str
(
experiment_config
[
'maxExecDuration'
])
+
's'
request_data
[
'maxTrialNum'
]
=
experiment_config
[
'maxTrialNum'
]
request_data
[
'maxTrialDuration'
]
=
experiment_config
[
'maxTrialDuration'
]
request_data
[
'maxTrialNumber'
]
=
experiment_config
[
'maxTrialNum'
]
request_data
[
'searchSpace'
]
=
experiment_config
.
get
(
'searchSpace'
)
request_data
[
'trainingServicePlatform'
]
=
experiment_config
.
get
(
'trainingServicePlatform'
)
...
...
@@ -538,7 +539,9 @@ def manage_stopped_experiment(args, mode):
#find the latest stopped experiment
if
not
args
.
id
:
print_error
(
'Please set experiment id!
\n
You could use
\'
nnictl {0} id
\'
to {0} a stopped experiment!
\n
'
\
'You could use
\'
nnictl experiment list --all
\'
to show all experiments!'
.
format
(
mode
))
'You could use
\'
nnictl experiment list --all
\'
to show all experiments!
\n
'
\
'If your experiment is not started in current machine, you could specify experiment folder using '
\
'--experiment_dir argument'
.
format
(
mode
))
exit
(
1
)
else
:
if
experiments_dict
.
get
(
args
.
id
)
is
None
:
...
...
@@ -569,8 +572,48 @@ def manage_stopped_experiment(args, mode):
def
view_experiment
(
args
):
'''view a stopped experiment'''
manage_stopped_experiment
(
args
,
'view'
)
if
args
.
experiment_dir
:
manage_external_experiment
(
args
,
'view'
)
else
:
manage_stopped_experiment
(
args
,
'view'
)
def
resume_experiment
(
args
):
'''resume an experiment'''
manage_stopped_experiment
(
args
,
'resume'
)
'''view a stopped experiment'''
if
args
.
experiment_dir
:
manage_external_experiment
(
args
,
'resume'
)
else
:
manage_stopped_experiment
(
args
,
'resume'
)
def
manage_external_experiment
(
args
,
mode
):
'''view a experiment from external path'''
# validate arguments
if
not
os
.
path
.
exists
(
args
.
experiment_dir
):
print_error
(
'Folder %s does not exist!'
%
args
.
experiment_dir
)
exit
(
1
)
if
not
os
.
path
.
isdir
(
args
.
experiment_dir
):
print_error
(
'Path %s is not folder directory!'
%
args
.
experiment_dir
)
exit
(
1
)
if
args
.
id
:
experiment_id
=
args
.
id
log_dir
=
args
.
experiment_dir
else
:
print_normal
(
'NNI can not detect experiment id in argument, will use last folder name as experiment id in experiment_dir argument.'
)
experiment_id
=
Path
(
args
.
experiment_dir
).
name
log_dir
=
os
.
path
.
dirname
(
args
.
experiment_dir
)
if
not
experiment_id
:
print_error
(
"Please set experiment id argument, or add id as the last folder name in experiment_dir argument."
)
exit
(
1
)
args
.
url_prefix
=
None
experiment_config
=
Config
(
experiment_id
,
log_dir
).
get_config
()
assert
'trainingService'
in
experiment_config
or
'trainingServicePlatform'
in
experiment_config
try
:
if
'trainingServicePlatform'
in
experiment_config
:
experiment_config
[
'logDir'
]
=
log_dir
launch_experiment
(
args
,
experiment_config
,
mode
,
experiment_id
,
1
)
else
:
experiment_config
[
'experimentWorkingDirectory'
]
=
log_dir
launch_experiment
(
args
,
experiment_config
,
mode
,
experiment_id
,
2
)
except
Exception
as
exception
:
print_error
(
exception
)
exit
(
1
)
nni/tools/nnictl/launcher_utils.py
View file @
403195f0
...
...
@@ -110,6 +110,8 @@ def set_default_values(experiment_config):
experiment_config
[
'maxExecDuration'
]
=
'999d'
if
experiment_config
.
get
(
'maxTrialNum'
)
is
None
:
experiment_config
[
'maxTrialNum'
]
=
99999
if
experiment_config
.
get
(
'maxTrialDuration'
)
is
None
:
experiment_config
[
'maxTrialDuration'
]
=
'999d'
if
experiment_config
[
'trainingServicePlatform'
]
==
'remote'
or
\
experiment_config
[
'trainingServicePlatform'
]
==
'hybrid'
and
\
'remote'
in
experiment_config
[
'hybridConfig'
][
'trainingServicePlatforms'
]:
...
...
@@ -126,3 +128,5 @@ def validate_all_content(experiment_config, config_path):
if
'maxExecDuration'
in
experiment_config
:
experiment_config
[
'maxExecDuration'
]
=
parse_time
(
experiment_config
[
'maxExecDuration'
])
if
'maxTrialDuration'
in
experiment_config
:
experiment_config
[
'maxTrialDuration'
]
=
parse_time
(
experiment_config
[
'maxTrialDuration'
])
nni/tools/nnictl/nnictl.py
View file @
403195f0
...
...
@@ -66,12 +66,16 @@ def parse_args():
parser_resume
.
add_argument
(
'--port'
,
'-p'
,
default
=
DEFAULT_REST_PORT
,
dest
=
'port'
,
type
=
int
,
help
=
'the port of restful server'
)
parser_resume
.
add_argument
(
'--debug'
,
'-d'
,
action
=
'store_true'
,
help
=
' set debug mode'
)
parser_resume
.
add_argument
(
'--foreground'
,
'-f'
,
action
=
'store_true'
,
help
=
' set foreground mode, print log content to terminal'
)
parser_resume
.
add_argument
(
'--experiment_dir'
,
'-e'
,
help
=
'resume experiment from external folder, specify the full path of '
\
'experiment folder'
)
parser_resume
.
set_defaults
(
func
=
resume_experiment
)
# parse view command
parser_view
=
subparsers
.
add_parser
(
'view'
,
help
=
'view a stopped experiment'
)
parser_view
.
add_argument
(
'id'
,
nargs
=
'?'
,
help
=
'The id of the experiment you want to view'
)
parser_view
.
add_argument
(
'--port'
,
'-p'
,
default
=
DEFAULT_REST_PORT
,
dest
=
'port'
,
type
=
int
,
help
=
'the port of restful server'
)
parser_view
.
add_argument
(
'--experiment_dir'
,
'-e'
,
help
=
'view experiment from external folder, specify the full path of '
\
'experiment folder'
)
parser_view
.
set_defaults
(
func
=
view_experiment
)
# parse update command
...
...
nni/tools/nnictl/nnictl_utils.py
View file @
403195f0
...
...
@@ -524,7 +524,7 @@ def experiment_clean(args):
for
experiment_id
in
experiment_id_list
:
experiment_id
=
get_config_filename
(
args
)
experiment_config
=
Config
(
experiment_id
,
Experiments
().
get_all_experiments
()[
experiment_id
][
'logDir'
]).
get_config
()
platform
=
experiment_config
.
get
(
'trainingServicePlatform'
)
platform
=
experiment_config
.
get
(
'trainingServicePlatform'
)
or
experiment_config
.
get
(
'trainingService'
,
{}).
get
(
'platform'
)
if
platform
==
'remote'
:
machine_list
=
experiment_config
.
get
(
'machineList'
)
remote_clean
(
machine_list
,
experiment_id
)
...
...
pipelines/full-test-linux.yml
View file @
403195f0
...
...
@@ -15,8 +15,8 @@ jobs:
echo "##vso[task.setvariable variable=PATH]${PATH}:${HOME}/.local/bin"
echo "##vso[task.setvariable variable=NNI_RELEASE]999.$(date -u +%Y%m%d%H%M%S)"
python3 -m pip install -
-upgrade pip setuptools wheel
python3 -m pip install
pytes
t
python3 -m pip install -
U -r dependencies/setup.txt
python3 -m pip install
-r dependencies/develop.tx
t
displayName
:
Prepare
-
script
:
|
...
...
@@ -28,16 +28,9 @@ jobs:
-
script
:
|
set -e
python3 -m pip install scikit-learn==0.24.1
python3 -m pip install torchvision==0.7.0
python3 -m pip install torch==1.6.0
python3 -m pip install 'pytorch-lightning>=1.1.1'
python3 -m pip install keras==2.1.6
python3 -m pip install tensorflow==2.3.1 tensorflow-estimator==2.3.0
python3 -m pip install thop
python3 -m pip install pybnn
python3 -m pip install tianshou>=0.4.1 gym
sudo apt-get install swig -y
python3 -m pip install -r dependencies/recommended_gpu.txt
python3 -m pip install -e .[SMAC,BOHB,PPOTuner,DNGO]
displayName
:
Install extra dependencies
# Need del later
...
...
pipelines/full-test-windows.yml
View file @
403195f0
...
...
@@ -12,8 +12,8 @@ jobs:
steps
:
-
script
:
|
python -m pip install -
-upgrade pip setuptools wheel
python -m pip install
pytes
t
python -m pip install -
U -r dependencies/setup.txt
python -m pip install
-r dependencies/develop.tx
t
displayName
:
Install Python tools
-
script
:
|
...
...
@@ -25,13 +25,8 @@ jobs:
displayName
:
Install NNI
-
script
:
|
python -m pip install scikit-learn==0.24.1
python -m pip install keras==2.1.6
python -m pip install torch==1.6.0 torchvision==0.7.0 -f https://download.pytorch.org/whl/torch_stable.html
python -m pip install 'pytorch-lightning>=1.1.1'
python -m pip install tensorflow==2.3.1 tensorflow-estimator==2.3.0
python -m pip install pybnn
python -m pip install tianshou>=0.4.1 gym
python -m pip install -r dependencies/recommended.txt
python -m pip install -e .[DNGO]
displayName
:
Install extra dependencies
# Need del later
...
...
pipelines/integration-test-trt.yml
0 → 100644
View file @
403195f0
trigger
:
none
pr
:
none
schedules
:
-
cron
:
0 16 * * *
branches
:
include
:
[
master
]
jobs
:
-
job
:
trt
pool
:
NNI CI TENSORRT
timeoutInMinutes
:
120
steps
:
-
script
:
|
export NNI_RELEASE=999.$(date -u +%Y%m%d%H%M%S)
echo "##vso[task.setvariable variable=PATH]${PATH}:${HOME}/ENTER/bin"
echo "##vso[task.setvariable variable=NNI_RELEASE]${NNI_RELEASE}"
echo "Working directory: ${PWD}"
echo "NNI version: ${NNI_RELEASE}"
echo "Build docker image: $(build_docker_image)"
python3 -m pip install --upgrade pip setuptools
displayName
:
Prepare
-
script
:
|
set -e
python3 setup.py build_ts
python3 setup.py bdist_wheel -p manylinux1_x86_64
python3 -m pip install dist/nni-${NNI_RELEASE}-py3-none-manylinux1_x86_64.whl[SMAC,BOHB]
displayName
:
Build and install NNI
-
script
:
|
set -e
cd test
python3 nni_test/nnitest/test_quantize_model_speedup.py
displayName
:
Quantize model speedup test
test/.gitignore
View file @
403195f0
...
...
@@ -10,3 +10,4 @@ _generated_model
data
generated
lightning_logs
model.onnx
test/config/integration_tests.yml
View file @
403195f0
...
...
@@ -43,6 +43,7 @@ testCases:
storageAccountName
:
nennistorage
storageAccountKey
:
$(azureblob_token_test)
containerName
:
sharedstorage
trainingService
:
remote
validator
:
class
:
FileExistValidator
kwargs
:
...
...
test/ut/retiarii/test_convert_pytorch.py
View file @
403195f0
...
...
@@ -375,7 +375,7 @@ class TestPytorch(unittest.TestCase):
# NOTE: torch script gets an incorrect graph...
def
test_optional_inputs_with_mixed_optionals
(
self
):
class
MixedModel
(
nn
.
Module
):
def
forward
(
self
,
x
:
'Tensor'
,
y
:
'Tensor'
,
z
:
'Tensor'
):
def
forward
(
self
,
x
,
y
,
z
):
if
y
is
not
None
:
return
x
+
y
if
z
is
not
None
:
...
...
Prev
1
…
5
6
7
8
9
10
11
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment