From 0c33fadfffb4deb473c9145eb9d20aeeaad06fae Mon Sep 17 00:00:00 2001 From: cse0001 Date: Tue, 10 Sep 2024 12:27:59 -0700 Subject: [PATCH 1/8] VisualDL Adaptation for Paddle PIR --- demo/components/cond_inside_cond_test.py | 66 ++++ demo/components/cond_test.py | 59 ++++ demo/components/pir_graph_test.py | 52 ++++ .../{pir_translate.py => pir_program_test.py} | 20 +- demo/components/while_test.py | 48 +++ visualdl/component/graph/__init__.py | 3 +- visualdl/component/graph/exporter.py | 9 +- visualdl/component/graph/graph_component.py | 282 ++++++++++++++++-- 8 files changed, 503 insertions(+), 36 deletions(-) create mode 100755 demo/components/cond_inside_cond_test.py create mode 100755 demo/components/cond_test.py create mode 100755 demo/components/pir_graph_test.py rename demo/components/{pir_translate.py => pir_program_test.py} (53%) create mode 100755 demo/components/while_test.py diff --git a/demo/components/cond_inside_cond_test.py b/demo/components/cond_inside_cond_test.py new file mode 100755 index 000000000..93af553ea --- /dev/null +++ b/demo/components/cond_inside_cond_test.py @@ -0,0 +1,66 @@ +# Copyright (c) 2024 VisualDL Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ======================================================================= +import paddle +from visualdl import LogWriter +""" + pseudocode: + for i in range(1, 10): + a = 2 * i + if i < 5: + if i >= 3: + return a + a + else: + return a - a + else: + if i < 8: + return a * a + else: + return a / a +""" +paddle.enable_static() + +def less_than_branch(i, a): + return paddle.static.nn.cond( + i >= 3.0, + lambda: paddle.add(a, a), + lambda: paddle.subtract(a, a), + ) + +def greater_equal_branch(i, a): + return paddle.static.nn.cond( + i < 8.0, + lambda: paddle.multiply(a, a), + lambda: paddle.divide(a, a), + ) + +main_program = paddle.static.Program() +startup_program = paddle.static.Program() +with paddle.static.program_guard(main_program, startup_program): + i = paddle.static.data(name="i", shape=[1], dtype='float32') + i.stop_gradient = False + a = 2.0 * i + out = paddle.static.nn.cond( + i < 5.0, + lambda: less_than_branch(i, a), + lambda: greater_equal_branch(i, a), + ) + mean = paddle.mean(out) + +with LogWriter(logdir="./log/cond_inside_cond_test/") as writer: + writer.add_graph( + model=main_program, + input_spec=[paddle.static.InputSpec([1], dtype='float32')], + verbose=True, + is_pir=True) \ No newline at end of file diff --git a/demo/components/cond_test.py b/demo/components/cond_test.py new file mode 100755 index 000000000..625d334f1 --- /dev/null +++ b/demo/components/cond_test.py @@ -0,0 +1,59 @@ +# Copyright (c) 2024 VisualDL Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ======================================================================= +import paddle +from visualdl import LogWriter + +paddle.enable_static() +""" + pseudocode: + for i in range(1, 10): + a = 2 * i + if i < 5: + return a + a + else: + return a - a +""" +class ConditionalLayer(paddle.nn.Layer): + def __init__(self): + super(ConditionalLayer, self).__init__() + + def forward(self, i): + a = 2.0 * i + out = paddle.static.nn.cond( + i < 5.0, + lambda: paddle.add(a, a), + lambda: paddle.subtract(a, a), + ) + return out + +main_program = paddle.static.Program() +startup_program = paddle.static.Program() +with paddle.static.program_guard(main_program, startup_program): + i = paddle.static.data(name="i", shape=[1], dtype='float32') + i.stop_gradient = False + a = 2.0 * i + out = paddle.static.nn.cond( + i < 5.0, + lambda: paddle.add(a, a), + lambda: paddle.subtract(a, a), + ) + mean = paddle.mean(out) + +with LogWriter(logdir="./log/cond_test/") as writer: + writer.add_graph( + model=main_program, + input_spec=[paddle.static.InputSpec([1], 'float32')], + verbose=True, + is_pir=True) diff --git a/demo/components/pir_graph_test.py b/demo/components/pir_graph_test.py new file mode 100755 index 000000000..47bbf875a --- /dev/null +++ b/demo/components/pir_graph_test.py @@ -0,0 +1,52 @@ +# Copyright (c) 2024 VisualDL Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ======================================================================= +import paddle +import paddle.nn.functional as F +from paddle import nn +from visualdl import LogWriter + +class MyNet(nn.Layer): + def __init__(self): + super(MyNet, self).__init__() + self.conv1 = nn.Conv2D( + in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2) + self.max_pool1 = nn.MaxPool2D(kernel_size=2, stride=2) + self.conv2 = nn.Conv2D( + in_channels=20, + out_channels=20, + kernel_size=5, + stride=1, + padding=2) + self.max_pool2 = nn.MaxPool2D(kernel_size=2, stride=2) + self.fc = nn.Linear(in_features=980, out_features=10) + + def forward(self, inputs): + x = self.conv1(inputs) + x = F.relu(x) + x = self.max_pool1(x) + x = self.conv2(x) + x = F.relu(x) + x = self.max_pool2(x) + x = paddle.reshape(x, [x.shape[0], -1]) + x = self.fc(x) + return x + +net = MyNet() +with LogWriter(logdir="./log/pir_graph_test/") as writer: + writer.add_graph( + model=net, + input_spec=[paddle.static.InputSpec([-1, 1, 28, 28], 'float32')], + verbose=True, + is_pir=True) \ No newline at end of file diff --git a/demo/components/pir_translate.py b/demo/components/pir_program_test.py similarity index 53% rename from demo/components/pir_translate.py rename to demo/components/pir_program_test.py index 6b7868116..ee8b67993 100644 --- a/demo/components/pir_translate.py +++ b/demo/components/pir_program_test.py @@ -1,6 +1,18 @@ +# Copyright (c) 2024 VisualDL Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ======================================================================= import paddle -from paddle import ir - from visualdl import LogWriter paddle.enable_static() @@ -18,11 +30,9 @@ batch_norm = paddle.nn.BatchNorm(32, act='relu', data_layout='NHWC') out = batch_norm(conv2d(tanh_out)) -newir_program = ir.translate_to_new_ir(main_program.desc) - with LogWriter(logdir="./log/program_test/") as writer: writer.add_graph( - model=newir_program, + model=main_program, input_spec=[paddle.static.InputSpec([-1, 1, 28, 28], 'float32')], verbose=True, is_pir=True) diff --git a/demo/components/while_test.py b/demo/components/while_test.py new file mode 100755 index 000000000..2088d446b --- /dev/null +++ b/demo/components/while_test.py @@ -0,0 +1,48 @@ +# Copyright (c) 2024 VisualDL Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ======================================================================= +import paddle +from visualdl import LogWriter + +paddle.enable_static() +main_program = paddle.static.Program() +startup_program = paddle.static.Program() +with paddle.static.program_guard(main_program, startup_program): + linear = paddle.nn.Linear(16, 10) + def cond(i, loop_len, x, result): + return i < loop_len + + def body(i, loop_len, x, result): + result = linear(x) + paddle.increment(i) + return [i, loop_len, x, result] + + x = paddle.static.data(name='x', shape=[32, 16], dtype='float32') + i = paddle.zeros(shape=[1], dtype='int64') + loop_len = paddle.ones(shape=[1], dtype='int64') + result = paddle.zeros( + shape=x.shape[:-1] + linear.weight.shape[-1:], dtype="float32" + ) + result.stop_gradient = False + _, _, _, results = paddle.static.nn.while_loop( + cond, body, [i, loop_len, x, result] + ) + loss = paddle.mean(results) + +with LogWriter(logdir="./log/while_test/") as writer: + writer.add_graph( + model=main_program, + input_spec=[paddle.static.InputSpec([1], 'float32')], + verbose=True, + is_pir=True) \ No newline at end of file diff --git a/visualdl/component/graph/__init__.py b/visualdl/component/graph/__init__.py index bb89f7dc2..13e3036dc 100644 --- a/visualdl/component/graph/__init__.py +++ b/visualdl/component/graph/__init__.py @@ -14,6 +14,7 @@ # ======================================================================= from .exporter import translate_graph from .graph_component import analyse_model +from .graph_component import analyse_pir from .netron_graph import Model -__all__ = ['translate_graph', 'analyse_model', 'Model'] +__all__ = ['translate_graph', 'analyse_model', 'analyse_pir', 'Model'] diff --git a/visualdl/component/graph/exporter.py b/visualdl/component/graph/exporter.py index 3a9c545de..bd6d3e252 100644 --- a/visualdl/component/graph/exporter.py +++ b/visualdl/component/graph/exporter.py @@ -15,6 +15,7 @@ import json import os import tempfile +import paddle from .graph_component import analyse_model from .graph_component import analyse_pir @@ -34,7 +35,13 @@ def translate_graph(model, input_spec, verbose=True, **kwargs): model_data = open(os.path.join(tmp, 'temp.pdmodel'), 'rb').read() result = analyse_model(model_data) else: - result = analyse_pir(model) + if isinstance(model, paddle.base.libpaddle.pir.Program): + result = analyse_pir(model) + else: + model = paddle.jit.to_static(model, input_spec) + paddle.jit.save(model, os.path.join(tmp, 'temp')) + model_data = paddle.jit.load(os.path.join(tmp, 'temp')) + result = analyse_pir(model_data.program()) if verbose: print_model(result) result = json.dumps(result, indent=2) diff --git a/visualdl/component/graph/graph_component.py b/visualdl/component/graph/graph_component.py index 5e91869b3..68061b36b 100644 --- a/visualdl/component/graph/graph_component.py +++ b/visualdl/component/graph/graph_component.py @@ -16,6 +16,7 @@ import os.path import pathlib import re +import paddle from . import utils @@ -365,44 +366,247 @@ def analyse_model(model_pb): # noqa: C901 return final_data +def is_control_flow(op): + return op.name() == "pd_op.if" or op.name() == "pd_op.while" + + +def is_same_block_op(from_node, to_node, all_ops): + if all_ops[to_node]["parent_node"] == '/': + return False + from_ancestors = set() + while all_ops[from_node]["parent_node"] != '/': + from_ancestors.add(all_ops[from_node]["parent_node"]) + from_node = all_ops[from_node]["parent_node"] + if all_ops[to_node]["parent_node"] in from_ancestors: + return False + else: + return True + + +def create_control_output_node(all_ops, all_vars, control_node_name): + op_name = control_node_name + '/' + "output" + all_ops[op_name] = {} + all_ops[op_name]['name'] = op_name + all_ops[op_name]['show_name'] = op_name + + all_ops[op_name]['type'] = "control_op.output" + all_ops[op_name]['dtype'] = all_ops[control_node_name]['dtype'] + all_ops[op_name]['input_vars'] = {} + all_ops[op_name]['output_vars'] = all_ops[control_node_name]['output_vars'] + + all_ops[op_name]['is_leaf_node'] = True + for var in all_vars: + if all_vars[var]['from_node'] == control_node_name: + all_ops[op_name]['output_vars'][var] = [var] + all_vars[var]['from_node'] = op_name + + all_ops[op_name]['attrs'] = all_ops[control_node_name]['attrs'] + all_ops[op_name]['attr_types'] = all_ops[control_node_name]['attr_types'] + all_ops[op_name]['children_node'] = [] + all_ops[op_name]['input_nodes'] = [] + all_ops[op_name]['output_nodes'] = [] + all_ops[op_name]['edge_input_nodes'] = [] + all_ops[op_name]['edge_output_nodes'] = [] + all_ops[op_name]['parent_node'] = control_node_name + all_ops[control_node_name]['children_node'].append(op_name) + return all_ops, all_vars + + +def get_sub_ops(op, op_name, all_ops, all_vars): + for sub_block in op.blocks(): + for sub_op in sub_block.ops: + sub_op_name0 = paddle.utils.unique_name.generate(sub_op.name()) + sub_op_name = op_name + '/' + sub_op_name0 + all_ops[sub_op_name] = {} + all_ops[sub_op_name]['name'] = sub_op_name + all_ops[sub_op_name]['show_name'] = sub_op_name + all_ops[sub_op_name]['type'] = sub_op.name().replace("pd_op.", "") + + try: + all_ops[sub_op_name]['dtype'] = sub_op.result(0).dtype.name + except Exception as e: + all_ops[sub_op_name]['dtype'] = '' + + all_ops[sub_op_name]['input_vars'] = {} + all_ops[sub_op_name]['output_vars'] = {} + all_ops[sub_op_name]['is_leaf_node'] = True + now_var = utils.gen_var_name(sub_op.results()) + for source in sub_op.operands_source(): + input_name = utils.gen_var_name(source) + if sub_op.name() == "pd_op.increment_": + all_vars[now_var]['to_nodes'].append(all_vars[input_name]['from_node']) + all_ops[all_vars[input_name]['from_node']]['input_vars'][now_var] = [now_var] + all_ops[sub_op_name]['input_vars'][input_name] = [input_name] + all_vars[input_name]['to_nodes'].append(sub_op_name) + all_vars[now_var]['from_node'] = sub_op_name + all_ops[sub_op_name]['output_vars'][now_var] = [now_var] + + try: + attrs = op.results()[0].get_defining_op().attrs() + if 'place' in attrs: + attrs['place'] = str(attrs['place']) + try: + attrs['dtype'] = op.result(0).dtype.name + except Exception as e: + attrs['dtype'] = '' + except Exception as e: + # attrs = {} + pass + + all_ops[sub_op_name]['attrs'] = attrs + all_ops[sub_op_name]['attr_types'] = attrs + all_ops[sub_op_name]['children_node'] = [] + all_ops[sub_op_name]['input_nodes'] = [] + all_ops[sub_op_name]['output_nodes'] = [] + all_ops[sub_op_name]['edge_input_nodes'] = [] + all_ops[sub_op_name]['edge_output_nodes'] = [] + all_ops[sub_op_name]["parent_node"] = op_name + all_ops[op_name]['children_node'].append(sub_op_name) + + # yield + if sub_op.name() == 'cf.yield': + var_name = "tmp_var_" + sub_op_name0 + all_vars[var_name] = {} + all_vars[var_name]['name'] = var_name + all_vars[var_name]['dtype'] = '' + all_vars[var_name]['shape'] = [] + all_vars[var_name]['value'] = [] + all_vars[var_name]['persistable'] = False + all_vars[var_name]['attrs'] = {} + all_vars[var_name]['from_node'] = sub_op_name + all_ops[sub_op_name]['output_vars'][var_name] = [var_name] + control_output = all_ops[sub_op_name]["parent_node"] + '/' + "output" + all_vars[var_name]['to_nodes'] = [control_output] + all_ops[control_output]['input_vars'][var_name] = [var_name] + if is_control_flow(sub_op): + all_ops[sub_op_name]['is_leaf_node'] = False + all_ops, all_vars = create_control_output_node(all_ops, all_vars, sub_op_name) + all_ops, all_vars = get_sub_ops(sub_op, sub_op_name, all_ops, all_vars) + + return all_ops, all_vars + + +def get_sub_var(op, all_vars): + for sub_block in op.blocks(): + for sub_op in sub_block.ops: + var_name = utils.gen_var_name(sub_op.results()) + all_vars[var_name] = {} + all_vars[var_name]['name'] = var_name + try: + attrs = op.results()[0].get_defining_op().attrs() + if 'place' in attrs: + attrs['place'] = str(attrs['place']) + try: + attrs['dtype'] = op.result(0).dtype.name + except Exception as e: + attrs['dtype'] = '' + except Exception as e: + attrs = {} + + try: + all_vars[var_name]['shape'] = sub_op.result(0).shape + except Exception as e: + all_vars[var_name]['shape'] = [] + try: + all_vars[var_name]['type'] = sub_op.result(0).dtype.name + except Exception as e: + all_vars[var_name]['type'] = '' + try: + all_vars[var_name]['dtype'] = sub_op.result(0).dtype.name + except Exception as e: + all_vars[var_name]['dtype'] = '' + + all_vars[var_name]['value'] = [] + try: + all_vars[var_name]['persistable'] = sub_op.result(0).persistable + except Exception as e: + all_vars[var_name]['persistable'] = False + + if sub_op.name() == "builtin.parameter": + all_vars[var_name]['persistable'] = False + all_vars[var_name]['attrs'] = attrs + all_vars[var_name]['from_node'] = '' + all_vars[var_name]['to_nodes'] = [] + if is_control_flow(sub_op): + all_vars = get_sub_var(sub_op, all_vars) + return all_vars + + def analyse_pir(program): from paddle.utils.unique_name import generate all_ops = {} all_vars = {} all_edges = {} + + # create '/' op + all_ops['/'] = {} + all_ops['/']['name'] = '/' + all_ops['/']['show_name'] = '/' + all_ops['/']['type'] = '' + all_ops['/']['attrs'] = {} + all_ops['/']['input_vars'] = {} + all_ops['/']['output_vars'] = {} + all_ops['/']['is_leaf_node'] = False + all_ops['/']['children_node'] = [] + # vars info - for op in (program.global_block().ops): + for op in program.global_block().ops: var_name = utils.gen_var_name(op.results()) all_vars[var_name] = {} all_vars[var_name]['name'] = var_name - attrs = op.results()[0].get_defining_op().attrs() - - if 'place' in attrs: - attrs['place'] = str(attrs['place']) - attrs['dtype'] = op.result(0).dtype.name - - all_vars[var_name]['shape'] = op.result(0).shape - all_vars[var_name]['type'] = op.result(0).dtype.name - all_vars[var_name]['dtype'] = op.result(0).dtype.name + try: + attrs = op.results()[0].get_defining_op().attrs() + if 'place' in attrs: + attrs['place'] = str(attrs['place']) + try: + attrs['dtype'] = op.result(0).dtype.name + except Exception as e: + attrs['dtype'] = '' + except Exception as e: + pass + + try: + all_vars[var_name]['shape'] = op.result(0).shape + except Exception as e: + all_vars[var_name]['shape'] = [] + try: + all_vars[var_name]['type'] = var_name + except Exception as e: + all_vars[var_name]['type'] = '' + try: + all_vars[var_name]['dtype'] = op.result(0).dtype.name + except Exception as e: + all_vars[var_name]['dtype'] = '' all_vars[var_name]['value'] = [] - all_vars[var_name]['persistable'] = op.result(0).is_persistable + try: + all_vars[var_name]['persistable'] = op.result(0).persistable + except Exception as e: + all_vars[var_name]['persistable'] = False + if op.name() == "builtin.parameter": + all_vars[var_name]['persistable'] = False all_vars[var_name]['attrs'] = attrs all_vars[var_name]['from_node'] = '' all_vars[var_name]['to_nodes'] = [] + if is_control_flow(op): + all_vars = get_sub_var(op, all_vars) # ops info - for op in (program.global_block().ops): + for op in program.global_block().ops: op_name = generate(op.name()) + op_name = '/' + op_name - if op.num_operands() > 0: + if op.num_operands() >= 0: all_ops[op_name] = {} all_ops[op_name]['name'] = op_name all_ops[op_name]['show_name'] = op_name - all_ops[op_name]['type'] = op.result(0).dtype.name - all_ops[op_name]['dtype'] = op.result(0).dtype.name + all_ops[op_name]['type'] = op.name().replace("pd_op.", "") + try: + all_ops[op_name]['dtype'] = op.result(0).dtype.name + except Exception as e: + all_ops[op_name]['dtype'] = '' all_ops[op_name]['input_vars'] = {} all_ops[op_name]['output_vars'] = {} @@ -410,6 +614,9 @@ def analyse_pir(program): now_var = utils.gen_var_name(op.results()) for source in op.operands_source(): input_name = utils.gen_var_name(source) + if op.name() == "pd_op.increment_": + all_vars[now_var]['to_nodes'].append(all_vars[input_name]['from_node']) + all_ops[all_vars[input_name]['from_node']]['input_vars'][now_var] = [now_var] all_ops[op_name]['input_vars'][input_name] = [input_name] all_vars[input_name]['to_nodes'].append(op_name) all_vars[now_var]['from_node'] = op_name @@ -422,32 +629,49 @@ def analyse_pir(program): all_ops[op_name]['output_nodes'] = [] all_ops[op_name]['edge_input_nodes'] = [] all_ops[op_name]['edge_output_nodes'] = [] + all_ops[op_name]['parent_node'] = '/' + all_ops['/']['children_node'].append(op_name) - # create '/' op - all_ops['/'] = {} - all_ops['/']['name'] = '/' - all_ops['/']['show_name'] = '/' - all_ops['/']['type'] = '' - all_ops['/']['attrs'] = {} - all_ops['/']['input_vars'] = {} - all_ops['/']['output_vars'] = {} - all_ops['/']['is_leaf_node'] = False - all_ops['/']['children_node'] = [] - for node in all_ops: - if node != '/': - all_ops['/']['children_node'].append(node) + if is_control_flow(op): + all_ops[op_name]['is_leaf_node'] = False + all_ops, all_vars = create_control_output_node(all_ops, all_vars, op_name) + all_ops, all_vars = get_sub_ops(op, op_name, all_ops, all_vars) for variable_name in all_vars: if all_vars[variable_name]['from_node'] == '': continue + from_node = all_vars[variable_name]['from_node'] + for to_node in all_vars[variable_name]['to_nodes']: + if is_same_block_op(from_node, to_node, all_ops): + all_vars[variable_name]['to_nodes'].append(all_ops[to_node]["parent_node"]) + all_ops[all_ops[to_node]["parent_node"]]['input_vars'][variable_name] = [variable_name] from_node_name = all_vars[variable_name]['from_node'] for to_node_name in all_vars[variable_name]['to_nodes']: if to_node_name != from_node_name: all_ops[from_node_name]['output_nodes'].append(to_node_name) all_ops[to_node_name]['input_nodes'].append(from_node_name) + all_vars[variable_name]['to_nodes'] = list(set(all_vars[variable_name]['to_nodes'])) + for node in all_ops: + if node != '/': + all_ops[node]['input_nodes'] = list(set(all_ops[node]['input_nodes'])) + all_ops[node]['output_nodes'] = list(set(all_ops[node]['output_nodes'])) # edge info - # TODO(Difers):add edge info in future + for var_name in all_vars.keys(): + construct_edges(var_name, all_ops, all_vars, all_edges) + + for src_node, to_node in all_edges.keys(): + all_ops[src_node]['edge_output_nodes'].append(to_node) + all_ops[to_node]['edge_input_nodes'].append(src_node) + all_edges[(src_node, + to_node)]['vars'] = list(all_edges[(src_node, + to_node)]['vars']) + if len(all_edges[(src_node, to_node)]['vars']) > 1: + all_edges[(src_node, to_node)]['label'] = str( + len(all_edges[(src_node, to_node)]['vars'])) + ' tensors' + elif len(all_edges[(src_node, to_node)]['vars']) == 1: + all_edges[(src_node, to_node)]['label'] = str( + all_vars[all_edges[(src_node, to_node)]['vars'][0]]['shape']) final_data = { 'version': _graph_version, From 1af4e05b32067f6da317194116e19acbede486b0 Mon Sep 17 00:00:00 2001 From: cse0001 Date: Tue, 10 Sep 2024 12:29:56 -0700 Subject: [PATCH 2/8] [Doc] Add document for VisualDL Adaptation for Paddle PIR --- docs/Paddle PIR Visualization.md | 134 +++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100755 docs/Paddle PIR Visualization.md diff --git a/docs/Paddle PIR Visualization.md b/docs/Paddle PIR Visualization.md new file mode 100755 index 000000000..fcad6592f --- /dev/null +++ b/docs/Paddle PIR Visualization.md @@ -0,0 +1,134 @@ +# VisualDL适配Paddle3.0Beta PIR + +目前的VisualDL的计算图可视化思路为:根据静态图获取全部变量和算子信息,构建特定格式的vdlgraph.log文件,后利用自定义的Model类读取数据并支持前端进行可视化。其中需要重点关注的是计算图算子和变量之间的输入输出关系,即算子中的`input_vars`和`output_vars`信息和变量中的`from_node`和`to_nodes`信息,这些输入输出信息决定了可视化的计算图结构。 + +现有PIR计算图可视化思路为:从PIR的program中获取可视化所需的变量和算子信息,构建结构相同的vdlgraph.log文件,这种方式存在以下可以改进的地方: + ++ 现有方法没有考虑PIR的新特性,只提取顶层block的变量和算子,无法可视化含有多层block的控制流结构的计算图。 ++ 现有方法不支持layer的展开收缩,并且没有在vdlgraph.log文件中存储计算图边信息 ++ 现有方法只支持静态图(paddle.base.libpaddle.pir.Program)输入,不支持动态图输入 ++ 现有方法不支持可视化PIR json格式存储的模型 + +## VisualDL支持PIR 控制流算子可视化 +在PIR中,控制流算子都拥有子block,在子block中存放分支或者循环体包含的算子信息,下面为一个简单的包含ifop的模型IR + +```plain +{ + (%0) = "pd_op.data" () {dtype:(pd_op.DataType)float32,name:"i",place:(pd_op.Place)Place(undefined:0),shape:(pd_op.IntArray)[1],stop_gradient:[false]} : () -> builtin.tensor<1xf32> + (%1) = "pd_op.full" () {dtype:(pd_op.DataType)float32,place:(pd_op.Place)Place(cpu),shape:(pd_op.IntArray)[1],stop_gradient:[true],value:(Double)2} : () -> builtin.tensor<1xf32> + (%2) = "pd_op.scale" (%0, %1) {bias:(Float)0,bias_after_scale:true,stop_gradient:[false]} : (builtin.tensor<1xf32>, builtin.tensor<1xf32>) -> builtin.tensor<1xf32> + (%3) = "pd_op.full" () {dtype:(pd_op.DataType)float32,place:(pd_op.Place)Place(undefined:0),shape:(pd_op.IntArray)[],stop_gradient:[true],value:(Double)5} : () -> builtin.tensor + (%4) = "pd_op.less_than" (%0, %3) {stop_gradient:[true]} : (builtin.tensor<1xf32>, builtin.tensor) -> builtin.tensor<1xb> + (%5) = "pd_op.if" (%4) {stop_gradient:[false]} -> builtin.tensor<1xf32> { + (%6) = "pd_op.add" (%2, %2) {stop_gradient:[false]} : (builtin.tensor<1xf32>, builtin.tensor<1xf32>) -> builtin.tensor<1xf32> + () = "cf.yield" (%6) {} : (builtin.tensor<1xf32>) -> + } else { + (%7) = "pd_op.subtract" (%2, %2) {stop_gradient:[false]} : (builtin.tensor<1xf32>, builtin.tensor<1xf32>) -> builtin.tensor<1xf32> + () = "cf.yield" (%7) {} : (builtin.tensor<1xf32>) -> + } + (%8) = "pd_op.mean" (%5) {axis:(pd_op.IntArray)[],keepdim:false,stop_gradient:[false]} : (builtin.tensor<1xf32>) -> builtin.tensor +} +``` + +其中line8-12中的四个算子在子block中,由于现有方法只遍历顶层block的算子,忽略了子block,所以不支持控制流算子可视化。为了解决这个问题,我们首先从子block中获取算子和变量信息,增加`get_sub_var`和`get_sub_ops`函数用于提取子block内的算子和变量,二者均为递归函数进而处理多层block嵌套情况。在遍历顶层block时遇到控制流算子会调用这两个函数,判断控制流算子的代码为: + +```plain + +``` + +为了计算图可视化的直观和美观,我们在visualdl中将控制流算子表示成一个可收缩可展开的layer,效果如下: + +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725995876633-33d068ea-96ac-4c81-b637-0a242e22838a.png) + +展开后效果如下: + +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725995789430-d99ae3bd-b4b8-45a8-9dd1-42d13cefd871.png) + +其中展开后由layer上方的名称标识为这是一个控制流算子,这里需要注意的是,在此途中pd_op.less_than_0算子计算了ifop的条件值,应该有一条线连接pd_op.less_than_0和pd_op.if_0展开后的layer,但由于前端时基于netron的,目前不支持算子连线到一个layer。 + +还需要注意的是,为了使计算图数据流可视化效果更加直观,我们在控制流子block中添加了一个output算子,这是因为在PIR中,控制流子block以辅助算子cf.yield作为结束,这使得展开后控制流的layer没有参数传出,因此我们添加了一个output算子,输入为控制流所有cf.yield,输出为未展开前控制流算子的输出,具体实现代码如下: + +```plain +def create_control_output_node(all_ops, all_vars, control_node_name): + op_name = control_node_name + '/' + "output" + all_ops[op_name] = {} + all_ops[op_name]['name'] = op_name + all_ops[op_name]['show_name'] = op_name + + all_ops[op_name]['type'] = "control_op.output" + all_ops[op_name]['dtype'] = all_ops[control_node_name]['dtype'] + all_ops[op_name]['input_vars'] = {} + all_ops[op_name]['output_vars'] = all_ops[control_node_name]['output_vars'] + + all_ops[op_name]['is_leaf_node'] = True + for var in all_vars: + if all_vars[var]['from_node'] == control_node_name: + all_ops[op_name]['output_vars'][var] = [var] + all_vars[var]['from_node'] = op_name + + all_ops[op_name]['attrs'] = all_ops[control_node_name]['attrs'] + all_ops[op_name]['attr_types'] = all_ops[control_node_name]['attr_types'] + all_ops[op_name]['children_node'] = [] + all_ops[op_name]['input_nodes'] = [] + all_ops[op_name]['output_nodes'] = [] + all_ops[op_name]['edge_input_nodes'] = [] + all_ops[op_name]['edge_output_nodes'] = [] + all_ops[op_name]['parent_node'] = control_node_name + all_ops[control_node_name]['children_node'].append(op_name) + return all_ops, all_vars +``` + +在实现支持PIR 控制流算子可视化,还有一些trick,记录如下: + +1、对于循环控制流算子(whileop),为了可视化的直观,对于pd_op.increment算子我们增加一条边指向上游算子,代表循环结构的数据流向,效果如下:![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725975662978-9479a84a-6a1d-418c-b97e-d6c35d92677d.png?x-oss-process=image%2Fformat%2Cwebp%2Fresize%2Cw_937%2Climit_0)代码如下: + +```plain +if op.name() == "pd_op.increment_": + all_vars[now_var]['to_nodes'].append(all_vars[input_name]['from_node']) + all_ops[all_vars[input_name]['from_node']]['input_vars'][now_var] = [now_var] +``` + +2、对于builtin.parameter算子,默认的persistable属性为True,这会导致前端可视化后这个算子没有输出,故将此类算子persistable属性均设置为False,代码如下: + +```plain +if op.name() == "builtin.parameter": + all_vars[var_name]['persistable'] = False +``` + +3、visualdl的前端基于netron实现,其对于一些算子类型类如conv2d等会有不同颜色标识,前端根据vdlgraph.log中算子的type字段判断类型,在旧IR中,可以通过op.type()获取正确的算子类型,但是PIR的算子没有type()接口,因此我们从算子名中获取算子类型,后续Paddle更新后可以考虑修改,目前实现代码为:`all_ops[op_name]['type'] = op.name().replace("pd_op.", "")` + +## VisualDL支持获取PIR program边信息 +实现逻辑和旧IR中一致,代码如下: + +```plain +# edge info +for var_name in all_vars.keys(): + construct_edges(var_name, all_ops, all_vars, all_edges) + +for src_node, to_node in all_edges.keys(): + all_ops[src_node]['edge_output_nodes'].append(to_node) + all_ops[to_node]['edge_input_nodes'].append(src_node) + all_edges[(src_node, + to_node)]['vars'] = list(all_edges[(src_node, + to_node)]['vars']) + if len(all_edges[(src_node, to_node)]['vars']) > 1: + all_edges[(src_node, to_node)]['label'] = str( + len(all_edges[(src_node, to_node)]['vars'])) + ' tensors' + elif len(all_edges[(src_node, to_node)]['vars']) == 1: + all_edges[(src_node, to_node)]['label'] = str( + all_vars[all_edges[(src_node, to_node)]['vars'][0]]['shape']) +``` + +## add_graph支持输入静态计算图和动态计算图 +目前的PIR分析是针对于静态计算图的,对于动态计算图将进行动转静和save load得到静态图进行分析,代码如下: + +```plain +if isinstance(model, paddle.base.libpaddle.pir.Program): + result = analyse_pir(model) +else: + model = paddle.jit.to_static(model, input_spec) + paddle.jit.save(model, os.path.join(tmp, 'temp')) + model_data = paddle.jit.load(os.path.join(tmp, 'temp')) + result = analyse_pir(model_data.program()) +``` + From f018d83e7c18db7b9143486f39c2925ae19fdc0e Mon Sep 17 00:00:00 2001 From: cse0001 Date: Tue, 24 Sep 2024 14:01:10 -0700 Subject: [PATCH 3/8] Dynamic graph changemodel interfaces support *.json file --- .../packages/core/src/pages/graphDynamic.tsx | 2 +- frontend/packages/netron/src/view.js | 2 +- frontend/packages/netron/src/view2.js | 2 +- frontend/packages/netron2/src/view.js | 2 +- frontend/packages/netron2/src/view2.js | 2 +- visualdl/reader/graph_reader.py | 37 ++++++++++++++++++- visualdl/server/api.py | 3 ++ 7 files changed, 44 insertions(+), 6 deletions(-) diff --git a/frontend/packages/core/src/pages/graphDynamic.tsx b/frontend/packages/core/src/pages/graphDynamic.tsx index e2f8adb38..792627386 100644 --- a/frontend/packages/core/src/pages/graphDynamic.tsx +++ b/frontend/packages/core/src/pages/graphDynamic.tsx @@ -101,7 +101,7 @@ const Graph: FunctionComponent = () => { const onChangeFile = (e: React.ChangeEvent) => { const target = e.target as EventTarget & HTMLInputElement; const file: FileList | null = target.files as FileList; - if (file[0].name.split('.')[1] !== 'pdmodel') { + if (file[0].name.split('.')[1] !== 'pdmodel' && file[0].name.split('.')[1] !== 'json') { alert('该页面只能解析paddle的模型,如需解析请跳转网络结构静态图页面'); return; } diff --git a/frontend/packages/netron/src/view.js b/frontend/packages/netron/src/view.js index 4fb672696..e7cf17a2a 100644 --- a/frontend/packages/netron/src/view.js +++ b/frontend/packages/netron/src/view.js @@ -1394,7 +1394,7 @@ view.ModelFactoryService = class { this.register('./uff', ['.uff', '.pb', '.trt', '.pbtxt', '.uff.txt']); this.register('./sklearn', ['.pkl', '.pickle', '.joblib', '.model', '.meta', '.pb', '.pt', '.h5']); this.register('./cntk', ['.model', '.cntk', '.cmf', '.dnn']); - this.register('./paddle', ['.paddle', '.pdmodel', '__model__']); + this.register('./paddle', ['.paddle', '.pdmodel', '.json', '__model__']); this.register('./armnn', ['.armnn']); this.register('./bigdl', ['.model', '.bigdl']); this.register('./darknet', ['.cfg', '.model']); diff --git a/frontend/packages/netron/src/view2.js b/frontend/packages/netron/src/view2.js index 55c9705ef..bda5f4113 100644 --- a/frontend/packages/netron/src/view2.js +++ b/frontend/packages/netron/src/view2.js @@ -1075,7 +1075,7 @@ view.ModelFactoryService = class { this.register('./uff', ['.uff', '.pb', '.trt', '.pbtxt', '.uff.txt']); this.register('./sklearn', ['.pkl', '.pickle', '.joblib', '.model', '.meta', '.pb', '.pt', '.h5']); this.register('./cntk', ['.model', '.cntk', '.cmf', '.dnn']); - this.register('./paddle', ['.paddle', '.pdmodel', '__model__']); + this.register('./paddle', ['.paddle', '.pdmodel', '.json', '__model__']); this.register('./armnn', ['.armnn']); this.register('./bigdl', ['.model', '.bigdl']); this.register('./darknet', ['.cfg', '.model']); diff --git a/frontend/packages/netron2/src/view.js b/frontend/packages/netron2/src/view.js index ab45507da..acfc6bf37 100644 --- a/frontend/packages/netron2/src/view.js +++ b/frontend/packages/netron2/src/view.js @@ -1398,7 +1398,7 @@ view.ModelFactoryService = class { this.register('./uff', ['.uff', '.pb', '.trt', '.pbtxt', '.uff.txt']); this.register('./sklearn', ['.pkl', '.pickle', '.joblib', '.model', '.meta', '.pb', '.pt', '.h5']); this.register('./cntk', ['.model', '.cntk', '.cmf', '.dnn']); - this.register('./paddle', ['.paddle', '.pdmodel', '__model__']); + this.register('./paddle', ['.paddle', '.pdmodel', '.json', '__model__']); this.register('./armnn', ['.armnn']); this.register('./bigdl', ['.model', '.bigdl']); this.register('./darknet', ['.cfg', '.model']); diff --git a/frontend/packages/netron2/src/view2.js b/frontend/packages/netron2/src/view2.js index 7ad3c509d..daebb297d 100644 --- a/frontend/packages/netron2/src/view2.js +++ b/frontend/packages/netron2/src/view2.js @@ -1076,7 +1076,7 @@ view.ModelFactoryService = class { this.register('./uff', ['.uff', '.pb', '.trt', '.pbtxt', '.uff.txt']); this.register('./sklearn', ['.pkl', '.pickle', '.joblib', '.model', '.meta', '.pb', '.pt', '.h5']); this.register('./cntk', ['.model', '.cntk', '.cmf', '.dnn']); - this.register('./paddle', ['.paddle', '.pdmodel', '__model__']); + this.register('./paddle', ['.paddle', '.pdmodel', '.json', '__model__']); this.register('./armnn', ['.armnn']); this.register('./bigdl', ['.model', '.bigdl']); this.register('./darknet', ['.cfg', '.model']); diff --git a/visualdl/reader/graph_reader.py b/visualdl/reader/graph_reader.py index 1acc99ed9..e1b1df83c 100644 --- a/visualdl/reader/graph_reader.py +++ b/visualdl/reader/graph_reader.py @@ -14,10 +14,13 @@ # ======================================================================= import json import os +import tempfile from visualdl.component.graph import analyse_model +from visualdl.component.graph import analyse_pir from visualdl.component.graph import Model from visualdl.io import bfile +from paddle.jit import load def is_VDLGraph_file(path): @@ -30,7 +33,7 @@ def is_VDLGraph_file(path): Returns: True if the file is a VDL graph file, otherwise false. """ - if "vdlgraph" not in path and 'pdmodel' not in path: + if "vdlgraph" not in path and 'pdmodel' not in path and 'json' not in path: return False return True @@ -136,6 +139,13 @@ def get_graph(self, data = bfile.BFile(bfile.join(run, self.walks[run]), 'rb').read() if 'pdmodel' in self.walks[run]: graph_model = Model(analyse_model(data)) + elif 'json' in self.walks[run]: + json_object = json.loads(data) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: + json.dump(json_object, json_file, indent=4) + model_data = load(os.path.join(tmp, 'temp')) + graph_model = Model(analyse_pir(model_data.program())) else: graph_model = Model(json.loads(data.decode())) self.graph_buffer[run] = graph_model @@ -163,6 +173,13 @@ def search_graph_node(self, run, nodeid, keep_state=False, is_node=True): data = bfile.BFile(bfile.join(run, self.walks[run]), 'rb').read() if 'pdmodel' in self.walks[run]: graph_model = Model(analyse_model(data)) + elif 'json' in self.walks[run]: + json_object = json.loads(data) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: + json.dump(json_object, json_file, indent=4) + model_data = load(os.path.join(tmp, 'temp')) + graph_model = Model(analyse_pir(model_data.program())) else: graph_model = Model(json.loads(data.decode())) self.graph_buffer[run] = graph_model @@ -184,6 +201,13 @@ def get_all_nodes(self, run): data = bfile.BFile(bfile.join(run, self.walks[run]), 'rb').read() if 'pdmodel' in self.walks[run]: graph_model = Model(analyse_model(data)) + elif 'json' in self.walks[run]: + json_object = json.loads(data) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: + json.dump(json_object, json_file, indent=4) + model_data = load(os.path.join(tmp, 'temp')) + graph_model = Model(analyse_pir(model_data.program())) else: graph_model = Model(json.loads(data.decode())) self.graph_buffer[run] = graph_model @@ -206,6 +230,8 @@ def set_input_graph(self, content, file_type='pdmodel'): return if 'pdmodel' in content: file_type = 'pdmodel' + elif 'json' in content: + file_type = 'json' else: file_type = 'vdlgraph' content = bfile.BFile(content, 'rb').read() @@ -214,6 +240,15 @@ def set_input_graph(self, content, file_type='pdmodel'): data = analyse_model(content) self.graph_buffer['manual_input_model'] = Model(data) + elif file_type == 'json': + json_object = json.loads(content) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: + json.dump(json_object, json_file, indent=4) + model_data = load(os.path.join(tmp, 'temp')) + data = analyse_pir(model_data.program()) + self.graph_buffer['manual_input_model'] = Model(data) + elif file_type == 'vdlgraph': self.graph_buffer['manual_input_model'] = Model( json.loads(content.decode())) diff --git a/visualdl/server/api.py b/visualdl/server/api.py index 0ef7b6dc1..eea55455d 100644 --- a/visualdl/server/api.py +++ b/visualdl/server/api.py @@ -351,6 +351,9 @@ def graph_upload(self): if 'pdmodel' in file_handle.filename: graph_reader.set_input_graph(file_handle.stream.read(), 'pdmodel') + elif 'json' in file_handle.filename: + graph_reader.set_input_graph(file_handle.stream.read(), + 'json') elif 'vdlgraph' in file_handle.filename: graph_reader.set_input_graph(file_handle.stream.read(), 'vdlgraph') From d30bbf9a27752725fb9233ec8da3bf4703d795d4 Mon Sep 17 00:00:00 2001 From: cse0001 Date: Tue, 24 Sep 2024 18:56:47 -0700 Subject: [PATCH 4/8] [Doc] Add document for VisualDL Adaptation for Paddle PIR --- docs/Paddle PIR Visualization.md | 304 ++++++++++++++++++++++++++----- 1 file changed, 259 insertions(+), 45 deletions(-) diff --git a/docs/Paddle PIR Visualization.md b/docs/Paddle PIR Visualization.md index fcad6592f..62b97bb1f 100755 --- a/docs/Paddle PIR Visualization.md +++ b/docs/Paddle PIR Visualization.md @@ -1,15 +1,134 @@ -# VisualDL适配Paddle3.0Beta PIR +## 项目信息 +### 项目名称 +飞桨PaddlePaddle-PIR适配VisualDL模型可视化 +### 方案描述 目前的VisualDL的计算图可视化思路为:根据静态图获取全部变量和算子信息,构建特定格式的vdlgraph.log文件,后利用自定义的Model类读取数据并支持前端进行可视化。其中需要重点关注的是计算图算子和变量之间的输入输出关系,即算子中的`input_vars`和`output_vars`信息和变量中的`from_node`和`to_nodes`信息,这些输入输出信息决定了可视化的计算图结构。 -现有PIR计算图可视化思路为:从PIR的program中获取可视化所需的变量和算子信息,构建结构相同的vdlgraph.log文件,这种方式存在以下可以改进的地方: +现有PIR计算图可视化初步实现为:从PIR的program中获取可视化所需的变量和算子信息,构建结构相同的vdlgraph.log文件,目前实现的不足主要有以下四点: -+ 现有方法没有考虑PIR的新特性,只提取顶层block的变量和算子,无法可视化含有多层block的控制流结构的计算图。 -+ 现有方法不支持layer的展开收缩,并且没有在vdlgraph.log文件中存储计算图边信息 -+ 现有方法只支持静态图(paddle.base.libpaddle.pir.Program)输入,不支持动态图输入 -+ 现有方法不支持可视化PIR json格式存储的模型 +1. 现有方法没有考虑PIR的新特性,只提取顶层block的变量和算子,无法可视化含有多层block的控制流结构的计算图。 +2. 现有方法不支持layer的展开收缩,并且没有在vdlgraph.log文件中存储计算图边信息 +3. 现有方法只支持静态图(paddle.base.libpaddle.pir.Program)输入,不支持动态图输入 +4. 现有方法不支持可视化PIR json格式存储的模型 -## VisualDL支持PIR 控制流算子可视化 +针对以上四点不足,分别设计对应解决方案: + +1. 重写PIR的program分析部分,按照深度优先搜索策略,从顶层block逐层获取每层的算子和变量,重点关注跨block的变量输入输出关系。 +2. 将控制流结构构建为layer结构(可收缩、展开),收缩时隐藏内部子block,重点体现整体模型结构和数据流向,展开时虚线框标记控制流内部block。重点体现内部算子关系和数据流向;仿照paddle2.x的program分析部分,增加模型边信息分析功能。 +3. 基于paddle3.x的动转静、save_load功能,修改生成vdlgraph文件的功能的输入接口,支持静态图和动态图的输入。 +4. 修改前端动态图更换模型接口,支持*.json文件输入,修改后端所有相关接口和功能代码,分别处理*.pdmodel和*.json两类文件。 + +### 时间规划 +#### 7月1日 - 7月25日 ++ 进一步熟悉VisualDL项目源码及其现有功能。理解项目的需求和范围。(5天) ++ 阅读PIR设计文档,总结PIR新特性,设计可视化方案。(20天) + +#### 7月26日 - 8月25日 ++ 基于PIR新特性开发PIR解析功能,获取可视化所需的全部模型数据。(10天) ++ 对接内部接口,修改前端设计,根据可视化方案实现前端对于PIR新特性的可视化。(10天) ++ 开发PIR下控制流的可视化。(10天) + +#### 8月26日 – 9月15日 ++ 对于选定的两个模型进行可视化,验证功能可用性。(10天) ++ 对于所有实现的功能进行测试和调试,并尝试复杂模型结构,验证可靠性。(10天) + +#### 9月16日 – 9月30日 ++ 优化和完善模型可视化实现。 ++ 准备文档和代码示例。 ++ 提交 PR/MR 和项目结项报告。 + +## 项目总结 +### 已完成工作 +下面按照时间线进行总结 + +#### 7月1日-7月14日 +项目预热、和导师沟通、制定实现方案和工作计划 + +#### 7月15日-7月28日 +**1. 配置开发环境,熟悉Paddle和VisualDL** + ++ 本地源码编译安装Paddle和VisualDL的develop分支 + +**2. 熟悉VisualDL进行计算图可视化的设计** + ++ 阅读visualdl#Model()类存储计算图的设计逻辑 ++ 阅读visualdl#analyse_model()函数从**.pdmodel*文件获取计算图数据的逻辑 ++ 阅读visualdl#analyse_pir()函数从PIR表示的模型获取计算图数据的逻辑 + +**3. 熟悉PIR进行计算图存储的设计** + ++ 阅读pir#program.cc, operation.cc, block.cc, region.cc等文件,熟悉PIR进行模型存储的设计 ++ 阅读fluid#pir.cc中和获取Program数据有关接口API + +**4. 完成Visual现有PIR计算图可视化方法适配Paddle3.0版本** + ++ 修改visualdl#analyse_pir()等函数,并成功可视化原有测试demo#pir_translate.py + +**5. 尝试可视化具有分支结构模型** + ++ 实现了具有if分支结构的简单模型并进行可视化 + +#### 7月29日-8月11日(解决不足1) +**1. 测试目前 Visualdl 对于控制结构可视化的支持能力** + ++ 静态方式组建六种包含控制结构的网络,包含循环结构和分支结构,以及多层嵌套控制等 ++ 利用现有 pir 可视化方法进行可视化发现不支持包含多层 Block 网络的可视化 + +**2. 开发 Visualdl 多层 Block 解析功能** + ++ 修改 visualdl#analyse_pir(),支持获取子 Block 中的全部变量 ++ 修改 visualdl#analyse_pir(),支持获取子 Block 中的全部算子 ++ 增加控制流算子识别功能 ++ 完善模型分析代码,实现 visualdl 对 pir 中 yield 等新算子的表示 ++ 完善模型分析代码,实现多层 Block 嵌套模型的全部变量和算子解析 ++ 完善模型分析代码,实现 Block 内外算子参数传递关系的正确表示 + +**3. 完善 visualdl 对控制流算子的可视化** + ++ 将控制流算子构建为 visualdl 中的 layer,可以收缩或展开表示 + +#### 8月12日-8月25日(解决不足2、3) +**1. 完成 visualdl 可视化控制流算子的功能开发** + ++ 针对 ifop 和 whileop 算子开发特定的解析方法,在前端表现为 layer ++ 完善对控制流算子传入变量和传出变量的可视化处理,控制流算子内部所有算子接收外部传入的变量均传入控制流算子,控制流算子传出的变量在内部构建一个抽象 output 算子接收 yield 算子的输出,并将 output 作为展开控制流算子后代表控制流算子的输出 ++ 提升控制流算子 layer 的可视化美观程度 + +**2. 初步实现 json 格式存储的 paddle 模型的计算图可视化** + ++ 利用 save load 实现提取 json 格式模型的计算图 ++ 对接接口,初步实现 json 格式模型的计算图的可视化,目前只能全部展开 + +**3. 完成获取PIR program边信息功能** + +**4. 修复已知 Bug** + +#### 8月26日-9月8日 +**1. 继续完善 visualdl 算子可视化功能** + ++ 修改 visualdl 后端,提供每个算子的具体类型以优化前端可视化效果 + +**2. 编写功能文档** + ++ 编写项目功能文档,并记录开发遇到的问题以及目前的解决方案 + +**3. 编写测试用例** + ++ 编写静态图、动态图、控制流结构,多层控制流结构等多个测试用例和测试脚本,并记录测试结果 + +#### 9月9日-9月30日(解决不足4) +**1. 修改动态图更换模型接口支持json格式模型** + ++ 修改 visualdl 前端,支持网页上传json格式模型 ++ 修改 visualdl 后端,支持json格式模型的输入和数据解析 + +**2. 完成结项报告** + +**3. 提交PR** + +### 核心功能描述 +#### VisualDL支持PIR 控制流算子可视化 在PIR中,控制流算子都拥有子block,在子block中存放分支或者循环体包含的算子信息,下面为一个简单的包含ifop的模型IR ```plain @@ -30,10 +149,20 @@ } ``` -其中line8-12中的四个算子在子block中,由于现有方法只遍历顶层block的算子,忽略了子block,所以不支持控制流算子可视化。为了解决这个问题,我们首先从子block中获取算子和变量信息,增加`get_sub_var`和`get_sub_ops`函数用于提取子block内的算子和变量,二者均为递归函数进而处理多层block嵌套情况。在遍历顶层block时遇到控制流算子会调用这两个函数,判断控制流算子的代码为: +其中line8-12中的四个算子在子block中,由于现有方法只遍历顶层block的算子,忽略了子block,所以不支持控制流算子可视化。为了解决这个问题,我们首先从子block中获取算子和变量信息,增加`get_sub_var`和`get_sub_ops`函数用于提取子block内的算子和变量,二者均为递归函数进而处理多层block嵌套情况。在遍历顶层block时遇到控制流算子会调用这两个函数,在这个需求中要重点关注跨block的变量输入输出关系,相关判断代码为: ```plain - +def is_same_block_op(from_node, to_node, all_ops): + if all_ops[to_node]["parent_node"] == '/': + return False + from_ancestors = set() + while all_ops[from_node]["parent_node"] != '/': + from_ancestors.add(all_ops[from_node]["parent_node"]) + from_node = all_ops[from_node]["parent_node"] + if all_ops[to_node]["parent_node"] in from_ancestors: + return False + else: + return True ``` 为了计算图可视化的直观和美观,我们在visualdl中将控制流算子表示成一个可收缩可展开的layer,效果如下: @@ -46,7 +175,80 @@ 其中展开后由layer上方的名称标识为这是一个控制流算子,这里需要注意的是,在此途中pd_op.less_than_0算子计算了ifop的条件值,应该有一条线连接pd_op.less_than_0和pd_op.if_0展开后的layer,但由于前端时基于netron的,目前不支持算子连线到一个layer。 -还需要注意的是,为了使计算图数据流可视化效果更加直观,我们在控制流子block中添加了一个output算子,这是因为在PIR中,控制流子block以辅助算子cf.yield作为结束,这使得展开后控制流的layer没有参数传出,因此我们添加了一个output算子,输入为控制流所有cf.yield,输出为未展开前控制流算子的输出,具体实现代码如下: +#### VisualDL支持获取PIR program边信息 +实现逻辑和旧IR中一致,核心代码如下: + +```plain +# edge info +for var_name in all_vars.keys(): + construct_edges(var_name, all_ops, all_vars, all_edges) + +for src_node, to_node in all_edges.keys(): + all_ops[src_node]['edge_output_nodes'].append(to_node) + all_ops[to_node]['edge_input_nodes'].append(src_node) + all_edges[(src_node, + to_node)]['vars'] = list(all_edges[(src_node, + to_node)]['vars']) + if len(all_edges[(src_node, to_node)]['vars']) > 1: + all_edges[(src_node, to_node)]['label'] = str( + len(all_edges[(src_node, to_node)]['vars'])) + ' tensors' + elif len(all_edges[(src_node, to_node)]['vars']) == 1: + all_edges[(src_node, to_node)]['label'] = str( + all_vars[all_edges[(src_node, to_node)]['vars'][0]]['shape']) +``` + +#### 支持输入静态计算图和动态计算图 +目前的PIR分析是针对于静态计算图的,对于动态计算图将进行动转静和save load得到静态图进行分析,核心代码如下: + +```plain +if isinstance(model, paddle.base.libpaddle.pir.Program): + result = analyse_pir(model) +else: + model = paddle.jit.to_static(model, input_spec) + paddle.jit.save(model, os.path.join(tmp, 'temp')) + model_data = paddle.jit.load(os.path.join(tmp, 'temp')) + result = analyse_pir(model_data.program()) +``` + +#### 支持输入json格式模型 +PIR下模型动转静后存储为json文件,再前端需要能导入json文件进行可视化,目前只能实现在动态图界面导入json文件,核心后端代码为: + +```plain +def set_input_graph(self, content, file_type='pdmodel'): + if isinstance(content, str): + if not is_VDLGraph_file(content): + return + if 'pdmodel' in content: + file_type = 'pdmodel' + elif 'json' in content: + file_type = 'json' + else: + file_type = 'vdlgraph' + content = bfile.BFile(content, 'rb').read() + + if file_type == 'pdmodel': + data = analyse_model(content) + self.graph_buffer['manual_input_model'] = Model(data) + + elif file_type == 'json': + json_object = json.loads(content) + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: + json.dump(json_object, json_file, indent=4) + model_data = load(os.path.join(tmp, 'temp')) + data = analyse_pir(model_data.program()) + self.graph_buffer['manual_input_model'] = Model(data) + + elif file_type == 'vdlgraph': + self.graph_buffer['manual_input_model'] = Model( + json.loads(content.decode())) + + else: + return +``` + +### 遇到的问题及解决方案 +1. 为了使计算图数据流可视化效果更加直观,我们在控制流子block中添加了一个output算子,这是因为在PIR中,控制流子block以辅助算子cf.yield作为结束,这使得展开后控制流的layer没有参数传出,因此我们添加了一个output算子,输入为控制流所有cf.yield,输出为未展开前控制流算子的输出,具体实现代码如下: ```plain def create_control_output_node(all_ops, all_vars, control_node_name): @@ -78,9 +280,7 @@ def create_control_output_node(all_ops, all_vars, control_node_name): return all_ops, all_vars ``` -在实现支持PIR 控制流算子可视化,还有一些trick,记录如下: - -1、对于循环控制流算子(whileop),为了可视化的直观,对于pd_op.increment算子我们增加一条边指向上游算子,代表循环结构的数据流向,效果如下:![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725975662978-9479a84a-6a1d-418c-b97e-d6c35d92677d.png?x-oss-process=image%2Fformat%2Cwebp%2Fresize%2Cw_937%2Climit_0)代码如下: +2. 对于循环控制流算子(whileop),根据中间表示可视化会体现不出循环的那条线 ,不利于数据流的展示,因此我们对于pd_op.increment算子我们增加一条边指向上游算子,代表循环结构的数据流向,效果如下:![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725975662978-9479a84a-6a1d-418c-b97e-d6c35d92677d.png?x-oss-process=image%2Fformat%2Cwebp%2Fresize%2Cw_937%2Climit_0)代码如下: ```plain if op.name() == "pd_op.increment_": @@ -88,47 +288,61 @@ if op.name() == "pd_op.increment_": all_ops[all_vars[input_name]['from_node']]['input_vars'][now_var] = [now_var] ``` -2、对于builtin.parameter算子,默认的persistable属性为True,这会导致前端可视化后这个算子没有输出,故将此类算子persistable属性均设置为False,代码如下: +3. 对于builtin.parameter算子,默认的persistable属性为True,这会导致前端可视化后这个算子没有输出,因此我们将此类算子persistable属性均设置为False,代码如下: ```plain if op.name() == "builtin.parameter": all_vars[var_name]['persistable'] = False ``` -3、visualdl的前端基于netron实现,其对于一些算子类型类如conv2d等会有不同颜色标识,前端根据vdlgraph.log中算子的type字段判断类型,在旧IR中,可以通过op.type()获取正确的算子类型,但是PIR的算子没有type()接口,因此我们从算子名中获取算子类型,后续Paddle更新后可以考虑修改,目前实现代码为:`all_ops[op_name]['type'] = op.name().replace("pd_op.", "")` +4. visualdl的前端基于netron实现,其对于一些算子类型类如conv2d等会有不同颜色标识,前端根据vdlgraph.log中算子的type字段判断类型,在旧IR中,可以通过op.type()获取正确的算子类型,但是PIR的算子没有type()接口,因此我们从算子名中获取算子类型,后续Paddle更新后可以考虑修改,目前实现代码为:`all_ops[op_name]['type'] = op.name().replace("pd_op.", "")` -## VisualDL支持获取PIR program边信息 -实现逻辑和旧IR中一致,代码如下: +5. 目前前端静态页面的上传模型和终端visualdl --model命令不支持可视化json格式的模型,这是因为静态模型可视化是基于netron实现的,在visualdl中,静态模型解析是在前端进行的,利用netron的模型解析实现,目前netron不支持json格式paddle模型的解析,自行实现也十分复杂,这个需求列入TODO。 -```plain -# edge info -for var_name in all_vars.keys(): - construct_edges(var_name, all_ops, all_vars, all_edges) +### 测试用例 +目前实现了五个测试用例,分别为pir_program_test,pir_graph_test,cond_test,while_test,cond_inside_cond_test,分别测试静态图输入,动态图输入,分支结构模型,循环结构模型,分支嵌套结构模型 -for src_node, to_node in all_edges.keys(): - all_ops[src_node]['edge_output_nodes'].append(to_node) - all_ops[to_node]['edge_input_nodes'].append(src_node) - all_edges[(src_node, - to_node)]['vars'] = list(all_edges[(src_node, - to_node)]['vars']) - if len(all_edges[(src_node, to_node)]['vars']) > 1: - all_edges[(src_node, to_node)]['label'] = str( - len(all_edges[(src_node, to_node)]['vars'])) + ' tensors' - elif len(all_edges[(src_node, to_node)]['vars']) == 1: - all_edges[(src_node, to_node)]['label'] = str( - all_vars[all_edges[(src_node, to_node)]['vars'][0]]['shape']) -``` +#### 测试脚本 +1. cd VisualDL +2. export FLAGS_enable_pir_api=1 +3. python demo/components/pir_program_test.py (pir_graph_test,cond_test,while_test,cond_inside_cond_test)输出文件将在VisualDL/log/cond_test路径下 +4. visualdl --logdir ./log/program_test/ --host 0.0.0.0 注意此时在VisualDL目录下 -## add_graph支持输入静态计算图和动态计算图 -目前的PIR分析是针对于静态计算图的,对于动态计算图将进行动转静和save load得到静态图进行分析,代码如下: +#### 测试效果 +运行测例后在[http://0.0.0.0:8040/](http://0.0.0.0:8040/)查看可视化计算图 -```plain -if isinstance(model, paddle.base.libpaddle.pir.Program): - result = analyse_pir(model) -else: - model = paddle.jit.to_static(model, input_spec) - paddle.jit.save(model, os.path.join(tmp, 'temp')) - model_data = paddle.jit.load(os.path.join(tmp, 'temp')) - result = analyse_pir(model_data.program()) -``` +##### pir_program_test(PIR静态计算图可视化) +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996090463-249b4ade-ad21-47fc-8017-dee9ad30f979.png) + +##### pir_graph_test(输入PIR动态图可视化) +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996125143-5c21f177-6fa3-4203-84ea-d1d276323163.png) + +##### cond_test(ifop可视化) +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996043233-fbf7b462-7436-41b4-8d8e-55febb4d10fe.png) + +全展开后 + +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996056427-ca87bc8e-78aa-4bbd-8abb-d9c6669780e9.png) + +##### while_test(whileop可视化) +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996156062-275278ea-900a-44d9-a477-55dac6494456.png) + +全展开后 + +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996166186-f5f350af-90fc-4192-835c-4d7847a9fa43.png) + +##### cond_inside_cond_test(双层ifop嵌套结构可视化) +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996206268-4586af9c-7f68-4b4f-9790-f26db4f1dd58.png) + +展开一层ifop + +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996221339-32a802b7-bad3-4369-9505-9b5b5e08e863.png) + +全展开后 + +![](https://cdn.nlark.com/yuque/0/2024/png/32921027/1725996236240-5b6252b6-acb3-4f4b-9bfa-7aab74ce4b35.png) + +### 后续工作安排 ++ 探索如何从json格式模型中获取layer数据 ++ 优化完善代码 From 7b88975f837f2b8a1444c68edf069f5e583ecb63 Mon Sep 17 00:00:00 2001 From: cse0001 Date: Tue, 24 Sep 2024 20:00:01 -0700 Subject: [PATCH 5/8] [Doc] Add document for VisualDL Adaptation for Paddle PIR --- docs/Paddle PIR Visualization.md | 107 ------------------------------- 1 file changed, 107 deletions(-) diff --git a/docs/Paddle PIR Visualization.md b/docs/Paddle PIR Visualization.md index 62b97bb1f..f3835ccd5 100755 --- a/docs/Paddle PIR Visualization.md +++ b/docs/Paddle PIR Visualization.md @@ -19,114 +19,7 @@ 3. 基于paddle3.x的动转静、save_load功能,修改生成vdlgraph文件的功能的输入接口,支持静态图和动态图的输入。 4. 修改前端动态图更换模型接口,支持*.json文件输入,修改后端所有相关接口和功能代码,分别处理*.pdmodel和*.json两类文件。 -### 时间规划 -#### 7月1日 - 7月25日 -+ 进一步熟悉VisualDL项目源码及其现有功能。理解项目的需求和范围。(5天) -+ 阅读PIR设计文档,总结PIR新特性,设计可视化方案。(20天) - -#### 7月26日 - 8月25日 -+ 基于PIR新特性开发PIR解析功能,获取可视化所需的全部模型数据。(10天) -+ 对接内部接口,修改前端设计,根据可视化方案实现前端对于PIR新特性的可视化。(10天) -+ 开发PIR下控制流的可视化。(10天) - -#### 8月26日 – 9月15日 -+ 对于选定的两个模型进行可视化,验证功能可用性。(10天) -+ 对于所有实现的功能进行测试和调试,并尝试复杂模型结构,验证可靠性。(10天) - -#### 9月16日 – 9月30日 -+ 优化和完善模型可视化实现。 -+ 准备文档和代码示例。 -+ 提交 PR/MR 和项目结项报告。 - ## 项目总结 -### 已完成工作 -下面按照时间线进行总结 - -#### 7月1日-7月14日 -项目预热、和导师沟通、制定实现方案和工作计划 - -#### 7月15日-7月28日 -**1. 配置开发环境,熟悉Paddle和VisualDL** - -+ 本地源码编译安装Paddle和VisualDL的develop分支 - -**2. 熟悉VisualDL进行计算图可视化的设计** - -+ 阅读visualdl#Model()类存储计算图的设计逻辑 -+ 阅读visualdl#analyse_model()函数从**.pdmodel*文件获取计算图数据的逻辑 -+ 阅读visualdl#analyse_pir()函数从PIR表示的模型获取计算图数据的逻辑 - -**3. 熟悉PIR进行计算图存储的设计** - -+ 阅读pir#program.cc, operation.cc, block.cc, region.cc等文件,熟悉PIR进行模型存储的设计 -+ 阅读fluid#pir.cc中和获取Program数据有关接口API - -**4. 完成Visual现有PIR计算图可视化方法适配Paddle3.0版本** - -+ 修改visualdl#analyse_pir()等函数,并成功可视化原有测试demo#pir_translate.py - -**5. 尝试可视化具有分支结构模型** - -+ 实现了具有if分支结构的简单模型并进行可视化 - -#### 7月29日-8月11日(解决不足1) -**1. 测试目前 Visualdl 对于控制结构可视化的支持能力** - -+ 静态方式组建六种包含控制结构的网络,包含循环结构和分支结构,以及多层嵌套控制等 -+ 利用现有 pir 可视化方法进行可视化发现不支持包含多层 Block 网络的可视化 - -**2. 开发 Visualdl 多层 Block 解析功能** - -+ 修改 visualdl#analyse_pir(),支持获取子 Block 中的全部变量 -+ 修改 visualdl#analyse_pir(),支持获取子 Block 中的全部算子 -+ 增加控制流算子识别功能 -+ 完善模型分析代码,实现 visualdl 对 pir 中 yield 等新算子的表示 -+ 完善模型分析代码,实现多层 Block 嵌套模型的全部变量和算子解析 -+ 完善模型分析代码,实现 Block 内外算子参数传递关系的正确表示 - -**3. 完善 visualdl 对控制流算子的可视化** - -+ 将控制流算子构建为 visualdl 中的 layer,可以收缩或展开表示 - -#### 8月12日-8月25日(解决不足2、3) -**1. 完成 visualdl 可视化控制流算子的功能开发** - -+ 针对 ifop 和 whileop 算子开发特定的解析方法,在前端表现为 layer -+ 完善对控制流算子传入变量和传出变量的可视化处理,控制流算子内部所有算子接收外部传入的变量均传入控制流算子,控制流算子传出的变量在内部构建一个抽象 output 算子接收 yield 算子的输出,并将 output 作为展开控制流算子后代表控制流算子的输出 -+ 提升控制流算子 layer 的可视化美观程度 - -**2. 初步实现 json 格式存储的 paddle 模型的计算图可视化** - -+ 利用 save load 实现提取 json 格式模型的计算图 -+ 对接接口,初步实现 json 格式模型的计算图的可视化,目前只能全部展开 - -**3. 完成获取PIR program边信息功能** - -**4. 修复已知 Bug** - -#### 8月26日-9月8日 -**1. 继续完善 visualdl 算子可视化功能** - -+ 修改 visualdl 后端,提供每个算子的具体类型以优化前端可视化效果 - -**2. 编写功能文档** - -+ 编写项目功能文档,并记录开发遇到的问题以及目前的解决方案 - -**3. 编写测试用例** - -+ 编写静态图、动态图、控制流结构,多层控制流结构等多个测试用例和测试脚本,并记录测试结果 - -#### 9月9日-9月30日(解决不足4) -**1. 修改动态图更换模型接口支持json格式模型** - -+ 修改 visualdl 前端,支持网页上传json格式模型 -+ 修改 visualdl 后端,支持json格式模型的输入和数据解析 - -**2. 完成结项报告** - -**3. 提交PR** - ### 核心功能描述 #### VisualDL支持PIR 控制流算子可视化 在PIR中,控制流算子都拥有子block,在子block中存放分支或者循环体包含的算子信息,下面为一个简单的包含ifop的模型IR From d56a6dfdea6cddc3fea1f72766973d23fb3d80a8 Mon Sep 17 00:00:00 2001 From: csexyf <59339190+cse0001@users.noreply.github.com> Date: Mon, 30 Sep 2024 03:13:12 +0800 Subject: [PATCH 6/8] Update Paddle PIR Visualization.md --- docs/Paddle PIR Visualization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Paddle PIR Visualization.md b/docs/Paddle PIR Visualization.md index f3835ccd5..1e7e1a36f 100755 --- a/docs/Paddle PIR Visualization.md +++ b/docs/Paddle PIR Visualization.md @@ -198,7 +198,7 @@ if op.name() == "builtin.parameter": #### 测试脚本 1. cd VisualDL 2. export FLAGS_enable_pir_api=1 -3. python demo/components/pir_program_test.py (pir_graph_test,cond_test,while_test,cond_inside_cond_test)输出文件将在VisualDL/log/cond_test路径下 +3. python demo/components/pir_program_test.py (pir_graph_test,cond_test,while_test,cond_inside_cond_test)输出文件将在VisualDL/log/program_test路径下 4. visualdl --logdir ./log/program_test/ --host 0.0.0.0 注意此时在VisualDL目录下 #### 测试效果 From d0e0f2853a118cc13078e69cdb1a39f2c5917e0a Mon Sep 17 00:00:00 2001 From: cse0001 Date: Sun, 13 Oct 2024 12:16:28 -0700 Subject: [PATCH 7/8] Fix code style --- demo/components/cond_inside_cond_test.py | 5 +- demo/components/cond_test.py | 5 +- demo/components/pir_graph_test.py | 50 ++++--- demo/components/while_test.py | 41 +++--- visualdl/component/graph/graph_component.py | 155 +++++++++----------- 5 files changed, 128 insertions(+), 128 deletions(-) diff --git a/demo/components/cond_inside_cond_test.py b/demo/components/cond_inside_cond_test.py index 93af553ea..557156b6a 100755 --- a/demo/components/cond_inside_cond_test.py +++ b/demo/components/cond_inside_cond_test.py @@ -31,6 +31,7 @@ """ paddle.enable_static() + def less_than_branch(i, a): return paddle.static.nn.cond( i >= 3.0, @@ -38,6 +39,7 @@ def less_than_branch(i, a): lambda: paddle.subtract(a, a), ) + def greater_equal_branch(i, a): return paddle.static.nn.cond( i < 8.0, @@ -45,6 +47,7 @@ def greater_equal_branch(i, a): lambda: paddle.divide(a, a), ) + main_program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(main_program, startup_program): @@ -63,4 +66,4 @@ def greater_equal_branch(i, a): model=main_program, input_spec=[paddle.static.InputSpec([1], dtype='float32')], verbose=True, - is_pir=True) \ No newline at end of file + is_pir=True) diff --git a/demo/components/cond_test.py b/demo/components/cond_test.py index 625d334f1..3c0fe8d9e 100755 --- a/demo/components/cond_test.py +++ b/demo/components/cond_test.py @@ -25,6 +25,8 @@ else: return a - a """ + + class ConditionalLayer(paddle.nn.Layer): def __init__(self): super(ConditionalLayer, self).__init__() @@ -38,6 +40,7 @@ def forward(self, i): ) return out + main_program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(main_program, startup_program): @@ -49,7 +52,7 @@ def forward(self, i): lambda: paddle.add(a, a), lambda: paddle.subtract(a, a), ) - mean = paddle.mean(out) + mean = paddle.mean(out) with LogWriter(logdir="./log/cond_test/") as writer: writer.add_graph( diff --git a/demo/components/pir_graph_test.py b/demo/components/pir_graph_test.py index 47bbf875a..e870b8dcc 100755 --- a/demo/components/pir_graph_test.py +++ b/demo/components/pir_graph_test.py @@ -17,31 +17,33 @@ from paddle import nn from visualdl import LogWriter + class MyNet(nn.Layer): - def __init__(self): - super(MyNet, self).__init__() - self.conv1 = nn.Conv2D( - in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2) - self.max_pool1 = nn.MaxPool2D(kernel_size=2, stride=2) - self.conv2 = nn.Conv2D( - in_channels=20, - out_channels=20, - kernel_size=5, - stride=1, - padding=2) - self.max_pool2 = nn.MaxPool2D(kernel_size=2, stride=2) - self.fc = nn.Linear(in_features=980, out_features=10) + def __init__(self): + super(MyNet, self).__init__() + self.conv1 = nn.Conv2D( + in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2) + self.max_pool1 = nn.MaxPool2D(kernel_size=2, stride=2) + self.conv2 = nn.Conv2D( + in_channels=20, + out_channels=20, + kernel_size=5, + stride=1, + padding=2) + self.max_pool2 = nn.MaxPool2D(kernel_size=2, stride=2) + self.fc = nn.Linear(in_features=980, out_features=10) + + def forward(self, inputs): + x = self.conv1(inputs) + x = F.relu(x) + x = self.max_pool1(x) + x = self.conv2(x) + x = F.relu(x) + x = self.max_pool2(x) + x = paddle.reshape(x, [x.shape[0], -1]) + x = self.fc(x) + return x - def forward(self, inputs): - x = self.conv1(inputs) - x = F.relu(x) - x = self.max_pool1(x) - x = self.conv2(x) - x = F.relu(x) - x = self.max_pool2(x) - x = paddle.reshape(x, [x.shape[0], -1]) - x = self.fc(x) - return x net = MyNet() with LogWriter(logdir="./log/pir_graph_test/") as writer: @@ -49,4 +51,4 @@ def forward(self, inputs): model=net, input_spec=[paddle.static.InputSpec([-1, 1, 28, 28], 'float32')], verbose=True, - is_pir=True) \ No newline at end of file + is_pir=True) diff --git a/demo/components/while_test.py b/demo/components/while_test.py index 2088d446b..a5e0838a3 100755 --- a/demo/components/while_test.py +++ b/demo/components/while_test.py @@ -19,30 +19,31 @@ main_program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(main_program, startup_program): - linear = paddle.nn.Linear(16, 10) - def cond(i, loop_len, x, result): - return i < loop_len + linear = paddle.nn.Linear(16, 10) - def body(i, loop_len, x, result): - result = linear(x) - paddle.increment(i) - return [i, loop_len, x, result] - - x = paddle.static.data(name='x', shape=[32, 16], dtype='float32') - i = paddle.zeros(shape=[1], dtype='int64') - loop_len = paddle.ones(shape=[1], dtype='int64') - result = paddle.zeros( - shape=x.shape[:-1] + linear.weight.shape[-1:], dtype="float32" - ) - result.stop_gradient = False - _, _, _, results = paddle.static.nn.while_loop( - cond, body, [i, loop_len, x, result] - ) - loss = paddle.mean(results) + def cond(i, loop_len, x, result): + return i < loop_len + + def body(i, loop_len, x, result): + result = linear(x) + paddle.increment(i) + return [i, loop_len, x, result] + + x = paddle.static.data(name='x', shape=[32, 16], dtype='float32') + i = paddle.zeros(shape=[1], dtype='int64') + loop_len = paddle.ones(shape=[1], dtype='int64') + result = paddle.zeros( + shape=x.shape[:-1] + linear.weight.shape[-1:], dtype="float32" + ) + result.stop_gradient = False + _, _, _, results = paddle.static.nn.while_loop( + cond, body, [i, loop_len, x, result] + ) + loss = paddle.mean(results) with LogWriter(logdir="./log/while_test/") as writer: writer.add_graph( model=main_program, input_spec=[paddle.static.InputSpec([1], 'float32')], verbose=True, - is_pir=True) \ No newline at end of file + is_pir=True) diff --git a/visualdl/component/graph/graph_component.py b/visualdl/component/graph/graph_component.py index 68061b36b..d432e8fef 100644 --- a/visualdl/component/graph/graph_component.py +++ b/visualdl/component/graph/graph_component.py @@ -412,6 +412,37 @@ def create_control_output_node(all_ops, all_vars, control_node_name): return all_ops, all_vars +def safe_get_shape(op): + try: + return op.result(0).shape + except Exception: + return [] + + +def safe_get_type(op): + try: + return op.result(0).dtype.name + except Exception: + return '' + + +def safe_get_dtype(op): + try: + return op.result(0).dtype.name + except Exception: + return '' + + +def safe_get_persistable(op): + try: + if op.name() == "builtin.parameter": + return False + else: + return op.result(0).persistable + except Exception: + return False + + def get_sub_ops(op, op_name, all_ops, all_vars): for sub_block in op.blocks(): for sub_op in sub_block.ops: @@ -421,12 +452,7 @@ def get_sub_ops(op, op_name, all_ops, all_vars): all_ops[sub_op_name]['name'] = sub_op_name all_ops[sub_op_name]['show_name'] = sub_op_name all_ops[sub_op_name]['type'] = sub_op.name().replace("pd_op.", "") - - try: - all_ops[sub_op_name]['dtype'] = sub_op.result(0).dtype.name - except Exception as e: - all_ops[sub_op_name]['dtype'] = '' - + all_ops[sub_op_name]['dtype'] = safe_get_dtype(sub_op) all_ops[sub_op_name]['input_vars'] = {} all_ops[sub_op_name]['output_vars'] = {} all_ops[sub_op_name]['is_leaf_node'] = True @@ -445,11 +471,8 @@ def get_sub_ops(op, op_name, all_ops, all_vars): attrs = op.results()[0].get_defining_op().attrs() if 'place' in attrs: attrs['place'] = str(attrs['place']) - try: - attrs['dtype'] = op.result(0).dtype.name - except Exception as e: - attrs['dtype'] = '' - except Exception as e: + attrs['dtype'] = safe_get_dtype(op) + except Exception: # attrs = {} pass @@ -496,34 +519,15 @@ def get_sub_var(op, all_vars): attrs = op.results()[0].get_defining_op().attrs() if 'place' in attrs: attrs['place'] = str(attrs['place']) - try: - attrs['dtype'] = op.result(0).dtype.name - except Exception as e: - attrs['dtype'] = '' - except Exception as e: + attrs['dtype'] = safe_get_dtype(op) + except Exception: attrs = {} - try: - all_vars[var_name]['shape'] = sub_op.result(0).shape - except Exception as e: - all_vars[var_name]['shape'] = [] - try: - all_vars[var_name]['type'] = sub_op.result(0).dtype.name - except Exception as e: - all_vars[var_name]['type'] = '' - try: - all_vars[var_name]['dtype'] = sub_op.result(0).dtype.name - except Exception as e: - all_vars[var_name]['dtype'] = '' - + all_vars[var_name]['shape'] = safe_get_shape(sub_op) + all_vars[var_name]['type'] = safe_get_type(sub_op) + all_vars[var_name]['dtype'] = safe_get_dtype(sub_op) all_vars[var_name]['value'] = [] - try: - all_vars[var_name]['persistable'] = sub_op.result(0).persistable - except Exception as e: - all_vars[var_name]['persistable'] = False - - if sub_op.name() == "builtin.parameter": - all_vars[var_name]['persistable'] = False + all_vars[var_name]['persistable'] = safe_get_persistable(sub_op) all_vars[var_name]['attrs'] = attrs all_vars[var_name]['from_node'] = '' all_vars[var_name]['to_nodes'] = [] @@ -532,6 +536,30 @@ def get_sub_var(op, all_vars): return all_vars +def update_node_connections(all_vars, all_ops): + for variable_name in all_vars: + if all_vars[variable_name]['from_node'] == '': + continue + from_node = all_vars[variable_name]['from_node'] + for to_node in all_vars[variable_name]['to_nodes']: + if is_same_block_op(from_node, to_node, all_ops): + all_vars[variable_name]['to_nodes'].append(all_ops[to_node]["parent_node"]) + all_ops[all_ops[to_node]["parent_node"]]['input_vars'][variable_name] = [variable_name] + from_node_name = all_vars[variable_name]['from_node'] + for to_node_name in all_vars[variable_name]['to_nodes']: + if to_node_name != from_node_name: + all_ops[from_node_name]['output_nodes'].append(to_node_name) + all_ops[to_node_name]['input_nodes'].append(from_node_name) + all_vars[variable_name]['to_nodes'] = list(set(all_vars[variable_name]['to_nodes'])) + + for node in all_ops: + if node != '/': + all_ops[node]['input_nodes'] = list(set(all_ops[node]['input_nodes'])) + all_ops[node]['output_nodes'] = list(set(all_ops[node]['output_nodes'])) + + return all_vars, all_ops + + def analyse_pir(program): from paddle.utils.unique_name import generate @@ -559,33 +587,15 @@ def analyse_pir(program): attrs = op.results()[0].get_defining_op().attrs() if 'place' in attrs: attrs['place'] = str(attrs['place']) - try: - attrs['dtype'] = op.result(0).dtype.name - except Exception as e: - attrs['dtype'] = '' - except Exception as e: + attrs['dtype'] = safe_get_dtype(op) + except Exception: pass - try: - all_vars[var_name]['shape'] = op.result(0).shape - except Exception as e: - all_vars[var_name]['shape'] = [] - try: - all_vars[var_name]['type'] = var_name - except Exception as e: - all_vars[var_name]['type'] = '' - try: - all_vars[var_name]['dtype'] = op.result(0).dtype.name - except Exception as e: - all_vars[var_name]['dtype'] = '' - + all_vars[var_name]['shape'] = safe_get_shape(op) + all_vars[var_name]['type'] = safe_get_type(op) + all_vars[var_name]['dtype'] = safe_get_dtype(op) all_vars[var_name]['value'] = [] - try: - all_vars[var_name]['persistable'] = op.result(0).persistable - except Exception as e: - all_vars[var_name]['persistable'] = False - if op.name() == "builtin.parameter": - all_vars[var_name]['persistable'] = False + all_vars[var_name]['persistable'] = safe_get_persistable(op) all_vars[var_name]['attrs'] = attrs all_vars[var_name]['from_node'] = '' all_vars[var_name]['to_nodes'] = [] @@ -603,10 +613,7 @@ def analyse_pir(program): all_ops[op_name]['show_name'] = op_name all_ops[op_name]['type'] = op.name().replace("pd_op.", "") - try: - all_ops[op_name]['dtype'] = op.result(0).dtype.name - except Exception as e: - all_ops[op_name]['dtype'] = '' + all_ops[op_name]['dtype'] = safe_get_dtype(op) all_ops[op_name]['input_vars'] = {} all_ops[op_name]['output_vars'] = {} @@ -637,24 +644,8 @@ def analyse_pir(program): all_ops, all_vars = create_control_output_node(all_ops, all_vars, op_name) all_ops, all_vars = get_sub_ops(op, op_name, all_ops, all_vars) - for variable_name in all_vars: - if all_vars[variable_name]['from_node'] == '': - continue - from_node = all_vars[variable_name]['from_node'] - for to_node in all_vars[variable_name]['to_nodes']: - if is_same_block_op(from_node, to_node, all_ops): - all_vars[variable_name]['to_nodes'].append(all_ops[to_node]["parent_node"]) - all_ops[all_ops[to_node]["parent_node"]]['input_vars'][variable_name] = [variable_name] - from_node_name = all_vars[variable_name]['from_node'] - for to_node_name in all_vars[variable_name]['to_nodes']: - if to_node_name != from_node_name: - all_ops[from_node_name]['output_nodes'].append(to_node_name) - all_ops[to_node_name]['input_nodes'].append(from_node_name) - all_vars[variable_name]['to_nodes'] = list(set(all_vars[variable_name]['to_nodes'])) - for node in all_ops: - if node != '/': - all_ops[node]['input_nodes'] = list(set(all_ops[node]['input_nodes'])) - all_ops[node]['output_nodes'] = list(set(all_ops[node]['output_nodes'])) + # update node connections + all_vars, all_ops = update_node_connections(all_vars, all_ops) # edge info for var_name in all_vars.keys(): From 78f37390c3f421206cacd0b51a466deeca77a950 Mon Sep 17 00:00:00 2001 From: cse0001 Date: Mon, 14 Oct 2024 03:20:29 -0700 Subject: [PATCH 8/8] Fix paddle dependency --- visualdl/component/graph/exporter.py | 8 +++++- visualdl/component/graph/graph_component.py | 18 ++++++++++--- visualdl/reader/graph_reader.py | 29 ++++++++++++++++++++- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/visualdl/component/graph/exporter.py b/visualdl/component/graph/exporter.py index bd6d3e252..c04fdadb0 100644 --- a/visualdl/component/graph/exporter.py +++ b/visualdl/component/graph/exporter.py @@ -15,7 +15,6 @@ import json import os import tempfile -import paddle from .graph_component import analyse_model from .graph_component import analyse_pir @@ -24,6 +23,13 @@ def translate_graph(model, input_spec, verbose=True, **kwargs): + try: + import paddle + except Exception: + print("Paddlepaddle is required to use add_graph interface.\n\ + Please refer to \ + https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html\ + to install paddlepaddle.") is_pir = kwargs.get('is_pir', False) with tempfile.TemporaryDirectory() as tmp: if (not is_pir): diff --git a/visualdl/component/graph/graph_component.py b/visualdl/component/graph/graph_component.py index d432e8fef..775da25c5 100644 --- a/visualdl/component/graph/graph_component.py +++ b/visualdl/component/graph/graph_component.py @@ -16,7 +16,6 @@ import os.path import pathlib import re -import paddle from . import utils @@ -444,9 +443,16 @@ def safe_get_persistable(op): def get_sub_ops(op, op_name, all_ops, all_vars): + try: + from paddle.utils.unique_name import generate + except Exception: + print("Paddlepaddle is required to use add_graph interface.\n\ + Please refer to \ + https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html\ + to install paddlepaddle.") for sub_block in op.blocks(): for sub_op in sub_block.ops: - sub_op_name0 = paddle.utils.unique_name.generate(sub_op.name()) + sub_op_name0 = generate(sub_op.name()) sub_op_name = op_name + '/' + sub_op_name0 all_ops[sub_op_name] = {} all_ops[sub_op_name]['name'] = sub_op_name @@ -561,7 +567,13 @@ def update_node_connections(all_vars, all_ops): def analyse_pir(program): - from paddle.utils.unique_name import generate + try: + from paddle.utils.unique_name import generate + except Exception: + print("Paddlepaddle is required to use add_graph interface.\n\ + Please refer to \ + https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html\ + to install paddlepaddle.") all_ops = {} all_vars = {} diff --git a/visualdl/reader/graph_reader.py b/visualdl/reader/graph_reader.py index e1b1df83c..4e8035bd5 100644 --- a/visualdl/reader/graph_reader.py +++ b/visualdl/reader/graph_reader.py @@ -20,7 +20,6 @@ from visualdl.component.graph import analyse_pir from visualdl.component.graph import Model from visualdl.io import bfile -from paddle.jit import load def is_VDLGraph_file(path): @@ -140,6 +139,13 @@ def get_graph(self, if 'pdmodel' in self.walks[run]: graph_model = Model(analyse_model(data)) elif 'json' in self.walks[run]: + try: + from paddle.jit import load + except Exception: + print("Paddlepaddle is required to load json file.\n\ + Please refer to \ + https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html\ + to install paddlepaddle.") json_object = json.loads(data) with tempfile.TemporaryDirectory() as tmp: with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: @@ -174,6 +180,13 @@ def search_graph_node(self, run, nodeid, keep_state=False, is_node=True): if 'pdmodel' in self.walks[run]: graph_model = Model(analyse_model(data)) elif 'json' in self.walks[run]: + try: + from paddle.jit import load + except Exception: + print("Paddlepaddle is required to load json file.\n\ + Please refer to \ + https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html\ + to install paddlepaddle.") json_object = json.loads(data) with tempfile.TemporaryDirectory() as tmp: with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: @@ -202,6 +215,13 @@ def get_all_nodes(self, run): if 'pdmodel' in self.walks[run]: graph_model = Model(analyse_model(data)) elif 'json' in self.walks[run]: + try: + from paddle.jit import load + except Exception: + print("Paddlepaddle is required to load json file.\n\ + Please refer to \ + https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html\ + to install paddlepaddle.") json_object = json.loads(data) with tempfile.TemporaryDirectory() as tmp: with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: @@ -241,6 +261,13 @@ def set_input_graph(self, content, file_type='pdmodel'): self.graph_buffer['manual_input_model'] = Model(data) elif file_type == 'json': + try: + from paddle.jit import load + except Exception: + print("Paddlepaddle is required to load json file.\n\ + Please refer to \ + https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html\ + to install paddlepaddle.") json_object = json.loads(content) with tempfile.TemporaryDirectory() as tmp: with open(os.path.join(tmp, 'temp.json'), 'w') as json_file: