You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mxnet.apache.org by GitBox <gi...@apache.org> on 2017/11/18 21:35:30 UTC
[GitHub] piiswrong closed pull request #8641: Doc updates for sparse operators
piiswrong closed pull request #8641: Doc updates for sparse operators
URL: https://github.com/apache/incubator-mxnet/pull/8641
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/docs/api/python/ndarray/sparse.md b/docs/api/python/ndarray/sparse.md
index 9b742f4fc5..dd0286d092 100644
--- a/docs/api/python/ndarray/sparse.md
+++ b/docs/api/python/ndarray/sparse.md
@@ -123,13 +123,22 @@ We summarize the interface for each class in the following sections.
CSRNDArray.copy
CSRNDArray.copyto
CSRNDArray.as_in_context
- CSRNDArray.asnumpy
CSRNDArray.asscipy
+ CSRNDArray.asnumpy
CSRNDArray.asscalar
CSRNDArray.astype
CSRNDArray.tostype
```
+### Array inspection
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ CSRNDArray.check_format
+```
+
### Array creation
```eval_rst
@@ -139,6 +148,25 @@ We summarize the interface for each class in the following sections.
CSRNDArray.zeros_like
```
+### Array reduction
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ CSRNDArray.sum
+ CSRNDArray.mean
+```
+
+### Powers
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ CSRNDArray.square
+```
+
### Indexing
```eval_rst
@@ -190,6 +218,15 @@ We summarize the interface for each class in the following sections.
RowSparseNDArray.tostype
```
+### Array inspection
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ RowSparseNDArray.check_format
+```
+
### Array creation
```eval_rst
@@ -213,6 +250,52 @@ We summarize the interface for each class in the following sections.
RowSparseNDArray.trunc
```
+### Trigonometric functions
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ RowSparseNDArray.sin
+ RowSparseNDArray.tan
+ RowSparseNDArray.arcsin
+ RowSparseNDArray.arctan
+ RowSparseNDArray.degrees
+ RowSparseNDArray.radians
+```
+
+### Hyperbolic functions
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ RowSparseNDArray.sinh
+ RowSparseNDArray.tanh
+ RowSparseNDArray.arcsinh
+ RowSparseNDArray.arctanh
+```
+
+### Exponents and logarithms
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ RowSparseNDArray.expm1
+ RowSparseNDArray.log1p
+```
+
+### Powers
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ RowSparseNDArray.sqrt
+ RowSparseNDArray.square
+```
+
### Indexing
```eval_rst
@@ -221,6 +304,7 @@ We summarize the interface for each class in the following sections.
RowSparseNDArray.__getitem__
RowSparseNDArray.__setitem__
+ RowSparseNDArray.retain
```
### Lazy evaluation
@@ -232,6 +316,16 @@ We summarize the interface for each class in the following sections.
RowSparseNDArray.wait_to_read
```
+### Miscellaneous
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ RowSparseNDArray.clip
+ RowSparseNDArray.sign
+```
+
## Array creation routines
```eval_rst
@@ -311,6 +405,16 @@ We summarize the interface for each class in the following sections.
arctanh
```
+### Reduce functions
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ sum
+ mean
+```
+
### Rounding
```eval_rst
@@ -355,6 +459,20 @@ We summarize the interface for each class in the following sections.
sign
```
+## Neural network
+
+### Updater
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ sgd_update
+ sgd_mom_update
+ adam_update
+ ftrl_update
+```
+
### More
```eval_rst
@@ -363,6 +481,7 @@ We summarize the interface for each class in the following sections.
make_loss
stop_gradient
+ mxnet.ndarray.contrib.SparseEmbedding
```
## API Reference
@@ -372,10 +491,10 @@ We summarize the interface for each class in the following sections.
```eval_rst
.. autoclass:: mxnet.ndarray.sparse.CSRNDArray
- :members: shape, context, dtype, stype, data, indices, indptr, copy, copyto, as_in_context, asnumpy, asscalar, astype, tostype, slice, wait_to_read, zeros_like, __getitem__, __setitem__
+ :members: shape, context, dtype, stype, data, indices, indptr, copy, copyto, as_in_context, asscipy, asnumpy, asscalar, astype, tostype, slice, wait_to_read, zeros_like, __neg__, sum, mean, square, __getitem__, __setitem__, check_format
.. autoclass:: mxnet.ndarray.sparse.RowSparseNDArray
- :members: shape, context, dtype, stype, data, indices, copy, copyto, as_in_context, asnumpy, asscalar, astype, tostype, wait_to_read, zeros_like, round, rint, fix, floor, ceil, trunc, __getitem__, __setitem__
+ :members: shape, context, dtype, stype, data, indices, copy, copyto, as_in_context, asnumpy, asscalar, astype, tostype, wait_to_read, zeros_like, round, rint, fix, floor, ceil, trunc, sin, tan, arcsin, arctan, degrees, radians, sinh, tanh, arcsinh, arctanh, expm1, log1p, sqrt, square, __negative__, __getitem__, __setitem__, check_format, retain, clip, sign
.. automodule:: mxnet.ndarray.sparse
:members:
diff --git a/docs/api/python/symbol/sparse.md b/docs/api/python/symbol/sparse.md
index 5ebbfcd057..b40276b9f1 100644
--- a/docs/api/python/symbol/sparse.md
+++ b/docs/api/python/symbol/sparse.md
@@ -95,10 +95,107 @@ In the rest of this document, we list sparse related routines provided by the
:nosignatures:
elemwise_add
+ elemwise_sub
+ elemwise_mul
+ negative
dot
add_n
```
+### Trigonometric functions
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ sin
+ tan
+ arcsin
+ arctan
+ degrees
+ radians
+```
+
+### Hyperbolic functions
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ sinh
+ tanh
+ arcsinh
+ arctanh
+```
+
+### Reduce functions
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ sum
+ mean
+```
+
+### Rounding
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ round
+ rint
+ fix
+ floor
+ ceil
+ trunc
+```
+
+### Exponents and logarithms
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ expm1
+ log1p
+```
+
+### Powers
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ sqrt
+ square
+```
+
+### Miscellaneous
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ clip
+ abs
+ sign
+```
+
+## Neural network
+
+### More
+
+```eval_rst
+.. autosummary::
+ :nosignatures:
+
+ make_loss
+ stop_gradient
+ mxnet.symbol.contrib.SparseEmbedding
+```
+
## API Reference
<script type="text/javascript" src='../../../_static/js/auto_module_index.js'></script>
diff --git a/docs/tutorials/sparse/row_sparse.md b/docs/tutorials/sparse/row_sparse.md
index 6a69341da9..55f8a7d1c5 100644
--- a/docs/tutorials/sparse/row_sparse.md
+++ b/docs/tutorials/sparse/row_sparse.md
@@ -6,7 +6,7 @@
Many real world datasets deal with high dimensional sparse feature vectors. When learning
the weights of models with sparse datasets, the derived gradients of the weights could be sparse.
-Let's say we perform a matrix multiplication of ``X`` and ``W``, where ``X`` is a 2x2 matrix, and ``W`` is a 2x1 matrix. Let ``Y`` be the matrix multiplication of the two matrices:
+Let's say we perform a matrix multiplication of ``X`` and ``W``, where ``X`` is a 1x2 matrix, and ``W`` is a 2x3 matrix. Let ``Y`` be the matrix multiplication of the two matrices:
```python
import mxnet as mx
diff --git a/example/sparse/linear_classification.py b/example/sparse/linear_classification.py
index 70f896386c..1d63c55b11 100644
--- a/example/sparse/linear_classification.py
+++ b/example/sparse/linear_classification.py
@@ -126,8 +126,8 @@
# evaluate metric on validation dataset
score = mod.score(eval_data, ['nll_loss'])
logging.info('epoch %d, eval nll = %s ' % (epoch, score[0][1]))
- save_optimizer_states = 'dist' not in kv.type
- mod.save_checkpoint("checkpoint", epoch, save_optimizer_states=False)
+ save_optimizer_states = 'dist' not in kv.type if kv else True
+ mod.save_checkpoint("checkpoint", epoch, save_optimizer_states=save_optimizer_states)
# reset the iterator for next pass of data
data_iter.reset()
logging.info('Training completed.')
diff --git a/python/mxnet/ndarray/sparse.py b/python/mxnet/ndarray/sparse.py
index 044ce81428..229044e289 100644
--- a/python/mxnet/ndarray/sparse.py
+++ b/python/mxnet/ndarray/sparse.py
@@ -747,6 +747,13 @@ def copyto(self, other):
else:
raise TypeError('copyto does not support type ' + str(type(other)))
+ def retain(self, *args, **kwargs):
+ """Convenience fluent method for :py:func:`retain`.
+
+ The arguments are the same as for :py:func:`retain`, with
+ this array as data.
+ """
+ return retain(self, *args, **kwargs)
def _prepare_src_array(source_array, dtype):
"""Prepare `source_array` so that it can be used to construct NDArray.
diff --git a/python/mxnet/optimizer.py b/python/mxnet/optimizer.py
index eaaf521645..5eb4f05d6d 100644
--- a/python/mxnet/optimizer.py
+++ b/python/mxnet/optimizer.py
@@ -442,13 +442,20 @@ class SGD(Optimizer):
weight = weight - state
If the storage types of weight, state and grad are all ``row_sparse``, \
- sparse updates are applied by::
+ **sparse updates** are applied by::
for row in grad.indices:
rescaled_grad[row] = lr * rescale_grad * clip(grad[row], clip_gradient) + wd * weight[row]
state[row] = momentum[row] * state[row] + rescaled_grad[row]
weight[row] = weight[row] - state[row]
+ The sparse update only updates the momentum for the weights whose row_sparse
+ gradient indices appear in the current batch, rather than updating it for all
+ indices. Compared with the original update, it can provide large
+ improvements in model training throughput for some applications. However, it
+ provides slightly different semantics than the original update, and
+ may lead to different empirical results.
+
For details of the update algorithm see
:class:`~mxnet.ndarray.sgd_update` and :class:`~mxnet.ndarray.sgd_mom_update`.
@@ -667,7 +674,7 @@ class Adam(Optimizer):
w = w - learning_rate * m / (sqrt(v) + epsilon)
If the storage types of weight, state and grad are all ``row_sparse``, \
- sparse updates are applied by::
+ **sparse updates** are applied by::
for row in grad.indices:
rescaled_grad[row] = clip(grad[row] * rescale_grad + wd * weight[row], clip_gradient)
@@ -675,6 +682,12 @@ class Adam(Optimizer):
v[row] = beta2 * v[row] + (1 - beta2) * (rescaled_grad[row]**2)
w[row] = w[row] - learning_rate * m[row] / (sqrt(v[row]) + epsilon)
+ The sparse update only updates the mean and var for the weights whose row_sparse
+ gradient indices appear in the current batch, rather than updating it for all indices.
+ Compared with the original update, it can provide large improvements in model training
+ throughput for some applications. However, it provides slightly different semantics than
+ the original update, and may lead to different empirical results.
+
This optimizer accepts the following parameters in addition to those accepted
by :class:`.Optimizer`.
@@ -936,7 +949,7 @@ class Ftrl(Optimizer):
w = (sign(z) * lamda1 - z) / ((beta + sqrt(n)) / learning_rate + wd) * (abs(z) > lamda1)
If the storage types of weight, state and grad are all ``row_sparse``, \
- sparse updates are applied by::
+ **sparse updates** are applied by::
for row in grad.indices:
rescaled_grad[row] = clip(grad[row] * rescale_grad, clip_gradient)
@@ -944,6 +957,13 @@ class Ftrl(Optimizer):
n[row] += rescaled_grad[row]**2
w[row] = (sign(z[row]) * lamda1 - z[row]) / ((beta + sqrt(n[row])) / learning_rate + wd) * (abs(z[row]) > lamda1)
+ The sparse update only updates the z and n for the weights whose row_sparse
+ gradient indices appear in the current batch, rather than updating it for all
+ indices. Compared with the original update, it can provide large
+ improvements in model training throughput for some applications. However, it
+ provides slightly different semantics than the original update, and
+ may lead to different empirical results.
+
For details of the update algorithm, see :class:`~mxnet.ndarray.ftrl_update`.
This optimizer accepts the following parameters in addition to those accepted
diff --git a/src/operator/operator_common.h b/src/operator/operator_common.h
index d036355392..24fdd3c44c 100644
--- a/src/operator/operator_common.h
+++ b/src/operator/operator_common.h
@@ -207,6 +207,10 @@ inline bool dispatch_mode_assign(DispatchMode *y, const DispatchMode& x) {
return true;
}
+/*! \brief Register op name as an alias */
+#define MXNET_ADD_SPARSE_OP_ALIAS(__name$) \
+ .add_alias("_sparse_" #__name$)
+
/*!
* \brief macro assign shape to out if out is unknown otherwise check consistency
* Use macro so we can see the error file more clearly
diff --git a/src/operator/optimizer_op.cc b/src/operator/optimizer_op.cc
index 0382070010..3d8774cade 100644
--- a/src/operator/optimizer_op.cc
+++ b/src/operator/optimizer_op.cc
@@ -36,6 +36,7 @@ DMLC_REGISTER_PARAMETER(RMSPropAlexParam);
DMLC_REGISTER_PARAMETER(FtrlParam);
NNVM_REGISTER_OP(sgd_update)
+MXNET_ADD_SPARSE_OP_ALIAS(sgd_update)
.describe(R"code(Update function for Stochastic Gradient Descent (SDG) optimizer.
It updates the weights using::
@@ -62,6 +63,7 @@ only the row slices whose indices appear in grad.indices are updated::
.add_arguments(SGDParam::__FIELDS__());
NNVM_REGISTER_OP(sgd_mom_update)
+MXNET_ADD_SPARSE_OP_ALIAS(sgd_mom_update)
.describe(R"code(Momentum update function for Stochastic Gradient Descent (SDG) optimizer.
Momentum update has better convergence rates on neural networks. Mathematically it looks
@@ -141,6 +143,7 @@ NNVM_REGISTER_OP(mp_sgd_mom_update)
.add_arguments(SGDMomParam::__FIELDS__());
NNVM_REGISTER_OP(adam_update)
+MXNET_ADD_SPARSE_OP_ALIAS(adam_update)
.describe(R"code(Update function for Adam optimizer. Adam is seen as a generalization
of AdaGrad.
@@ -280,6 +283,7 @@ to be 0.9 and the learning rate :math:`\eta` to be 0.0001.
.add_arguments(RMSPropAlexParam::__FIELDS__());
NNVM_REGISTER_OP(ftrl_update)
+MXNET_ADD_SPARSE_OP_ALIAS(ftrl_update)
.describe(R"code(Update function for Ftrl optimizer.
Referenced from *Ad Click Prediction: a View from the Trenches*, available at
http://dl.acm.org/citation.cfm?id=2488200.
diff --git a/src/operator/tensor/elemwise_binary_op_basic.cc b/src/operator/tensor/elemwise_binary_op_basic.cc
index b3be9e4c20..8f1ae79ec0 100644
--- a/src/operator/tensor/elemwise_binary_op_basic.cc
+++ b/src/operator/tensor/elemwise_binary_op_basic.cc
@@ -35,6 +35,7 @@ MXNET_ADD_SPARSE_OP_ALIAS(elemwise_add)
The storage type of ``elemwise_add`` output depends on storage types of inputs
- elemwise_add(row_sparse, row_sparse) = row_sparse
+ - elemwise_add(csr, csr) = csr
- otherwise, ``elemwise_add`` generates output with default storage
)code")
@@ -69,7 +70,8 @@ MXNET_ADD_SPARSE_OP_ALIAS(elemwise_sub)
The storage type of ``elemwise_sub`` output depends on storage types of inputs
- elemwise_sub(row_sparse, row_sparse) = row_sparse
- - otherwise, ``elemwise_add`` generates output with default storage
+ - elemwise_sub(csr, csr) = csr
+ - otherwise, ``elemwise_sub`` generates output with default storage
)code")
.set_attr<nnvm::FGradient>("FGradient", ElemwiseGradUseNone{"_backward_sub"});
@@ -100,6 +102,7 @@ The storage type of ``elemwise_mul`` output depends on storage types of inputs
- elemwise_mul(row_sparse, row_sparse) = row_sparse
- elemwise_mul(default, row_sparse) = default
- elemwise_mul(row_sparse, default) = default
+ - elemwise_mul(csr, csr) = csr
- otherwise, ``elemwise_mul`` generates output with default storage
)code")
@@ -138,7 +141,7 @@ MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(elemwise_div, mshadow::op::div
MXNET_ADD_SPARSE_OP_ALIAS(elemwise_div)
.describe(R"code(Divides arguments element-wise.
-The storage type of ``elemwise_dev`` output is always dense
+The storage type of ``elemwise_div`` output is always dense
)code")
.add_alias("_div").add_alias("_Div")
diff --git a/src/operator/tensor/elemwise_unary_op.h b/src/operator/tensor/elemwise_unary_op.h
index 6fbde05c46..6e635d954b 100644
--- a/src/operator/tensor/elemwise_unary_op.h
+++ b/src/operator/tensor/elemwise_unary_op.h
@@ -416,10 +416,6 @@ void CastCompute(const nnvm::NodeAttrs& attrs,
}) \
.add_argument("data", "NDArray-or-Symbol", "The input array.")
-/*! \brief Register scalar op name as an alias */
-#define MXNET_ADD_SPARSE_OP_ALIAS(__name$) \
- .add_alias("_sparse_" #__name$)
-
/*! \brief Unary compute, with FComputeEx for csr and rsp available */
#define MXNET_OPERATOR_REGISTER_UNARY_WITH_RSP_CSR(__name$, __xpu$, __kernel$) \
MXNET_OPERATOR_REGISTER_UNARY(__name$) \
diff --git a/src/operator/tensor/matrix_op.cc b/src/operator/tensor/matrix_op.cc
index cba9efd1a9..97e688078c 100644
--- a/src/operator/tensor/matrix_op.cc
+++ b/src/operator/tensor/matrix_op.cc
@@ -247,7 +247,7 @@ will return a new array with shape ``(2,1,3,4)``.
.add_arguments(ExpandDimParam::__FIELDS__());
NNVM_REGISTER_OP(slice)
-.add_alias("_sparse_slice")
+MXNET_ADD_SPARSE_OP_ALIAS(slice)
.add_alias("crop")
.describe(R"code(Slices a region of the array.
@@ -395,6 +395,7 @@ NNVM_REGISTER_OP(_backward_slice_axis)
.set_attr<FCompute>("FCompute<cpu>", SliceAxisGrad_<cpu>);
NNVM_REGISTER_OP(clip)
+MXNET_ADD_SPARSE_OP_ALIAS(clip)
.describe(R"code(Clips (limits) the values in an array.
Given an interval, values outside the interval are clipped to the interval edges.
diff --git a/tests/python/unittest/test_sparse_ndarray.py b/tests/python/unittest/test_sparse_ndarray.py
index c7ac1ca93a..e59e476601 100644
--- a/tests/python/unittest/test_sparse_ndarray.py
+++ b/tests/python/unittest/test_sparse_ndarray.py
@@ -22,9 +22,8 @@
from mxnet.base import mx_real_t
from numpy.testing import assert_allclose
import numpy.random as rnd
-
-from mxnet.ndarray.sparse import RowSparseNDArray, CSRNDArray
from common import assertRaises
+from mxnet.ndarray.sparse import RowSparseNDArray, CSRNDArray
def sparse_nd_ones(shape, stype):
@@ -234,6 +233,7 @@ def check_binary(fn, stype):
oshape = np.random.randint(1, 6, size=(ndim,))
bdim = 2
lshape = list(oshape)
+ # one for broadcast op, another for elemwise op
rshape = list(oshape[ndim-bdim:])
for i in range(bdim):
sep = np.random.uniform(0, 1)
@@ -737,20 +737,47 @@ def test_powerlaw_generator(csr_arr, final_row=1):
test_powerlaw_generator(csr_arr_big, final_row=4)
test_powerlaw_generator(csr_arr_square, final_row=6)
+def test_sparse_nd_fluent():
+ def check_fluent_regular(stype, func, kwargs, shape=(5, 17), equal_nan=False):
+ with mx.name.NameManager():
+ data = mx.nd.random_uniform(shape=shape, ctx=default_context()).tostype(stype)
+ regular = getattr(mx.ndarray, func)(data, **kwargs)
+ fluent = getattr(data, func)(**kwargs)
+ if isinstance(regular, list):
+ for r, f in zip(regular, fluent):
+ assert almost_equal(r.asnumpy(), f.asnumpy(), equal_nan=equal_nan)
+ else:
+ assert almost_equal(regular.asnumpy(), fluent.asnumpy(), equal_nan=equal_nan)
+
+ common_func = ['zeros_like', 'square']
+ rsp_func = ['round', 'rint', 'fix', 'floor', 'ceil', 'trunc',
+ 'abs', 'sign', 'sin', 'degrees', 'radians', 'expm1']
+ for func in common_func:
+ check_fluent_regular('csr', func, {})
+ for func in common_func + rsp_func:
+ check_fluent_regular('row_sparse', func, {})
+
+ rsp_func = ['arcsin', 'arctan', 'tan', 'sinh', 'tanh',
+ 'arcsinh', 'arctanh', 'log1p', 'sqrt', 'relu']
+ for func in rsp_func:
+ check_fluent_regular('row_sparse', func, {}, equal_nan=True)
+
+ check_fluent_regular('csr', 'slice', {'begin': (2, 5), 'end': (4, 7)}, shape=(5, 17))
+ check_fluent_regular('row_sparse', 'clip', {'a_min': -0.25, 'a_max': 0.75})
+
+ for func in ['sum', 'mean']:
+ check_fluent_regular('csr', func, {'axis': 0})
+
+
def test_sparse_nd_exception():
""" test invalid sparse operator will throw a exception """
a = mx.nd.ones((2,2))
- assert_exception(mx.nd.sparse.retain, mx.base.MXNetError,
- a, invalid_arg="garbage_value")
- assert_exception(mx.nd.sparse.csr_matrix, ValueError,
- a, shape=(3,2))
- assert_exception(mx.nd.sparse.csr_matrix, ValueError,
- (2,2), shape=(3,2))
- assert_exception(mx.nd.sparse.row_sparse_array, ValueError,
- (2,2), shape=(3,2))
- assert_exception(mx.nd.sparse.zeros, ValueError,
- "invalid_stype", (2,2))
-
+ assertRaises(mx.base.MXNetError, mx.nd.sparse.retain, a, invalid_arg="garbage_value")
+ assertRaises(ValueError, mx.nd.sparse.csr_matrix, a, shape=(3,2))
+ assertRaises(ValueError, mx.nd.sparse.csr_matrix, (2,2), shape=(3,2))
+ assertRaises(ValueError, mx.nd.sparse.row_sparse_array, (2,2), shape=(3,2))
+ assertRaises(ValueError, mx.nd.sparse.zeros, "invalid_stype", (2,2))
+
def test_sparse_nd_check_format():
""" test check_format for sparse ndarray """
shape = rand_shape_2d()
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
With regards,
Apache Git Services