@@ -209,7 +209,8 @@ def _poisson( | |||
def _permutation(n: int, seed: int, device: str, handle: int, dtype: str) -> Tensor: | |||
assert isinstance(n, int) and n > 0, "Permutation is not defined when n <= 0" | |||
assert isinstance(n, int) | |||
assert n >= 0, "Permutation is not defined when n < 0" | |||
size = (n,) | |||
op = PermutationRNG(seed=seed, handle=handle, dtype=dtype) | |||
_ref = Tensor([], dtype="int32", device=device) | |||
@@ -10,7 +10,7 @@ import numpy as np | |||
import pytest | |||
import megengine.functional as F | |||
from megengine import Tensor | |||
from megengine import Tensor, jit, random | |||
from megengine.core._imperative_rt import CompNode | |||
from megengine.core._imperative_rt.core2 import apply | |||
from megengine.core._imperative_rt.ops import ( | |||
@@ -402,3 +402,44 @@ def test_seed(): | |||
seed(11) | |||
out4 = uniform(size=[10, 10]) | |||
assert not (out1.numpy() == out4.numpy()).all() | |||
@pytest.mark.parametrize("is_symbolic", [None, False, True]) | |||
def test_rng_empty_tensor(is_symbolic): | |||
shapes = [ | |||
(0,), | |||
(0, 0, 0), | |||
(10, 0, 10), | |||
] | |||
def fn(shape): | |||
o1 = random.uniform(0, 1, shape) | |||
o2 = random.normal(0, 1, shape) | |||
o3 = random.gamma(2, 1, shape) | |||
o4 = random.beta(2, 1, shape) | |||
o5 = random.poisson(2, shape) | |||
return o1, o2, o3, o4, o5 | |||
for shape in shapes: | |||
if is_symbolic is not None: | |||
fn_ = jit.trace(symbolic=is_symbolic)(fn) | |||
else: | |||
fn_ = fn | |||
for _ in range(3): | |||
outs = fn_(shape) | |||
for out in outs: | |||
np.testing.assert_equal(out.numpy().shape, shape) | |||
if is_symbolic is None: | |||
break | |||
def fn2(n): | |||
return random.permutation(n=n) | |||
if is_symbolic is not None: | |||
fn2 = jit.trace(symbolic=is_symbolic)(fn2) | |||
for _ in range(3): | |||
out = fn2(0) | |||
np.testing.assert_equal(out.numpy().shape, (0,)) | |||
if is_symbolic is None: | |||
break |
@@ -312,12 +312,7 @@ struct _InferLayout<false> | |||
template<typename Op> | |||
static TensorLayout do_infer(const LogicalTensorDesc& inp, const Op& rng){ | |||
size_t size = inp.layout.total_nr_elems(); | |||
mgb_assert( | |||
size > 0, | |||
"target size of %s expects size>0; got size=%lu actually", | |||
rng.dyn_typeinfo()->name, | |||
size); | |||
mgb_assert(inp.layout.ndim); | |||
return inp.layout; | |||
} | |||
}; | |||
@@ -376,6 +371,7 @@ void exec(const OpDef& op, const SmallVector<TensorPtr>& inputs, | |||
auto&& rng = op.cast_final_safe<Op>(); | |||
auto dest = outputs[0]; | |||
if (dest->layout().is_empty()) return; | |||
auto cn = dest->comp_node(); | |||
auto handle = rng.handle; | |||
if (!handle) { | |||
@@ -48,6 +48,9 @@ cg::OperatorNodeBase::NodeProp* RNGOpr::do_make_node_prop() const { | |||
auto prop = Super::do_make_node_prop(); \ | |||
prop->add_flag(NodeProp::Flag::IMPURE_FUNC); \ | |||
prop->reset_dep_type(input(), {NodeProp::DepType::HOST_VALUE}); \ | |||
for (auto i: input()) { \ | |||
prop->add_dep_type_existing_var(i, NodeProp::DepType::VALUE_ALLOW_EMPTY); \ | |||
} \ | |||
return prop; \ | |||
} \ | |||
RNGOpr::RNGOpr(VarNode *shape, const Param ¶m, \ | |||
@@ -56,7 +59,7 @@ RNGOpr::RNGOpr(VarNode *shape, const Param ¶m, | |||
{ \ | |||
DType dtype = DType::from_enum(param.dtype); \ | |||
add_input({shape}); \ | |||
add_output(None)->dtype(dtype); \ | |||
add_output(None)->dtype(dtype).add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); \ | |||
cg::add_workspace_output(this); \ | |||
add_equivalence_component<ScalarHash<void*>>(this); \ | |||
} \ | |||
@@ -84,7 +87,12 @@ void RNGOpr::init_output_static_infer_desc() { | |||
{SourceType::DEP, {{output(0), DepType::SHAPE}}, infer_wk}); \ | |||
} \ | |||
void RNGOpr::scn_do_execute() { \ | |||
m_dnn_opr->exec(output(0)->dev_tensor().as_megdnn(), \ | |||
auto&& ret = output(0); \ | |||
if (ret->layout().is_empty()) { \ | |||
mgb_assert(ret->dev_tensor().empty()); \ | |||
return; \ | |||
} \ | |||
m_dnn_opr->exec(ret->dev_tensor().as_megdnn(), \ | |||
get_megdnn_workspace_from_var(output(1))); \ | |||
} | |||
@@ -105,7 +113,7 @@ RNGOpr::RNGOpr(_INPUTS(VarNode*,), const Param ¶m, | |||
Super({i0->owner_graph(), config, (name), {_INPUTS(,)}}, param) \ | |||
{ \ | |||
add_input({_INPUTS(,)}); \ | |||
add_output(None)->dtype(i0->dtype()); \ | |||
add_output(None)->dtype(i0->dtype()).add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); \ | |||
cg::add_workspace_output(this); \ | |||
add_equivalence_component<ScalarHash<void*>>(this); \ | |||
} \ | |||
@@ -132,9 +140,22 @@ void RNGOpr::add_input_layout_constraint(){ | |||
for (auto i : input()) i->add_layout_constraint_contiguous(); \ | |||
}; \ | |||
void RNGOpr::scn_do_execute() { \ | |||
auto&& ret = output(0); \ | |||
if (ret->layout().is_empty()) { \ | |||
mgb_assert(ret->dev_tensor().empty()); \ | |||
return; \ | |||
} \ | |||
m_dnn_opr->exec(_FOR_EACH(_AS_MEGDNN),output(0)->dev_tensor().as_megdnn(), \ | |||
get_megdnn_workspace_from_var(output(1))); \ | |||
} | |||
} \ | |||
cg::OperatorNodeBase::NodeProp* RNGOpr::do_make_node_prop() const { \ | |||
auto prop = Super::do_make_node_prop(); \ | |||
prop->add_flag(NodeProp::Flag::IMPURE_FUNC); \ | |||
for (auto i: input()) { \ | |||
prop->add_dep_type_existing_var(i, NodeProp::DepType::VALUE_ALLOW_EMPTY); \ | |||
} \ | |||
return prop; \ | |||
} | |||
/* ================= 1 input ================= */ | |||
#define _INPUTS(prefix, subfix) prefix i0 subfix | |||
@@ -67,6 +67,7 @@ _DEFINE_RNG_OPR_WITH_SHAPE_CLASS(PermutationRNG) | |||
#define _DEFINE_RNG_OPR_WITH_INPUT_CLASS(RNG) \ | |||
MGB_DEFINE_OPR_CLASS(RNG, RNGOprBase<megdnn::RNG>) \ | |||
void add_input_layout_constraint() override; \ | |||
cg::OperatorNodeBase::NodeProp* do_make_node_prop() const override; \ | |||
public: \ | |||
RNG(_INPUTS(VarNode*), const Param ¶m, \ | |||
const OperatorNodeConfig &config); \ | |||
@@ -247,6 +247,92 @@ TEST(TestOprRand, PermutationRNG) { | |||
} | |||
} | |||
TEST(TestOprRand, EmptyShape) { | |||
auto test_uniform = []() { | |||
static constexpr size_t M = 128, N = 0; | |||
auto graph = ComputingGraph::make(); | |||
SymbolVar dev_out = opr::UniformRNG::make( | |||
*graph, {M, N}, {CompNode::load("xpu0")}, {23, DTypeEnum::Float32}); | |||
HostTensorND host_out; | |||
auto func = graph->compile({make_callback_copy(dev_out, host_out)}); | |||
func->execute(); | |||
ASSERT_EQ(host_out.shape(), TensorShape({M, N})); | |||
}; | |||
auto test_gaussian = []() { | |||
size_t SIZE = 0; | |||
constexpr float MEAN = 1, STD = 2; | |||
auto graph = ComputingGraph::make(); | |||
auto y = opr::GaussianRNG::make( | |||
SymbolVar::make_scalar(int(SIZE), *graph, {CompNode::load("xpu0")}), | |||
{23, MEAN, STD, DTypeEnum::Float32}); | |||
HostTensorND host_y; | |||
auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
func->execute(); | |||
ASSERT_EQ(TensorShape({SIZE}), host_y.shape()); | |||
}; | |||
auto test_gamma = []() { | |||
std::shared_ptr<HostTensorND> shape_host(new HostTensorND{ | |||
CompNode::load("xpux"), TensorShape{10, 0}, dtype::Float32()}); | |||
std::shared_ptr<HostTensorND> scale_host(new HostTensorND{ | |||
CompNode::load("xpux"), TensorShape{10, 0}, dtype::Float32()}); | |||
auto graph = ComputingGraph::make(); | |||
auto shape_sym = opr::Host2DeviceCopy::make(*graph, shape_host); | |||
auto scale_sym = opr::Host2DeviceCopy::make(*graph, scale_host); | |||
auto y = opr::GammaRNG::make(shape_sym, scale_sym, {10}); | |||
HostTensorND host_y; | |||
auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
func->execute(); | |||
ASSERT_EQ(TensorShape({10, 0}), host_y.shape()); | |||
}; | |||
auto test_poisson = []() { | |||
std::shared_ptr<HostTensorND> lam_host(new HostTensorND{ | |||
CompNode::load("xpux"), TensorShape{10, 0}, dtype::Float32()}); | |||
auto graph = ComputingGraph::make(); | |||
auto lam_sym = opr::Host2DeviceCopy::make(*graph, lam_host); | |||
auto y = opr::PoissonRNG::make(lam_sym, {10}); | |||
HostTensorND host_y; | |||
auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
func->execute(); | |||
ASSERT_EQ(TensorShape({10, 0}), host_y.shape()); | |||
}; | |||
auto test_beta = []() { | |||
std::shared_ptr<HostTensorND> alpha_host(new HostTensorND{ | |||
CompNode::load("xpux"), TensorShape{10, 0}, dtype::Float32()}); | |||
std::shared_ptr<HostTensorND> beta_host(new HostTensorND{ | |||
CompNode::load("xpux"), TensorShape{10, 0}, dtype::Float32()}); | |||
auto graph = ComputingGraph::make(); | |||
auto alpha_sym = opr::Host2DeviceCopy::make(*graph, alpha_host); | |||
auto beta_sym = opr::Host2DeviceCopy::make(*graph, beta_host); | |||
auto y = opr::BetaRNG::make(alpha_sym,beta_sym, {10}); | |||
HostTensorND host_y; | |||
auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
func->execute(); | |||
ASSERT_EQ(TensorShape({10, 0}), host_y.shape()); | |||
}; | |||
auto test_permutation = []() { | |||
static constexpr size_t SIZE = 0; | |||
auto graph = ComputingGraph::make(); | |||
auto y = opr::PermutationRNG::make( | |||
SymbolVar::make_scalar(int(SIZE), *graph, {CompNode::load("xpu0")}), | |||
{23, DTypeEnum::Int32}); | |||
HostTensorND host_y; | |||
auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
func->execute(); | |||
ASSERT_EQ(TensorShape({SIZE}), host_y.shape()); | |||
}; | |||
test_uniform(); | |||
test_gaussian(); | |||
test_gamma(); | |||
test_poisson(); | |||
test_beta(); | |||
test_permutation(); | |||
} | |||
TEST(TestOprRand, UniformReprod) { | |||
static constexpr size_t SIZE = 123; | |||
auto graph = ComputingGraph::make(); | |||