def test_shapes(shape1: List[int], shape2: List[int], expected: List[int], dtype: str): """Test the shapes and np broadcasting. Don't really need to test the broadcasting as that is tested at C++ level. But try a few cases to be sure binding works correctly. Args: shape1 (List[int]): First tensor shape shape2 (List[int]): Second Tensor Shape expected (List[int]): Expected shape dtype (Str): Popart data type to use """ ir, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier("ai.onnx", "Identity", 1, num_inputs, 1) op = _ir.Op(opid, settings) shape = op.prettyNpOut(shape1, shape2) assert shape == list(expected) t1 = _ir.TensorInfo(dtype, shape1) t2 = _ir.TensorInfo(dtype, shape2) shape = op.prettyNpOut(t1, t2) assert shape == _ir.TensorInfo(dtype, expected)
def test_accumulate_op(connected: bool) -> None: """Test the Accumulate Op. Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() main = graphs[0] num_inputs = _ir.NumInputs(2, 2) weight = add_random_tensor("weight", _ir.TensorType.Variable, [4], main) grad = add_actgrad_tensor("grad", [4], main) out = add_actgrad_tensor("updated_weight", [4], main) opid = _ir.OperatorIdentifier("ai.graphcore", "Accumulate", 1, num_inputs, 1) settings = _ir.Settings(main, "accumulate") if connected: ins: Dict[int, str] = {0: weight.id, 1: grad.id} outs: Dict[int, str] = {0: out.id} op = main.createConnectedOp_AccumulateOp(ins, outs, _ir.AccumulationType.Add, _ir.OptimizerValue(0.5), settings=settings) return op = main.createOp_AccumulateOp(_ir.AccumulationType.Add, _ir.OptimizerValue(0.5), settings=settings) op.connectInTensor(0, weight.id) op.connectInTensor(1, grad.id) op.connectOutTensor(0, out.id) op.setup()
def test_tiedgather_op(connected: bool) -> None: """Test the Tied Gather Op. Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() main = graphs[0] num_inputs = _ir.NumInputs(2, 2) in0 = add_actgrad_tensor("in0", [8], main, _ir.DataType.INT32) indices = add_random_tensor("indices", _ir.TensorType.Variable, [16, 4], main) out0 = add_actgrad_tensor("out0", [8, 4], main) settings = _ir.Settings(main, "tiedgather") if connected: ins: Dict[int, str] = {0: in0.id, 1: indices.id} outs: Dict[int, str] = {0: out0.id} op = main.createConnectedOp_TiedGatherOp( ins, outs, axis_=0, available_memory_proportion_=_ir.OptionalFloat(0.4), settings=settings) op.setup() return op = main.createOp_TiedGatherOp( axis_=0, available_memory_proportion_=_ir.OptionalFloat(0.4), settings=settings) op.connectInTensor(0, in0.id) op.connectInTensor(1, indices.id) op.connectOutTensor(0, out0.id) op.setup()
def test_ipu_copy_op(source: int, destination: int, connected: bool) -> None: """Test the ipu copy op Args: source (int): Source IPU destination (int): Destination IPU connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() g = graphs[0] in0 = add_actgrad_tensor("in0", [1, 2, 3], g) opid = _ir.OperatorIdentifier("ai.graphcore", "IpuCopy", 1, _ir.NumInputs(0, 0), 1) settings = _ir.Settings(g, "new_settings") if connected: op = g.createConnectedOp_IpuCopyOp({0: in0.id}, {0: "outId"}, opid, source, destination, settings) op.setup() else: op = g.createOp_IpuCopyOp(opid, destination, settings) op.connectInTensor(0, in0.id, source) op.createAndConnectOutTensor(0, "outId") op.setup() assert op.inTensor(0) == in0 assert op.hasInput(0) assert op.hasOutput(0) assert op.outId(0) == "outId" assert op.getDestIpu() == destination assert op.getSourceIpu() == source assert op.getSourceIpu("in0") == source assert op.getMinSourceIpu() == source assert op.getMaxSourceIpu() == source
def test_accumulate_zero_op(connected: bool) -> None: """Test the AccumulatorZeroOp. Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() main = graphs[0] num_inputs = _ir.NumInputs(2, 2) input_ = add_actgrad_tensor("input", [4], main) updater = add_actgrad_tensor("updater", [4], main) factor = add_actgrad_tensor("factor", [4], main) out = add_actgrad_tensor("updated_weight", [4], main) opid = _ir.OperatorIdentifier("ai.graphcore", "AccumulatorZeroOp", 1, num_inputs, 1) settings = _ir.Settings(main, "AccumulatorZeroOp") if connected: ins: Dict[int, str] = {0: input_.id, 1: updater.id, 2: factor.id} outs: Dict[int, str] = {0: out.id} op = main.createConnectedOp_AccumulatorZeroOp(ins, outs, settings=settings) return op = main.createOp_AccumulatorZeroOp(settings=settings) op.connectInTensor(0, input_.id) op.connectInTensor(1, updater.id) op.connectInTensor(2, factor.id) op.connectOutTensor(0, out.id) op.setup()
def test_init_op(init_type: "_ir.InitType", connected: bool): """Test the special case of the init op Args: init_type (_ir.InitType): The initialisation type to use (zero/no init) connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() g = graphs[0] out0 = add_actgrad_tensor("out0", [1, 2, 3], g) opid = _ir.OperatorIdentifier("ai.onnx", "Init", 1, _ir.NumInputs(0, 0), 1) settings = _ir.Settings(g, "new_settings") if connected: op = g.createConnectedOp_InitOp({}, {0: out0.id}, opid, out0.info, out0.tensorType(), init_type, settings, 0) else: op = g.createOp_InitOp(opid, out0.info, out0.tensorType(), init_type, settings, 0) op.connectOutTensor(0, out0.id) op.setup() assert not op.hasInput(0) assert op.outTensor(0) == out0 assert op.hasOutput(0) assert op.outId(0) == out0.id
def test_op_attributes(attribute: str, shorthand: str, input_id: int): """Test the various attributes that can be applied to ops. Args: attribute (str): Name of the attribute shorthand (str): Shorthand of the attribute e.g. VirtualGraphId -> VGraphId input_id (int): Long int for the id to use for the attribute. """ _, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier("ai.onnx", "Identity", 1, num_inputs, 1) op = _ir.Op(opid, settings) getter = getattr(op, "get" + attribute) setter = getattr(op, "set" + attribute) hasser = getattr(op, "has" + attribute) get_optional = getattr(op, "getOptional" + shorthand) assert not hasser() id_ = getattr(_ir, "Optional" + shorthand) # Unset optional setter(id_()) assert get_optional() == id_() with pytest.raises(popart.popart_exception) as e_info: getter() assert (e_info.value.args[0] == f"Cannot return {attribute} for Op") assert not hasser() # Set optional == 0 setter(id_(input_id)) assert getter() == input_id assert hasser() assert get_optional() == id_(input_id)
def test_call_op(connected: bool): """Test the special case of the call op Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir(["sub_graph"]) # main graph and 'sub_graph' main = graphs[0] sub_graph = graphs[1] num_inputs = _ir.NumInputs(1, 1) in0 = add_actgrad_tensor("in0", [1, 2, 3], main) out0 = add_actgrad_tensor("out0", [1, 2, 3], main) sub_graph.addInput("inputA", in0.info) opid = _ir.OperatorIdentifier("ai.graphcore", "Call", 1, num_inputs, 1) settings = _ir.Settings(main, "new_settings") if connected: ins: Dict[int, str] = {0: in0.id} outs: Dict[int, str] = {0: out0.id} op = main.createConnectedOp_CallOp(ins, outs, opid, sub_graph, settings) else: op = main.createOp_CallOp(opid, sub_graph, settings) op.connectInTensor(0, in0.id) op.connectOutTensor(0, out0.id) op.setup() assert op.getCalledGraphs()[0] == sub_graph assert op.getCalledGraphIds()[0] == "sub_graph"
def test_multi_graph(): """Test adding ops to multiple graphs. """ ir, graphs = create_ir(["A", "B"]) g = graphs[0] h = graphs[1] settings_g = _ir.Settings(g, "settings_g") settings_h = _ir.Settings(h, "settings_h") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier("ai.onnx", "Identity", 1, num_inputs, 1) op1 = _ir.Op(opid, settings_g) op2 = _ir.Op(opid, settings_h) assert op1.id == 100 # default Id assert op2.id == 101 # default Id + 1 assert op1.opid == opid == op2.opid assert op1.getGraph() == g assert op2.getGraph() == h
def test_settings(): """Test creating settings """ ir, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") assert settings.name == "new_settings" assert settings.getIr() == ir
def test_remote_load_op(connected: bool, use_offset: bool, inplace: bool) -> None: """Test that the input and output tensors of remote load op are correct. Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> use_offset (bool): Whether or not to specify the optional offset Tensor inplace (bool): Whether or not to use the inplace version """ _, graphs = create_ir() g = graphs[0] t = add_actgrad_tensor("t", [1, 2, 3], g) opid = _ir.OperatorIdentifier("ai.onnx", "Init", 1, _ir.NumInputs(0, 0), 1) settings = _ir.Settings(g, "new_settings") out_id = "out_id" offset = add_actgrad_tensor("offset", [1], g) opCreator = g.createOp_RemoteLoadOp if not inplace else g.createOp_RemoteLoadInplaceOp connectedOpCreator = g.createConnectedOp_RemoteLoadOp if not inplace else g.createConnectedOp_RemoteLoadInplaceOp if use_offset: if connected: op = connectedOpCreator({ 0: t.id, 1: offset.id }, {0: "out_id"}, opid, settings, 1) else: op = opCreator(opid, settings, 1) op.connectInTensor(0, t.id) op.connectInTensor(1, offset.id) op.createAndConnectOutTensor(0, out_id) else: if connected: op = connectedOpCreator({ 0: t.id, }, {0: "out_id"}, opid, settings, 1) else: op = opCreator(opid, settings, 1) op.connectInTensor(0, t.id) op.createAndConnectOutTensor(0, out_id) op.setup() assert op.hasInput(0) assert op.inTensor(0) == t assert op.inId(0) == t.id if use_offset: assert op.hasInput(1) assert op.inTensor(1) == offset assert op.inId(1) == offset.id else: assert not op.hasInput(1) assert op.hasOutput(0) assert op.outId(0) == out_id
def test_sparse_accumulate_op(connected: bool) -> None: """Test the SparseAccumulateOp. Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() main = graphs[0] num_inputs = _ir.NumInputs(2, 2) input_ = add_actgrad_tensor("input", [4], main) updater = add_actgrad_tensor("updater", [4], main) factor = add_actgrad_tensor("factor", [4], main) indices = add_actgrad_tensor("indices", [4], main) original_var = add_random_tensor("original_var", _ir.TensorType.Variable, [4], main) out = add_actgrad_tensor("updated_weight", [4], main) opid = _ir.OperatorIdentifier("ai.graphcore", "SparseAccumulate", 1, num_inputs, 1) settings = _ir.Settings(main, "SparseAccumulate") if connected: ins: Dict[int, str] = { 0: input_.id, 1: updater.id, 2: factor.id, 3: indices.id, 4: original_var.id } outs: Dict[int, str] = {0: out.id} op = main.createConnectedOp_SparseAccumulateOp( ins, outs, _ir.AccumulationType.Add, _ir.OptimizerValue(0.5), 0, settings=settings) return op = main.createOp_SparseAccumulateOp(_ir.AccumulationType.Add, _ir.OptimizerValue(0.5), 0, settings=settings) op.connectInTensor(0, input_.id) op.connectInTensor(1, updater.id) op.connectInTensor(2, factor.id) op.connectInTensor(3, indices.id) op.connectInTensor(4, original_var.id) op.connectOutTensor(0, out.id) op.setup()
def test_graph_in_outs(): """Test default behaviour for no inputs or outputs. """ ir, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier("ai.onnx", "Identity", 1, num_inputs, 1) op = _ir.Op(opid, settings) assert op.hasInput(0) == False assert op.hasOutput(0) == False assert op.optionalInputs() == set() assert op.getInBatchAxis(0) == 0 assert op.getOutBatchAxis(0) == 0
def test_nll_op(reduction: _ir.ReductionType, ignoreIndex: int, connected: bool) -> None: """Test the Nll Op, special case with unusual arguments. Args: reduction (_ir.ReductionType): The reduction type to use ignoreIndex (int): The index to ignore. Note this has to be converted to an _ir.OptionalInt connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() main = graphs[0] num_inputs = _ir.NumInputs(2, 2) in0 = add_actgrad_tensor("in0", [8, 2, 3], main) t_info = _ir.TensorInfo(_ir.DataType.INT32, [8, 2]) main.addActGrad("label") l = main.getTensor("label") l.info = t_info out0 = add_actgrad_tensor("out0", [1, 2, 3], main) opid = _ir.OperatorIdentifier("ai.graphcore", "nll", 1, num_inputs, 1) settings = _ir.Settings(main, "nll") if connected: ins: Dict[int, str] = {0: in0.id, 1: l.id} outs: Dict[int, str] = {0: out0.id} op = main.createConnectedOp_NllOp( ins, outs, ignoreIndex=_ir.OptionalInt(ignoreIndex), # <- Note this reduction=_ir.ReductionType.Mean, inputIsLogProbability=True, opid=opid, settings=settings) return op = main.createOp_NllOp( opid=opid, ignoreIndex=_ir.OptionalInt(ignoreIndex), # <- Note this reduction=_ir.ReductionType.Mean, inputIsLogProbability=True, settings=settings) op.connectInTensor(0, in0.id) op.connectInTensor(1, l.id) op.connectOutTensor(0, out0.id) op.setup()
def test_op_clone(): """Op::Clone is pure virtual, this should throw an error. Derived classes should be able to call without issue. """ ir, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier("ai.onnx", "Identity", 1, num_inputs, 1) op = _ir.Op(opid, settings) with pytest.raises(RuntimeError) as e_info: op2 = op.clone() assert ( e_info.value.args[0] == "RuntimeError: Tried to call pure virtual function \"Op::clone\"")
def test_op_creation(): """Test simple op creation. """ ir, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier("ai.onnx", "Identity", 1, num_inputs, 1) op = _ir.Op(opid, settings) assert op.id == 100 # default Id assert op.opid == opid assert op.opid.domain == "ai.onnx" assert op.opid.type == "Identity" assert op.opid.version == 1 assert op.opid.numOutputs == 1
def test_remote_store_op(connected: bool, use_offset: bool) -> None: """Test that the input and output tensors of remote store op are correct Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> use_offset (bool): Whether or not to specify the optional offset Tensor """ _, graphs = create_ir() g = graphs[0] t = add_actgrad_tensor("t", [1, 2, 3], g) opid = _ir.OperatorIdentifier("ai.onnx", "Init", 1, _ir.NumInputs(0, 0), 1) settings = _ir.Settings(g, "new_settings") offset = add_actgrad_tensor("offset", [1], g) if use_offset: if connected: op = g.createConnectedOp_RemoteStoreOp({ 0: t.id, 1: offset.id }, {}, opid, settings, 1) else: op = g.createOp_RemoteStoreOp(opid, settings, 1) op.connectInTensor(0, t.id) op.connectInTensor(1, offset.id) else: if connected: op = g.createConnectedOp_RemoteStoreOp({ 0: t.id, }, {}, opid, settings, 1) else: op = g.createOp_RemoteStoreOp(opid, settings, 1) op.connectInTensor(0, t.id) op.setup() assert op.hasInput(0) assert op.inTensor(0) == t assert op.inId(0) == t.id if use_offset: assert op.hasInput(1) assert op.inTensor(1) == offset assert op.inId(1) == offset.id else: assert not op.hasInput(1) assert not op.hasOutput(0)
def test_matmul_op(serialise_mode: _ir.op.SerialiseSettingsMode, serialise_factor: int, partials_type: _ir.op.MatMulPartialsType, connected: bool): """Test the bindings for matmul op, a special case op with extra settings. Args: serialise_mode (_ir.op.SerialiseSettingsMode): Serialisation mode (see matmul.hpp) serialise_factor (int): Factor to serialise by. partials_type (_ir.op.MatMulPartialsType): Partials calculation type (FLOAT, HALF) connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() g = graphs[0] in0 = add_actgrad_tensor("in0", [4, 6], g) in1 = add_actgrad_tensor("in1", [6, 12], g) out0 = add_actgrad_tensor("out0", [4, 12], g) opid = _ir.OperatorIdentifier("ai.onnx", "MatMul", 1, _ir.NumInputs(2, 2), 1) serialise_settings = _ir.op.SerialiseSettings() serialise_settings.mode = serialise_mode serialise_settings.factor = serialise_factor optfloat = _ir.OptionalFloat(1.0) settings = _ir.Settings(g, "new_settings") dtype = _ir.OptionalDataType(_ir.DataType.FLOAT) if connected: op = g.createConnectedOp_MatMulOp({ 0: in0.id, 1: in1.id }, {0: out0.id}, opid, settings, optfloat, serialise_settings, dtype, partials_type) return op = g.createOp_MatMulOp(opid, settings, optfloat, serialise_settings, dtype, partials_type) op.connectInTensor(0, in0.id) op.connectInTensor(1, in1.id) op.connectOutTensor(0, out0.id) op.setup() assert isinstance(op, _ir.op.MatMulOp) assert op.inTensor(0) == in0 assert op.inTensor(1) == in1 assert op.outTensor(0) == out0
def test_group_norm_op(connected: bool, num_groups: int) -> None: """Test the Group Norm Op. Args: num_groups (int): The number of groups used by the Op connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() main = graphs[0] num_inputs = _ir.NumInputs(3, 3) in0 = add_actgrad_tensor("in0", [8, 4], main) weight = add_random_tensor("weight", _ir.TensorType.Variable, [4], main) bias = add_random_tensor("bias", _ir.TensorType.Variable, [4], main) out = add_actgrad_tensor("out", [8, 4], main) mean = add_actgrad_tensor("mean", [8 * num_groups], main) invstddev = add_actgrad_tensor("invstddev", [8 * num_groups], main) opid = _ir.OperatorIdentifier("ai.graphcore", "GroupNormalization", 1, num_inputs, 3) settings = _ir.Settings(main, "nll") if connected: ins: Dict[int, str] = {0: in0.id, 1: weight.id, 2: bias.id} outs: Dict[int, str] = {0: out.id, 1: mean.id, 2: invstddev.id} op = main.createConnectedOp_GroupNormOp(ins, outs, opid=opid, num_groups_=num_groups, epsilon_=1e-5, settings=settings) return op = main.createOp_GroupNormOp(opid=opid, num_groups_=num_groups, epsilon_=1e-5, settings=settings) op.connectInTensor(0, in0.id) op.connectInTensor(1, weight.id) op.connectInTensor(2, bias.id) op.connectOutTensor(0, out.id) op.connectOutTensor(1, mean.id) op.connectOutTensor(2, invstddev.id) op.setup()
def test_loop_op(): """Test setting up the loop op. """ _, graphs = create_ir(["sub_graph"]) # main graph and 'sub_graph' main = graphs[0] sub_graph = graphs[1] num_inputs = _ir.NumInputs(2, 2) main.addConstInit("loopcount", _ir.TensorInfo(_ir.DataType.INT64, []), np.array(10)) main.addConstInit("keepgoing", _ir.TensorInfo(_ir.DataType.BOOL, []), np.array(True)) a = add_actgrad_tensor("a", [1, 2, 3], main, _ir.DataType.FLOAT) b = add_actgrad_tensor("b", [1, 2, 3], main, _ir.DataType.FLOAT) sub_graph.addInput("loopcount", _ir.TensorInfo(_ir.DataType.INT64, [])) sub_graph.addInput("keepgoing", _ir.TensorInfo(_ir.DataType.BOOL, [])) sub_graph.addInput("a", a.info) sub_graph.addInput("b", b.info) opid = _ir.OperatorIdentifier("ai.graphcore", "Loop", 1, num_inputs, 1) settings = _ir.Settings(main, "new_settings") add = sub_graph.createConnectedOp_AddOp( { 0: a.id, 1: b.id }, {0: "out"}, _ir.OperatorIdentifier("ai.onnx", "Add", 1, num_inputs, 1), settings, ) sub_graph.markAsOutput(add.outTensor(0).id) sub_graph.markAsOutput("keepgoing") ins: Dict[int, str] = {0: "loopcount", 1: "keepgoing", 2: a.id} outs: Dict[int, str] = {0: add.outTensor(0).id} op = main.createConnectedOp_LoopOp(ins, outs, opid, settings, sub_graph) assert op.getCalledGraphs()[0] == sub_graph assert op.getCalledGraphIds()[0] == "sub_graph"
def create_dummy_op(op_domain: str, op_type: str, op_version: int, num_inputs: int, num_outputs: int) -> _ir.Op: """Create an op with the provided properties. Args: op_domain (str): Op domain op_type (str): Op type name op_version (int): Op version num_inputs (int): Max = min number of outputs num_outputs (int): Number of outputs Returns: _ir.Op: The op in question. """ ir, graphs = create_ir(["graph_123"]) graph = graphs[0] settings = _ir.Settings(graph, "new_settings") num_inputs_obj = _ir.NumInputs(num_inputs, num_inputs) opid = _ir.OperatorIdentifier(op_domain, op_type, op_version, num_inputs_obj, num_outputs) return _ir.Op(opid, settings), ir, graph
def _get_op_settings(self, name: str) -> _ir.Settings: """Return an internal_ir Settings object using any values specified by a context. For example: virtual_graph""" pb_g = self.graph._pb_graph settings = _ir.Settings(pb_g, "/".join((*self.name_scopes, name))) vgid = self.virtual_graph_id if vgid is not None: settings.vgraphId = _ir.OptionalVGraphId(vgid) pstage = self.pipeline_stage if pstage is not None: settings.pipelineStage = _ir.OptionalPipelineStage(pstage) if self.io_tile_set: settings.tileSet = _ir.TileSet.IO if self._debug_info is not None: settings.debugInfoId = self._debug_info.getId() return settings
def create_new_op(inputs: Dict[int, "_ir.Tensor"], outputs: Dict[int, "_ir.Tensor"], op_name: str, g: "_ir.Graph", inplace: bool = False, connected: bool = False, **kwargs): num_inputs = _ir.NumInputs(len(inputs), len(inputs)) opid = _ir.OperatorIdentifier("ai.onnx", op_name, 1, num_inputs, len(outputs)) settings = _ir.Settings(g, "new_settings") if connected: create_new_op_fn = getattr(g, f"createConnectedOp_{op_name}") inp: Dict[int, str] = {} outp: Dict[int, str] = {} for i, t in inputs.items(): inp[i] = t.id for i, t in outputs.items(): outp[i] = t.id # For some reason inplace ops don't need opid if inplace: op = create_new_op_fn(inp, outp, settings=settings, **kwargs) else: op = create_new_op_fn(inp, outp, opid=opid, settings=settings, **kwargs) else: create_new_op_fn = getattr(g, f"createOp_{op_name}") if inplace: op = create_new_op_fn(settings=settings, **kwargs) else: op = create_new_op_fn(opid=opid, settings=settings, **kwargs) for i, t in inputs.items(): op.connectInTensor(i, t.id) for i, t in outputs.items(): op.connectOutTensor(i, t.id) op.setup() return op
def test_scatter_op(connected: bool) -> None: """Test the scatter Op. Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() main = graphs[0] num_inputs = _ir.NumInputs(3, 3) in0 = add_actgrad_tensor("in0", [3, 3], main, _ir.DataType.INT32) indices = add_random_tensor("indices", _ir.TensorType.Variable, [2, 3], main) updates = add_actgrad_tensor("updates", [2, 3], main, _ir.DataType.INT32) out0 = add_actgrad_tensor("out0", [3, 3], main) opid = _ir.OperatorIdentifier("ai.onnx", "Scatter", 11, num_inputs, 1) settings = _ir.Settings(main, "scatter") if connected: ins: Dict[int, str] = {0: in0.id, 1: indices.id, 2: updates.id} outs: Dict[int, str] = {0: out0.id} op = main.createConnectedOp_ScatterOp( ins, outs, axis_=0, opid=opid, available_memory_proportion_=_ir.OptionalFloat(0.4), settings=settings) op.setup() return op = main.createOp_ScatterOp( axis_=0, opid=opid, available_memory_proportion_=_ir.OptionalFloat(0.4), settings=settings) op.connectInTensor(0, in0.id) op.connectInTensor(1, indices.id) op.connectInTensor(2, updates.id) op.connectOutTensor(0, out0.id) op.setup()
def test_bools(): """Test default behaviour of bool returns. """ ir, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier("ai.onnx", "Identity", 1, num_inputs, 1) op = _ir.Op(opid, settings) assert op.isInplaceViewChange() == False assert op.isOutplaceViewChange() == False assert op.isLossOp() == False assert op.isIpuCopyOp() == False assert op.isOptimizerOp() == False assert op.requiresRandomSeed() == False assert op.isOutlineable() assert op.hasSideEffect() == False assert op.isNorm() == False assert op.canBeReplacedByIdentity() == False assert op.copiesOptimizerTensors() == False assert op.inputsUnmodifiable() == False assert op.isElementWiseUnary() == False
def test_adam_updater_op(connected: bool) -> None: """Test the AdamUpdater Op. Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() g = graphs[0] w = add_random_tensor("w", _ir.TensorType.Variable, [0, 1], g) mode = _ir.AdamMode.Adam wd = _ir.OptimizerValue(0.02) b1 = _ir.OptimizerValue(0.9) b2 = _ir.OptimizerValue(0.99) eps = _ir.OptimizerValue(0.4) m = add_random_tensor("m", _ir.TensorType.Variable, [4, 1], g) v = add_random_tensor("v", _ir.TensorType.Variable, [2, 1], g) t = add_random_tensor("t", _ir.TensorType.Variable, [1], g) out = add_actgrad_tensor("out", [1, 4], g) ins = {0: w.id, 1: m.id, 2: v.id, 3: t.id} outs: Dict[int, str] = {0: out.id} settings = _ir.Settings(g, "new_settings") if connected: op = g.createConnectedOp_AdamUpdaterOp(ins, outs, mode, wd, b1, b2, eps, settings) op.setup() return op = g.createOp_AdamUpdaterOp(mode, wd, b1, b2, eps, settings) op.connectInTensor(0, w.id) op.connectInTensor(1, m.id) op.connectInTensor(2, v.id) op.connectInTensor(3, t.id) op.connectOutTensor(0, out.id) op.setup()
def test_string_methods(op_name: str, domain: str, op_type: str, op_num: int, op_version: int): """Test various string methods (name, id etc) Args: op_name (str): Name for the op domain (str): Domain e.g. ai.onnx op_type (str): Op type op_num (int): Op number to test against (default 100) op_version (int): Op version """ ir, graphs = create_ir(["A"]) g = graphs[0] settings = _ir.Settings(g, "new_settings") num_inputs = _ir.NumInputs(1, 1) opid = _ir.OperatorIdentifier(domain, op_type, op_version, num_inputs, 1) op = _ir.Op(opid, settings) op.setName(op_name) assert op.getName() == op_name assert op.name() == op_name assert op.str() == f"{op_num} ({domain}.{op_type}:{op_version})" assert op.debugName( ) == f"Op({op_name} ({domain}.{op_type}:{op_version}), inputs=[], outputs=[])"
def test_host_store_op(connected: bool) -> None: """Test the host store op Args: connected (bool): Whether to use the createConnected<opname> function or just create<opname> """ _, graphs = create_ir() g = graphs[0] in0 = add_actgrad_tensor("out0", [1, 2, 3], g) opid = _ir.OperatorIdentifier("ai.onnx", "Init", 1, _ir.NumInputs(0, 0), 1) settings = _ir.Settings(g, "new_settings") if connected: op = g.createConnectedOp_HostStoreOp({0: in0.id}, {}, opid, settings, "streamTensor") else: op = g.createOp_HostStoreOp(opid, settings, "streamTensor") op.connectInTensor(0, in0.id) op.setup() assert op.inTensor(0) == in0 assert op.hasInput(0) assert not op.hasOutput(0) assert op.inId(0) == in0.id
def make_sub_graph(ir: _ir.Ir, ins: Dict[int, _ir.TensorInfo]) -> _ir.Graph: """ Makes the following subgraph, with len(ins) inputs. input0 input1 input2 ... input n │ │ │ │ │ │ │ │ │ │ │ │ └─►add ◄┘ │ │ │ │ │ └──────►add◄┘ │ │ │ │ │ │ │ └────►add ... ▼ add │ ▼ softmax │ ▼ out Args: ir (_ir.Ir): The ir to add the subgraph to ins (Dict[int, _ir.TensorInfo]): The map of in indices to tensorinfos. Returns: _ir.Graph: The subgraph in question. """ g = ir.createGraph(_ir.GraphId("fwd")) for i, tinfo in ins.items(): g.addInput(_ir.addScope(g, f"in{i}"), tinfo) inputs = g.getInputIds() t = g.getTensor(inputs[0]) for i in range(1, len(ins)): settings = _ir.Settings(g, f"add{i}") opid = _ir.OperatorIdentifier("ai.onnx", f"Add{i}", 1, _ir.NumInputs(2, 2), 1) add = g.createConnectedOp_AddOp({ 0: t.id, 1: inputs[i] }, {0: _ir.addScope(g, f"add{i}")}, opid, settings) t = add.outTensor(0) settings = _ir.Settings(g, "softmax0") opid = _ir.OperatorIdentifier("ai.onnx", "SoftMax", 1, _ir.NumInputs(1, 1), 1) sm = g.createConnectedOp_SoftmaxOp({0: t.id}, {0: _ir.addScope(g, "sm0")}, opid=opid, axis_=0, settings=settings) g.markAsOutput(sm.outTensor(0).id) return g
def make_main_graph( num_inputs: int = 2 ) -> Tuple[_ir.Ir, List[_ir.Tensor], List[_ir.Tensor]]: """ Creates the following graph, with num_inputs inputs, alternating data inputs and variable inputs. Init (act) Init (var) Init (act) Init (var) │ │ │ │ ▼ ▼ ▼ ▼ Hostload Hostload Hostload Hostload etc.. │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ┌────────────────────────────────────┐ │ Call │ │ │ └──────────────────┬─────────────────┘ │ │ ▼ HostStore Args: num_inputs (int, optional): Number of main graph inputs. Defaults to 2. Returns: Tuple[_ir.Ir, List[_ir.Tensor], List[_ir.Tensor]]: The ir, The subgraph output tensors, and the variable tensors """ ir, _ = create_ir() main = ir.getMainGraph() t_info = _ir.TensorInfo(_ir.DataType.FLOAT, [1, 2, 3]) inits: Dict[int, _ir.Op] = dict() hostloads: Dict[int, _ir.Op] = dict() inputs = {i: t_info for i in range(num_inputs)} for i in range(len(inputs)): # init i opid = _ir.OperatorIdentifier("ai.onnx", f"Init{i}", 1, _ir.NumInputs(0, 0), 1) actgrads = [] vars_ = [] settings = _ir.Settings(main, f"input{i}") if i % 2 == 0: ttype = _ir.TensorType.ActGrad inits[i] = main.createConnectedOp_InitOp({}, {0: f"init{i}"}, opid, t_info, ttype, _ir.InitType.Zero, settings, 0) actgrads.append(inits[i]) else: ttype = _ir.TensorType.Variable inits[i] = main.createConnectedOp_InitOp({}, {0: f"init{i}"}, opid, t_info, ttype, _ir.InitType.Zero, settings, 0) vars_.append(inits[i].outTensor(0)) # hostload i opid = _ir.OperatorIdentifier("ai.onnx", f"HostLoad{i}", 1, _ir.NumInputs(1, 1), 1) hostloads[i] = main.createConnectedOp_HostLoadOp( {0: inits[i].outTensor(0).id}, {0: f"hostload{i}"}, opid, settings, f"hl{i}") settings = _ir.Settings(main, "call") fwd = make_sub_graph(ir, inputs) fwd_outs = [fwd.getTensor(tid) for tid in fwd.getOutputIds()] opid = _ir.OperatorIdentifier("ai.graphcore", "Call", 1, _ir.NumInputs(num_inputs, num_inputs), 1) call = main.createConnectedOp_CallOp( {i: hostloads[i].outTensor(0).id for i in range(len(hostloads))}, {0: "call0"}, opid, fwd, settings) # host store opid = _ir.OperatorIdentifier("ai.onnx", "HostStore", 1, _ir.NumInputs(1, 1), 1) settings = _ir.Settings(main, "host_store") _ = main.createConnectedOp_HostStoreOp({0: call.outTensor(0).id}, {}, opid, settings, "hs1") deviceInfo = popart.DeviceManager().createIpuModelDevice({}) ir.setDeviceInfo(deviceInfo) ir.setIsPrepared() ir.logIr() print(fwd_outs, vars_) return ir, fwd_outs, vars_