Pysyft: Protocol.plans should be a dictionary, not a list of tuples

Created on 19 Oct 2019  路  14Comments  路  Source: OpenMined/PySyft

At present, Protocol.plans is a list of tuples, where each tuple is a (worker_name_string, plan). Instead, Protocol.plans should be a dictionary, where each key is the worker_name_string and each value is the plan.

Good first issue Type

Most helpful comment

Chatting with @Jasopaum offline - I put together an example for how we might initialize this and a use case.

Story: Let's say that cute little Prince Charles wants to know whether to ask his mommy or his daddy for money to go to the store (but not both... for some reason). So, he wants to execute a protocol wherein he compares how much gold and silver his mom and dad have, and then whichever is greater he will ask.

The resulting sum between his gold and silver and his richer parent's gold and silver will be his budget for the store! This will be the final output of the protocol.

# Role is functionally equivalent to VirtualWorker but it's purpose is to define what someone WILL do when they fill this role.
# This might allow us to skip calling "hook.local_worker.is_client_worker" which would be nice.

king = sy.Role(id="king") 
queen = sy.Role(id="queen")
prince = sy.Role(id="prince")

@sy.func2plan(args_shape=[(1,)], worker=king)
def add_kings_gold(kings_gold, kings_silver):
    ptr = (kings_gold + kings_silver).send(prince, id="kings bank balance", garbage_collect=False)
    return True

@sy.func2plan(args_shape=[(1,)], worker=queen)
def queen_ops(queens_gold, queens_silver):
    (queens_gold + queens_silver).send(prince, id="queens bank balance", garbage_collect=False)
    return True

@sy.func2plan(args_shape=[(1,)], worker=prince)
def prince_ops(princes_gold, princes_silver):

    king_balance = sy.BlockForTensor("kings bank balance")
    queen_balance = sy.BlockForTensor("queens bank balance")

    if(king_balance > queen_balance):
        return king_balance + princes_gold + princes_silver
    else:
        return queen_balance + princes_gold + princes_silver

protocol = sy.Protocol(king_ops, queen_ops, prince_ops)

george = sy.VirtualWorker(hook, id="King George II")
elizabeth = sy.VirtualWorker(hook, id="Queen Elizabeth III")
charlie = sy.VirtualWorker(hook, id="Prince Charlie")

protocol.deploy(king=george, queen=elizabeth, prince=charlie)

All 14 comments

Hi Andrew, You can assign this to me. Thanks.

Hey!
I think Protocol.plans was not a dict in the first place because:

  • we can have several plans for a same worker during a protocol
  • we need the order in which the plans have to be executed

Maybe @LaRiffle can confirm or correct me?

Exactly!
this notion of order and the ability to have several plans for the same worker are critical

Hi Jasopaum and Theo,
Thanks!
Yes - as I was exploring the code in the fork that I have - I noticed that : it cannot be dictionary if we want to have the flexibility of several plans for same worker. And that is also evident (in a very concrete manner) from the line me.request_search(...) and onward in test_protocol.py

@Jasopaum - I disagree that we should be specifying the order in which the plans should execute. Instead, the plans should execute as fast as possible in an asynchronous manner, only pausing to wait for a tensor from another plan if necessary. I do support the idea that one worker could have multiple plans, but this would lead me to believe that it should be a dictionary of lists.

The main value proposition of a Protocol is to have plans which can execute asynchronously on multiple workers. Otherwise, Protocol is just a plan of plans (which we already can do with normal plans).

I also noticed in our tutorial that we assume that the output of the first plan is the input to the second plan. This seems to deviate from the above intuition as well. Instead, a plan should support the ability to call .send() to a worker and that worker would (presumably) have a plan waiting to execute when a tensor with that specific ID shows up.

Chatting with @Jasopaum offline - I put together an example for how we might initialize this and a use case.

Story: Let's say that cute little Prince Charles wants to know whether to ask his mommy or his daddy for money to go to the store (but not both... for some reason). So, he wants to execute a protocol wherein he compares how much gold and silver his mom and dad have, and then whichever is greater he will ask.

The resulting sum between his gold and silver and his richer parent's gold and silver will be his budget for the store! This will be the final output of the protocol.

# Role is functionally equivalent to VirtualWorker but it's purpose is to define what someone WILL do when they fill this role.
# This might allow us to skip calling "hook.local_worker.is_client_worker" which would be nice.

king = sy.Role(id="king") 
queen = sy.Role(id="queen")
prince = sy.Role(id="prince")

@sy.func2plan(args_shape=[(1,)], worker=king)
def add_kings_gold(kings_gold, kings_silver):
    ptr = (kings_gold + kings_silver).send(prince, id="kings bank balance", garbage_collect=False)
    return True

