To: mpi-1sided @ mcs.anl.gov
cc: (bcc: Marc Snir/Watson/IBM Research)
Subject: Reasons for negative votes on 1-sided at Sept. MPI mtg
--0__=6DehArVw5G9dszM6no7TS1NszWfnwk5oRKztvYXw479dmejmDy1wCjg0
A colleague requested that I record the reasons that voted against the
1-sided proposal at the September MPI mtg. For anyone interested, here
is the response I sent him. If I have misunderstood some important
points of the proposal, I hope that someone will feel free to correct me.
-Dave
===========================================================================
Specifically, my vote was yes on the amended section 1.3 (GET, PUT, and
ACCUMULATE), to demonstrate that I was in favor of having 1-sided in MPI
and these functions seemed quite appropriate, and no on everything else.
References herein are to the version of the proposal handed out on the
last day of the September meeting. Marc has evidently tried to publish
another version today (Friday, Sept 13), but I have not been able to find
a readable copy.
In order to be as constructive as possible, I have made specific
suggestions to address most of my objections. In addition, I have
already presented an alternate proposal, as appendix A in the document
disseminated at the last
meeting, and at
http://www.nas.nasa.gov/NAS/Tools/MPI2/
which addresses all of the points I have made here. (That proposal was
voted down in subcommittee within an hour or so of the beginning of the
September meeting, and was not discussed further.)
Specific objections to the proposal voted upon:
(a) Severely restricted:
The point-to-point proposal allows one-sided communications only
between a fixed set of processes that know of each other's existence.
The target must name the origins which may access it, and the origins
must name the target(s) which they will access. In other words, even
the point-to-point operations are essentially collective, but instead
of being collective over all of the processes in a communicator (like
the rest of MPI), they are collective over the groups defined by the
transitive closure of the ranks mentioned in the MPI_RMA_POST (and
MPI_RMA_START or PUT, GET and ACCUMULATE) operations.
In general, it is much more difficult to use these "semi-collective"
point-to-point operations than it is to just define a smaller
communicator and using MPI_RMA_BARRIER. The semi-collective calls
require that the each process call START and COMPLETE or POST and WAIT,
depending upon whether the process will be an origin or target, or all
four if it will be used as both host and target, and these must be
called in the proper order with the proper arguments, or the result is
undefined. Also, the MPI_RMA_BARRIER approach can execute with order
log(n) synchronization messages, where the POST/WAIT/START/COMPLETE
approach can take n*n, in some cases.
********
This specific construct (post/wait/start/complete) is expected to be used
in cases where the communication graph has low degree (very frequent case
in real applications). The communication complexity is n*d, where d is
the average degree. The number of synchronizations is proportional to the
number of data communications; it will be n*n only if any process
communicates with any other.
**********
There is one reason, however, that the RMA_BARRIER approach is worse.
It does not allow the target to hold-off PUTs and GETs until the target
is ready. Other synchronization must be used. This severely restricts
options for efficient implementation (e.g. to buffer PUTs and GETs),
which is present in some cases with the POST/WAIT/START/COMPLETE.
*********
This, I don't understand. A valid implementation of RMA_BARRIER (weak
style) is to allow the call to barrier to proceed, and allow the ensuing
calls to put/get to proceed, but postpone actual RMA data transfers until
the ensuing call to RMA_BARRIER occur. I doubt, however, that
implementors will want to buffer put/get communication. More buffering and
data copying decouples sender code from receiver code, at the expense of
additional communication overhead. My take, for RMA, is that
implementations will move in the direction of less buffering, in
similarity with a shared memory programming paradigm. One main purpose of
RMA communication is to allow a simplified communication protocol, with
less hand-shaking, by providing full information on source and target
buffers at the origin. It is up to the user to use the weakest possible
synchronization (post windows as early as possible, wait as late as
possible), so as to decouple as much as possible progress on one process
from progress on another process. But, the user cannot assume that data
will be buffered, so that a "put" will not complete unless the target
window is available.
**********
So, when the user *does* want to perform collective communication, they
are left to choose between the lesser of two evils.
The only way to perform a truly non-collective one-sided communication
in the proposal is to use the LOCK/UNLOCK model, which is expected to
have very high overhead on some systems. The high overhead is not
related to the fact that it is non-collective, it is related to the
fact that the same constructs also support third-party communication,
which often requires an intermediary demon or signal handler to convey
messages properly.
(b) Unnecessarily complex:
Only three operations in the proposal perform communication: PUT, GET,
and ACCUMULATE. The remaining 9 operations are infrastructure to
support them: i.e. allocate and deallocate memory (MEM_ALLOC and
MEM_FREE) required for some operations, synchronization (RMA_POST,
RMA_WAIT, RMA_START, RMA_COMPLETE, RMA_LOCK, RMA_UNLOCK, and
RMA_BARRIER), and creation and freeing (RMA_INIT and RMA_FREE) of a
brand new object type (MPI_Wins). Even more complexity is introduced
by adding a "flag" argument with three possible values (MPI_STRONG,
MPI_WEAK, and MPI_NOCHECK) to many of these operations. As a result of
this design, the ratio of legal combinations of these operations and
flags to the total number of combinations is relatively small,
providing ample opportunity for users to create erroneous programs with
undefined consequences.
**********
I see no harm in having complexity, as long as users are exposed to this
complexity only when they need it. I expect many (most?) users will use
only init, put, get, barrier -- four calls. The post/wait/start/complete
and malloc calls are for users that want to optimize their code. They
want more control on communication and will use functions that provide
more control.
**************
*I have seen no rationale for an MPI_STRONG flag on RMA_POST or
RMA_START operations. This flag does nothing but delay the operation
containing the flag until some other operation is performed in another
process. In other words, it does nothing but slow down the program,
and possibly keep other statements within the program from executing
for some period of time, but there is never any rationale given for
these delays. If the MPI Forum wants a general synchronization
operation that is faster than sending a 0-byte message, then it should
introduce one, and that operation should be used here by users that
need it.
*********
I have no strong argument for MPI_STRONG. The weak argument is that the
behavior of MPI_STRONG is easier to explain and understand (after all
"delaying some operation until some other operation is performed in
another process" is exactly what synchronization calls are supposed to
do). I think its availability will facilitate the debugging of code. To
the same extent it is a good idea to start with blocking send/receive
calls, and introduce later nonblocking calls, it will be a good idea to
start with strong synchronization, and introduce later weak
synchronization.
*********
Not only is STRONG useless, it makes programs non-portable.
On message-passing systems, it is usually desirable to allow
PUTs, for example, to forward their data to the target as early
as possible, even if the target has not yet executed a POST, as
long as there is buffer space on the target. On shared-memory
systems, it is probably desirable to have the PUT or GET wait
until the POST has occurred. STRONG takes this decision out of
the hands of the implementation. (Of course, a similar argument could
be made against SSEND.)
*If the MPI_STRONG flag is not used on RMA_START, the RMA_START
operation itself is not needed. PUTs and GETs can be restricted from
executing on the target until the target executes an MPI_POST, as is
done when the MPI_WEAK flag is specified. The only remaining use I can
imagine for
RMA_START is that it could specify the MPA_NOCHECK flag, but this
more logically belongs on the 1-sided operations themselves (e.g.
RPUT, RGET, RACCUMULATE) just as for RSEND. The list of ranks
specified on RMA_POST serves no purpose, especially with no MPI_STRONG
flag. (To its credit, the proposal voted upon includes the possibility
of omitting this under "Rationale" at the top of page 18, though it's
proposed alternatives are too extreme.)
*******
I suppose your argument is that the list of flags has no purpose, because
the information is repeated in the put/get calls themselves. This is
correct -- although providing twice the same information is not always
such a bad idea -- in particular, it provides more choices to the
implementer. Our decision was to separate synchronization calls from the
communication calls. The alternative, which you proposed, was to merge
them. To get the same functionality that we have now with
strong/weak/nocheck we would need an additional argument in each put/get
call, or 3 variants of put/get calls (or more, to handle also barrier and
lock/unlock). In any case, we should not mix the two issues: one is the
type of synchronization patterns that we want to support and the other is
whether synchronization calls are separate from communication calls.
*********
*I can also see no justification for having a separate RMA_WAIT
operation, since it plays exactly the role of the end of the RMA_POST
operation. That is, by changing RMA_POST to RMA_IPOST (IRMA_POST?),
RMA_WAIT can be omitted and replaced by an MPI_WAIT on the request
returned from RMA_IPOST, making it consistent with the rest of MPI.
This automatically suggests that a blocking RMA_POST should be
introduced as well, which would combine the RMA_IPOST and MPI_WAIT. I
believe that this would be a useful addition, and not at all confusing.
********
post and wait do not play exactly the same role. The first exposes a
window for external communication and the second hide it. Having separate
functions is clearer, and allows more efficient implementations.
*********
*Assuming that the above modification is made, I can see very little
justification for introducing an entirely new object -- i.e. MPI_Wins
-- since it is a small jump from the above to create a persistent
request for RMA_POST (with, say, MPI_RMA_POST_INIT). If the arguments
from RMA_INIT are added to RMA_POST and RMA_POST_INIT, the persistent
request would provide the same opportunities as RMA_INIT for
distributing the "window" information to the different processes, which
was the initial rationale for RMA_INIT. The only drawback is that
multiple persistent requests created for the same communicator could
cause confusion. This can be easily addressed by adding tags to the
calls. These suggestions are exactly consistent with
the rationale for adding persistent requests and tags to MPI in the
first place.
*****
I am totally lost here. RMA_INIT is likely to be a heavy operation, which
requires each process to register information about windows at all other
processes, information on displacement units, information, perhaps, on the
architecture of the processes; it will be used to select which mechanism
is used for RMA (are the windows in memory that can be directly accessed
by all processes?). It will be used, perhaps, to start daemons or post
handlers on distributed memory systems. I certainly would not want init
to disappear.
Bt the way, the new object MPI_Win was not introduced because it is
necessary to support RMA: the previous draft was using communicators for
that purpose. It was introduced, because most people believed it's a
cleaner design.
*******
(c) Poorly defined:
*The use of MPI_WEAK with the RMA_BARRIER operation is not well defined.
Specifically, the proposal states that
"All operations on (the window) that were started before the barrier
call will be completed at their origin before the barrier call
returns at the origin. They will be completed at their target before
the barrier call
returns at the target."
and
"...it need not act as a true barrier with respect to other operations".
Without loss of generality, suppose that processes A and B are
executing, and A comes to an RMA_BARRIER(MPI_WEAK) before B does.
Suppose that A completes the barrier before B enters it. Then B can
issue a PUT to A before it enters the barrier, meaning that A has
completed the barrier before the PUT is complete on the target (A).
Contradiction. Therefore, no process can exit the barrier before all
processes have entered it, which means that it must act as a true
barrier.
******
You have a valid point here. It is true that no process can leave the
barrier before all processes reached it, otherwise one does not know for
sure that all processes have completed their put/get calls. The
definition, as stated, is correct, but the sentence "it need not act as a
true barrier..." is misleading. We can still make a distinction between
"strong" and "weak", e.g., that "weak" only implies that RMA operations
have completed in the local window when the process exits the barrier,
whereas "strong" also implies that operations started by the local process
has completed remotely. On the other hand, I would not be strongly
opposed to the deletion of the strng/weak/nocheck flag from
MPI_RMA_BARRIER
******
Perhaps I misunderstood, and each RMA_BARRIER(MPI_WEAK) call is to be
treated as a completely independent entity. If so, the name and
association with RMA_BARRIER(MPI_STRONG) is very confusing, and the
statement that "operations will be completed at their target before the
(?) barrier call returns at the target" is poorly defined. (Which
barrier call?)
*Progress (liveness) rules are also another problem point. The question
is whether or not a PUT or GET must eventually complete, whether or not
the target executes an RMA_WAIT (or, identically, completes an
RMA_IPOST). While this may seem like a small technicality, it is in
fact a very important point.
If it is mandated that PUT and GET must complete in this case, then the
implementation becomes nearly as difficult and inefficient in some
cases as for the LOCK/UNLOCK case. In fact, the only thing keeping
users from using RMA_POST to allow third-party communication to the
calling process's address space is a rule that PUTs and GETs cannot
overlap in the target window. Since this rule is basically
unenforceable, it will be overly tempting to use a RMA_POST with no
completion to provide slightly more efficient third-party support than
LOCK/UNLOCK. In fact, since many of the proponents of third-party
communication that I can remember also proposed the absence of locks,
it is possible to remove LOCK and UNLOCK completely, by removing the
unenforceable non-overlap rule and making each PUT and GET logically
atomic on the target. This also solves the very ugly characteristic of
locks that they lock the entire target window, thus serializing *all*
accesses to the target window, even if no accesses overlap.
If it is not mandated that PUT and GET must complete until or unless
RMA_WAIT executes, then this should be clearly stated. MPI-1 made the
mistake of phrasing the progress guarantees so loosely that users could
interpret them one way and implementors could interpret them another
way. This mistake should not be repeated in MPI-2. However, if
progress is not mandated in this case, then the symmetric communication
shown in Figure 1.3 of the proposal is not guaranteed to work unless
(a) PUTs are required to complete on the origin in a bounded time, even
if not on their targets (requiring needless buffering in some cases),
or (b) other operations (like RMA_COMPLETE) are also required to
satisfy incoming PUTs. If b is accepted, then there is very little (if
any) reason not to delete RMA_COMPLETE from the proposal completely,
and simply require RMA_WAIT (i.e. the completion of RMA_IPOST) to
automatically perform an RMA_COMPLETE.
*******
The requirement in MPI2, as in MPI1, is that progress is guaranteed:
nonblocking calls complete within finite time; blocking calls complete
within finite time, once the event they blocked on has occurred. The only
debate is what "finite time" means. My interpretation is that finite time
means that there is a fixed uppoer bound on completion time, that does not
depend on code executed by other processes. Other people interpret it to
mean that it is finite, but no fixed upper bound can be provided that is
independent of the code executed by other processes. In this view, if
process B is caught in an inifinite loop, so that the parallel program may
never terminate, then a nonblocking MPI call at process A may never
complete. My view is that, even in this case, we need to guarantee that
the call at process A complete within finite time, as I want decent
semantics for nonterminating programs, as well.
Now, consider a code such as the one in Figure 1.3 (1.5 in the new text).
Two processes execute a sequence of post, start, put, complete, wait.
Suppose that strong synchronization is used. The post is nonblocking; the
start blocks until the matching start occurs. Once this happened, the
put, complete sequence is enabled, and must complete within finite time;
at this point, the wait of the other process is enabled and must complete
in a finite time. Thus, the code must complete.
This does not require buffering, even in a pure polling implementation,
assuming the weaker interpretation of "finite time". The implementation
must be so that polling goes on while a processes is blocked: Not just
polling for the request matching the blocking call, but polling for any
request where progress can be made. A process will not reach its wait
call, only if it blocks on another call. But, while blocked on that other
call, it will continue poll and satisfy requests where progress can be
made. In particular, it will satisfy the put and/or complete requests of
the other process (the put can be delayed, but must be handled once the
complete request arrives). Thus, the (put,complete) pair will terminate
in finite time, even without buffering, and even in a pure polling
implementation.
Thus, the progress statement is stronger than saying that "put/get may not
complete until a wait is executed on the target window". Such statement
would allow for deadlocks that the progress rule disallows. This is
similar to what happens in MPI: Suppose that process 1 sends two messages
to process 2, using ISend(...,tag1,..., request); Send(...,tag2,...);
Wait(request). process 2 receives these messages out of order, using
Recv(...,tag2,...,request) ; Recv(...,tag1,...); Wait(request). MPI
requires that this program do not deadlock. Deadlock can be avoided in a
pure polling implementation. But deadlock would not be avoided by a
statement saying that "a (nonblocking) send may not complete until a
matching receive occurs". Polling MPI implementations handle this problem
exaclty as I suggest to handle RMA: namely by polling for incoming
messages that match previously posted receives, while blocked in an MPI
call.
The argument for the weaker interpretation of "finite time" is that a
process that is involved in a communication will execute an MPI call
pretty soon, so that, in practice, a pure polling implementation will give
reasonable performance. (In IBM's implementation, pure polling is
supplemented by periodic clock-driven polling. This provides the stronger
progress guarantee. But the clock-driven polling is fairly infrequent; it
is expected that polling during MPI calls or while a process is blocked is
the most frequent progress engine.) On the other hand, it was felt in
MPI1 that requiring that messages are received in the order they are sent
is too strong a restriction. Similarly, I feel it is too restrictive to
say that a put may block until a wait occur on the target window.
To sum up, the current definition of progress is consistent with the
definition of progress in MPI1. The fuziness about "finite time" is that
same fuziness as in MPI1. This fuziness allows for a pure polling
implementation of ALL RMA calls. There are even reasons to believe that
such implementation would have reasonable quality for 2-party
communication (I happen to disagree). On the other hand, a pure polling
implementation of 3-rd party communication would be so obviously of poor
quality, that it makes no sense to even consider it: there is no reason to
believe that a process that is not involved in the communication will
execute an MPI call any time soon. There, we need an asynchronous agent.
********
One possible solution to the progress guarantee problem would be to
provide both forms of RMA_IPOST -- one which guarantees completion of
PUTs and GETs, whether or not the IPOST completes, for those who want
third-party-like communication, and the another which does not, for
those who want efficiency. However, it would be necessary to also
distinguish which sort of IPOST each PUT or GET was targetting, so that
it could also be optimized.
(d) Lacks justification and forethought:
RMA_MEM_ALLOC has been proposed (and widely accepted) to be used on an
optional basis to possibly speed up programs in those cases where
dynamically-allocated memory is natural and communication can be
implemented more efficiently through shared memory. In one case,
however -- i.e. LOCK/UNLOCK -- an amendment was passed that
RMA_MEM_ALLOC'd memory be required, even if it is unnatural for the
caller to use dynamically- allocated memory. Why? Certainly it is
more efficient for some vendors
to make this restriction, but if that rationale is sufficient, then
the restriction should be applied everywhere -- even to
message-passing. The fact is that some vendors can perform third-party
communication efficiently without RMA_MEM_ALLOC'd memory, and forcing
users to use this special memory will make some programs run slower on
those vendors' machines, because the user will be required to allocate
memory and explicitly copy between that memory and their static (or
automatic) data structures.
*******
I voted against the amendment, so that I need not justify it. I agree
with you.
*******
I felt that these problems were substantial enough that I could not vote
for the proposal in clear conscience.
Implementing all of the suggestions here reduces the number of support
calls (excluding PUT, GET, and ACCUMULATE) from 9 to about 4, while also
making them more consistent with the rest of MPI and more understandable
by users. The P/G/O alternate proposal, mentioned at the beginning of
this message, provides a completely consistent and simple solution to all
of these problems, by enacting all of these suggestions and then cleaning
up the loose ends.
*******
The number of functions can be reduced by reducing the number of
synchronization paradigms provided. With the same number of options, we
have roughly the same number of calls (or same number of call options)
with the P/G/O proposal or the current proposal. Again, it is important
not to mix issues: (1) Do we separate synchronization calls from
communication calls; (2) what synchronization and progress semantics we
want to support for RMA; (3) whether we want an initialization call
(init), or want the "initialization" to be dynamically done, as part of
the communication; (4) whether we want to mandate the use of Malloced
memory. The current design reflects decisions made on each of these
issues. A simpler syntax will need to revist some, or all of these
decisions.
********
-Dave
========================================================================
David C. DiNucci | MRJ, Inc., Rsrch Scntst | NASA Ames Rsrch Ctr
dinucci@nas.nasa.gov| NAS (Num. Aerospace Sim.)| M/S T27A-2
(415)604-4430 | Parallel Tools Team | Moffett Field, CA 94035
--0__=6DehArVw5G9dszM6no7TS1NszWfnwk5oRKztvYXw479dmejmDy1wCjg0
Content-type: application/octet-stream;
name="PIC17716.PCX"
Content-transfer-encoding: base64
CgUBCAAAAABoACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAABaQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD1E9sTzRPHE8MTwhP1E9sTzRPHE8MTwhP1E9sTzRPHE8MTwhP1E9sTzRPH
E8MTwhP1E9sTzRPHE8MTwhP1E9sTzRPHE8MTwhP1E9sTzRPHE8MTwhP1E9sTzRPHE8MTwhP1E9sT
zRPHE8MTwhPwEwzIBgzYE8wTxhPDE8IT7hPOBtcTzBPGE8MTE+wTwgbCBwbCEgbCEgbCEsUG1hPL
E8YTwxMT6hMMwgYHwgLCAwISwgfEEsMCwwbVE8sTxRPDExPpE8MGAwcCBwMCwhLDB8ISwgISwgLD
BtUTyhPFE8MTE+gTwgIHA8ICEw4DDgLDE8USwwLCEMIG1BPKE8UTwxMT5xMCAwcDAg4TDgITwgIS
D8ISD8ISBRICEcICwwbUE8oTxRPCExPmEwYCBwMCDgIOwgLDExITEhPCEg8GxgLDBtMMDAfJE8QT
whMT5hMGwwITBgMCDhLFEw8SE8ISBgIDwhIDEsMGB9MDxwwHxRPDExPlEwYHAhESAg8CwhMPwhMP
xBMPxRIQwgIDAgMCBtMDxwPEDAfDE8IT4RMHwwzCBgLCEhMCDxLIE8MSD8MSwwIQAwIDBgfSDMkD
wgPCDAfCExPbEwfGDMIDDAIHERITEhMSwxMPwxMPwxPDEgIDAgMCwwMCBgzREwfHDMYDDMITE9YT
B8UMyAMGB8ICBhLDAsYTEhMSExIPwhIHAgcCAwUQAgYRBgfSE8UTB8QMwgMMwhMT0hMHxAzLA8IM
BsISDxESExITAw4DxBMSExITwxICBwPCAsMDDMIGB9ITyRMHwwzCExPPEwfDDMkDxQwHwhMGBxIT
AhECEwMOAg7DExITDxMPwxIDAgMCBwMCDAYRBgfSE8kTwhPCDMITE8wTB8MMxwPEDMIHxxMGxBLD
Ag4DDgIGwg/IEgIDwgIDAgwCEMIGB9ITyRMHDAcMwhMTyhMHwgzGA8MMwgfMEwYHwhLCEAIOAg4C
DhDDAhIPxhIFAgXDAgUCEQYH0hPHEwfCDAcPDMITE8gTB8IMxQPDDAfQEwbDEhDEAhAOEA4QwgLG
EgcSBhIGBcMCBcIGB9ATB8UMEwfCDA8HDwwHwhMTxhMHwgzEA8MMB9MTBgfCEhADEMICDhAOEMIC
EQIDxxIGBwbCAgUCEQYHyxMHxAwHwhMHEwzCEwcPBw8MB8MTE8UTBwzEA8IMB9YTBsQSEAMCA8UC
EQIDAgPDEgcSBgfCBgUQAhDCBgfGEwfEDAfGE8INEwzCEw8HwgwHwxPCE8QTBwzDA8IMB9gTBgfE
EhACEMYCEQIDAsQSBhLDBsICEALCBgfCEwfDDAfKEwfCDRMHwhPCDAfEE8ITE8MTBwzCA8IMB9oT
DBIHwxLDDBEDxQIDAgPDEgYSBgfCBgIQAhAGDAfCEwzDE8MHyRMHwhPCBxMHxRPDExPDEwzCAwwH
3RMGxxICEQPDAgMCA8MSBhIGBwYMBhACEAIGDMMTDBPCB8YTwwfHEwfGE8MTwhPDEwwDDAfeEwYH
xxICEQPDAgMCwhIGEgYHBgwGEAIQAsIGB8MTDMYTwwfKEwzGE8MTwhPDE8IMB98TDBLCB8USAgMR
xAISB8ISBgcGDAYQBhAGEAYMB8MMB8kTwwfHEwzGE8MTwhPDEwwPwgzfEwYSB8ISB8ISAhECAwID
EgcSBwYHBgwGEAYQxgzDD8IHxRPDB8kTBwzGE8MTwhPDEwzDD8QM3BPCBhIGwxIGAhECAwIHBgcG
yAzJDxMHzRMHwwwHxxPDE8ITwxMHDMYPxwwH1BMGEgYSBhLLDM4PwwwTDMcTwgfEDAfJE8QTwhMT
xBMHwgzLD9sM0w/GDAfDEwzDEwfEDAfLE8YTwxMTxhMHxAztD8gMBgfIE8QMB84TxxPDE8ITyhMH
xwzbD8sMEAUMBcIMwgYH1RPKE8UTwxMT0RMH2wwGEAYQBhACBQwFDAUMBgwHBgfWE8sTxRPDExPu
EwYMBhAGEAIGDAYMwwYH1xPLE8YTwxMT8BPKBgfYE8wTxhPDExP1E9sTzRPHE8MTwhP1E9sTzRPH
E8MTwhMMAAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw//vwoKCkgICA/wAAAP8A//8AAAD/
/wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw//vwoKCkgICA/wAAAP8A//8A
AAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw//vwoKCkgICA/wAAAP8A
//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw//vwoKCkgICA/wAA
AP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw//vwoKCkgICA
/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw//vwoKCk
gICA/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw//vw
oKCkgICA/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzApsrw
//vwoKCkgICA/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDAwNzA
psrw//vwoKCkgICA/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICAwMDA
wNzApsrw//vwoKCkgICA/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACAAICA
wMDAwNzApsrw//vwoKCkgICA/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACAgACA
AICAwMDAwNzApsrw//vwoKCkgICA/wAAAP8A//8AAAD//wD/AP//////AAAAgAAAAIAAgIAAAACA
gACA//vwoKCkgICA/wAAAP8A//8AAAD//wD/AP//////
--0__=6DehArVw5G9dszM6no7TS1NszWfnwk5oRKztvYXw479dmejmDy1wCjg0--