Android ND4J conflicts with gRPC

Hello there!
I am a student and I am currently developing (as my bachelor’s degree task) an Android application that uses DL4J, ND4J and gRPC libraries. Not long ago I encountered a problem that I have tried to fix myself, but eventually it just turned out as a waste of my time. So here I am now, hoping to find the solution.

1. What exactly is going on?

  • My team is currently developing a java library for federated learning. The library have been tested and now it’s used by another team developing a web application (both the client and the server) for federated learning. As far as I know, this application works perfectly, so the library too.
  • My task is to develop an Android application that will be used as federated learning client. This application uses my team’s library too (with minor changes for using it on Android).
  • The library consists of 2 modules: ‘framework’ (client-server communication using gRPC protocol, data reading, etc.) and ‘deeplearning’ (for NN learning, obviously).

2. What problems have I encountered with (and how have I tried to fix them)?

  1. When I try to build the application the ‘checkDebugDuplicateClasses’ task fails with the message:
Duplicate class com.google.thirdparty.publicsuffix.PublicSuffixPatterns found in modules guava-30.1.1-android.jar (com.google.guava:guava:30.1.1-android) and protobuf-1.0.0-M1.1.jar (org.nd4j:protobuf:1.0.0-M1.1)
Duplicate class com.google.thirdparty.publicsuffix.PublicSuffixType found in modules guava-30.1.1-android.jar (com.google.guava:guava:30.1.1-android) and protobuf-1.0.0-M1.1.jar (org.nd4j:protobuf:1.0.0-M1.1)
Duplicate class com.google.thirdparty.publicsuffix.TrieParser found in modules guava-30.1.1-android.jar (com.google.guava:guava:30.1.1-android) and protobuf-1.0.0-M1.1.jar (org.nd4j:protobuf:1.0.0-M1.1)
Duplicate class javax.annotation.CheckForNull found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.CheckForSigned found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.CheckReturnValue found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Detainted found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.MatchesPattern found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.MatchesPattern$Checker found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Nonnegative found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Nonnegative$Checker found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Nonnull found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Nonnull$Checker found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Nullable found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.OverridingMethodsMustInvokeSuper found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.ParametersAreNonnullByDefault found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.ParametersAreNullableByDefault found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.PropertyKey found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.RegEx found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.RegEx$Checker found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Signed found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Syntax found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Tainted found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.Untainted found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.WillClose found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.WillCloseWhenClosed found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.WillNotClose found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.concurrent.GuardedBy found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.concurrent.Immutable found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.concurrent.NotThreadSafe found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.concurrent.ThreadSafe found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.meta.Exclusive found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.meta.Exhaustive found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.meta.TypeQualifier found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.meta.TypeQualifierDefault found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.meta.TypeQualifierNickname found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.meta.TypeQualifierValidator found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
Duplicate class javax.annotation.meta.When found in modules guava-1.0.0-M1.1.jar (org.nd4j:guava:1.0.0-M1.1) and jsr305-3.0.2.jar (com.google.code.findbugs:jsr305:3.0.2)
  1. I tried to exclude conflicting modules:
    applicationVariants.all { variant ->
        variant.getRuntimeConfiguration().exclude group: 'com.google.code.findbugs', module: 'jsr305'
        variant.getRuntimeConfiguration().exclude group: 'org.nd4j', module: 'protobuf'
    }

But then in the runtime when the NN Learning starts, the app crashes with the exception:

java.lang.NoClassDefFoundError: Failed resolution of: Lorg/nd4j/shade/protobuf/ProtocolMessageEnum;
...
Caused by: java.lang.ClassNotFoundException: Didn't find class "org.nd4j.shade.protobuf.ProtocolMessageEnum"
  1. Then I tried to substitute conflicting modules:
configurations {
    all {
        resolutionStrategy.dependencySubstitution {
            substitute module('com.google.guava:guava:30.1.1-android') with module('org.nd4j:protobuf:1.0.0-M1.1')
            substitute module('com.google.code.findbugs:jsr305:3.0.2') with module('org.nd4j:guava:1.0.0-M1.1')
            substitute module('com.google.guava:guava:20.0') with module('org.nd4j:protobuf:1.0.0-M1.1')
        }
    }
}

It also caused the exception in the runtime:

