Skip to content

Commit

Permalink
Fix Serialization Issues, Improve RBM Performance, and Implement… (#32)
Browse files Browse the repository at this point in the history
* Fix Serialization Issues, Improve RBM Performance, and Implement… (#31)

* add performance collector

* manual instrument performance measuring

* mitigate measure global instance issue

* revert instrumentation

* fix performance measure

* instrumentation of eigen assignment blocks

* improve transpose

* fix grader and coorder

* fix ade and perf tests

* move initialization out of mlp + remove unneeded bazel flags

* start moving mlp functionality over to model layer + layer tagging

* save and load tags

* add loading layer and tags

* fix save and load

* update rocnnet pybind wrapper, remove mlp

* update rbm demo with dense

* implement rbm layer

* fix rbm

* implement bernoulli rbm

* fix bernoulli rbmtrainer

* fix ead serialization

* fix ead serialization and update saves

* add preliminary kpartition

* implement partitioning and async session

* fix async session

* fix parallel session

* update pbm and update prebuilt-models accordingly

* restore documents

* move docs out and make it non-essential for travis tests

* increase jobs in tests.sh

* fix opt test in tests.sh

* reduce ead build time

* also build coverage with fast ead

* revert to filtered lcov

* update cppkg

* update inspector with latest cppkg
  • Loading branch information
raggledodo committed Sep 3, 2019
1 parent 4ecb961 commit ac63b42
Show file tree
Hide file tree
Showing 1,918 changed files with 29,717 additions and 96,697 deletions.
9 changes: 5 additions & 4 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

# ------ ALL SETTINGS ------

run --incompatible_disallow_dict_plus=false --incompatible_disable_deprecated_attr_params=false --incompatible_depset_is_not_iterable=false --incompatible_no_support_tools_in_action_inputs=false
build --incompatible_disallow_dict_plus=false --incompatible_disable_deprecated_attr_params=false --incompatible_depset_is_not_iterable=false --incompatible_no_support_tools_in_action_inputs=false
test --incompatible_disallow_dict_plus=false --incompatible_disable_deprecated_attr_params=false --incompatible_depset_is_not_iterable=false --incompatible_no_support_tools_in_action_inputs=false
coverage --incompatible_disallow_dict_plus=false --incompatible_disable_deprecated_attr_params=false --incompatible_depset_is_not_iterable=false --incompatible_no_support_tools_in_action_inputs=false
run --incompatible_depset_is_not_iterable=false --jobs=2
build --incompatible_depset_is_not_iterable=false --jobs=2
test --incompatible_depset_is_not_iterable=false --jobs=2
coverage --incompatible_depset_is_not_iterable=false --jobs=2

# ------ OPTIMIZATION ------

Expand All @@ -31,6 +31,7 @@ coverage:cc_coverage --instrument_test_targets --experimental_cc_coverage --comb
run:valgrind --run_under="valgrind --leak-check=full"
test:valgrind --run_under="valgrind --leak-check=full"

run:asan --linkopt -fsanitize=address
test:asan --linkopt -fsanitize=address

# === GTESTS ===
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,7 @@ html/
!.gitmodules
!.astylerc
!.bazelrc
!models/*.pbx
!models/**/*.pbx
!rocnnet/pretrained/*.pbx
!rocnnet/notebooks/*.pbx
17 changes: 17 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,20 @@ filegroup(
"BUILD.bazel",
],
)

filegroup(
name = "test_models",
srcs = glob([
"models/test/*.pbx",
"models/test/*.txt",
"models/test/*.json",
])
)

filegroup(
name = "models",
srcs = glob([
"models/*.pbx",
"models/*.json",
])
)
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ COVERAGE_INFO_FILE := bazel-out/_coverage/_coverage_report.dat

COVERAGE_IGNORE := 'external/*' '**/test/*' 'testutil/*' '**/genfiles/*' 'dbg/*'

CCOVER := bazel coverage --config asan --action_env="ASAN_OPTIONS=detect_leaks=0" --config gtest --config cc_coverage
CCOVER := bazel coverage --config asan --action_env="ASAN_OPTIONS=detect_leaks=0" --config gtest --config cc_coverage --define EAD_CFG=MIN

ADE_TEST := //ade:test

Expand All @@ -26,8 +26,8 @@ print_vars:
rocnnet_py_build:
bazel build --config $(CC)_eigen_optimal //rocnnet:rocnnet_py

rocnnet_py_export: rocnnet_py_build
cp -f bazel-bin/rocnnet/*.so rocnnet/notebooks/rocnnet
rocnnet_py_export: bazel-bin/rocnnet/rocnnet.so bazel-bin/ead/tenncor.so bazel-bin/ead/ead.so
cp -f bazel-bin/rocnnet/rocnnet.so rocnnet/notebooks/rocnnet
cp -f bazel-bin/ead/*.so rocnnet/notebooks/ead


Expand Down
4 changes: 2 additions & 2 deletions ade/functor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct Functor final : public iFunctor
{
if (0 == args.size())
{
logs::fatalf("cannot perform %s with no arguments",
logs::fatalf("cannot perform `%s` with no arguments",
opcode.name_.c_str());
}

Expand All @@ -32,7 +32,7 @@ struct Functor final : public iFunctor
Shape ishape = args[i].shape();
if (false == ishape.compatible_after(shape, 0))
{
logs::fatalf("cannot perform %s with incompatible shapes %s "
logs::fatalf("cannot perform `%s` with incompatible shapes %s "
"and %s", opcode.name_.c_str(), shape.to_string().c_str(),
ishape.to_string().c_str());
}
Expand Down
2 changes: 1 addition & 1 deletion ade/grad_def.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ struct iGradientBuilder
std::list<iFunctor*> parents;
std::transform(pathmap.begin(), pathmap.end(),
std::back_inserter(parents),
[](std::pair<iTensor*,std::unordered_set<size_t>> parent)
[](std::pair<iTensor*,std::vector<size_t>> parent)
{
return static_cast<iFunctor*>(parent.first);
});
Expand Down
3 changes: 3 additions & 0 deletions ade/ileaf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ struct iLeaf : public iTensor, public iData
{
visiter.visit(this);
}

/// Return true if leaf is immutable, otherwise false
virtual bool is_const (void) const = 0;
};

/// Leaf smart pointer
Expand Down
20 changes: 11 additions & 9 deletions ade/shape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ namespace ade
/// Type used for shape rank
using RankT = uint8_t;

// /// Type used for shape dimension
// #if !defined(SDIM_BYTES) || SDIM_BYTES <= 1
// using DimT = uint8_t;
// #elif SDIM_BYTES <= 2
#define SDIM_BYTES 2

/// Type used for shape dimension
#if !defined(SDIM_BYTES) || SDIM_BYTES <= 1
using DimT = uint8_t;
#elif SDIM_BYTES <= 2
using DimT = uint16_t;
// #elif SDIM_BYTES <= 4
// using DimT = uint32_t;
// #else
// using DimT = uint64_t;
// #endif
#elif SDIM_BYTES <= 4
using DimT = uint32_t;
#else
using DimT = uint64_t;
#endif

/// Type used for coordinate dimensions
using CDimT = double;
Expand Down
5 changes: 3 additions & 2 deletions ade/src/coord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ CoordptrT reduce (RankT rank, std::vector<DimT> red)
logs::fatalf("cannot reduce shape rank %d beyond rank_cap with n_red %d",
rank, n_red);
}
if (0 == n_red)
if (0 == n_red || std::all_of(red.begin(), red.end(),
[](DimT d) { return 1 == d; }))
{
logs::warn("reducing with empty vector ... will do nothing");
logs::warn("reducing scalar ... will do nothing");
return identity;
}

Expand Down
5 changes: 5 additions & 0 deletions ade/test/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ struct MockTensor : public ade::iLeaf
return 0;
}

bool is_const (void) const override
{
return true;
}

ade::Shape shape_;
};

Expand Down
2 changes: 1 addition & 1 deletion ade/test/test_coord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ TEST(COORD, Reduce)
rank + 1, red.size());
EXPECT_FATAL(ade::reduce(rank + 1, red), fatalmsg.c_str());

EXPECT_WARN(ade::reduce(0, {}), "reducing with empty vector ... will do nothing");
EXPECT_WARN(ade::reduce(0, {}), "reducing scalar ... will do nothing");
}


Expand Down
4 changes: 2 additions & 2 deletions ade/test/test_functor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ TEST(FUNCTOR, Shapes)
EXPECT_ARREQ(shape, gotshape);

EXPECT_FATAL(ade::Functor::get(ade::Opcode{"MOCK", 0}, {}),
"cannot perform MOCK with no arguments");
"cannot perform `MOCK` with no arguments");

std::string fatalmsg = fmts::sprintf(
"cannot perform MOCK with incompatible shapes %s and %s",
"cannot perform `MOCK` with incompatible shapes %s and %s",
shape.to_string().c_str(), badshape.to_string().c_str());
EXPECT_FATAL(ade::Functor::get(ade::Opcode{"MOCK", 0}, {
ade::identity_map(leaf),
Expand Down
70 changes: 35 additions & 35 deletions ade/test/test_grad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct MockGradientBuilder final : public ade::iGradientBuilder
return ade::TensptrT(ade::Functor::get(ade::Opcode{"FUNC4", 3},
{op->get_children()[arg_idx]}));
}
return ade::TensptrT(new LabelledMockTensor("other", op->shape()));;
return ade::TensptrT(new LabelledMockTensor("other", op->shape()));
}

ade::TensptrT chain_rule (ade::FuncptrT op, const ade::TensptrT& local_der,
Expand Down Expand Up @@ -135,20 +135,20 @@ TEST(GRAD, BuilderStandardV)
"(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(1[94\\78\\70\\82\\62\\29\\38\\1])",
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:leaf2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(constant:1[94\\78\\70\\82\\62\\29\\38\\1])",
gl);

EXPECT_GRAPHEQ(
"(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(leaf2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(1[94\\78\\70\\82\\62\\29\\38\\1])",
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:leaf2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:leaf2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(constant:1[94\\78\\70\\82\\62\\29\\38\\1])",
gl2);
}

Expand Down Expand Up @@ -180,32 +180,32 @@ TEST(GRAD, BuilderDiamond)
" `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC4[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(1[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:1[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(1[94\\78\\70\\82\\62\\29\\38\\1])",
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(constant:1[94\\78\\70\\82\\62\\29\\38\\1])",
gl);
}

Expand Down Expand Up @@ -237,45 +237,45 @@ TEST(GRAD, TadPole)
"(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC4[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(1[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:1[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC4[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(FUNC4[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC2[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC3[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(FUNC[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | | `--(leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(1[94\\78\\70\\82\\62\\29\\38\\1])",
" | | `--(constant:leaf[94\\78\\70\\82\\62\\29\\38\\1])\n"
" | `--(constant:other[94\\78\\70\\82\\62\\29\\38\\1])\n"
" `--(constant:1[94\\78\\70\\82\\62\\29\\38\\1])",
gl);
}

Expand Down
8 changes: 4 additions & 4 deletions ade/test/test_traveler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ TEST(TRAVELER, PathFinder)

{
ASSERT_HAS(finder.parents_, g.get());
EXPECT_HAS(finder.parents_[g.get()], 1);
EXPECT_ARRHAS(finder.parents_[g.get()], 1);

ASSERT_HAS(finder.parents_, f.get());
EXPECT_HAS(finder.parents_[f.get()], 0);
EXPECT_ARRHAS(finder.parents_[f.get()], 0);
}

finder.parents_.clear();
Expand All @@ -72,15 +72,15 @@ TEST(TRAVELER, PathFinder)
ASSERT_HASNOT(finder.parents_, g.get());

ASSERT_HAS(finder.parents_, f.get());
EXPECT_HAS(finder.parents_[f.get()], 0);
EXPECT_ARRHAS(finder.parents_[f.get()], 0);
}

ade::PathFinder finder2(c.get());
g->accept(finder2);

{
ASSERT_HAS(finder2.parents_, g.get());
EXPECT_HAS(finder2.parents_[g.get()], 0);
EXPECT_ARRHAS(finder2.parents_[g.get()], 0);
}

finder2.parents_.clear();
Expand Down
9 changes: 3 additions & 6 deletions ade/traveler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,15 @@ struct GraphStat final : public iTraveler
std::unordered_map<iTensor*,estd::NumRange<size_t>> graphsize_;
};

using ParentMapT = std::unordered_map<iTensor*,std::vector<size_t>>;

/// Traveler that paints paths to a target tensor
/// All nodes in the path are added as keys to the parents_ map with the values
/// being a boolean vector denoting nodes leading to target
/// For a boolean value x at index i in mapped vector,
/// x is true if the ith child leads to target
struct PathFinder final : public iTraveler
{
/// Type for mapping function nodes in path to boolean vector
using ParentMapT = std::unordered_map<iTensor*,std::unordered_set<size_t>>;

PathFinder (const iTensor* target) : target_(target) {}

/// Implementation of iTraveler
Expand Down Expand Up @@ -150,7 +149,7 @@ struct PathFinder final : public iTraveler
}
if (false == path.empty())
{
parents_[func] = path;
parents_[func] = std::vector<size_t>(path.begin(), path.end());
}
}
}
Expand All @@ -165,8 +164,6 @@ struct PathFinder final : public iTraveler
/// Traveler that for each child tracks the relationship to all parents
struct ParentFinder final : public iTraveler
{
using ParentMapT = std::unordered_map<iTensor*,std::vector<size_t>>;

/// Implementation of iTraveler
void visit (iLeaf* leaf) override
{
Expand Down
Loading

0 comments on commit ac63b42

Please sign in to comment.