test_tracing.py 12.1 KB
Newer Older
1 2 3
# -*- coding: utf-8 -*-
# MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
#
4
# Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
5 6 7 8
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
M
Megvii Engine Team 已提交
9
import io
10
import itertools
11
from tempfile import mkstemp
M
Megvii Engine Team 已提交
12

M
Megvii Engine Team 已提交
13
import numpy as np
14
import pytest
M
Megvii Engine Team 已提交
15

16
import megengine.core.tensor.megbrain_graph as G
17
import megengine.functional as F
18
import megengine.optimizer as optim
19
import megengine.utils.comp_graph_tools as cgtools
20 21
from megengine import Parameter, tensor
from megengine.autodiff import GradManager
22
from megengine.core._trace_option import set_symbolic_shape
M
Megvii Engine Team 已提交
23
from megengine.core.ops import builtin as ops
24
from megengine.core.ops.builtin import Elemwise
25
from megengine.core.tensor.utils import isscalar
26
from megengine.functional import exp, log
M
Megvii Engine Team 已提交
27
from megengine.jit import exclude_from_trace, trace
28
from megengine.module import Module
29
from megengine.random import normal, uniform
M
Megvii Engine Team 已提交
30 31


32 33 34 35 36 37 38 39 40 41 42 43
@pytest.mark.parametrize("trace_mode", [False, True])
@pytest.mark.parametrize("return_mode", ["Value", "Tuple", "List", "Dict"])
def test_trace(trace_mode, return_mode):
    @trace(symbolic=trace_mode)
    def f(x):
        if return_mode == "Tuple":
            return (-x,)
        elif return_mode == "List":
            return [-x]
        elif return_mode == "Dict":
            return {"neg": -x}
        else:
44
            return -x
M
Megvii Engine Team 已提交
45

46 47 48 49 50 51
    def get_numpy(y):
        if return_mode == "Tuple" or return_mode == "List":
            return y[0].numpy()
        elif return_mode == "Dict":
            return y["neg"].numpy()
        return y.numpy()
M
Megvii Engine Team 已提交
52

53 54 55 56 57
    x = tensor([1])
    y = get_numpy(f(x))

    for i in range(3):
        np.testing.assert_equal(get_numpy(f(x)), y)
M
Megvii Engine Team 已提交
58 59


60 61 62 63 64 65 66 67 68 69 70 71
def test_output_copy_trace():
    class Simple(Module):
        def __init__(self):
            super().__init__()
            self.a = Parameter([1.0], dtype=np.float32)

        def forward(self, x):
            x = x * self.a
            # will result into a copy of output in grad
            x = F.exp(x)
            return x

72
    ys = {False: [], True: []}
73

74 75 76 77 78
    for symbolic in [False, True]:
        net = Simple()
        gm = GradManager().attach(net.parameters())
        opt = optim.SGD(net.parameters(), 1e-3, momentum=0.9)
        data = tensor(np.arange(4).reshape(2, 2), dtype="float32")
79

80 81 82 83 84 85 86
        @trace(symbolic=symbolic)
        def train_func(d):
            with gm:
                loss = net(d)
                gm.backward(loss)
                opt.step().clear_grad()
            return loss
87

88 89 90
        for i in range(3):
            y = train_func(data).numpy()
            ys[symbolic].append(y)
91

92 93
    for i in range(3):
        np.testing.assert_equal(ys[False][i], ys[True][i])
94

M
Megvii Engine Team 已提交
95

96 97 98 99 100 101 102 103 104 105
@pytest.mark.parametrize("trace_mode", [False, True])
def test_exclude_from_trace(trace_mode):
    @trace(symbolic=trace_mode)
    def f(x):
        x = -x
        with exclude_from_trace():
            if i % 2:
                x = -x
        x = -x
        return x
M
Megvii Engine Team 已提交
106

107
    x = tensor([1])
M
Megvii Engine Team 已提交
108

109 110 111
    for i in range(3):
        y = f(x).numpy()
        np.testing.assert_equal(f(x).numpy(), y)
M
Megvii Engine Team 已提交
112 113 114 115 116 117 118 119