java.lang.NoClassDefFoundError: Failed resolution of: Lcom/google/common/base/Preconditions;
  1. Finally, I decided to try to use the same version of grpc- libraries as nd4j uses (1.14.0) but it also didn’t resolve conflict between these modules.

3. Why do I think that it is the gRPC conflicting with ND4J?
The answer is simple - when I delete these dependencies the app builds without errors:

    implementation 'io.grpc:grpc-netty-shaded:1.44.1'
    implementation 'io.grpc:grpc-android:1.44.1'
    implementation 'io.grpc:grpc-protobuf-lite:1.44.1'
    implementation 'io.grpc:grpc-stub:1.44.1'

But removing them is not an option, because the ‘framework’ module of our library needs these dependencies.

4. Some other notes.
I suppose we need to use 1.0.0-M1.1 version of DL4J instead of 1.0.0-beta7 because all attempts to run NN learning on 1.0.0-beta7 ended with this exception:

E/Job: java.lang.ExceptionInInitializerError
    java.lang.ExceptionInInitializerError
        at org.nd4j.nativeblas.NativeOpsHolder.getInstance(NativeOpsHolder.java:119)
        at org.nd4j.linalg.cpu.nativecpu.ops.NativeOpExecutioner.<init>(NativeOpExecutioner.java:86)
        at java.lang.Class.newInstance(Native Method)
        at org.nd4j.linalg.factory.Nd4j.initWithBackend(Nd4j.java:5178)
        at org.nd4j.linalg.factory.Nd4j.initContext(Nd4j.java:5092)
        at org.nd4j.linalg.factory.Nd4j.<clinit>(Nd4j.java:270)
        at org.nd4j.linalg.factory.Nd4j.getRandom(Nd4j.java:493)
        at org.deeplearning4j.nn.conf.NeuralNetConfiguration$Builder.seed(NeuralNetConfiguration.java:579)
        at org.etu.fl.dl4j.nn.converters.DL4JNeuralNetConverter.createDL4JNetwork(DL4JNeuralNetConverter.java:215)
        at org.etu.fl.dl4j.nn.engines.NNInitializationEngine.learn(NNInitializationEngine.java:40)
        at org.etu.fl.core.algorithms.EnginedBlock.execute(EnginedBlock.java:89)
        at org.etu.fl.core.algorithms.AlgorithmBlock.run(AlgorithmBlock.java:63)
        at org.etu.fl.core.algorithms.SequenceBlock.execute(SequenceBlock.java:96)
        at org.etu.fl.core.algorithms.AlgorithmBlock.run(AlgorithmBlock.java:63)
        at org.etu.fl.client.worker.ProcessingWorker.call(ProcessingWorker.java:43)
        at org.etu.fl.client.worker.ProcessingWorker.call(ProcessingWorker.java:28)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: java.lang.RuntimeException: ND4J is probably missing dependencies. For more information, please refer to: https://deeplearning4j.konduit.ai/nd4j/backend
        ...
     Caused by: java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found
        ...

At the same time, the 1.0.0-M1.1 version works fine.

5. Sources and versions.

  • The deeplearning module dependencies:
def dl4jVersion = '1.0.0-M1.1'
def openblasVersion = '0.3.10-1.5.5'
def opencvVersion = '4.5.1-1.5.5'
def leptonicaVersion = '1.80.0-1.5.5'

implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'

testImplementation 'junit:junit:4.13.2'

implementation project(path: ':fl4j-android-framework')

implementation(group: 'org.deeplearning4j', name: 'deeplearning4j-core', version: dl4jVersion) {
    exclude group: 'org.bytedeco', module: 'opencv-platform'
    exclude group: 'org.bytedeco', module: 'leptonica-platform'
    exclude group: 'org.bytedeco', module: 'hdf5-platform'
    exclude group: 'org.nd4j', module: 'nd4j-base64'
    exclude group: 'org.nd4j', module: 'nd4j-api'
}


implementation  group: 'org.nd4j', name: 'nd4j-native', version: dl4jVersion
implementation  group: 'org.nd4j', name: 'nd4j-native', version:  dl4jVersion, classifier: "android-arm"
implementation  group: 'org.nd4j', name: 'nd4j-native', version:  dl4jVersion, classifier: "android-arm64"
implementation  group: 'org.nd4j', name: 'nd4j-native', version:  dl4jVersion, classifier: "android-x86"
implementation  group: 'org.nd4j', name: 'nd4j-native', version:  dl4jVersion, classifier: "android-x86_64"

