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
gaoqiong
MIGraphX
Commits
b9d37172
Commit
b9d37172
authored
Oct 10, 2023
by
Khalique Ahmed
Browse files
manual merge
parents
1af66a1c
ea62d7aa
Changes
337
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
886 additions
and
150 deletions
+886
-150
src/include/migraphx/op/random_seed.hpp
src/include/migraphx/op/random_seed.hpp
+72
-0
src/include/migraphx/op/random_uniform.hpp
src/include/migraphx/op/random_uniform.hpp
+103
-0
src/include/migraphx/op/reduce_op.hpp
src/include/migraphx/op/reduce_op.hpp
+2
-2
src/include/migraphx/op/reshape.hpp
src/include/migraphx/op/reshape.hpp
+20
-46
src/include/migraphx/op/reshape_lazy.hpp
src/include/migraphx/op/reshape_lazy.hpp
+279
-0
src/include/migraphx/op/reverse.hpp
src/include/migraphx/op/reverse.hpp
+5
-5
src/include/migraphx/op/roialign.hpp
src/include/migraphx/op/roialign.hpp
+6
-7
src/include/migraphx/op/scatter.hpp
src/include/migraphx/op/scatter.hpp
+1
-1
src/include/migraphx/op/slice.hpp
src/include/migraphx/op/slice.hpp
+216
-66
src/include/migraphx/operators.hpp
src/include/migraphx/operators.hpp
+1
-0
src/include/migraphx/pad_calc.hpp
src/include/migraphx/pad_calc.hpp
+9
-1
src/include/migraphx/ranges.hpp
src/include/migraphx/ranges.hpp
+1
-1
src/include/migraphx/shape.hpp
src/include/migraphx/shape.hpp
+1
-1
src/include/migraphx/shape_for_each.hpp
src/include/migraphx/shape_for_each.hpp
+9
-5
src/include/migraphx/simplify_dyn_ops.hpp
src/include/migraphx/simplify_dyn_ops.hpp
+49
-0
src/include/migraphx/simplify_reshapes.hpp
src/include/migraphx/simplify_reshapes.hpp
+1
-0
src/include/migraphx/stringutils.hpp
src/include/migraphx/stringutils.hpp
+5
-1
src/include/migraphx/verify.hpp
src/include/migraphx/verify.hpp
+96
-8
src/include/migraphx/verify_args.hpp
src/include/migraphx/verify_args.hpp
+9
-5
src/instruction.cpp
src/instruction.cpp
+1
-1
No files found.
src/include/migraphx/op/random_seed.hpp
0 → 100644
View file @
b9d37172
/*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MIGRAPHX_GUARD_OPERATORS_RANDOM_SEED_HPP
#define MIGRAPHX_GUARD_OPERATORS_RANDOM_SEED_HPP
#include <migraphx/check_shapes.hpp>
#include <migraphx/argument.hpp>
#include <random>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
/**
* Generates a random seed for the use of random number generators. Generating the seed
* at runtime guarantees there will be a different random sequence on every execution.
* This operation has no inputs or attributes, and outputs an unsigned integer tensor with
* a single value.
*/
struct
random_seed
{
shape
::
type_t
dtype
=
shape
::
type_t
::
uint64_type
;
template
<
class
Self
,
class
F
>
static
auto
reflect
(
Self
&
self
,
F
f
)
{
return
pack
(
f
(
self
.
dtype
,
"dtype"
));
}
std
::
string
name
()
const
{
return
"random_seed"
;
}
shape
compute_shape
(
const
std
::
vector
<
shape
>&
inputs
)
const
{
check_shapes
{
inputs
,
*
this
}.
has
(
0
);
return
shape
{
dtype
};
}
argument
compute
(
const
shape
&
output_shape
,
const
std
::
vector
<
argument
>&
)
const
{
argument
result
(
output_shape
);
result
.
visit
([
&
](
auto
output
)
{
output
.
front
()
=
std
::
random_device
{}();
});
return
result
;
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/random_uniform.hpp
0 → 100644
View file @
b9d37172
/*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Random Uniform distribution operator. Given a shape, populate it with random
* values. Calls to random_uniform using the same randomization seed as a
* literal input will
* always generate the same pseudo-random sequence.
*
* Inputs: (1) randomization seed (any type is allowed)
* (2) output buffer argument to be populated.
*
* Attributes: none
*
* Output: Returns the buffer from input #2.
*
*/
#ifndef MIGRAPHX_GUARD_OPERATORS_RANDOM_UNIFORM_HPP
#define MIGRAPHX_GUARD_OPERATORS_RANDOM_UNIFORM_HPP
#include <migraphx/check_shapes.hpp>
#include <migraphx/argument.hpp>
#include <random>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
/**
* random_uniform populates the passed shape with random numbers, in a uniform
* distribution. Range for floating-point data types is (0, 1);
* for integer types it is [0, <max value for the type>]
*/
struct
random_uniform
{
// The random_uniform operation needs the random number generator seed
// to be passed as a runtime input.
std
::
string
name
()
const
{
return
"random_uniform"
;
}
shape
compute_shape
(
std
::
vector
<
shape
>
inputs
)
const
{
check_shapes
{
inputs
,
*
this
,
true
}.
has
(
2
);
return
inputs
.
at
(
1
);
}
argument
compute
(
const
shape
&
,
std
::
vector
<
argument
>
args
)
const
{
// Output goes into the passed buffer, not the shape output.
auto
result
=
args
[
1
];
uint64_t
local_seed
=
args
[
0
].
at
<
uint64_t
>
(
0
);
std
::
mt19937
gen
(
local_seed
);
result
.
visit
([
&
](
auto
output
)
{
using
type
=
typename
decltype
(
output
)
::
value_type
;
if
constexpr
(
std
::
is_integral
<
type
>
{})
{
// default range for all integer types is
// (0, std::uniform_int_distribution<type>::max()).
// Todo: enable different ranges
std
::
uniform_int_distribution
<
type
>
dis
;
std
::
generate
(
output
.
begin
(),
output
.
end
(),
[
&
]
{
return
dis
(
gen
);
});
}
else
{
// default real distribution type is double with range (0, 1);
std
::
uniform_real_distribution
<>
dis
;
std
::
generate
(
output
.
begin
(),
output
.
end
(),
[
&
]
{
return
dis
(
gen
);
});
}
});
return
result
;
}
std
::
ptrdiff_t
output_alias
(
const
std
::
vector
<
shape
>&
)
const
{
return
1
;
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/reduce_op.hpp
View file @
b9d37172
/*
/*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) 2015-202
2
Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2015-202
3
Advanced Micro Devices, Inc. All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
...
@@ -163,7 +163,7 @@ struct reduce_op : op_name<Derived>
...
@@ -163,7 +163,7 @@ struct reduce_op : op_name<Derived>
auto
&
self
=
static_cast
<
const
Derived
&>
(
*
this
);
auto
&
self
=
static_cast
<
const
Derived
&>
(
*
this
);
auto
data_idx
=
out_idx
;
auto
data_idx
=
out_idx
;
accumulator
val
=
self
.
init
();
accumulator
val
=
self
.
init
();
shape_for_each
(
batch_shape
,
[
&
](
auto
b_idx
)
{
shape_for_each
(
batch_shape
,
[
&
](
const
auto
&
b_idx
)
{
this
->
tune_dims
(
tuned_axes
,
b_idx
,
data_idx
);
this
->
tune_dims
(
tuned_axes
,
b_idx
,
data_idx
);
accumulator
x
=
input
(
data_idx
.
begin
(),
data_idx
.
end
());
accumulator
x
=
input
(
data_idx
.
begin
(),
data_idx
.
end
());
val
=
self
.
op
()(
accumulator
{
self
.
input
()(
x
)},
val
);
val
=
self
.
op
()(
accumulator
{
self
.
input
()(
x
)},
val
);
...
...
src/include/migraphx/op/reshape.hpp
View file @
b9d37172
/*
/*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) 2015-202
2
Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2015-202
3
Advanced Micro Devices, Inc. All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
...
@@ -29,7 +29,8 @@
...
@@ -29,7 +29,8 @@
#include <migraphx/config.hpp>
#include <migraphx/config.hpp>
#include <migraphx/value.hpp>
#include <migraphx/value.hpp>
#include <migraphx/dyn_output.hpp>
#include <migraphx/dyn_output.hpp>
#include <migraphx/optional.hpp>
#include <algorithm>
namespace
migraphx
{
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
inline
namespace
MIGRAPHX_INLINE_NS
{
...
@@ -45,8 +46,6 @@ struct reshape
...
@@ -45,8 +46,6 @@ struct reshape
return
pack
(
f
(
self
.
dims
,
"dims"
));
return
pack
(
f
(
self
.
dims
,
"dims"
));
}
}
value
attributes
()
const
{
return
{{
"require_std_shape"
,
true
}};
}
std
::
string
name
()
const
{
return
"reshape"
;
}
std
::
string
name
()
const
{
return
"reshape"
;
}
shape
dyn_compute_shape
(
shape
s0
)
const
shape
dyn_compute_shape
(
shape
s0
)
const
...
@@ -110,27 +109,9 @@ struct reshape
...
@@ -110,27 +109,9 @@ struct reshape
return
it
;
return
it
;
}
}
template
<
class
DimIterator
,
class
StrideIterator
>
// This will attempt to alias the dimensions of the input shape to the lens of
static
auto
can_strides_merge
(
DimIterator
dim_start
,
// `rdims`. Unlike reshape_lazy though we can modify memory layout with copies and this
DimIterator
dim_last
,
// can remove previous nullopts that were sent back for the alias case
StrideIterator
stride_start
,
StrideIterator
stride_last
)
{
assert
(
std
::
distance
(
dim_start
,
dim_last
)
==
std
::
distance
(
stride_start
,
stride_last
));
auto
cstride
=
*
std
::
prev
(
stride_last
);
return
std
::
equal
(
std
::
make_reverse_iterator
(
dim_last
),
std
::
make_reverse_iterator
(
dim_start
+
1
),
std
::
make_reverse_iterator
(
stride_last
-
1
),
std
::
make_reverse_iterator
(
stride_start
),
[
&
](
auto
dim
,
auto
stride
)
{
cstride
*=
dim
;
return
stride
==
cstride
;
});
}
// This will reshape the dimesions of the input shape to use the lens of
// `rdims`. If this can't be done without changing memory layout then it
// will return nullopt
static
optional
<
shape
>
reshape_dims
(
const
shape
&
input
,
const
std
::
vector
<
std
::
size_t
>&
rdims
)
static
optional
<
shape
>
reshape_dims
(
const
shape
&
input
,
const
std
::
vector
<
std
::
size_t
>&
rdims
)
{
{
if
(
input
.
standard
())
if
(
input
.
standard
())
...
@@ -155,13 +136,8 @@ struct reshape
...
@@ -155,13 +136,8 @@ struct reshape
{
{
auto
start
=
idims
.
begin
()
+
i
;
auto
start
=
idims
.
begin
()
+
i
;
auto
it
=
compute_end_dim
(
start
,
idims
.
end
(),
rdim
);
auto
it
=
compute_end_dim
(
start
,
idims
.
end
(),
rdim
);
if
(
it
==
start
)
return
nullopt
;
auto
n
=
it
-
start
;
auto
n
=
it
-
start
;
assert
((
i
+
n
)
<=
istrides
.
size
());
assert
((
i
+
n
)
<=
istrides
.
size
());
if
(
not
can_strides_merge
(
start
,
it
+
1
,
istrides
.
begin
()
+
i
,
istrides
.
begin
()
+
i
+
n
+
1
))
return
nullopt
;
i
+=
n
;
i
+=
n
;
rstrides
.
push_back
(
istrides
[
i
]);
rstrides
.
push_back
(
istrides
[
i
]);
}
}
...
@@ -170,8 +146,7 @@ struct reshape
...
@@ -170,8 +146,7 @@ struct reshape
{
{
auto
start
=
rdims
.
begin
()
+
i
;
auto
start
=
rdims
.
begin
()
+
i
;
auto
it
=
compute_end_dim
(
start
,
rdims
.
end
(),
idim
);
auto
it
=
compute_end_dim
(
start
,
rdims
.
end
(),
idim
);
if
(
it
==
start
)
return
nullopt
;
auto
n
=
it
-
start
;
auto
n
=
it
-
start
;
assert
((
r
+
n
)
<=
rdims
.
size
());
assert
((
r
+
n
)
<=
rdims
.
size
());
auto
stride
=
istrides
[
i
]
*
idim
;
auto
stride
=
istrides
[
i
]
*
idim
;
...
@@ -191,15 +166,11 @@ struct reshape
...
@@ -191,15 +166,11 @@ struct reshape
auto
stride
=
rstrides
.
back
();
auto
stride
=
rstrides
.
back
();
for
(
auto
d
:
range
(
rdims
.
begin
()
+
rstrides
.
size
(),
rdims
.
end
()))
for
(
auto
d
:
range
(
rdims
.
begin
()
+
rstrides
.
size
(),
rdims
.
end
()))
{
{
if
(
d
!=
1
)
(
void
)
d
;
return
nullopt
;
rstrides
.
push_back
(
stride
);
rstrides
.
push_back
(
stride
);
}
}
}
}
if
(
rdims
.
size
()
!=
rstrides
.
size
())
return
nullopt
;
return
shape
{
input
.
type
(),
rdims
,
rstrides
};
return
shape
{
input
.
type
(),
rdims
,
rstrides
};
}
}
...
@@ -233,25 +204,24 @@ struct reshape
...
@@ -233,25 +204,24 @@ struct reshape
}
}
auto
s
=
reshape_dims
(
inputs
.
front
(),
rdims
);
auto
s
=
reshape_dims
(
inputs
.
front
(),
rdims
);
if
(
not
s
.
has_value
())
MIGRAPHX_THROW
(
"Reshape on axis that is not packed."
);
if
(
s
->
elements
()
!=
inputs
.
front
().
elements
())
if
(
s
->
elements
()
!=
inputs
.
front
().
elements
())
MIGRAPHX_THROW
(
"
R
eshape: Wrong number of elements for reshape: reshape has "
+
MIGRAPHX_THROW
(
"
r
eshape: Wrong number of elements for reshape: reshape has "
+
std
::
to_string
(
s
->
elements
())
+
" elements whereas the input has "
+
std
::
to_string
(
s
->
elements
())
+
" elements whereas the input has "
+
std
::
to_string
(
inputs
.
front
().
elements
()));
std
::
to_string
(
inputs
.
front
().
elements
()));
assert
(
s
->
bytes
()
==
inputs
.
front
().
bytes
());
return
*
s
;
return
*
s
;
}
}
shape
compute_shape
(
std
::
vector
<
shape
>
inputs
)
const
shape
compute_shape
(
std
::
vector
<
shape
>
inputs
)
const
{
{
check_shapes
{
inputs
,
*
this
,
true
}.
has
(
1
);
check_shapes
{
inputs
,
*
this
,
true
}.
has
(
1
);
auto
n_neg_dims
=
std
::
count
(
dims
.
begin
(),
dims
.
end
(),
-
1
);
auto
n_neg_dims
=
std
::
count
(
dims
.
begin
(),
dims
.
end
(),
-
1
);
if
(
n_neg_dims
>
1
)
if
(
n_neg_dims
>
1
)
MIGRAPHX_THROW
(
"Reshape: Dimensions for reshape can only have one -1 dim"
);
MIGRAPHX_THROW
(
"reshape: Dimensions for reshape can only have one -1 dim"
);
auto
s0
=
inputs
[
0
];
auto
s0
=
inputs
.
front
();
if
(
s0
.
dynamic
())
if
(
s0
.
dynamic
())
{
{
return
dyn_compute_shape
(
s0
);
return
dyn_compute_shape
(
s0
);
...
@@ -264,10 +234,14 @@ struct reshape
...
@@ -264,10 +234,14 @@ struct reshape
argument
compute
(
const
dyn_output
&
dyn_out
,
std
::
vector
<
argument
>
args
)
const
argument
compute
(
const
dyn_output
&
dyn_out
,
std
::
vector
<
argument
>
args
)
const
{
{
return
args
[
0
].
reshape
(
dyn_out
.
computed_shape
);
assert
(
dyn_out
.
computed_shape
.
standard
()
);
}
argument
result
{
dyn_out
.
computed_shape
};
std
::
ptrdiff_t
output_alias
(
const
std
::
vector
<
shape
>&
)
const
{
return
0
;
}
visit_all
(
result
,
args
[
0
])([
&
](
auto
output
,
auto
input
)
{
std
::
copy
(
input
.
begin
(),
input
.
end
(),
output
.
begin
());
});
return
result
;
}
};
};
}
// namespace op
}
// namespace op
...
...
src/include/migraphx/op/reshape_lazy.hpp
0 → 100644
View file @
b9d37172
/*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MIGRAPHX_GUARD_OPERATORS_RESHAPE_LAZY_HPP
#define MIGRAPHX_GUARD_OPERATORS_RESHAPE_LAZY_HPP
#include <migraphx/check_shapes.hpp>
#include <migraphx/argument.hpp>
#include <migraphx/config.hpp>
#include <migraphx/value.hpp>
#include <migraphx/dyn_output.hpp>
#include <migraphx/optional.hpp>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
reshape_lazy
{
std
::
vector
<
int64_t
>
dims
;
template
<
class
Self
,
class
F
>
static
auto
reflect
(
Self
&
self
,
F
f
)
{
return
pack
(
f
(
self
.
dims
,
"dims"
));
}
value
attributes
()
const
{
return
{{
"require_std_shape"
,
true
}};
}
std
::
string
name
()
const
{
return
"reshape_lazy"
;
}
shape
dyn_compute_shape
(
shape
s0
)
const
{
auto
dyn_dims
=
s0
.
dyn_dims
();
auto
num_not_fixed
=
std
::
count_if
(
dyn_dims
.
cbegin
(),
dyn_dims
.
cend
(),
[](
auto
dd
)
{
return
not
dd
.
is_fixed
();
});
if
(
num_not_fixed
!=
1
)
{
MIGRAPHX_THROW
(
"reshape_lazy: Only supports one non-fixed dynamic_dimension"
);
}
// track number of fixed elements in input and output
std
::
size_t
num_dims_ele
=
1
;
std
::
size_t
num_dd_ele
=
1
;
for
(
std
::
size_t
i
=
0
;
i
<
dyn_dims
.
size
();
++
i
)
{
if
(
dyn_dims
[
i
].
is_fixed
())
{
num_dims_ele
*=
dims
[
i
];
num_dd_ele
*=
dyn_dims
[
i
].
min
;
}
else
{
if
(
dims
[
i
]
!=
0
and
dims
[
i
]
!=
-
1
)
{
MIGRAPHX_THROW
(
"reshape_lazy: Non-fixed dynamic_dimension doesn't match with 0 or -1 "
"output dimension"
);
}
}
}
if
(
num_dims_ele
!=
num_dd_ele
)
{
MIGRAPHX_THROW
(
"reshape_lazy: Number of fixed elements must match. Input: "
+
std
::
to_string
(
num_dd_ele
)
+
" Output: "
+
std
::
to_string
(
num_dims_ele
));
}
// construct output dynamic shape from dims attribute
std
::
vector
<
shape
::
dynamic_dimension
>
output_dyn_dims
(
dims
.
size
());
std
::
transform
(
dims
.
cbegin
(),
dims
.
cend
(),
dyn_dims
.
cbegin
(),
output_dyn_dims
.
begin
(),
[](
std
::
size_t
dim
,
auto
dyn_dim
)
{
if
(
not
dyn_dim
.
is_fixed
())
return
dyn_dim
;
return
shape
::
dynamic_dimension
{
dim
,
dim
};
});
return
{
s0
.
type
(),
output_dyn_dims
};
}
template
<
class
Iterator
>
static
auto
compute_end_dim
(
Iterator
start
,
Iterator
last
,
std
::
size_t
dim
)
{
std
::
size_t
x
=
1
;
auto
it
=
std
::
find_if
(
start
,
last
,
[
&
](
auto
i
)
{
x
*=
i
;
return
x
>=
dim
;
});
if
(
x
!=
dim
)
return
start
;
return
it
;
}
template
<
class
DimIterator
,
class
StrideIterator
>
static
auto
can_strides_merge
(
DimIterator
dim_start
,
DimIterator
dim_last
,
StrideIterator
stride_start
,
StrideIterator
stride_last
)
{
assert
(
std
::
distance
(
dim_start
,
dim_last
)
==
std
::
distance
(
stride_start
,
stride_last
));
auto
cstride
=
*
std
::
prev
(
stride_last
);
return
std
::
equal
(
std
::
make_reverse_iterator
(
dim_last
),
std
::
make_reverse_iterator
(
dim_start
+
1
),
std
::
make_reverse_iterator
(
stride_last
-
1
),
std
::
make_reverse_iterator
(
stride_start
),
[
&
](
auto
dim
,
auto
stride
)
{
cstride
*=
dim
;
return
stride
==
cstride
;
});
}
// This will attempt to alias the dimensions of the input shape to the lens of
// `rdims`. If this can't be done without changing memory layout then it
// will return nullopt
static
optional
<
shape
>
reshape_lazy_dims
(
const
shape
&
input
,
const
std
::
vector
<
std
::
size_t
>&
rdims
)
{
if
(
input
.
standard
())
return
shape
{
input
.
type
(),
rdims
};
const
auto
&
idims
=
input
.
lens
();
const
auto
&
istrides
=
input
.
strides
();
std
::
vector
<
std
::
size_t
>
rstrides
;
std
::
size_t
i
=
0
;
std
::
size_t
r
=
0
;
while
(
i
<
idims
.
size
()
and
r
<
rdims
.
size
())
{
auto
idim
=
idims
[
i
];
auto
rdim
=
rdims
[
r
];
if
(
rdim
==
idim
)
{
rstrides
.
push_back
(
istrides
[
i
]);
}
// squeeze
else
if
(
rdim
>
idim
)
{
auto
start
=
idims
.
begin
()
+
i
;
auto
it
=
compute_end_dim
(
start
,
idims
.
end
(),
rdim
);
if
(
it
==
start
)
return
nullopt
;
auto
n
=
it
-
start
;
assert
((
i
+
n
)
<=
istrides
.
size
());
if
(
not
can_strides_merge
(
start
,
it
+
1
,
istrides
.
begin
()
+
i
,
istrides
.
begin
()
+
i
+
n
+
1
))
return
nullopt
;
i
+=
n
;
rstrides
.
push_back
(
istrides
[
i
]);
}
// unsqueeze
else
// if(rdim < idim)
{
auto
start
=
rdims
.
begin
()
+
i
;
auto
it
=
compute_end_dim
(
start
,
rdims
.
end
(),
idim
);
if
(
it
==
start
)
return
nullopt
;
auto
n
=
it
-
start
;
assert
((
r
+
n
)
<=
rdims
.
size
());
auto
stride
=
istrides
[
i
]
*
idim
;
std
::
for_each
(
start
,
it
+
1
,
[
&
](
auto
dim
)
{
stride
/=
dim
;
rstrides
.
push_back
(
stride
);
});
r
+=
n
;
}
i
++
;
r
++
;
}
// Handle trailing 1s
if
(
rstrides
.
size
()
<
rdims
.
size
()
and
not
rstrides
.
empty
())
{
auto
stride
=
rstrides
.
back
();
for
(
auto
d
:
range
(
rdims
.
begin
()
+
rstrides
.
size
(),
rdims
.
end
()))
{
if
(
d
!=
1
)
return
nullopt
;
rstrides
.
push_back
(
stride
);
}
}
if
(
rdims
.
size
()
!=
rstrides
.
size
())
return
nullopt
;
return
shape
{
input
.
type
(),
rdims
,
rstrides
};
}
shape
static_compute_shape
(
std
::
vector
<
shape
>
inputs
,
std
::
size_t
n_neg_dims
)
const
{
check_shapes
{
inputs
,
*
this
}.
has
(
1
);
auto
&&
idims
=
inputs
.
front
().
lens
();
std
::
vector
<
std
::
size_t
>
rdims
(
dims
.
begin
(),
dims
.
end
());
for
(
std
::
size_t
i
=
0
;
i
<
dims
.
size
();
i
++
)
{
if
(
dims
[
i
]
==
0
)
rdims
[
i
]
=
idims
[
i
];
// since rdims using size_t type, -1 is the max value
// is size_t that cause later compuation incorrect
if
(
dims
[
i
]
==
-
1
)
rdims
[
i
]
=
1
;
}
if
(
n_neg_dims
>
0
)
{
size_t
missing_dim
=
inputs
.
front
().
elements
()
/
std
::
accumulate
(
rdims
.
begin
(),
rdims
.
end
(),
1
,
std
::
multiplies
<
int64_t
>
());
for
(
std
::
size_t
i
=
0
;
i
<
rdims
.
size
();
i
++
)
{
if
(
dims
[
i
]
==
-
1
)
rdims
[
i
]
=
missing_dim
;
}
}
auto
s
=
reshape_lazy_dims
(
inputs
.
front
(),
rdims
);
if
(
not
s
.
has_value
())
MIGRAPHX_THROW
(
"reshape_lazy on axis that is not packed."
);
if
(
s
->
elements
()
!=
inputs
.
front
().
elements
())
MIGRAPHX_THROW
(
"reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has "
+
std
::
to_string
(
s
->
elements
())
+
" elements whereas the input has "
+
std
::
to_string
(
inputs
.
front
().
elements
()));
assert
(
s
->
bytes
()
==
inputs
.
front
().
bytes
());
return
*
s
;
}
shape
compute_shape
(
std
::
vector
<
shape
>
inputs
)
const
{
check_shapes
{
inputs
,
*
this
,
true
}.
has
(
1
);
auto
n_neg_dims
=
std
::
count
(
dims
.
begin
(),
dims
.
end
(),
-
1
);
if
(
n_neg_dims
>
1
)
MIGRAPHX_THROW
(
"reshape_lazy: Dimensions for reshape_lazy can only have one -1 dim"
);
auto
s0
=
inputs
[
0
];
if
(
s0
.
dynamic
())
{
return
dyn_compute_shape
(
s0
);
}
else
{
return
static_compute_shape
(
inputs
,
n_neg_dims
);
}
}
argument
compute
(
const
dyn_output
&
dyn_out
,
std
::
vector
<
argument
>
args
)
const
{
return
args
[
0
].
reshape
(
dyn_out
.
computed_shape
);
}
std
::
ptrdiff_t
output_alias
(
const
std
::
vector
<
shape
>&
)
const
{
return
0
;
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/reverse.hpp
View file @
b9d37172
/*
/*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) 2015-202
2
Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2015-202
3
Advanced Micro Devices, Inc. All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
...
@@ -70,13 +70,13 @@ struct reverse
...
@@ -70,13 +70,13 @@ struct reverse
argument
result
{
s
};
argument
result
{
s
};
auto
lens
=
s
.
lens
();
auto
lens
=
s
.
lens
();
visit_all
(
result
,
args
.
front
())([
&
](
auto
output
,
auto
input
)
{
visit_all
(
result
,
args
.
front
())([
&
](
auto
output
,
auto
input
)
{
shape_for_each
(
s
,
[
&
](
const
auto
&
out_idx
)
{
shape_for_each
(
s
,
[
&
](
const
auto
&
out_idx_v
,
size_t
out_idx
)
{
auto
in_idx
=
out_idx
;
auto
in_idx
=
out_idx
_v
;
for
(
const
auto
&
axis
:
axes
)
for
(
const
auto
&
axis
:
axes
)
{
{
in_idx
[
axis
]
=
lens
[
axis
]
-
1
-
out_idx
[
axis
];
in_idx
[
axis
]
=
lens
[
axis
]
-
1
-
out_idx
_v
[
axis
];
}
}
output
[
s
.
index
(
out_idx
)
]
=
input
[
s
.
index
(
in_idx
)];
output
[
out_idx
]
=
input
[
s
.
index
(
in_idx
)];
});
});
});
});
...
...
src/include/migraphx/op/roialign.hpp
View file @
b9d37172
/*
/*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) 2015-202
2
Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2015-202
3
Advanced Micro Devices, Inc. All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
...
@@ -113,10 +113,9 @@ struct roialign
...
@@ -113,10 +113,9 @@ struct roialign
{
{
std
::
vector
<
pos_weight
>
results
(
bin_grid_size
[
0
]
*
bin_grid_size
[
1
]
*
output_height
*
std
::
vector
<
pos_weight
>
results
(
bin_grid_size
[
0
]
*
bin_grid_size
[
1
]
*
output_height
*
output_width
);
output_width
);
shape_for_each
(
comp_s
,
[
&
](
auto
idx
)
{
shape_for_each
(
comp_s
,
[
&
](
const
auto
&
idx_v
,
size_t
index
)
{
std
::
array
<
std
::
size_t
,
2
>
p
=
{
idx
[
0
],
idx
[
1
]};
std
::
array
<
std
::
size_t
,
2
>
p
=
{
idx_v
[
0
],
idx_v
[
1
]};
std
::
array
<
std
::
size_t
,
2
>
i
=
{
idx
[
2
],
idx
[
3
]};
std
::
array
<
std
::
size_t
,
2
>
i
=
{
idx_v
[
2
],
idx_v
[
3
]};
auto
index
=
comp_s
.
index
(
idx
);
std
::
array
<
float
,
2
>
xy
{};
std
::
array
<
float
,
2
>
xy
{};
std
::
array
<
int64_t
,
2
>
low
{};
std
::
array
<
int64_t
,
2
>
low
{};
...
@@ -125,7 +124,7 @@ struct roialign
...
@@ -125,7 +124,7 @@ struct roialign
{
{
xy
[
ii
]
=
roi_start
[
ii
]
+
p
[
ii
]
*
bin_size
[
ii
]
+
xy
[
ii
]
=
roi_start
[
ii
]
+
p
[
ii
]
*
bin_size
[
ii
]
+
(
i
[
ii
]
+
.5
f
)
*
bin_size
[
ii
]
/
bin_grid_size
[
ii
];
(
i
[
ii
]
+
.5
f
)
*
bin_size
[
ii
]
/
bin_grid_size
[
ii
];
xy
[
ii
]
=
(
coord_trans_mode
==
"
output_
half_pixel"
)
?
(
xy
[
ii
]
-
0.5
f
)
:
xy
[
ii
];
xy
[
ii
]
=
(
coord_trans_mode
==
"half_pixel"
)
?
(
xy
[
ii
]
-
0.5
f
)
:
xy
[
ii
];
if
(
xy
[
ii
]
<
-
1.0
or
xy
[
ii
]
>
dims
[
ii
])
if
(
xy
[
ii
]
<
-
1.0
or
xy
[
ii
]
>
dims
[
ii
])
{
{
results
[
index
]
=
pos_weight
{};
results
[
index
]
=
pos_weight
{};
...
@@ -255,7 +254,7 @@ struct roialign
...
@@ -255,7 +254,7 @@ struct roialign
std
::
vector
<
std
::
size_t
>
comp_lens1
=
{
channels
,
out_dims
[
0
],
out_dims
[
1
]};
std
::
vector
<
std
::
size_t
>
comp_lens1
=
{
channels
,
out_dims
[
0
],
out_dims
[
1
]};
shape
comp_s1
{
migraphx
::
shape
::
float_type
,
comp_lens1
};
shape
comp_s1
{
migraphx
::
shape
::
float_type
,
comp_lens1
};
std
::
vector
<
int64_t
>
vec_index
(
channels
,
0
);
std
::
vector
<
int64_t
>
vec_index
(
channels
,
0
);
shape_for_each
(
comp_s1
,
[
&
](
auto
idx
)
{
shape_for_each
(
comp_s1
,
[
&
](
const
auto
&
idx
)
{
auto
c
=
idx
[
0
];
auto
c
=
idx
[
0
];
auto
ph
=
idx
[
1
];
auto
ph
=
idx
[
1
];
auto
pw
=
idx
[
2
];
auto
pw
=
idx
[
2
];
...
...
src/include/migraphx/op/scatter.hpp
View file @
b9d37172
/*
/*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) 2015-202
2
Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2015-202
3
Advanced Micro Devices, Inc. All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
...
...
src/include/migraphx/op/slice.hpp
View file @
b9d37172
...
@@ -27,19 +27,34 @@
...
@@ -27,19 +27,34 @@
#include <migraphx/check_shapes.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/argument.hpp>
#include <migraphx/argument.hpp>
#include <migraphx/config.hpp>
#include <migraphx/config.hpp>
#include <migraphx/dyn_output.hpp>
#include <migraphx/value.hpp>
#include <migraphx/value.hpp>
#include <migraphx/dyn_output.hpp>
#include <migraphx/op/normalize_attribute.hpp>
#include <migraphx/op/normalize_attribute.hpp>
#include <migraphx/normalize_attributes.hpp>
namespace
migraphx
{
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
namespace
op
{
/**
* Slice operator that accepts variable axes, starts and ends.
*
* Attributes:
* axes: constant axes to slice over (optional)
* starts: constant slice starting indices (optional)
* ends: constant slice ending indices (optional)
*
* Parameters:
* data: the input tensor to slice (dynamic or static shape)
* input_starts: starting indicies of slice (optional, static shape)
* input_ends: ending indicies of slice (optional, static shape)
* input_axes: axes to slice over (optional, static shape)
*/
struct
slice
struct
slice
{
{
std
::
vector
<
int64_t
>
axes
;
std
::
vector
<
int64_t
>
axes
{}
;
std
::
vector
<
int64_t
>
starts
;
std
::
vector
<
int64_t
>
starts
{}
;
std
::
vector
<
int64_t
>
ends
;
std
::
vector
<
int64_t
>
ends
{}
;
template
<
class
Self
,
class
F
>
template
<
class
Self
,
class
F
>
static
auto
reflect
(
Self
&
self
,
F
f
)
static
auto
reflect
(
Self
&
self
,
F
f
)
...
@@ -48,8 +63,8 @@ struct slice
...
@@ -48,8 +63,8 @@ struct slice
}
}
/**
/**
* Ensure that attribute vectors axes, starts, and ends are all the same size and values are
in
* Ensure that attribute vectors axes, starts, and ends are all the same size and values are
* limits.
*
within
limits.
*/
*/
value
attributes
()
const
value
attributes
()
const
{
{
...
@@ -70,6 +85,90 @@ struct slice
...
@@ -70,6 +85,90 @@ struct slice
std
::
string
name
()
const
{
return
"slice"
;
}
std
::
string
name
()
const
{
return
"slice"
;
}
/**
* Computes the slice output shape dimensions for given starts, ends,and axes.
* Templated to also handle tensor views.
* Possibily different type between [in_starts, in_ends] and [in_axes] if in_axes is this
* object's axes attribute. Assumes in_starts and in_ends are normalized; in_axes are valid.
*/
template
<
class
A
,
class
B
>
std
::
vector
<
std
::
size_t
>
lens_calc
(
const
std
::
vector
<
std
::
size_t
>&
lengths
,
A
in_starts
,
A
in_ends
,
B
in_axes
)
const
{
auto
new_lens
=
lengths
;
for
(
std
::
size_t
i
=
0
;
i
<
in_axes
.
size
();
++
i
)
{
auto
axis
=
in_axes
[
i
];
new_lens
[
axis
]
=
in_ends
[
i
]
-
in_starts
[
i
];
}
return
new_lens
;
}
shape
normalize_compute_shape
(
std
::
vector
<
shape
>
inputs
)
const
{
check_shapes
{
inputs
,
*
this
,
true
}.
has
(
1
,
3
,
4
);
auto
input_shape
=
inputs
[
0
];
if
(
inputs
.
size
()
==
1
)
{
auto
t
=
input_shape
.
type
();
if
(
input_shape
.
dynamic
()
and
std
::
any_of
(
axes
.
begin
(),
axes
.
end
(),
[
&
](
auto
axis
)
{
return
not
input_shape
.
dyn_dims
()[
axis
].
is_fixed
();
}))
{
MIGRAPHX_THROW
(
"SLICE: slicing is not allowed on non-fixed dynamic input axis "
);
}
if
(
input_shape
.
dynamic
())
{
return
shape
{
t
,
lens_calc
(
input_shape
.
min_lens
(),
starts
,
ends
,
axes
),
lens_calc
(
input_shape
.
max_lens
(),
starts
,
ends
,
axes
),
{}};
}
else
{
return
shape
{
t
,
lens_calc
(
input_shape
.
lens
(),
starts
,
ends
,
axes
),
input_shape
.
strides
()};
}
}
else
{
// check that starts, ends, and optionally input_axes are all 1D, have the same
// dimension, and are static
check_shapes
{
inputs
.
begin
()
+
1
,
inputs
.
end
(),
std
::
string
(
"SLICE: inputs (starts, ends, and input_axes)"
),
false
}
.
only_dims
(
1
)
.
same_dims
();
auto
dds
=
input_shape
.
to_dynamic
().
dyn_dims
();
if
(
inputs
.
size
()
==
3
)
{
if
(
inputs
[
1
].
lens
().
at
(
0
)
!=
axes
.
size
())
{
MIGRAPHX_THROW
(
"SLICE: inputs starts and ends do not have the same dimension "
"as the axes attribute"
);
}
std
::
for_each
(
axes
.
cbegin
(),
axes
.
cend
(),
[
&
](
const
auto
&
axis
)
{
dds
.
at
(
axis
)
=
{
0
,
dds
.
at
(
axis
).
max
};
});
}
else
{
// if axes is an input, then all the output dimensions could be 0 to the max value
std
::
transform
(
dds
.
begin
(),
dds
.
end
(),
dds
.
begin
(),
[](
auto
dd
)
{
return
shape
::
dynamic_dimension
{
0
,
dd
.
max
};
});
}
return
shape
{
input_shape
.
type
(),
dds
};
}
}
/**
* Calculates the starting offset for the sliced tensor.
* Used in compute when only data input and all other information are in the attributes.
*
* \param s static input shape
*/
auto
compute_offset
(
const
shape
&
s
)
const
auto
compute_offset
(
const
shape
&
s
)
const
{
{
const
std
::
vector
<
std
::
size_t
>&
lens
=
s
.
lens
();
const
std
::
vector
<
std
::
size_t
>&
lens
=
s
.
lens
();
...
@@ -90,80 +189,131 @@ struct slice
...
@@ -90,80 +189,131 @@ struct slice
offset
+=
starts
[
axis
]
*
strides
[
axis
];
offset
+=
starts
[
axis
]
*
strides
[
axis
];
}
}
}
}
return
offset
;
return
offset
*
s
.
type_size
()
;
}
}
shape
normalize_compute_shape
(
std
::
vector
<
shape
>
inputs
)
const
/**
* Calculates the starting offset for the sliced tensor (for aliasing).
* Used when the starts and/or the axes are inputs.
*
* \param s static input shape
* \param input_starts starting indices of slice
* \param ax_vec axes to slice on
*/
template
<
class
IndView
,
class
Axes
>
auto
compute_offset
(
const
shape
&
s
,
const
IndView
&
input_starts
,
const
Axes
&
ax_vec
)
const
{
{
check_shapes
{
inputs
,
*
this
,
true
}.
has
(
1
);
auto
ret
=
0
;
auto
input_shape
=
inputs
[
0
];
for
(
std
::
size_t
i
=
0
;
i
<
ax_vec
.
size
();
++
i
)
auto
t
=
input_shape
.
type
();
// TODO: When support for dynamic shapes is added to normalize_attributes,
// remove this restriction.
if
(
input_shape
.
dynamic
()
and
std
::
any_of
(
axes
.
begin
(),
axes
.
end
(),
[
&
](
auto
axis
)
{
return
not
input_shape
.
dyn_dims
()[
axis
].
is_fixed
();
}))
{
{
MIGRAPHX_THROW
(
"SLICE: slicing is not allowed on non-fixed dynamic input axis "
);
auto
axis
=
ax_vec
[
i
];
ret
+=
input_starts
[
i
]
*
s
.
strides
().
at
(
axis
);
}
}
return
ret
*
s
.
type_size
();
}
std
::
unordered_map
<
std
::
string
,
std
::
vector
<
int64_t
>>
normalize_inputs
(
const
shape
&
input_shape
,
const
std
::
vector
<
int64_t
>&
input_starts
,
const
std
::
vector
<
int64_t
>&
input_ends
)
const
{
auto
attrs
=
this
->
attributes
().
at
(
"normalize_axes"
);
return
{{
"input_starts"
,
normalize_indices
(
input_starts
,
this
->
axes
,
input_shape
,
attrs
.
at
(
"starts"
),
"Slice variable input_starts"
)},
{
"input_ends"
,
normalize_indices
(
input_ends
,
this
->
axes
,
input_shape
,
attrs
.
at
(
"ends"
),
"Slice variable input_ends"
)}};
}
/**
* Three input version of the normalize_inputs.
* This one also checks that the input_axes are valid.
*/
std
::
unordered_map
<
std
::
string
,
std
::
vector
<
int64_t
>>
normalize_inputs
(
shape
input_shape
,
const
std
::
vector
<
int64_t
>&
input_starts
,
const
std
::
vector
<
int64_t
>&
input_ends
,
const
std
::
vector
<
int64_t
>&
input_axes
)
const
{
auto
attrs
=
this
->
attributes
().
at
(
"normalize_axes"
);
auto
norm_axes
=
normalize_axes
(
input_axes
,
input_shape
,
attrs
.
at
(
"axes"
),
"Slice variable input_axes"
);
return
{{
"input_starts"
,
normalize_indices
(
input_starts
,
norm_axes
,
input_shape
,
attrs
.
at
(
"starts"
),
"Slice variable input_starts"
)},
{
"input_ends"
,
normalize_indices
(
input_ends
,
norm_axes
,
input_shape
,
attrs
.
at
(
"ends"
),
"Slice variable input ends"
)},
{
"input_axes"
,
norm_axes
}};
}
// For a static shape, old_lens will be adjusted to a new size
argument
compute
(
const
dyn_output
&
dyn_out
,
std
::
vector
<
argument
>
args
)
const
// for those axes that are sliced.
{
// For dynamic shape, the adjusted old_lens become the new max values,
auto
input
=
args
[
0
];
// while updating the old mins and optimals if possible.
auto
input_shape
=
input
.
get_shape
();
std
::
vector
<
std
::
size_t
>
new_mins
;
switch
(
args
.
size
())
std
::
vector
<
std
::
size_t
>
old_lens
;
std
::
vector
<
std
::
size_t
>
old_strides
;
// Doesn't handle optimals
if
(
input_shape
.
dynamic
())
{
{
old_lens
=
input_shape
.
max_lens
();
case
1
:
{
new_mins
=
input_shape
.
min_lens
();
std
::
size_t
offset
=
compute_offset
(
input_shape
);
return
{
dyn_out
.
computed_shape
,
[
=
]
{
return
input
.
data
()
+
offset
;
}};
}
}
else
case
3
:
{
{
shape
calc_shape
;
old_lens
=
input_shape
.
lens
();
std
::
size_t
offset
=
0
;
// For static shape (including during eval step after a dynamic input) the strides are
visit_all
(
args
[
1
],
args
[
2
])([
&
](
auto
input_starts
,
auto
input_ends
)
{
// indexed into the pre-slice array, so they are larger than the apparent size of the
auto
norm_inputs
=
normalize_inputs
(
input_shape
,
// resulting shape.
input_starts
.
template
to_vector
<
int64_t
>(),
old_strides
=
input_shape
.
strides
();
input_ends
.
template
to_vector
<
int64_t
>());
offset
=
compute_offset
(
input_shape
,
norm_inputs
.
at
(
"input_starts"
),
this
->
axes
);
calc_shape
=
{
input_shape
.
type
(),
lens_calc
(
input_shape
.
lens
(),
norm_inputs
.
at
(
"input_starts"
),
norm_inputs
.
at
(
"input_ends"
),
this
->
axes
),
input_shape
.
strides
()};
});
return
{
calc_shape
,
[
=
]
{
return
input
.
data
()
+
offset
;
}};
}
}
case
4
:
{
std
::
vector
<
std
::
size_t
>
new_lens
=
old_lens
;
shape
calc_shape
;
for
(
std
::
size_t
i
=
0
;
i
<
axes
.
size
();
i
++
)
std
::
size_t
offset
=
0
;
{
visit_all
(
args
[
1
],
args
[
2
],
args
[
3
])(
auto
axis
=
axes
[
i
];
[
&
](
auto
input_starts
,
auto
input_ends
,
auto
input_axes
)
{
size_t
sliced_length
=
ends
[
i
]
-
starts
[
i
];
auto
norm_inputs
=
normalize_inputs
(
input_shape
,
// A Numpy indexing convention: a slice size larger than the actual dimension
input_starts
.
template
to_vector
<
int64_t
>(),
// is legal and the "ends" value is clipped to the axis size
input_ends
.
template
to_vector
<
int64_t
>(),
new_lens
[
axis
]
=
std
::
min
(
new_lens
[
axis
],
sliced_length
);
input_axes
.
template
to_vector
<
int64_t
>());
if
(
input_shape
.
dynamic
())
offset
=
compute_offset
(
{
input_shape
,
norm_inputs
.
at
(
"input_starts"
),
norm_inputs
.
at
(
"input_axes"
));
// TODO: when non-fixed shape slicing is allowed, this will be different than
calc_shape
=
shape
{
input_shape
.
type
(),
// sliced_length, making use of TBD start/end values.
lens_calc
(
input_shape
.
lens
(),
std
::
size_t
sliced_min_length
=
ends
[
i
]
-
starts
[
i
];
norm_inputs
.
at
(
"input_starts"
),
// if the slice size is smaller than maxes but larger than mins
norm_inputs
.
at
(
"input_ends"
),
new_mins
[
axis
]
=
std
::
min
(
sliced_min_length
,
new_mins
[
axis
]);
norm_inputs
.
at
(
"input_axes"
)),
}
input_shape
.
strides
()};
});
return
{
calc_shape
,
[
=
]
{
return
input
.
data
()
+
offset
;
}};
}
}
if
(
input_shape
.
dynamic
())
default:
{
{
// Should never get here; covering in case some code change occurs
return
shape
{
t
,
new_mins
,
new_lens
,
{}}
;
MIGRAPHX_THROW
(
"SLICE: invalid number of inputs"
)
;
}
}
else
{
return
shape
{
t
,
new_lens
,
old_strides
};
}
}
}
}
argument
compute
(
const
dyn_output
&
dyn_out
,
std
::
vector
<
argument
>
args
)
const
{
auto
input
=
args
[
0
];
auto
offset
=
compute_offset
(
input
.
get_shape
())
*
dyn_out
.
computed_shape
.
type_size
();
return
{
dyn_out
.
computed_shape
,
[
=
]
{
return
input
.
data
()
+
offset
;
}};
}
std
::
ptrdiff_t
output_alias
(
const
std
::
vector
<
shape
>&
)
const
{
return
0
;
}
std
::
ptrdiff_t
output_alias
(
const
std
::
vector
<
shape
>&
)
const
{
return
0
;
}
};
};
...
...
src/include/migraphx/operators.hpp
View file @
b9d37172
...
@@ -55,6 +55,7 @@
...
@@ -55,6 +55,7 @@
#include <migraphx/op/equal.hpp>
#include <migraphx/op/equal.hpp>
#include <migraphx/op/erf.hpp>
#include <migraphx/op/erf.hpp>
#include <migraphx/op/exp.hpp>
#include <migraphx/op/exp.hpp>
#include <migraphx/op/fill.hpp>
#include <migraphx/op/flatten.hpp>
#include <migraphx/op/flatten.hpp>
#include <migraphx/op/floor.hpp>
#include <migraphx/op/floor.hpp>
#include <migraphx/op/fmod.hpp>
#include <migraphx/op/fmod.hpp>
...
...
src/include/migraphx/pad_calc.hpp
View file @
b9d37172
/*
/*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) 2015-202
2
Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2015-202
3
Advanced Micro Devices, Inc. All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
...
@@ -62,6 +62,14 @@ shape compute_padded_shape(const shape& input,
...
@@ -62,6 +62,14 @@ shape compute_padded_shape(const shape& input,
const
std
::
vector
<
std
::
size_t
>&
stride
,
const
std
::
vector
<
std
::
size_t
>&
stride
,
const
std
::
vector
<
std
::
size_t
>&
dilation
);
const
std
::
vector
<
std
::
size_t
>&
dilation
);
// Used for dynamic auto padding of pooling operators where padding needs to be computed at
// evaulation time.
shape
compute_padded_pool_shape
(
const
shape
&
input
,
const
shape
&
kernel
,
const
std
::
vector
<
std
::
size_t
>&
padding
,
const
std
::
vector
<
std
::
size_t
>&
stride
,
const
std
::
vector
<
std
::
size_t
>&
dilation
);
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
}
// namespace migraphx
...
...
src/include/migraphx/ranges.hpp
View file @
b9d37172
...
@@ -205,7 +205,7 @@ void transform(Range1&& r1, Range2&& r2, Iterator it, F f)
...
@@ -205,7 +205,7 @@ void transform(Range1&& r1, Range2&& r2, Iterator it, F f)
}
}
template
<
class
Range
>
template
<
class
Range
>
auto
reverse
(
Range
&
r
)
auto
reverse
(
Range
&
&
r
)
{
{
return
range
(
std
::
make_reverse_iterator
(
r
.
end
()),
std
::
make_reverse_iterator
(
r
.
begin
()));
return
range
(
std
::
make_reverse_iterator
(
r
.
end
()),
std
::
make_reverse_iterator
(
r
.
begin
()));
}
}
...
...
src/include/migraphx/shape.hpp
View file @
b9d37172
...
@@ -263,7 +263,7 @@ struct MIGRAPHX_EXPORT shape
...
@@ -263,7 +263,7 @@ struct MIGRAPHX_EXPORT shape
/// no padding
/// no padding
bool
packed
()
const
;
bool
packed
()
const
;
/// Returns true i
s
the shape has been transposed. That is the strides are not in descending
/// Returns true i
f
the shape has been transposed. That is the strides are not in descending
/// order
/// order
bool
transposed
()
const
;
bool
transposed
()
const
;
...
...
src/include/migraphx/shape_for_each.hpp
View file @
b9d37172
/*
/*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) 2015-202
2
Advanced Micro Devices, Inc. All rights reserved.
* Copyright (c) 2015-202
3
Advanced Micro Devices, Inc. All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
...
@@ -37,11 +37,11 @@ inline namespace MIGRAPHX_INLINE_NS {
...
@@ -37,11 +37,11 @@ inline namespace MIGRAPHX_INLINE_NS {
template
<
class
F
>
template
<
class
F
>
void
shape_for_each
(
const
migraphx
::
shape
&
s
,
F
f
)
void
shape_for_each
(
const
migraphx
::
shape
&
s
,
F
f
)
{
{
// Ensure calls to f use const ref to vector
auto
call
=
[
&
f
](
const
std
::
vector
<
std
::
size_t
>&
i
)
{
f
(
i
);
};
std
::
vector
<
std
::
size_t
>
indices
(
s
.
lens
().
size
());
std
::
vector
<
std
::
size_t
>
indices
(
s
.
lens
().
size
());
const
auto
&
index_const_ref
=
indices
;
shape
ss
{
s
.
type
(),
s
.
lens
()};
shape
ss
{
s
.
type
(),
s
.
lens
()};
for
(
std
::
size_t
i
=
0
;
i
<
ss
.
elements
();
i
++
)
size_t
max
=
ss
.
elements
();
for
(
std
::
size_t
i
=
0
;
i
<
max
;
i
++
)
{
{
std
::
transform
(
ss
.
strides
().
begin
(),
std
::
transform
(
ss
.
strides
().
begin
(),
ss
.
strides
().
end
(),
ss
.
strides
().
end
(),
...
@@ -51,9 +51,13 @@ void shape_for_each(const migraphx::shape& s, F f)
...
@@ -51,9 +51,13 @@ void shape_for_each(const migraphx::shape& s, F f)
assert
(
len
>
0
and
stride
>
0
);
assert
(
len
>
0
and
stride
>
0
);
return
(
i
/
stride
)
%
len
;
return
(
i
/
stride
)
%
len
;
});
});
call
(
indices
);
if
constexpr
(
std
::
is_invocable
<
F
,
decltype
(
index_const_ref
),
decltype
(
i
)
>
{})
f
(
index_const_ref
,
i
);
else
f
(
index_const_ref
);
}
}
}
}
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
}
// namespace migraphx
...
...
src/include/migraphx/simplify_dyn_ops.hpp
0 → 100644
View file @
b9d37172
/*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_DYN_OPS_HPP
#define MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_DYN_OPS_HPP
#include <string>
#include <migraphx/instruction_ref.hpp>
#include <migraphx/config.hpp>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
struct
module
;
/**
* Convert dynamic ops to their static version if possible.
* Should be run after the split_single_dyn_dims pass.
*/
struct
MIGRAPHX_EXPORT
simplify_dyn_ops
{
std
::
string
name
()
const
{
return
"simplify_dyn_ops"
;
}
void
apply
(
module
&
m
)
const
;
};
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/simplify_reshapes.hpp
View file @
b9d37172
...
@@ -38,6 +38,7 @@ struct module;
...
@@ -38,6 +38,7 @@ struct module;
*/
*/
struct
MIGRAPHX_EXPORT
simplify_reshapes
struct
MIGRAPHX_EXPORT
simplify_reshapes
{
{
size_t
depth
=
4
;
std
::
string
name
()
const
{
return
"simplify_reshapes"
;
}
std
::
string
name
()
const
{
return
"simplify_reshapes"
;
}
void
apply
(
module
&
m
)
const
;
void
apply
(
module
&
m
)
const
;
};
};
...
...
src/include/migraphx/stringutils.hpp
View file @
b9d37172
...
@@ -86,7 +86,7 @@ inline std::string join_strings(Strings strings, const std::string& delim)
...
@@ -86,7 +86,7 @@ inline std::string join_strings(Strings strings, const std::string& delim)
inline
std
::
vector
<
std
::
string
>
split_string
(
const
std
::
string
&
s
,
char
delim
)
inline
std
::
vector
<
std
::
string
>
split_string
(
const
std
::
string
&
s
,
char
delim
)
{
{
std
::
vector
<
std
::
string
>
elems
;
std
::
vector
<
std
::
string
>
elems
;
std
::
stringstream
ss
(
s
+
' '
);
std
::
stringstream
ss
(
s
+
delim
);
std
::
string
item
;
std
::
string
item
;
while
(
std
::
getline
(
ss
,
item
,
delim
))
while
(
std
::
getline
(
ss
,
item
,
delim
))
{
{
...
@@ -149,6 +149,10 @@ interpolate_string(const std::string& input, F f, std::string start = "${", std:
...
@@ -149,6 +149,10 @@ interpolate_string(const std::string& input, F f, std::string start = "${", std:
result
.
append
(
it
,
next_start
);
result
.
append
(
it
,
next_start
);
if
(
next_start
==
input
.
end
())
if
(
next_start
==
input
.
end
())
break
;
break
;
if
(
next_end
==
input
.
end
())
{
throw
std
::
runtime_error
(
"Unbalanced brackets"
);
}
auto
r
=
f
(
next_start
+
start
.
size
(),
next_end
);
auto
r
=
f
(
next_start
+
start
.
size
(),
next_end
);
result
.
append
(
r
.
begin
(),
r
.
end
());
result
.
append
(
r
.
begin
(),
r
.
end
());
it
=
next_end
+
end
.
size
();
it
=
next_end
+
end
.
size
();
...
...
src/include/migraphx/verify.hpp
View file @
b9d37172
...
@@ -29,10 +29,13 @@
...
@@ -29,10 +29,13 @@
#include <functional>
#include <functional>
#include <iostream>
#include <iostream>
#include <numeric>
#include <numeric>
#include <assert.h>
#include <migraphx/float_equal.hpp>
#include <migraphx/float_equal.hpp>
#include <migraphx/config.hpp>
#include <migraphx/config.hpp>
#include <migraphx/env.hpp>
MIGRAPHX_DECLARE_ENV_VAR
(
MIGRAPHX_VERIFY_ENABLE_ALLCLOSE
)
namespace
migraphx
{
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
verify
{
namespace
verify
{
...
@@ -87,8 +90,7 @@ struct not_finite_fn
...
@@ -87,8 +90,7 @@ struct not_finite_fn
template
<
class
T
>
template
<
class
T
>
bool
operator
()(
T
x
)
const
bool
operator
()(
T
x
)
const
{
{
using
std
::
isfinite
;
return
not
std
::
isfinite
(
static_cast
<
double
>
(
x
));
return
not
isfinite
(
x
);
}
}
};
};
static
constexpr
not_finite_fn
not_finite
{};
static
constexpr
not_finite_fn
not_finite
{};
...
@@ -98,8 +100,7 @@ struct compare_mag_fn
...
@@ -98,8 +100,7 @@ struct compare_mag_fn
template
<
class
T
,
class
U
>
template
<
class
T
,
class
U
>
bool
operator
()(
T
x
,
U
y
)
const
bool
operator
()(
T
x
,
U
y
)
const
{
{
using
std
::
fabs
;
return
std
::
fabs
(
x
)
<
std
::
fabs
(
y
);
return
fabs
(
x
)
<
fabs
(
y
);
}
}
};
};
static
constexpr
compare_mag_fn
compare_mag
{};
static
constexpr
compare_mag_fn
compare_mag
{};
...
@@ -187,16 +188,103 @@ double rms_range(const R1& r1, const R2& r2)
...
@@ -187,16 +188,103 @@ double rms_range(const R1& r1, const R2& r2)
return
std
::
numeric_limits
<
range_value
<
R1
>>::
max
();
return
std
::
numeric_limits
<
range_value
<
R1
>>::
max
();
}
}
template
<
class
R
>
double
get_rms_tol
(
const
R
&
,
std
::
size_t
tolerance
=
80
)
{
double
threshold
=
std
::
numeric_limits
<
range_value
<
R
>>::
epsilon
()
*
tolerance
;
return
threshold
;
}
/*
C++ doesn't support named arguments, this is just wrapper that helps distinguish between actual
results v/s expected results arguments.
*/
template
<
class
T
>
struct
expected
{
expected
()
=
default
;
explicit
expected
(
const
T
&
input
)
:
x
(
&
input
)
{}
const
T
&
data
()
const
{
assert
(
x
!=
nullptr
);
return
*
x
;
}
private:
const
T
*
x
=
nullptr
;
};
// deduction guide for templated expected class
template
<
class
T
>
expected
(
const
T
&
)
->
expected
<
T
>
;
struct
tolerance
{
double
rms_tol
=
0.001
;
double
atol
=
0.001
;
double
rtol
=
0.001
;
};
/*
MIGraphX implementation of numpy's np.allclose() which checks if elementwise absolute diff is within
tolerance using this formula: abs(a - b) < atol + rtol(abs(b))
*/
template
<
class
R1
,
class
R2
>
bool
allclose
(
const
R1
&
r1
,
const
R2
&
r2
,
tolerance
tols
)
{
std
::
size_t
n
=
range_distance
(
r1
);
if
(
n
==
range_distance
(
r2
))
{
auto
idx
=
mismatch_idx
(
r1
,
r2
,
[
&
](
auto
x
,
auto
y
)
{
return
abs_diff
(
double
(
x
),
double
(
y
))
<
tols
.
atol
+
tols
.
rtol
*
std
::
abs
(
double
(
y
));
});
return
idx
>=
range_distance
(
r1
);
}
return
false
;
}
template
<
class
R1
,
class
R2
>
template
<
class
R1
,
class
R2
>
bool
verify_range
(
const
R1
&
r1
,
const
R2
&
r2
,
double
tolerance
=
80
,
double
*
out_error
=
nullptr
)
bool
verify_rms_range
(
const
R1
&
r1
,
const
R2
&
r2
,
std
::
size_t
tolerance
=
80
,
double
*
out_rms_error
=
nullptr
)
{
{
double
threshold
=
std
::
numeric_limits
<
range_value
<
R1
>>::
epsilon
()
*
tolerance
;
double
threshold
=
get_rms_tol
(
r1
,
tolerance
)
;
auto
error
=
rms_range
(
r1
,
r2
);
auto
error
=
rms_range
(
r1
,
r2
);
if
(
out_error
!=
nullptr
)
if
(
out_
rms_
error
!=
nullptr
)
*
out_error
=
error
;
*
out_
rms_
error
=
error
;
return
error
<=
threshold
;
return
error
<=
threshold
;
}
}
template
<
class
R1
,
class
R2
>
bool
verify_range_with_tolerance
(
const
R1
&
r1
,
const
expected
<
R2
>&
r2
,
tolerance
tols
=
tolerance
{},
double
*
out_rms_error
=
nullptr
)
{
auto
rms_error
=
rms_range
(
r1
,
r2
.
data
());
// disable ewise_verify by default for now, it requires lot of tests to be fixed
bool
ewise_verify
=
true
;
if
(
enabled
(
MIGRAPHX_VERIFY_ENABLE_ALLCLOSE
{}))
{
ewise_verify
=
allclose
(
r1
,
r2
.
data
(),
tols
);
}
if
(
out_rms_error
!=
nullptr
)
*
out_rms_error
=
rms_error
;
return
rms_error
<=
tols
.
rms_tol
and
ewise_verify
;
}
// expected argument should be passed as second, but if it is passed as the first by mistake then
// flip the order
template
<
class
R1
,
class
R2
>
bool
verify_range_with_tolerance
(
const
expected
<
R1
>&
r1
,
const
R2
&
r2
,
tolerance
tols
=
tolerance
{},
double
*
out_rms_error
=
nullptr
)
{
return
verify_rms_range
(
r2
,
r1
,
tols
,
out_rms_error
);
}
}
// namespace verify
}
// namespace verify
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
}
// namespace migraphx
...
...
src/include/migraphx/verify_args.hpp
View file @
b9d37172
...
@@ -31,11 +31,15 @@
...
@@ -31,11 +31,15 @@
namespace
migraphx
{
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
inline
namespace
MIGRAPHX_INLINE_NS
{
MIGRAPHX_EXPORT
MIGRAPHX_EXPORT
bool
verify_args
(
const
std
::
string
&
name
,
bool
verify_args
(
const
std
::
string
&
name
,
const
argument
&
target_arg
,
const
argument
&
ref_arg
,
const
verify
::
expected
<
argument
>&
ref_arg
,
const
argument
&
target_arg
,
verify
::
tolerance
);
double
tolerance
=
80
);
MIGRAPHX_EXPORT
bool
verify_args_with_tolerance
(
const
std
::
string
&
name
,
const
argument
&
target_arg
,
const
verify
::
expected
<
argument
>&
ref_arg
,
std
::
size_t
tolerance
=
80
);
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
}
// namespace migraphx
...
...
src/instruction.cpp
View file @
b9d37172
...
@@ -389,7 +389,7 @@ void instruction::print(std::ostream& os,
...
@@ -389,7 +389,7 @@ void instruction::print(std::ostream& os,
if
(
not
ins
->
module_inputs
().
empty
())
if
(
not
ins
->
module_inputs
().
empty
())
{
{
std
::
string
delim
=
", ["
;
std
::
string
delim
=
", ["
;
for
(
auto
&
&
mod_arg
:
ins
->
module_inputs
())
for
(
const
const_module_ref
&
mod_arg
:
ins
->
module_inputs
())
{
{
os
<<
delim
<<
mod_arg
->
name
();
os
<<
delim
<<
mod_arg
->
name
();
delim
=
", "
;
delim
=
", "
;
...
...
Prev
1
2
3
4
5
6
7
8
…
17
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