-
Notifications
You must be signed in to change notification settings - Fork 209
Add replica groups in dstack-service #3408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add replica groups in dstack-service #3408
Conversation
|
Will be solving merge conflicts as review continues. |
Related PRs#3205 from @DragonStuff |
|
@Bihan Do we really need replica group names? |
|
@Bihan Also please check the conflicts with |
|
Cosmetics only: I would rename |
Yes. will rename it. |
Yes. Without replica names, we would rely on indices, which are position-dependent. If groups are reordered by users during manual scaling, indices shift, but existing jobs and persisted state (like desired_replica_counts) still reference the old positions. This mismatch prevents reliable identification of which group a job belongs to, leading to incorrect scaling decisions. Replica names are not affected by reordering in the YAML file. Initial Manual Scaling Instead of relying on replica group's position in the config, another possibility is matching job specs to identify replicas; but this approach fails during rolling deployments because old and new jobs from the same group have different specs. |
|
As a user I find it unnecessary to give names. I would prefer not to ask names if this is possible technically. |
If a user changes commands for group 0 and reorders groups at the same time, they expect a rolling deployment for group 0 only. However, the system detects the order change and triggers a full redeployment for all groups. Users may find this implicit behavior annoying because it provisions extra instances for each groups. |
|
Perhaps we could make these names optional? |
Yes, we can make it optional. |
add_replica_groups_model Replica Groups AutoScaling Rolling deployment and UI Replica Groups implementation clean up
86139c5 to
5abbcad
Compare
| ) | ||
|
|
||
|
|
||
| class ReplicaGroup(ConfigurationWithCommandsParams, CoreModel): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) I think we could allow to set many more properties per replica group. If the user can set commands, they may also want to set entrypoint, working_dir, image, volumes, repos, etc. And if the user can set resources, they may also want to set instance_types, spot_policy, reservation, etc.
Although it may be a good idea to leave this to a future iteration, because some properties may be non-trivial to support correctly
|
@Bihan the test seem to be broken. Have you seen it? |
| volumes = await get_job_configured_volumes( | ||
| session=session, | ||
| project=project, | ||
| run_spec=run_spec, | ||
| job_num=0, | ||
| ) | ||
| candidate_fleet_models = await _select_candidate_fleet_models( | ||
| session=session, | ||
| project=project, | ||
| run_model=None, | ||
| run_spec=run_spec, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why doing these calls for every replica_group?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@r4victor Thanks for pointing it. Both the calls don't depend on replica_group and can be called once for all replica groups. I will update it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| name: Annotated[ | ||
| Optional[str], | ||
| Field( | ||
| description="The name of the replica group. If not provided, defaults to 'replica0', 'replica1', etc. based on position." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, there can be multiple different replicas per replica group, right? So then you can have replica group 'replica0' with replica=0, replica=1. The naming is very confusing. Maybe name replica groups replica_group_0, replica_group_1 or group_0,group_1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@r4victor Yes. You are right. I will update the replica groups name to replica_group_0, replica_group_1. This will make it clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we prefer kebab-case for generated names, so that'd be replica-group-X.
Alternatively, I can suggest to use just 0, 1, etc (but still as str). The fact that it refers to the replica group should be clear from the context where it is displayed (e.g., group=0 in dstack ps).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| run_spec=run_spec, | ||
| desired_replica_counts=counts, | ||
| ) | ||
| return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) Since this returns for any service, most of the code below is redundant, because it is (or was) only applicable to services.
If I'm not mistaken, only the _update_jobs_to_new_deployment_in_place call below is relevant for non-services, and the rest can be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check in detail whether the code below is redundant and is only used by services.
| if group_index != last_shown_group_index: | ||
| # First job in group: use 3 spaces indent | ||
| prefix = " " | ||
| name_parts.append(f"group={group_index} replica={job.job_spec.replica_num}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) Maybe show the group name instead of the index? For example, group=prefill could be more informative than group=0
| DEFAULT_PROBE_READY_AFTER = 1 | ||
| DEFAULT_PROBE_METHOD = "get" | ||
| MAX_PROBE_URL_LEN = 2048 | ||
| DEFAULT_REPLICA_GROUP_NAME = "default" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) Maybe use replica0 (or other name of the first replica group if you decide to change the naming scheme) as the default? That would allow switching from replicas: int to replicas: list in-place, without replica redeployment
| from dstack._internal.server.services.runs.replicas import ( | ||
| _build_replica_lists, | ||
| scale_run_replicas_for_group, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit)
- Not sure if there's a reason to import here rather than at the top of the module.
- By convention, functions that start with an underscore are private functions for internal use within one module, they are not supposed be imported to other modules. But you can rename
_build_replica_lists->build_replica_lists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The imports are still in the function body though, as of 8aee1dd
| name: Annotated[ | ||
| Optional[str], | ||
| Field( | ||
| description="The name of the replica group. If not provided, defaults to 'replica0', 'replica1', etc. based on position." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we prefer kebab-case for generated names, so that'd be replica-group-X.
Alternatively, I can suggest to use just 0, 1, etc (but still as str). The fact that it refers to the replica group should be clear from the context where it is displayed (e.g., group=0 in dstack ps).
| if run.run_spec.configuration.type == "service" and hasattr( | ||
| run.run_spec.configuration, "replica_groups" | ||
| ): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) ServiceConfiguration always has the replica_groups attribute, so hasattr is redundant
| try: | ||
| job_spec = JobSpec.__response__.parse_raw(job.job_spec_data) | ||
| existing_group_names.add(job_spec.replica_group) | ||
| except Exception: | ||
| continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) Same comment as here
| # Determine replica group from existing job | ||
| run_spec = RunSpec.__response__.parse_raw(run_model.run_spec) | ||
| job_spec = JobSpec.parse_raw(latest_jobs[0].job_spec_data) | ||
| job_spec = JobSpec.__response__.parse_raw(latest_jobs[0].job_spec_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍, important fix
Steps To Test
Step1: Create
replica-groups-service.ymlStep2:
dstack apply -f replica-groups-service.ymlStep3: Run
load_test_replica_groups.pyby subsituting yourURLandTOKENExpected Output
Each group gets one replica
Later, both groups scale respecting group configs.
group0 scales to 2 replicas,
and group1 scales to 3.
Below is the expected output
Step4: Check whether replica specific commands were executed.
Attach to the desired replica
Eg:
dstack attach -replica 2 replica-groups-testssh replica-groups-test-0-2 'cat /tmp/version.txt'output: Group 1 - Version 0Step5: Check rolling deployment.
Important:
Rolling deployments are currently affected by a race condition that also impacts the non–replica group implementation and must be addressed separately (issue). However, when each replica group is configured with a single replica, this race condition does not affect rolling deployments.
Testing instructions:
Scale down each replica group to 1 replica.
Restart the load-testing script with RPS = 2.
After all groups have scaled down to a single replica, re-apply the configuration:
Re-apply
dstack apply -f replica-groups-service.yml