|
25 | 25 | Field, |
26 | 26 | Tag, |
27 | 27 | ValidationError, |
| 28 | + field_validator, |
28 | 29 | model_validator, |
29 | 30 | ) |
30 | 31 | from sqlalchemy.exc import IntegrityError |
@@ -531,11 +532,23 @@ class LoRAConfig(BaseModel): |
531 | 532 | ) |
532 | 533 |
|
533 | 534 |
|
| 535 | +def _validate_adam_optimizer_config(optimizer_config: dict[str, Any] | None) -> dict[str, Any] | None: |
| 536 | + """SDK >= 0.32 selects the optimizer family at model creation; SkyRL only has Adam.""" |
| 537 | + if optimizer_config is not None and optimizer_config.get("type", "adamw") != "adamw": |
| 538 | + raise ValueError( |
| 539 | + f"Unsupported optimizer_config type {optimizer_config.get('type')!r}; only 'adamw' is supported" |
| 540 | + ) |
| 541 | + return optimizer_config |
| 542 | + |
| 543 | + |
534 | 544 | class CreateModelRequest(BaseModel): |
535 | 545 | session_id: str |
536 | 546 | base_model: str |
537 | 547 | lora_config: LoRAConfig |
538 | 548 | model_role: str = "policy" |
| 549 | + optimizer_config: dict[str, Any] | None = None |
| 550 | + |
| 551 | + _check_optimizer = field_validator("optimizer_config")(_validate_adam_optimizer_config) |
539 | 552 |
|
540 | 553 |
|
541 | 554 | class CreateModelResponse(BaseModel): |
@@ -762,9 +775,21 @@ def to_types(self) -> types.AdamParams: |
762 | 775 |
|
763 | 776 | class OptimStepRequest(BaseModel): |
764 | 777 | model_id: str |
765 | | - adam_params: AdamParams |
| 778 | + adam_params: AdamParams | None = None |
| 779 | + # SDK >= 0.32 sends non-Adam families (e.g. Dimuon) under this key instead. |
| 780 | + optimizer_params: dict[str, Any] | None = None |
766 | 781 | seq_id: int | None = None |
767 | 782 |
|
| 783 | + @model_validator(mode="after") |
| 784 | + def validate_adam_only(self): |
| 785 | + if self.optimizer_params is not None: |
| 786 | + raise ValueError( |
| 787 | + f"Unsupported optimizer_params type {self.optimizer_params.get('type')!r}; only Adam (adam_params) is supported" |
| 788 | + ) |
| 789 | + if self.adam_params is None: |
| 790 | + raise ValueError("adam_params is required") |
| 791 | + return self |
| 792 | + |
768 | 793 |
|
769 | 794 | class SaveWeightsForSamplerRequest(BaseModel): |
770 | 795 | model_id: str |
@@ -832,8 +857,31 @@ class SampleRequest(BaseModel): |
832 | 857 | seq_id: int | None = None |
833 | 858 | prompt_logprobs: bool | None = None |
834 | 859 | topk_prompt_logprobs: int = Field(default=0, ge=0) |
| 860 | + # SDK >= 0.32 / 0.33 request options SkyRL's engines do not produce. They |
| 861 | + # are rejected rather than ignored, since the SDK would otherwise hand the |
| 862 | + # caller None for data it asked for. |
| 863 | + topk_sample_logprobs: int = 0 |
| 864 | + target_prompt_logprobs: Any | None = None |
| 865 | + prompt_alt_tokens_k: int = 0 |
| 866 | + prompt_logprobs_last_n: int | None = None |
835 | 867 | type: Literal["sample"] = "sample" |
836 | 868 |
|
| 869 | + @model_validator(mode="after") |
| 870 | + def validate_unsupported_options(self): |
| 871 | + unsupported = [ |
| 872 | + name |
| 873 | + for name, value in ( |
| 874 | + ("topk_sample_logprobs", self.topk_sample_logprobs), |
| 875 | + ("target_prompt_logprobs", self.target_prompt_logprobs), |
| 876 | + ("prompt_alt_tokens_k", self.prompt_alt_tokens_k), |
| 877 | + ("prompt_logprobs_last_n", self.prompt_logprobs_last_n), |
| 878 | + ) |
| 879 | + if value not in (0, None) |
| 880 | + ] |
| 881 | + if unsupported: |
| 882 | + raise ValueError(f"Unsupported sampling options: {unsupported}") |
| 883 | + return self |
| 884 | + |
837 | 885 | @model_validator(mode="after") |
838 | 886 | def validate_model_source(self): |
839 | 887 | """Valid if: |
@@ -862,8 +910,11 @@ class LoadWeightsRequest(BaseModel): |
862 | 910 | path: str |
863 | 911 | optimizer: bool = True |
864 | 912 | seq_id: int | None = None |
| 913 | + optimizer_config: dict[str, Any] | None = None |
865 | 914 | type: Literal["load_weights"] | None = None |
866 | 915 |
|
| 916 | + _check_optimizer = field_validator("optimizer_config")(_validate_adam_optimizer_config) |
| 917 | + |
867 | 918 |
|
868 | 919 | class FutureResponse(BaseModel): |
869 | 920 | future_id: str |
@@ -1498,7 +1549,7 @@ async def optim_step(request: OptimStepRequest, session: AsyncSession = Depends( |
1498 | 1549 | session=session, |
1499 | 1550 | request_type=types.RequestType.OPTIM_STEP, |
1500 | 1551 | model_id=request.model_id, |
1501 | | - request_data=types.OptimStepInput(adam_params=request.adam_params.to_types()), |
| 1552 | + request_data=types.OptimStepInput(adam_params=request.adam_params.to_types()), # validated non-None |
1502 | 1553 | seq_id=request.seq_id, |
1503 | 1554 | ) |
1504 | 1555 |
|
|
0 commit comments