This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch unity
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/unity by this push:
     new 603f8bd721 [Unity][Op] Negative Log Likelihood Loss (#14517)
603f8bd721 is described below

commit 603f8bd721dc2756115aac5f113958cbe8ac2ce3
Author: Chaofan Lin <[email protected]>
AuthorDate: Fri Apr 7 00:10:13 2023 +0800

    [Unity][Op] Negative Log Likelihood Loss (#14517)
    
    This PR adds support for Negative Log Likelihood Loss
---
 include/tvm/relax/attrs/nn.h                       |  13 +
 python/tvm/relax/op/nn/nn.py                       |  45 +++
 python/tvm/relax/transform/legalize_ops/nn.py      |  29 ++
 src/relax/op/nn/nn.cc                              | 225 +++++++++++
 src/relax/op/nn/nn.h                               |   4 +
 tests/python/relax/test_op_nn.py                   | 437 +++++++++++++++++++++
 .../python/relax/test_transform_legalize_ops_nn.py | 257 ++++++++++++
 tests/python/relax/test_tvmscript_parser_op_nn.py  |  44 +++
 8 files changed, 1054 insertions(+)

diff --git a/include/tvm/relax/attrs/nn.h b/include/tvm/relax/attrs/nn.h
index bcfe3207bc..36e15909e2 100644
--- a/include/tvm/relax/attrs/nn.h
+++ b/include/tvm/relax/attrs/nn.h
@@ -285,6 +285,19 @@ struct GroupNormAttrs : public 
tvm::AttrsNode<GroupNormAttrs> {
   }
 };  // struct GroupNormAttrs
 
+/*! \brief Attributes used in nll_loss operator */
+struct NLLLossAttrs : public tvm::AttrsNode<NLLLossAttrs> {
+  String reduction;
+  int ignore_index;
+
+  TVM_DECLARE_ATTRS(NLLLossAttrs, "relax.attrs.NLLLossAttrs") {
+    TVM_ATTR_FIELD(reduction).set_default("mean").describe(
+        "The reduction method to apply to the output. Can be"
+        "'none', 'mean' or 'sum'.");
+    TVM_ATTR_FIELD(ignore_index).describe("The target value to ignore.");
+  }
+};  // struct NLLLossAttrs
+
 /*! \brief Attributes used in dropout operator */
 struct DropoutAttrs : public tvm::AttrsNode<DropoutAttrs> {
   double rate;
diff --git a/python/tvm/relax/op/nn/nn.py b/python/tvm/relax/op/nn/nn.py
index 02468637e0..cd1dfe1fb0 100644
--- a/python/tvm/relax/op/nn/nn.py
+++ b/python/tvm/relax/op/nn/nn.py
@@ -914,6 +914,51 @@ def cross_entropy_with_logits(predictions: Expr, labels: 
Expr) -> Expr:
     return _ffi_api.cross_entropy_with_logits(predictions, labels)  # type: 
ignore
 
 
+def nll_loss(
+    predictions: Expr,
+    targets: Expr,
+    weights: Optional[Expr] = None,
+    reduction: str = "mean",
+    ignore_index: int = -100,
+) -> Expr:
+    """Negative log likelihood loss.
+
+    `output[n, i_1, i_2, ..., i_k] = -p * w`, where
+    - `p = predictions[n, t, i_1, i_2, i_k]`,
+    - `t = targets[n, i_1, i_2, ..., i_k]`,
+    - `w = weights[t] if t != ignore_index else 0`
+
+    result = reduction(output)
+
+    Parameters
+    ----------
+    predictions : relax.Expr
+      The predictions. Should be a `(k+2)-D` Tensor with shape `(N, C, d_1, 
d_2, ..., d_k)` where C
+      is the number of target classes.
+
+    targets : relax.Expr
+      The target value of each prediction. Should be a `(k+1)-D` Tensor with 
shape
+      `(N, d_1, d_2, ..., d_k)`. Must be of int dtype.
+
+    weights : Optional[relax.Expr]
+      The weight of each target value. Should be a `1-D` Tensor with shape 
`(C,)`.
+      If not specified, it is treated as if having all ones.
+
+    reduction : str
+      The reduction method to apply to the output.
+      Possible values are "mean", "sum" and "none".
+
+    ignore_index : int
+      The target value to ignore.
+
+    Returns
+    -------
+    result : relax.Expr
+      The computed result.
+    """
+    return _ffi_api.nll_loss(predictions, targets, weights, reduction, 
ignore_index)  # type: ignore
+
+
 def attention(
     query: Expr,
     key: Expr,
diff --git a/python/tvm/relax/transform/legalize_ops/nn.py 
b/python/tvm/relax/transform/legalize_ops/nn.py
index 1ce4520635..59146186f9 100644
--- a/python/tvm/relax/transform/legalize_ops/nn.py
+++ b/python/tvm/relax/transform/legalize_ops/nn.py
@@ -368,3 +368,32 @@ def _nn_attention_bias(bb: BlockBuilder, call: Call) -> 
Expr:
         call.attrs.scale,
         primfunc_name_hint="attention_bias",
     )
+
+
+@register_legalize("relax.nn.nll_loss")
+def _nn_nll_loss(bb: BlockBuilder, call: Call) -> Expr:
+    def nll_loss_without_weight(predictions, targets, reduction, ignore_index):
+        weight = topi.full(
+            (predictions.shape[1] if len(predictions.shape) > 1 else 
predictions.shape[0],),
+            predictions.dtype,
+            1.0,
+        )
+        return topi.nn.nll_loss(predictions, targets, weight, reduction, 
ignore_index)
+
+    if len(call.args) == 2:
+        return bb.call_te(
+            nll_loss_without_weight,
+            call.args[0],
+            call.args[1],
+            reduction=call.attrs.reduction,
+            ignore_index=call.attrs.ignore_index,
+        )
+
+    return bb.call_te(
+        topi.nn.nll_loss,
+        call.args[0],
+        call.args[1],
+        call.args[2],
+        reduction=call.attrs.reduction,
+        ignore_index=call.attrs.ignore_index,
+    )
diff --git a/src/relax/op/nn/nn.cc b/src/relax/op/nn/nn.cc
index c3e18f8e3b..54faf3ee84 100644
--- a/src/relax/op/nn/nn.cc
+++ b/src/relax/op/nn/nn.cc
@@ -492,5 +492,230 @@ TVM_REGISTER_OP("relax.nn.cross_entropy_with_logits")
     .add_argument("labels", "Tensor", "The labels.")
     .set_attr<FInferStructInfo>("FInferStructInfo", 
InferStructInfoCrossEntropy);
 
+/* relax.nn.nll_loss */
+TVM_REGISTER_NODE_TYPE(NLLLossAttrs);
+
+Expr nll_loss(Expr predictions, Expr targets, Optional<Expr> weights, String 
reduction,
+              int ignore_index) {
+  ObjectPtr<NLLLossAttrs> attrs = make_object<NLLLossAttrs>();
+
+  ICHECK(reduction == "none" || reduction == "sum" || reduction == "mean")
+      << "The argument reduction of NLLLoss should be one of the following "
+         "values: none, mean, sum. However, the given value is "
+      << reduction;
+
+  attrs->reduction = std::move(reduction);
+  attrs->ignore_index = ignore_index;
+
+  static const Op& op = Op::Get("relax.nn.nll_loss");
+  if (weights.defined()) {
+    return Call(op, {std::move(predictions), std::move(targets), 
std::move(weights.value())},
+                Attrs{attrs}, {});
+  } else {
+    return Call(op, {std::move(predictions), std::move(targets)}, 
Attrs{attrs}, {});
+  }
+}
+
+TVM_REGISTER_GLOBAL("relax.op.nn.nll_loss").set_body_typed(nll_loss);
+
+StructInfo InferStructInfoNLLLoss(const Call& call, const BlockBuilder& ctx) {
+  if (call->args.size() < 2 || call->args.size() > 3) {
+    ctx->ReportFatal(Diagnostic::Error(call) << "NLLLoss op should take 2 or 3 
arguments");
+  }
+
+  const auto* pred_sinfo = 
GetStructInfoAs<TensorStructInfoNode>(call->args[0]);
+  const auto* tgt_sinfo = GetStructInfoAs<TensorStructInfoNode>(call->args[1]);
+  const TensorStructInfoNode* wgt_sinfo = nullptr;
+  if (call->args.size() == 3) {
+    wgt_sinfo = GetStructInfoAs<TensorStructInfoNode>(call->args[2]);
+    if (wgt_sinfo == nullptr) {
+      ctx->ReportFatal(
+          Diagnostic::Error(call)
+          << "NLLLoss requires the argument weights to be Tensor. However, the 
given one is "
+          << call->args[2]->struct_info_->GetTypeKey());
+    }
+  }
+
+  if (pred_sinfo == nullptr) {
+    ctx->ReportFatal(
+        Diagnostic::Error(call)
+        << "NLLLoss requires the argument preditions to be Tensor. However, 
the given one is "
+        << call->args[0]->struct_info_->GetTypeKey());
+  }
+  if (tgt_sinfo == nullptr) {
+    ctx->ReportFatal(
+        Diagnostic::Error(call)
+        << "NLLLoss requires the argument targets to be Tensor. However, the 
given one is "
+        << call->args[1]->struct_info_->GetTypeKey());
+  }
+
+  // infer dtype
+  DataType output_dtype;
+  if (wgt_sinfo != nullptr) {
+    output_dtype = InferBinaryArithOpOutDtype(call, ctx, 
GetRef<TensorStructInfo>(pred_sinfo),
+                                              
GetRef<TensorStructInfo>(wgt_sinfo));
+  } else {
+    output_dtype = pred_sinfo->dtype;
+  }
+
+  // the type of targets must be int/uint.
+  if (!tgt_sinfo->IsUnknownDtype() && !tgt_sinfo->dtype.is_int() && 
!tgt_sinfo->dtype.is_uint()) {
+    ctx->ReportFatal(
+        Diagnostic::Error(call)
+        << "NLLLoss expects the dtype of targets to be int/uint. However, the 
dtype of targets is "
+        << tgt_sinfo->dtype);
+  }
+
+  // infer ndim
+  int K = kUnknownNDim;  // k dim
+  if (!pred_sinfo->IsUnknownNdim()) {
+    if (pred_sinfo->ndim < 1) {
+      ctx->ReportFatal(
+          Diagnostic::Error(call)
+          << "NLLLoss expects the ndim of predictions >= 1. However, the ndim 
of predictions is "
+          << pred_sinfo->ndim);
+    }
+    K = pred_sinfo->ndim <= 2 ? 0 : pred_sinfo->ndim - 2;
+  }
+  if (!tgt_sinfo->IsUnknownNdim()) {
+    int K_tgt = tgt_sinfo->ndim <= 1 ? 0 : tgt_sinfo->ndim - 1;
+    if (K != kUnknownNDim && K != K_tgt) {
+      ctx->ReportFatal(Diagnostic::Error(call)
+                       << "NLLLoss expects number of dimensions K inferred 
from different "
+                          "arguments to be equal. However, K from predictions 
is "
+                       << K << " while K from targets is " << K_tgt);
+    }
+  }
+  if (wgt_sinfo != nullptr && !wgt_sinfo->IsUnknownNdim() && wgt_sinfo->ndim 
!= 1) {
+    ctx->ReportFatal(Diagnostic::Error(call)
+                     << "NLLLoss expects the ndim of weights == 1. However, 
the ndim of weights is "
+                     << wgt_sinfo->ndim);
+  }
+
+  arith::Analyzer* analyzer = ctx->GetAnalyzer();
+  Optional<PrimExpr> N;
+  Optional<PrimExpr> C;
+  Array<PrimExpr> output_shape;  // N, d1, d2, ..., dk
+
+  Optional<Array<PrimExpr>> pred_shape_value;
+  if (pred_sinfo->shape.defined()) {
+    pred_shape_value = 
GetStructInfoAs<ShapeStructInfoNode>(pred_sinfo->shape.value())->values;
+  }
+  if (pred_shape_value.defined()) {
+    if (pred_shape_value.value().size() == 1) {
+      // (C,)
+      ICHECK(pred_sinfo->ndim == 1);
+      C = pred_shape_value.value()[0];
+    } else {
+      // (N, C, d1, d2, ..., dk)
+      ICHECK(pred_shape_value.value().size() >= 2);
+      ICHECK(pred_sinfo->ndim == 
static_cast<int>(pred_shape_value.value().size()));
+      N = pred_shape_value.value()[0];
+      C = pred_shape_value.value()[1];
+      output_shape = Array<PrimExpr>();
+      output_shape.push_back(N.value());
+      for (size_t i = 2; i < pred_shape_value.value().size(); ++i) {
+        output_shape.push_back(pred_shape_value.value()[i]);
+      }
+    }
+  }
+
+  Optional<Array<PrimExpr>> tgt_shape_value;
+  if (tgt_sinfo->shape.defined()) {
+    tgt_shape_value = 
GetStructInfoAs<ShapeStructInfoNode>(tgt_sinfo->shape.value())->values;
+  }
+  if (tgt_shape_value.defined()) {
+    if (tgt_shape_value.value().empty()) {
+      // ()
+      ICHECK(tgt_sinfo->ndim == 0);
+      if (N.defined()) {
+        ctx->ReportFatal(Diagnostic::Error(call)
+                         << "Shape mismatch for NLLLoss. Predictions shape is "
+                            "(N, C, ...) while targets is a scalar");
+      }
+    } else {
+      // (N,) or (N, d1, d2, ..., dk)
+      // check N
+      const PrimExpr& N_tgt = tgt_shape_value.value()[0];
+      if (N.defined() && analyzer->CanProve(N.value() != N_tgt)) {
+        ctx->ReportFatal(Diagnostic::Error(call)
+                         << "NLLLoss expects minibatch size N inferred from 
different "
+                            "arguments to be equal. However, N from 
predictions is "
+                         << N << " while N from targets is " << N_tgt);
+      }
+      // only C case
+      if (!N.defined() && C.defined()) {
+        ctx->ReportFatal(Diagnostic::Error(call)
+                         << "Shape mismatch for NLLLoss. Predictions shape is "
+                            "(C,) while targets is not a scalar");
+      }
+
+      if (tgt_shape_value.value().size() == 1) {
+        // (N,)
+        ICHECK(tgt_sinfo->IsUnknownNdim() || tgt_sinfo->ndim == 1);
+      } else {
+        // (N, d1, d2, ..., dk)
+        ICHECK(tgt_shape_value.value().size() >= 2);
+        ICHECK(tgt_sinfo->IsUnknownNdim() ||
+               tgt_sinfo->ndim == 
static_cast<int>(tgt_shape_value.value().size()));
+
+        if (pred_shape_value.defined()) {
+          // check (d1, d2, ..., dk)
+          for (size_t i = 1; i < tgt_shape_value.value().size(); ++i) {
+            if (analyzer->CanProve(output_shape[i] != 
tgt_shape_value.value()[i])) {
+              ctx->ReportFatal(Diagnostic::Error(call)
+                               << "Shape mismatch for NLLLoss. The prediction 
shape at this dim is "
+                               << output_shape[i] << " while the target shape 
at this dim is "
+                               << tgt_shape_value.value()[i]);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if (wgt_sinfo != nullptr) {
+    Optional<Array<PrimExpr>> wgt_shape_value;
+    if (wgt_sinfo->shape.defined()) {
+      wgt_shape_value = 
GetStructInfoAs<ShapeStructInfoNode>(wgt_sinfo->shape.value())->values;
+    }
+    if (wgt_shape_value.defined()) {
+      ICHECK(wgt_shape_value.value().size() == 1);
+      ICHECK(wgt_sinfo->IsUnknownNdim() || wgt_sinfo->ndim == 1);
+      const PrimExpr& C_wgt = wgt_shape_value.value()[0];
+      if (C.defined() && analyzer->CanProve(C.value() != C_wgt)) {
+        ctx->ReportFatal(Diagnostic::Error(call)
+                         << "NLLLoss expects number of classes C inferred from 
different "
+                            "arguments to be equal. However, C from 
predictions is "
+                         << C << " while C from weights is " << C_wgt);
+      }
+    }
+  }
+
+  const auto* attrs = call->attrs.as<NLLLossAttrs>();
+  String reduction = attrs->reduction;
+
+  if (reduction == "none") {
+    // () or (N,) or (N, d1, d2, ..., dk)
+    if (pred_sinfo->shape.as<ShapeExprNode>()) {
+      return TensorStructInfo(ShapeExpr(output_shape), output_dtype);
+    } else {
+      int output_ndim = pred_sinfo->ndim == kUnknownNDim ? kUnknownNDim : 
pred_sinfo->ndim - 1;
+      return TensorStructInfo(output_dtype, /*ndim=*/output_ndim);
+    }
+  } else {
+    // sum or mean. output is scalar
+    return TensorStructInfo(/*shape=*/ShapeExpr(Array<PrimExpr>()), 
output_dtype);
+  }
+}
+
+TVM_REGISTER_OP("relax.nn.nll_loss")
+    .set_attrs_type<NLLLossAttrs>()
+    .set_num_inputs(3)
+    .add_argument("predictions", "Tensor", "The prediction tensor.")
+    .add_argument("targets", "Tensor", "The target tensor.")
+    .add_argument("weights", "Optional<Tensor>", "The weight of each target 
values.")
+    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoNLLLoss);
+
 }  // namespace relax
 }  // namespace tvm
diff --git a/src/relax/op/nn/nn.h b/src/relax/op/nn/nn.h
index f578f89346..f0962f3018 100644
--- a/src/relax/op/nn/nn.h
+++ b/src/relax/op/nn/nn.h
@@ -85,6 +85,10 @@ Expr dropout(Expr data, double rate);
 /*! \brief CrossEntropy with logits. */
 Expr cross_entropy_with_logits(Expr predictions, Expr labels);
 
+/*! \brief Negative log likelihood loss. */
+Expr nll_loss(Expr predictions, Expr targets, Optional<Expr> weights, String 
reduction,
+              int ignore_index);
+
 }  // namespace relax
 }  // namespace tvm
 
diff --git a/tests/python/relax/test_op_nn.py b/tests/python/relax/test_op_nn.py
index 5114478463..1b811ba0e5 100644
--- a/tests/python/relax/test_op_nn.py
+++ b/tests/python/relax/test_op_nn.py
@@ -1320,5 +1320,442 @@ def 
test_cross_entropy_infer_struct_info_wrong_input_type():
         bb.normalize(relax.op.nn.cross_entropy_with_logits(x1, y))
 
 
+def test_nll_loss_infer_struct_info():
+    bb = relax.BlockBuilder()
+
+    x0 = relax.Var("x", R.Tensor((3, 5, 10, 10), "float32"))
+    x1 = relax.Var("x", R.Tensor("float32", ndim=4))
+    x2 = relax.Var("x", R.Tensor("float32"))
+    x3 = relax.Var("x", R.Tensor((3, 5, 10, 10)))
+    x4 = relax.Var("x", R.Tensor((3, 5), "float32"))  # (N, C)
+    x5 = relax.Var("x", R.Tensor((5,), "float32"))  # (C,)
+
+    y0 = relax.Var("y", R.Tensor((3, 10, 10), "int64"))
+    y1 = relax.Var("y", R.Tensor("int64", ndim=3))
+    y2 = relax.Var("y", R.Tensor("int64"))
+    y3 = relax.Var("y", R.Tensor((3, 10, 10)))
+    y4 = relax.Var("y", R.Tensor((3,)))  # (N,)
+    y5 = relax.Var("y", R.Tensor(()))  # ()
+
+    w0 = relax.Var("w", R.Tensor((5,), "float32"))
+    w1 = relax.Var("w", R.Tensor("float32", ndim=1))
+    w2 = relax.Var("w", R.Tensor("float32"))
+    w3 = relax.Var("w", R.Tensor((5,)))
+
+    # reduction = mean
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x1, y0, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x2, y0, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x3, y0, w0, reduction="mean"),
+        relax.TensorStructInfo((), ""),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y1, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y2, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y3, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w1, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w2, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w3, reduction="mean"),
+        relax.TensorStructInfo((), ""),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x4, y4, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x5, y5, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+
+    # reduction=sum is totally the same as mean. Just need one test to ensure 
they behave the same
+    _check_inference(
+        bb, relax.op.nn.nll_loss(x0, y0, w0, reduction="sum"), 
relax.TensorStructInfo((), "float32")
+    )
+
+    # reduction=none
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w0, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x1, y0, w0, reduction="none"),
+        relax.TensorStructInfo(dtype="float32", ndim=3),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x2, y0, w0, reduction="none"),
+        relax.TensorStructInfo(dtype="float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x3, y0, w0, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), ""),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y1, w0, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y2, w0, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y3, w0, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w1, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w2, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w3, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), ""),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x4, y4, w0, reduction="none"),
+        relax.TensorStructInfo((3,), "float32"),  # (N,)
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x5, y5, w0, reduction="none"),
+        relax.TensorStructInfo((), "float32"),  # ()
+    )
+
+
+def test_nll_loss_infer_struct_info_shape_symbolic():
+    bb = relax.BlockBuilder()
+    N = tir.Var("N", "int64")
+    C = tir.Var("C", "int64")
+    d1 = tir.Var("d", "int64")
+    d2 = tir.Var("d", "int64")
+    x0 = relax.Var("x", R.Tensor((N, C, d1, d2), "float32"))
+    x1 = relax.Var("x", R.Tensor((N, C), "float32"))
+    x2 = relax.Var("x", R.Tensor((C,), "float32"))
+    x3 = relax.Var("x", R.Tensor((3, C, d1, 2), "float32"))
+    y0 = relax.Var("y", R.Tensor((N, d1, d2), "int64"))
+    y1 = relax.Var("y", R.Tensor((N,), "int64"))
+    y2 = relax.Var("y", R.Tensor((), "int64"))
+    y3 = relax.Var("y", R.Tensor((3, d1, 2), "int64"))
+    w0 = relax.Var("w", R.Tensor((C,), "float32"))
+    w1 = relax.Var("w", R.Tensor((5,), "float32"))
+
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w0, reduction="none"),
+        relax.TensorStructInfo((N, d1, d2), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x1, y1, w0, reduction="none"),
+        relax.TensorStructInfo((N,), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x2, y2, w0, reduction="none"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x3, y3, w0, reduction="none"),
+        relax.TensorStructInfo((3, d1, 2), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x3, y3, w1, reduction="none"),
+        relax.TensorStructInfo((3, d1, 2), "float32"),
+    )
+
+
+def test_nll_loss_infer_struct_info_shape_var():
+    bb = relax.BlockBuilder()
+
+    s0 = relax.Var("s0", relax.ShapeStructInfo((3, 5, 10, 10)))
+    s1 = relax.Var("s1", relax.ShapeStructInfo(ndim=4))
+    s2 = relax.Var("s2", relax.ShapeStructInfo())
+    s3 = relax.Var("s3", relax.ShapeStructInfo((3, 10, 10)))
+    s4 = relax.Var("s4", relax.ShapeStructInfo(ndim=3))
+    s5 = relax.Var("s5", relax.ShapeStructInfo((5,)))
+    s6 = relax.Var("s6", relax.ShapeStructInfo(ndim=1))
+
+    x0 = relax.Var("x", relax.TensorStructInfo(s0, "float32"))
+    x1 = relax.Var("x", relax.TensorStructInfo(s1, "float32"))
+    x2 = relax.Var("x", relax.TensorStructInfo(s2, "float32"))
+    y0 = relax.Var("y", relax.TensorStructInfo(s3, "int64"))
+    y1 = relax.Var("y", relax.TensorStructInfo(s4, "int64"))
+    w0 = relax.Var("w", relax.TensorStructInfo(s5, "float32"))
+    w1 = relax.Var("w", relax.TensorStructInfo(s6, "float32"))
+
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w0, reduction="none"),
+        relax.TensorStructInfo(dtype="float32", ndim=3),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x1, y0, w0, reduction="none"),
+        relax.TensorStructInfo(dtype="float32", ndim=3),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x2, y0, w0, reduction="none"),
+        relax.TensorStructInfo(dtype="float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y1, w0, reduction="none"),
+        relax.TensorStructInfo(dtype="float32", ndim=3),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w1, reduction="none"),
+        relax.TensorStructInfo(dtype="float32", ndim=3),
+    )
+
+
+def test_nll_loss_infer_struct_info_no_weights():
+    bb = relax.BlockBuilder()
+    x = relax.Var("x", R.Tensor((3, 5, 10, 10), "float32"))
+    y = relax.Var("x", R.Tensor((3, 10, 10), "int64"))
+
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x, y, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x, y, reduction="none"),
+        relax.TensorStructInfo((3, 10, 10), "float32"),
+    )
+
+
+def test_nll_loss_infer_struct_info_no_weights_symbolic():
+    N = tir.Var("N", "int64")
+    C = tir.Var("C", "int64")
+    d1 = tir.Var("d", "int64")
+    d2 = tir.Var("d", "int64")
+    bb = relax.BlockBuilder()
+    x = relax.Var("x", R.Tensor((N, C, d1, d2), "float32"))
+    y = relax.Var("y", R.Tensor((N, d1, d2), "int64"))
+
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x, y, reduction="mean"),
+        relax.TensorStructInfo((), "float32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x, y, reduction="none"),
+        relax.TensorStructInfo((N, d1, d2), "float32"),
+    )
+
+
+def test_nll_loss_infer_struct_info_wrong_input_type():
+    bb = relax.BlockBuilder()
+    x0 = relax.Var("x", R.Tensor((3, 5, 10, 10), "float32"))
+    x1 = relax.Var("x", relax.ShapeStructInfo((2, 3)))
+    x2 = relax.Var("x", relax.FuncStructInfo([], R.Tensor((2, 3), "float32")))
+    y0 = relax.Var("y", R.Tensor((3, 10, 10), "int64"))
+    y1 = relax.Var("y", relax.ShapeStructInfo((2, 3)))
+    y2 = relax.Var("y", relax.FuncStructInfo([], R.Tensor((2, 3), "float32")))
+    w0 = relax.Var("w", R.Tensor((5,), "float32"))
+    w1 = relax.Var("w", relax.ShapeStructInfo((2, 3)))
+    w2 = relax.Var("w", relax.FuncStructInfo([], R.Tensor((2, 3), "float32")))
+
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x1, y0, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x2, y0, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y1, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y2, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y0, w1))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y0, w2))
+
+
+def test_nll_loss_infer_struct_info_more_input_dtype():
+    bb = relax.BlockBuilder()
+    x0 = relax.Var("x", R.Tensor((3, 5, 10, 10), "float16"))
+    x1 = relax.Var("x", R.Tensor((3, 5, 10, 10), "int8"))
+    x2 = relax.Var("x", R.Tensor((3, 5, 10, 10), "int32"))
+    x3 = relax.Var("x", R.Tensor((3, 5, 10, 10), "float64"))
+    y0 = relax.Var("y", R.Tensor((3, 10, 10), "int8"))
+    w0 = relax.Var("y", R.Tensor((5,), "float16"))
+    w1 = relax.Var("y", R.Tensor((5,), "int8"))
+    w2 = relax.Var("y", R.Tensor((5,), "int32"))
+    w3 = relax.Var("y", R.Tensor((5,), "float64"))
+
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x0, y0, w0, reduction="mean"),
+        relax.TensorStructInfo((), "float16"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x1, y0, w1, reduction="mean"),
+        relax.TensorStructInfo((), "int8"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x2, y0, w2, reduction="mean"),
+        relax.TensorStructInfo((), "int32"),
+    )
+    _check_inference(
+        bb,
+        relax.op.nn.nll_loss(x3, y0, w3, reduction="mean"),
+        relax.TensorStructInfo((), "float64"),
+    )
+
+
+def test_nll_loss_infer_struct_info_targets_dtype():
+    bb = relax.BlockBuilder()
+    x = relax.Var("x", R.Tensor((3, 5, 10, 10), "float32"))
+    w = relax.Var("w", R.Tensor((5,), "float32"))
+    targets0 = relax.Var("targets", R.Tensor((3, 10, 10), "float32"))
+    targets1 = relax.Var("targets", R.Tensor((3, 10, 10), "float64"))
+    targets2 = relax.Var("targets", R.Tensor((3, 10, 10), "bool"))
+    targets3 = relax.Var("targets", R.Tensor((3, 10, 10), "int32"))
+    targets4 = relax.Var("targets", R.Tensor((3, 10, 10), "int64"))
+    targets5 = relax.Var("targets", R.Tensor((3, 10, 10), "uint32"))
+    targets6 = relax.Var("targets", R.Tensor((3, 10, 10), ""))
+
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x, targets0, w))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x, targets1, w))
+
+    # correct cases
+    bb.normalize(relax.op.nn.nll_loss(x, targets2, w))  # bool is uint1
+    bb.normalize(relax.op.nn.nll_loss(x, targets3, w))
+    bb.normalize(relax.op.nn.nll_loss(x, targets4, w))
+    bb.normalize(relax.op.nn.nll_loss(x, targets5, w))
+    bb.normalize(relax.op.nn.nll_loss(x, targets6, w))  # unknwon dtype
+
+
+def test_nll_loss_infer_struct_info_ndim_mismatch():
+    bb = relax.BlockBuilder()
+    x0 = relax.Var("x", R.Tensor((3, 5, 10, 10), "float32"))
+    x1 = relax.Var("x", R.Tensor((3, 5, 10, 10, 10), "float32"))
+    x2 = relax.Var("x", R.Tensor((3, 5, 10), "float32"))
+    y0 = relax.Var("x", R.Tensor((3, 10, 10), "int64"))
+    y1 = relax.Var("x", R.Tensor((3, 10, 10, 10), "int64"))
+    y2 = relax.Var("x", R.Tensor((3, 10), "int64"))
+    w0 = relax.Var("w", R.Tensor((5,), "float32"))
+    w1 = relax.Var("w", R.Tensor((5, 5), "float32"))
+    w2 = relax.Var("w", R.Tensor((), "float32"))
+
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x1, y0, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x2, y0, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y1, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y2, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y0, w1))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y0, w2))
+
+
+def test_nll_loss_infer_struct_info_shape_mismatch():
+    bb = relax.BlockBuilder()
+    x0 = relax.Var("x", R.Tensor((3, 5, 10, 10), "float32"))
+    x1 = relax.Var("x", R.Tensor((3, 6, 10, 10), "float32"))
+    x2 = relax.Var("x", R.Tensor((4, 5, 10, 10), "float32"))
+    x3 = relax.Var("x", R.Tensor((3, 5, 11, 10), "float32"))
+    y0 = relax.Var("x", R.Tensor((3, 10, 10), "int64"))
+    y1 = relax.Var("x", R.Tensor((4, 10, 10), "int64"))
+    y2 = relax.Var("x", R.Tensor((3, 11, 10), "int64"))
+    w0 = relax.Var("w", R.Tensor((5,), "float32"))
+    w1 = relax.Var("w", R.Tensor((4,), "float32"))
+
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x1, y0, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x2, y0, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x3, y0, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y1, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y2, w0))
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x0, y0, w1))
+
+
+def test_nll_loss_infer_struct_info_wrong_reduction():
+    bb = relax.BlockBuilder()
+    x = relax.Var("x", R.Tensor((3, 5, 10, 10), "float32"))
+    y = relax.Var("x", R.Tensor((3, 10, 10), "int64"))
+    w = relax.Var("w", R.Tensor((5,), "float32"))
+
+    with pytest.raises(TVMError):
+        bb.normalize(relax.op.nn.nll_loss(x, y, w, reduction="foo"))
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/tests/python/relax/test_transform_legalize_ops_nn.py 
b/tests/python/relax/test_transform_legalize_ops_nn.py
index e807082e35..fe663527c3 100644
--- a/tests/python/relax/test_transform_legalize_ops_nn.py
+++ b/tests/python/relax/test_transform_legalize_ops_nn.py
@@ -2443,5 +2443,262 @@ def test_attention():
     tvm.ir.assert_structural_equal(mod, Expected)
 
 
