You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

mini_graph.h 21 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. /**
  2. * \file imperative/src/impl/proxy_graph/mini_graph.h
  3. * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
  4. *
  5. * Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
  6. *
  7. * Unless required by applicable law or agreed to in writing,
  8. * software distributed under the License is distributed on an
  9. * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. */
  11. #include "megbrain/graph/operator_node.h"
  12. #include "megbrain/imperative/physical_tensor.h"
  13. #include "megbrain/imperative/op_def.h"
  14. #include "./common.h"
  15. #include "./proxy_graph_base.h"
  16. #include <optional>
  17. #include "range/v3/all.hpp"
  18. namespace mgb::imperative::proxy_graph {
  19. using cg::OperatorNodeBase;
  20. template<typename C, typename E>
  21. std::pair<bool, size_t> find_index(const C& container, const E& item) {
  22. auto&& it = std::find(container.begin(), container.end(), item);
  23. return {it != container.end(), it - container.begin()};
  24. }
  25. template <typename T, typename = void> class TensorAdaptor;
  26. template <typename T, typename U>
  27. using enable_if_same_upto_cv_t = std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::remove_cv_t<U>>>;
  28. template<typename T>
  29. class TensorAdaptor<T, enable_if_same_upto_cv_t<T, LogicalTensorDesc>> {
  30. T& wrapped;
  31. template <typename U>
  32. using maybe_add_const_t = std::conditional_t<std::is_const_v<T>, const U, U>;
  33. public:
  34. using type = T;
  35. TensorAdaptor(T& desc) : wrapped(desc) {}
  36. TensorAdaptor(T* desc) : wrapped(*desc) {}
  37. DType dtype() {return wrapped.layout.dtype;}
  38. CompNode comp_node() {return wrapped.comp_node;}
  39. maybe_add_const_t<TensorShape>& shape() {return wrapped.layout;}
  40. bool has_value() {return wrapped.value.shape_valid();}
  41. auto& value() {return wrapped.value;}
  42. auto* operator->() {return &wrapped;}
  43. };
  44. template<typename T>
  45. class TensorAdaptor<T, enable_if_same_upto_cv_t<T, Tensor>> {
  46. Tensor& wrapped;
  47. public:
  48. using type = Tensor;
  49. TensorAdaptor(Tensor& tensor) : wrapped(tensor) {}
  50. TensorAdaptor(Tensor* tensor) : wrapped(*tensor) {}
  51. DType dtype() {return wrapped.dtype();}
  52. CompNode comp_node() {return wrapped.comp_node();}
  53. const TensorShape& shape() {return wrapped.shape();}
  54. type* operator->() {return &wrapped;}
  55. };
  56. // deduction guides
  57. template <typename T> TensorAdaptor(T&) -> TensorAdaptor<T, void>;
  58. template <typename T> TensorAdaptor(T*) -> TensorAdaptor<T, void>;
  59. // single opr graph, for static inference and execution
  60. // contains static inference descs
  61. class ProxyGraph::MiniGraph {
  62. protected:
  63. struct InferDepItem {
  64. bool is_input : 1;
  65. size_t idx : 63;
  66. cg::static_infer::DepType type;
  67. };
  68. enum class InferStatus {
  69. UNKOWN,
  70. READY,
  71. FAILED
  72. };
  73. // inference desc and pre-allocated storage for a single var
  74. template <typename T>
  75. struct InferData {
  76. SmallVector<InferDepItem> deps;
  77. thin_function<bool(T&, const cg::static_infer::InpVal&)> infer_func;
  78. // pre-allocated infer states
  79. InferStatus status = InferStatus::UNKOWN;
  80. cg::static_infer::InpVal inp_val;
  81. T dest;
  82. void initialize(OperatorNodeBase* opr, const cg::static_infer::DepVal& dep_val,
  83. const thin_function<bool(T&, const cg::static_infer::InpVal&)>& func) {
  84. mgb_assert(!infer_func);
  85. infer_func = func;
  86. inp_val.val.resize(dep_val.size());
  87. deps.reserve(dep_val.size());
  88. for (auto&& dep : dep_val) {
  89. auto [found, i] = find_index(opr->input(), dep.dest);
  90. if (found) {
  91. deps.push_back({true, i, dep.type});
  92. } else {
  93. auto [found, i] = find_index(opr->output(), dep.dest);
  94. mgb_assert(found);
  95. deps.push_back({false, i, dep.type});
  96. }
  97. }
  98. }
  99. void reset() {
  100. status = InferStatus::UNKOWN;
  101. if constexpr (std::is_same_v<T, TensorShape>) {
  102. dest.ndim = 0;
  103. } else {
  104. static_assert(std::is_same_v<T, DeviceTensorND>);
  105. dest.storage({});
  106. }
  107. }
  108. };
  109. struct OutputData {
  110. InferData<TensorShape> shape_infer;
  111. InferData<DeviceTensorND> value_infer;
  112. };
  113. struct InferSessionBase {
  114. virtual const TensorShape& infer_shape(VarNode*) {mgb_assert(0);}
  115. virtual const TensorShape* infer_shape_fallible(VarNode*) {mgb_assert(0);}
  116. virtual const DeviceTensorND& infer_value(VarNode*) {mgb_assert(0);}
  117. virtual const DeviceTensorND* infer_value_fallible(VarNode*) {mgb_assert(0);}
  118. };
  119. OperatorNodeBase* m_opr = nullptr;
  120. SmallVector<std::unique_ptr<OperatorNodeBase>> opr_ref_keeper;
  121. size_t run_id = 0;
  122. SmallVector<OutputData> output_data;
  123. SmallVector<size_t> input_remap;
  124. SmallVector<size_t> output_remap;
  125. // pre-allocated buffer for converted inputs
  126. SmallVector<std::optional<DeviceTensorND>> input_value_storage;
  127. InferSessionBase* m_sess = nullptr;
  128. template <typename T>
  129. struct InputAdaptor {
  130. T& wrapped;
  131. SmallVector<std::optional<DeviceTensorND>>& value_storage;
  132. InputAdaptor(MiniGraph& owner, T& inputs) : wrapped(inputs), value_storage(owner.input_value_storage) {}
  133. ~InputAdaptor() {
  134. for (auto& i : value_storage) {
  135. i.reset();
  136. }
  137. }
  138. const TensorShape* shape(size_t i) {
  139. TensorAdaptor tensor(wrapped[i]);
  140. auto& shape = tensor.shape();
  141. return shape.ndim ? &shape : nullptr;
  142. }
  143. const DeviceTensorND* value(size_t i, bool sync) {
  144. TensorAdaptor tensor(wrapped[i]);
  145. using tensor_t = std::remove_cv_t<typename decltype(tensor)::type>;
  146. if constexpr (std::is_same_v<tensor_t, Tensor>) {
  147. auto& storage = value_storage[i];
  148. if (!storage) {
  149. if (sync) {
  150. return &storage.emplace(tensor->get_value().proxy_to_default_cpu());
  151. } else {
  152. if (auto* hv = tensor->try_get_value()) {
  153. return &storage.emplace(hv->proxy_to_default_cpu());
  154. }
  155. return nullptr;
  156. }
  157. }
  158. } else {
  159. auto& value = tensor.value();
  160. return value.shape_valid() ? &value : nullptr;
  161. }
  162. }
  163. };
  164. public:
  165. template <typename I, typename G>
  166. MiniGraph(G& graph, const OpDef& opdef, const I& inputs) : input_value_storage(inputs.size()) {
  167. mgb_assert(!m_opr);
  168. auto _ = graph.scoped_attach(this);
  169. cg::VarNodeArray vinputs(inputs.size());
  170. for (auto&& [i, t] : ranges::views::enumerate(inputs)) {
  171. auto tensor = TensorAdaptor(t);
  172. opr_ref_keeper.emplace_back(new InputPlaceholder(graph, tensor.dtype(), tensor.comp_node()));
  173. vinputs[i] = opr_ref_keeper.back()->output(0);
  174. }
  175. auto ovars = OpDef::apply_on_var_node(opdef, vinputs);
  176. mgb_assert(m_opr);
  177. output_data.resize(m_opr->output().size());
  178. for (auto* v : ovars) {
  179. mgb_assert(v->owner_opr() == m_opr);
  180. }
  181. m_opr->init_output_static_infer_desc();
  182. // fix permuted input
  183. input_remap.reserve(m_opr->input().size());
  184. for (auto* v : m_opr->input()) {
  185. auto [found, i] = find_index(vinputs, v);
  186. mgb_assert(found);
  187. input_remap.push_back(i);
  188. }
  189. auto fix_dep_idx = [&](SmallVector<InferDepItem>& deps) {
  190. for (auto& dep : deps) {
  191. if (dep.is_input) {
  192. dep.idx = input_remap[dep.idx];
  193. }
  194. }
  195. };
  196. for (auto& data : output_data) {
  197. fix_dep_idx(data.shape_infer.deps);
  198. fix_dep_idx(data.value_infer.deps);
  199. }
  200. // fix permuted output
  201. output_remap.reserve(ovars.size());
  202. for (auto* v : ovars) {
  203. auto [found, i] = find_index(m_opr->output(), v);
  204. mgb_assert(found);
  205. output_remap.push_back(i);
  206. }
  207. }
  208. // methods for containing graph
  209. OperatorNodeBase* insert_opr(std::unique_ptr<OperatorNodeBase> opr_uniqp) {
  210. mgb_assert(!m_opr);
  211. m_opr = opr_uniqp.get();
  212. mgb_assert(opr_ref_keeper.back()->owner_graph() == m_opr->owner_graph());
  213. mgb_assert(!m_opr->inserted_in_graph());
  214. opr_ref_keeper.push_back(std::move(opr_uniqp));
  215. m_opr->set_inserted_in_graph();
  216. m_opr->init_output_comp_node();
  217. m_opr->init_output_dtype();
  218. return m_opr;
  219. }
  220. void register_shape_infer(VarNode* varnode, const cg::static_infer::ShapeInferDesc& desc) {
  221. auto [found, i] = find_index(m_opr->output(), varnode);
  222. mgb_assert(found);
  223. output_data[i].shape_infer.initialize(m_opr, desc.deps, desc.infer_func);
  224. }
  225. void register_value_infer(VarNode* varnode, const cg::static_infer::ValueInferDesc& desc) {
  226. auto [found, i] = find_index(m_opr->output(), varnode);
  227. mgb_assert(found);
  228. output_data[i].value_infer.initialize(m_opr, desc.deps, desc.infer_func);
  229. }
  230. const TensorShape& infer_shape(VarNode* var) {
  231. return m_sess->infer_shape(var);
  232. }
  233. const DeviceTensorND& infer_value(VarNode* var) {
  234. return m_sess->infer_value(var);
  235. }
  236. OperatorNodeBase* opr() {
  237. return m_opr;
  238. }
  239. // inference routine template for type of input
  240. template<typename I>
  241. class InferSession : protected InferSessionBase {
  242. MiniGraph& owner;
  243. SmallVector<OutputData>& output_data;
  244. InputAdaptor<I> inputs;
  245. template<typename T>
  246. const T* infer(InferData<T>& target, bool sync) {
  247. bool ret;
  248. if (target.status != InferStatus::UNKOWN) {
  249. ret = target.status == InferStatus::READY;
  250. } else {
  251. ret = target.infer_func && do_infer(target, sync);
  252. target.status = ret ? InferStatus::READY : InferStatus::FAILED;
  253. }
  254. return ret ? &target.dest : nullptr;
  255. }
  256. template<typename T>
  257. bool do_infer(InferData<T>& target, bool sync) {
  258. for (size_t i = 0; i < target.deps.size(); ++i) {
  259. target.inp_val.run_id = owner.run_id;
  260. auto& dep = target.deps[i];
  261. if (dep.is_input) {
  262. if (dep.type == cg::static_infer::DepType::SHAPE) {
  263. if (auto* val = inputs.shape(dep.idx)) {
  264. target.inp_val.val[i].m_shape = val;
  265. } else return false;
  266. } else {
  267. if (auto* val = inputs.value(dep.idx, sync)) {
  268. target.inp_val.val[i].m_value = val;
  269. } else return false;
  270. }
  271. } else {
  272. if (dep.type == cg::static_infer::DepType::SHAPE) {
  273. if (auto* val = infer(output_data[dep.idx].shape_infer, sync)) {
  274. target.inp_val.val[i].m_shape = val;
  275. } else return false;
  276. } else {
  277. if (auto* val = infer(output_data[dep.idx].value_infer, sync)) {
  278. target.inp_val.val[i].m_value = val;
  279. } else return false;
  280. }
  281. }
  282. }
  283. return target.infer_func(target.dest, target.inp_val);
  284. }
  285. // methods for owner mini graph
  286. // corresponding methods of containing ComputingGraph will be redirected here
  287. const TensorShape& infer_shape(VarNode* var) override {
  288. mgb_assert(owner.m_opr);
  289. auto [found, i] = find_index(owner.m_opr->input(), var);
  290. mgb_assert(found);
  291. i = owner.input_remap[i];
  292. auto* shape = inputs.shape(i);
  293. mgb_assert(shape);
  294. return *shape;
  295. }
  296. const DeviceTensorND& infer_value(VarNode* var) override {
  297. mgb_assert(owner.m_opr);
  298. auto [found, i] = find_index(owner.m_opr->input(), var);
  299. mgb_assert(found);
  300. i = owner.input_remap[i];
  301. auto* value = inputs.value(i, false);
  302. mgb_assert(value);
  303. return *value;
  304. }
  305. public:
  306. InferSession(MiniGraph& mgraph, I& inputs_)
  307. : owner(mgraph), output_data(mgraph.output_data), inputs(mgraph, inputs_) {
  308. mgraph.run_id++;
  309. mgb_assert(!owner.m_sess);
  310. owner.m_sess = this;
  311. }
  312. ~InferSession() {
  313. owner.m_sess = nullptr;
  314. for (auto& i : output_data) {
  315. i.shape_infer.reset();
  316. i.value_infer.reset();
  317. }
  318. }
  319. const TensorShape* infer_shape(size_t i, bool sync) {
  320. i = owner.output_remap[i];
  321. return infer(output_data[i].shape_infer, sync);
  322. }
  323. const DeviceTensorND* infer_value(size_t i, bool sync) {
  324. i = owner.output_remap[i];
  325. return infer(output_data[i].shape_infer, sync);
  326. }
  327. };
  328. template <typename T>
  329. InferSession<T> infer_session(T& inputs) {return InferSession(*this, inputs);}
  330. size_t output_size() {
  331. return output_remap.size();
  332. }
  333. VarNode* output_var(size_t i) {
  334. i = output_remap[i];
  335. return m_opr->output(i);
  336. }
  337. };
  338. class CompNodeTracker {
  339. static constexpr size_t bucket_size = 100;
  340. static constexpr size_t bucket_count = 10;
  341. CompNode comp_node;
  342. std::array<std::unique_ptr<CompNode::Event>, bucket_count> events;
  343. size_t free_slots = bucket_size;
  344. size_t head = 0; // events[head] is not recorded
  345. size_t tail = 0; // events[tail] is not finished
  346. void rotate() {
  347. while (tail < head && events[tail % bucket_count]->finished()) {
  348. ++tail;
  349. }
  350. auto& ev = events[head % bucket_count];
  351. if (head == tail + bucket_count) {
  352. // do not wait if head == tail
  353. ev->host_wait();
  354. ++tail;
  355. }
  356. ev->record();
  357. ++head;
  358. free_slots = bucket_size;
  359. }
  360. public:
  361. CompNodeTracker(CompNode cn) : comp_node(cn) {
  362. for (auto& e : events) {
  363. e = cn.create_event();
  364. }
  365. }
  366. size_t add_opr() {
  367. if (!free_slots) rotate();
  368. --free_slots;
  369. return head;
  370. }
  371. size_t progress() {
  372. return tail;
  373. }
  374. };
  375. class ExecMiniGraph : public ProxyGraph::MiniGraph {
  376. union BusyListItem {
  377. size_t finish_time;
  378. OperatorNodeBase* opr;
  379. };
  380. SmallVector<CompNodeTracker*> comp_node_trackers;
  381. std::deque<BusyListItem> busy_oprs;
  382. SmallVector<OperatorNodeBase*> idle_oprs;
  383. OperatorNodeBase* acquire_opr() {
  384. mgb_assert(!m_opr);
  385. if (!idle_oprs.empty()) {
  386. m_opr = idle_oprs.back();
  387. idle_oprs.pop_back();
  388. return m_opr;
  389. }
  390. mgb_assert(busy_oprs.size() > comp_node_trackers.size());
  391. bool can_pop = true;
  392. for (auto [item, tracker] : ranges::views::zip(busy_oprs, comp_node_trackers)) {
  393. if (item.finish_time >= tracker->progress()) {
  394. can_pop = false;
  395. break;
  396. }
  397. }
  398. if (can_pop) {
  399. for (auto _ : comp_node_trackers) {
  400. MGB_MARK_USED_VAR(_);
  401. busy_oprs.pop_front();
  402. }
  403. m_opr = busy_oprs.front().opr;
  404. busy_oprs.pop_front();
  405. return m_opr;
  406. }
  407. }
  408. template <bool in_use>
  409. void release_opr() {
  410. if constexpr (in_use) {
  411. for (auto tracker : comp_node_trackers) {
  412. tracker->add_opr();
  413. }
  414. }
  415. }
  416. };
  417. class ProxyGraphTypeI : public ProxyGraphBase {
  418. class StaticInferManager : public StaticInferManagerBase {
  419. ProxyGraph::MiniGraph* target = nullptr;
  420. friend class ProxyGraphTypeI;
  421. public:
  422. void register_shape_infer(VarNode* var, const cg::static_infer::ShapeInferDesc& desc) override {
  423. target->register_shape_infer(var, desc);
  424. };
  425. void register_value_infer(VarNode* var, const cg::static_infer::ValueInferDesc& desc) override {
  426. target->register_value_infer(var, desc);
  427. };
  428. cg::static_infer::InferType get_infer_type(VarNode*) override {
  429. return {cg::static_infer::InferType::MISSING_INP, cg::static_infer::InferType::MISSING_INP};
  430. }
  431. // some poorly written inference func would call infer_{shape,value}
  432. const TensorShape& infer_shape(VarNode* var) override {
  433. return target->infer_shape(var);
  434. }
  435. const DeviceTensorND& infer_value(VarNode* var) override {
  436. return target->infer_value(var);
  437. }
  438. };
  439. ProxyGraph::MiniGraph* target = nullptr;
  440. StaticInferManager m_static_infer_manager;
  441. std::unordered_map<size_t, ProxyGraph::MiniGraph> m_mini_graph_cache;
  442. size_t opr_count = 0;
  443. static thread_local std::unique_ptr<ProxyGraphTypeI> sm_instance;
  444. friend class ProxyGraph::MiniGraph;
  445. size_t nr_oprs_in_graph() const override {
  446. return opr_count;
  447. }
  448. size_t next_node_id() override {
  449. return opr_count;
  450. }
  451. std::shared_ptr<void> on_comp_node_finalize() override {
  452. sm_instance.reset();
  453. return {};
  454. }
  455. cg::static_infer::StaticInferManager& static_infer_manager() override {
  456. return m_static_infer_manager;
  457. }
  458. void attach(ProxyGraph::MiniGraph* target_) {
  459. target = target_;
  460. m_static_infer_manager.target = target_;
  461. }
  462. struct AttachGuard {
  463. ProxyGraphTypeI* owner = nullptr;
  464. ProxyGraph::MiniGraph* target = nullptr;
  465. AttachGuard(ProxyGraphTypeI* owner_ = nullptr, ProxyGraph::MiniGraph* target_ = nullptr)
  466. : owner(owner_), target(target_) {}
  467. AttachGuard(AttachGuard&) = delete;
  468. AttachGuard& operator=(AttachGuard&) = delete;
  469. AttachGuard(AttachGuard&& rhs) : owner(rhs.owner), target(rhs.target) {rhs.owner = nullptr;}
  470. AttachGuard& operator=(AttachGuard&& rhs) = delete;
  471. ~AttachGuard() {if (owner) owner->attach(target);}
  472. };
  473. [[nodiscard]]
  474. AttachGuard scoped_attach(ProxyGraph::MiniGraph* target_) {
  475. attach(target_);
  476. return attach_guard();
  477. }
  478. [[nodiscard]]
  479. AttachGuard attach_guard(ProxyGraph::MiniGraph* target_ = nullptr) {
  480. return {this, target_};
  481. }
  482. public:
  483. OperatorNodeBase* insert_opr(std::unique_ptr<OperatorNodeBase> opr_uniqp) override {
  484. return target->insert_opr(std::move(opr_uniqp));
  485. }
  486. static ProxyGraphTypeI& inst() {
  487. if (!sm_instance) {
  488. sm_instance.reset(new ProxyGraphTypeI);
  489. }
  490. return *sm_instance;
  491. }
  492. std::tuple<SmallVector<LogicalTensorDesc>, bool> infer_output_attrs_fallible(const OpDef& def,
  493. const SmallVector<LogicalTensorDesc>& inputs) {
  494. size_t buf_size = 2 * inputs.size() + 1;
  495. size_t buf[buf_size];
  496. size_t pos = 0;
  497. buf[pos++] = def.hash();
  498. for (auto&& desc : inputs) {
  499. buf[pos++] = mgb::hash(desc.layout.dtype.handle());
  500. buf[pos++] = mgb::hash(desc.comp_node);
  501. }
  502. mgb_assert(pos == buf_size);
  503. auto key = XXHash{}.update(buf, buf_size*sizeof(size_t)).digest();
  504. auto it = m_mini_graph_cache.find(key);
  505. if (it == m_mini_graph_cache.end()) {
  506. auto&& result = m_mini_graph_cache.emplace(
  507. std::piecewise_construct,
  508. std::make_tuple(key),
  509. std::forward_as_tuple(*this, def, inputs));
  510. mgb_assert(result.second);
  511. it = result.first;
  512. }
  513. auto& minigraph = it->second;
  514. auto _ = scoped_attach(&minigraph);
  515. auto sess = minigraph.infer_session(inputs);
  516. std::tuple<SmallVector<LogicalTensorDesc>, bool> ret;
  517. auto& [descs, noerr] = ret;
  518. descs.reserve(minigraph.output_size());
  519. for (size_t i = 0; i < minigraph.output_size(); ++i) {
  520. descs.emplace_back();
  521. auto& desc = descs.back();
  522. desc.layout.dtype = minigraph.output_var(i)->dtype();
  523. desc.comp_node = minigraph.output_var(i)->comp_node();
  524. if (auto* shape = sess.infer_shape(i, false)) {
  525. desc.layout.init_contiguous_stride(*shape);
  526. noerr = true;
  527. } else {
  528. noerr = false;
  529. }
  530. }
  531. return ret;
  532. }
  533. };
  534. } // namespace mgb::imperative::proxy_graph

MegEngine 安装包中集成了使用 GPU 运行代码所需的 CUDA 环境,不用区分 CPU 和 GPU 版。 如果想要运行 GPU 程序,请确保机器本身配有 GPU 硬件设备并安装好驱动。 如果你想体验在云端 GPU 算力平台进行深度学习开发的感觉,欢迎访问 MegStudio 平台