def test_print_in_trace():
    for symbolic in [False]:  # cannot read value in symbolic mode

        @trace(symbolic=symbolic)
        def f(x):
            nonlocal buf
120
            x = -x
M
Megvii Engine Team 已提交
121
            buf = x.numpy()
122
            x = -x
M
Megvii Engine Team 已提交
123 124 125
            return x

        buf = None
126
        x = tensor([1])
M
Megvii Engine Team 已提交
127 128

        for i in range(3):
129
            y = f(x).numpy()
M
Megvii Engine Team 已提交
130 131
            z = buf
            buf = None
132
            np.testing.assert_equal(f(x).numpy(), y)
M
Megvii Engine Team 已提交
133
            np.testing.assert_equal(z, buf)
M
Megvii Engine Team 已提交
134 135 136


def test_dump():
137 138
    @trace(symbolic=True, capture_as_const=True)
    def f(a, b):
139
        return a + b
140

141 142 143
    a = tensor([2])
    b = tensor([4])
    y = f(a, b).numpy()
144 145

    for i in range(3):
146
        np.testing.assert_equal(f(a, b).numpy(), y)
147 148

    file = io.BytesIO()
149 150
    dump_info = f.dump(file)
    assert dump_info.nr_opr == 3
151
    np.testing.assert_equal(dump_info.inputs, ["arg_0", "arg_1"])
152
    np.testing.assert_equal(dump_info.outputs, ["ADD"])
153
    file.seek(0)
154 155
    infer_cg = cgtools.GraphInference(file)
    result = list((infer_cg.run(a, b)).values())[0]
156 157 158 159
    np.testing.assert_equal(result[0], y)


def test_capture_dump():
160
    a = tensor([2])
161 162 163

    @trace(symbolic=True, capture_as_const=True)
    def f(x):
164
        return x * a
165

166 167
    x = tensor([3])
    y = f(x).numpy()
168 169

    for i in range(3):
170
        np.testing.assert_equal(f(x).numpy(), y)
171 172 173 174

    file = io.BytesIO()
    f.dump(file)
    file.seek(0)
175 176
    infer_cg = cgtools.GraphInference(file)
    result = list((infer_cg.run(x)).values())[0]
177 178 179 180
    np.testing.assert_equal(result[0], y)


def test_dump_volatile():
181
    p = tensor([2])
182

M
Megvii Engine Team 已提交
183 184
    @trace(symbolic=True, capture_as_const=True)
    def f(x):
185
        return x * p
M
Megvii Engine Team 已提交
186

187 188
    x = tensor([3])
    y = f(x).numpy()
M
Megvii Engine Team 已提交
189 190

    for i in range(3):
191
        np.testing.assert_equal(f(x).numpy(), y)
M
Megvii Engine Team 已提交
192 193

    file = io.BytesIO()
194
    f.dump(file, optimize_for_inference=False)
195
    file.seek(0)
196
    cg, _, outputs = G.load_graph(file)
197 198 199
    (out,) = outputs
    assert (
        cgtools.get_owner_opr_type(cgtools.get_owner_opr_inputs(out)[1])
200
        == "ImmutableTensor"
201
    )
202 203


204 205 206 207 208
@pytest.mark.parametrize("trace_mode", [False, True])
def test_trace_profiler(trace_mode):
    @trace(symbolic=trace_mode, profiling=True)
    def f(x):
        return -x
209

210 211
    x = tensor([1])
    y = f(x).numpy()
212

213 214
    f(x)
    f(x)  # XXX: has to run twice
215

216 217
    out = f.get_profile()
    assert out.get("profiler")
218 219


220
@pytest.mark.skip(reason="force opt_level=0 when building graph")
221
def test_goptions():
222 223
    @trace(symbolic=True, opt_level=0, capture_as_const=True)
    def f(x):
224 225 226 227
        # directly return x / x will not trigger gopt
        # since there's no way to tell the two x are the same
        y = 2.0 * x
        return y / y
228 229 230

    @trace(symbolic=True, opt_level=1, capture_as_const=True)
    def g(x):
231 232
        y = 2.0 * x
        return y / y
