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
2fc6b715
"vscode:/vscode.git/clone" did not exist on "d25a4ddff30095270032bda0cbcf4f61e65e531f"
Commit
2fc6b715
authored
Apr 14, 2023
by
Paul
Browse files
Merge
parents
5967d68d
118e05c7
Changes
177
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
644 additions
and
94 deletions
+644
-94
src/register_op.cpp
src/register_op.cpp
+10
-0
src/register_target.cpp
src/register_target.cpp
+23
-1
src/replace_allocate.cpp
src/replace_allocate.cpp
+1
-2
src/schedule.cpp
src/schedule.cpp
+3
-3
src/shape.cpp
src/shape.cpp
+83
-34
src/simplify_algebra.cpp
src/simplify_algebra.cpp
+36
-3
src/simplify_reshapes.cpp
src/simplify_reshapes.cpp
+1
-1
src/split_single_dyn_dim.cpp
src/split_single_dyn_dim.cpp
+126
-0
src/targets/cpu/include/migraphx/cpu/parallel.hpp
src/targets/cpu/include/migraphx/cpu/parallel.hpp
+1
-1
src/targets/cpu/include/migraphx/cpu/target.hpp
src/targets/cpu/include/migraphx/cpu/target.hpp
+0
-3
src/targets/cpu/target.cpp
src/targets/cpu/target.cpp
+0
-2
src/targets/fpga/include/migraphx/fpga/target.hpp
src/targets/fpga/include/migraphx/fpga/target.hpp
+0
-3
src/targets/gpu/CMakeLists.txt
src/targets/gpu/CMakeLists.txt
+2
-1
src/targets/gpu/compile_gen.cpp
src/targets/gpu/compile_gen.cpp
+137
-4
src/targets/gpu/compile_hip.cpp
src/targets/gpu/compile_hip.cpp
+52
-12
src/targets/gpu/fuse_mlir.cpp
src/targets/gpu/fuse_mlir.cpp
+37
-22
src/targets/gpu/hip.cpp
src/targets/gpu/hip.cpp
+14
-2
src/targets/gpu/hiprtc/CMakeLists.txt
src/targets/gpu/hiprtc/CMakeLists.txt
+33
-0
src/targets/gpu/hiprtc/main.cpp
src/targets/gpu/hiprtc/main.cpp
+68
-0
src/targets/gpu/include/migraphx/gpu/compile_gen.hpp
src/targets/gpu/include/migraphx/gpu/compile_gen.hpp
+17
-0
No files found.
src/register_op.cpp
View file @
2fc6b715
...
@@ -33,7 +33,17 @@ std::unordered_map<std::string, operation>& op_map()
...
@@ -33,7 +33,17 @@ std::unordered_map<std::string, operation>& op_map()
static
std
::
unordered_map
<
std
::
string
,
operation
>
m
;
// NOLINT
static
std
::
unordered_map
<
std
::
string
,
operation
>
m
;
// NOLINT
return
m
;
return
m
;
}
}
void
register_op_init
()
{
(
void
)
op_map
();
}
void
register_op
(
const
operation
&
op
)
{
op_map
()[
op
.
name
()]
=
op
;
}
void
register_op
(
const
operation
&
op
)
{
op_map
()[
op
.
name
()]
=
op
;
}
void
unregister_op
(
const
std
::
string
&
op_name
)
{
assert
(
op_map
().
count
(
op_name
));
op_map
().
erase
(
op_name
);
}
operation
load_op
(
const
std
::
string
&
name
)
operation
load_op
(
const
std
::
string
&
name
)
{
{
return
at
(
op_map
(),
name
,
"Operator not found: "
+
name
);
return
at
(
op_map
(),
name
,
"Operator not found: "
+
name
);
...
...
src/register_target.cpp
View file @
2fc6b715
...
@@ -21,26 +21,48 @@
...
@@ -21,26 +21,48 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* THE SOFTWARE.
*/
*/
#include <string>
#include <unordered_map>
#include <unordered_map>
#include <migraphx/register_target.hpp>
#include <migraphx/register_target.hpp>
#include <migraphx/ranges.hpp>
#include <migraphx/dynamic_loader.hpp>
namespace
migraphx
{
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
inline
namespace
MIGRAPHX_INLINE_NS
{
void
store_target_lib
(
const
dynamic_loader
&
lib
)
{
static
std
::
vector
<
dynamic_loader
>
target_loader
;
target_loader
.
emplace_back
(
lib
);
}
std
::
unordered_map
<
std
::
string
,
target
>&
target_map
()
std
::
unordered_map
<
std
::
string
,
target
>&
target_map
()
{
{
static
std
::
unordered_map
<
std
::
string
,
target
>
m
;
// NOLINT
static
std
::
unordered_map
<
std
::
string
,
target
>
m
;
// NOLINT
return
m
;
return
m
;
}
}
void
register_target_init
()
{
(
void
)
target_map
();
}
void
unregister_target
(
const
std
::
string
&
name
)
{
assert
(
target_map
().
count
(
name
));
target_map
().
erase
(
name
);
}
void
register_target
(
const
target
&
t
)
{
target_map
()[
t
.
name
()]
=
t
;
}
void
register_target
(
const
target
&
t
)
{
target_map
()[
t
.
name
()]
=
t
;
}
target
make_target
(
const
std
::
string
&
name
)
target
make_target
(
const
std
::
string
&
name
)
{
{
if
(
not
contains
(
target_map
(),
name
))
{
std
::
string
target_name
=
"libmigraphx_"
+
name
+
".so"
;
store_target_lib
(
dynamic_loader
(
target_name
));
}
const
auto
it
=
target_map
().
find
(
name
);
const
auto
it
=
target_map
().
find
(
name
);
if
(
it
==
target_map
().
end
())
if
(
it
==
target_map
().
end
())
{
{
MIGRAPHX_THROW
(
"Requested target '"
+
name
+
"' is not
enabl
ed or not supported"
);
MIGRAPHX_THROW
(
"Requested target '"
+
name
+
"' is not
load
ed or not supported"
);
}
}
return
it
->
second
;
return
it
->
second
;
}
}
...
...
src/replace_allocate.cpp
View file @
2fc6b715
...
@@ -104,8 +104,7 @@ void replace_allocate::apply(module& m) const
...
@@ -104,8 +104,7 @@ void replace_allocate::apply(module& m) const
continue
;
continue
;
auto
s
=
ins
->
get_shape
();
auto
s
=
ins
->
get_shape
();
if
(
not
(
main_offload_copy
or
m
.
use_local_alloc
)
and
model
.
needs_out_params
()
and
if
(
not
main_offload_copy
and
model
.
needs_out_params
()
and
contains
(
mod_output_names
,
ins
))
contains
(
mod_output_names
,
ins
))
{
{
auto
out_param
=
m
.
add_parameter
(
mod_output_names
[
ins
],
s
);
auto
out_param
=
m
.
add_parameter
(
mod_output_names
[
ins
],
s
);
m
.
replace_instruction
(
ins
,
out_param
);
m
.
replace_instruction
(
ins
,
out_param
);
...
...
src/schedule.cpp
View file @
2fc6b715
...
@@ -327,10 +327,10 @@ struct stream_info
...
@@ -327,10 +327,10 @@ struct stream_info
return
[
=
](
auto
f
)
{
return
[
=
](
auto
f
)
{
return
fix
<
bool
>
([
&
](
auto
self
,
auto
ins
)
{
return
fix
<
bool
>
([
&
](
auto
self
,
auto
ins
)
{
return
all_of
(
select
(
ins
),
[
&
](
auto
i
)
{
return
all_of
(
select
(
ins
),
[
&
](
auto
i
)
{
if
(
iweights
.
at
(
i
)
==
0
)
if
(
has_stream
(
i
))
return
self
(
i
);
else
return
f
(
this
->
get_stream
(
i
));
return
f
(
this
->
get_stream
(
i
));
else
return
self
(
i
);
});
});
})(
start
);
})(
start
);
};
};
...
...
src/shape.cpp
View file @
2fc6b715
...
@@ -74,13 +74,23 @@ struct shape_impl
...
@@ -74,13 +74,23 @@ struct shape_impl
shape_impl
(
shape
::
type_t
t
,
shape_impl
(
shape
::
type_t
t
,
std
::
vector
<
std
::
size_t
>
mins
,
std
::
vector
<
std
::
size_t
>
mins
,
std
::
vector
<
std
::
size_t
>
maxes
,
std
::
vector
<
std
::
size_t
>
maxes
,
std
::
vector
<
std
::
size_t
>
opt
s
)
std
::
vector
<
std
::
set
<
std
::
size_t
>
>
opt
imals_list
)
:
m_type
(
t
)
:
m_type
(
t
)
{
{
assert
(
mins
.
size
()
==
maxes
.
size
()
and
maxes
.
size
()
==
opts
.
size
());
if
(
optimals_list
.
empty
())
for
(
size_t
i
=
0
;
i
<
mins
.
size
();
++
i
)
{
{
m_dyn_dims
.
push_back
(
shape
::
dynamic_dimension
{
mins
[
i
],
maxes
[
i
],
opts
[
i
]});
for
(
size_t
i
=
0
;
i
<
mins
.
size
();
++
i
)
{
m_dyn_dims
.
push_back
(
shape
::
dynamic_dimension
{
mins
[
i
],
maxes
[
i
]});
}
}
else
{
assert
(
mins
.
size
()
==
maxes
.
size
()
and
maxes
.
size
()
==
optimals_list
.
size
());
for
(
size_t
i
=
0
;
i
<
mins
.
size
();
++
i
)
{
m_dyn_dims
.
push_back
(
shape
::
dynamic_dimension
{
mins
[
i
],
maxes
[
i
],
optimals_list
[
i
]});
}
}
}
}
}
...
@@ -147,7 +157,7 @@ struct shape_impl
...
@@ -147,7 +157,7 @@ struct shape_impl
std
::
transform
(
m_dyn_dims
.
cbegin
(),
std
::
transform
(
m_dyn_dims
.
cbegin
(),
m_dyn_dims
.
cend
(),
m_dyn_dims
.
cend
(),
ret
.
begin
(),
ret
.
begin
(),
[](
shape
::
dynamic_dimension
x
)
{
return
x
.
min
;
});
[](
const
shape
::
dynamic_dimension
&
x
)
{
return
x
.
min
;
});
return
ret
;
return
ret
;
}
}
...
@@ -157,19 +167,20 @@ struct shape_impl
...
@@ -157,19 +167,20 @@ struct shape_impl
std
::
transform
(
m_dyn_dims
.
cbegin
(),
std
::
transform
(
m_dyn_dims
.
cbegin
(),
m_dyn_dims
.
cend
(),
m_dyn_dims
.
cend
(),
ret
.
begin
(),
ret
.
begin
(),
[](
shape
::
dynamic_dimension
x
)
{
return
x
.
max
;
});
[](
const
shape
::
dynamic_dimension
&
x
)
{
return
x
.
max
;
});
return
ret
;
return
ret
;
}
}
std
::
vector
<
std
::
size_t
>
opt_lens
()
const
std
::
vector
<
std
::
set
<
std
::
size_t
>
>
opt_lens
()
const
{
{
std
::
vector
<
std
::
size_t
>
ret
(
m_dyn_dims
.
size
());
std
::
vector
<
std
::
set
<
std
::
size_t
>
>
ret
(
m_dyn_dims
.
size
());
std
::
transform
(
m_dyn_dims
.
cbegin
(),
std
::
transform
(
m_dyn_dims
.
cbegin
(),
m_dyn_dims
.
cend
(),
m_dyn_dims
.
cend
(),
ret
.
begin
(),
ret
.
begin
(),
[](
shape
::
dynamic_dimension
x
)
{
return
x
.
opt
;
});
[](
const
shape
::
dynamic_dimension
&
x
)
{
return
x
.
opt
imals
;
});
return
ret
;
return
ret
;
}
}
// Does the shape skip over elements?
// Does the shape skip over elements?
bool
skips
()
const
bool
skips
()
const
{
{
...
@@ -240,8 +251,9 @@ shape::shape(type_t t, std::vector<shape::dynamic_dimension> dims)
...
@@ -240,8 +251,9 @@ shape::shape(type_t t, std::vector<shape::dynamic_dimension> dims)
shape
::
shape
(
type_t
t
,
shape
::
shape
(
type_t
t
,
std
::
vector
<
std
::
size_t
>
mins
,
std
::
vector
<
std
::
size_t
>
mins
,
std
::
vector
<
std
::
size_t
>
maxes
,
std
::
vector
<
std
::
size_t
>
maxes
,
std
::
vector
<
std
::
size_t
>
opts
)
std
::
vector
<
std
::
set
<
std
::
size_t
>>
optimals_list
)
:
impl
(
std
::
make_shared
<
shape_impl
>
(
t
,
std
::
move
(
mins
),
std
::
move
(
maxes
),
std
::
move
(
opts
)))
:
impl
(
std
::
make_shared
<
shape_impl
>
(
t
,
std
::
move
(
mins
),
std
::
move
(
maxes
),
std
::
move
(
optimals_list
)))
{
{
}
}
...
@@ -469,12 +481,44 @@ shape shape::with_type(type_t t) const
...
@@ -469,12 +481,44 @@ shape shape::with_type(type_t t) const
shape
shape
::
to_dynamic
()
const
shape
shape
::
to_dynamic
()
const
{
{
if
(
not
sub_shapes
().
empty
())
{
std
::
vector
<
shape
>
subs
;
std
::
transform
(
sub_shapes
().
cbegin
(),
sub_shapes
().
cend
(),
std
::
back_inserter
(
subs
),
[](
auto
s
)
{
return
s
.
to_dynamic
();
});
return
{
subs
};
}
if
(
this
->
dynamic
())
if
(
this
->
dynamic
())
{
{
return
*
this
;
return
*
this
;
}
}
std
::
vector
<
std
::
size_t
>
zeroes
(
this
->
ndim
(),
0
);
return
{
type
(),
lens
(),
lens
(),
{}};
return
{
type
(),
lens
(),
lens
(),
zeroes
};
}
shape
shape
::
to_static
(
std
::
size_t
x
)
const
{
if
(
not
sub_shapes
().
empty
())
{
std
::
vector
<
shape
>
subs
;
std
::
transform
(
sub_shapes
().
cbegin
(),
sub_shapes
().
cend
(),
std
::
back_inserter
(
subs
),
[
&
](
auto
s
)
{
return
s
.
to_static
(
x
);
});
return
{
subs
};
}
if
(
not
this
->
dynamic
())
{
return
*
this
;
}
auto
static_lens
=
this
->
max_lens
();
std
::
transform
(
static_lens
.
begin
(),
static_lens
.
end
(),
this
->
dyn_dims
().
cbegin
(),
static_lens
.
begin
(),
[
&
](
auto
sl
,
auto
dd
)
{
return
dd
.
is_fixed
()
?
sl
:
x
;
});
return
{
type
(),
static_lens
};
}
}
std
::
size_t
shape
::
element_space
()
const
{
return
impl
->
element_space
();
}
std
::
size_t
shape
::
element_space
()
const
{
return
impl
->
element_space
();
}
...
@@ -506,23 +550,22 @@ std::vector<std::size_t> shape::max_lens() const
...
@@ -506,23 +550,22 @@ std::vector<std::size_t> shape::max_lens() const
return
this
->
dynamic
()
?
impl
->
max_lens
()
:
this
->
lens
();
return
this
->
dynamic
()
?
impl
->
max_lens
()
:
this
->
lens
();
}
}
std
::
vector
<
std
::
size_t
>
shape
::
opt_lens
()
const
std
::
vector
<
std
::
set
<
std
::
size_t
>>
shape
::
opt_lens
()
const
{
return
impl
->
opt_lens
();
}
{
return
this
->
dynamic
()
?
impl
->
opt_lens
()
:
this
->
lens
();
}
bool
shape
::
dynamic_dimension
::
is_fixed
()
const
{
return
this
->
min
==
this
->
max
;
}
bool
shape
::
dynamic_dimension
::
is_fixed
()
const
{
return
this
->
min
==
this
->
max
;
}
bool
shape
::
dynamic_dimension
::
has_optimal
()
const
{
return
opt
!=
0
;
}
bool
shape
::
dynamic_dimension
::
has_optimal
()
const
{
return
not
optimals
.
empty
()
;
}
shape
::
dynamic_dimension
&
shape
::
dynamic_dimension
::
operator
+=
(
const
std
::
size_t
&
x
)
shape
::
dynamic_dimension
&
shape
::
dynamic_dimension
::
operator
+=
(
const
std
::
size_t
&
x
)
{
{
this
->
min
+=
x
;
this
->
min
+=
x
;
this
->
max
+=
x
;
this
->
max
+=
x
;
if
(
this
->
opt
!=
0
)
std
::
set
<
std
::
size_t
>
new_optimals
;
{
std
::
transform
(
this
->
optimals
.
begin
(),
this
->
opt
+=
x
;
this
->
optimals
.
end
(),
};
std
::
inserter
(
new_optimals
,
new_optimals
.
begin
()),
[
&
x
](
const
auto
&
opt
)
{
return
(
opt
+
x
);
});
this
->
optimals
=
new_optimals
;
return
*
this
;
return
*
this
;
}
}
...
@@ -532,19 +575,23 @@ shape::dynamic_dimension& shape::dynamic_dimension::operator-=(const std::size_t
...
@@ -532,19 +575,23 @@ shape::dynamic_dimension& shape::dynamic_dimension::operator-=(const std::size_t
assert
(
this
->
max
>=
x
);
assert
(
this
->
max
>=
x
);
this
->
min
-=
x
;
this
->
min
-=
x
;
this
->
max
-=
x
;
this
->
max
-=
x
;
if
(
this
->
opt
!=
0
)
std
::
set
<
std
::
size_t
>
new_optimals
;
{
std
::
transform
(
this
->
optimals
.
begin
(),
assert
(
this
->
opt
>=
x
);
this
->
optimals
.
end
(),
this
->
opt
-=
x
;
std
::
inserter
(
new_optimals
,
new_optimals
.
begin
()),
}
[
&
x
](
const
auto
&
opt
)
{
assert
(
opt
>=
x
);
return
(
opt
-
x
);
});
this
->
optimals
=
new_optimals
;
return
*
this
;
return
*
this
;
}
}
bool
operator
==
(
const
shape
::
dynamic_dimension
&
x
,
const
shape
::
dynamic_dimension
&
y
)
bool
operator
==
(
const
shape
::
dynamic_dimension
&
x
,
const
shape
::
dynamic_dimension
&
y
)
{
{
// don't check opt if both are fixed
// don't check opt
imals
if both are fixed
return
(
x
.
min
==
y
.
min
and
x
.
max
==
y
.
max
and
return
(
x
.
min
==
y
.
min
and
x
.
max
==
y
.
max
and
((
x
.
is_fixed
()
and
y
.
is_fixed
())
or
(
x
.
opt
==
y
.
opt
)));
((
x
.
is_fixed
()
and
y
.
is_fixed
())
or
(
x
.
opt
imals
==
y
.
opt
imals
)));
}
}
bool
operator
!=
(
const
shape
::
dynamic_dimension
&
x
,
const
shape
::
dynamic_dimension
&
y
)
bool
operator
!=
(
const
shape
::
dynamic_dimension
&
x
,
const
shape
::
dynamic_dimension
&
y
)
...
@@ -553,7 +600,7 @@ bool operator!=(const shape::dynamic_dimension& x, const shape::dynamic_dimensio
...
@@ -553,7 +600,7 @@ bool operator!=(const shape::dynamic_dimension& x, const shape::dynamic_dimensio
}
}
std
::
ostream
&
operator
<<
(
std
::
ostream
&
os
,
const
shape
::
dynamic_dimension
&
x
)
std
::
ostream
&
operator
<<
(
std
::
ostream
&
os
,
const
shape
::
dynamic_dimension
&
x
)
{
{
os
<<
"["
<<
x
.
min
<<
", "
<<
x
.
max
<<
", "
<<
x
.
opt
<<
"]"
;
os
<<
"[
"
<<
x
.
min
<<
", "
<<
x
.
max
<<
",
{
"
<<
migraphx
::
to_string_range
(
x
.
optimals
)
<<
"
}
]"
;
return
os
;
return
os
;
}
}
...
@@ -663,10 +710,12 @@ void migraphx_from_value(const value& v, shape& s)
...
@@ -663,10 +710,12 @@ void migraphx_from_value(const value& v, shape& s)
auto
v_dd
=
v
.
at
(
"dynamic_dimensions"
);
auto
v_dd
=
v
.
at
(
"dynamic_dimensions"
);
std
::
vector
<
shape
::
dynamic_dimension
>
dyn_dims
(
v
.
at
(
"dynamic_dimensions"
).
size
());
std
::
vector
<
shape
::
dynamic_dimension
>
dyn_dims
(
v
.
at
(
"dynamic_dimensions"
).
size
());
std
::
transform
(
v_dd
.
begin
(),
v_dd
.
end
(),
dyn_dims
.
begin
(),
[](
migraphx
::
value
x
)
{
std
::
transform
(
v_dd
.
begin
(),
v_dd
.
end
(),
dyn_dims
.
begin
(),
[](
migraphx
::
value
x
)
{
auto
x_min
=
x
.
at
(
"min"
).
template
to
<
size_t
>();
auto
x_min
=
x
.
at
(
"min"
).
template
to
<
size_t
>();
auto
x_max
=
x
.
at
(
"max"
).
template
to
<
size_t
>();
auto
x_max
=
x
.
at
(
"max"
).
template
to
<
size_t
>();
auto
x_opt
=
x
.
at
(
"opt"
).
template
to
<
size_t
>();
auto
v_optimals
=
x
.
at
(
"optimals"
);
return
shape
::
dynamic_dimension
{
x_min
,
x_max
,
x_opt
};
std
::
set
<
size_t
>
set_x_optimals
=
from_value
<
std
::
set
<
std
::
size_t
>>
(
x
.
at
(
"optimals"
));
return
shape
::
dynamic_dimension
{
x_min
,
x_max
,
set_x_optimals
};
});
});
s
=
shape
{
shape
::
parse_type
(
t
),
dyn_dims
};
s
=
shape
{
shape
::
parse_type
(
t
),
dyn_dims
};
...
...
src/simplify_algebra.cpp
View file @
2fc6b715
...
@@ -52,8 +52,9 @@ auto op_lit_broadcast(std::string op, std::string x, std::string y)
...
@@ -52,8 +52,9 @@ auto op_lit_broadcast(std::string op, std::string x, std::string y)
auto
conv_const_weights
()
auto
conv_const_weights
()
{
{
return
match
::
name
(
"convolution"
)(
match
::
used_once
(),
return
match
::
name
(
"convolution"
)(
match
::
args
(
match
::
any
(),
match
::
is_constant
().
bind
(
"w"
)));
match
::
used_once
(),
match
::
args
(
match
::
none_of
(
match
::
is_constant
()),
match
::
is_constant
().
bind
(
"w"
)));
}
}
auto
reduction
()
{
return
match
::
name_contains
(
"reduce"
);
}
auto
reduction
()
{
return
match
::
name_contains
(
"reduce"
);
}
...
@@ -203,7 +204,12 @@ struct find_mul_slice_conv
...
@@ -203,7 +204,12 @@ struct find_mul_slice_conv
}
}
};
};
// a * (x + b) => a * x + a * b
// ******************************
// a * (x + b) => a * x + a * b
// ******************************
// When a * (x + b) is followed by another add of constant, then the
// additional add can be const folded. Also, better fusions can be applied
// when the add comes after.
struct
find_mul_add
struct
find_mul_add
{
{
auto
matcher
()
const
auto
matcher
()
const
...
@@ -268,6 +274,32 @@ struct find_dot_add
...
@@ -268,6 +274,32 @@ struct find_dot_add
}
}
};
};
struct
find_conv_add
{
auto
matcher
()
const
{
auto
add
=
match
::
name
(
"add"
)(
match
::
either_arg
(
0
,
1
)(
match
::
any
().
bind
(
"x"
),
match
::
any_of
(
match
::
is_constant
()).
bind
(
"a"
)),
match
::
used_once
());
return
match
::
name
(
"convolution"
)(
match
::
used_once
(),
match
::
args
(
add
,
match
::
is_constant
().
bind
(
"w"
)));
}
void
apply
(
module
&
m
,
const
match
::
matcher_result
&
r
)
const
{
auto
ins
=
r
.
result
;
auto
a_ins
=
r
.
instructions
[
"a"
];
auto
x_ins
=
r
.
instructions
[
"x"
];
auto
w_ins
=
r
.
instructions
[
"w"
];
auto
conv1
=
m
.
insert_instruction
(
ins
,
ins
->
get_operator
(),
a_ins
,
w_ins
);
auto
conv2
=
m
.
insert_instruction
(
ins
,
ins
->
get_operator
(),
x_ins
,
w_ins
);
m
.
replace_instruction
(
ins
,
make_op
(
"add"
),
conv1
,
conv2
);
}
};
struct
find_add_lit_broadcast
struct
find_add_lit_broadcast
{
{
auto
matcher
()
const
auto
matcher
()
const
...
@@ -1239,6 +1271,7 @@ void simplify_algebra::apply(module& m) const
...
@@ -1239,6 +1271,7 @@ void simplify_algebra::apply(module& m) const
find_neg_unit_ops
{},
find_neg_unit_ops
{},
find_zero_ops
{},
find_zero_ops
{},
find_dot_add
{},
find_dot_add
{},
find_conv_add
{},
find_div_const
{},
find_div_const
{},
find_sub_const
{},
find_sub_const
{},
find_rsqrt
{},
find_rsqrt
{},
...
...
src/simplify_reshapes.cpp
View file @
2fc6b715
...
@@ -786,7 +786,7 @@ struct find_transpose_slice
...
@@ -786,7 +786,7 @@ struct find_transpose_slice
return
;
return
;
// Compute axis before transpose to use for unsqueeze
// Compute axis before transpose to use for unsqueeze
auto
perm
=
ins
->
get_operator
().
to_value
()[
"permutation"
].
to_vector
<
int64_t
>
();
auto
perm
=
ins
->
get_operator
().
to_value
()[
"permutation"
].
to_vector
<
int64_t
>
();
auto
preaxis
=
std
::
find
(
perm
.
begin
(),
perm
.
end
(),
axis
)
-
perm
.
begin
()
;
auto
preaxis
=
perm
[
axis
]
;
// Make unsqueeze
// Make unsqueeze
std
::
vector
<
int64_t
>
steps
(
sdistance
.
size
());
std
::
vector
<
int64_t
>
steps
(
sdistance
.
size
());
std
::
transform
(
std
::
transform
(
...
...
src/split_single_dyn_dim.cpp
0 → 100644
View file @
2fc6b715
/*
* 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.
*/
#include <migraphx/split_single_dyn_dim.hpp>
#include <migraphx/module.hpp>
#include <migraphx/pass_manager.hpp>
#include <migraphx/functional.hpp>
#include <migraphx/make_op.hpp>
#include <migraphx/ranges.hpp>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
struct
dynamic_dimensions_check
{
std
::
string
dyn_param_str
;
size_t
dyn_index
;
size_t
min_dim
;
size_t
max_dim
;
};
optional
<
dynamic_dimensions_check
>
has_one_dyn_dim
(
const
std
::
unordered_map
<
std
::
string
,
shape
>&
param_shapes
)
{
// True if parameters contain exactly one dynamic shape with exactly one non-fixed
// dynamic_dimension.
auto
is_dynamic
=
[](
const
auto
&
p
)
{
return
p
.
second
.
dynamic
();
};
auto
ps_it
=
std
::
find_if
(
param_shapes
.
begin
(),
param_shapes
.
end
(),
is_dynamic
);
if
(
ps_it
==
param_shapes
.
end
())
return
std
::
nullopt
;
// Check if there is a second dynamic parameter
if
(
std
::
any_of
(
std
::
next
(
ps_it
),
param_shapes
.
end
(),
is_dynamic
))
return
std
::
nullopt
;
const
auto
&
dds
=
ps_it
->
second
.
dyn_dims
();
auto
is_non_fixed
=
[](
const
auto
&
dd
)
{
return
not
dd
.
is_fixed
();
};
auto
dds_it
=
std
::
find_if
(
dds
.
begin
(),
dds
.
end
(),
is_non_fixed
);
if
(
dds_it
==
dds
.
end
())
return
std
::
nullopt
;
// Check if there is a second non-fixed dynamic_dimension
if
(
std
::
any_of
(
std
::
next
(
dds_it
),
dds
.
end
(),
is_non_fixed
))
return
std
::
nullopt
;
return
dynamic_dimensions_check
{
ps_it
->
first
,
static_cast
<
std
::
size_t
>
(
std
::
distance
(
dds
.
begin
(),
dds_it
)),
dds_it
->
min
,
dds_it
->
max
};
}
/**
* Makes all the shapes in the dynamic_dimension range.
* Probably won't work for `if` and `loop` instructions, depending on how the submodules for those
* work. Inserts select_module instruction to the top. Replaces return, bypassing other
* instructions.
*/
void
split_single_dyn_dim
::
apply
(
module_pass_manager
&
mpm
)
const
{
module_ref
mm
=
&
mpm
.
get_module
();
auto
param_names
=
mm
->
get_parameter_names
();
auto
param_shapes
=
mm
->
get_parameter_shapes
();
optional
<
dynamic_dimensions_check
>
dd_check
=
has_one_dyn_dim
(
param_shapes
);
if
(
dd_check
.
has_value
())
{
const
auto
&
dyn_param
=
mm
->
get_parameter
(
dd_check
->
dyn_param_str
);
auto
dyn_param_shape
=
mm
->
get_parameter_shape
(
dd_check
->
dyn_param_str
);
std
::
vector
<
module_ref
>
submodules
;
// create submodules for each dimension size
for
(
size_t
dim_size
:
migraphx
::
range
(
dd_check
->
min_dim
,
dd_check
->
max_dim
+
1
))
{
auto
*
submod
=
mpm
.
create_module
(
"dim_"
+
std
::
to_string
(
dim_size
));
// instruction map for new static shaped submodule parameters
std
::
unordered_map
<
instruction_ref
,
instruction_ref
>
map_ins
;
// create static shape using dim_size
auto
static_lens
=
dyn_param_shape
.
max_lens
();
static_lens
.
at
(
dd_check
->
dyn_index
)
=
dim_size
;
map_ins
[
dyn_param
]
=
submod
->
add_parameter
(
dd_check
->
dyn_param_str
,
migraphx
::
shape
{
dyn_param_shape
.
type
(),
static_lens
});
auto
outputs
=
submod
->
add_instructions
(
mm
,
map_ins
);
submod
->
add_return
({
outputs
});
submodules
.
push_back
(
submod
);
}
// redirect to select_module operator and return
std
::
vector
<
instruction_ref
>
sm_inputs
;
std
::
transform
(
param_names
.
cbegin
(),
param_names
.
cend
(),
std
::
back_inserter
(
sm_inputs
),
[
&
](
auto
pn
)
{
return
mm
->
get_parameter
(
pn
);
});
auto
output_shapes
=
mm
->
get_output_shapes
();
migraphx
::
shape
out_attr
=
migraphx
::
shape
{
output_shapes
};
auto
sm_ins
=
mm
->
add_instruction
(
migraphx
::
make_op
(
"select_module"
,
{{
"output_dyn_shapes"
,
migraphx
::
to_value
(
out_attr
)}}),
sm_inputs
,
submodules
);
std
::
vector
<
instruction_ref
>
outputs
(
output_shapes
.
size
());
for
(
size_t
i
=
0
;
i
<
output_shapes
.
size
();
++
i
)
{
outputs
.
at
(
i
)
=
mm
->
add_instruction
(
migraphx
::
make_op
(
"get_tuple_elem"
,
{{
"index"
,
i
}}),
sm_ins
);
}
mm
->
replace_return
(
outputs
);
}
}
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
src/targets/cpu/include/migraphx/cpu/parallel.hpp
View file @
2fc6b715
...
@@ -25,7 +25,7 @@
...
@@ -25,7 +25,7 @@
#define MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_PARALLEL_HPP
#define MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_PARALLEL_HPP
// #define MIGRAPHX_DISABLE_OMP
// #define MIGRAPHX_DISABLE_OMP
#include <cmath>
#include <migraphx/config.hpp>
#include <migraphx/config.hpp>
#ifdef MIGRAPHX_DISABLE_OMP
#ifdef MIGRAPHX_DISABLE_OMP
#include <migraphx/par_for.hpp>
#include <migraphx/par_for.hpp>
...
...
src/targets/cpu/include/migraphx/cpu/target.hpp
View file @
2fc6b715
...
@@ -40,14 +40,11 @@ struct target
...
@@ -40,14 +40,11 @@ struct target
std
::
string
name
()
const
;
std
::
string
name
()
const
;
std
::
vector
<
pass
>
get_passes
(
migraphx
::
context
&
gctx
,
const
compile_options
&
)
const
;
std
::
vector
<
pass
>
get_passes
(
migraphx
::
context
&
gctx
,
const
compile_options
&
)
const
;
migraphx
::
context
get_context
()
const
{
return
context
{};
}
migraphx
::
context
get_context
()
const
{
return
context
{};
}
argument
copy_to
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
copy_to
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
copy_from
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
copy_from
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
allocate
(
const
shape
&
s
)
const
;
argument
allocate
(
const
shape
&
s
)
const
;
};
};
MIGRAPHX_REGISTER_TARGET
(
target
);
}
// namespace cpu
}
// namespace cpu
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
}
// namespace migraphx
...
...
src/targets/cpu/target.cpp
View file @
2fc6b715
...
@@ -23,7 +23,6 @@
...
@@ -23,7 +23,6 @@
*/
*/
#include <migraphx/auto_contiguous.hpp>
#include <migraphx/auto_contiguous.hpp>
#include <migraphx/check_context.hpp>
#include <migraphx/adjust_allocation.hpp>
#include <migraphx/adjust_allocation.hpp>
#include <migraphx/dead_code_elimination.hpp>
#include <migraphx/dead_code_elimination.hpp>
#include <migraphx/eliminate_allocation.hpp>
#include <migraphx/eliminate_allocation.hpp>
...
@@ -83,7 +82,6 @@ std::vector<pass> target::get_passes(migraphx::context& gctx, const compile_opti
...
@@ -83,7 +82,6 @@ std::vector<pass> target::get_passes(migraphx::context& gctx, const compile_opti
dead_code_elimination
{},
dead_code_elimination
{},
simplify_algebra
{},
simplify_algebra
{},
simplify_reshapes
{},
simplify_reshapes
{},
layout_nhwc
{},
dead_code_elimination
{},
dead_code_elimination
{},
simplify_reshapes
{},
simplify_reshapes
{},
simplify_algebra
{},
simplify_algebra
{},
...
...
src/targets/fpga/include/migraphx/fpga/target.hpp
View file @
2fc6b715
...
@@ -43,14 +43,11 @@ struct target
...
@@ -43,14 +43,11 @@ struct target
std
::
vector
<
pass
>
get_passes
(
migraphx
::
context
&
ctx
,
const
compile_options
&
)
const
;
std
::
vector
<
pass
>
get_passes
(
migraphx
::
context
&
ctx
,
const
compile_options
&
)
const
;
migraphx
::
context
get_context
()
const
{
return
context
{};
}
migraphx
::
context
get_context
()
const
{
return
context
{};
}
supported_segments
find_supported
(
const_module_ref
mod
,
support_metric
m
)
const
;
supported_segments
find_supported
(
const_module_ref
mod
,
support_metric
m
)
const
;
argument
copy_to
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
copy_to
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
copy_from
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
copy_from
(
const
argument
&
arg
)
const
{
return
arg
;
}
argument
allocate
(
const
shape
&
s
)
const
;
argument
allocate
(
const
shape
&
s
)
const
;
};
};
MIGRAPHX_REGISTER_TARGET
(
target
);
}
// namespace fpga
}
// namespace fpga
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
}
// namespace migraphx
...
...
src/targets/gpu/CMakeLists.txt
View file @
2fc6b715
...
@@ -22,7 +22,7 @@
...
@@ -22,7 +22,7 @@
# THE SOFTWARE.
# THE SOFTWARE.
# ####################################################################################
# ####################################################################################
list
(
APPEND CMAKE_PREFIX_PATH /opt/rocm
/opt/rocm/hip
)
list
(
APPEND CMAKE_PREFIX_PATH /opt/rocm
)
find_package
(
miopen
)
find_package
(
miopen
)
# rocblas
# rocblas
...
@@ -240,6 +240,7 @@ target_link_libraries(migraphx_gpu PUBLIC migraphx MIOpen roc::rocblas)
...
@@ -240,6 +240,7 @@ target_link_libraries(migraphx_gpu PUBLIC migraphx MIOpen roc::rocblas)
target_link_libraries
(
migraphx_gpu PRIVATE migraphx_device migraphx_kernels
)
target_link_libraries
(
migraphx_gpu PRIVATE migraphx_device migraphx_kernels
)
add_subdirectory
(
driver
)
add_subdirectory
(
driver
)
add_subdirectory
(
hiprtc
)
rocm_install_targets
(
rocm_install_targets
(
TARGETS migraphx_gpu migraphx_device compile_for_gpu
TARGETS migraphx_gpu migraphx_device compile_for_gpu
...
...
src/targets/gpu/compile_gen.cpp
View file @
2fc6b715
...
@@ -168,7 +168,7 @@ std::string make_transformer_args(std::vector<std::string> transformers)
...
@@ -168,7 +168,7 @@ std::string make_transformer_args(std::vector<std::string> transformers)
return
join_strings
(
std
::
move
(
transformers
),
", "
);
return
join_strings
(
std
::
move
(
transformers
),
", "
);
}
}
std
::
string
generate_pointwise
(
const
module
&
pm
,
const
std
::
string
&
name
)
void
generate_pointwise
(
cpp_generator
&
gg
,
const
module
&
pm
,
const
std
::
string
&
name
)
{
{
module
m
=
pm
;
module
m
=
pm
;
run_passes
(
m
,
{
eliminate_common_subexpression
{},
dead_code_elimination
{}});
run_passes
(
m
,
{
eliminate_common_subexpression
{},
dead_code_elimination
{}});
...
@@ -184,8 +184,131 @@ std::string generate_pointwise(const module& pm, const std::string& name)
...
@@ -184,8 +184,131 @@ std::string generate_pointwise(const module& pm, const std::string& name)
// Add explict conversions
// Add explict conversions
g
.
fresult
(
g
.
fresult
(
[](
const
shape
&
s
)
{
return
"migraphx::convert<"
+
shape
::
cpp_type
(
s
.
type
())
+
">"
;
});
[](
const
shape
&
s
)
{
return
"migraphx::convert<"
+
shape
::
cpp_type
(
s
.
type
())
+
">"
;
});
g
.
create_function
(
gg
.
create_function
(
g
.
generate_module
(
m
)
g
.
generate_module
(
m
).
set_attributes
({
"__device__"
}).
set_generic_types
(
m
).
set_name
(
name
));
.
set_attributes
({
"__device__"
,
"__attribute__((const))"
})
.
set_generic_types
(
m
)
.
set_name
(
name
));
}
std
::
string
generate_pointwise
(
const
module
&
pm
,
const
std
::
string
&
name
)
{
cpp_generator
g
;
generate_pointwise
(
g
,
pm
,
name
);
return
g
.
str
();
}
std
::
string
reduce_op
::
str
()
const
{
return
write
+
"(r.reduce("
+
reduction
+
", "
+
init
+
", "
+
read
+
")("
+
input
+
"))"
;
}
void
reduce_op
::
set
(
instruction_ref
ins
,
const
operation
&
op
)
{
if
(
op
.
name
()
==
"reduce_sum"
)
{
reduction
=
"op::sum{}"
;
}
else
if
(
op
.
name
()
==
"reduce_mean"
)
{
auto
s
=
ins
->
inputs
().
front
()
->
get_shape
();
auto
reduce_elements
=
s
.
elements
()
/
ins
->
get_shape
().
elements
();
auto
reduce_type
=
s
.
type
();
reduction
=
"op::sum{}"
;
std
::
string
mean
=
"op::mean<"
+
std
::
to_string
(
reduce_elements
)
+
">{}"
;
// Use float accumulator when reduction size is too large for half
if
(
reduce_type
==
shape
::
half_type
and
reduce_elements
>
16384
)
read
=
"compose("
+
mean
+
", op::convert_to<float>{})"
;
else
if
(
contains
({
shape
::
float_type
,
shape
::
half_type
,
shape
::
double_type
},
reduce_type
))
read
=
mean
;
else
write
=
mean
;
}
else
if
(
op
.
name
()
==
"reduce_max"
)
{
reduction
=
"op::max{}"
;
init
=
"lowest{}"
;
}
else
if
(
op
.
name
()
==
"reduce_min"
)
{
reduction
=
"op::min{}"
;
init
=
"highest{}"
;
}
else
if
(
op
.
name
()
==
"reduce_prod"
)
{
reduction
=
"op::product{}"
;
init
=
"1"
;
}
else
{
MIGRAPHX_THROW
(
"Unsupported reduce"
);
}
}
std
::
string
reduce_op
::
generate
(
instruction_ref
ins
,
const
std
::
string
&
x
)
{
reduce_op
r
{
x
};
r
.
set
(
ins
,
ins
->
get_operator
());
return
r
.
str
();
}
static
bool
use_lazy_inner
(
instruction_ref
ins
)
{
if
(
ins
->
outputs
().
size
()
!=
1
)
return
false
;
auto
output
=
ins
->
outputs
().
front
();
return
contains
(
output
->
name
(),
"reduce"
)
or
output
->
name
()
==
"@return"
;
}
std
::
string
generate_reduce
(
const
module
&
m
,
const
std
::
string
&
name
)
{
cpp_generator
g
;
auto
ilens
=
m
.
get_parameter_shapes
().
begin
()
->
second
.
lens
();
std
::
size_t
i
=
0
;
auto
f
=
g
.
generate_module
(
m
,
[
&
](
instruction_ref
ins
,
const
auto
&
names
)
{
if
(
contains
(
ins
->
name
(),
"reduce"
))
{
return
reduce_op
::
generate
(
ins
,
names
.
at
(
ins
->
inputs
().
front
()));
}
else
if
(
ins
->
name
()
==
"pointwise"
)
{
auto
pointwise_name
=
"pointwise"
+
std
::
to_string
(
i
);
i
++
;
generate_pointwise
(
g
,
*
ins
->
module_inputs
().
front
(),
pointwise_name
);
std
::
vector
<
instruction_ref
>
tensors
;
std
::
copy_if
(
ins
->
inputs
().
begin
(),
ins
->
inputs
().
end
(),
std
::
back_inserter
(
tensors
),
[
&
](
auto
input
)
{
return
input
->
get_shape
().
lens
()
==
ilens
and
not
input
->
get_shape
().
broadcasted
();
});
auto
inner_names
=
names
;
for
(
auto
input
:
tensors
)
inner_names
[
input
]
+=
"_lambda_param"
;
auto
call_function
=
pointwise_name
+
"("
+
join_strings
(
cpp_generator
::
to_args
(
ins
->
inputs
(),
inner_names
),
", "
)
+
")"
;
if
(
tensors
.
empty
())
return
call_function
;
const
std
::
string
inner_template
=
"r.${inner}([=](${params}) { return ${call}; })(${args})"
;
std
::
string
inner_name
=
use_lazy_inner
(
ins
)
?
"lazy_inner"
:
"inner"
;
auto
args
=
cpp_generator
::
to_args
(
tensors
,
names
);
auto
params
=
cpp_generator
::
to_args
(
tensors
,
inner_names
);
std
::
transform
(
params
.
begin
(),
params
.
end
(),
params
.
begin
(),
[](
auto
s
)
{
return
"auto "
+
s
;
});
return
interpolate_string
(
inner_template
,
{{
"inner"
,
inner_name
},
{
"params"
,
join_strings
(
params
,
", "
)},
{
"args"
,
join_strings
(
args
,
", "
)},
{
"call"
,
call_function
}});
}
else
if
(
ins
->
name
()
==
"multibroadcast"
)
{
return
names
.
at
(
ins
->
inputs
().
front
());
}
MIGRAPHX_THROW
(
"Unknown operator: "
+
ins
->
name
());
});
f
.
set_attributes
({
"__device__"
,
"__attribute__((const))"
}).
set_generic_types
(
m
).
set_name
(
name
);
f
.
add_generic_param
(
"r"
);
g
.
create_function
(
f
);
return
g
.
str
();
return
g
.
str
();
}
}
...
@@ -196,7 +319,17 @@ static std::vector<std::string> get_op_names(const module& m)
...
@@ -196,7 +319,17 @@ static std::vector<std::string> get_op_names(const module& m)
{
{
if
(
starts_with
(
ins
.
name
(),
"@"
))
if
(
starts_with
(
ins
.
name
(),
"@"
))
continue
;
continue
;
result
.
push_back
(
ins
.
name
());
if
(
ins
.
name
()
==
"multibroadcast"
)
continue
;
if
(
ins
.
name
()
==
"pointwise"
)
{
auto
names
=
get_op_names
(
*
ins
.
module_inputs
().
front
());
result
.
insert
(
result
.
end
(),
names
.
begin
(),
names
.
end
());
}
else
{
result
.
push_back
(
ins
.
name
());
}
}
}
return
result
;
return
result
;
}
}
...
...
src/targets/gpu/compile_hip.cpp
View file @
2fc6b715
...
@@ -32,6 +32,13 @@
...
@@ -32,6 +32,13 @@
#ifdef MIGRAPHX_USE_HIPRTC
#ifdef MIGRAPHX_USE_HIPRTC
#include <hip/hiprtc.h>
#include <hip/hiprtc.h>
#include <migraphx/manage_ptr.hpp>
#include <migraphx/manage_ptr.hpp>
#include <migraphx/value.hpp>
#include <migraphx/tmp_dir.hpp>
#include <migraphx/dynamic_loader.hpp>
#include <migraphx/process.hpp>
#include <migraphx/msgpack.hpp>
#include <migraphx/serialize.hpp>
#include <migraphx/file_buffer.hpp>
#else
#else
#include <migraphx/compile_src.hpp>
#include <migraphx/compile_src.hpp>
#include <migraphx/process.hpp>
#include <migraphx/process.hpp>
...
@@ -63,6 +70,7 @@ void hiprtc_check_error(hiprtcResult err, const std::string& msg, const std::str
...
@@ -63,6 +70,7 @@ void hiprtc_check_error(hiprtcResult err, const std::string& msg, const std::str
throw
make_exception
(
ctx
,
hiprtc_error
(
err
,
msg
));
throw
make_exception
(
ctx
,
hiprtc_error
(
err
,
msg
));
}
}
// NOLINTNEXTLINE
#define MIGRAPHX_HIPRTC(...) \
#define MIGRAPHX_HIPRTC(...) \
hiprtc_check_error(__VA_ARGS__, #__VA_ARGS__, MIGRAPHX_MAKE_SOURCE_CTX())
hiprtc_check_error(__VA_ARGS__, #__VA_ARGS__, MIGRAPHX_MAKE_SOURCE_CTX())
...
@@ -110,21 +118,19 @@ struct hiprtc_program
...
@@ -110,21 +118,19 @@ struct hiprtc_program
std
::
string
cpp_src
=
""
;
std
::
string
cpp_src
=
""
;
std
::
string
cpp_name
=
""
;
std
::
string
cpp_name
=
""
;
hiprtc_program
(
const
std
::
vector
<
src_file
>
&
srcs
)
hiprtc_program
(
std
::
vector
<
hiprtc_
src_file
>
srcs
)
{
{
for
(
auto
&&
src
:
srcs
)
for
(
auto
&&
src
:
srcs
)
{
{
std
::
string
content
{
src
.
content
.
first
,
src
.
content
.
second
};
if
(
ends_with
(
src
.
path
,
".cpp"
))
std
::
string
path
=
src
.
path
.
string
();
if
(
src
.
path
.
extension
().
string
()
==
".cpp"
)
{
{
cpp_src
=
std
::
move
(
content
);
cpp_src
=
std
::
move
(
src
.
content
);
cpp_name
=
std
::
move
(
path
);
cpp_name
=
std
::
move
(
src
.
path
);
}
}
else
else
{
{
headers
.
push_back
(
std
::
move
(
content
));
headers
.
push_back
(
std
::
move
(
src
.
content
));
include_names
.
push_back
(
std
::
move
(
path
));
include_names
.
push_back
(
std
::
move
(
src
.
path
));
}
}
}
}
prog
=
hiprtc_program_create
(
cpp_src
.
c_str
(),
prog
=
hiprtc_program_create
(
cpp_src
.
c_str
(),
...
@@ -134,7 +140,7 @@ struct hiprtc_program
...
@@ -134,7 +140,7 @@ struct hiprtc_program
include_names
.
data
());
include_names
.
data
());
}
}
void
compile
(
const
std
::
vector
<
std
::
string
>&
options
)
void
compile
(
const
std
::
vector
<
std
::
string
>&
options
)
const
{
{
if
(
enabled
(
MIGRAPHX_TRACE_HIPRTC
{}))
if
(
enabled
(
MIGRAPHX_TRACE_HIPRTC
{}))
std
::
cout
<<
"hiprtc "
<<
join_strings
(
options
,
" "
)
<<
" "
<<
cpp_name
<<
std
::
endl
;
std
::
cout
<<
"hiprtc "
<<
join_strings
(
options
,
" "
)
<<
" "
<<
cpp_name
<<
std
::
endl
;
...
@@ -175,10 +181,11 @@ struct hiprtc_program
...
@@ -175,10 +181,11 @@ struct hiprtc_program
}
}
};
};
std
::
vector
<
std
::
vector
<
char
>>
std
::
vector
<
std
::
vector
<
char
>>
compile_hip_src_with_hiprtc
(
std
::
vector
<
hiprtc_src_file
>
srcs
,
compile_hip_src
(
const
std
::
vector
<
src_file
>&
srcs
,
std
::
string
params
,
const
std
::
string
&
arch
)
std
::
string
params
,
const
std
::
string
&
arch
)
{
{
hiprtc_program
prog
(
srcs
);
hiprtc_program
prog
(
std
::
move
(
srcs
)
)
;
auto
options
=
split_string
(
params
,
' '
);
auto
options
=
split_string
(
params
,
' '
);
options
.
push_back
(
"-DMIGRAPHX_USE_HIPRTC=1"
);
options
.
push_back
(
"-DMIGRAPHX_USE_HIPRTC=1"
);
// remove following three compilation flags for HIPRTC once fixes from hipRTC are available in
// remove following three compilation flags for HIPRTC once fixes from hipRTC are available in
...
@@ -205,8 +212,41 @@ compile_hip_src(const std::vector<src_file>& srcs, std::string params, const std
...
@@ -205,8 +212,41 @@ compile_hip_src(const std::vector<src_file>& srcs, std::string params, const std
return
{
prog
.
get_code_obj
()};
return
{
prog
.
get_code_obj
()};
}
}
std
::
vector
<
std
::
vector
<
char
>>
compile_hip_src
(
const
std
::
vector
<
src_file
>&
srcs
,
std
::
string
params
,
const
std
::
string
&
arch
)
{
std
::
vector
<
hiprtc_src_file
>
hsrcs
{
srcs
.
begin
(),
srcs
.
end
()};
auto
p
=
dynamic_loader
::
path
(
&
compile_hip_src_with_hiprtc
);
auto
driver
=
p
.
parent_path
().
parent_path
()
/
"bin"
/
"migraphx-hiprtc-driver"
;
if
(
fs
::
exists
(
driver
))
{
value
v
;
v
[
"srcs"
]
=
to_value
(
hsrcs
);
v
[
"params"
]
=
to_value
(
params
);
v
[
"arch"
]
=
to_value
(
arch
);
tmp_dir
td
{};
auto
out
=
td
.
path
/
"output"
;
process
(
driver
.
string
()
+
" "
+
out
.
string
()).
write
([
&
](
auto
writer
)
{
to_msgpack
(
v
,
writer
);
});
if
(
fs
::
exists
(
out
))
return
{
read_buffer
(
out
.
string
())};
}
return
compile_hip_src_with_hiprtc
(
std
::
move
(
hsrcs
),
std
::
move
(
params
),
arch
);
}
#else // MIGRAPHX_USE_HIPRTC
#else // MIGRAPHX_USE_HIPRTC
std
::
vector
<
std
::
vector
<
char
>>
compile_hip_src_with_hiprtc
(
std
::
vector
<
hiprtc_src_file
>
,
// NOLINT
std
::
string
,
// NOLINT
const
std
::
string
&
)
{
MIGRAPHX_THROW
(
"Not using hiprtc"
);
}
bool
is_hip_clang_compiler
()
bool
is_hip_clang_compiler
()
{
{
static
const
auto
result
=
ends_with
(
MIGRAPHX_STRINGIZE
(
MIGRAPHX_HIP_COMPILER
),
"clang++"
);
static
const
auto
result
=
ends_with
(
MIGRAPHX_STRINGIZE
(
MIGRAPHX_HIP_COMPILER
),
"clang++"
);
...
...
src/targets/gpu/fuse_mlir.cpp
View file @
2fc6b715
...
@@ -27,6 +27,7 @@
...
@@ -27,6 +27,7 @@
#include <migraphx/pass_manager.hpp>
#include <migraphx/pass_manager.hpp>
#include <migraphx/make_op.hpp>
#include <migraphx/make_op.hpp>
#include <migraphx/register_op.hpp>
#include <migraphx/register_op.hpp>
#include <migraphx/env.hpp>
namespace
migraphx
{
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
inline
namespace
MIGRAPHX_INLINE_NS
{
...
@@ -35,9 +36,13 @@ struct module;
...
@@ -35,9 +36,13 @@ struct module;
namespace
gpu
{
namespace
gpu
{
MIGRAPHX_DECLARE_ENV_VAR
(
MIGRAPHX_ENABLE_MLIR
);
#ifdef MIGRAPHX_MLIR
#ifdef MIGRAPHX_MLIR
struct
mlir_conv
struct
mlir_op
{
{
std
::
string
name
()
const
{
return
"gpu::mlir_op"
;
}
operation
op
=
make_op
(
"convolution"
);
operation
op
=
make_op
(
"convolution"
);
template
<
class
Self
,
class
F
>
template
<
class
Self
,
class
F
>
...
@@ -46,7 +51,6 @@ struct mlir_conv
...
@@ -46,7 +51,6 @@ struct mlir_conv
return
pack
(
f
(
self
.
op
,
"op"
));
return
pack
(
f
(
self
.
op
,
"op"
));
}
}
std
::
string
name
()
const
{
return
"gpu::mlir_conv"
;
}
shape
compute_shape
(
std
::
vector
<
shape
>
inputs
,
const
std
::
vector
<
module_ref
>&
mods
)
const
shape
compute_shape
(
std
::
vector
<
shape
>
inputs
,
const
std
::
vector
<
module_ref
>&
mods
)
const
{
{
check_shapes
{
inputs
,
*
this
}.
packed_or_broadcasted
();
check_shapes
{
inputs
,
*
this
}.
packed_or_broadcasted
();
...
@@ -58,7 +62,7 @@ struct mlir_conv
...
@@ -58,7 +62,7 @@ struct mlir_conv
return
op
.
compute_shape
({
inputs
[
n
-
2
],
inputs
[
n
-
1
]});
return
op
.
compute_shape
({
inputs
[
n
-
2
],
inputs
[
n
-
1
]});
}
}
};
};
MIGRAPHX_REGISTER_OP
(
mlir_
conv
);
MIGRAPHX_REGISTER_OP
(
mlir_
op
);
namespace
{
namespace
{
...
@@ -76,27 +80,27 @@ MIGRAPHX_PRED_MATCHER(is_mlir_conv, instruction_ref ins)
...
@@ -76,27 +80,27 @@ MIGRAPHX_PRED_MATCHER(is_mlir_conv, instruction_ref ins)
return
true
;
return
true
;
}
}
struct
find_
conv_pointwise
struct
find_
mlir_op
{
{
// Find a convolution followed by a pointwise operation.
auto
matcher
()
const
auto
matcher
()
const
{
{
auto
convolution
=
auto
dot_or_conv
=
match
::
skip
(
match
::
name
(
"contiguous"
))(
match
::
skip
(
match
::
name
(
"
contiguous"
))(
is_mlir_conv
().
bind
(
"
convolution
"
));
match
::
any_of
(
match
::
name
(
"
dot"
),
is_mlir_conv
()
)
.
bind
(
"
gemm_based_op
"
));
return
match
::
name
(
"pointwise"
)(
match
::
any_of
[
match
::
inputs
()](
convoluti
on
.
bind
(
"x"
)));
return
match
::
name
(
"pointwise"
)(
match
::
any_of
[
match
::
inputs
()](
dot_or_c
on
v
.
bind
(
"x"
)));
}
}
void
apply
(
module_pass_manager
&
mpm
,
const
match
::
matcher_result
&
r
)
const
void
apply
(
module_pass_manager
&
mpm
,
const
match
::
matcher_result
&
r
)
const
{
{
auto
ins
=
r
.
result
;
auto
ins
=
r
.
result
;
auto
conv_ins
=
r
.
instructions
[
"
convolution
"
];
auto
gemm_based_op
=
r
.
instructions
[
"
gemm_based_op
"
];
auto
x_ins
=
r
.
instructions
[
"x"
];
// input after contiguous
auto
x_ins
=
r
.
instructions
[
"x"
];
// input after contiguous
auto
*
pm
=
ins
->
module_inputs
().
front
();
auto
*
pm
=
ins
->
module_inputs
().
front
();
auto
names
=
pm
->
get_parameter_names
();
auto
names
=
pm
->
get_parameter_names
();
// Whitelist pointwise operators
// Whitelist pointwise operators
if
(
std
::
any_of
(
pm
->
begin
(),
pm
->
end
(),
[](
const
auto
&
i
)
{
if
(
std
::
any_of
(
pm
->
begin
(),
pm
->
end
(),
[](
const
auto
&
i
)
{
return
not
contains
({
"@literal"
,
"@param"
,
"@return"
,
"convolution"
,
"add"
,
"relu"
},
return
not
contains
(
i
.
name
());
{
"@literal"
,
"@param"
,
"@return"
,
"convolution"
,
"dot"
,
"add"
,
"relu"
},
i
.
name
());
}))
}))
return
;
return
;
// Only fuse with fp32/fp16
// Only fuse with fp32/fp16
...
@@ -110,10 +114,10 @@ struct find_conv_pointwise
...
@@ -110,10 +114,10 @@ struct find_conv_pointwise
mm
->
set_bypass
();
mm
->
set_bypass
();
std
::
unordered_map
<
instruction_ref
,
instruction_ref
>
param_map
;
std
::
unordered_map
<
instruction_ref
,
instruction_ref
>
param_map
;
auto
x
=
mm
->
add_parameter
(
"x"
+
std
::
to_string
(
names
.
size
()),
auto
x
=
mm
->
add_parameter
(
"x"
+
std
::
to_string
(
names
.
size
()),
conv_ins
->
inputs
().
at
(
0
)
->
get_shape
());
gemm_based_op
->
inputs
().
at
(
0
)
->
get_shape
());
auto
w
=
mm
->
add_parameter
(
"x"
+
std
::
to_string
(
names
.
size
()
+
1
),
auto
w
=
mm
->
add_parameter
(
"x"
+
std
::
to_string
(
names
.
size
()
+
1
),
conv_ins
->
inputs
().
at
(
1
)
->
get_shape
());
gemm_based_op
->
inputs
().
at
(
1
)
->
get_shape
());
auto
conv
=
mm
->
add_instruction
(
conv_ins
->
get_operator
(),
{
x
,
w
});
auto
conv
=
mm
->
add_instruction
(
gemm_based_op
->
get_operator
(),
{
x
,
w
});
std
::
transform
(
names
.
begin
(),
std
::
transform
(
names
.
begin
(),
names
.
end
(),
names
.
end
(),
ins
->
inputs
().
begin
(),
ins
->
inputs
().
begin
(),
...
@@ -130,12 +134,13 @@ struct find_conv_pointwise
...
@@ -130,12 +134,13 @@ struct find_conv_pointwise
std
::
copy_if
(
ins
->
inputs
().
begin
(),
std
::
copy_if
(
ins
->
inputs
().
begin
(),
ins
->
inputs
().
end
(),
ins
->
inputs
().
end
(),
std
::
back_inserter
(
inputs
),
std
::
back_inserter
(
inputs
),
[
&
](
auto
input
)
{
return
input
!=
conv_ins
;
});
[
&
](
auto
input
)
{
return
input
!=
gemm_based_op
;
});
inputs
.
insert
(
inputs
.
end
(),
conv_ins
->
inputs
().
begin
(),
conv_ins
->
inputs
().
end
());
inputs
.
insert
(
inputs
.
end
(),
gemm_based_op
->
inputs
().
begin
(),
gemm_based_op
->
inputs
().
end
());
mpm
.
get_module
().
replace_instruction
(
mpm
.
get_module
().
replace_instruction
(
ins
,
mlir_
conv
{
conv_ins
->
get_operator
()},
inputs
,
{
mm
});
ins
,
mlir_
op
{
gemm_based_op
->
get_operator
()},
inputs
,
{
mm
});
}
}
};
};
}
// namespace
}
// namespace
#endif
#endif
...
@@ -143,7 +148,17 @@ struct find_conv_pointwise
...
@@ -143,7 +148,17 @@ struct find_conv_pointwise
void
fuse_mlir
::
apply
(
module_pass_manager
&
mpm
)
const
void
fuse_mlir
::
apply
(
module_pass_manager
&
mpm
)
const
{
{
#ifdef MIGRAPHX_MLIR
#ifdef MIGRAPHX_MLIR
match
::
find_matches
(
mpm
,
find_conv_pointwise
{});
const
bool
mlir_enabled
=
enabled
(
MIGRAPHX_ENABLE_MLIR
{});
if
(
mlir_enabled
)
{
match
::
find_matches
(
mpm
,
find_mlir_op
{});
}
else
{
std
::
cerr
<<
"WARNING: MIGraphX built with MLIR but it is not enabled. Please set the env "
"var MIGRAPHX_ENABLE_MLIR to use MLIR kernel generator."
<<
std
::
endl
;
}
#else
#else
(
void
)
mpm
;
(
void
)
mpm
;
#endif
#endif
...
...
src/targets/gpu/hip.cpp
View file @
2fc6b715
...
@@ -189,8 +189,20 @@ argument register_on_gpu(const argument& arg)
...
@@ -189,8 +189,20 @@ argument register_on_gpu(const argument& arg)
argument
to_gpu
(
const
argument
&
arg
,
bool
host
)
argument
to_gpu
(
const
argument
&
arg
,
bool
host
)
{
{
auto
p
=
write_to_gpu
(
arg
.
data
(),
arg
.
get_shape
().
bytes
(),
host
);
argument
result
;
return
{
arg
.
get_shape
(),
p
};
arg
.
visit
(
[
&
](
auto
x
)
{
auto
p
=
write_to_gpu
(
arg
.
data
(),
arg
.
get_shape
().
bytes
(),
host
);
result
=
{
x
.
get_shape
(),
p
};
},
[
&
](
const
auto
&
xs
)
{
std
::
vector
<
argument
>
args
;
std
::
transform
(
xs
.
begin
(),
xs
.
end
(),
std
::
back_inserter
(
args
),
[
&
](
auto
x
)
{
return
to_gpu
(
x
,
host
);
});
result
=
argument
{
args
};
});
return
result
;
}
}
argument
from_gpu
(
const
argument
&
arg
)
argument
from_gpu
(
const
argument
&
arg
)
...
...
src/targets/gpu/hiprtc/CMakeLists.txt
0 → 100644
View file @
2fc6b715
#####################################################################################
# The MIT License (MIT)
#
# Copyright (c) 2015-2022 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.
#####################################################################################
add_executable
(
migraphx-hiprtc-driver
main.cpp
)
rocm_clang_tidy_check
(
migraphx-hiprtc-driver
)
target_link_libraries
(
migraphx-hiprtc-driver PRIVATE migraphx_gpu
)
add_dependencies
(
migraphx_all_targets migraphx-hiprtc-driver
)
rocm_install_targets
(
TARGETS migraphx-hiprtc-driver
)
src/targets/gpu/hiprtc/main.cpp
0 → 100644
View file @
2fc6b715
/*
* The MIT License (MIT)
*
* Copyright (c) 2015-2022 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.
*/
#include <migraphx/gpu/compile_hip.hpp>
#include <migraphx/serialize.hpp>
#include <migraphx/value.hpp>
#include <migraphx/msgpack.hpp>
#include <migraphx/file_buffer.hpp>
#include <migraphx/ranges.hpp>
#include <iostream>
#include <cstring>
std
::
vector
<
char
>
read_stdin
()
{
std
::
vector
<
char
>
result
;
std
::
array
<
char
,
1024
>
buffer
;
std
::
size_t
len
=
0
;
while
((
len
=
std
::
fread
(
buffer
.
data
(),
1
,
buffer
.
size
(),
stdin
))
>
0
)
{
if
(
std
::
ferror
(
stdin
)
!=
0
and
std
::
feof
(
stdin
)
==
0
)
MIGRAPHX_THROW
(
std
::
strerror
(
errno
));
result
.
insert
(
result
.
end
(),
buffer
.
data
(),
buffer
.
data
()
+
len
);
}
return
result
;
}
int
main
(
int
argc
,
char
const
*
argv
[])
{
if
(
argc
<
2
or
migraphx
::
contains
({
"-h"
,
"--help"
,
"-v"
,
"--version"
},
std
::
string
(
argv
[
1
])))
{
std
::
cout
<<
"USAGE:"
<<
std
::
endl
;
std
::
cout
<<
" "
;
std
::
cout
<<
"Used internally by migraphx to compile hip programs out-of-process."
<<
std
::
endl
;
std
::
exit
(
0
);
}
std
::
string
output_name
=
argv
[
1
];
auto
v
=
migraphx
::
from_msgpack
(
read_stdin
());
std
::
vector
<
migraphx
::
gpu
::
hiprtc_src_file
>
srcs
;
migraphx
::
from_value
(
v
.
at
(
"srcs"
),
srcs
);
auto
out
=
migraphx
::
gpu
::
compile_hip_src_with_hiprtc
(
std
::
move
(
srcs
),
v
.
at
(
"params"
).
to
<
std
::
string
>
(),
v
.
at
(
"arch"
).
to
<
std
::
string
>
());
if
(
not
out
.
empty
())
migraphx
::
write_buffer
(
output_name
,
out
.
front
());
}
src/targets/gpu/include/migraphx/gpu/compile_gen.hpp
View file @
2fc6b715
...
@@ -26,6 +26,7 @@
...
@@ -26,6 +26,7 @@
#include <migraphx/config.hpp>
#include <migraphx/config.hpp>
#include <migraphx/module_ref.hpp>
#include <migraphx/module_ref.hpp>
#include <migraphx/instruction_ref.hpp>
#include <string>
#include <string>
#include <unordered_map>
#include <unordered_map>
#include <vector>
#include <vector>
...
@@ -34,6 +35,7 @@ namespace migraphx {
...
@@ -34,6 +35,7 @@ namespace migraphx {
inline
namespace
MIGRAPHX_INLINE_NS
{
inline
namespace
MIGRAPHX_INLINE_NS
{
struct
shape
;
struct
shape
;
struct
operation
;
namespace
gpu
{
namespace
gpu
{
...
@@ -72,8 +74,23 @@ std::string make_transformer_args(Ts... xs)
...
@@ -72,8 +74,23 @@ std::string make_transformer_args(Ts... xs)
std
::
string
generate_pointwise
(
const
module
&
pm
,
const
std
::
string
&
name
);
std
::
string
generate_pointwise
(
const
module
&
pm
,
const
std
::
string
&
name
);
std
::
string
generate_reduce
(
const
module
&
m
,
const
std
::
string
&
name
);
std
::
string
generate_name_from_ops
(
const
module
&
m
);
std
::
string
generate_name_from_ops
(
const
module
&
m
);
struct
reduce_op
{
std
::
string
input
=
""
;
std
::
string
reduction
=
""
;
std
::
string
init
=
"0"
;
std
::
string
read
=
"op::id{}"
;
std
::
string
write
=
"op::id{}"
;
void
set
(
instruction_ref
ins
,
const
operation
&
op
);
std
::
string
str
()
const
;
static
std
::
string
generate
(
instruction_ref
ins
,
const
std
::
string
&
x
);
};
}
// namespace gen
}
// namespace gen
}
// namespace gpu
}
// namespace gpu
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace MIGRAPHX_INLINE_NS
...
...
Prev
1
2
3
4
5
6
7
8
9
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