Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions include/tim/experimental/trace/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

ApiTracer is a header only library provides macros and template functions to trace the C++ object-oriented programs, it not only trace the call-stacks, but also trace the C++ Apis runtime parameters. With the trace log and binary, it's convenient to replay the program execute scene, which helps developer to reproduce bugs and debug.

## Usage
There is a simple case using api trace in `src/tim/vx/graph_test.cc` which named trace_test, follow those steps:
1. You need set the cmake option `-DTIM_VX_ENABLE_API_TRACE=1` at first, and rebuild;
2. Setting up the run time environments for tim-vx, then execute `$build/install/bin/unit-test --gtest_filter=*trace_test*`;
3. `trace_log.cc` and `trace_bin.bin` will generate in the workspace, first one is the tim-vx code record the all api calls and function's runtime parameters, second one is serializable data like std::vector data. You can set the environment `TRACE_DUMP_PREFIX` to control will to dump those file;
4. Copy `trace_log.cc` to the root of tim-vx source code, and rename it with `trace_log.rpl.cc`, then follow the guide in `src/tim/vx/graph_test.cc`.
5. Rebuild unit-test, execute `$build/install/bin/unit-test --gtest_filter=*replay_test*`, you will get the exactly same result with traced execution.

## Coding work
ApiTracer was implemented by warp original Apis in traced Apis, they got same class names, same function names, but different namespace. So you need implement the traced Apis for specific programs at first.