233

234 235 236
    d = tensor(0.0)
    assert not np.isfinite(f(d).numpy())
    np.testing.assert_equal(g(d).numpy().item(), 1.0)
237 238


239
@pytest.mark.skip(reason="force opt_level=0 when building graph")
240 241 242 243 244 245 246 247 248
def test_goptions_log_sum_exp():
    @trace(symbolic=True, opt_level=0, capture_as_const=True)
    def f(x, y):
        return log(exp(x) + exp(y))

    @trace(symbolic=True, opt_level=1, capture_as_const=True)
    def g(x, y):
        return log(exp(x) + exp(y))

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    val = 1.0e4
    d = tensor(val)
    o = tensor(0.0)
    assert not np.isfinite(f(d, o).numpy())
    np.testing.assert_almost_equal(g(d, o), val)


def test_goptions_log_exp():
    @trace(symbolic=True, opt_level=0, capture_as_const=True)
    def f(x):
        return log(exp(x))

    @trace(symbolic=True, opt_level=1, capture_as_const=True)
    def g(x):
        return log(exp(x))

    f(tensor(1.0))
266
    _, out = mkstemp()
267 268
    f.dump(out, optimize_for_inference=False)
    *_, outputs = G.load_graph(out)
269 270
    oprs_1 = cgtools.get_oprs_seq(outputs)

271
    g(tensor(1.0))
272 273
    g.dump(out, optimize_for_inference=False)
    *_, outputs = G.load_graph(out)
274 275 276 277 278 279 280 281 282 283 284 285
    oprs_2 = cgtools.get_oprs_seq(outputs)

    assert len(oprs_1) - len(oprs_2) == 2


def test_optimize_for_inference():
    @trace(symbolic=True, capture_as_const=True)
    def f(x):
        return exp(x)

    _, out = mkstemp()
    f(tensor(5.0))
286
    f.dump(out, enable_io16xc32=True)
287

288
    res = G.load_graph(out)
289 290
    computing_input = res.output_vars_list[0].owner.inputs[0]
    assert computing_input.dtype == np.float16
291 292


293 294 295
def test_optimize_for_inference_broadcast():
    a = tensor(np.ones(1, dtype=np.float32))

296
    @trace(capture_as_const=True, symbolic_shape=True)
297
    def f():
298
        return a._broadcast(tensor([1, 10], dtype=np.int32))
299 300 301 302 303

    f()
    f.dump(io.BytesIO())


304 305 306 307 308
def test_trace_cvt_bool():
    x = tensor([0], dtype=np.int32)

    @trace(symbolic=True)
    def f(x):
309 310 311 312
        a = x.shape
        b = a[0]
        assert isscalar(b)
        return b == 0
313 314

    for i in range(3):
315
        np.testing.assert_equal(f(x).numpy(), False)
316 317


318 319 320 321 322
@pytest.mark.parametrize("trace_mode", [False, True])
def test_trace_reshape(trace_mode):
    x1 = tensor(np.random.randn(2, 10, 10))
    x2 = tensor(np.random.randn(4, 10, 10))
    x3 = tensor(np.random.randn(8, 10, 10))
323

324 325 326 327
    @trace(symbolic=trace_mode, capture_as_const=True)
    def f(x):
        y = x.reshape(x.shape[0], 100)
        return y
328

329 330 331
    f(x1)
    f(x2)
    f(x3)
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362


def test_trace_topk():
    x = tensor([5, 2, 7, 1, 0, 3, 2])

    @trace(symbolic=True)
    def f(x):
        y = F.topk(x, 3)
        np.testing.assert_equal(y[0].shape.numpy(), np.array([3,]))
        return y

    for i in range(3):
        f(x)


def test_trace_warp_perspective():
    inp_shape = (1, 1, 4, 4)
    x = tensor(np.arange(16, dtype=np.float32).reshape(inp_shape))
    M_shape = (1, 3, 3)
    M = tensor(
        np.array(
            [[1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0]], dtype=np.float32
        ).reshape(M_shape)
    )

    @trace(symbolic=True)
    def f(x, M):
        out = F.warp_perspective(x, M, (2, 2))
        np.testing.assert_equal(out.shape.numpy(), np.array([1, 1, 2, 2]))
        return out

