ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sboi...@apache.org
Subject [13/22] ignite git commit: IGNITE-6783: Create common mechanism for group training.
Date Tue, 09 Jan 2018 07:36:49 GMT
http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/ComputationsChain.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/ComputationsChain.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/ComputationsChain.java
new file mode 100644
index 0000000..080494c
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/ComputationsChain.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group.chain;
+
+import java.io.Serializable;
+import java.util.UUID;
+import java.util.stream.Stream;
+import javax.cache.processor.EntryProcessor;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.cluster.ClusterGroup;
+import org.apache.ignite.lang.IgniteBiPredicate;
+import org.apache.ignite.ml.math.functions.Functions;
+import org.apache.ignite.ml.math.functions.IgniteBiFunction;
+import org.apache.ignite.ml.math.functions.IgniteBinaryOperator;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+import org.apache.ignite.ml.trainers.group.GroupTrainerCacheKey;
+import org.apache.ignite.ml.trainers.group.GroupTrainerEntriesProcessorTask;
+import org.apache.ignite.ml.trainers.group.GroupTrainerKeysProcessorTask;
+import org.apache.ignite.ml.trainers.group.GroupTrainingContext;
+import org.apache.ignite.ml.trainers.group.ResultAndUpdates;
+
+/**
+ * This class encapsulates convenient way for creating computations chain for distributed model training.
+ * Chain is meant in the sense that output of each non-final computation is fed as input to next computation.
+ * Chain is basically a bi-function from context and input to output, context is separated from input
+ * because input is specific to each individual step and context is something which is convenient to have access to in each of steps.
+ * Context is separated into two parts: local context and remote context.
+ * There are two kinds of computations: local and distributed.
+ * Local steps are just functions from two arguments: input and local context.
+ * Distributed steps are more sophisticated, but basically can be thought as functions of form
+ * localContext -> (function of remote context -> output), locally we fix local context and get function
+ * (function of remote context -> output) which is executed distributed.
+ * Chains are composable through 'then' method.
+ *
+ * @param <L> Type of local context.
+ * @param <K> Type of cache keys.
+ * @param <V> Type of cache values.
+ * @param <I> Type of input of this chain.
+ * @param <O> Type of output of this chain.
+ * // TODO: IGNITE-7322 check if it is possible to integrate with {@link EntryProcessor}.
+ */
+@FunctionalInterface
+public interface ComputationsChain<L extends HasTrainingUUID, K, V, I, O> {
+    /**
+     * Process given input and {@link GroupTrainingContext}.
+     *
+     * @param input Computation chain input.
+     * @param ctx {@link GroupTrainingContext}.
+     * @return Result of processing input and context.
+     */
+    O process(I input, GroupTrainingContext<K, V, L> ctx);
+
+    /**
+     * Add a local step to this chain.
+     *
+     * @param locStep Local step.
+     * @param <O1> Output of local step.
+     * @return Composition of this chain and local step.
+     */
+    default <O1> ComputationsChain<L, K, V, I, O1> thenLocally(IgniteBiFunction<O, L, O1> locStep) {
+        ComputationsChain<L, K, V, O, O1> nextStep = (input, context) -> locStep.apply(input, context.localContext());
+        return then(nextStep);
+    }
+
+    /**
+     * Add a distributed step which works in the following way:
+     * 1. apply local context and input to local context extractor and keys supplier to get corresponding suppliers;
+     * 2. on each node_n
+     * 2.1. get context object.
+     * 2.2. for each entry_i e located on node_n with key_i from keys stream compute worker((context, entry_i)) and get
+     * (cachesUpdates_i, result_i).
+     * 2.3. for all i on node_n merge cacheUpdates_i and apply them.
+     * 2.4. for all i on node_n, reduce result_i into result_n.
+     * 3. get all result_n, reduce them into result and return result.
+     *
+     * @param <O1> Type of worker output.
+     * @param <G> Type of context used by worker.
+     * @param workerCtxExtractor Extractor of context for worker.
+     * @param worker Function computed on each entry of cache used for training. Second argument is context:
+     * common part of data which is independent from key.
+     * @param ks Function from chain input and local context to supplier of keys for worker.
+     * @param reducer Function used for reducing results of worker.
+     * @param identity Identity for reducer.
+     * @return Combination of this chain and distributed step specified by given parameters.
+     */
+    default <O1 extends Serializable, G> ComputationsChain<L, K, V, I, O1> thenDistributedForEntries(
+        IgniteBiFunction<O, L, IgniteSupplier<G>> workerCtxExtractor,
+        IgniteFunction<EntryAndContext<K, V, G>, ResultAndUpdates<O1>> worker,
+        IgniteBiFunction<O, L, IgniteSupplier<Stream<GroupTrainerCacheKey<K>>>> ks,
+        IgniteBinaryOperator<O1> reducer, O1 identity) {
+        ComputationsChain<L, K, V, O, O1> nextStep = (input, context) -> {
+            L locCtx = context.localContext();
+            IgniteSupplier<Stream<GroupTrainerCacheKey<K>>> keysSupplier = ks.apply(input, locCtx);
+
+            Ignite ignite = context.ignite();
+            UUID trainingUUID = context.localContext().trainingUUID();
+            String cacheName = context.cache().getName();
+            ClusterGroup grp = ignite.cluster().forDataNodes(cacheName);
+
+            // Apply first two arguments locally because it is common for all nodes.
+            IgniteSupplier<G> extractor = Functions.curry(workerCtxExtractor).apply(input).apply(locCtx);
+
+            return ignite.compute(grp).execute(new GroupTrainerEntriesProcessorTask<>(trainingUUID, extractor, worker, keysSupplier, reducer, identity, cacheName, ignite), null);
+        };
+        return then(nextStep);
+    }
+
+    /**
+     * Add a distributed step which works in the following way:
+     * 1. apply local context and input to local context extractor and keys supplier to get corresponding suppliers;
+     * 2. on each node_n
+     * 2.1. get context object.
+     * 2.2. for each key_i from keys stream such that key_i located on node_n compute worker((context, entry_i)) and get
+     * (cachesUpdates_i, result_i).
+     * 2.3. for all i on node_n merge cacheUpdates_i and apply them.
+     * 2.4. for all i on node_n, reduce result_i into result_n.
+     * 3. get all result_n, reduce them into result and return result.
+     *
+     * @param <O1> Type of worker output.
+     * @param <G> Type of context used by worker.
+     * @param workerCtxExtractor Extractor of context for worker.
+     * @param worker Function computed on each entry of cache used for training. Second argument is context:
+     * common part of data which is independent from key.
+     * @param keysSupplier Function from chain input and local context to supplier of keys for worker.
+     * @param reducer Function used for reducing results of worker.
+     * @param identity Identity for reducer.
+     * @return Combination of this chain and distributed step specified by given parameters.
+     */
+    default <O1 extends Serializable, G> ComputationsChain<L, K, V, I, O1> thenDistributedForKeys(
+        IgniteBiFunction<O, L, IgniteSupplier<G>> workerCtxExtractor,
+        IgniteFunction<KeyAndContext<K, G>, ResultAndUpdates<O1>> worker,
+        IgniteBiFunction<O, L, IgniteSupplier<Stream<GroupTrainerCacheKey<K>>>> keysSupplier,
+        IgniteBinaryOperator<O1> reducer, O1 identity) {
+        ComputationsChain<L, K, V, O, O1> nextStep = (input, context) -> {
+            L locCtx = context.localContext();
+            IgniteSupplier<Stream<GroupTrainerCacheKey<K>>> ks = keysSupplier.apply(input, locCtx);
+
+            Ignite ignite = context.ignite();
+            UUID trainingUUID = context.localContext().trainingUUID();
+            String cacheName = context.cache().getName();
+            ClusterGroup grp = ignite.cluster().forDataNodes(cacheName);
+
+            // Apply first argument locally because it is common for all nodes.
+            IgniteSupplier<G> extractor = Functions.curry(workerCtxExtractor).apply(input).apply(locCtx);
+
+            return ignite.compute(grp).execute(new GroupTrainerKeysProcessorTask<>(trainingUUID, extractor, worker, ks, reducer, identity, cacheName, ignite), null);
+        };
+        return then(nextStep);
+    }
+
+    /**
+     * Add a distributed step specified by {@link DistributedEntryProcessingStep}.
+     *
+     * @param step Distributed step.
+     * @param <O1> Type of output of distributed step.
+     * @param <G> Type of context of distributed step.
+     * @return Combination of this chain and distributed step specified by input.
+     */
+    default <O1 extends Serializable, G> ComputationsChain<L, K, V, I, O1> thenDistributedForEntries(
+        DistributedEntryProcessingStep<L, K, V, G, O, O1> step) {
+        return thenDistributedForEntries(step::remoteContextSupplier, step.worker(), step::keys, step.reducer(), step.identity());
+    }
+
+    /**
+     * Add a distributed step specified by {@link DistributedKeyProcessingStep}.
+     *
+     * @param step Distributed step.
+     * @param <O1> Type of output of distributed step.
+     * @param <G> Type of context of distributed step.
+     * @return Combination of this chain and distributed step specified by input.
+     */
+    default <O1 extends Serializable, G> ComputationsChain<L, K, V, I, O1> thenDistributedForKeys(
+        DistributedKeyProcessingStep<L, K, G, O, O1> step) {
+        return thenDistributedForKeys(step::remoteContextSupplier, step.worker(), step::keys, step.reducer(), step.identity());
+    }
+
+    /**
+     * Version of 'thenDistributedForKeys' where worker does not depend on context.
+     *
+     * @param worker Worker.
+     * @param kf Function providing supplier
+     * @param reducer Function from chain input and local context to supplier of keys for worker.
+     * @param <O1> Type of worker output.
+     * @return Combination of this chain and distributed step specified by given parameters.
+     */
+    default <O1 extends Serializable> ComputationsChain<L, K, V, I, O1> thenDistributedForKeys(
+        IgniteFunction<GroupTrainerCacheKey<K>, ResultAndUpdates<O1>> worker,
+        IgniteBiFunction<O, L, IgniteSupplier<Stream<GroupTrainerCacheKey<K>>>> kf,
+        IgniteBinaryOperator<O1> reducer) {
+
+        return thenDistributedForKeys((o, lc) -> () -> o, (context) -> worker.apply(context.key()), kf, reducer, null);
+    }
+
+    /**
+     * Combine this computation chain with other computation chain in the following way:
+     * 1. perform this calculations chain and get result r.
+     * 2. while 'cond(r)' is true, r = otherChain(r, context)
+     * 3. return r.
+     *
+     * @param cond Condition checking if 'while' loop should continue.
+     * @param otherChain Chain to be combined with this chain.
+     * @return Combination of this chain and otherChain.
+     */
+    default ComputationsChain<L, K, V, I, O> thenWhile(IgniteBiPredicate<O, L> cond,
+        ComputationsChain<L, K, V, O, O> otherChain) {
+        ComputationsChain<L, K, V, I, O> me = this;
+        return (input, context) -> {
+            O res = me.process(input, context);
+
+            while (cond.apply(res, context.localContext()))
+                res = otherChain.process(res, context);
+
+            return res;
+        };
+    }
+
+    /**
+     * Combine two this chain to other: feed this chain as input to other, pass same context as second argument to both chains
+     * process method.
+     *
+     * @param next Next chain.
+     * @param <O1> Type of next chain output.
+     * @return Combined chain.
+     */
+    default <O1> ComputationsChain<L, K, V, I, O1> then(ComputationsChain<L, K, V, O, O1> next) {
+        ComputationsChain<L, K, V, I, O> me = this;
+        return (input, context) -> {
+            O myRes = me.process(input, context);
+            return next.process(myRes, context);
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedEntryProcessingStep.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedEntryProcessingStep.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedEntryProcessingStep.java
new file mode 100644
index 0000000..8fd1264
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedEntryProcessingStep.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group.chain;
+
+import java.io.Serializable;
+
+/**
+ * {@link DistributedStep} specialized to {@link EntryAndContext}.
+ *
+ * @param <L> Local context.
+ * @param <K> Type of keys of cache used for group training.
+ * @param <V> Type of values of cache used for group training.
+ * @param <C> Context used by worker.
+ * @param <I> Type of input to this step.
+ * @param <O> Type of output of this step.
+ */
+public interface DistributedEntryProcessingStep<L, K, V, C, I, O extends Serializable> extends
+    DistributedStep<EntryAndContext<K, V, C>, L, K, C, I, O> {
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedKeyProcessingStep.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedKeyProcessingStep.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedKeyProcessingStep.java
new file mode 100644
index 0000000..fb8d867
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedKeyProcessingStep.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group.chain;
+
+import java.io.Serializable;
+
+/**
+ * {@link DistributedStep} specialized to {@link KeyAndContext}.
+ *
+ * @param <L> Local context.
+ * @param <K> Type of keys of cache used for group training.
+ * @param <C> Context used by worker.
+ * @param <I> Type of input to this step.
+ * @param <O> Type of output of this step.
+ */
+public interface DistributedKeyProcessingStep<L, K, C, I, O extends Serializable> extends
+    DistributedStep<KeyAndContext<K, C>, L, K, C, I, O> {
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedStep.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedStep.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedStep.java
new file mode 100644
index 0000000..7ddc649
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/DistributedStep.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group.chain;
+
+import java.io.Serializable;
+import java.util.stream.Stream;
+import org.apache.ignite.ml.math.functions.IgniteBinaryOperator;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+import org.apache.ignite.ml.trainers.group.GroupTrainerCacheKey;
+import org.apache.ignite.ml.trainers.group.ResultAndUpdates;
+
+/**
+ * Class encapsulating logic of distributed step in {@link ComputationsChain}.
+ *
+ * @param <T> Type of elements to be processed by worker.
+ * @param <L> Local context.
+ * @param <K> Type of keys of cache used for group training.
+ * @param <C> Context used by worker.
+ * @param <I> Type of input to this step.
+ * @param <O> Type of output of this step.
+ */
+public interface DistributedStep<T, L, K, C, I, O extends Serializable> {
+    /**
+     * Create supplier of context used by worker.
+     *
+     * @param input Input.
+     * @param locCtx Local context.
+     * @return Context used by worker.
+     */
+    IgniteSupplier<C> remoteContextSupplier(I input, L locCtx);
+
+    /**
+     * Get function applied to each cache element specified by keys.
+     *
+     * @return Function applied to each cache entry specified by keys..
+     */
+    IgniteFunction<T, ResultAndUpdates<O>> worker();
+
+    /**
+     * Get supplier of keys for worker.
+     *
+     * @param input Input to this step.
+     * @param locCtx Local context.
+     * @return Keys for worker.
+     */
+    IgniteSupplier<Stream<GroupTrainerCacheKey<K>>> keys(I input, L locCtx);
+
+    /**
+     * Get function used to reduce results returned by worker.
+     *
+     * @return Function used to reduce results returned by worker..
+     */
+    IgniteBinaryOperator<O> reducer();
+
+    /**
+     * Identity for reduce.
+     *
+     * @return Identity for reduce.
+     */
+    O identity();
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/EntryAndContext.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/EntryAndContext.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/EntryAndContext.java
new file mode 100644
index 0000000..59c3b34
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/EntryAndContext.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group.chain;
+
+import java.util.Map;
+import org.apache.ignite.ml.trainers.group.GroupTrainerCacheKey;
+
+/**
+ * Entry of cache used for group training and context.
+ * This class is used as input for workers of distributed steps of {@link ComputationsChain}.
+ *
+ * @param <K> Type of cache keys used for training.
+ * @param <V> Type of cache values used for training.
+ * @param <C> Type of context.
+ */
+public class EntryAndContext<K, V, C> {
+    /**
+     * Entry of cache used for training.
+     */
+    private Map.Entry<GroupTrainerCacheKey<K>, V> entry;
+
+    /**
+     * Context.
+     */
+    private C ctx;
+
+    /**
+     * Construct instance of this class.
+     *
+     * @param entry Entry of cache used for training.
+     * @param ctx Context.
+     */
+    public EntryAndContext(Map.Entry<GroupTrainerCacheKey<K>, V> entry, C ctx) {
+        this.entry = entry;
+        this.ctx = ctx;
+    }
+
+    /**
+     * Get entry of cache used for training.
+     *
+     * @return Entry of cache used for training.
+     */
+    public Map.Entry<GroupTrainerCacheKey<K>, V> entry() {
+        return entry;
+    }
+
+    /**
+     * Get context.
+     *
+     * @return Context.
+     */
+    public C context() {
+        return ctx;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/HasTrainingUUID.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/HasTrainingUUID.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/HasTrainingUUID.java
new file mode 100644
index 0000000..d855adf
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/HasTrainingUUID.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group.chain;
+
+import java.util.UUID;
+
+/**
+ * Interface for classes which contain UUID of training.
+ */
+public interface HasTrainingUUID {
+    /**
+     * Get training UUID.
+     *
+     * @return Training UUID.
+     */
+    UUID trainingUUID();
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/KeyAndContext.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/KeyAndContext.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/KeyAndContext.java
new file mode 100644
index 0000000..ba36e0e
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/KeyAndContext.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group.chain;
+
+import org.apache.ignite.ml.trainers.group.GroupTrainerCacheKey;
+
+/**
+ * Class containing key and remote context (see explanation of remote context in {@link ComputationsChain}).
+ *
+ * @param <K> Cache key type.
+ * @param <C> Remote context.
+ */
+public class KeyAndContext<K, C> {
+    /**
+     * Key of group trainer.
+     */
+    private GroupTrainerCacheKey<K> key;
+
+    /**
+     * Remote context.
+     */
+    private C ctx;
+
+    /**
+     * Construct instance of this class.
+     *
+     * @param key Cache key.
+     * @param ctx Remote context.
+     */
+    public KeyAndContext(GroupTrainerCacheKey<K> key, C ctx) {
+        this.key = key;
+        this.ctx = ctx;
+    }
+
+    /**
+     * Get group trainer cache key.
+     *
+     * @return Group trainer cache key.
+     */
+    public GroupTrainerCacheKey<K> key() {
+        return key;
+    }
+
+    /**
+     * Get remote context.
+     *
+     * @return Remote context.
+     */
+    public C context() {
+        return ctx;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/package-info.java
new file mode 100644
index 0000000..46dcc6b
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/chain/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * <!-- Package description. -->
+ * Contains classes related to computations chain.
+ */
+package org.apache.ignite.ml.trainers.group.chain;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/package-info.java
new file mode 100644
index 0000000..9b7f7cd
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/group/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * <!-- Package description. -->
+ * Contains group trainers.
+ */
+package org.apache.ignite.ml.trainers.group;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/trainers/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trainers/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/package-info.java
new file mode 100644
index 0000000..b6e4fe2
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trainers/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * <!-- Package description. -->
+ * Contains model trainers.
+ */
+package org.apache.ignite.ml.trainers;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java b/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java
index a3f1d21..a30cfe9 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java
@@ -38,7 +38,7 @@ public class MnistUtils {
      *
      * @param imagesPath Path to the file with images.
      * @param labelsPath Path to the file with labels.
-     * @param rnd Random numbers generatror.
+     * @param rnd Random numbers generator.
      * @param cnt Count of samples to read.
      * @return Stream of MNIST samples.
      * @throws IOException In case of exception.
@@ -85,7 +85,7 @@ public class MnistUtils {
      * @param outPath Path to output path.
      * @param rnd Random numbers generator.
      * @param cnt Count of samples to read.
-     * @throws IOException In case of exception.
+     * @throws IgniteException In case of exception.
      */
     public static void asLIBSVM(String imagesPath, String labelsPath, String outPath, Random rnd, int cnt)
         throws IOException {
@@ -121,4 +121,4 @@ public class MnistUtils {
     private static int read4Bytes(FileInputStream is) throws IOException {
         return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | (is.read());
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java b/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
index fafd364..35ffdbc 100644
--- a/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
@@ -22,6 +22,7 @@ import org.apache.ignite.ml.knn.KNNTestSuite;
 import org.apache.ignite.ml.math.MathImplMainTestSuite;
 import org.apache.ignite.ml.nn.MLPTestSuite;
 import org.apache.ignite.ml.regressions.RegressionsTestSuite;
+import org.apache.ignite.ml.trainers.group.TrainersGroupTestSuite;
 import org.apache.ignite.ml.trees.DecisionTreesTestSuite;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -37,7 +38,8 @@ import org.junit.runners.Suite;
     DecisionTreesTestSuite.class,
     KNNTestSuite.class,
     LocalModelsTest.class,
-    MLPTestSuite.class
+    MLPTestSuite.class,
+    TrainersGroupTestSuite.class
 })
 public class IgniteMLTestSuite {
     // No-op.

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/DistributedWorkersChainTest.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/DistributedWorkersChainTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/DistributedWorkersChainTest.java
new file mode 100644
index 0000000..987595d
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/DistributedWorkersChainTest.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.ml.math.functions.IgniteBiFunction;
+import org.apache.ignite.ml.math.functions.IgniteBinaryOperator;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+import org.apache.ignite.ml.trainers.group.chain.Chains;
+import org.apache.ignite.ml.trainers.group.chain.ComputationsChain;
+import org.apache.ignite.ml.trainers.group.chain.EntryAndContext;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Assert;
+
+/** */
+public class DistributedWorkersChainTest extends GridCommonAbstractTest {
+    /** Count of nodes. */
+    private static final int NODE_COUNT = 3;
+
+    /** Grid instance. */
+    private Ignite ignite;
+
+    /**
+     * Default constructor.
+     */
+    public DistributedWorkersChainTest() {
+        super(false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override protected void beforeTest() throws Exception {
+        ignite = grid(NODE_COUNT);
+        TestGroupTrainingCache.getOrCreate(ignite).removeAll();
+        TestGroupTrainingSecondCache.getOrCreate(ignite).removeAll();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        for (int i = 1; i <= NODE_COUNT; i++)
+            startGrid(i);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** */
+    public void testId() {
+        ComputationsChain<TestLocalContext, Double, Integer, Integer, Integer> chain = Chains.create();
+
+        UUID trainingUUID = UUID.randomUUID();
+        Integer res = chain.process(1, new GroupTrainingContext<>(new TestLocalContext(0, trainingUUID), TestGroupTrainingCache.getOrCreate(ignite), ignite));
+
+        Assert.assertEquals(1L, (long)res);
+    }
+
+    /** */
+    public void testSimpleLocal() {
+        ComputationsChain<TestLocalContext, Double, Integer, Integer, Integer> chain = Chains.create();
+
+        IgniteCache<GroupTrainerCacheKey<Double>, Integer> cache = TestGroupTrainingCache.getOrCreate(ignite);
+        int init = 1;
+        int initLocCtxData = 0;
+        UUID trainingUUID = UUID.randomUUID();
+        TestLocalContext locCtx = new TestLocalContext(initLocCtxData, trainingUUID);
+
+        Integer res = chain.
+            thenLocally((prev, lc) -> prev + 1).
+            process(init, new GroupTrainingContext<>(locCtx, cache, ignite));
+
+        Assert.assertEquals(init + 1, (long)res);
+        Assert.assertEquals(initLocCtxData, locCtx.data());
+    }
+
+    /** */
+    public void testChainLocal() {
+        ComputationsChain<TestLocalContext, Double, Integer, Integer, Integer> chain = Chains.create();
+
+        IgniteCache<GroupTrainerCacheKey<Double>, Integer> cache = TestGroupTrainingCache.getOrCreate(ignite);
+        int init = 1;
+        int initLocCtxData = 0;
+        UUID trainingUUID = UUID.randomUUID();
+        TestLocalContext locCtx = new TestLocalContext(initLocCtxData, trainingUUID);
+
+        Integer res = chain.
+            thenLocally((prev, lc) -> prev + 1).
+            thenLocally((prev, lc) -> prev * 5).
+            process(init, new GroupTrainingContext<>(locCtx, cache, ignite));
+
+        Assert.assertEquals((init + 1) * 5, (long)res);
+        Assert.assertEquals(initLocCtxData, locCtx.data());
+    }
+
+    /** */
+    public void testChangeLocalContext() {
+        ComputationsChain<TestLocalContext, Double, Integer, Integer, Integer> chain = Chains.create();
+        IgniteCache<GroupTrainerCacheKey<Double>, Integer> cache = TestGroupTrainingCache.getOrCreate(ignite);
+        int init = 1;
+        int newData = 10;
+        UUID trainingUUID = UUID.randomUUID();
+        TestLocalContext locCtx = new TestLocalContext(0, trainingUUID);
+
+        Integer res = chain.
+            thenLocally((prev, lc) -> { lc.setData(newData); return prev;}).
+            process(init, new GroupTrainingContext<>(locCtx, cache, ignite));
+
+        Assert.assertEquals(newData, locCtx.data());
+        Assert.assertEquals(init, res.intValue());
+    }
+
+    /** */
+    public void testDistributed() {
+        ComputationsChain<TestLocalContext, Double, Integer, Integer, Integer> chain = Chains.create();
+        IgniteCache<GroupTrainerCacheKey<Double>, Integer> cache = TestGroupTrainingCache.getOrCreate(ignite);
+        int init = 1;
+        UUID trainingUUID = UUID.randomUUID();
+        TestLocalContext locCtx = new TestLocalContext(0, trainingUUID);
+
+        Map<GroupTrainerCacheKey<Double>, Integer> m = new HashMap<>();
+        m.put(new GroupTrainerCacheKey<>(0L, 1.0, trainingUUID), 1);
+        m.put(new GroupTrainerCacheKey<>(1L, 2.0, trainingUUID), 2);
+        m.put(new GroupTrainerCacheKey<>(2L, 3.0, trainingUUID), 3);
+        m.put(new GroupTrainerCacheKey<>(3L, 4.0, trainingUUID), 4);
+
+        Stream<GroupTrainerCacheKey<Double>> keys = m.keySet().stream();
+
+        cache.putAll(m);
+
+        IgniteBiFunction<Integer, TestLocalContext, IgniteSupplier<Stream<GroupTrainerCacheKey<Double>>>> function = (o, l) -> () -> keys;
+        IgniteBinaryOperator<Integer> max = Integer::max;
+
+        Integer res = chain.
+            thenDistributedForEntries((integer, context) -> () -> null, this::readAndIncrement, function, max, Integer.MIN_VALUE).
+            process(init, new GroupTrainingContext<>(locCtx, cache, ignite));
+
+        int locMax = m.values().stream().max(Comparator.comparingInt(i -> i)).orElse(Integer.MIN_VALUE);
+
+        assertEquals((long)locMax, (long)res);
+
+        for (GroupTrainerCacheKey<Double> key : m.keySet())
+            m.compute(key, (k, v) -> v + 1);
+
+        assertMapEqualsCache(m, cache);
+    }
+
+    /** */
+    private ResultAndUpdates<Integer> readAndIncrement(EntryAndContext<Double, Integer, Void> ec) {
+        Integer val = ec.entry().getValue();
+
+        ResultAndUpdates<Integer> res = ResultAndUpdates.of(val);
+        res.update(TestGroupTrainingCache.getOrCreate(Ignition.localIgnite()), ec.entry().getKey(), val + 1);
+
+        return res;
+    }
+
+    /** */
+    private <K, V> void assertMapEqualsCache(Map<K, V> m, IgniteCache<K, V> cache) {
+        assertEquals(m.size(), cache.size());
+
+        for (K k : m.keySet())
+            assertEquals(m.get(k), cache.get(k));
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/GroupTrainerTest.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/GroupTrainerTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/GroupTrainerTest.java
new file mode 100644
index 0000000..5bb9a47
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/GroupTrainerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+/**
+ * Test of {@link GroupTrainer}.
+ */
+public class GroupTrainerTest extends GridCommonAbstractTest {
+    /** Count of nodes. */
+    private static final int NODE_COUNT = 3;
+
+    /** Grid instance. */
+    private Ignite ignite;
+
+    /**
+     * Default constructor.
+     */
+    public GroupTrainerTest() {
+        super(false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override protected void beforeTest() throws Exception {
+        ignite = grid(NODE_COUNT);
+        TestGroupTrainingCache.getOrCreate(ignite).removeAll();
+        TestGroupTrainingSecondCache.getOrCreate(ignite).removeAll();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        for (int i = 1; i <= NODE_COUNT; i++)
+            startGrid(i);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** */
+    public void testGroupTrainer() {
+        TestGroupTrainer trainer = new TestGroupTrainer(ignite);
+
+        int limit = 5;
+        int eachNumCnt = 3;
+        int iterCnt = 2;
+
+        ConstModel<Integer> mdl = trainer.train(new SimpleGroupTrainerInput(limit, eachNumCnt, iterCnt));
+        int locRes = computeLocally(limit, eachNumCnt, iterCnt);
+        assertEquals(locRes, (int)mdl.apply(10));
+    }
+
+    /** */
+    private int computeLocally(int limit, int eachNumCnt, int iterCnt) {
+        Map<GroupTrainerCacheKey<Double>, Integer> m = new HashMap<>();
+
+        for (int i = 0; i < limit; i++) {
+            for (int j = 0; j < eachNumCnt; j++)
+                m.put(new GroupTrainerCacheKey<>(i, (double)j, null), i);
+        }
+
+        for (int i = 0; i < iterCnt; i++)
+            for (GroupTrainerCacheKey<Double> key : m.keySet())
+                m.compute(key, (key1, integer) -> integer * integer);
+
+        return m.values().stream().filter(x -> x % 2 == 0).mapToInt(i -> i).sum();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/SimpleGroupTrainerInput.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/SimpleGroupTrainerInput.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/SimpleGroupTrainerInput.java
new file mode 100644
index 0000000..efca26a
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/SimpleGroupTrainerInput.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.UUID;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+
+/** */
+class SimpleGroupTrainerInput implements GroupTrainerInput<Double> {
+    /** */
+    private final int limit;
+    /** */
+    private final int eachNumCnt;
+    /** */
+    private final int iterCnt;
+
+    /** */
+    SimpleGroupTrainerInput(int limit, int eachNumCnt, int iterCnt) {
+        this.limit = limit;
+        this.eachNumCnt = eachNumCnt;
+        this.iterCnt = iterCnt;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteSupplier<Stream<GroupTrainerCacheKey<Double>>> initialKeys(UUID trainingUUID) {
+        return () -> IntStream.range(0, limit).mapToObj(i -> new GroupTrainerCacheKey<>(i, 0.0, trainingUUID));
+    }
+
+    /** */
+    int limit() {
+        return limit;
+    }
+
+    /** */
+    int iterCnt() {
+        return iterCnt;
+    }
+
+    /** */
+    int eachNumberCount() {
+        return eachNumCnt;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainer.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainer.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainer.java
new file mode 100644
index 0000000..75be373
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainer.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.ml.math.functions.IgniteBinaryOperator;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+import org.apache.ignite.ml.trainers.group.chain.Chains;
+import org.apache.ignite.ml.trainers.group.chain.ComputationsChain;
+import org.apache.ignite.ml.trainers.group.chain.EntryAndContext;
+
+/**
+ * Test group trainer.
+ */
+class TestGroupTrainer extends GroupTrainer<TestGroupTrainerLocalContext, Double, Integer, Integer, Integer, Double,
+    ConstModel<Integer>, SimpleGroupTrainerInput, Void> {
+    /**
+     * Construct instance of this class with given parameters.
+     *
+     * @param ignite Ignite instance.
+     */
+    TestGroupTrainer(Ignite ignite) {
+        super(TestGroupTrainingCache.getOrCreate(ignite), ignite);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected TestGroupTrainerLocalContext initialLocalContext(SimpleGroupTrainerInput data,
+        UUID trainingUUID) {
+        return new TestGroupTrainerLocalContext(data.iterCnt(), data.eachNumberCount(), data.limit(), trainingUUID);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteFunction<GroupTrainerCacheKey<Double>, ResultAndUpdates<Integer>> distributedInitializer(
+        SimpleGroupTrainerInput data) {
+        return key -> {
+            long i = key.nodeLocalEntityIndex();
+            UUID trainingUUID = key.trainingUUID();
+            IgniteCache<GroupTrainerCacheKey<Double>, Integer> cache = TestGroupTrainingCache.getOrCreate(Ignition.localIgnite());
+
+            long sum = i * data.eachNumberCount();
+
+            ResultAndUpdates<Integer> res = ResultAndUpdates.of((int)sum);
+
+            for (int j = 0; j < data.eachNumberCount(); j++)
+                res.update(cache, new GroupTrainerCacheKey<>(i, (double)j, trainingUUID), (int)i);
+
+            return res;
+        };
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteBinaryOperator<Integer> reduceDistributedInitData() {
+        return (a, b) -> a + b;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Double locallyProcessInitData(Integer data, TestGroupTrainerLocalContext locCtx) {
+        return data.doubleValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected ComputationsChain<TestGroupTrainerLocalContext,
+        Double, Integer, Double, Double> trainingLoopStep() {
+        ComputationsChain<TestGroupTrainerLocalContext, Double, Integer, Double, Double> chain = Chains.
+            create(new TestTrainingLoopStep());
+        return chain.
+            thenLocally((aDouble, context) -> {
+                context.incCnt();
+                return aDouble;
+            });
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean shouldContinue(Double data, TestGroupTrainerLocalContext locCtx) {
+        return locCtx.cnt() < locCtx.maxCnt();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteSupplier<Void> extractContextForFinalResultCreation(Double data,
+        TestGroupTrainerLocalContext locCtx) {
+        // No context is needed.
+        return () -> null;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteSupplier<Stream<GroupTrainerCacheKey<Double>>> finalResultKeys(Double data,
+        TestGroupTrainerLocalContext locCtx) {
+        int limit = locCtx.limit();
+        int cnt = locCtx.eachNumberCnt();
+        UUID uuid = locCtx.trainingUUID();
+
+        return () -> TestGroupTrainingCache.allKeys(limit, cnt, uuid);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteFunction<EntryAndContext<Double, Integer, Void>, ResultAndUpdates<Integer>> finalResultsExtractor() {
+        return entryAndCtx -> {
+            Integer val = entryAndCtx.entry().getValue();
+            return ResultAndUpdates.of(val % 2 == 0 ? val : 0);
+        };
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Integer defaultFinalResult() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteBinaryOperator<Integer> finalResultsReducer() {
+        return (a, b) -> a + b;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected ConstModel<Integer> mapFinalResult(Integer res, TestGroupTrainerLocalContext locCtx) {
+        return new ConstModel<>(res);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void cleanup(TestGroupTrainerLocalContext locCtx) {
+        Stream<GroupTrainerCacheKey<Double>> toRemote = TestGroupTrainingCache.allKeys(locCtx.limit(), locCtx.eachNumberCnt(), locCtx.trainingUUID());
+        TestGroupTrainingCache.getOrCreate(ignite).removeAll(toRemote.collect(Collectors.toSet()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainerLocalContext.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainerLocalContext.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainerLocalContext.java
new file mode 100644
index 0000000..b8db56e
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainerLocalContext.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.UUID;
+import org.apache.ignite.ml.trainers.group.chain.HasTrainingUUID;
+
+/** */
+class TestGroupTrainerLocalContext implements HasTrainingUUID {
+    /** */
+    private int cnt;
+    /** */
+    private final int maxCnt;
+    /** */
+    private final int eachNumCnt;
+    /** */
+    private final int limit;
+    /** */
+    private final UUID trainingUUID;
+
+    /** */
+    TestGroupTrainerLocalContext(int maxCnt, int eachNumCnt, int limit, UUID trainingUUID) {
+        this.maxCnt = maxCnt;
+        this.eachNumCnt = eachNumCnt;
+        this.limit = limit;
+        this.trainingUUID = trainingUUID;
+        this.cnt = 0;
+    }
+
+    /** */
+    int cnt() {
+        return cnt;
+    }
+
+    /** */
+    void incCnt() {
+        this.cnt++;
+    }
+
+    /** */
+    int maxCnt() {
+        return maxCnt;
+    }
+
+    /** */
+    int eachNumberCnt() {
+        return eachNumCnt;
+    }
+
+    /** */
+    int limit() {
+        return limit;
+    }
+
+    /** {@inheritDoc} */
+    @Override public UUID trainingUUID() {
+        return trainingUUID;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingCache.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingCache.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingCache.java
new file mode 100644
index 0000000..e7826ae
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingCache.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.Arrays;
+import java.util.UUID;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+
+/** */
+class TestGroupTrainingCache {
+    /** */
+    private static final String CACHE_NAME = "TEST_GROUP_TRAINING_CACHE";
+
+    /** */
+    static IgniteCache<GroupTrainerCacheKey<Double>, Integer> getOrCreate(Ignite ignite) {
+        CacheConfiguration<GroupTrainerCacheKey<Double>, Integer> cfg = new CacheConfiguration<>();
+
+        // Write to primary.
+        cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+        // Atomic transactions only.
+        cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+        // No copying of values.
+        cfg.setCopyOnRead(false);
+
+        // Cache is partitioned.
+        cfg.setCacheMode(CacheMode.PARTITIONED);
+
+        cfg.setBackups(0);
+
+        cfg.setOnheapCacheEnabled(true);
+
+        cfg.setName(CACHE_NAME);
+
+        return ignite.getOrCreateCache(cfg);
+    }
+
+    /** */
+    static Stream<GroupTrainerCacheKey<Double>> allKeys(int limit, int eachNumCnt, UUID trainingUUID) {
+        @SuppressWarnings("unchecked")
+        GroupTrainerCacheKey<Double>[] a = new GroupTrainerCacheKey[limit * eachNumCnt];
+
+        for (int num = 0; num < limit; num++)
+            for (int i = 0; i < eachNumCnt; i++)
+                a[num * eachNumCnt + i] = new GroupTrainerCacheKey<>(num, (double)i, trainingUUID);
+
+        return Arrays.stream(a);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingSecondCache.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingSecondCache.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingSecondCache.java
new file mode 100644
index 0000000..fea931e
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestGroupTrainingSecondCache.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+
+/** */
+class TestGroupTrainingSecondCache {
+    /** */
+    private static final String CACHE_NAME = "TEST_GROUP_TRAINING_SECOND_CACHE";
+
+    /** */
+    static IgniteCache<GroupTrainerCacheKey<Character>, Integer> getOrCreate(Ignite ignite) {
+        CacheConfiguration<GroupTrainerCacheKey<Character>, Integer> cfg = new CacheConfiguration<>();
+
+        // Write to primary.
+        cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+        // Atomic transactions only.
+        cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+        // No copying of values.
+        cfg.setCopyOnRead(false);
+
+        // Cache is partitioned.
+        cfg.setCacheMode(CacheMode.PARTITIONED);
+
+        cfg.setBackups(0);
+
+        cfg.setOnheapCacheEnabled(true);
+
+        cfg.setName(CACHE_NAME);
+
+        return ignite.getOrCreateCache(cfg);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestLocalContext.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestLocalContext.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestLocalContext.java
new file mode 100644
index 0000000..8348334
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestLocalContext.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.UUID;
+import org.apache.ignite.ml.trainers.group.chain.HasTrainingUUID;
+
+/** */
+class TestLocalContext implements HasTrainingUUID {
+    /** */
+    private final UUID trainingUUID;
+    /** */
+    private int data;
+
+    /** */
+    TestLocalContext(int data, UUID trainingUUID) {
+        this.data = data;
+        this.trainingUUID = trainingUUID;
+    }
+
+    /** */
+    int data() {
+        return data;
+    }
+
+    /** */
+    void setData(int data) {
+        this.data = data;
+    }
+
+    /** {@inheritDoc} */
+    @Override public UUID trainingUUID() {
+        return trainingUUID;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestTrainingLoopStep.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestTrainingLoopStep.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestTrainingLoopStep.java
new file mode 100644
index 0000000..21f328a
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TestTrainingLoopStep.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import java.util.UUID;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.ml.math.functions.IgniteBinaryOperator;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+import org.apache.ignite.ml.trainers.group.chain.DistributedEntryProcessingStep;
+import org.apache.ignite.ml.trainers.group.chain.EntryAndContext;
+
+/** */
+class TestTrainingLoopStep implements DistributedEntryProcessingStep<TestGroupTrainerLocalContext, Double, Integer, Void, Double, Double> {
+    /** {@inheritDoc} */
+    @Override public IgniteSupplier<Void> remoteContextSupplier(Double input, TestGroupTrainerLocalContext locCtx) {
+        // No context is needed.
+        return () -> null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteFunction<EntryAndContext<Double, Integer, Void>, ResultAndUpdates<Double>> worker() {
+        return entryAndContext -> {
+            Integer oldVal = entryAndContext.entry().getValue();
+            double v = oldVal * oldVal;
+            ResultAndUpdates<Double> res = ResultAndUpdates.of(v);
+            res.update(TestGroupTrainingCache.getOrCreate(Ignition.localIgnite()), entryAndContext.entry().getKey(), (int)v);
+            return res;
+        };
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteSupplier<Stream<GroupTrainerCacheKey<Double>>> keys(Double input,
+        TestGroupTrainerLocalContext locCtx) {
+        // Copying here because otherwise locCtx will be serialized with supplier returned in result.
+        int limit = locCtx.limit();
+        int cnt = locCtx.eachNumberCnt();
+        UUID uuid = locCtx.trainingUUID();
+
+        return () -> TestGroupTrainingCache.allKeys(limit, cnt, uuid);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Double identity() {
+        return 0.0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteBinaryOperator<Double> reducer() {
+        return (a, b) -> a + b;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/b0c5ef1e/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TrainersGroupTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TrainersGroupTestSuite.java b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TrainersGroupTestSuite.java
new file mode 100644
index 0000000..0ec5afb
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trainers/group/TrainersGroupTestSuite.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ml.trainers.group;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Test suite for group trainer tests.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    DistributedWorkersChainTest.class,
+    GroupTrainerTest.class
+})
+public class TrainersGroupTestSuite {
+}


Mime
View raw message