+def test_nll_loss():
+    # fmt: off
+    @tvm.script.ir_module
+    class NLLLoss:
+        @R.function
+        def main(predictions: R.Tensor((2, 3, 4, 5), "float32"), targets: 
R.Tensor((2, 4, 5), "int64"), weights: R.Tensor((4,), "float32")) -> 
R.Tensor((), "float32"):
+            gv: R.Tensor((), "float32") = R.nn.nll_loss(predictions, targets, 
weights, reduction="mean", ignore_index=-1)
+            return gv
+
+    @tvm.script.ir_module
+    class Expected:
+        @R.function
+        def main(predictions: R.Tensor((2, 3, 4, 5), dtype="float32"), 
targets: R.Tensor((2, 4, 5), dtype="int64"), weights: R.Tensor((4,), 
dtype="float32"),) -> R.Tensor((), dtype="float32"):
+            # block 0
+            gv = R.call_tir(Expected.nll_loss, (predictions, targets, 
weights), R.Tensor((), dtype="float32"))
+            return gv
+
+        @T.prim_func
+        def nll_loss(rxplaceholder: T.Buffer((T.int64(2), T.int64(3), 
T.int64(4), T.int64(5)), "float32"), rxplaceholder_1: T.Buffer((T.int64(2), 
T.int64(4), T.int64(5)), "int64"), rxplaceholder_2: T.Buffer(T.int64(4), 
"float32"), T_divide: T.Buffer((), "float32"),):
+            # function attr dict
+            T.func_attr({"tir.noalias": True})
+            # body
+            # with T.block("root")
+            nll_loss = T.alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], 
dtype="float32")
+            nll_loss_red = T.alloc_buffer([], dtype="float32")
+            nll_loss_1 = T.alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], 
dtype="float32")
+            nll_loss_red_1 = T.alloc_buffer([], dtype="float32")
+            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss"):
+                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
+                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], 
rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2], 
rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]])
+                    T.writes(nll_loss[v_ax0, v_ax1, v_ax2])
+                    nll_loss[v_ax0, v_ax1, v_ax2] = 
T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), (T.float32(0) - 
rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2]) * 
rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0))
+            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss_red"):
+                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
+                    T.reads(nll_loss[v_k0, v_k1, v_k2])
+                    T.writes(nll_loss_red[()])
+                    with T.init():
+                        nll_loss_red[()] = T.float32(0)
+                    nll_loss_red[()] = nll_loss_red[()] + nll_loss[v_k0, v_k1, 
v_k2]
+            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss_1"):
+                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
+                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], 
rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]])
+                    T.writes(nll_loss_1[v_ax0, v_ax1, v_ax2])
+                    nll_loss_1[v_ax0, v_ax1, v_ax2] = 
T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), 
rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0))
+            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss_red_1"):
+                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
+                    T.reads(nll_loss_1[v_k0, v_k1, v_k2])
+                    T.writes(nll_loss_red_1[()])
+                    with T.init():
+                        nll_loss_red_1[()] = T.float32(0)
+                    nll_loss_red_1[()] = nll_loss_red_1[()] + nll_loss_1[v_k0, 
v_k1, v_k2]
+            with T.block("T_divide"):
+                vi = T.axis.spatial(1, T.int64(0))
+                T.reads(nll_loss_red[()], nll_loss_red_1[()])
+                T.writes(T_divide[()])
+                T_divide[()] = nll_loss_red[()] / nll_loss_red_1[()]
+    # fmt: on
+    mod = LegalizeOps()(NLLLoss)
+    tvm.ir.assert_structural_equal(mod, Expected)
+
+
+def test_nll_no_weight():
+    # fmt: off
+    @tvm.script.ir_module
+    class NLLLoss:
+        @R.function
+        def main(predictions: R.Tensor((2, 3, 4, 5), "float32"), targets: 
R.Tensor((2, 4, 5), "int64")) -> R.Tensor((), "float32"):
+            gv: R.Tensor((), "float32") = R.nn.nll_loss(predictions, targets, 
reduction="mean", ignore_index=-1)
+            return gv
+
+    @tvm.script.ir_module
+    class Expected:
+        @R.function
+        def main(predictions: R.Tensor((2, 3, 4, 5), dtype="float32"), 
targets: R.Tensor((2, 4, 5), dtype="int64"),) -> R.Tensor((), dtype="float32"):
+            # block 0
+            gv = R.call_tir(Expected.nll_loss_without_weight, (predictions, 
targets), R.Tensor((), dtype="float32"))
+            return gv
+
+        @T.prim_func
+        def nll_loss_without_weight(rxplaceholder: T.Buffer((T.int64(2), 
T.int64(3), T.int64(4), T.int64(5)), "float32"), rxplaceholder_1: 
T.Buffer((T.int64(2), T.int64(4), T.int64(5)), "int64"), T_divide: T.Buffer((), 
"float32"),):
+            # function attr dict
+            T.func_attr({"tir.noalias": True})
+            # body
+            # with T.block("root")
+            T_full = T.alloc_buffer([T.int64(3)], dtype="float32")
+            nll_loss = T.alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], 
dtype="float32")
+            nll_loss_red = T.alloc_buffer([], dtype="float32")
+            nll_loss_1 = T.alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], 
dtype="float32")
+            nll_loss_red_1 = T.alloc_buffer([], dtype="float32")
+            for ax0 in T.serial(T.int64(3)):
+                with T.block("T_full"):
+                    v_ax0 = T.axis.spatial(T.int64(3), ax0)
+                    T.reads()
+                    T.writes(T_full[v_ax0])
+                    T_full[v_ax0] = T.float32(1)
+            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss"):
+                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
+                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], 
rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2], 
T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]])
+                    T.writes(nll_loss[v_ax0, v_ax1, v_ax2])
+                    nll_loss[v_ax0, v_ax1, v_ax2] = 
T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), (T.float32(0) - 
rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2]) * 
T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0))
+            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss_red"):
+                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
+                    T.reads(nll_loss[v_k0, v_k1, v_k2])
+                    T.writes(nll_loss_red[()])
+                    with T.init():
+                        nll_loss_red[()] = T.float32(0)
+                    nll_loss_red[()] = nll_loss_red[()] + nll_loss[v_k0, v_k1, 
v_k2]
+            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss_1"):
+                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
+                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], 
T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]])
+                    T.writes(nll_loss_1[v_ax0, v_ax1, v_ax2])
+                    nll_loss_1[v_ax0, v_ax1, v_ax2] = 
T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), 
T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0))
+            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
+                with T.block("nll_loss_red_1"):
+                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
+                    T.reads(nll_loss_1[v_k0, v_k1, v_k2])
+                    T.writes(nll_loss_red_1[()])
+                    with T.init():
+                        nll_loss_red_1[()] = T.float32(0)
+                    nll_loss_red_1[()] = nll_loss_red_1[()] + nll_loss_1[v_k0, 
v_k1, v_k2]
+            with T.block("T_divide"):
+                vi = T.axis.spatial(1, T.int64(0))
+                T.reads(nll_loss_red[()], nll_loss_red_1[()])
+                T.writes(T_divide[()])
+                T_divide[()] = nll_loss_red[()] / nll_loss_red_1[()]
+    # fmt: on
+
+    mod = LegalizeOps()(NLLLoss)
+    tvm.ir.assert_structural_equal(mod, Expected)
+
+
+def test_nll_no_batch():
+    # fmt: off
+    @tvm.script.ir_module
+    class NLLLoss:
+        @R.function
+        def main(predictions: R.Tensor(("C",), "float32"), targets: 
R.Tensor((), "int64"), weights: R.Tensor(("C",), "float32")) -> R.Tensor((), 
"float32"):
+            gv = R.nn.nll_loss(predictions, targets, weights, 
reduction="mean", ignore_index=1)
+            return gv
+
+    @tvm.script.ir_module
+    class Expected:
+        @R.function
+        def main(predictions: R.Tensor(("C",), dtype="float32"), targets: 
R.Tensor((), dtype="int64"), weights: R.Tensor(("C",), dtype="float32")) -> 
R.Tensor((), dtype="float32"):
+            C = T.int64()
+            gv = R.call_tir(Expected.nll_loss, (predictions, targets, 
weights), out_sinfo=R.Tensor((), dtype="float32"))
+            return gv
+
+        @T.prim_func
+        def nll_loss(var_rxplaceholder: T.handle, rxplaceholder: T.Buffer((), 
"int64"), var_rxplaceholder_1: T.handle, T_divide: T.Buffer((), "float32")):
+            T.func_attr({"tir.noalias": True})
+            C = T.int64()
+            rxplaceholder_1 = T.match_buffer(var_rxplaceholder, (C,))
+            rxplaceholder_2 = T.match_buffer(var_rxplaceholder_1, (C,))
+            # with T.block("root"):
+            nll_loss = T.alloc_buffer(())
+            nll_loss_1 = T.alloc_buffer(())
+            with T.block("nll_loss"):
+                vi = T.axis.spatial(T.int64(1), T.int64(0))
+                T.reads(rxplaceholder[()], rxplaceholder_1[rxplaceholder[()]], 
rxplaceholder_2[rxplaceholder[()]])
+                T.writes(nll_loss[()])
+                nll_loss[()] = T.Select(rxplaceholder[()] != T.int64(1), 
(T.float32(0) - rxplaceholder_1[rxplaceholder[()]]) * 
rxplaceholder_2[rxplaceholder[()]], T.float32(0))
+            with T.block("nll_loss_1"):
+                vi = T.axis.spatial(T.int64(1), T.int64(0))
+                T.reads(rxplaceholder[()], rxplaceholder_2[rxplaceholder[()]])
+                T.writes(nll_loss_1[()])
+                nll_loss_1[()] = T.Select(rxplaceholder[()] != T.int64(1), 
rxplaceholder_2[rxplaceholder[()]], T.float32(0))
+            with T.block("T_divide"):
+                vi = T.axis.spatial(1, T.int64(0))
+                T.reads(nll_loss[()], nll_loss_1[()])
+                T.writes(T_divide[()])
+                T_divide[()] = nll_loss[()] / nll_loss_1[()]
+    # fmt: on
+
+    mod = LegalizeOps()(NLLLoss)
+    tvm.ir.assert_structural_equal(mod, Expected)
+
+
+def test_nll_loss_symbolic():
+    # fmt: off
+    @tvm.script.ir_module
+    class NLLLoss:
+        @R.function
+        def main(predictions: R.Tensor(("N", "C", "d1", "d2"), "float32"), 
targets: R.Tensor(("N", "d1", "d2"), "int64"), weights: R.Tensor(("C",), 
"float32")) -> R.Tensor((), "float32"):
+            gv: R.Tensor((), "float32") = R.nn.nll_loss(predictions, targets, 
weights, reduction="mean", ignore_index=-1)
+            return gv
+
+    @tvm.script.ir_module
+    class Expected:
+        @R.function
+        def main(predictions: R.Tensor(("N", "C", "d1", "d2"), 
dtype="float32"), targets: R.Tensor(("N", "d1", "d2"), dtype="int64"), weights: 
R.Tensor(("C",), dtype="float32")) -> R.Tensor((), dtype="float32"):
+            # block 0
+            gv = R.call_tir(Expected.nll_loss, (predictions, targets, 
weights), R.Tensor((), dtype="float32"))
+            return gv
+
+        @T.prim_func
+        def nll_loss(var_rxplaceholder: T.handle, var_rxplaceholder_1: 
T.handle, var_rxplaceholder_2: T.handle, T_divide: T.Buffer((), "float32"),):
+            # function attr dict
+            T.func_attr({"tir.noalias": True})
+            C = T.int64()
+            N = T.int64()
+            d1 = T.int64()
+            d2 = T.int64()
+            rxplaceholder = T.match_buffer(var_rxplaceholder, [N, C, d1, d2], 
dtype="float32")
+            rxplaceholder_1 = T.match_buffer(var_rxplaceholder_1, [N, d1, d2], 
dtype="int64")
+            rxplaceholder_2 = T.match_buffer(var_rxplaceholder_2, [C], 
dtype="float32")
+            # body
+            # with T.block("root")
+            nll_loss = T.alloc_buffer([N, d1, d2], dtype="float32")
+            nll_loss_red = T.alloc_buffer([], dtype="float32")
+            nll_loss_1 = T.alloc_buffer([N, d1, d2], dtype="float32")
+            nll_loss_red_1 = T.alloc_buffer([], dtype="float32")
+            for ax0, ax1, ax2 in T.grid(N, d1, d2):
+                with T.block("nll_loss"):
+                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
+                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], 
rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, 
v_ax2],rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]],)
+                    T.writes(nll_loss[v_ax0, v_ax1, v_ax2])
+                    nll_loss[v_ax0, v_ax1, v_ax2] = 
T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), (T.float32(0) - 
rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2]) * 
rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0),)
+            for k0, k1, k2 in T.grid(N, d1, d2):
+                with T.block("nll_loss_red"):
+                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
+                    T.reads(nll_loss[v_k0, v_k1, v_k2])
+                    T.writes(nll_loss_red[()])
+                    with T.init():
+                        nll_loss_red[()] = T.float32(0)
+                    nll_loss_red[()] = nll_loss_red[()] + nll_loss[v_k0, v_k1, 
v_k2]
+            for ax0, ax1, ax2 in T.grid(N, d1, d2):
+                with T.block("nll_loss_1"):
+                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
+                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], 
rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]],)
+                    T.writes(nll_loss_1[v_ax0, v_ax1, v_ax2])
+                    nll_loss_1[v_ax0, v_ax1, v_ax2] = 
T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), 
rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0),)
+            for k0, k1, k2 in T.grid(N, d1, d2):
+                with T.block("nll_loss_red_1"):
+                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
+                    T.reads(nll_loss_1[v_k0, v_k1, v_k2])
+                    T.writes(nll_loss_red_1[()])
+                    with T.init():
+                        nll_loss_red_1[()] = T.float32(0)
+                    nll_loss_red_1[()] = nll_loss_red_1[()] + nll_loss_1[v_k0, 
v_k1, v_k2]
+            with T.block("T_divide"):
+                vi = T.axis.spatial(1, T.int64(0))
+                T.reads(nll_loss_red[()], nll_loss_red_1[()])
+                T.writes(T_divide[()])
+                T_divide[()] = nll_loss_red[()] / nll_loss_red_1[()]
+    # fmt: on
+    mod = LegalizeOps()(NLLLoss)
+    tvm.ir.assert_structural_equal(mod, Expected)
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/tests/python/relax/test_tvmscript_parser_op_nn.py 
b/tests/python/relax/test_tvmscript_parser_op_nn.py
index a822fae719..014d524751 100644
--- a/tests/python/relax/test_tvmscript_parser_op_nn.py
+++ b/tests/python/relax/test_tvmscript_parser_op_nn.py
@@ -302,5 +302,49 @@ def test_cross_entropy_with_logits():
     _check(foo, bb.get()["foo"])
 
 
