Conversation
The AWS/GCP compute keys name VM instance types, which K8s-stack clouds (AKS/EKS/GKE) cannot honor — the backend currently flattens them to a head-only CPU pod at launch. A declarative k8s.yaml (required_resources) instead resolves server-side into free pods, needing no registered instance types, and one config covers every K8s stack — hence a single K8S key rather than per-provider ones. Pilots span the shape space: single node + auto-select (workspace-intro), multi-node CPU (getting-started), multi-node GPU (pytorch-fsdp, 2x T4 — chosen because Azure GPU pools have no L4/A10G). The validator keeps k8s.yaml declarative (instance_type is an error) so the file class cannot drift back into per-cluster registration dependencies. Claude-Session: https://claude.ai/code/session_013BsS5dSk6s6xsLx2FbBy7g Signed-off-by: Aydin Abiar <aydin@anyscale.com>
A non-dict node (e.g. a bare string in worker_nodes) crashed the hook
with a traceback before collected errors printed — pydantic already
reports those, so skip them here. And required_resources: {} slipped
past the is-None guard while the SDK rejects it at deploy time; treat
empty as missing.
Claude-Session: https://claude.ai/code/session_013BsS5dSk6s6xsLx2FbBy7g
Signed-off-by: Aydin Abiar <aydin@anyscale.com>
Two authoring errors vs the declarative contract (docs.anyscale.com/configuration/compute/declarative + product compute_templates.py check_gpu_accelerator_consistency): auto_select_worker_config is not compatible with declarative configs, and the GPU type must ride in required_labels (ray.io/accelerator-type) — the launch-time GPU validation never reads required_resources.accelerator, so the previous form would have been rejected at launch. basic-single-node becomes an explicit single cpu_worker group, heads slim to coordinator sizing (4CPU/8Gi, they are CPU: 0), the FSDP GPU workers keep the AWS-proven g4dn.xlarge shape (4CPU/16Gi/1xT4). The hook now enforces all three rules so the next k8s.yaml can't repeat this. Claude-Session: https://claude.ai/code/session_013BsS5dSk6s6xsLx2FbBy7g Signed-off-by: Aydin Abiar <aydin@anyscale.com>
elliot-barn
reviewed
Aug 14, 2026
| @@ -0,0 +1,12 @@ | |||
| head_node: | |||
Contributor
There was a problem hiding this comment.
can we rename them to azure.yaml to match the convention of the other templates?
This was referenced Sep 17, 2026
elliot-barn
added a commit
to ray-project/rayci
that referenced
this pull request
Sep 17, 2026
A K8s-stack cloud (AKS/EKS/GKE) cannot honor a named instance type:
those are per-cluster registrations, while `required_resources`
resolves server-side into a free pod. So whatever a template puts under
the K8S key is packaged declaratively:
K8S: configs/<t>/k8s.yaml the authored config, put through the same
translation -- a no-op when it is already
declarative, and it converts any
instance_type the file still names
K8S: auto derived from the template's AWS config
(no K8S key) no K8S config, exactly as before
AWS and GCP are packaged as they always have been. Deriving is opt-in
per template, and a template that asks for one and cannot have it fails
the build rather than quietly shipping without.
What "declarative" means here
-----------------------------
Nothing in the schema flags it. ComputeNodeType carries instance_type
and required_resources both as optional -- "Optional when using free
pod shapes with required_resources" -- and which one is set is the
whole difference. So the decision is per node, on what that node
carries, never on a filename or a provider key, and a node that is
already declarative passes through untouched. The docs make this a
rule: "Each node group must use either predefined instances or
declarative syntax, not both"
(docs.anyscale.com/configuration/compute/declarative), so a node
setting both is reported rather than silently half-honored.
Translation
-----------
- instance_type -> required_resources {CPU, memory, GPU} from a
hand-maintained shape table; an unlisted type is reported, not
guessed, since a wrong shape is a pod that never schedules.
- GPU instances also get required_labels ray.io/accelerator-type; the
launch-time GPU validation reads the label, never
required_resources.accelerator. Values stay inside the documented
set, which a test enforces.
- An unschedulable head (CPU: 0) only coordinates, so it gets
coordinator sizing rather than the whole machine.
- auto_select_worker_config has no declarative form, and it is the
most common template shape (21 templates share one auto-select
config). One group shaped like the head's machine stands in, the
same substitution the hand-written single-node config makes. This is
the only invented shape here.
Memory is emitted as integer bytes: the published bundle is parsed
straight into the backend's PhysicalResources, whose memory is an int.
Test
----
The rules were derived from the three hand-written pilots in
anyscale/templates#940 and reproduce them. Building that branch with
`K8S: auto` in place of each authored path gives a config
byte-identical to the authored getting-started/k8s.yaml and
basic-single-node/k8s.yaml, and identical to pytorch-fsdp/k8s.yaml
except the worker group name, which is carried over from the VM config
rather than renamed. Those three are golden tests here.
Also verified end to end on anyscale/templates: an authored k8s.yaml
round-trips unchanged with AWS and GCP byte-identical, `K8S: auto`
derives, and a template with no K8S key is untouched.
go test ./rayapp/ passes, coverage 87.0% (gate 80%); gofmt and go vet
clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Ray CI Test <rayci@ray.io>
elliot-barn
added a commit
to ray-project/rayci
that referenced
this pull request
Sep 18, 2026
The API takes `required_resources.memory` as an integer number of bytes,
but every user-facing surface advertises Kubernetes quantity strings
("8Gi"). The SDK reconciles those in PhysicalResources.to_dict(for_api=
True) -> _parse_memory_string; the published bundle never touches the
SDK, since rayapp copies required_resources verbatim into ray-app.json
and the console clone path parses it straight into the backend's
PhysicalResources, whose `memory` is an Optional[int]. So "8Gi" passes
`anyscale compute-config create -f` and then 422s at launch:
body.config.head_node_type.required_resources.memory:
value is not a valid integer
Convert in the Go mirror too, so the two conversion paths agree and a
template can be authored the way the docs describe.
Parse with k8s.io/apimachinery's resource.ParseQuantity -- the same
parser Kubernetes uses, and the one the SDK reaches through
kubernetes.utils.quantity -- rather than hand-rolling the grammar, so
the accepted syntax cannot drift from what the docs describe. Pinned to
apimachinery v0.34.0, which needs only go 1.24; v0.37 would drag the go
directive to 1.26 and bump x/net.
Three rules on top of the parser, each because the value has to land in
an integer bytes field:
- Whole bytes only. Kubernetes tolerates a fraction ("100m" is 100
*milli*bytes) but its own docs say "this isn't useful to specify
since you must always assign whole numbers of bytes", and a fraction
could not survive PhysicalResources.memory anyway. This also removes
the one place the two parsers disagree: ParseQuantity().Value()
rounds up where the SDK's int(Decimal) truncates, and that only
differs for values we now reject.
- No negatives. Nothing downstream rejects them -- the backend only
range-checks int64 -- so a negative would reach launch as a nonsense
pod request. YAML parses `memory: -1` as an int, so the integer path
needs this too.
- Must contain a digit. ParseQuantity reads a bare suffix ("Gi", "k",
".") as zero, so a typo would silently become no memory at all. The
SDK rejects those.
TestParseMemoryBytesMatchesSDK runs every golden through the SDK
function this mirrors and compares, so the two cannot drift unnoticed;
it skips where python3 or the anyscale package is unavailable.
No effect on VM configs: this fires only on required_resources.memory,
which instance-type configs (every aws.yaml/gce.yaml in
anyscale/templates today) never set. Building anyscale/templates#940
with this binary gives byte-identical AWS and GCP configs and converts
only the K8S memory values.
Test: go test ./rayapp/ passes, coverage 87.5% (gate 80%); gofmt and go
vet clean. golangci-lint panics on this package on a clean tree too
(toolchain mismatch: built with go1.25, sources need go1.26).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Ray CI Test <rayci@ray.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an optional
K8S:compute-config key pointing at a declarativek8s.yaml—required_resourcesshapes resolved server-side into free pods, no registered instance types, one config covering AKS/EKS/GKE. Three pilots span the shape space:workspace-intro(single node),getting-started(multi-node CPU),pytorch-fsdp(multi-node GPU, 2×T4 viarequired_labels: {ray.io/accelerator-type: T4}— Azure GPU pools have no L4/A10G). The validator accepts the new key and enforces the declarative contract: noinstance_type, noauto_select_worker_config(incompatible with declarative configs), and GPU counts paired with the accelerator-type label (launch validation reads the label, not arequired_resources.acceleratorkey).Groundwork for the Azure/K8s template path:
rayapp buildalready carries the key into bundles as-is; the product launch path selecting + honoring it lands separately (today it force-selects AWS and flattens on K8s clouds), so the key is inert in prod until then.Testing
pre-commit run --all-filesgreen (BUILD.yaml validator gate included).rayapp build: all 56 bundles build; the three pilots'ray-app.jsoncarry theK8Sconfig withrequired_resources+required_labelspreserved verbatim.anyscale.compute_config.models.ComputeConfig(instance_type→custom).check_gpu_accelerator_consistency(GPU count must pair with theray.io/accelerator-typelabel; auto-select unsupported with declarative).instance_type,auto_select_worker_config, GPU without label,acceleratorinsiderequired_resources, emptyrequired_resources, non-dict nodes, cross-dir K8S path, wrong filename — each fails with a targeted error.https://claude.ai/code/session_013BsS5dSk6s6xsLx2FbBy7g