chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:05 +08:00
commit 4f3b7da785
7394 changed files with 2005594 additions and 0 deletions
+233
View File
@@ -0,0 +1,233 @@
There's multiple different Ops designs supported in libND4j, and in this guide we'll try to explain how to build your very own operation.
## XYZ operations
This kind of operations is actually split into multiple subtypes, based on element-access and result type:
- Transform operations: These operations typically take some NDArray in, and change each element independent of others.
- Reduction operations: These operations take some NDArray and dimensions, and return reduced NDArray (or scalar) back. I.e. sum along dimension(s).
- Scalar operations: These operations are similar to transforms, but they only do arithmetic operations, and second operand is scalar. I.e. each element in given NDArray will add given scalar value.
- Pairwise operations: These operations are between regular transform operations and scalar operations. I.e. element-wise addition of two NDArrays.
- Random operations: Most of these operations related to random numbers distributions: Uniform, Gauss, Bernoulli etc.
Despite differences between these operations, they are all using XZ/XYZ three-operand design, where X and Y are inputs, and Z is output.
Data access in these operations is usually trivial, and loop based. I.e. most trivial loop for scalar transform will look like this:
```c++
for (sd::LongType i = start; i < end; i++) {
result[i] = OpType::op(x[i], scalar, extraParams);
}
```
Operation used in this loop will be template-driven, and compiled statically. There are another loops implementation, depending on op group or strides within NDArrays, but idea will be the same all the time: each element of the NDArray will be accessed within loop.
Now, let's take a look into typical XYZ op implementation. Here's how `Add` operation will look like:
```c++
template<typename T>
class Add {
public:
SD_OP_DEF static T op(T d1, T d2) {
return d1 + d2;
}
// this signature will be used in Scalar loops
SD_OP_DEF static T op(T d1, T d2, T *params) {
return d1 + d2;
}
// this signature will be used in reductions
SD_OP_DEF static T op(T d1) {
return d1;
}
// op for MetaOps
SD_OP_DEF static T op(T d1, T *params) {
return d1 + params[0];
}
};
```
This particular operation is used in different XYZ op groups, but you see the idea: element-wise operation, which is invoked on each element in given NDArray.
So, if you want to add new XYZ operation to libnd4j, you should just add operation implementation to file `includes/ops/ops.h`, and assign it to specific ops group in file `includes/loops/legacy_ops.h` together with some number unique to this ops group, i.e.: `(21, simdOps::Add)`
After libnd4j is recompiled, this op will become available for legacy execution mechanism, NDArray wrappers, and `LegacyOp` wrappers (those are made to map legacy operations to CustomOps design for Graph).
## Custom operations
Custom operations is a new concept, added recently and mostly suits SameDiff/Graph needs.
For CustomOps we defined universal signature, with variable number of input/output NDArrays, and variable number of floating-point and integer arguments.
However, there are some minor differences between various CustomOp declarations:
- **DECLARE_OP**(string, int, int, bool): these operations take no fp/int arguments, and output shape equals to input shape.
- **DECLARE_CONFIGURABLE_OP**(string, int, int, bool, int, int): these operations do take fp/int output arguments, and output shape equals to input shape.
- **DECLARE_REDUCTION_OP**(string, int, int, bool, int, int): these operations do take fp/int output arguments, and output shape is calculated as Reduction.
- **DECLARE_CUSTOM_OP**(string, int, int, bool, int, int): these operations return NDArray with custom shape, that usually depends on input and arguments.
- **DECLARE_BOOLEAN_OP**(string, int, bool): these operations take some NDArrays and return scalar, where 0 is **False**, and other values are treated as **True**.
Let's take a look at example CustomOp:
```c++
CUSTOM_OP_IMPL(tear, 1, -1, false, 0, -1) {
auto input = INPUT_VARIABLE(0);
REQUIRE_TRUE(!block.getIArguments()->empty(), 0, "At least 1 dimension should be specified for Tear");
std::vector<int> dims(*block.getIArguments());
for (auto &v: dims)
REQUIRE_TRUE(v >= 0 && v < input->rankOf(), 0, "Tear dimensions should be non-negative values, and lower than input rank. Got %i instead", v);
auto tads = input->allTensorsAlongDimension(dims);
for (int e = 0; e < tads->size(); e++) {
auto outE = OUTPUT_VARIABLE(e);
outE->assign(tads->at(e));
this->storeResult(block, e, *outE);
}
delete tads;
return sd::Status::OK;
}
DECLARE_SHAPE_FN(tear) {
auto inShape = inputShape->at(0);
std::vector<int> dims(*block.getIArguments());
if (dims.size() > 1)
std::sort(dims.begin(), dims.end());
shape::TAD tad(inShape, dims.data(), (int) dims.size());
tad.createTadOnlyShapeInfo();
sd::LongType numTads = shape::tadLength(inShape, dims.data(), (int) dims.size());
auto result = SHAPELIST();
for (int e = 0; e < numTads; e++) {
result->push_back(tad.tadOnlyShapeInfo);
}
return result;
}
```
In the example above, we declare `tear` CustomOp implementation, and shape function for this op.
So, at the moment of op execution, we assume that we will either have output array(s) provided by end-user, or they will be generated with shape function.
You can also see number of macros used, we'll cover those later as well. Beyond that - op execution logic is fairly simple & linear:
Each new op implements protected member function `DeclarableOp<T>::validateAndExecute(Block<T>& block)`, and this method is eventually called either from GraphExecutioner, or via direct call, like `DeclarableOp<T>::execute(Block<T>& block)`.
Important part of op declaration is input/output description for the op. I.e. as shown above: `CUSTOM_OP_IMPL(tear, 1, -1, false, 0, -1)`.
This declaration means:
- Op name: `tear`
- Op expects at least 1 NDArray as input
- Op returns unknown positive number of NDArrays as output
- Op can't be run in-place, so under any circumstances original NDArray will stay intact
- Op doesn't expect any T (aka floating point) arguments
- Op expects unknown positive number of integer arguments. In case of this op it's dimensions to split input NDArray.
Here's another example: `DECLARE_CUSTOM_OP(permute, 1, 1, true, 0, -2);`
This declaration means:
- Op name: `permute`
- Op expects at least 1 NDArray as input
- Op returns 1 NDArray as output
- Op can be run in-place if needed (it means: input == output, and input is modified and returned as output)
- Op doesn't expect any T arguments
- Op expects unknown number of integer arguments OR no integer arguments at all.
## c++11 syntactic sugar
In ops you can easily use c++11 features, including lambdas. In some cases it might be easiest way to build your custom op (or some part of it) via `NDArray::applyLambda` or `NDArray::applyPairwiseLambda`:
```c++
auto lambda = LAMBDA_TT(_x, _y) {
return (_x + _y) * 2;
};
x.applyPairwiseLambda(&y, lambda);
```
In this simple example, each element of NDArray `x` will get values set to `x[e] = (x[e] + y[e]) * 2`.
## Tests
For tests libnd4j uses Google Tests suit. All tests are located at `tests_cpu/layers_tests` folder. Here's a simple way to run those from command line:
```
cd tests_cpu
cmake -G "Unix Makefiles"
make -j 4
./layers_tests/runtests
```
You can also use your IDE (i.e. Jetbrains CLion) to run tests via GUI.
**PLEASE NOTE:** if you're considering submitting your new op to libnd4j repository via pull request - consider adding tests for it. Ops without tests won't be approved.
## Backend-specific operation
GPU/MPI/whatever to be added soon.
## Utility macros
We have number of utility macros, suitable for custom ops. Here they are:
- **INPUT_VARIABLE**(int): this macro returns you NDArray at specified input index.
- **OUTPUT_VARIABLE**(int): this macro returns you NDArray at specified output index.
- **STORE_RESULT**(NDArray<T>): this macro stores result to VariableSpace.
- **STORE_2_RESULTS**(NDArray<T>, NDArray<T>): this macro stores results according to VariableSpace.
- **INT_ARG**(int): this macro returns you specific Integer argument passed to the given op.
- **T_ARG**(int): this macro returns you specific T argument passed to the given op.
- **ALLOCATE**(...): this macro check if Workspace is available, and either uses Workspace or direct memory allocation if Workspace isn't available.
- **RELEASE**(...): this macro is made to release memory allocated with **ALLOCATE()** macro.
- **REQUIRE_TRUE**(...): this macro takes condition, and evaluates it. If evaluation doesn't end up as True - exception is raised, and specified message is printed out.
- **LAMBDA_T**(X) and **LAMBDA_TT**(X, Y): lambda declaration for `NDArray::applyLambda` and `NDArray::applyPairwiseLambda`
- **COPY_SHAPE**(SRC, TGT): this macro allocates memory for TGT pointer and copies shape from SRC pointer
- **ILAMBDA_T**(X) and **ILAMBDA_TT**(X, Y): lambda declaration for indexed lambdas, index argument is passed in as sd::LongType (aka **long long**)
- **SD_INLINE**: platform-specific definition for functions inlining
#### Explicit template instantiations in helper methods.
We should explicitly instantiate template methods for different data types in libraries. Furthermore, to speed up parallel compilation we need to add those template instantiations in separate source files. Besides, another reason is that: some compilers are choked when these template instantiations are many in one translation unit.
To ease this cumbersome operation we have Cmake helper and macros helpers.
Example:
Suppose we have such function:
template<typename X, typename Z>
void argMin_(const NDArray& input, NDArray& output, const std::vector<sd::LongType>& dimensions);
We should write this to explicitly instantiate it.
BUILD_DOUBLE_TEMPLATE(template void argMin_, (const NDArray& input, NDArray& output, const std::vector<sd::LongType>& dimensions),
SD_COMMON_TYPES, SD_INDEXING_TYPES);
Here:
- ***SD_COMMON_TYPES*** means we want to use all types in the place of X
- ***SD_INDEXING_TYPES*** means we will use index types ( int, int64_t) as Z type
But to speed up compilation process and also helping compilers we can further separate it into different source files. Firstly we rename the original template source with ***hpp*** extension:
Secondly we add file with the suffix ***cpp.in*** (or ***cu.in*** for cuda) that will include that hpp header and place it in the appropriate compilation units folder. in our case it will be in **./libnd4j/include/ops/declarable/helpers/cpu/compilation_units** folder with the name ***argmax.cpp.in*** .
Later we decide which type we want to separate into different sources. In our case we want to split ***SD_COMMON_TYPES*** (other ones: ***SD_INTEGER_TYPES , SD_FLOAT_TYPES, SD_PAIRWISE_TYPES*** ). We hint cmake that case with this (adding ***_GEN*** suffix):
#cmakedefine SD_COMMON_TYPES_GEN
Then we just add ***_@FL_TYPE_INDEX@*** as suffix in type name and it will split those types for us and generate cpp files inside ${CMAKE_BINARY_DIR}/compilation_units folder.
LIBND4J_TYPE_@FL_TYPE_INDEX@
Here is how the complete cpp.in file will look like:
#cmakedefine SD_COMMON_TYPES_GEN
//this header is where our template functions resides
#include <ops/declarable/helpers/cpu/indexReductions.hpp>
//guard against undefined cases
#if defined(SD_COMMON_TYPES_GEN) && defined(SD_COMMON_TYPES_@FL_TYPE_INDEX@)
namespace sd {
namespace ops {
namespace helpers {
BUILD_DOUBLE_TEMPLATE(template void argMax_, (const NDArray& input, NDArray& output, const std::vector<sd::LongType>& dimensions),
SD_COMMON_TYPES_@FL_TYPE_INDEX@, SD_INDEXING_TYPES);
}
}
}
#endif
+56
View File
@@ -0,0 +1,56 @@
# Debugging Practices with C++ Tests in libnd4j Library
In this document, we will walk through some general debugging practices while working with C++ tests in the libnd4j library. This involves the use of Google Test framework (gtest), building with debug flags, and employing memory checking tools like Valgrind.
## Prerequisites
Ensure that you have the following installed on your system:
- Google Test framework (gtest)
- CMake (for building the project)
- Valgrind (for memory leak checking)
- A modern C++ compiler that supports C++11 or later
## Building with Debug Flags
In order to debug effectively, you will need to compile your project with debugging information. This can be accomplished by adding debug flags during the build process.
If you're using CMake, you can do this by setting the `CMAKE_BUILD_TYPE` variable to `Debug`. This can be done through the command line as follows:
```
cmake -DCMAKE_BUILD_TYPE=Debug ..
```
When you're building your project with debug flags, the compiler will include extra information in the resulting executable that can be used to help find bugs.
## Debugging with Google Test (gtest)
Google Test is a powerful framework for writing C++ tests. It provides a simple and flexible way to write reliable tests, including features like test suites and assertions that make it easy to structure your tests and verify your code.
You can run a specific test using the `--gtest_filter` flag. The value passed to `--gtest_filter` is a colon-separated list of wildcard patterns (they may include `*` as a wildcard). Only the tests whose full names match one of the patterns will be executed.
For example, to run the `BasicTest_Scatter_1` test of the `ListOperationsTests` test case, you can use:
```
./blasbuild/cpu/tests_cpu/layers_tests/runtests --gtest_filter=ListOperationsTests.BasicTest_Scatter_1
```
## Debugging with Valgrind
Valgrind is an open-source tool that can be used for memory debugging, memory leak detection, and profiling. If your program is crashing or behaving unexpectedly, Valgrind can help identify memory leaks, uninitialized memory, and other related problems.
To run your tests under Valgrind, use the following command:
```
valgrind --track-origins=yes -v ./blasbuild/cpu/tests_cpu/layers_tests/runtests --gtest_filter=ListOperationsTests.BasicTest_Scatter_1
```
Here's what the flags do:
- `--track-origins=yes`: This tells Valgrind to track the origin of uninitialized values. This can be useful for finding the source of uninitialized value errors.
- `-v`: This increases the verbosity of Valgrind's output, giving you more information about what's happening.
- `--gtest_filter=ListOperationsTests.BasicTest_Scatter_1`: This is telling gtest to run only the `BasicTest_Scatter_1` test from the `ListOperationsTests` test suite.
## Conclusion
This should give you a solid foundation for debugging C++ tests in the libnd4j library. Remember, the key to effective debugging is to take a systematic approach, carefully observing the behavior of your program and making hypotheses about what could be causing any issues.
+40
View File
@@ -0,0 +1,40 @@
# Deeplearning4j: Enhanced Stack Trace Feature Overview
## Introduction
For developers who are knee-deep in troubleshooting, understanding where a problem originated can be invaluable. In line with that, Deeplearning4j now introduces an advanced feature that provides an insightful fusion of Java and C++ stack traces. This is especially useful when debugging issues related to memory allocation and deallocation.
## Feature: SD_GCC_FUNCTRACE
When you build Deeplearning4j with the `SD_GCC_FUNCTRACE` option turned on, it activates the ability to display C++ stack traces. This powerful feature, however, comes with a caveat: it requires numerous platform-specific dependencies to function seamlessly.
### What's New?
When the aforementioned feature is active, developers can now enable a fresh capability that showcases both Java and C++ stack traces at every instance of memory allocation and deallocation in the Deeplearning4j codebase.
Here's the crux of this new feature:
1. **Allocation and Deallocation Triggers**: The stack traces will be printed just as a buffer is about to be deallocated.
2. **Crash Insights**: Typically, the last deallocation that took place will pinpoint the site of the crash.
3. **Full Problem Context**: By analyzing Java and C++ stack traces side by side, developers can derive a comprehensive understanding of the issue at hand.
4. **Enhancement Over Sanitizers**: This feature is a supplement to sanitizers, which occasionally falter in showing internal stack traces instead of the real underlying problem.
## Enabling the Feature
Activating this feature is straightforward. Here's a snippet to do just that:
```java
Nd4j.getEnvironment().setFuncTraceForAllocate(true);
Nd4j.getEnvironment().setFuncTraceForDeallocate(true);
```
With these lines of code:
- The first line will enable the printing of stack traces during memory allocation.
- The second line will do the same for deallocation.
## Conclusion
By leveraging this new feature, developers can achieve a granular understanding of memory-related issues in Deeplearning4j's operations. This comprehensive insight into both Java and C++ realms will significantly streamline the debugging process and enhance code reliability.
_Remember, while powerful, this feature can also be verbose. Hence, it's recommended to use it judiciously, primarily when deep troubleshooting is necessary._
+172
View File
@@ -0,0 +1,172 @@
## Helpers
### Requirements Helper
Requirements helper was introduced to replace plain checks for making them output informative messages (Debug and Verbose mode) and also replace macros REQUIRE_TRUE.
- it will lazily evaluate values and messages if the type wrapped and has` getValue` and `getMsg` methods
- it is implicit bool. this makes it usable with logical operators and also inside if conditions. Besides it will benefit from shortcircuit nature of those operators.
- it has the following check methods
```cpp
Requirements& expect(const T& expVar,const T1& reqVar, Op comparison, const char *first_half="")
Requirements& expectEq(const T& exp,const T1& req)
Requirements& expectNotEq(const T& exp,const T1& req)
Requirements& expectLess(const T& exp,const T1& req)
Requirements& expectLessEq(const T& exp,const T1& req)
Requirements& expectGreater(T exp, T1 req)
Requirements& expectGreaterEq(const T& exp,const T1& req
Requirements& expectTrue(const T& expVar, const char *msg=)
Requirements& expectFalse(const T& expVar, const char *msg=)
```
- you can either log the success case or throw error on the failure
- it can use plain types for checks.
- if value has stream operator it will be used to output its value. for custom types you may need add that by yourself
`ostream& operator<<(ostream& os, const CustomUserType& dt)`
- there is generic template `InfoVariable` wrapper for types to make it informative. you can use lambda operators with them as well to make it lazily evaluated
- we added custom `ShapeInfoVariable` wrapper for the NDArray and vector<> shapes to make them informative
- one can use `expect` to add its own proper comparison. simple lambda for that will be like this:
```cpp
[](const decltype(expType)& l, const decltype(reqType)& r){
//compare and return
return ....;
}
```
#### Examples:
firstly, we should enable logging
```cpp
sd::Environment::getInstance().setDebug(true);
sd::Environment::getInstance().setVerbose(true);
```
1. simple case
```cpp
Requirements req1("Requirement Helper Example#1");
int x = 20;
req1.expectLess(x, 22);
req1.expectEq(x, 21); //should fail
```
Output:
``` Requirement Helper Example#1: {20} expected to be equal to 21```
2. using InfoVariable wrapper
```cpp
int age = 15;
Requirements req2("Requirement Helper Example#2");
req2.expectGreaterEq(makeInfoVariable(age, "the user's age"), 18);
```
Output:
```
Requirement Helper Example#2: the user's age {15} expected to be greater than or equal 18
```
3. helper behavior while using many checks in one block
```cpp
int getAge(){
std::cout<<"getAge() was called"<<std::endl;
return 15;
}
....
Requirements req3("Requirement Helper Example#3");
int z = 20;
req3.expectEq(z, 21);
req3.expectGreaterEq(makeInfoVariable(getAge(), "the user's age"), 18);
```
Output:
```
Requirement Helper Example#3: {20} expected to be equal to 21
getAge() was called
```
As it is seen the second check did not happen as the previous failed. But still ```getAge()``` method was called as its function argument.
4. using **shortcircuit** to avoid Requirement call at all if the previous one was failed
```cpp
Requirements req4("Requirement Helper Example#4");
int zz = 20;
req4.expectEq(zz, 21) && //shortcircuit And
req4.expectGreaterEq(makeInfoVariable(getAge(), "the user's age"), 18);
```
Output:
```
Requirement Helper Example#4: {20} expected to be equal to 21
```
5. using lambdas with InfoVariable. it will make it lazily evaluated
```cpp
Requirements req5("Requirement Helper Example#5");
req5.expectEq( 21,
makeInfoVariable(21, []{
std::cout<<"lambda call#1"<<std::endl;
return "twenty one";
}));
req5.expectEq(makeInfoVariable([]{ return 20;}, []{return "twenty";}),
makeInfoVariable(21, []{
std::cout<<"lambda call#2"<<std::endl;
return "twenty one";
}));
req5.expectGreaterEq(makeInfoVariable([]{
std::cout<<"lambda call#3" <<std::endl;
return 15;
},
[]{ return "the user's age";}),
makeInfoVariable([]{return 18;}, []{return "the allowed age";})
);
```
Output:
```
lambda call#2
Requirement Helper Example#5: twenty {20} expected to be equal to twenty one 21
```
6. use bool nature and also log the success case
```cpp
Requirements req6("Requirement Helper Example#6");
NDArray * arr= nullptr;
arr !=nullptr && req6.expectEq(arr->rankOf(), 3) ;
req6.logTheSuccess();
```
Output:
```
Requirement Helper Example#6: meets the requirements
```
7. custom comparision lambda and also another usage of the custom wrapper written by us ```ShapeInfoVariable```. Note: we will use ```std::vector<int>```. this wrapper can be used with ```NDArray``` as well.
```cpp
Requirements req7("Requirement Helper Example#7");
req7.expect(makeShapeInfoVariable(std::vector<sd::LongType>{2,3,4,5}, SHAPE_MSG_INPUT0), makeShapeInfoVariable(std::vector<sd::LongType>{2,3,4,7}, SHAPE_MSG_INPUT1),
[](const std::vector<sd::LongType>& l, const std::vector<sd::LongType>& r){
return l == r;
}
, EXPECTED_EQ_MSG);
}
```
Output:
```
Requirement Helper Example#7: the Shape of the Input NDArray#0 {[2, 3, 4, 5]} expected to be equal to the Shape of the Input NDArray#1 [2, 3, 4, 7]
```
8. throw error when there is failure
```cpp
Requirements req8("Requirement Helper Example#8");
req8.expectEq(6,6) &&
req8.expectIn(6, {1,2,3,7,8,9});
req8.throws();
```
Output:
```
terminate called after throwing an instance of 'std::invalid_argument'
what(): Op validation failed
...
Requirement Helper Example#8: {6} expected to be one of these {[1, 2, 3, 7, 8, 9, ]}
```
##### Here is live example:
**Note:** some classes were mocked there and do not represent the exact implementations in libnd4j.
https://godbolt.org/z/sq98vchs5
+32
View File
@@ -0,0 +1,32 @@
# libnd4j CUDA Kernel Launch Configuration
This part of the libnd4j codebase provides a centralized way to handle CUDA launch configurations. It allows customization of launch dimensions using environment variables.
## Conceptual Overview
The code sets up CUDA launch configurations for a wide range of algorithms used in the libnd4j codebase. These configurations are expressed as `dim3` structures, representing grid and block dimensions for launching CUDA kernels. The configurations are stored in a map, improving performance via a caching mechanism.
### Key Feature
A significant feature of this code is the flexibility it offers, allowing users to specify custom launch dimensions through environment variables. This enables modifications of these configurations at both compile-time and runtime.
## Examples of Usage
```cpp
// Fetching Launch Dimensions
dim3 launchDims = getLaunchDims("matrixMultiply");
//invoke your cuda function
yourKernel<<<launchDims.y,launchDims.x,launchDims.z>>>
//This returns the blocks, threads and shared memory used for the kernel.
```
The launch configuration can be found in
```bash
../include/execution/cuda/LaunchDims.h
../include/execution/cuda/LaunchDims.cu
```
Every kernel has environment variables that work at build time and runtime.
Build time is used to modify defaults. Runtime is used to override buildtime and defaults.
+35
View File
@@ -0,0 +1,35 @@
Please follow following instructions to build nd4j for raspberry PI:
1. download cross compilation tools for Raspberry PI
```
$ apt-get/yum install git cmake
(You may substitute any path you prefer instead of $HOME/raspberrypi in the following two steps)
$ mkdir $HOME/raspberrypi
$ export RPI_HOME=$HOME/raspberrypi
$ cd $RPI_HOME
$ git clone git://github.com/raspberrypi/tools.git
$ export PATH=$PATH:$RPI_HOME/tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin
```
2. download deeplearning4j:
```
$ cd $HOME
$ git clone https://github.com/eclipse/deeplearning4j.git
```
3. build libnd4j:
```
$ cd deeplearning4j/libnd4j
$ ./buildnativeoperations.sh -o linux-armhf
```
4. build nd4j
```
$ export LIBND4J_HOME=<pathTond4JNI>
$ cd $HOME/deeplearning4j/nd4j
$ mvn clean install -Djavacpp.platform=linux-armhf -Djavacpp.platform.compiler=$HOME/raspberrypi/tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ -DskipTests -Dmaven.javadoc.skip=true -pl '!:nd4j-cuda-9.1,!:nd4j-cuda-9.1-platform,!:nd4j-tests'
```
+12
View File
@@ -0,0 +1,12 @@
Due to the macros and sheer number of type combinations we often run in to undefined symbols when linking.
We typically use this command on linux to find issues:
nm -uC libnd4j/blasbuild/cpu/blas/libnd4jcpu.so
or cuda:
nm -uC libnd4j/blasbuild/cuda/blas/libnd4jcuda.so
You can also do the same for *just* undefined:
nm -C --undefined-only libnd4jcpu.so
nm -C --undefined-only libnd4jcuda.so
+145
View File
@@ -0,0 +1,145 @@
# Graph
### Basic idea
libnd4j contains Directed Acyclic Graph execution engine, suited for both local and remote execution. However, main goal here is execution of externally originated graphs, serialized into FlatBuffers and provided either via pointer, or file.
This basic example shows execution of graph loaded from file:
```c++
auto graph = GraphExecutioner<float>::importFromFlatBuffers("./some_file.fb");
GraphExecutioner<float>::execute(graph);
// ... do something with results ...
delete graph;
```
### FlatBuffers schemas
You can find scheme files [here](https://github.com/eclipse/deeplearning4j/tree/master/libnd4j/include/graph/scheme).
At this moment libnd4j repo contains compiled definitions for C++, Python, Java, and JSON, but FlatBuffers can be compiled for PHP, C#, JavaScript, TypeScript and Go as well. Please refer to `flatc` instructions to do that.
Such bindings allow you to build FlatBuffers files/buffers suitable for remote execution of your graph and obtaining results back. I.e. you can use JavaScript to build graph (or just update variables/placeholders), send them to remote RPC server powered by libnd4j, and get results back.
Use flatc-generate.sh to generate the relevant source files if you need to change anything related to flatbuffers such
as the samediff file format or the UI.
### Graph execution logic
No matter how graph is represented on the front-end, on backend it's rather simple: topologically sorted list of operations executed sequentially if there's shared dependencies, or (optionally) in parallel, if there's no shared dependencies for current graph nodes.
Each node in the graph represents single linear algebra operation applied to input(s) of the node. For example: `z = Add(x, y)` is operation that takes 2 NDArrays as input, and produes 1 NDArray as output. So, graph is built of such primitive operations, which are executed sequentially.
### Memory management within graph
Everything that happens within graph during execution, stays within VariableSpace. It acts as storage for Variables and NDArrays produced during graph execution. On top of that, there's an option to use pre-allocated Workspaces for allocation of NDArrays.
### Current graph limitations
There are some limitations. Some of them will be lifted eventually, others won't be. Here's the list:
- Graph has single data type. I.e. Graph&lt;float&gt; or Graph&lt;float16&gt; or Graph&lt;double&gt; _This limitation will be lifted soon._
- On some platforms, like Java, single Variable/Placeholder size is limited to 2GB buffer size. However, on libnd4j side there's no such limitation.
- Variable size/dimensionality has limitations: max NDArray rank is limited to 32 at this moment, and any single dimension is limited to SD_MAX_INT size.
- Recursion isn't directly supported at this moment.
- CUDA isn't supported at this moment. _This limitation will be lifted soon._
- When used from C++, Graph only supports FeedForward mode. _This limitation will be lifted soon._
### Documentation
Documentation for individual operations, and basic classes (like NDArray, Graph etc) is available as part of Nd4j javadoc: https://javadoc.io/doc/org.nd4j/nd4j-api/latest/index.html
### Embedded profiling
If you're adding new ops, and want to make sure they run ok on your specific device - you might want to give a shot to embedded Graph profiling helper.
Despite being simple - it still provides you with time spent in various parts of Graph.
```c++
Environment::getInstance().setProfiling(true);
auto graph = GraphExecutioner::importFromFlatBuffers("./resources/ae_00.fb");
auto profile = GraphProfilingHelper::profile(graph, 1000);
profile->printOut();
delete graph;
```
1000 iterations laterm you'll get statistics printed out. Statistics basically includes time spent in various parts of code and memory allocation details.
Here's how it'll look like:
```
Printing out Graph...
8. matmul; Inputs: [{1:0}, {2:0}];
9. biasadd; Inputs: [{8:0}, {3:0}];
10. TRANSFORM:{15}; Inputs: [{9:0}];
11. rank; Inputs: [{2:0}];
12. subtract; Inputs: [{11:0}, {4:0}];
13. range; Inputs: [{5:0}, {11:0}, {6:0}];
14. subtract; Inputs: [{12:0}, {13:0}];
15. transpose; Inputs: [{2:0}, {14:0}];
16. matmul; Inputs: [{10:0}, {15:0}];
17. biasadd; Inputs: [{16:0}, {7:0}];
18. TRANSFORM:{15}; Inputs: [{17:0}];
Printing out Scopes...
Graph profile: 1000 executions
Memory:
ACT: 0; TMP: 0; OBJ: 0; TTL: 1788;
Time:
Construction time: 2135 ns;
Execution time: 41820 ns;
Per-node reports:
Node: <8:MatMul>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 200;
Time: PREP: 1160 ns; EXEC: 3167 ns; TTL: 5929 ns;
PREP: INPUT: 251 ns; SHAPE: 382 ns; ARRAY: 217 ns;
Node: <9:BiasAdd>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 104;
Time: PREP: 917 ns; EXEC: 3580 ns; TTL: 5957 ns;
PREP: INPUT: 220 ns; SHAPE: 213 ns; ARRAY: 217 ns;
Node: <10:Tanh>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 104;
Time: PREP: 756 ns; EXEC: 241 ns; TTL: 1927 ns;
PREP: INPUT: 140 ns; SHAPE: 195 ns; ARRAY: 205 ns;
Node: <11:transpose/Rank>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 36;
Time: PREP: 522 ns; EXEC: 119 ns; TTL: 1403 ns;
PREP: INPUT: 109 ns; SHAPE: 69 ns; ARRAY: 171 ns;
Node: <12:transpose/sub>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 36;
Time: PREP: 666 ns; EXEC: 185 ns; TTL: 1684 ns;
PREP: INPUT: 192 ns; SHAPE: 94 ns; ARRAY: 168 ns;
Node: <13:transpose/Range>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 556;
Time: PREP: 808 ns; EXEC: 647 ns; TTL: 2416 ns;
PREP: INPUT: 297 ns; SHAPE: 228 ns; ARRAY: 181 ns;
Node: <14:transpose/sub_1>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 56;
Time: PREP: 721 ns; EXEC: 541 ns; TTL: 2205 ns;
PREP: INPUT: 23 ns; SHAPE: 92 ns; ARRAY: 165 ns;
Node: <15:transpose>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 96;
Time: PREP: 3936 ns; EXEC: 602 ns; TTL: 5811 ns;
PREP: INPUT: 194 ns; SHAPE: 3241 ns; ARRAY: 257 ns;
Node: <16:MatMul_1>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 312;
Time: PREP: 970 ns; EXEC: 3565 ns; TTL: 6066 ns;
PREP: INPUT: 203 ns; SHAPE: 320 ns; ARRAY: 193 ns;
Node: <17:BiasAdd_1>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 144;
Time: PREP: 914 ns; EXEC: 3528 ns; TTL: 5870 ns;
PREP: INPUT: 231 ns; SHAPE: 191 ns; ARRAY: 223 ns;
Node: <18:output>
Memory: ACT: 0; TMP: 0; OBJ: 0; TTL: 144;
Time: PREP: 805 ns; EXEC: 285 ns; TTL: 1928 ns;
PREP: INPUT: 157 ns; SHAPE: 192 ns; ARRAY: 232 ns;
Special timers:
No special timers were set
```
### Roadmap
In short-to-medium term following improvements are expected:
- CUDA support for all new ops
- Additional data types support: int, long long, q types, bool
- Sparse tensors support
+4
View File
@@ -0,0 +1,4 @@
### Note to iOS build
I used LLVM 4.0 to build `ios-arm`, `ios-x86`, and `ios-x86_64`. LLVM clang seems to have a bug in recognizing arm64, I used Xcode 9.0 to build `ios-arm64` without `-fopenmp`.
This is for build only, though. Since I have not built nd4j yet, it is to be decided later if MLPMnistSingleLayerExample and MLPMnistTwoLayerExample will run on iOS.
+82
View File
@@ -0,0 +1,82 @@
# Build DL4J for Linux on Power
This document explains how to build DL4J for Linux on Power
## Check pre-requirements
The following tools are necessary to build DL4J for Linux on Power
1. gcc version 4.9.X (older or newer version may cause build errors.)
Check the version by "gcc --version", and install it from https://gcc.gnu.org/ if necessary.
Look at Apppendix of this document to install gcc 4.9.
2. OpenJDK java 1.8
Check the version is OpenJDK and 1.8 by "java -version".
3. Apache Maven 3.3 or later
Check the version by "mvn -v", and install it from https://maven.apache.org/ if necessary.
4. CUDA7.5 (CUDA7.0 is not supported)
Check the version by "nvcc -V"
5. cmake version 3.5.0 or later
## Set Environment Variables
Edit the CUDA, JAVA_HOME, CC, CXX environment variables according to your system
```
export CUDA=/usr/local/cuda-7.5 # CUDA Directory
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-ppc64el # JAVA Directory
export CC=/path/to/gcc # gcc command for build
export CXX=/path/to/g++ # g++ command for build
export MAVEN_OPTS='-Xmx4096M -Dos.arch=ppc64le'
export _JAVA_OPTIONS=-Dos.arch=ppc64le
export LIBND4J_HOME=`/bin/pwd`/libnd4j
export CUDA_VISIBLE_DEVICES=0
```
## Clone and build DL4J, and execute a sample program
1. Clone necessary modules from github as follows.
```
git clone https://github.com/eclipse/deeplearning4j.git
```
2. Modify the setting file to enable GPU. (Skip this step if you do not use GPU)
Modify the following line in dl4j-0.4-examples/pom.xml as follows.
```
Before: <nd4j.backend>nd4j-native</nd4j.backend>
After: <nd4j.backend>nd4j-cuda-7.5</nd4j.backend>
```
3. Build modules as follows. (Make sure you follow the instructions in order.)
```
(cd javacpp/; mvn clean install -DskipTests)
(cd libnd4j; ./buildnativeoperations.sh)
(cd libnd4j; ./buildnativeoperations.sh -c cuda)
(cd nd4j; mvn -e clean install -DskipTests -DskipTests -Djavacpp.platform.dependency=false -Dmaven.javadoc.skip=true)
(cd Canova/; mvn clean install -DskipTests -Djavacpp.platform.dependency=false)
(cd deeplearning4j; mvn clean package -DskipTests -Djavacpp.platform.dependency=false)
(cd dl4j-0.4-examples; mvn clean package -DskipTests)
```
4. Test the module by running the LenetMnist example
```
(cd dl4j-0.4-examples; java -cp target/deeplearning4j-examples-0.4-rc0-SNAPSHOT-bin.jar org.deeplearning4j.examples.convolution.LenetMnistExample)
```
## Appendix
How to build gcc 4.9.3 on Linux on Power
```
$ tar xvfz gcc-4.9.3.tar.gz
$ cd gcc-4.9.3
$ mkdir -p build
$ (cd build; ../configure --enable-languages=c,c++ --prefix=<install path> --disable-bootstrap --disable-multilib)
$ (cd build; make)
$ (cd build; make install)
# You need to add <install path>/lib64 in LD_LIBRRY_PATH
# BLAS need to be specified in LD_LIBRARY_PATH to run CPU(native) version
```
+52
View File
@@ -0,0 +1,52 @@
## Overview
The nd4j and libnd4j code base can be difficult to debug due to the lack of tools for tracing program execution. We have implemented function tracing using the `-finstrument-functions` flag provided by GCC to trace function calls and exits in the code base. This feature will help developers better understand the flow of the program and identify potential issues.
## How to Enable Function Tracing
To enable function tracing, follow these steps:
1. Build the code base with `-Dlibnd4j.functrace=ON`.
2. Add the correct compiler flags to the javacpp plugin.
3. Add the correct flags to the cmake build.
4. Add a maven profile to each nd4j backend containing the correct compiler flags to work with `-finstrument-functions`.
## Implementation Details
### Compiler Flags
We have used the following compiler flags to enable function tracing:
- `-Bsymbolic`: Bind references to global symbols at link time, reducing the runtime overhead.
- `-rdynamic`: Export all dynamic symbols to the dynamic symbol table, making them available for backtracing.
- `-fno-omit-frame-pointer`: Do not omit the frame pointer, allowing for accurate backtraces.
- `-fno-optimize-sibling-calls`: Disable sibling call optimization to maintain the correct call stack.
- `-finstrument-functions`: Enable instrumentation of function entry and exit points.
- `-g`: Enable debugging information.
- `-O0`: Set the optimization level to zero for easier debugging.
### Setting the Output File
We have implemented a method in each backend to set the output file for tracing results. For example, in the Nd4jCpu class:
```java
Nd4jCpu nd4jCpu = (Nd4jCpu) NativeOpsHolder.getInstance().getDeviceNativeOps();
nd4jCpu.setInstrumentOut("profilerout.txt");
```
This calls a C++ method that sets the appropriate file to use. Each backend will have this call. Note that we don't put this in NativeOps (the parent backend agnostic interface for this) because this normally should not be included in any builds due to overhead.
### LD_PRELOAD and LD_DEBUG
To ensure the correct implementation of the enter/exit functions is used, `LD_PRELOAD` is utilized to preload the libnd4j binary generated during the build. The built-in libc implementation of these functions is a no-op, so preloading the libnd4j binary is necessary.
`LD_DEBUG` can be used to verify that the correct implementation is being used by showing the symbols being loaded and their origin.
## Sample Log Output
```
g long> > >::end() (/home/agibsonccc/Documents/GitHub/deeplearning4j/libnd4j/blasbuild/cpu/blas/libnd4jcpu.so)
enter std::operator==(std::_Rb_tree_iterator<std::pair<int const, long long> > const&, std::_Rb_tree_iterator<std::pair<int const, long long> > const&) (/home/agibsonccc/Documents/GitHub/deeplearning4j/libnd4j/blasbuild/cpu/blas/libnd4jcpu.so)
exit std::operator==(std::_Rb_tree_iterator<std::pair<int const, long long> > const&, std::_Rb_tree_iterator<std::pair<int const, long long> > const&) (/home/agibsonccc/Documents/GitHub/deeplearning4j/libnd4j/blasbuild/cpu/blas/libnd4