From 90bf20b5cfb2d77c70b4f351bf6bafbbe102f4e5 Mon Sep 17 00:00:00 2001 From: Yiwei Zhang Date: Thu, 7 Sep 2023 15:08:24 -0700 Subject: [PATCH 1/2] adding pictures and update README.md --- README.md | 131 +++++++++++++++++++++++++++++------- imgs/original_structure.png | Bin 0 -> 20270 bytes imgs/proposed_structure.png | Bin 0 -> 32733 bytes 3 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 imgs/original_structure.png create mode 100644 imgs/proposed_structure.png diff --git a/README.md b/README.md index 29262806..5f45179d 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,124 @@ -# Ray Enhancement Proposals -This repo tracks Ray Enhancement Proposals (REP). The REP process is the main way to propose, discuss, and decide on features and other major changes to the Ray project. We'll start with a simple decision-making process (and evolve it over time):. -- First, a draft PR is created against the repo with a draft REP. A senior Ray committer should be designated as the shepherd in the Stewardship section and assigned to the PR. -- The shepherd will review the PR and get it into a polished state for further review by Ray committers. -- Once the PR is reviewable, we will hold a vote on the ``ray-committers`` mailing list. In most cases this should reach consensus; if the result is not unanimous, Eric Liang (@ericl) and Philipp Moritz (@pcmoritz) will be the final deciders on whether to accept the change. -- Based on the results of the vote and possible final decision, the PR will either be merged (REP approved) or closed (REP rejected) with a short summary of the decision. +# Object Store Plugin Manager -You can find a list of PRs for REPs here (both open and merged PRs are available for comment): https://github.com/ray-project/enhancements/pulls?q=is%3Apr - -Each REP should include the following information: ## Summary ### General Motivation -What use cases is this proposal supposed to enhance. If possible, please include details like the environment and scale. -### Should this change be within `ray` or outside? -From a software layering perspective, should this change be part of the main `ray` project, part of an ecosystem project under `ray-project`, or a new ecosystem project? +Ray stores large objects in the Plasma distributed object store. The object store is implemented with shared memory, and multiple workers on the same node can reference the same copy of an object. On the other hand, if the worker’s local shared memory store does not yet contain a copy of the object, the object has to be replicated over with RPCs. We propose object store plugin interfaces to allow ray to support other object stores that may provide additional features without disruption of the overall ray object management. For example, a CXL memory sharing based object store may avoid object replication entirely. -When reviewing the REP, the reviewers and the shepherd should apply the following judgements: -- If an author proposes a change to be within the `ray` repo, the reviewers and the shepherd should assess whether the change can be layered on top of `ray` instead. -If so we should try to make the change in a separate repo. -- For a change proposed as an ecosystem project under `ray-project`: the reviewers and the shepherd should make sure that the technical quality -meets the bar of (at least) a good "experimental" or "alpha" feature -- we should be comfortable welcoming Ray users with similar use cases to try this project. -- For a change proposed as a new ecosystem project (outside of `ray-project`): then this REP is just serving as a "request for comments". -We don't need to go through the voting process, since it's not Ray committers' decision to approve the change. +### Should this change be within `ray` or outside? +Yes. The change should be within `ray`. ## Stewardship ### Required Reviewers -The proposal will be open to the public, but please suggest a few experienced Ray contributors in this technical domain whose comments will help this proposal. Ideally, the list should include Ray committers. +@ericl, @scv119 + ### Shepherd of the Proposal (should be a senior committer) -To make the review process more productive, the owner of each proposal should identify a **shepherd** (should be a senior Ray committer). The shepherd is responsible for working with the owner and making sure the proposal is in good shape (with necessary information) before marking it as ready for broader review. ## Design and Architecture -The proposal should include sufficient technical details for reviewers to determine the anticipated benefits and risks. +The design consists of the object store plugin interface and the plugin manager. + +### Plugin Interface + +The plugin provides the functionalities of an object store responsible for storing and retrieving logic. It can be implemented as source code within the Ray repository or a shared library to be loaded dynamically. + +It shall implement classes inherited from `ObjectStoreClientInterface` and `ObjectStoreRunnerInterface`. +`ObjectStoreClientInterface` replaces the current `PlasmaClientInterface`. It keeps all the functions from the current `PlasmaClientInterface` to retain full compatibility along with some extensions. Current `PlasmaClient` shall implement `ObjectStoreClientInterface`. + +New virtual functions of `ObjectStoreClientInterface` : +```c++ +/// Authentication to the object store. +virtual Status Authenticate(const std::string& user, const std::string& passwd) = 0; +virtual Status Authenticate(const std::string& secret) = 0; +/// The object storage optimized memory copy. +virtual void MemoryCopy(void* dest, const void* src, size_t len) = 0; +``` + +`ObjectStoreRunnerInterface` base class has all the functions from the current `PlasmaStoreRunner` to retain full compatibility along with some extensions. Current `PlasmaStoreRunner` shall implement `ObjectStoreRunnerInterface`. + +Updated virtual function of `ObjectStoreRunnerInterface` with additional parameters: +```c++ +// Pass additional startup parameters +virtual void Start(const std::map& params, + ray::SpillObjectsCallback spill_objects_callback, + std::function object_store_full_callback, + ray::AddObjectCallback add_object_callback, + ray::DeleteObjectCallback delete_object_callback) = 0; +``` + +New virtual functions of `ObjectStoreRunnerInterface`: +```c++ +/// The Current total allocated available memory size. +virtual int64_t GetTotalMemorySize() const = 0; + +/// Maximal available memory size. It can be different from the Total memory size if the memory is dynamically expandable. +virtual int64_t GetMaxMemorySize() const = 0; +``` + +The diagram for current plasma object store classes: +![image](https://github.com/yiweizh-memverge/ray_plugin_manager_rep/blob/main/imgs/original_structure.png) + +The diagram for proposed object store plugin classes: +![image](https://github.com/yiweizh-memverge/ray_plugin_manager_rep/blob/main/imgs/proposed_structure.png) + +If the plugin is implemented as a shared library, the implementation should implement an API to create the object store client instance and the runner instance. +```c++ +extern "C" { + ObjectStoreRunnerInterface CreateRunner(void); + ObjectStoreClientInterface CreateClient(void); +} +``` +### Plugin Manager + +The plugin manager shall create the instances that implement `ObjectStoreClientInterface` and `ObjectStoreRunnerInterface` based on specified plugin name if the plugin is implemented within the Ray repository. If a plugin object store is implemented as a POSIX dynamic linking library, the library shall be opened with its full path using `dlopen()`, `dlsym()` shall be used to look up symbols for `CreateRunner()` and `CreateClient()` and to lazy create the instances when actually needed. + +The plugin manager shall provide the following APIs: +```c++ +// Get an instance of the singleton PluginManager +Static PluginManager& GetInstance() + +// Create the object store runner interface with the plugin object store name and configurations +std::unique_ptr + CreateObjectorStoreRunnerInstance(const std::string& plugin_name, + const std::string& plugin_path, + const std::string& plugin_config) + +// Create the object store client interface with the plugin object store name and configurations +std::shared_ptr + CreateObjectorStoreClientInstance(const std::string& plugin_name, + const std::string& plugin_path, + const std::string& plugin_config) +``` + +The plugin object store can be specified during Ray startup. The following CLI parameters, or `ray.init()` options are proposed for the plugin. +```c++ + plugin_name: Optional[str] = ‘default’ + The name of the plugin object store. + plugin_path: Optional[str] = ‘’ + The full path of the library if the plugin is a shared library. + plugin_config: Optional[Dict[str, any]] = None + The objectstore startup configurations as a series of key-value pairs. +``` + +The modified plasma plugin also would change the data path of how `CoreWorkerPlasmaStoreProvider` accesses the plasma storage. If global shared memory is used, it would retrieve from global shared memory, which appears as local memory. +```c++ +CoreWorkerPlasmaStoreProvider::Get(&object_ids, …,results, ...) { + /* check whether global shared Plasma object store is in use. */ + if (store_client_.IsGlobal()) { + std::vector obj_list; + for (const auto& id : object_ids) { + obj_list.emplace_back(id); + } + /*retrieve objects from global shared Plasma object store*/ + return GetIfLocal(obj_list, results); + } + ... +} +``` ## Compatibility, Deprecation, and Migration Plan -An important part of the proposal is to explicitly point out any compability implications of the proposed change. If there is any, we should thouroughly discuss a plan to deprecate existing APIs and migration to the new one(s). +Currently this plugin manager doesn’t support native Windows platform, as Windows platform doesn’t have POSIX dlfcn support, which is used for loading shared libraries. ## Test Plan and Acceptance Criteria -The proposal should discuss how the change will be tested **before** it can be merged or enabled. It should also include other acceptance criteria including documentation and examples. +The plugin manager will be fully unit tested. ## (Optional) Follow-on Work Optionally, the proposal should discuss necessary follow-on work after the change is accepted. diff --git a/imgs/original_structure.png b/imgs/original_structure.png new file mode 100644 index 0000000000000000000000000000000000000000..90a5099520f50479acfd38420dd5ff8e287d4bf0 GIT binary patch literal 20270 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 literal 0 HcmV?d00001 diff --git a/imgs/proposed_structure.png b/imgs/proposed_structure.png new file mode 100644 index 0000000000000000000000000000000000000000..6aea5059d484e91b10b01c70e187bcd8e0f93aba GIT binary patch literal 32733 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 G`u_l5_CKcp literal 0 HcmV?d00001 From 5d6c1a3e03b9d9e1291a3a7cf656c9e1685b1f52 Mon Sep 17 00:00:00 2001 From: leomem Date: Wed, 8 Nov 2023 13:32:39 -0800 Subject: [PATCH 2/2] proposal rewritten in a new file instead of README.md --- README.md | 131 ++++-------------- imgs/original_structure.png | Bin 20270 -> 0 bytes imgs/proposed_structure.png | Bin 32733 -> 0 bytes reps/2023-11-07-object-store-plugin.md | 181 +++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 107 deletions(-) delete mode 100644 imgs/original_structure.png delete mode 100644 imgs/proposed_structure.png create mode 100644 reps/2023-11-07-object-store-plugin.md diff --git a/README.md b/README.md index 5f45179d..29262806 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,41 @@ -# Object Store Plugin Manager +# Ray Enhancement Proposals +This repo tracks Ray Enhancement Proposals (REP). The REP process is the main way to propose, discuss, and decide on features and other major changes to the Ray project. We'll start with a simple decision-making process (and evolve it over time):. +- First, a draft PR is created against the repo with a draft REP. A senior Ray committer should be designated as the shepherd in the Stewardship section and assigned to the PR. +- The shepherd will review the PR and get it into a polished state for further review by Ray committers. +- Once the PR is reviewable, we will hold a vote on the ``ray-committers`` mailing list. In most cases this should reach consensus; if the result is not unanimous, Eric Liang (@ericl) and Philipp Moritz (@pcmoritz) will be the final deciders on whether to accept the change. +- Based on the results of the vote and possible final decision, the PR will either be merged (REP approved) or closed (REP rejected) with a short summary of the decision. +You can find a list of PRs for REPs here (both open and merged PRs are available for comment): https://github.com/ray-project/enhancements/pulls?q=is%3Apr + +Each REP should include the following information: ## Summary ### General Motivation -Ray stores large objects in the Plasma distributed object store. The object store is implemented with shared memory, and multiple workers on the same node can reference the same copy of an object. On the other hand, if the worker’s local shared memory store does not yet contain a copy of the object, the object has to be replicated over with RPCs. We propose object store plugin interfaces to allow ray to support other object stores that may provide additional features without disruption of the overall ray object management. For example, a CXL memory sharing based object store may avoid object replication entirely. - +What use cases is this proposal supposed to enhance. If possible, please include details like the environment and scale. ### Should this change be within `ray` or outside? -Yes. The change should be within `ray`. +From a software layering perspective, should this change be part of the main `ray` project, part of an ecosystem project under `ray-project`, or a new ecosystem project? + +When reviewing the REP, the reviewers and the shepherd should apply the following judgements: +- If an author proposes a change to be within the `ray` repo, the reviewers and the shepherd should assess whether the change can be layered on top of `ray` instead. +If so we should try to make the change in a separate repo. +- For a change proposed as an ecosystem project under `ray-project`: the reviewers and the shepherd should make sure that the technical quality +meets the bar of (at least) a good "experimental" or "alpha" feature -- we should be comfortable welcoming Ray users with similar use cases to try this project. +- For a change proposed as a new ecosystem project (outside of `ray-project`): then this REP is just serving as a "request for comments". +We don't need to go through the voting process, since it's not Ray committers' decision to approve the change. ## Stewardship ### Required Reviewers -@ericl, @scv119 - +The proposal will be open to the public, but please suggest a few experienced Ray contributors in this technical domain whose comments will help this proposal. Ideally, the list should include Ray committers. ### Shepherd of the Proposal (should be a senior committer) +To make the review process more productive, the owner of each proposal should identify a **shepherd** (should be a senior Ray committer). The shepherd is responsible for working with the owner and making sure the proposal is in good shape (with necessary information) before marking it as ready for broader review. ## Design and Architecture -The design consists of the object store plugin interface and the plugin manager. - -### Plugin Interface - -The plugin provides the functionalities of an object store responsible for storing and retrieving logic. It can be implemented as source code within the Ray repository or a shared library to be loaded dynamically. - -It shall implement classes inherited from `ObjectStoreClientInterface` and `ObjectStoreRunnerInterface`. -`ObjectStoreClientInterface` replaces the current `PlasmaClientInterface`. It keeps all the functions from the current `PlasmaClientInterface` to retain full compatibility along with some extensions. Current `PlasmaClient` shall implement `ObjectStoreClientInterface`. - -New virtual functions of `ObjectStoreClientInterface` : -```c++ -/// Authentication to the object store. -virtual Status Authenticate(const std::string& user, const std::string& passwd) = 0; -virtual Status Authenticate(const std::string& secret) = 0; -/// The object storage optimized memory copy. -virtual void MemoryCopy(void* dest, const void* src, size_t len) = 0; -``` - -`ObjectStoreRunnerInterface` base class has all the functions from the current `PlasmaStoreRunner` to retain full compatibility along with some extensions. Current `PlasmaStoreRunner` shall implement `ObjectStoreRunnerInterface`. - -Updated virtual function of `ObjectStoreRunnerInterface` with additional parameters: -```c++ -// Pass additional startup parameters -virtual void Start(const std::map& params, - ray::SpillObjectsCallback spill_objects_callback, - std::function object_store_full_callback, - ray::AddObjectCallback add_object_callback, - ray::DeleteObjectCallback delete_object_callback) = 0; -``` - -New virtual functions of `ObjectStoreRunnerInterface`: -```c++ -/// The Current total allocated available memory size. -virtual int64_t GetTotalMemorySize() const = 0; - -/// Maximal available memory size. It can be different from the Total memory size if the memory is dynamically expandable. -virtual int64_t GetMaxMemorySize() const = 0; -``` - -The diagram for current plasma object store classes: -![image](https://github.com/yiweizh-memverge/ray_plugin_manager_rep/blob/main/imgs/original_structure.png) - -The diagram for proposed object store plugin classes: -![image](https://github.com/yiweizh-memverge/ray_plugin_manager_rep/blob/main/imgs/proposed_structure.png) - -If the plugin is implemented as a shared library, the implementation should implement an API to create the object store client instance and the runner instance. -```c++ -extern "C" { - ObjectStoreRunnerInterface CreateRunner(void); - ObjectStoreClientInterface CreateClient(void); -} -``` -### Plugin Manager - -The plugin manager shall create the instances that implement `ObjectStoreClientInterface` and `ObjectStoreRunnerInterface` based on specified plugin name if the plugin is implemented within the Ray repository. If a plugin object store is implemented as a POSIX dynamic linking library, the library shall be opened with its full path using `dlopen()`, `dlsym()` shall be used to look up symbols for `CreateRunner()` and `CreateClient()` and to lazy create the instances when actually needed. - -The plugin manager shall provide the following APIs: -```c++ -// Get an instance of the singleton PluginManager -Static PluginManager& GetInstance() - -// Create the object store runner interface with the plugin object store name and configurations -std::unique_ptr - CreateObjectorStoreRunnerInstance(const std::string& plugin_name, - const std::string& plugin_path, - const std::string& plugin_config) - -// Create the object store client interface with the plugin object store name and configurations -std::shared_ptr - CreateObjectorStoreClientInstance(const std::string& plugin_name, - const std::string& plugin_path, - const std::string& plugin_config) -``` - -The plugin object store can be specified during Ray startup. The following CLI parameters, or `ray.init()` options are proposed for the plugin. -```c++ - plugin_name: Optional[str] = ‘default’ - The name of the plugin object store. - plugin_path: Optional[str] = ‘’ - The full path of the library if the plugin is a shared library. - plugin_config: Optional[Dict[str, any]] = None - The objectstore startup configurations as a series of key-value pairs. -``` - -The modified plasma plugin also would change the data path of how `CoreWorkerPlasmaStoreProvider` accesses the plasma storage. If global shared memory is used, it would retrieve from global shared memory, which appears as local memory. -```c++ -CoreWorkerPlasmaStoreProvider::Get(&object_ids, …,results, ...) { - /* check whether global shared Plasma object store is in use. */ - if (store_client_.IsGlobal()) { - std::vector obj_list; - for (const auto& id : object_ids) { - obj_list.emplace_back(id); - } - /*retrieve objects from global shared Plasma object store*/ - return GetIfLocal(obj_list, results); - } - ... -} -``` +The proposal should include sufficient technical details for reviewers to determine the anticipated benefits and risks. ## Compatibility, Deprecation, and Migration Plan -Currently this plugin manager doesn’t support native Windows platform, as Windows platform doesn’t have POSIX dlfcn support, which is used for loading shared libraries. +An important part of the proposal is to explicitly point out any compability implications of the proposed change. If there is any, we should thouroughly discuss a plan to deprecate existing APIs and migration to the new one(s). ## Test Plan and Acceptance Criteria -The plugin manager will be fully unit tested. +The proposal should discuss how the change will be tested **before** it can be merged or enabled. It should also include other acceptance criteria including documentation and examples. ## (Optional) Follow-on Work Optionally, the proposal should discuss necessary follow-on work after the change is accepted. diff --git a/imgs/original_structure.png b/imgs/original_structure.png deleted file mode 100644 index 90a5099520f50479acfd38420dd5ff8e287d4bf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20270 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 diff --git a/imgs/proposed_structure.png b/imgs/proposed_structure.png deleted file mode 100644 index 6aea5059d484e91b10b01c70e187bcd8e0f93aba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32733 zcmeFZXH=6-*ESpw6>NYgN(X5wO+sNGjg|BmY%YKON2YkV|IlNu*F<{6_K;+xt#Qo=0|(JH)p>8e#Mw+ z&$TewqFwX+9^p@TxVYGE@wm|KiFSLRe8P+g_ij4TLuGh1LYR(0#P*CGuxC;h8hZd4 z?X`VT$pLxyN;&<;$=KVL;qk}!ur4v5>Wm$CIo0`m9CJ4Lp$d0{JLF`xr^=W#YJJUcd+HI9#?u^BHidWZMDP#SAB0Jk zMjZce$Dr=;B;}(>Mc8im9v@-9F|R*P2rE5Qx%m3n(dC4rvZ&&N;Z5 zmWK&^_)Obh%2hY9@nxWr-)hTK)>6{p>oxxGKaNF3^C#^`2_2D6SEoeDU5m2#8O(8X zPg6_t*bx)^s5(LA@##lb0~MYO?|V9S6uR{OLZ$HJWsJpv7ngs1{Ne%8#~kinyN1}QUqpW>Ld=h)gd+}ijH5Mwu)Te@ z_}nx4n_;D*$@LREr&{IT@Xbb?^nS4fmGzYRI1{sXtoFLV>2bA6CeP2qZ-gY~*)`F+ z@gJVpBpux}9tchT!fveVsPA_o-|aegZvx8%ad2*i#3=v}icuYO$9*fQ4gNbQf%b-tjI z@7K6hdR5odkT0l5m$R;pbcHEiIcF&SwN>`L!jEI&#!maIVnk*~2Eh`&w9+Q@yY39@EbU&K!FczOU#=9y95)UeRtc z1pOuC8*5D1%h3astZH8rJ@*!*2`)0PKJXt^_(NFi;nQy%$Oe|;i)7AY55w0*zFZbN z9PwUw<}&w*i(+37+!4GHF0GFjNq%8{N9cQ0%EeAmyRR}6Oy8f?=xMz@p7V#xTfL7* z)%M+ebL||np-yv#)z3Shj>}v+mf`x1*XP(kSlUIocZ6>qtGm_0n6B}3f4L^VH!8fi z=)UyPwuXj2={)}22A86syxm&UQkKg22xLQ~%E7GhL!P@MenhGqOOL|U9s5CA6bRTw zeC+T=XmopBnK|y(dTNLg2#FKaagJ7Ndi5DMrKS>1DVP0Qo z7cr8QJy9SzFOnwg$0@|Q_p(^5R!iE1!BYvZ%a7il{cinT_Pf{jyEoWp9>kW!M#et7 z@#IEM?Dg!6**BcCzRQ1TyJ{S#^|H4m{tnFhT%vxOp8W;lRSyw|6V&J0rsq!Lk2qXq z7LPcaQ1bYOv8rK}L10E+o?9mQ1Angc^|Xu{y}-g{)yciBLT2a8$TuxdJD)xr*LZr+ z%;@9LjX>QGH}XF+_1tt3@s%%&4|*DuzIi?_bM|Aa`DAvddHxOe>zKUF8_{|9vYT!u zzcJ4<&-T-qi?vQyvl_md`k~V#{e%4nVqSCpfpVj?&cmtOY6M@c8Qz(*z8ls1eM!D@q*?;s+hcE<2~vc?41dFgms)n( zmf9Afi6I#~n!AQRcM9WgJ^ixhLH8b{=qJ7PsL_~{LdBsC?z%nSI?r@^R{!9x_p4_T zg=SnAebHr|VXB90Qfp#rQf-ooM)6RSzFph8#v&nQ7?Z4#<=UONs!y}#COjZ1R3bF*_o5+1vZ!xZ{6o2GJPy5b#^UD3qy?n7npinj9H67(I^ z0HSX{nK0-dEf%%Pdy3ymVfpccr@Ok}etp~dBRnGp`W_ky%@}Vp;m)|BFT>~S*P|@y zpQVwNZ{(k%m|CA|kYbbKfbOgEw9BzAsJ>yRYwLtos;c!a^&dyOd!lEI;6meCBbeUp z+=#4*z6@dpp}S6DxO8A;kM1+hddmit=}U|IR+Hv@8%P-y5&mzfAq&`G{$OvU-=+qi zyJnJh5b39W?Y&z5k&fEo5Fe}$)-YsvCv%6N&P$JAnqsO6b7wM6EN|zZlAUVP$C^^=GfelNzp#>@YFy(ll$G!$JDby!bP@4O`n>(JU^BF%&KaN${vAb}qF7}~%q*7#T1757`{ipW>@2M^4TiRZt zn`3y@;pE06500F_YI^#V;wfL7+0T`uUb#(c@2j%nKG+GtG(%?BSGW#HOc<)DnYf>D z8VmQi5ahktxhj`(-{_!|@Pky7)GpjzFAKQCB6EOLAST#h$9t#y+DQ9kBCaI3)cnrc zJ(`onJt~{!AHIKBs5+|h!HLs)(;hg1c7x8rmxZs=(?+Cwj!f+vl;AN=Qp+~DBX^q1 z*O-!4NtKYA$6e%;(g z2y6ZuTZ2BqYW_i_1Km7uCym!FDOG{oXQiO#B)66R55QU0cR5$sQdZxX0#rklv2omSf39Z z{&VUvZ*EenOoPlNn>07Wfgp-<6Rd3TK)IP4t=g^mFuD3mT#?z47^?5^WM=~Hx%jz z^*6n0Y5@;`yFN2+ds`n9Q6VI+dBgW6?Po0X74$>%9@H@*Ar%p|F8{Nni41&XzQ0kl zrf-={dH%Rh!wlT%LT8Fvn+9SlWuZ!R8iP}IxMh34=BHQj+hNT1w}pAE(Cp&R`;`-c z+1cH>r5{{<2*qtX3C8wp)!tL5X|s*4CWPt&NqcOd=$szakl5i=vFw4Vw)3H_q^kO} z`L``(kL1?C0@zH`e`t#R&{LJ$F=PwKgQFeoETe0F zkdLPz-$pm0&Kia6ygYg_V366Nm0@ z;V=8&6cp6^{>+n5x462WGi{erG4|esQ&_0!gj1)`*iO~%8)nLjaGG3YWpHxZZK4d= zP&~bBdF7s=A>=eDvqD&ycpoJ{#?E3Yd83KuLhOqqE#t1waKhMF3 z(dYMbSL|cRUhvmp@CkUs{8wuh%9~w(m6`5=I>>ob-78nX(-i7`_pS&0KFsIN&i*Ih z1)Jw(D>wwgEzbBbUAZB)2*xKk-?a3xG`yw;g}KW*-hth|D;wbM$ruNs5ugT&?st70 zg#+B(Jm6{pnkRm>Py=PgYq=A`znb{CYM!t(ye_N@^S&#rBzs2o%n7Z1!otED-goY) z-MFatXLsvhze0`ti4 ze9`zOcmgZ?^-~Idp9CMq6MW7Yh6uM;f+x?Fi|20!Fp-BfW5rqmr=mB$IDUB-{_#NCEmV=@&-{v@wD9^LoIE^n zJOZ2>`%4SjeL}al*RfqQ_Qpu$($Yqdw@4U-nN|3|e|>(-8hpM_{U%oI+5cL-pQ$d5 zkBNzeLkIF-zruGN7A6Z6Ip5Ix?;1wKu%craB^1vc%p0g%c();{1Pc_vfF7E{7HQf6lzZ^S@Ttr6K+|-XZ^r_TO{!|MaXK zG9ImuU^j-1)M^F`?9+jS!#UQoLW8KQYXpbXjN@jEMWrsXIGKdUi|ljA-z8j{rZnlqdMALsfSc57_L z0=wl@N7So%6c}`ZT($p@F<3I|+@~So-5kQ?)Xcbj=ON*#oWkU}Jdy99c!l{P=E^yV z`HP^LWYLg^eppe3fa?nmom*)TM_3xl3arpuGC){sZ@fs@XFOP(HeAP0z#ph+fVV$v zK0;WGbV27N^V2L$EPi^Rf9d1Pdzn}yfJ72quPeDgv((StXaVr_%e!;|5*`R>25Q{{ zE+f1QjOW^76#-$k;Swgh8`^4sRXn-~-bTRQGIOXL1f86%vpHBe`glOGllO!G(1(~# zU-Cuhm=S2^&)hcW2cCn)+-yuNQ-J2w#_LN!Yk1(#^2MD5m|5Kn0Z)ovOTev!*8?g7 zwU74cjGhCXvYbC$0OC1;qPpu7A5e@1LLZKfR0j$gW|OWM1W)&7YcW7m640E6a)arx zGyvnpr}(l87byas@C}Wkz|Z}w=zkUcuSNgsI{xptjsRO`A+6wmpp~f(7sx|Q?@#HA zknm{W4VCuD4ski+3_SyY)2*+#&cvGX7~;5aF8?MgGwaZEoyZ^%2$7q!AleZzh>BJP zN7zxNX8_nKfN+7_^OIdLwum~a{F$d!lAQYXnTyUSKk$~*KQm5)HsvfQ$Z>?SF+s**FXWvO}0^jPXH;Mj@Z-8lE8hc2R?`cjy{+5Q=Pr4rRY2-#r$mr zD-(;>F5pF-!gfYQ*bwX?yHef;*AaQDKki}aJP$^T(aK?FV(~B#t|OvCH&?P8wwKc# zQm~=)ot%*>{XVq2aeyu)oW^k2C>b0SNGX3TWA6z-?t1C_r=ME@Cq~3ulOT4TX3j6N zICNqjxwY<9tX)UEee(b(kP}1%%T}s<-;#`zYyvdrdr!8|KCuNpvBuE?1 zh_6m!*B89j1Q6PrEx5GhE3oajq0LT=_%TNqo467P1$&tTQD}<#OX_npf6NuYczbP2 zwTMoi>#cyN5a#r_&#n(KH3>*OGmAcmPV<*Xm9g6uSqn(|UOCR?$c67C6+jp$v{DZe zo_zQ#jn8o*(^54V43HcSojuO7+~SF_40fLy+4?c!V7s*$x;-`nqwipAH0VplAzCx# zWbDQWHZ-Z!`51lHL2ElC@dIUUz(yNej<;#XmSI=lt*JLwZ7yQ4v>*QIckZ~W<2U=4 zH`3-`82BEm*mCIKg*MnA!gsjqh%zH>^Hx$L44C1{Ht}*qe2i#bM=YCu=ZZU2oUWE! z6F*Z%dTERoa28sC8y{S=^sUhdkrKn!1cb3WdOy@MFYxW1zV16WNpCPXTe$U{s#Hgm zI&WS38pKxl(~lA2p018^>90|<+g-+?N<%&+Q}nG7y0SIJ30fApJ+8e~;Uqs3VT|3F z!|wDCVeDUBJ~trkx<$ZlHJ%~g%vU`VwRKRy{ak>%%#5y$Q?T2qyou{C7jB)jZJFby zULH=S*5K---HEYwx94Z%7j_f2h#x0HNz{_{wI^UxL{8KpD$7M8Y*-Q7X?Sl}-2S-$ z{I!mX@->Y$Sycs!E1{-fOYeknC$DHad>XY}k)UwPGREb8b37%}4&F2ExID08IV?m7 z?ylpt4_zUIt__z|A?DRHc4>-`1wL9yUWqI6kJZ!i=Be1cQ>j$6S&#wSS)9q$EBi8m zUb9|u^-=jMQ9bcI{y~p9pQU?JhNbEgJf<6OU1$n(AmOVlWDN?$%T^EEFtCAc)lklS z5}PjJnfw+*Rh3)gL%YtmK>O}(6?+|H@i81XUu`2AN8+_5_S#W@AhzYcWwQF@aaSA z&OWo1$T}<7dsZ=hDa0@u)|JreFB@SKY~d6%L@630&L*|e&38~gB^P!-#EAMCcLF$F z#X9kF^r`mRPN1=4p(b_JsirkMbpfZ7NWyWIdHD1atq9ys(Gz&WtEKFe(u6hA*^okb z;}gP=OrWP{Ml zZ`@{ik!9s}4oV3jpNh#V22L~>w6H38nt6m8 z#9`g+@gXz=Pg=0O1a>X8tg*JBwv%X~O)vSmp?hdN;m{sVm6!e-a!PY=#pPex-0p!+ z)dihSFKOEh((;w-vEATZ%Bxz_Y0B#xoE)a40SZ0wJ3Z^@m(yUAx8+7s&4U~(kk!z5~dlSUOiBHEqSnQVkM#>mtSw?E>M%xavn@b-_Knz z-(6Fvqt`MBKV?oqrfzQr3CLpH^Ko&$#i==Ha-G~ZYDB!Od}CGqtbfNw0ksSNQe!a5 zrFJ=F4)aQ7?RAX;w_spu^9R3T2ioO68-vd3KleNpe9RRblz4v68;ker+bsoBTtwXp z%~f)i2CnP9Gwf(I*2MJ|;sOrEg5G_SMolF7y}I^gcq6)r zw675FaU|ExCOJp|-D3tdwU|uUWz{b>f_K_l+uIODtsN}G;DS;IN(#z^Hrj)|Nu;ML zRuMz0BUb5^Ykf4{VONY{z7)DzPu@WyiM<8;SZ}R2z}2)6F4N(3(`MuKG9TSWGxJa# z(Y&D%>=_j}z9ObyeY8*{8uS^E#c7u*Z~UV13W~lIT)V!CPLi6&7uXZx0=74# zLbR;5XU0z!wu;t$O_!zz%eD8Hry93UoLL?om=2unBxy7`U}A9Xz0lrtxsUfW{EnhE z+ZqV513?bkRWd7q2c9*2TxkXXqBQ&yWlrMt!FRQ1(I(p(NMME>T^w$y=`tU2uW)Y(k&yOow`1UB~%@%1iW_F14(})$wsB~Ue ztU=NI%jz6`MGK4CptO3I^w8JQTZGN{^O*TLsf6lHgtnhz!s`}vQxI0Md@YV{@eq^W z@(iFYeevhij%=YXhIUkLN<3L)j>!As<8bn60=cR`U61B6mu=F;My5 z!D^W;<2vqI;NOyu{}d=@q|S}qwr$IGJ3TEvjV@{1$Sx~f>ap1w;2*56FU*I%GH86# z@#M;u@uaEhL*2pYiSN`ZvI2pKXn8v|y_~l(lw6is(7;M-E$giEEt8LRiq0ws3OSYD zC%a^`V~^mZ*c*J?bOrvpK)fl$pQW6eQesk;?5)%_aC6{6*H^!D@g12C4n^bWVd*Tg z0&&Wm?+&fn{Z_D39(i`O3;(<-h=0<4JW{B=E=P>&ehp&?Y@fUSz3?{A3aH3pIJGj zT@0H(YrfJ_nnoyVus}=HwuT~qlwqzOcq(i;?tW-3=;_A&g1dt>XE3e{7##8!!74po zWBml+zRW=!`<2nMI^vMTS?03?(j})>`EKRoY?Z!Otra_u$B?s>vKm|)t4iFJ%klo2 z#^9{_W4wuMnTKrpI&9mpALTY!v+3(Nxal!Bot72v=RTIjUYt0Vux>ye&y7~*%}r4t zHqVh@q}(cT3Nkt$Wir&$HI9-7?9p?qxWlfbQ>7`M@u zKoM9Gz52(%iJPY$bD(zN06@~o$}szoLgI%Y>Iw2BQj69xq@^@KEz$a;X=wBI_l0dZ zMUgO{Tx?jNIo?hkUkmz_iNlb`GBo%11lYmXP-L35b|CQP#e+3x@dMFm4t%*dQQub! zt1@0^2~Dq|u_>ndc&bz7dU}b_r4;F^Mk5+_WGUAln(J-rAc?_=N-iM|fMBYj7$Q({#KAGGyv`4E#K6)$; ziDr_-|15UX=L>`-cbprcIIJL}Ok^{P@znbvImDvb@g@hAh`aX2{Z7L__^_;vwX5c!N4Tzp%@40~fI1^wcJwr$a^{cg;6?jG`D)vbDN&I2~UeOz5RQ@d|$E zh|WZ&4OxfGZQanGOG0R{7Xp=1zdkc&KoxeDkge`??d_kEIIP}Spz&9sHp|VBs$-6F z2v#e!6zfOgqWi!ga8du(ImaP@HZ+E=MgXC+(-2Y_&gf$D`b&nqF<6OekT0 zxeh}{-EIg~SLQ{(auQOeBrNkU6{Za42A6495AO8MzoPpg({0o(aI_HWimN>r1%JJC zN)Z@s;ajIUSyne0^4(Hb?3%Zu)=e8RBs_pB%J@iM} zDpxOJ@{1g>*&@l`n|2yy&Xf%iO7quVH|jx8p{?e{f#s?rHpD%)iAmeeE=X8vbQ;Q=r(cFglq~bMVJaRdw&HyY!AWG%+OaUU;9&44E7U$-cfbBK zdVCkKOB23)a(kcfCZlHuTS~R~rc;m|hP~z?%SQ`epnCeJ5`i^T1#j|i5+a3S5>zJS z@}id`3wMg$NV#1bl$|{H6{8N-a}#SHf|oOJqUkNrbIQC@w5W;{R}E^*Pc6QVj%d4_ zQ`)j|X;HTy8@ClLw+CnzN)_k8w*AQ+8}v_`-OcM!g?8_;YzwK6c6$sR2nF%fP!HJc*uzegI~;7pJp zP_A*H{iMBSkh5$2&-~KUm(8?pAT)(0l@k256NXL~rl4A3b{SO-Mjh$W z+DP*Ba!|pPbz(JFp*BLi>B*JbQTV8-sV0w9r;+4|^q_%&IUzQ-2ovKHCDc;?W~(jj zH61$FH!%GWQ#O%j^XKU@TEug|qa18K<6x!Sxnv|-^&S$d+B*;e=Xbxjq-s6VER?_C zK0ug6kF3Tnd}~)Y~U8<+GememDHo# zM;l7z5MpAXTokUe_pUbY$@QfkrWEu$=+W$(GAYH`NIBZc1eR6sf4D9@Tl-ih%2wR7&FWLM?>Ik_Jm}y6K}5 z$foc%F?oG?J$Od3ffvnIu3r*IvA;SrYav&1SJtin8yQ!&y`>4(`uzDgoGLi- zQB|8dbbd6xj`*QNB>8H%v+}#DgQBh_iBU@L@?gmF#WsDG*se`{xwa0auuW+x))ZZf zBo+!Z3PzV{-3Udz{<+<>6I+;6n7e&?MX{`B;EYk8QsD%zY;j_>+-C6Yk9 z(0q5Hipt(NUVHmsnj^4HbVDwN(CSOpm#@#w_dge}VG6Uc*op{rBgxmg6cp^Bx9#^} zx3AZdTo$5XSxU&uz#t8a_df_`erv&ADLCAcW>=)(53!f}{~wjsG|puuSVZgO!$`=-WYD|~xDwS370)%cb^ZVRt1 zk-)YUOW7W}k8~pyPmx+TBq*j-gIO1nVS86n*U)`wuR-8u ziecJ$9i@{aaM(7=`!p>(_^Gblnn1j$ME-&n)z4!N{_FZDUt)2qC1QWLE?0n-a7geD z`{GjIF4}sHYTl-WYn%Qyg1GuvYsxK6Ij*H_DS2ot3xCJW13B3}eUxU>ad&WY-oc`M z;yPSZ=&r)kWZ!&xNot!UT!vs`gtT9)rdyC^9n`hrOkEW$43aI#))JBZF^&FH%W74h zw5F#r0f>Xok2m^j#(-(a2x%~$g{`2FZUfCPw(m>3o+aQF&0O;%k^McSo|lBB!Oq?! zzhKhz58t24W-X%N_)Zxxk6yJW)~C~%=Us4Rdq2^dWkyC;h?>*dh#N2kMf;7r1;fdQ zr8aPL4-#_+pxVp*xV!05?)J^HH0NZCsadZh6-~4uS84em**RN2Nv+KkYdTo-ReY%;n1|@qq!WTCMc^vRYHK_ou(3{G>Oi@JNF96L%XJNos@P1~PB2R(_@%rtg|O9XX7@UYr@+Tb*ZRYQQHcKQM3>Kb^ef zhg|;z&2~MPP`o_cGRJQZD#nd#rYr=c~5b+hK?!BtP#kLP1ewDQyON?HTO>*?0r zQ+$I#DJw_Or_!x#P@;o5*+MY)>8aR-k4;^+O+j=X0M6hoZ;{vRK;+{A5a^NB2Jc2< zYpDPVWzycN92nybBfLuQn+oYjYWO+cRJ*3Y+qOxBZDFTg6j77>5v~Mq0G|K$UPl4z z(ABz`NbGEn3 zP;suV4!FS$yhC#6g8xK@-C(ChyC0HPbGxD(v@GZ-O{3a=kMPq_T=lJ}9h{!5C==MY z7Gnx`Piqk`KPp8c$(PSrrlF<=Y;7E}6+6CBvYHWEcXWK(g;24&@dYm+V14;kZ}R&)AnhWYi{mS82Se;GrQW0SXvRKe1Ub0xVA9*msv2-7^=t;qYR8o2N%Bz5 zn1uX&T#xVaDf6@v!jtwE%=X1#XkC9^Pmh7mSQn<}l3?@w{1f|=Qt{j0~7cWT6 zZ7wOy-5GO1m*ZtNNLF&s(zSaA=v(8V8g#Ab5$pk(NE*puXH0!?t1)jqw|Nu8xu$-l z)#H`1H*CA7dM5#ox3rrq++jgZuGb`nP%ZAc7Ia{5_bM)%VoA4Ok=p)%X@BB(z5fcq zn4|SIHPuzkk5xTgQ!k25Iz>k?cK8Y{DRVnhI1eoFULLi3r}nwAJ$)4j%#R2$m_4;n zY|~nVzqwO2J?q9Z1yLkiy)ue#x}P2gKgCwB!{_Lkhwlee2cqN&EiE1ARDxt(|LUTAU3@ zj$d}JqXlg@h&RJQ;rksX<*IsDlsuZGjCxHiQvDBWJEiwcO0V2rpNjy7KVWI4pqx+% z_jLm3`6;K6JGn~H6ux9y)t*X~x)&4t6HMAK# zj`p6VqhhuicN9?}%|zFqR%mcl1Usnc>UU{Wu#Tv?KhN-Pi=0fY)1hF|u+K==2`+H5 zk6d2{_ZujKTT+e-#D}HEQ4cYQ<|%*`%E8^m;N#Mhq2Mlt#Wu zoI!tu@ZdVVKDas~xuQZ`rw=V-Oav!q(s&IhIr0^u)*qgx0oF_yRypDhq z<90JFD*_cv$l1;V!#mC8RRcYw7e{wUb$g1^yu?z#^mm2>FFRC^@w#8)g387Kn~a7Emts9e&jQMICv(D zPI54@m@{^jEKWR(&2Tt44Q3YZ23rh1P(S=>%<-@YS@T*h&gP`!LJh`@hgn|~+>lu} zPk=lzeK1k#SFKF z9x<R}`KkOP1)&mIZ*Znl&%EKb+nS3qS+lQDHiuqku{%9c9 zeSw!?@5KKI6i=qcN-~4CL%aS=9;rzNmwChdry_vhj7$!3l{SVS+z&WC21x`rYs0{& z&t)EEf63~&;Ao8`USLB^&*isrsPF=@y3`~bKx*1PP&82!2OD5U`dYaJu;Qf30A+B2 ziB`RF2nf!wj;o&W34 zu5stJw+d6H3_d8NVmD^ss83CQj`Ha46Blr(i& z6ieT@aUEv0X2ZNbC{@ew`iowfo0G3P` z#qXyAPlQ1nahiW6yr5Nq4mw_Vg9|k&Fq!6mlpX+yF_nP2Khtw(kgUVV+wrhoIupr3 z5zOqJmU&g=PbB6X2+sibv;z6i)$B z+75eqEtRUR0(*NJOUjt99xfLK?A0+cC$5no)QoU+|Tbmeg?u0qxme`X+y*1 zS8lTkp8?q?%1ljXfbiVmTk`+_LD%fc{>r)5ks@zk?$7T90pp+8QK=eEIb+d@{6Lv{ z0=(`&18Y~Aux*}L@E8=62RZ=`I2i&x&cq@Zp@Vpxn*-W=1r^@-%?5{#uyz&a;o7f! zhXsLWXJvTypRxR)SirJbpHkREJgPkX7Z)H@u7a^6H^A5fqMRV00vB8XIm`vvk_+#0 zEZ8|=E%4L!ujUZuX^C(22|Y8?vzqDos#y*dKH#VO)p7)Z^M3mI=NXGbTdYlH$9Wh# z=3kv6#LlSg2b_!8oN{Inj0Zl-(WcOoh2tc1M!jt`?W((pCnFC2YCA1{X0s*wCKy}f z2PZfy0&%T^dZ>jRWV`6ynm+^5TloB9fSvqOOSm0}C-=}LKCy5Jb0NSN2yvY@AY&!V z>=`OZ#0|C3!N{JMBcS~kmU~2hj}M4^Zei_FE$R&Q?S*c$dTi&C+T^fX^BZkp?2Jky z5Q_W;jyr=)h;o+MTNdxquz!^Qx;OrlrY6Vw*fTl%hZ>DOvu=!ODv+$)+3zJhGlcn~4M zq3a$d79&O$?4@tfqIph@fx z2(tx1uXb!vs?4k@FM*Y%AkB{cHS7PVv`8Q5P1Em^5zre0flPhf{m&}<4U;Z|B;*1h z8T#;XI9LRdpp_~u=k#A|>lPb$armkmfaxfp2?Y6R3MewBqvHkma?hV6{{%+QL5iF$ zLo)F*Q@}usKr1-K%dyI1GxMKI#{zcuk2zdyPNygdVV zN`=eogIst(Jhiki=fYpk>mM8Z55xS|!2U6?aCRXrjo$G2Z5q`WMyyeFdhw6)IF18# z9xOf%0QfGBbvpQ-5S?yqdHo0YPFN`8OG0z0#_?h;Ug`TKzj697&z&=_<9a(|WxDrs zlDdniv%XBjycMVV4ZhnEiidOx_yV2_zE>VtV$onW{x<8-yLASS=QiO$ zWea^kBuYja}>*ca>r}M z;KvXaVhSW0qpbh*!e0XlpE<}>TdZuD1NKQAmq9~u;hIMf7Q7gs(^12v_?ulooCA;` z?*g2gGoPc}>);R3g)qaehBXnB$1d{x-RkvIFn;#;N2MHLI>I_oxzD5GfIV5zmo%03 z^>6kd(V7&Xi8E55qV(P&tOz7rSzHIYVCePkz%Mx&O1gFljDB;b&Wcq>nBz_Hv0ICW znK>*I00CdP@xtBsbwnGvzW8INJInYpW3tX11uU9=*zW{bECoH)pGgP( zEym|{ps?t4QH_TX78jrv7ul!_5awr`Z;C4{>)4L{EeQ*#V?JOH>P$hMvRSn!H~5yt zGr(}^*LVLki!uiMtgF`Pvg*8%(18|C)tv;&I}R9lCZ6K?H&q?s(Iz7Jic3039X@uC zA&AZ`H$b7Olbv`T?2Z8mf6n{ue_D(%B%F!uTOZ$i?CWB9k|@#A8Xt|Fq=Ea7xX?yYijn~_*(sesUDb(KJ(`Io@h)A+vjsE!r$V; z_z*!Kdn*#E7$%W-QD?-`e7l<-AZUMTWv=*ppQ}@I>r2+kVto;HwlgkZl76slk)5YozCnK60B`<2g=R@mgaxj{uNjZ54c7H z0~T2h{uGSHFug3PHaJq!>knB1HRmXOy8!K0psV~8WHWd z$nOCf|H7Ckjq{azLB2R+X=MkP9AaWUU=%j};|tfhHwZxXTI;N=$yf6crUrH9th?jG ze59nC6AD({afUzn!u1dV@|=D5nO;-W-T89 zT}V!+-%dTic?!gT36_xcaiKJ=kgfIA`C+tjmvYYGP-wWCs>|cW{md|9oi+DIO@eE4 zbJRn;B1QX{dau|?ZM`5wYijK;C_zWUeVOyu@?D|EyJx{yj3RpB`XJhUW@5(fpdB=~ zw?}q5ZbV~7wkSgBwHq7h0&gAyXm>_5Txe$X^ExfKp!(G&Z& zIjVkJx^QCxM(*lEL?2n<2si+Ei*6!9C(=&}7aaio+TJJWh!pWNN*_|f0BYL9oX=i+ zFtb|jZ81S}w+bZOfbA1rnHQ0e$p!@kFo3|Y!Sgjx{K%k{#Ipq^L+=EG){O%dn-`!9 z<~P2cAC+hQ)eH3^^j+BJv!JESOBd!xpS>BS1aD@r0pVr+q_wW}dbajqlRfpwD1=2; z{G0X2eX?DWL=?bNR0f`sJC6sbDFYOhUG4$pa~zMay6Lp#ii7&Z;t#Zmd{xB8ru&VYTv z5Ci*TSgZhwief;mkBML8fMJL+^dQ>knJagW;vxQspGEM&uU^#S8+UcYP; z68?grhnOSv`L*Br&~Zn+|DB|NC+Xjn^zTag*WLXeib-4(^ac7n%n!4Lqpx8@6$>LC z2<8Xv>(?t zA2bmaBUciim*vBKq9q+{ieXC!LS=mCpCH#m>C>Uwe47>Y9c_&feWd?-ZXp zNpaJSp04!v<(I#pMTSv&9k!Nn=~}_-i>o`^v~&r<#a`-2D4l;A7D7Ymh8QjJjOYsC z!X|49l1ke%hK}ZHBdYdL^4j#Psy*iNZA4IrC6pQgL|zvSM2KBiAAC4gC}S>=hA%De z+Z$@+jp2*##cNf6z^_f9C#gB(zheKWy56IULdy>5p07_EFYR3?gwk@S_gL_U8sef> zV#=1c0TddWk)yXcAeX+~%6yT?{w}SdMC!g|V4eK*2Opj(g)M>HYSWGJVrQ6T6iax|1-CcAYgR z+f(DICV@o046E;b=!A)Fl7l*5`E%S8ZR7P|%lWSPn9`?G3dK>cXL*0XQMe@JV6}r4 zp*;B3mMb$cG+hh18MORGui!p8G3(pfrknU+?}8r<-&Tkj*=@6S-s|UnCD9%=ueE6< z3=v)KLM8G}5!1q`TV3F^XJHP$l*v23mtCMdct&pT=HsLS%SGFVg42eyJTXJO4&I|!4>B%j_0^Al4_V#rISZ=XxVf$4IH(v)O5X zzlru$O%qJ12)6beLhRfJ*im^sCQ?Y__KRm8=${Sjf`WXLyKb&h^X=93W(H4R&^rF& zL1I&J#qx}~rs%q7Zed1CSD(7Js8{Hwwt8QVwi9BUU9?zasub%xk6Udiub7Q+HKKHk z%f~2e*IcUT_h=lm9ADs`S*uPfjvB3(i9)rm0}QHub$)IVL$BzJklwUyRv0Tzn;xHY z-g@7KxG8EDHN~BH89$ED5clt$RH1AYM+LVkZlBgJHsxAWRo@)7apgm-lAG)`l3nH} zN)h4$+a5SdDoN1k8zBF1x)f%{Z{giu?y=E6VR@FG_l!Etmo-wK26MXE+?P4hLbh-77rIkKnb=^?%q3F^2#e%s&n9^G~-(xm@E zf(r@>XM|eN`QF88%Zq9aR^6i|_(X{opf}5STIbV@De58jk*`c{fiyf7C%Uy-5TsDeK)000R!16(+ zel9N}$7*W86-)9*)S=IK-TU<+j?Y8|O(F9LFb=Bj@)h2q3PCTjv0hd3xJUnu^B-8}e^uCQw z8Fdv(Yt&1?dQFrq)u%Ka(QOo=$=LM^MYq7{S)ZqD_4Q^^s*A3(d2>`PrTgIdO zmv)8W+VFBg=>n;23+@{?=eB>6WJ(I4)>$h#27Vd^L0PT~HJbak*2lS%4|h1JHr-Y# zC^Nx2^;6eO${qUwo+5uXX}G>lM{};PzZx zO2N-(ME%fJEn2R;ofp#9v}L4juW7#5g6rGY5^u|yLOF7az4FAITu5|Uy|1}zVpdG6nZAj zW_q&~7fg{U@seJeuN-p{Q}D^pz3qGOo2^;>IS&bhQ!gc7-=9`MsI`9vH8xuMnX@{j zO5=C^g!ky>X*2MTnM~UjOU4Tz`)|u^ypUbk$YAW@E?Y?lcV+hMMde4h$Sv*h6!9}@ z5wet?_FbAU6D&Q1rD~Esr8Jp6AHR#0LieXZqe3Pq-a7c9}6z$n-16(2ip^eDutIHl{c?<82Gi7RQ8PQZ897*eS z)vrb1_>qeOs+lq)oo?hnIYcNz&Wv_#ClNhC4Y6pptdWSBDRZAK+y3V2kVXyKc!hg# zXGM@;`N4P`pG~OdFZ8*o*jT<&ozUw*|5U5OW#zFUzQG^iMr!q~ck(r``2X5F_o%e5 zbnnlZc2=gRlXyC9ZpO=L&1D*IH5QFG>@m*NxYlD9Y4 zcau@(2Yul`XpB9^>u%s6te)L0Q>r!_;|G!norZuy>AK3*Z{w=wd>mhFuObH0^_itST@wg&P-5s>KUxZ!NW1zXCZu&%hcyZxC;ZJ&6$eojHU0p>b!+v2xid%ew=Ds z41*kBi=;+V9T<0V){Q?ZktgMtxpl0zM591{rXuC9PStu*Pw;O#mu#ocViyW$SRWP& z+hr`82=ij|vh^L*efyW5vg9DMudHA=o+~L%!hG+iGw?7&AeUC$nX>Q&hpt1 z7a_3MD&;w~Ez;fS=u#S*{E9yPf2~;GVd%L-!nh1>ezbB>N;UdX|O;V zCv`G2D`^2-)+3>2H}BQk+v7|>Y@clibm;r;gnoUbHP6OF%kTB$UFhMttd>IAk@6T> ztkYcrjqPa93HA|OdAoV;${TF`5|kk>5ih#f93@>2F}RhcUXAjOW+o zjKMZc6imNbx*7w%5J5JbcZRtNVc)@b(&DojrlGHYT0prnP*XvF*tI;eDg6s zoa?KVxD6ThWYAP2X7*&(Ja4S1lrwNpS#9#vE9eS zltGkzsshk)LSKfzRom6BI*09~R;XUw(nF*A3vek2W?)gu5leY*p_iVT?v2YzXIQwV zD_|*DJWLHvX()smD5d?eJ{LJGt&bS#3Tky1te-MqtC=N(*i%7T`oha;3LX?Ur1u#X zy2}E^$@^kFq*WRx;8NrDC(y!`R44g)jn7(D2z+Iy{h#Z+e%#e=2;&cl9}V;CRDZ7W zO4xQ!d#>G^=a^e)2kQ{YH4@PN1_X4%=bM7AsM@(hOGrrQ^%pTb{g= zV5YW`^Dhp3lia<58(8kK@L)Ov=NQoD;Tbxs3|MJF2IgmVLH&>>T`qfrTUjjEpGj^Tdfl6dic+sVJrz;e<0xK|u%5 zqt=;>RKSAwQNt+8V(ucKG3hgOxEF2b^tbF|R%#HUJ=8a-Xp!73)zl1C9SYB&L#lY;d+V;Fy#alGsJJuRVSf+ZHF~0 zl>}yzWl>dk7ux?`Y3H&p6)gnCIMqeoJz^XXyT3@h2Wrbzj)_Xsrl6e|J=f|X?sPnxek-C?Sk{Kz)T%`PRIBxzf z$&Mdx?R@o>1n|WNI=dX10uQ)29&G61g+gOY%btnd&U3jDA0T|$Wq2{!eg`yXsH&93 z?EW6Izu{Ope;bnLDL0(8ajm3INm>t?5nd=&Oa8d-QQ(jY!<>zq%A0&84S1;U-B))!OwSEdjg}u`XRY~zi5_waMshRX>AWi*SsJ|!o13l{KVmMp9vz)Se!=Pw z)(~J#zY{HIZnlTtR1xRyvMCvZ`Ufr6;Kjge4dCcr7^Db8-kZ?5AB!en8MKlXM_d(g zvrZ!x2798Y+{8T33h+g)*G@#2m+;}iBC`mEk>oDHwX`KC#Kx30MDvPIbYE+X7sd5Y z3)=IA#O{9J_#4y`YVFA{Kzu8{o`Fu$8U+&6hO+oPlW~Sg(0-(LV39<{r>vCF#)82m z(1f}uSKvn=qjg0vpz6GMI)RS?qc7T>)(8Gph&9@5R9J+cKrbn>xuO zKhTpT{w#!Iyw!f2Lu0=qd)#?0dr;qi^Ii?pB%}QMI<9egZ?Lech-CZGKv3dWn z;~j5nVJt#V;#W&rd*@kXOVWNgCbq+x6e1^ID#nBnr%*aWT~7J;NfyCMd@$lJbD>?( zoT(YiU?}vSI8>QWD_zm&G@xv_qruM`gCufpssh{5%Q)0?TvEt4q34{7)$VYPwBpp- z^y?(3CF#bXgX<&cM}ALsK8B)GDj^pFr@ouq6YFowS^s+mtgWlyLa_eUcZCVKe%zn{ z=p0DY;$N0d9i z%S=!uA?)p;o%)I^C-AZOn+r3+)J9O7eoVQI>~=Fq7?ZR}pD+OgiwA@~|`jNU0KJsL&$ z=fZT}U6`Xii?Nj*?Bs-rZ*!YP^T&th+p(0btuv^+%_I9IS>T=9nj)RkG(~q`w%iE< z(_FYN{L1Z3V_dhk{wM4;=XIET{z$3SvhfQ4Wp4B_qDy>Rh7J zI=F>nM77g!CP*_o=a|nq!S{u|j)rAjl<{dJm{5LRS-eCP!8jS1h$I>K$z}(5RXZHG z1tSWx7M4!i3!fr8IzFs7_E_p9$d679XHQ2grP=$CAr(Q(HM8S~+~*vP`%Rl~e@vMS z5wiS|3Hv4KneJior@6$PtC&9Pd%oRh&(_f!p%LZezIR=X}~l(Q6` zESm{zVQ9UxLCe06xEf;sP`VrKUkp&ZgmgFRDV5+?TYbt4(wrI>38Yvuqb&YVAtOW* zv%Hp{W}|?lp(5R5dAKVcEF{q2Q5I6?pIT&fZ2}g&hA=!k!khTs2QH@wXg(qd-ap(C zXJ=nowa4=eas!0Bdui3Ho$FMTk3z{KC*10be?9)AZ_*$)m_Hc%Ua7{7xC%<+kQEOQ~&swD~yTb z9Mq&>S5H@fUyZ%jLI`1Z#WfvMn3N^9+1V_)h*Yj)#9PwAvNq2E-7s<-ndI%ooFYW_ z8@xMBx=WpapJeZw6Du+pGCklYX^z+H@RB|w4;?eh+3MW70X`Wr)wsHJNDZeLtql7; z1PiYYCpoY35uva15z_EtBqsIlg-yS#CE3Q}8{UP4Qg45G(rANtkBGCS+_x&9orrP< z>me;3Dfq{1&i# zHZ`RQd}1A1;6W{7h;x;eY(n>S*XB-W!%_G!q*CJ~MEQZ>P3jS$EKj~ixLV(`&hI+J z#Jr#&xD}VcqOg#}W|8RP<;;>ltBZxa6Lh#G7&*YYJRF+~gKsv20y2Tfa)C$^WdnI- z)3N>+8i4HN=VmEVoGc_^hrL>39=ntAC3w+#G(cBQOohgAqME7>4wI?%Ot3Kqqy{BO zaVV#nM$GAo7saYEI^5_k{L>K}2b9%>Z3>7BsA?os^o$^U_6;w0EO8R=;VJCOaSAY7 z9=YR4&i4OykhhTLp36KnQ8|jkdblTexhOxM`f+sVM_umw)9f@mta@JfD46WPGKEXA z3w@_uRWA#i*yZiQY=Mt9pNB=mY9hqCj-`&SgK5P6FyOQk_1Bpt{ye|U-BZb}XG1YJ zd7OF$J6arr0N>io80pl&g67TRR))xD30tjhAxgBFwn9Fnwjs%q0g3EJIGbYv@vGth zL3_B4BVbH|Ul2AFy5QKM>qV@~T9)!O8FOhY4ir}oA^y`;=~U%Hjip~wJg;w3T=C{C z^8K4vqyf{$1k_1qd8i_Kbe(>cD=<2ir#0;ql27X~QRGE#V*AocwDf(5WN7;1P8tin zc}XVfpyn+T%7##E&q`#W<~i=atiYx~T7?Pc)aPc@@HB~Ka>~Y7K|lpiWAznSrR5=d zx2|^ccZVTfYo57Gsk%3vkHKYy&DD!c7ZK7UlKTP-B(gF0@*RZt1YCB+c1UtN8N=;l z6e=V}*uj8+3J>?78W+)d=etwt-gv-B>PmqsxhW>%C>br+{7K)%E%Ol!>@Qv&i4@`J zhI2V?@UfxYLUq<6ooE*S?u1KyJanui8t0Z119a1cvCdtx2BV)b< z0vq=7Jrnm$Pzr8>+jib)xJd4=VR6SeiGPs0YO|qOwERW2@MAbobm>eVLzE2fj9uT7 zRKjBoY=DPlHs{ROTClT|@s38wuwK=dKE~;`s30=#RZOxg{fuf`P=|R$JljAi#p0FC z#PZ&VCo_ri)3I7vtwe9seVN}(5XS-KmZIt>KB-FA&zm#$bM2%?wD1tf1*0W&w^&l> zvJ_Ret<|!5s|I~O#idkd*%}V)0nE%Dnnb1W8-UI9_Ti1qmxX;(Zu-kqf6a5#r^pYf ze=E3|ksl7WMmO!vpD zgWgv_83t}VW(8SMz9B$jfrZu-Wz9`1Xb$pz3n?_e7B?FWixva-i{Rs#m}yw>ptLHv zN6_^opfU|id8?>%y>^Mvx=<@1oJ%NqfBP|ETl3D*Bda>X{$g~R(!2mE2Zf!gc$~<8 zKr4yrTZw!SZtQCwJ5*`EvF{iv2uN@JyoGIra3F7vBgW*qEgkPKOt{_wN&tT?jqZpREm0#CCSg4ek3?|9;G^VICLX!&%9aX zI55_qK1&=Lgm1dZJ?9`<52AG#w0NkX#oL?qs&|*IK2T@Q)*Iu^-%3g(f0-AuGa@-l z8Iqj-ICBdfjis}+y>yX^vtQrU`HD9a3slWQ67CLjKqT38>*~eh@PK|#3XF^{fLiFH zTpa3X^w7dg4dUsjISV-mHc%-3z;=33~MUg60izQx!mY{}$vA}VdXnEop%;baXPT`mNT1AZGj z?;9;_5_%3iaGzn*bNe5NayW-EwN=UZgX}HakKmYKT7ZJKHB--avHgtOfFMXT{Ca(q zzQX=Xr6#=i!|nLraQhzjL^MEMKOhVZRK(sq$`qkC-uEW9KW{{%%WcfoyQXA*;pGi) zVl_G(tYcFh^D{{6bK7A9<_+9fpF6*1ECg3?bks8Pupt(2V4q{VD~#tET_l>WiB$qE zU|7%KCui!R^8Sr05%bp`I@AnMN;N^t<0#z>4LOC?jnCcOn6c(nV9*4cH25fW;LbaU z*$I^?W>)e|=+473!bAZlSzb_S2D#|6?oFZ;4_#BDe1Igbts@_T?Cu3=)=QAk&`H$# z7twzKG7oy8KGuen1nI-64x)5k;GXkpMx=$&gX>3Kt{BFyz=K;TqV=c!s|8Jhg{%C^ zxO+<)qO`90jxw4yaa|L4T-ggJq}IPB`YQf{bgAG;-s(|E4!7o~YDPulnr zJgJ;I2+YnJ_{8Ib+$9V|&<8Dsr4(7ydP^oFzX+W=RDMqGNqykYVndzS+v%ASX8e*< z{?NnMi;uf|+R}nNumhmk-T-WE`|X}Apt7tMSwOrXMZuAtPde zsKKwEI|qP1H%@#d-92-RcnP z+;4i#8Og>PYXAm*4**&Rej+>yboCZcrX^n!{2suE0=|;&nLj+(+U1XszC8Tf)ig&M^!iDdO#$ve&-;KgAk@p!OeINrP z%zrDT3;@=v-%SO!Mm`yiINy}L)c2o7-0uN$a1?Uv5`a^^0X(qteFUKOc=T6bgyA0{ z-UQSgjk^R`+4afk1|QU`KYVrbHDUz|%8dnjb_O0;_Z#PGfKEFGj4B*H!YXhD4c*1T`0z^${>s=8i> zBKoI*I!$a^&HeLA4#Bq8bkb+%*1zHY67sBj57^(__q@}+oJf3>m3RiN&VL(sZ7t;C zr-bM-{DDRs^$X-vxFqDIOg|6btfDtCBt?rqv;R|aY2~XS^A9_(bSe`=;co`-eDKpQ zQvQF{mH*Oo1ol>_H-)Eukp}+HZ*2hjNahJb2EZx*B1HU`=KU%la)sUS07Cx1^!ZEk zlSm+XVkd-t|Ldm!B>n4;|Ds;}MZ&rJ_Wu8V?K`fxc--FyemJln&?Nun;<<>kou6L& G`u_l5_CKcp diff --git a/reps/2023-11-07-object-store-plugin.md b/reps/2023-11-07-object-store-plugin.md new file mode 100644 index 00000000..912957b5 --- /dev/null +++ b/reps/2023-11-07-object-store-plugin.md @@ -0,0 +1,181 @@ +# Object Store Plugin Manager + +## Summary +### General Motivation +Ray stores large objects in the Plasma distributed object store. The object store is implemented with shared memory, and multiple workers on the same node can reference the same copy of an object. On the other hand, if the worker’s local shared memory store does not yet contain a copy of the object, the object has to be replicated over with RPCs. We propose object store plugin interfaces to allow ray to support other object stores that may provide additional features without disruption of the overall ray object management. For example, a CXL memory sharing based object store may avoid object replication entirely. + +### Should this change be within `ray` or outside? +Yes. The change should be within `ray`. + +## Stewardship +### Required Reviewers +@ericl, @scv119 + +### Shepherd of the Proposal (should be a senior committer) +stephanie-wang + +## Design and Architecture +The design consists of the object store plugin interface and the plugin manager. + +For instance, GISMO, which is a shared memory object store designed for use with CXL, can be integrated into Ray as a plugin. This integration is facilitated through the specification of the GISMO shared library within Ray's configuration settings. Upon initialization, Ray employs GISMO as an alternative to its default per-node shared memory object store. + +During application runtime, when an object instantiation is required, the GISMO-provided function is invoked to allocate a memory object within the CXL shared memory space. This allocation is managed by the GISMO server, which then supplies the object reference back to the application. + +Subsequently, this setup allows direct access to the memory object across all nodes in the Ray cluster via CXL. In scenarios where a node requests to retrieve an object using its reference, the GISMO shared library responds by returning the address of the shared memory on the local node. +Such direct access eliminates the necessity for data transmission over the network, irrespective of the original node of object creation. Consequently, Ray cluster is able to fully exploit the advantages of CXL's memory-sharing capabilities. The additional cost introduced by remote memory object is eliminated. The problem of Memory usage spike on a node caused by unbalanced data partition can also be addressed. + +For demostration purpose, there may be an example Redis plugin for Ray to use Redis to store the objects. + +### Plugin Interface + +The plugin provides the functionalities of an object store responsible for storing and retrieving logic. It can be implemented as source code within the Ray repository or a shared library to be loaded dynamically. + +It shall implement classes inherited from `ObjectStoreClientInterface` and `ObjectStoreRunnerInterface`. +`ObjectStoreClientInterface` replaces the current `PlasmaClientInterface`. It keeps all the functions from the current `PlasmaClientInterface` to retain full compatibility along with some extensions. Current `PlasmaClient` shall implement `ObjectStoreClientInterface`. + +New virtual functions of `ObjectStoreClientInterface` : +```c++ +// Since the object store is intended for access by trusted clients inside trusted environment, authentication will be considered in the future. + +// For optimized memory copy. The C standard function memcpy() is used for data copy to object data buffer in writing object chunks. +// This function is to provide possible more optimization around memcpy(). +// For example, additional steps may be needed to guarantee cache coherence of CXL memory shared between hosts. +virtual void MemoryCopy(void* dest, const void* src, size_t len) = 0; +``` + +`ObjectStoreRunnerInterface` base class has all the functions from the current `PlasmaStoreRunner` to retain full compatibility along with some extensions. Current `PlasmaStoreRunner` shall implement `ObjectStoreRunnerInterface`. + +Updated virtual function of `ObjectStoreRunnerInterface` with additional parameters: +```c++ +// Pass additional startup parameters +virtual void Start(const std::map& params, + ray::SpillObjectsCallback spill_objects_callback, + std::function object_store_full_callback, + ray::AddObjectCallback add_object_callback, + ray::DeleteObjectCallback delete_object_callback) = 0; +``` + +New virtual functions of `ObjectStoreRunnerInterface`: +```c++ +/// The Current total allocated available memory size. +virtual int64_t GetTotalMemorySize() const = 0; + +/// Maximal available memory size. It can be different from the Total memory size if the memory is dynamically expandable. +virtual int64_t GetMaxMemorySize() const = 0; +``` + +The diagram for current plasma object store classes: + + +```mermaid +classDiagram + PlasmaInterface <|-- PlasmaClient + PlasmaInterface --> PlasmaStorerunner : communicates + + class PlasmaInterface{ + <> + } + + class PlasmaClient{ + } + + class PlasmaStorerunner{ + } +``` + +The diagram for proposed object store plugin classes: +```mermaid +classDiagram + ObjectStoreClientInterface <|.. PlasmaClient + ObjectStoreClientInterface <|.. PluginClient + ObjectStoreRunnerInterface <|.. PlasmaStoreRunner + ObjectStoreRunnerInterface <|.. PluginStoreRunner + ObjectStoreClientInterface ..> ObjectStoreRunnerInterface : communicates + + class ObjectStoreClientInterface{ + <> + +clientFunctionality() + } + class ObjectStoreRunnerInterface{ + <> + +runnerFunctionality() + } + class PlasmaClient{ + +specificClientFunctionality() + } + class PluginClient{ + +pluginClientFunctionality() + } + class PlasmaStoreRunner{ + +specificRunnerFunctionality() + } + class PluginStoreRunner{ + +pluginStoreFunctionality() + } +``` + + +If the plugin is implemented as a shared library, the implementation should implement an API to create the object store client instance and the runner instance. +```c++ +extern "C" { + ObjectStoreRunnerInterface CreateRunner(void); + ObjectStoreClientInterface CreateClient(void); +} +``` +### Plugin Manager + +The plugin manager shall create the instances that implement `ObjectStoreClientInterface` and `ObjectStoreRunnerInterface` based on specified plugin name if the plugin is implemented within the Ray repository. If a plugin object store is implemented as a POSIX dynamic linking library, the library shall be opened with its full path using `dlopen()`, `dlsym()` shall be used to look up symbols for `CreateRunner()` and `CreateClient()` and to lazy create the instances when actually needed. + +The plugin manager shall provide the following APIs: +```c++ +// Get an instance of the singleton PluginManager +Static PluginManager& GetInstance() + +// Create the object store runner interface with the plugin object store name and configurations +std::unique_ptr + CreateObjectorStoreRunnerInstance(const std::string& plugin_name, + const std::string& plugin_path, + const std::string& plugin_config) + +// Create the object store client interface with the plugin object store name and configurations +std::shared_ptr + CreateObjectorStoreClientInstance(const std::string& plugin_name, + const std::string& plugin_path, + const std::string& plugin_config) +``` + +The object store plugin can be specified in Ray environment variables in ray_config_def.h +```c++ +Ray_object_store_plugin_name: RAY_CONFIG(std::string, object_store_plugin_name, "") + The name of the plugin object store. +Ray_object_store_plugin_path: RAY_CONFIG(std::string, object_store_plugin_path, "") + The full path of the library if the plugin is a shared library. +Ray_object_store_plugin_config: RAY_CONFIG(std::vector, object_store_plugin_config, {}) + The objectstore startup configurations as a series of key-value pairs. +``` + +The modified plasma plugin also would change the data path of how `CoreWorkerPlasmaStoreProvider` accesses the plasma storage. If global shared memory is used, it would retrieve from global shared memory, which appears as local memory. +```c++ +CoreWorkerPlasmaStoreProvider::Get(&object_ids, …,results, ...) { + /* check whether global shared Plasma object store is in use. */ + if (store_client_.IsGlobal()) { + std::vector obj_list; + for (const auto& id : object_ids) { + obj_list.emplace_back(id); + } + /*retrieve objects from global shared Plasma object store*/ + return GetIfLocal(obj_list, results); + } + ... +} +``` + +## Compatibility, Deprecation, and Migration Plan +Currently this plugin manager doesn’t support native Windows platform, as Windows platform doesn’t have POSIX dlfcn support, which is used for loading shared libraries. + +## Test Plan and Acceptance Criteria +The plugin manager will be fully unit tested. + +## (Optional) Follow-on Work +Optionally, the proposal should discuss necessary follow-on work after the change is accepted. +