utility.cpp 18.4 KB
Newer Older
1 2 3 4
/**
 * \file imperative/src/impl/ops/utility.cpp
 * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
 *
5
 * Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
6 7 8 9 10 11
 *
 * 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 已提交
12
#include <queue>
13

M
Megvii Engine Team 已提交
14 15 16
#include "../op_trait.h"
#include "megbrain/imperative/graph_cache.h"
#include "megbrain/imperative/opr_utility.h"
17
#include "megbrain/imperative/ops/autogen.h"
18
#include "megbrain/imperative/ops/opr_attr.h"
M
Megvii Engine Team 已提交
19
#include "megbrain/imperative/ops/utility.h"
20
#include "megbrain/imperative/subgraph_detail.h"
M
Megvii Engine Team 已提交
21
#include "megbrain/opr/io.h"
22 23
#include "megbrain/opr/tensor_gen.h"
#include "megbrain/opr/tensor_manip.h"
M
Megvii Engine Team 已提交
24
#include "megbrain/opr/utility.h"
25 26 27 28

namespace mgb::imperative {

MGB_DYN_TYPE_OBJ_FINAL_IMPL(GenericPyOp);
29
OP_TRAIT_REG(GenericPyOp, GenericPyOp).fallback();
30

M
Megvii Engine Team 已提交
31 32 33 34 35
namespace {
namespace fastpathcopy {
auto apply_on_var_node(const OpDef& def, const VarNodeArray& inputs) {
    return inputs;
}
36

37 38 39 40 41 42 43 44 45 46 47
auto make_backward_graph(
        const OpDef& def, const SmallVector<LogicalTensorDesc>& inputs,
        const SmallVector<bool>& input_requires_grad,
        const SmallVector<bool>& output_has_grad) {
    Subgraph graph;
    graph.inputs = {1, 2, 3};
    graph.outputs = {3};
    graph.exprs = {};
    return EncodedSubgraph::make(graph);
}

M
Megvii Engine Team 已提交
48 49
OP_TRAIT_REG(FastpathCopy, FastpathCopy)
        .apply_on_var_node(apply_on_var_node)
50
        .make_backward_graph(make_backward_graph)
M
Megvii Engine Team 已提交
51 52 53
        .fallback();
}  // namespace fastpathcopy
}  // namespace
54

M
Megvii Engine Team 已提交
55 56 57
namespace {
namespace shape_infer {
auto apply_on_physical_tensor(const OpDef& def, const SmallVector<TensorPtr>& inputs) {
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
    auto& op = def.cast_final_safe<ShapeInfer>();
    size_t nr_inputs = inputs.size();
    mgb_assert(nr_inputs > 0, "no inputs for ShapeInfer");
    SmallVector<LogicalTensorDesc> input_descs;
    for (size_t i = 0; i < nr_inputs; ++i) {
        auto input = inputs[i]->get_value();
        TensorLayout layout;
        layout.ndim = input.shape(0);
        for (size_t i = 0; i < layout.ndim; ++i) {
            layout[i] = input.ptr<int32_t>()[i];
        }
        layout.dtype = op.dtypes[i];
        layout.init_contiguous_stride();
        input_descs.push_back({layout, op.devices[i]});
    }
M
Megvii Engine Team 已提交
73 74
    auto [output_descs, valid] =
            OpDef::infer_output_attrs_fallible(*op.op, input_descs);
75 76
    mgb_assert(valid, "shape inference incomplete");
    SmallVector<TensorPtr> outputs;
M
Megvii Engine Team 已提交
77 78 79
    for (auto&& output_desc : output_descs) {
        HostTensorND shape_tensor{
                output_desc.comp_node, {output_desc.layout.ndim}, dtype::Int32()};
80 81 82 83 84 85 86 87
        for (size_t i = 0; i < output_desc.layout.ndim; ++i) {
            shape_tensor.ptr<int32_t>()[i] = output_desc.layout[i];
        }
        auto output = Tensor::make(shape_tensor);
        outputs.push_back(output);
    }
    return outputs;
}
M
Megvii Engine Team 已提交
88
auto apply_on_var_node(const OpDef& def, const VarNodeArray& inputs) {
89 90 91 92 93
    auto& op = def.cast_final_safe<ShapeInfer>();
    size_t nr_inputs = inputs.size();
    VarNodeArray input_values, outputs;
    mgb_assert(nr_inputs > 0, "no inputs for ShapeInfer");
    for (size_t i = 0; i < nr_inputs; ++i) {
M
Megvii Engine Team 已提交
94 95
        auto input_value =
                opr::Alloc::make(SymbolVar(inputs[i]), op.dtypes[i], {op.devices[i]});
96 97 98
        input_values.push_back(input_value.node());
    }
    auto output_values = OpDef::apply_on_var_node(*op.op, input_values);
M
Megvii Engine Team 已提交
99
    for (auto&& output_value : output_values) {
100 101 102 103 104 105
        outputs.push_back(opr::GetVarShape::make(output_value).node());
    }
    return outputs;
}

auto infer_output_attrs_fallible(
M
Megvii Engine Team 已提交
106
        const OpDef& def, const SmallVector<LogicalTensorDesc>& input_descs) {
107 108 109
    auto& op = def.cast_final_safe<ShapeInfer>();
    SmallVector<LogicalTensorDesc> input_shape_descs;
    size_t nr_inputs = op.devices.size();
M
Megvii Engine Team 已提交
110 111 112
    mgb_assert(
            op.dtypes.size() == nr_inputs,
            "number of input devices and dtypes mismatch");
113 114 115 116 117 118 119
    for (size_t i = 0; i < nr_inputs; ++i) {
        LogicalTensorDesc input_shape_desc;
        input_shape_desc.comp_node = op.devices[i];
        input_shape_desc.layout.ndim = 0;
        input_shape_desc.layout.dtype = op.dtypes[i];
        input_shape_descs.push_back(input_shape_desc);
    }
M
Megvii Engine Team 已提交
120 121
    auto [output_shape_descs, _] =
            OpDef::infer_output_attrs_fallible(*op.op, input_shape_descs);
122
    SmallVector<LogicalTensorDesc> output_descs;
M
Megvii Engine Team 已提交
123
    for (auto&& output_shape_desc : output_shape_descs) {
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
        LogicalTensorDesc output_desc;
        output_desc.comp_node = output_shape_desc.comp_node;
        output_desc.layout.ndim = 1;
        output_desc.layout.dtype = dtype::Int32();
        output_descs.push_back(output_desc);
    }
    return std::make_tuple(output_descs, false);
}

auto props(const OpDef& def) {
    auto& op = def.cast_final_safe<ShapeInfer>();
    return OpDef::props(*op.op);
}

auto make_name(const OpDef& def) {
    auto& op = def.cast_final_safe<ShapeInfer>();
    MGB_MARK_USED_VAR(op);
    return ssprintf("ShapeInfer[%s]", op.op->make_name().c_str());
}

auto hash(const OpDef& def) {
    auto& op = def.cast_final_safe<ShapeInfer>();
    return op.op->hash();
}

auto is_same_st(const OpDef& def, const OpDef& another) {
    if (!another.same_type<ShapeInfer>()) {
        return false;
    }
    auto& lhs = def.cast_final_safe<ShapeInfer>();
    auto& rhs = another.cast_final_safe<ShapeInfer>();
    if (!lhs.op->is_same(*rhs.op)) {
        return false;
    }
M
Megvii Engine Team 已提交
158
    return std::tie(lhs.devices, lhs.dtypes) == std::tie(rhs.devices, rhs.dtypes);
159 160
}

M
Megvii Engine Team 已提交
161 162 163 164 165 166 167 168 169 170 171
OP_TRAIT_REG(ShapeInfer, ShapeInfer)
        .apply_on_var_node(apply_on_var_node)
        .apply_on_physical_tensor(apply_on_physical_tensor)
        .infer_output_attrs_fallible(infer_output_attrs_fallible)
        .make_name(make_name)
        .props(props)
        .hash(hash)
        .is_same_st(is_same_st)
        .fallback();
}  // namespace shape_infer
}  // namespace
172 173 174

MGB_DYN_TYPE_OBJ_FINAL_IMPL(ShapeInfer);

M
Megvii Engine Team 已提交
175 176 177
namespace {
namespace identity {
auto apply_on_var_node(const OpDef& def, const VarNodeArray& inputs) {
178 179 180 181 182 183
    auto&& op = def.cast_final_safe<Identity>();
    mgb_assert(inputs.size() == 1);
    OperatorNodeConfig config{op.make_name()};
    return opr::Identity::make(inputs[0], config);
}

M
Megvii Engine Team 已提交
184
auto apply_on_physical_tensor(const OpDef& def, const SmallVector<TensorPtr>& inputs) {
185 186 187
    return SmallVector<TensorPtr>{inputs[0]};
}
OP_TRAIT_REG(Identity, Identity)
M
Megvii Engine Team 已提交
188 189 190 191 192
        .apply_on_var_node(apply_on_var_node)
        .apply_on_physical_tensor(apply_on_physical_tensor)
        .fallback();
}  // namespace identity
}  // namespace
193

M
Megvii Engine Team 已提交
194 195
namespace {
namespace subgraph {
196

M
Megvii Engine Team 已提交
197 198
EncodedSubgraph make_forward_graph(
        const OpDef& def, SmallVector<LogicalTensorDesc> inputs) {
M
Megvii Engine Team 已提交
199
    return EncodedSubgraph::make(*def.cast_final_safe<SubgraphOp>().graph);
200 201
}

M
Megvii Engine Team 已提交
202
EncodedSubgraph make_backward_graph(
M
Megvii Engine Team 已提交
203
        const OpDef& def, const SmallVector<LogicalTensorDesc>& inputs,
204 205 206 207 208 209 210 211 212
        const SmallVector<bool>& input_requires_grad,
        SmallVector<bool> output_has_grad) {
    auto& op = def.cast_final_safe<SubgraphOp>();
    mgb_assert(output_has_grad.size() == op.output_grad_mask.size());
    for (size_t i = 0; i < output_has_grad.size(); ++i) {
        if (!op.output_grad_mask[i]) {
            output_has_grad[i] = false;
        }
    }
M
Megvii Engine Team 已提交
213 214
    auto bgraph = subgraph_detail::make_backward_graph(
            def, inputs, input_requires_grad, output_has_grad);
M
Megvii Engine Team 已提交
215
    return EncodedSubgraph::make_single(
M
Megvii Engine Team 已提交
216 217
            SubgraphOp::make(
                    op.name + "Grad", std::make_shared<Subgraph>(bgraph.graph)),
218
            bgraph.input_mask, bgraph.output_mask);
219 220 221 222 223
}

std::vector<std::pair<const char*, std::string>> props(const OpDef& def) {
    auto& op = def.cast_final_safe<SubgraphOp>();
    return {
M
Megvii Engine Team 已提交
224 225 226 227
            {"name", op.name},
            {"inputs", mgb::imperative::to_string(op.graph->inputs)},
            {"exprs", mgb::imperative::to_string(op.graph->exprs)},
            {"outputs", mgb::imperative::to_string(op.graph->outputs)},
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
    };
}

std::string make_name(const OpDef& def) {
    auto& op = def.cast_final_safe<SubgraphOp>();
    if (op.name.empty()) {
        return "SubgraphOp";
    } else {
        return op.name;
    }
}

auto hash(const OpDef& def) {
    auto& op = def.cast_final_safe<SubgraphOp>();
    if (!op.graph_key) {
M
Megvii Engine Team 已提交
243
        return (size_t) reinterpret_cast<uintptr_t>(op.graph.get());
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    }
    return op.graph_key->hash();
}

auto is_same_st(const OpDef& def, const OpDef& another) {
    if (!another.same_type<SubgraphOp>()) {
        return false;
    }
    auto& lhs = def.cast_final_safe<SubgraphOp>();
    auto& rhs = another.cast_final_safe<SubgraphOp>();
    auto has_graph_key = bool(lhs.graph_key);
    bool graph_same = false;
    if (has_graph_key) {
        graph_same = rhs.graph_key && lhs.graph_key->is_same(*rhs.graph_key);
    } else {
259
        graph_same = !rhs.graph_key && lhs.graph.get() == rhs.graph.get();
260 261 262 263 264
    }
    return graph_same;
}

OP_TRAIT_REG(SubgraphOp, SubgraphOp)
M
Megvii Engine Team 已提交
265 266 267 268 269 270 271
        .make_forward_graph(make_forward_graph)
        .make_backward_graph(make_backward_graph)
        .props(props)
        .make_name(make_name)
        .hash(hash)
        .is_same_st(is_same_st)
        .fallback();
272

M
Megvii Engine Team 已提交
273 274
}  // namespace subgraph
}  // namespace
275

M
Megvii Engine Team 已提交
276 277
namespace {
namespace compiled_op {
278

M
Megvii Engine Team 已提交
279
struct DeviceMemoryAllocatorImpl : cg::DeviceMemoryAllocator {
280
    std::shared_ptr<OpDef> current_op;
M
Megvii Engine Team 已提交
281 282
    void alloc_static(
            ComputingGraph* graph, DeviceTensorStorage& dest, size_t size) override {
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
        mgb_assert(0, "alloc_static is not allowed in CompiledOp");
    }
    void alloc_dynamic(VarNode* var, DeviceTensorStorage& dest, size_t size) override {
        auto comp_node = var->comp_node();
        auto storage = current_op->allocate(comp_node, size);
        dest.reset(comp_node, size, storage);
    }
};

struct ComputingGraphHolder {
    std::shared_ptr<ComputingGraph> graph;
    std::unique_ptr<cg::AsyncExecutable> executable;
    SmallVector<std::shared_ptr<DeviceTensorND>> inputs;
    SmallVector<std::shared_ptr<DeviceTensorND>> outputs;
    std::shared_ptr<DeviceMemoryAllocatorImpl> allocator;
298
    SmallVector<std::unique_ptr<CompNode::Event>> events;
299 300
};

M
Megvii Engine Team 已提交
301 302 303 304
ComputingGraphHolder& get_computing_graph(
        std::shared_ptr<OpDef> compiled_op, SmallVector<LogicalTensorDesc> descs) {
    using ComputingGraphHolderCache =
            OpMethResultCache<std::queue<std::unique_ptr<ComputingGraphHolder>>>;
305
    thread_local auto cache = std::make_unique<ComputingGraphHolderCache>();
306 307
    thread_local size_t nr_cg_holders = 0;
    ComputingGraphHolderCache::key_t cache_key = {compiled_op, descs};
308
    auto& cg_holder_queue = (*cache)[cache_key];
309
    std::unique_ptr<ComputingGraphHolder> holder;
M
Megvii Engine Team 已提交
310
    if (!cg_holder_queue.empty()) {
311 312 313
        // pick one
        std::swap(cg_holder_queue.front(), holder);
        // check all events finished
M
Megvii Engine Team 已提交
314
        for (auto&& event : holder->events) {
315
            if (!event->finished()) {
M
Megvii Engine Team 已提交
316 317
                bool queue_limited =
                        event->comp_node().contain_flag(CompNode::Flag::QUEUE_LIMITED);
318 319 320 321 322 323
                bool many_graph = cg_holder_queue.size() > 10;
                if (queue_limited || !many_graph) {
                    std::swap(cg_holder_queue.front(), holder);
                    break;
                } else {
                    // graph limit
M
Megvii Engine Team 已提交
324 325 326
                    mgb_log_debug(
                            "computing graph limit for compiled op exceeded, waiting "
                            "for prev graph");
327 328 329 330 331 332 333 334 335 336 337 338
                    event->host_wait();
                }
            }
        }
        if (holder) {
            cg_holder_queue.pop();
        }
    }
    if (!holder) {
        // create new computing graph
        holder = std::make_unique<ComputingGraphHolder>();
        auto& cg_holder = *holder;
339 340 341 342
        cg_holder.allocator = std::make_shared<DeviceMemoryAllocatorImpl>();
        cg_holder.graph = ComputingGraph::make();
        cg_holder.graph->options().force_dynamic_alloc = true;
        cg_holder.graph->options().async_exec_level = 0;
M
Megvii Engine Team 已提交
343 344
        cg_holder.graph->options().graph_opt_level =
                compiled_op->cast_final_safe<CompiledOp>().gopt_level;
345
        cg_holder.graph->options().enable_var_mem_defragment = false;
346
        cg_holder.graph->options().comp_seq_sync_device = false;
347
        // set allocator for DTR support
348 349
        cg_holder.graph->set_device_memory_allocator(cg_holder.allocator);
        VarNodeArray input_vars;
M
Megvii Engine Team 已提交
350
        for (auto&& desc : descs) {
351 352 353 354 355
            auto input_device_nd = std::make_shared<DeviceTensorND>();
            input_device_nd->dtype(desc.layout.dtype);
            input_device_nd->comp_node(desc.comp_node);
            input_device_nd->resize(desc.layout);
            cg_holder.inputs.push_back(input_device_nd);
M
Megvii Engine Team 已提交
356 357 358 359 360
            auto callback = [input_device_nd] { return *input_device_nd; };
            auto* input_var = opr::InputCallback::make(
                                      *cg_holder.graph, callback, desc.comp_node,
                                      desc.layout.dtype, TensorShape())[0]
                                      .node();
361 362 363 364 365 366 367 368 369
            input_vars.push_back(input_var);
        }
        // forward to inner op
        auto output_vars = OpDef::apply_on_var_node(*compiled_op, input_vars);
        ComputingGraph::OutputSpec output_spec;
        size_t nr_outputs = output_vars.size();
        for (size_t i = 0; i < nr_outputs; ++i) {
            auto* output_var = output_vars[i];
            auto output_ptr = std::make_shared<DeviceTensorND>();
M
Megvii Engine Team 已提交
370
            auto callback = [output_ptr](DeviceTensorND output) {
371 372 373 374 375 376
                output_ptr->reset(output.storage(), output.layout());
            };
            output_spec.push_back({output_var, callback});
            cg_holder.outputs.push_back(output_ptr);
        }
        cg_holder.executable = cg_holder.graph->compile(output_spec);
377
        CompNode::UnorderedSet comp_nodes;
M
Megvii Engine Team 已提交
378
        for (auto&& output_var : output_vars) {
379 380
            comp_nodes.insert(output_var->comp_node());
        }
M
Megvii Engine Team 已提交
381
        for (auto&& comp_node : comp_nodes) {
382 383 384 385
            cg_holder.events.push_back(comp_node.create_event());
            cg_holder.events.back()->record();
        }
        nr_cg_holders++;
M
Megvii Engine Team 已提交
386 387 388
        mgb_log_debug(
                "add new computing graph for compiled op, now %zu graphs",
                nr_cg_holders);
389
    }
390 391
    cg_holder_queue.push(std::move(holder));
    return *cg_holder_queue.back();
392 393
}

M
Megvii Engine Team 已提交
394
auto apply_on_physical_tensor(const OpDef& def, const SmallVector<TensorPtr>& inputs) {
395
    SmallVector<LogicalTensorDesc> input_descs;
M
Megvii Engine Team 已提交
396
    for (auto&& input : inputs) {
397 398 399 400 401
        input_descs.push_back({input->layout(), input->comp_node()});
    }
    size_t nr_inputs = inputs.size();
    auto shared_def = const_cast<OpDef&>(def).shared_from_this();
    auto& cg_holder = get_computing_graph(shared_def, input_descs);
402 403
    // wait for last execution
    cg_holder.executable->wait();
404 405
    for (size_t i = 0; i < nr_inputs; ++i) {
        auto input_dev_tensor = inputs[i]->dev_tensor();
M
Megvii Engine Team 已提交
406 407
        cg_holder.inputs[i]->reset(
                input_dev_tensor.storage(), input_dev_tensor.layout());
408 409 410
    }
    cg_holder.allocator->current_op = shared_def;
    cg_holder.executable->execute();
M
Megvii Engine Team 已提交
411
    for (auto&& event : cg_holder.events) {
412 413
        event->record();
    }
414
    SmallVector<TensorPtr> outputs;
M
Megvii Engine Team 已提交
415
    for (auto input_nd : cg_holder.inputs) {
416 417
        *input_nd = {};
    }
M
Megvii Engine Team 已提交
418
    for (auto output_nd : cg_holder.outputs) {
419 420 421 422 423 424 425
        outputs.push_back(Tensor::make(*output_nd));
        *output_nd = {};
    }
    cg_holder.executable->clear_device_memory();
    cg_holder.allocator->current_op = nullptr;
    return outputs;
}
M
Megvii Engine Team 已提交
426
auto apply_on_var_node(const OpDef& def, const VarNodeArray& inputs) {
427 428 429
    auto& op = def.cast_final_safe<CompiledOp>();
    op.op->set_scope(op.scope());
    return OpDef::apply_on_var_node(*op.op, inputs);
430 431 432
}

auto infer_output_attrs_fallible(
M
Megvii Engine Team 已提交
433 434 435
        const OpDef& def, const SmallVector<LogicalTensorDesc>& input_descs) {
    return OpDef::infer_output_attrs_fallible(
            *def.cast_final_safe<CompiledOp>().op, input_descs);
436 437 438 439 440 441 442 443 444 445 446 447
}

auto props(const OpDef& def) {
    return OpDef::props(*def.cast_final_safe<CompiledOp>().op);
}

auto make_name(const OpDef& def) {
    auto& op = def.cast_final_safe<CompiledOp>();
    MGB_MARK_USED_VAR(op);
    return ssprintf("CompiledOp[%s]", op.op->make_name().c_str());
}

M
Megvii Engine Team 已提交
448
EncodedSubgraph make_backward_graph(
M
Megvii Engine Team 已提交
449
        const OpDef& def, const SmallVector<LogicalTensorDesc>& inputs,
450 451 452
        const SmallVector<bool>& input_requires_grad,
        const SmallVector<bool>& output_has_grad) {
    auto& op = def.cast_final_safe<CompiledOp>();
M
Megvii Engine Team 已提交
453 454
    auto backward_graph = OpDef::make_backward_graph(
            *op.op, inputs, input_requires_grad, output_has_grad);
455 456 457 458 459 460 461 462 463 464
    auto name = def.trait()->make_name(def);
    auto key = std::make_shared<BackwardOpKey>();
    key->op = op.op;
    key->inputs = inputs;
    key->extras = {input_requires_grad, output_has_grad};
    SmallVector<bool> grad_outputs_has_grad(backward_graph.graph.outputs.size(), true);
    std::shared_ptr<OpDef> bgraph_op;
    if (backward_graph.graph.is_single()) {
        bgraph_op = backward_graph.graph.as_single();
    } else {
465 466 467
        bgraph_op = SubgraphOp::make(
                name + "Grad", std::make_shared<Subgraph>(backward_graph.graph),
                grad_outputs_has_grad, key);
468 469
    }
    auto compiled_op = CompiledOp::make(bgraph_op, op.gopt_level);
M
Megvii Engine Team 已提交
470 471
    auto encoded_graph = EncodedSubgraph::make_single(
            compiled_op, backward_graph.input_mask, backward_graph.output_mask);
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
    return encoded_graph;
}

auto hash(const OpDef& def) {
    auto& op = def.cast_final_safe<CompiledOp>();
    return mgb::hash_pair_combine(op.op->hash(), op.gopt_level);
}

auto is_same_st(const OpDef& def, const OpDef& another) {
    if (!another.same_type<CompiledOp>()) {
        return false;
    }
    auto& lhs = def.cast_final_safe<CompiledOp>();
    auto& rhs = another.cast_final_safe<CompiledOp>();
    return lhs.op->is_same(*rhs.op) && lhs.gopt_level == rhs.gopt_level;
}

OP_TRAIT_REG(CompiledOp, CompiledOp)
M
Megvii Engine Team 已提交
490 491 492 493 494 495 496 497 498 499 500
        .apply_on_var_node(apply_on_var_node)
        .apply_on_physical_tensor(apply_on_physical_tensor)
        .infer_output_attrs_fallible(infer_output_attrs_fallible)
        .make_backward_graph(make_backward_graph)
        .make_name(make_name)
        .props(props)
        .hash(hash)
        .is_same_st(is_same_st)
        .fallback();
}  // namespace compiled_op
}  // namespace
501

502 503
MGB_DYN_TYPE_OBJ_FINAL_IMPL(UniqueKey);

504 505 506 507 508 509
MGB_DYN_TYPE_OBJ_FINAL_IMPL(SubgraphOp);

MGB_DYN_TYPE_OBJ_FINAL_IMPL(BackwardOpKey);

MGB_DYN_TYPE_OBJ_FINAL_IMPL(CompiledOp);

M
Megvii Engine Team 已提交
510
}  // namespace mgb::imperative