@sy.func2plan(args_shape=[(1,)], worker=queen)
def queen_ops(queens_gold, queens_silver):
    (queens_gold + queens_silver).send(prince, id="queens bank balance", garbage_collect=False)
    return True

@sy.func2plan(args_shape=[(1,)], worker=prince)
def prince_ops(princes_gold, princes_silver):

    king_balance = sy.BlockForTensor("kings bank balance")
    queen_balance = sy.BlockForTensor("queens bank balance")

    if(king_balance > queen_balance):
        return king_balance + princes_gold + princes_silver
    else:
        return queen_balance + princes_gold + princes_silver

protocol = sy.Protocol(king_ops, queen_ops, prince_ops)

george = sy.VirtualWorker(hook, id="King George II")
elizabeth = sy.VirtualWorker(hook, id="Queen Elizabeth III")
charlie = sy.VirtualWorker(hook, id="Prince Charlie")

protocol.deploy(king=george, queen=elizabeth, prince=charlie)

Alternatively, we could also have the QUEEN role check the final budget of the prince to make sure it isn't too high.

# Role is functionally equivalent to VirtualWorker but it's purpose is to define what someone WILL do when they fill this role.
# This might allow us to skip calling "hook.local_worker.is_client_worker" which would be nice.

king = sy.Role(id="king") 
queen = sy.Role(id="queen")
prince = sy.Role(id="prince")

@sy.func2plan(args_shape=[(1,)], worker=king)
def add_kings_gold(kings_gold, kings_silver):
    ptr = (kings_gold + kings_silver).send(prince, id="kings bank balance", garbage_collect=False)
    return True

@sy.func2plan(args_shape=[(1,)], worker=queen)
def queen_ops(queens_gold, queens_silver):
    (queens_gold + queens_silver).send(prince, id="queens bank balance", garbage_collect=False)
    return True

@sy.func2plan(args_shape=[(1,)], worker=prince)
def prince_ops(princes_gold, princes_silver):

    king_balance = sy.BlockForTensor("kings bank balance")
    queen_balance = sy.BlockForTensor("queens bank balance")

    if(king_balance > queen_balance):
        balance = king_balance + princes_gold + princes_silver
    else:
        balance = queen_balance + princes_gold + princes_silver

    balance.send(queen, id="princes budget", garbage_collect=False)

    return balance

@sy.func2plan(args_shape=[(1,)], worker=queen)
def more_queen_ops(queens_gold, queens_silver):
    budget = sy.BlockForTensor("princes budget")
    if(budget > 10000):
        return Exception("this is way too much money for a field trip")
    else:
        # Prince doesn't have too much money

protocol = sy.Protocol(king_ops, queen_ops, prince_ops, more_queen_ops)

george = sy.VirtualWorker(hook, id="King George II")
elizabeth = sy.VirtualWorker(hook, id="Queen Elizabeth III")
charlie = sy.VirtualWorker(hook, id="Prince Charlie")

protocol.deploy(king=geroge, queen=elizabeth, prince=charlie)

Note that this doesn't require editing the protocol.deploy method at all.

Looks super great, just a few notes:

  • protocol = sy.Protocol(king_ops, queen_ops, prince_ops, more_queen_ops) should be changed to specify which Role owns which plans
  • we can specify a structure of graph for plans, which is in terms of dependency, while having an asynchronous execution
  • What exactly is sy.BlockForTensor vs sy.PromiseTensor?
  • I don't think you have to - since when I defined the plan I specified which Role owned the plan.
@sy.func2plan(args_shape=[(1,)], worker=king)
def add_kings_gold(kings_gold, kings_silver):
    ptr = (kings_gold + kings_silver).send(prince, id="kings bank balance", garbage_collect=False)
    return True
  • i think this already does that via .send() and BlockForTensor, although @Jasopaum and I figured out a way to do the same thing without having to specify BlockForTensor explicitly (just make them arguments and pass in PointerTensors to them and it'll happen automatically)

  • @Jasopaum and I had a massive discussion about this today. The three of us need to get on a call about this. It's no different. sy.BlockForTensor is a bad idea.

I agree with all what's you said 鈽濓笍

Is this issue still open? Can I take it up?

Protocol design will change a lot in the few days/weeks so I don't think it's worth working on this issue right now!

Okay, thanks

@Kritikalcoder Ping me and/or @Jasopaum on Slack if you want to work on Plans/Protocols though!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MetaT1an picture MetaT1an  路  3Comments

LaRiffle picture LaRiffle  路  3Comments

aristizabal95 picture aristizabal95  路  3Comments

gmuraru picture gmuraru  路  4Comments

swaroopch picture swaroopch  路  4Comments