This page records my notes about exploring MIGraphX, henceforth MGX.
Overview §
The hierarchy of the model abstraction in MGX is shown as below:
flowchart LR
A[program] --> B[modules]
B --> C[instructions]
C --> D[op]
When reading a ONNX (or other formats) model, MGX will try to parse and represent it with the abstraction.
MGX will first try to finalize all operations in a program (i.e., all of its modules and all instructions in the modules), then invoke the compute
function defined in each operation to evaluate the model.
Parse and compile an ONNX model §
Compiler §
Pass §
Evaluate and run a compiled program §
After creating a program, users can now call the eval
function to
Quick ref: common data structures §
Type erasure §
Many data structures in MGX follows the type erasure design pattern to hide the type information and expose the unified interfaces to outside.
Usually, the xxx
struct contains the xxx_impl
unique pointer with the real data member stored.
Common code snippets §
template < class T , class F , F f > // NOLINT
using manage_ptr = std :: unique_ptr < T , manage_deleter < F , f >>;
#define MIGRAPHX_MANAGE_PTR ( T , F ) \
migraphx::manage_ptr<std::remove_pointer_t<T>, decltype(&F), &F>
Macro MIGRAPHX_MANAGE_PTR
will return a std::unique_ptr
with the destructor set.
Shapes and arguments §
struct MIGRAPHX_EXPORT shape
{
shape ( type_t t , std :: vector < std :: size_t > l );
shape ( type_t t , std :: vector < std :: size_t > l , std :: vector < std :: size_t > s );
// for dynamic tensor shape
struct MIGRAPHX_EXPORT dynamic_dimension
{
std :: size_t min = 0 ;
std :: size_t max = 0 ;
std ::set < std :: size_t> optimals{};
};
std :: size_t elements () const ;
std :: size_t bytes () const ;
};
shape
is described as dimension sizes and optional stride sizes.
In additional to fixed shape tensors, MGX also supports shapes with dynamic dimensions (with min and max values alongside the dimension).
struct MIGRAPHX_EXPORT argument : raw_data< argument >
{
template < class T >
argument ( shape s , T * d )
: m_shape ( std :: move (s))
{
assign_buffer ([ d ] { return reinterpret_cast<char*> (d); });
}
template < class T >
argument ( shape s , std :: shared_ptr < T > d )
: m_shape ( std :: move (s))
{
assign_buffer ([ d ] { return reinterpret_cast<char*> (d. get ()); });
}
private:
void assign_buffer ( std :: function < char* ()> d );
struct data_t
{
std ::function <char* () > get = nullptr ;
std ::vector < data_t > sub = {};
data_t share () const ;
static data_t from_args ( const std :: vector < argument > & args );
};
argument ( const shape & s , const data_t & d );
shape m_shape;
data_t m_data{};
};
argument
is the structure to place the substantial data described by shape
.
Instructions and op §
using instruction_ref = std :: list < instruction >:: iterator ;
// members in an instruction
struct MIGRAPHX_EXPORT instruction
{
operation op;
shape result{};
std ::vector < instruction_ref > output;
std ::vector < instruction_ref > arguments;
std ::vector < module_ref > module_args;
literal lit;
bool normalized = false ;
std :: size_t target_id = 0 ;
};
Modules §
struct MIGRAPHX_EXPORT module
{
std ::unique_ptr < module_impl > impl;
};
struct module_impl
{
// A list is used to keep references to an instruction stable
std ::list < instruction > instructions;
std ::unordered_set < instruction *> instruction_set;
std ::string name;
uint32_t nparams = 0 ;
bool bypass = false ;
};
Programs §
struct MIGRAPHX_EXPORT program
{
std ::unique_ptr < program_impl > impl;
};
struct program_impl
{
// A map is used to keep references to modules of the program
std ::unordered_map < std ::string, module> modules;
std ::vector < context > contexts;
std ::vector < target > targets;
};
Target and context §
struct context
{
context ( std :: size_t device_id = 0 , std :: size_t n = value_of (MIGRAPHX_NSTREAMS{}, 1 ))
: current_device ( std :: make_shared < hip_device >(device_id, n)),
begin_event ( create_event ()),
finish_event ( create_event ())
{
}
private:
// TODO: Make this a vector to support multiple devices
std ::shared_ptr < hip_device > current_device;
std ::vector < shared < hip_event_ptr >> events;
bool exhaustive_tune = false ;
bool measure_perf = false ;
// for event perf timing
shared < hip_event_ptr > start_event = nullptr ;
shared < hip_event_ptr > stop_event = nullptr ;
// for stream syncronization
shared < hip_event_ptr > begin_event = nullptr ;
shared < hip_event_ptr > finish_event = nullptr ;
problem_cache pc{};
};
context
is an abstraction for managing HIP device (GPU, stream and event).
struct MIGRAPHX_GPU_EXPORT target
{
std :: string name () const ;
std :: vector < pass > get_passes ( migraphx :: context & gctx , const compile_options & options ) const ;
migraphx :: context get_context () const ;
argument copy_to ( const argument & arg ) const ;
argument copy_from ( const argument & arg ) const ;
argument allocate ( const shape & s ) const ;
};
target
rather defines the possible “actions” (both compile time and execution time) taken on the device.
Ranges §
template < class Iterator >
struct iterator_range
{
Iterator start;
Iterator last;
Iterator begin () const { return start; }
Iterator end () const { return last; }
};
template < class Iterator , MIGRAPHX_REQUIRES ( not std ::is_integral < Iterator > {})>
iterator_range < Iterator > range ( Iterator start , Iterator last )
{
return {start, last};
}
inline iterator_range < iota_iterator > range ( std :: ptrdiff_t start , std :: ptrdiff_t last )
{
return {{start, {}}, {last, {}}};
}
inline iterator_range < iota_iterator > range ( std :: ptrdiff_t last ) { return range ( 0 , last); }
Use case: for (auto i : range(N)) {}
, which will iterative i
from 0
to N - 1
.
Useful links §