Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
[MXNET-623] Fixing an integer overflow bug in large NDArray (#11742)
Browse files Browse the repository at this point in the history
* Fix integer overflow when the array size is too large

* Update issue templates

* Update issue templates

* Remove files added by mistake

* Fix compilation error after type index_t changed to int64_t

* Explicity specify type in std::max template to avoid platform dependent compilation error

* Add nightly test for large array

* Update submodule mshadow

* Fix compilation warning

* Fix compilation warning

* Change index variable type to size_t

* Fix integer overflow when the array size is too large

* Update issue templates

* Remove files added by mistake

* Fix compilation error after type index_t changed to int64_t

* Explicity specify type in std::max template to avoid platform dependent compilation error

* Add nightly test for large array

* [MXNET-531] NeuralStyle Example for Scala (#11621)

* add initial neuralstyle and test coverage

* Add two more test and README

* kill comments

* patch on memory leaks fix

* fix formatting issues

* remove redundant files

* disable the Gan example for now

* add ignore method

* add new download scheme to match the changes

* Update submodule mshadow

* Fix compilation warning

* Fix compilation warning

* Change index variable type to size_t

* Change temp_size type from size_t to index_t

* Fix lint error

* Fix compilation error in GPU

* Fix compilation error on GPU

* Fix compilation error in cpp-package

* Fix unit test in GPU

* Change correct type for nnvmGraph

* update mshadow submodule to local repo to verify

* update mshadow submodule

* change some data type to size_t

* change unit test style

* fix lint

* fix compilation error in Windows

* fix compilation error in Windows

* use forked submodule to verify

* temporarily update submodule to verify the fix

* update mshadow submodule to use remote

* add test to nightly test script
  • Loading branch information
apeforest authored and anirudh2290 committed Oct 5, 2018
1 parent e93af41 commit f9f7416
Show file tree
Hide file tree
Showing 41 changed files with 153 additions and 115 deletions.
2 changes: 1 addition & 1 deletion 3rdparty/mshadow
2 changes: 1 addition & 1 deletion src/c_api/c_api_function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ std::vector<nnvm::NodeEntry> Gradient(
g->inputs = out_grads;

std::vector<nnvm::NodeEntry> ret;
for (index_t i = 0; i < g->num_outputs(); ++i) {
for (uint32_t i = 0; i < g->num_outputs(); ++i) {
ret.emplace_back(nnvm::NodeEntry{g, i, 0});
}

Expand Down
2 changes: 1 addition & 1 deletion src/executor/graph_executor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,7 @@ void GraphExecutor::ExecuteMonCallback(size_t nid) {
}
}
CHECK_EQ(opnode.exec->out_array.size(), output_names.size());
for (index_t i = 0; i < opnode.exec->out_array.size(); ++i) {
for (size_t i = 0; i < opnode.exec->out_array.size(); ++i) {
NDArray *cpy = new NDArray(opnode.exec->out_array[i]);
std::string name = inode.source->attrs.name + "_" + output_names[i];
this->monitor_callback_(name.c_str(), reinterpret_cast<void*>(cpy));
Expand Down
6 changes: 3 additions & 3 deletions src/io/image_iter_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class ImageLabelMap {
* \param label_width predefined label_width
*/
explicit ImageLabelMap(const char *path_imglist,
mshadow::index_t label_width,
index_t label_width,
bool silent) {
this->label_width = label_width;
image_index_.clear();
Expand All @@ -58,7 +58,7 @@ class ImageLabelMap {
// skip space
while (isspace(*p) && p != end) ++p;
image_index_.push_back(static_cast<size_t>(atol(p)));
for (size_t i = 0; i < label_width; ++i) {
for (index_t i = 0; i < label_width; ++i) {
// skip till space
while (!isspace(*p) && p != end) ++p;
// skip space
Expand Down Expand Up @@ -171,7 +171,7 @@ struct ImageRecParserParam : public dmlc::Parameter<ImageRecParserParam> {
// Batch parameters
struct BatchParam : public dmlc::Parameter<BatchParam> {
/*! \brief label width */
index_t batch_size;
uint32_t batch_size;
/*! \brief use round roubin to handle overflow batch */
bool round_batch;
// declare parameters
Expand Down
41 changes: 21 additions & 20 deletions src/io/iter_image_recordio_2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class ImageRecordIOParser2 {
cv::Mat TJimdecode(cv::Mat buf, int color);
#endif
#endif
inline unsigned ParseChunk(DType* data_dptr, real_t* label_dptr, const unsigned current_size,
inline size_t ParseChunk(DType* data_dptr, real_t* label_dptr, const size_t current_size,
dmlc::InputSplit::Blob * chunk);
inline void CreateMeanImg(void);

Expand Down Expand Up @@ -104,10 +104,10 @@ class ImageRecordIOParser2 {
/*! \brief temp space */
mshadow::TensorContainer<cpu, 3> img_;
/*! \brief internal instance order */
std::vector<std::pair<unsigned, unsigned> > inst_order_;
unsigned inst_index_;
std::vector<std::pair<size_t, size_t> > inst_order_;
size_t inst_index_;
/*! \brief internal counter tracking number of already parsed entries */
unsigned n_parsed_;
size_t n_parsed_;
/*! \brief overflow marker */
bool overflow;
/*! \brief unit size */
Expand Down Expand Up @@ -200,7 +200,7 @@ inline void ImageRecordIOParser2<DType>::Init(
"larger chunk size";
}
// 1.1 ratio is for a bit more shuffle parts to avoid boundary issue
unsigned num_shuffle_parts =
size_t num_shuffle_parts =
std::ceil(source_->GetTotalSize() * 1.1 /
(param_.num_parts * (param_.shuffle_chunk_size << 20UL)));

Expand Down Expand Up @@ -262,7 +262,7 @@ inline bool ImageRecordIOParser2<DType>::ParseNext(DataBatch *out) {
}
CHECK(source_ != nullptr);
dmlc::InputSplit::Blob chunk;
unsigned current_size = 0;
size_t current_size = 0;
out->index.resize(batch_param_.batch_size);

// InitBatch
Expand Down Expand Up @@ -295,7 +295,7 @@ inline bool ImageRecordIOParser2<DType>::ParseNext(DataBatch *out) {

while (current_size < batch_param_.batch_size) {
// int n_to_copy;
unsigned n_to_out = 0;
size_t n_to_out = 0;
if (n_parsed_ == 0) {
if (source_->NextBatch(&chunk, batch_param_.batch_size)) {
inst_order_.clear();
Expand Down Expand Up @@ -328,15 +328,16 @@ inline bool ImageRecordIOParser2<DType>::ParseNext(DataBatch *out) {
n_to_out = 0;
}
} else {
int n_to_copy = std::min(n_parsed_, batch_param_.batch_size - current_size);
size_t n_to_copy = std::min(n_parsed_,
static_cast<size_t>(batch_param_.batch_size) - current_size);
n_parsed_ -= n_to_copy;
// Copy
#pragma omp parallel for num_threads(param_.preprocess_threads)
for (int i = 0; i < n_to_copy; ++i) {
for (int i = 0; i < static_cast<int>(n_to_copy); ++i) {
omp_exc_.Run([&] {
std::pair<unsigned, unsigned> place = inst_order_[inst_index_ + i];
std::pair<size_t, size_t> place = inst_order_[inst_index_ + i];
const DataInst& batch = temp_[place.first][place.second];
for (unsigned j = 0; j < batch.data.size(); ++j) {
for (size_t j = 0; j < batch.data.size(); ++j) {
CHECK_EQ(unit_size_[j], batch.data[j].Size());
MSHADOW_TYPE_SWITCH(out->data[j].data().type_flag_, dtype, {
mshadow::Copy(
Expand Down Expand Up @@ -482,18 +483,18 @@ cv::Mat ImageRecordIOParser2<DType>::TJimdecode(cv::Mat image, int color) {

// Returns the number of images that are put into output
template<typename DType>
inline unsigned ImageRecordIOParser2<DType>::ParseChunk(DType* data_dptr, real_t* label_dptr,
const unsigned current_size, dmlc::InputSplit::Blob * chunk) {
inline size_t ImageRecordIOParser2<DType>::ParseChunk(DType* data_dptr, real_t* label_dptr,
const size_t current_size, dmlc::InputSplit::Blob * chunk) {
temp_.resize(param_.preprocess_threads);
#if MXNET_USE_OPENCV
// save opencv out
dmlc::RecordIOChunkReader reader(*chunk, 0, 1);
unsigned gl_idx = current_size;
size_t gl_idx = current_size;
#pragma omp parallel num_threads(param_.preprocess_threads)
{
omp_exc_.Run([&] {
CHECK(omp_get_num_threads() == param_.preprocess_threads);
unsigned int tid = omp_get_thread_num();
int tid = omp_get_thread_num();
// dmlc::RecordIOChunkReader reader(*chunk, tid, param_.preprocess_threads);
ImageRecordIO rec;
dmlc::InputSplit::Blob blob;
Expand All @@ -502,7 +503,7 @@ inline unsigned ImageRecordIOParser2<DType>::ParseChunk(DType* data_dptr, real_t
out_tmp.Clear();
while (true) {
bool reader_has_data;
unsigned idx;
size_t idx;
#pragma omp critical
{
reader_has_data = reader.NextRecord(&blob);
Expand Down Expand Up @@ -567,7 +568,7 @@ inline unsigned ImageRecordIOParser2<DType>::ParseChunk(DType* data_dptr, real_t
data = mshadow::Tensor<cpu, 3, DType>(data_dptr + idx*unit_size_[0],
mshadow::Shape3(n_channels, res.rows, res.cols));
} else {
out_tmp.Push(static_cast<unsigned>(rec.image_index()),
out_tmp.Push(static_cast<size_t>(rec.image_index()),
mshadow::Shape3(n_channels, res.rows, res.cols),
mshadow::Shape1(param_.label_width));
data = out_tmp.data().Back();
Expand Down Expand Up @@ -612,7 +613,7 @@ inline unsigned ImageRecordIOParser2<DType>::ParseChunk(DType* data_dptr, real_t
});
}
omp_exc_.Rethrow();
return (std::min(batch_param_.batch_size, gl_idx) - current_size);
return (std::min(static_cast<size_t>(batch_param_.batch_size), gl_idx) - current_size);
#else
LOG(FATAL) << "Opencv is needed for image decoding and augmenting.";
return 0;
Expand All @@ -633,8 +634,8 @@ inline void ImageRecordIOParser2<DType>::CreateMeanImg(void) {
inst_order_.clear();
// Parse chunk w/o putting anything in out
ParseChunk(nullptr, nullptr, batch_param_.batch_size, &chunk);
for (unsigned i = 0; i < inst_order_.size(); ++i) {
std::pair<unsigned, unsigned> place = inst_order_[i];
for (size_t i = 0; i < inst_order_.size(); ++i) {
std::pair<size_t, size_t> place = inst_order_[i];
mshadow::Tensor<cpu, 3> outimg =
temp_[place.first][place.second].data[0].template get<cpu, 3, real_t>();
if (imcnt == 0) {
Expand Down
12 changes: 6 additions & 6 deletions src/ndarray/ndarray.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2105,10 +2105,10 @@ void Imdecode(NDArray *ret, NDArray mean, size_t index,
if (mean.is_none()) {
MSHADOW_TYPE_SWITCH(buff.dtype(), DType, {
mshadow::Tensor<cpu, 4, DType> tensor = buff.data().get<cpu, 4, DType>();
for (index_t i = 0; i < y1-y0; i++) {
for (size_t i = 0; i < y1-y0; i++) {
uchar* im_data = res.ptr<uchar>(y0+i) + res.channels()*x0;
for (index_t j = 0; j < x1-x0; j++) {
for (index_t k = 0; k < n_channels; k++) {
for (size_t j = 0; j < x1-x0; j++) {
for (size_t k = 0; k < n_channels; k++) {
tensor[0][k][i][j] = DType(im_data[k]); // NOLINT(*)
}
im_data += res.channels();
Expand All @@ -2125,10 +2125,10 @@ void Imdecode(NDArray *ret, NDArray mean, size_t index,
MSHADOW_TYPE_SWITCH(buff.dtype(), DType, {
mshadow::Tensor<cpu, 4, DType> tensor = buff.data().get<cpu, 4, DType>();
mshadow::Tensor<cpu, 3, DType> tmean = mean.data().get<cpu, 3, DType>();
for (index_t i = 0; i < y1-y0; i++) {
for (size_t i = 0; i < y1-y0; i++) {
uchar* im_data = res.ptr<uchar>(y0+i) + res.channels()*x0;
for (index_t j = 0; j < x1-x0; j++) {
for (index_t k = 0; k < n_channels; k++) {
for (size_t j = 0; j < x1-x0; j++) {
for (size_t k = 0; k < n_channels; k++) {
tensor[0][k][i][j] = DType(im_data[k]) - tmean[k][i][j]; // NOLINT(*)
}
im_data += res.channels();
Expand Down
2 changes: 1 addition & 1 deletion src/ndarray/ndarray_function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ void ElementwiseSumRspImpl(mshadow::Stream<cpu>* s,
auto out_value_cur_row = out_values[irow];
const auto offset = row_idx_ptr - nd_indices_start;
auto nd_value_cur_row = nd_values[offset];
for (size_t j = 0; j < nd_value_cur_row.shape_[0]; ++j) {
for (index_t j = 0; j < nd_value_cur_row.shape_[0]; ++j) {
out_value_cur_row[j] += nd_value_cur_row[j];
}
++irow;
Expand Down
4 changes: 2 additions & 2 deletions src/operator/batch_norm_v1-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,14 @@ class BatchNormV1Prop : public OperatorProperty {
// For other input types, these parameters have the same type as input
// NOTE: This requirement is from cuDNN (v. 4 and 5)
int dtype_param = (dtype == kFloat16) ? kFloat32 : dtype;
for (index_t i = 1; i < in_type->size(); ++i) {
for (size_t i = 1; i < in_type->size(); ++i) {
if ((*in_type)[i] == -1) {
(*in_type)[i] = dtype_param;
} else {
UNIFORM_TYPE_CHECK((*in_type)[i], dtype_param, ListArguments()[i]);
}
}
for (index_t i = 0; i < aux_type->size(); ++i) {
for (size_t i = 0; i < aux_type->size(); ++i) {
if ((*aux_type)[i] != -1) {
UNIFORM_TYPE_CHECK((*aux_type)[i], dtype_param, ListArguments()[i]);
}
Expand Down
10 changes: 5 additions & 5 deletions src/operator/bilinear_sampler.cu
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ __global__ void BilinearSamplerForwardKernel(const int i_c, const int i_h,
int h = (index / o_w) % o_h;
int c = (index / o_w / o_h) % o_c;
int n = index / o_w / o_h / o_c;
index_t out_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w;
index_t grid_index = n * o_h * o_w * 2 + h * o_w + w;
int out_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w;
int grid_index = n * o_h * o_w * 2 + h * o_w + w;
DType y_real = (*(grid + grid_index + o_h * o_w) + 1) * (i_h - 1) / 2;
DType x_real = (*(grid + grid_index) + 1) * (i_w - 1) / 2;
int top_left_y = static_cast<int>(floor(y_real));
Expand Down Expand Up @@ -96,16 +96,16 @@ __global__ void BilinearSamplerBackwardKernel(const int i_c, const int i_h,
int n = index / o_w / o_h;
DType top_left_y_gw = 0.0;
DType top_left_x_gw = 0.0;
index_t grid_src_index = n * o_h * o_w * 2 + h * o_w + w;
int grid_src_index = n * o_h * o_w * 2 + h * o_w + w;
DType y_real = (*(grid_src + grid_src_index + o_h * o_w) + 1) * (i_h - 1) / 2;
DType x_real = (*(grid_src + grid_src_index) + 1) * (i_w - 1) / 2;

int top_left_y = static_cast<int>(floor(y_real));
int top_left_x = static_cast<int>(floor(x_real));
DType top_left_y_w = 1.0 - (y_real - top_left_y);
DType top_left_x_w = 1.0 - (x_real - top_left_x);
for (index_t c = 0; c < o_c; ++c) {
index_t grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w;
for (int c = 0; c < o_c; ++c) {
int grad_index = n * o_c * o_h * o_w + c * o_h * o_w + h * o_w + w;
int data_index = n * i_c * i_h * i_w + c * i_h * i_w + top_left_y * i_w + top_left_x;
// calc 4 vertex value in input data
DType top_left_v = 0;
Expand Down
4 changes: 2 additions & 2 deletions src/operator/channel_op_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ inline void concatenate_helper(const std::vector<mshadow::Tensor<xpu, dim, DType
mshadow::Tensor<xpu, dim, DType> out = *output;
size_t size = input.size();
index_t begin = 0;
for (index_t i = 0; i < size; ++i) {
for (size_t i = 0; i < size; ++i) {
index_t end = begin + input[i].size(cdim);
Assign(slice<cdim>(out, begin, end), req, input[i]);
begin = end;
Expand Down Expand Up @@ -79,7 +79,7 @@ void split_helper(const mshadow::Tensor<xpu, dim, DType> &input,
std::vector<mshadow::Tensor<xpu, dim, DType> > out = *output;
size_t size = out.size();
index_t begin = 0;
for (index_t i = 0; i < size; ++i) {
for (size_t i = 0; i < size; ++i) {
index_t end = begin + out[i].size(cdim);
Assign(out[i], req[i], slice<cdim>(input, begin, end));
begin = end;
Expand Down
2 changes: 1 addition & 1 deletion src/operator/contrib/count_sketch-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class CountSketchProp : public OperatorProperty {
CHECK_GE(in_type->size(), 1);
int dtype = (*in_type)[0];
CHECK_NE(dtype, -1) << "First input must have specified type";
for (index_t i = 0; i < in_type->size(); ++i) {
for (size_t i = 0; i < in_type->size(); ++i) {
if ((*in_type)[i] == -1) {
(*in_type)[i] = dtype;
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/operator/contrib/deformable_convolution-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class DeformableConvolutionOp : public Operator {
// calculate the shape of col_buffer
TShape col_buffer_shape(num_spatial_axes_ + 1);
col_buffer_shape[0] = conv_in_channels_ * param_.kernel.Size();
for (index_t i = 1; i < col_buffer_shape.ndim(); ++i) {
for (size_t i = 1; i < col_buffer_shape.ndim(); ++i) {
col_buffer_shape[i] = out_data[0].shape_[i + 1];
}
// create a column buffer using workspace and col_buffer_shape
Expand Down Expand Up @@ -453,7 +453,7 @@ class DeformableConvolutionProp : public OperatorProperty {
CHECK_GE(in_type->size(), 1U);
int dtype = (*in_type)[0];
CHECK_NE(dtype, -1) << "First input must have specified type";
for (index_t i = 0; i < in_type->size(); ++i) {
for (size_t i = 0; i < in_type->size(); ++i) {
if ((*in_type)[i] == -1) {
(*in_type)[i] = dtype;
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/operator/contrib/fft-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class FFTProp : public OperatorProperty {
CHECK_GE(in_type->size(), 1);
int dtype = (*in_type)[0];
CHECK_NE(dtype, -1) << "First input must have specified type";
for (index_t i = 0; i < in_type->size(); ++i) {
for (size_t i = 0; i < in_type->size(); ++i) {
if ((*in_type)[i] == -1) {
(*in_type)[i] = dtype;
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/operator/contrib/ifft-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ class IFFTProp : public OperatorProperty {
CHECK_GE(in_type->size(), 1);
int dtype = (*in_type)[0];
CHECK_NE(dtype, -1) << "First input must have specified type";
for (index_t i=0; i < in_type->size(); ++i) {
for (size_t i=0; i < in_type->size(); ++i) {
if ((*in_type)[i] == -1) {
(*in_type)[i] = dtype;
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/operator/contrib/sync_batch_norm-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,14 +500,14 @@ class SyncBatchNormProp : public OperatorProperty {
// For other input types, these parameters have the same type as input
// NOTE: This requirement is from cuDNN (v. 4 and 5)
int dtype_param = (dtype == kFloat16) ? kFloat32 : dtype;
for (index_t i = 1; i < in_type->size(); ++i) {
for (size_t i = 1; i < in_type->size(); ++i) {
if ((*in_type)[i] == -1) {
(*in_type)[i] = dtype_param;
} else {
UNIFORM_TYPE_CHECK((*in_type)[i], dtype_param, ListArguments()[i]);
}
}
for (index_t i = 0; i < aux_type->size(); ++i) {
for (size_t i = 0; i < aux_type->size(); ++i) {
if ((*aux_type)[i] != -1) {
UNIFORM_TYPE_CHECK((*aux_type)[i], dtype_param, ListArguments()[i]);
}
Expand Down
12 changes: 5 additions & 7 deletions src/operator/convolution_v1-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,10 @@ class ConvolutionV1Op : public Operator {
oshape[2] * oshape[3]);
// param_.workspace is in elements of sizeof(DType)
// if param_.workspace is set to zero the nstep_ equals ishape[0] (batch)
nstep_ = std::max(
std::min(
static_cast<index_t>(
param_.workspace / (shape_colunit_.Size() + shape_dstunit_.Size())),
ishape[0]),
1U);
nstep_ = std::max<index_t>(
std::min(static_cast<index_t>(param_.workspace) /
(shape_colunit_.Size() + shape_dstunit_.Size()), ishape[0]),
1);

mshadow::Shape<2> scol = mshadow::Shape2(shape_colunit_[0],
shape_colunit_[1] * nstep_);
Expand Down Expand Up @@ -502,7 +500,7 @@ class ConvolutionV1Prop : public OperatorProperty {
CHECK_GE(in_type->size(), 1);
int dtype = (*in_type)[0];
CHECK_NE(dtype, -1) << "First input must have specified type";
for (index_t i = 0; i < in_type->size(); ++i) {
for (size_t i = 0; i < in_type->size(); ++i) {
if ((*in_type)[i] == -1) {
(*in_type)[i] = dtype;
} else {
Expand Down
6 changes: 3 additions & 3 deletions src/operator/custom/custom.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,14 @@ std::vector<nnvm::NodeEntry> Gradient(
}

std::vector<nnvm::NodeEntry> ret;
for (index_t i = 0; i < params.num_args; ++i) {
ret.emplace_back(nnvm::NodeEntry{g, i, 0});
for (size_t i = 0; i < params.num_args; ++i) {
ret.emplace_back(nnvm::NodeEntry{g, static_cast<uint32_t>(i), 0});
}
if (params.num_auxs) {
nnvm::NodePtr ng = nnvm::Node::Create();
ng->attrs.op = nnvm::Op::Get("_NoGradient");
ng->attrs.name = "NoGradient";
for (index_t i = 0; i < params.num_auxs; ++i) {
for (size_t i = 0; i < params.num_auxs; ++i) {
ret.emplace_back(nnvm::NodeEntry{ng, 0, 0});
}
}
Expand Down
Loading

0 comments on commit f9f7416

Please sign in to comment.