Expand Down Expand Up @@ -41,6 +49,8 @@ namespace trace {
struct TensorSpec : public TraceClassBase<target::TensorSpec> {
DEF_CONSTRUCTOR(TensorSpec)

// define constructor with boost seq arguments, do not need specific the
// parameter name, it's auto generate like param_0, param_1, ...
DEF_CONSTRUCTOR(TensorSpec, ((DataType))
((const ShapeType&))
((TensorAttribute))
Expand All @@ -55,6 +65,10 @@ struct TensorSpec : public TraceClassBase<target::TensorSpec> {
DEF_CONSTRUCTOR(TensorSpec, ((const TensorSpec&))
)

// Using DEF_TRACED_API is a more radical way, you only need specific the api
// return type and the api name, api arguments will automatically match by the
// template arguments variadic.
// But it's not fully around now, see next section of it's restrict.
DEF_TRACED_API(TensorSpec&, operator=)

DEF_TRACED_API(TensorSpec&, SetDataType)
Expand All @@ -70,13 +84,27 @@ struct TensorSpec : public TraceClassBase<target::TensorSpec> {
### Macros definition
- DEF_CONSTRUCTOR(class_name, optional_arguments_description, optional_macro_to_handle_pointer):
arguments_description must follow the format `((arg0))((arg1)(arg1_default_value))`

- DEF_TRACED_API(return_type, member_function_name, optional_lambda_to_handle_pointer):
this macro can define most member functions, except:
- function with default parameters
- static functions
- readonly functions

- DEF_MEMFN_SP(return_type_removed_shared_pointer, member_function_name,optional_arguments_description, optional_macro_to_handle_pointer):
- DEF_MEMFN_SP(return_type_removed_shared_pointer, member_function_name, optional_arguments_description, optional_macro_to_handle_pointer):
the macro can define those functions with `shared_ptr<TracedClass>`
- DEF_MEMFN(return_type, member_function_name, optional_arguments_description, optional_macro_to_handle_pointer)
- DEF_INPLACE_MEMFN(member_function_name, optional_arguments_description, optional_macro_to_handle_pointer)

- DEF_MEMFN(return_type, member_function_name, optional_arguments_description, optional_macro_to_handle_pointer):
to define normal member functions

- DEF_INPLACE_MEMFN(member_function_name, optional_arguments_description, optional_macro_to_handle_pointer):
to define those apis retval is the object itself

- DEF_SIMPLE_UNTRACED_API(retval, api_name):
to define those apis without argument and have no side effect on the graph compile and execution, like `Tensor::GetId()`

## Meta programming reference
api trace utilize some meta programming tricks, here are some reference:
1. Boost C/C++ preprocessor - https://www.boost.org/doc/libs/1_82_0/libs/preprocessor/doc/index.html
2. std meta programming library - https://en.cppreference.com/w/cpp/meta
3. Boost hana meta programming library - https://www.boost.org/doc/libs/1_82_0/libs/hana/doc/html/index.html
4 changes: 2 additions & 2 deletions include/tim/experimental/trace/tracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ static const char* target_namespace_name_ = TARGET_NAMESPACE_NAME;
std::string obj_name = \
Tracer::allocate_obj_name(Tracer::get_obj_prefix(#class_name)); \
Tracer::logging_msg("auto %s = %s::%s();\n", obj_name.c_str(), \
target_namespace_name_, __FUNCTION__); \
target_namespace_name_, __FUNCTION__); \
impl_ = std::make_shared<target::class_name>(); \
Tracer::insert_traced_obj( \
static_cast<void*>(impl_.get()), static_cast<void*>(this)); \
Expand Down Expand Up @@ -873,7 +873,7 @@ static const char* target_namespace_name_ = TARGET_NAMESPACE_NAME;
Args... params) { \
std::string this_obj_name = TraceGetObjName(); \
Tracer::push_back_msg_cache( \
this_obj_name + "->" + __FUNCTION__ + "("); \
this_obj_name + "." + __FUNCTION__ + "("); \
Tracer::clear_params_log_cache(); \
boost::hana::tuple<Args...> params_tuple = {params...}; \
boost::hana::for_each(params_tuple, [&] (auto x) { \
Expand Down
37 changes: 22 additions & 15 deletions include/tim/experimental/trace/tvx/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ struct Graph : public TraceClassBase<target::Graph> {
Tracer::amend_last_msg_cache(");\n");
Tracer::msg_cache_sync_to_file();

// ToDo: the feature to use fake network weights need further refine.
#if 1 /* if use fake input data */
if (param_0.TraceGetImpl().GetTensorAttribute() ==
TensorAttribute::TRANSIENT && param_1 == nullptr) {
TensorAttribute::CONSTANT && param_1 == nullptr) {
auto fake_vec_name = Tracer::allocate_obj_name("fake_vec_");
switch (param_0.TraceGetImpl().GetDataType()) {
case DataType::INT32:
Expand Down Expand Up @@ -313,7 +314,7 @@ struct Graph : public TraceClassBase<target::Graph> {

DEF_TRACED_API(void, UpdateTensorProducerMap)

// DEF_TRACED_API(cosnt std::vector<std::shared_ptr<Operation>>, GetConsumersOp)
// DEF_TRACED_API(const std::vector<std::shared_ptr<Operation>>, GetConsumersOp)

// DEF_TRACED_API(std::shared_ptr<Operation>, GetProducerOp)

Expand All @@ -329,21 +330,27 @@ struct Graph : public TraceClassBase<target::Graph> {
std::vector<TensorSpec> spec_keeper_;

DECL_CREATE_OPS(TVX_OPS_SEQ)
DECL_CREATE_OP_IMPL_(_, _, Pad)
DECL_CREATE_OP_IMPL_(_, _, PadV2)
DECL_CREATE_OP_IMPL_(_, _, Pad) // there are enums defined in the op
DECL_CREATE_OP_IMPL_(_, _, PadV2) // there are enums defined in the op

};
#define SPECIAL_MACRO_(params) \
std::string buf_name = Tracer::allocate_obj_name("nbg_buf_vec_"); \
FILE* nbg_dumped = fopen("network_binary_graph.nb", "r"); \
fseek(nbg_dumped, 0L, SEEK_END); \
uint32_t count = ftell(nbg_dumped); \
fclose(nbg_dumped); \
uint32_t offset = Tracer::dump_data( \
BOOST_PP_SEQ_ELEM(0, params), sizeof(char), count); \
Tracer::insert_before_last_msg_cache("std::vector<char> " + buf_name + \
" = trace::Replayer::get_vector<char>(" + std::to_string(offset) + \
"," + std::to_string(count) + ");\n"); \
// For the NBG op, has no good enough way to get the nbg buffer size in the
// scope of `CreateOperation<tim::vx::ops::NBG>(buf, inp_num, out_num)`, so
// we enable `export VIV_VX_ENABLE_DUMP_NBG=1` to dump nbg at first, then read
// it's size.
// The best solution is make nbg buf self-analytic the size.
// ToDo: push nbg team provide an api to self-analytic the nbg size.
#define SPECIAL_MACRO_(params) \
std::string buf_name = Tracer::allocate_obj_name("nbg_buf_vec_"); \
FILE* nbg_dumped = fopen("network_binary_graph.nb", "r"); \
fseek(nbg_dumped, 0L, SEEK_END); \
uint32_t count = ftell(nbg_dumped); \
fclose(nbg_dumped); \
uint32_t offset = Tracer::dump_data( \
BOOST_PP_SEQ_ELEM(0, params), sizeof(char), count); \
Tracer::insert_before_last_msg_cache("std::vector<char> " + buf_name + \
" = trace::Replayer::get_vector<char>(" + std::to_string(offset) + \
"," + std::to_string(count) + ");\n"); \
Tracer::insert_params_log_cache(buf_name + ".data()", 0);

SPECIALIZATION_CREATE_OP(NBG, ((const char*))((size_t))((size_t)),
Expand Down
7 changes: 7 additions & 0 deletions include/tim/experimental/trace/tvx/tensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct Quantization : public TraceClassBase<target::Quantization> {
DEF_TRACED_API(QuantType&, Type)

// DEF_TRACED_API(const QuantType&, Type)
// lack of macro to def readonly apis
template <class R = const QuantType &, class... Args>
typename std::enable_if_t<is_not_traced_obj_like<R>::value, R> Type(
Args... params) const {
Expand All @@ -70,9 +71,12 @@ struct Quantization : public TraceClassBase<target::Quantization> {
return boost::hana::unpack(params_impl, api_impl);
}

DEF_TRACED_API(Quantization&, SetType)

DEF_TRACED_API(int32_t&, ChannelDim)

// DEF_TRACED_API(const int32_t&, ChannelDim)
// lack of macro to def readonly apis
template <class R = const int32_t &, class... Args>
typename std::enable_if_t<is_not_traced_obj_like<R>::value, R> ChannelDim(
Args... params) const {
Expand All @@ -95,9 +99,12 @@ struct Quantization : public TraceClassBase<target::Quantization> {

DEF_TRACED_API(std::vector<float>&, Scales)

DEF_TRACED_API(Quantization&, SetScales)

DEF_TRACED_API(std::vector<int32_t>&, ZeroPoints)

// DEF_TRACED_API(const std::vector<int32_t>&, ZeroPoints)
// lack of macro to def readonly apis
template <class R = const std::vector<int32_t> &, class... Args>
typename std::enable_if_t<is_not_traced_obj_like<R>::value, R> ZeroPoints(
Args... params) const {
Expand Down
15 changes: 12 additions & 3 deletions src/tim/vx/graph_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,16 @@ TEST(graph, gen_binary_graph_with_simple_add) {
}

#ifdef ENABLE_API_TRACE
#define API_REPLAYER_IMPLEMENTATION
#define API_TRACER_IMPLEMENTATION
#define API_REPLAYER_IMPLEMENTATION // enable static members in api replayer
#define API_TRACER_IMPLEMENTATION // enable static members in api tracer
#define TARGET_NAMESPACE_NAME "tim::vx"
#include "tim/experimental/trace/trace_tvx.h"

namespace tvx = trace;

TEST(graph, trace_test) {
// Replace all tim::vx name space with tvx, tvx can be alias of trace
// namespace.
auto ctx = tvx::Context::Create();
auto graph = ctx->CreateGraph();

Expand Down Expand Up @@ -135,6 +137,13 @@ TEST(graph, trace_test) {
EXPECT_TRUE(output_t0->CopyDataFromTensor(output.data()));
EXPECT_EQ(output, expected_out);

// extra test for Quantization apis
tvx::Quantization quant0;
quant0.SetType(tvx::QuantType::ASYMMETRIC);
quant0.SetChannelDim(1);
quant0.SetScales(std::vector<float>({0.2, 0.3}));
quant0.SetZeroPoints(std::vector<int32_t>({2, 3}));

}

TEST(graph, replay_test) {
Expand All @@ -144,7 +153,7 @@ TEST(graph, replay_test) {
* rename with trace_bin.rpl.bin
*/

#include "trace_log.rpl.cc"
// #include "trace_log.rpl.cc"
// manual compile and run the selected graph, like:
// graph_12->Compile();
// graph_12->Run();
Expand Down