363
    for i in range(3):
364
        f(x, M)
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396


def test_raise_on_trace():
    step_count = 0
    catch_count = 0
    bad_step = 10

    class CatchMe(Exception):
        pass

    a = tensor([1, 2, 3, 4])
    b = tensor([5, 6, 7, 8])
    c = tensor([9, 0, 1, 2])

    @trace
    def add_abc(a, b, c):
        ps = a + b
        result = ps + c
        if step_count == bad_step:
            raise CatchMe("catch me")
        return result

    for i in range(100):
        try:
            d = add_abc(a, b, c)
        except CatchMe as e:
            catch_count += 1
        else:
            np.testing.assert_equal(d.numpy(), (a + b + c).numpy())
        step_count += 1

    assert catch_count == 1
397 398


399 400 401 402 403
@pytest.mark.parametrize("trace_mode", [False, True])
def test_trace_broadcast(trace_mode):
    x1 = tensor(np.random.randn(3, 1, 1))
    x2 = tensor(np.random.randn(1, 4, 1))
    x3 = tensor(np.random.randn(1, 1, 5))
404

405 406 407 408
    @trace(symbolic=trace_mode, capture_as_const=True)
    def f(x):
        y = F.broadcast_to(x, (3, 4, 5))
        return y
409

410 411 412
    f(x1)
    f(x2)
    f(x3)
413 414 415 416 417 418 419 420 421 422 423 424 425 426


def test_trace_nms():
    def make_inputs(n):
        boxes = np.zeros((n, 4))
        boxes[:, :2] = np.random.rand(n, 2) * 100
        boxes[:, 2:] = np.random.rand(n, 2) * 100 + 100

        scores = np.random.rand(n)

        return tensor(boxes), tensor(scores)

    @trace(symbolic=False)
    def f(boxes, scores):
427
        # with tracing, max_output must be specified
428
        results = F.nn.nms(boxes, scores=scores, iou_thresh=0.5, max_output=20)
429
        # without tracing, max output can be inferred inside nms
430 431 432 433 434 435 436
        with exclude_from_trace():
            _ = F.nn.nms(boxes, scores=scores, iou_thresh=0.5)
        return results

    f(*make_inputs(10))
    f(*make_inputs(20))
    f(*make_inputs(30))
437 438 439 440 441 442 443 444 445 446 447 448 449 450


def test_trace_valid_broadcast():
    x1 = tensor(np.random.randn(1, 1))
    x2 = tensor(np.random.randn(1, 2))
    shape = (tensor([2]), tensor([2]))

    @trace(symbolic=False)
    def f(x, shape):
        y = F.broadcast_to(x, shape)
        return y

    f(x1, shape)
    f(x2, shape)
451 452 453 454 455 456 457 458 459 460 461 462


def test_clip():
    x = tensor(np.random.randn(10, 10))

    @trace(symbolic=True)
    def f(x, lower, upper):
        y = F.clip(x, lower, upper)
        return y

    for i in range(3):
        f(x, tensor([0]), tensor([1]))
463 464 465 466 467 468 469 470 471 472 473 474 475


# test returning noncontiguous tensor from trace
def test_slice():
    @trace
    def f(x):
        return x[:, 1::2]

    x = F.arange(8).reshape(2, 4)
    f(x)
    y = f(x)
    np.testing.assert_array_equal(y.numpy(), x.numpy()[:, 1::2])
    y + y
476 477


478 479
@pytest.mark.parametrize("shape_mode", [False, True])
def test_random(shape_mode):
480
    def run_test(op):
481 482 483 484 485 486 487 488 489 490 491
        @trace(symbolic=True, symbolic_shape=shape_mode)
        def f():
            out = op(size=[10, 10])
            out_shape = out.shape
            assert out_shape is not None
            if not isinstance(out_shape, tuple):
                assert out.shape.numpy() is not None
            return out

        for _ in range(3):
            f()
492 493 494

    run_test(uniform)
    run_test(normal)