implementation  group: 'org.bytedeco', name: 'openblas', version: openblasVersion
implementation  group: 'org.bytedeco', name: 'openblas', version: openblasVersion, classifier: "android-arm"
implementation  group: 'org.bytedeco', name: 'openblas', version: openblasVersion, classifier: "android-arm64"
implementation  group: 'org.bytedeco', name: 'openblas', version: openblasVersion, classifier: "android-x86"
implementation  group: 'org.bytedeco', name: 'openblas', version: openblasVersion, classifier: "android-x86_64"

implementation  group: 'org.bytedeco', name: 'opencv', version: opencvVersion
implementation  group: 'org.bytedeco', name: 'opencv', version: opencvVersion, classifier: "android-arm"
implementation  group: 'org.bytedeco', name: 'opencv', version: opencvVersion, classifier: "android-arm64"
implementation  group: 'org.bytedeco', name: 'opencv', version: opencvVersion, classifier: "android-x86"
implementation  group: 'org.bytedeco', name: 'opencv', version: opencvVersion, classifier: "android-x86_64"

implementation  group: 'org.bytedeco', name: 'leptonica', version: leptonicaVersion
implementation  group: 'org.bytedeco', name: 'leptonica', version: leptonicaVersion, classifier: "android-arm"
implementation  group: 'org.bytedeco', name: 'leptonica', version: leptonicaVersion, classifier: "android-arm64"
implementation  group: 'org.bytedeco', name: 'leptonica', version: leptonicaVersion, classifier: "android-x86"
implementation  group: 'org.bytedeco', name: 'leptonica', version: leptonicaVersion, classifier: "android-x86_64"

annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.4'

implementation "org.deeplearning4j:deeplearning4j-core:1.0.0-beta7"
implementation "org.nd4j:nd4j-native-platform:1.0.0-beta7"

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
  • The framework module dependencies:
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'

testImplementation 'junit:junit:4.13.2'

implementation 'io.grpc:grpc-netty-shaded:1.44.1'
implementation 'io.grpc:grpc-android:1.44.1'
implementation 'io.grpc:grpc-protobuf-lite:1.44.1'
implementation 'io.grpc:grpc-stub:1.44.1'

implementation 'net.javacrumbs.future-converter:future-converter-java8-guava:1.2.0'

compileOnly 'org.apache.tomcat:annotations-api:6.0.53'

implementation 'com.google.code.gson:gson:2.8.9'

implementation group: 'com.opencsv', name: 'opencsv', version: '5.2'
implementation group: 'org.postgresql', name: 'postgresql', version: '42.2.12'

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
  • Android SDK versions:
compileSdkVersion 31
minSdkVersion 28
targetSdkVersion 30
  • Android gradle plugin version: com.android.tools.build:gradle:4.2.2
  • Gradle version: gradle-6.8.1-bin

@UnDan yes please use the updated versions. Can you clarify if this is still a problem with the newer versions?

@agibsonccc yep, this issue remains on 1.0.0-M1.1 version too.

UPD:
I forgot to mention that in addition to gRPC dependencies there is one more dependency conflicting with ND4J:

implementation 'net.javacrumbs.future-converter:future-converter-java8-guava:1.2.0'

This dependency causes this:

Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules guava-20.0.jar (com.google.guava:guava:20.0) and listenablefuture-1.0.jar (com.google.guava:listenablefuture:1.0)
Duplicate class com.google.thirdparty.publicsuffix.PublicSuffixPatterns found in modules guava-20.0.jar (com.google.guava:guava:20.0) and protobuf-1.0.0-M1.1.jar (org.nd4j:protobuf:1.0.0-M1.1)
Duplicate class com.google.thirdparty.publicsuffix.PublicSuffixType found in modules guava-20.0.jar (com.google.guava:guava:20.0) and protobuf-1.0.0-M1.1.jar (org.nd4j:protobuf:1.0.0-M1.1)
Duplicate class com.google.thirdparty.publicsuffix.TrieParser found in modules guava-20.0.jar (com.google.guava:guava:20.0) and protobuf-1.0.0-M1.1.jar (org.nd4j:protobuf:1.0.0-M1.1)