+def test_nll_loss():
+    @R.function
+    def foo(
+        predictions: R.Tensor((3, 5, 10, 10), dtype="float32"),
+        targets: R.Tensor((3, 10, 10), dtype="int64"),
+        weights: R.Tensor((5,), dtype="float32"),
+    ) -> R.Tensor((), dtype="float32"):
+        gv: R.Tensor((), dtype="float32") = R.nn.nll_loss(predictions, 
targets, weights, "mean", -1)
+        return gv
+
+    predictions = relax.Var("predictions", R.Tensor((3, 5, 10, 10), "float32"))
+    targets = relax.Var("targets", R.Tensor((3, 10, 10), "int64"))
+    weights = relax.Var("weights", R.Tensor((5,), "float32"))
+    bb = relax.BlockBuilder()
+    with bb.function("foo", [predictions, targets, weights]):
+        gv = bb.emit(
+            relax.op.nn.nll_loss(predictions, targets, weights, 
reduction="mean", ignore_index=-1)
+        )
+        bb.emit_func_output(gv)
+
+    _check(foo, bb.get()["foo"])
+
+
+def test_nll_loss_no_weights():
+    @R.function
+    def foo(
+        predictions: R.Tensor((3, 5, 10, 10), dtype="float32"),
+        targets: R.Tensor((3, 10, 10), dtype="int64"),
+    ) -> R.Tensor((), dtype="float32"):
+        gv: R.Tensor((), dtype="float32") = R.nn.nll_loss(
+            predictions, targets, reduction="mean", ignore_index=-1
+        )
+        return gv
+
+    predictions = relax.Var("predictions", R.Tensor((3, 5, 10, 10), "float32"))
+    targets = relax.Var("targets", R.Tensor((3, 10, 10), "int64"))
+    bb = relax.BlockBuilder()
+    with bb.function("foo", [predictions, targets]):
+        gv = bb.emit(relax.op.nn.nll_loss(predictions, targets, 
reduction="mean", ignore_index=-1))
+        bb.emit_func_output(gv)
+
+    _check(foo, bb.get()["foo"])
+
+
 if __name__ == "__main__":
     tvm.testing.main()

Reply via email to