Skip to content

Commit dd702af

Browse files
[raycicmd] Apply array adjustments during expansion (#464)
Modify expandSingleArrayStep to parse adjustments from inside the array definition and apply them in two passes: additions are appended first, then skipped elements are filtered out. Store the final element list on arrayConfig so depends_on resolution uses post-adjustment elements, ensuring skipped elements are excluded and added elements are included. Update array-test.rayci.yaml with adjustment examples. Topic: apply-adjustments Relative: parse-adjustments Signed-off-by: andrew <andrew@anyscale.com> Signed-off-by: andrew <andrew@anyscale.com>
1 parent c00f147 commit dd702af

6 files changed

Lines changed: 430 additions & 8 deletions

File tree

.buildkite/array-test.Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# syntax=docker/dockerfile:1.3-labs
2+
FROM ubuntu:22.04
3+
4+
ARG PYTHON_VERSION
5+
ARG CUDA_VERSION
6+
7+
# Validate that only expected combinations are built.
8+
RUN <<'EOF'
9+
#!/bin/bash
10+
set -euo pipefail
11+
12+
valid=false
13+
if [[ "$PYTHON_VERSION" = "3.12" && "$CUDA_VERSION" = "13.0.0-cudnn" ]]; then
14+
valid=true
15+
fi
16+
if [[ "$PYTHON_VERSION" = "3.11" && "$CUDA_VERSION" = "12.8.1-cudnn" ]]; then
17+
valid=true
18+
fi
19+
20+
if [[ "$valid" != "true" ]]; then
21+
echo "ERROR: unexpected combination py${PYTHON_VERSION}+cu${CUDA_VERSION}"
22+
exit 1
23+
fi
24+
25+
echo "py${PYTHON_VERSION}+cu${CUDA_VERSION}" > /image-info.txt
26+
EOF

.buildkite/array-test.rayci.yaml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ steps:
88
key: array-step
99
command: |
1010
echo "Running os={{array.os}} variant={{array.variant}}"
11-
if [ "{{array.os}}" != "linux" ] && [ "{{array.os}}" != "macos" ]; then
11+
if [[ "{{array.os}}" != "linux" && "{{array.os}}" != "macos" ]]; then
1212
echo "ERROR: unexpected os={{array.os}}"
1313
exit 1
1414
fi
15-
if [ "{{array.variant}}" != "1" ]; then
15+
if [[ "{{array.variant}}" != "1" ]]; then
1616
echo "ERROR: unexpected variant={{array.variant}}"
1717
exit 1
1818
fi
@@ -21,6 +21,35 @@ steps:
2121
os: ["linux", "windows", "macos"]
2222
variant: ["1", "2"]
2323

24+
- name: array-cuda-build
25+
label: "wanda: cuda py{{array.python}} cu{{array.cuda}}"
26+
wanda: .buildkite/array-test.wanda.yaml
27+
env:
28+
PYTHON_VERSION: "{{array.python}}"
29+
CUDA_VERSION: "{{array.cuda}}"
30+
depends_on: forge
31+
array:
32+
python: ["3.12"]
33+
cuda: ["13.0.0-cudnn"]
34+
adjustments:
35+
- with:
36+
python: "3.11"
37+
cuda: "12.8.1-cudnn"
38+
39+
# Verify that the expected images were built
40+
- label: "Verify cuda images"
41+
key: verify-cuda-images
42+
command: |
43+
tag="${RAYCI_WORK_REPO}:${RAYCI_BUILD_ID}"
44+
expected="array-test-py3.12-cu13.0.0-cudnn array-test-py3.11-cu12.8.1-cudnn"
45+
for name in $expected; do
46+
echo "Checking ${tag}-${name} ..."
47+
docker manifest inspect "${tag}-${name}"
48+
done
49+
depends_on:
50+
- array-cuda-build
51+
- forge
52+
2453
# Depends on only the linux-1 and macos-1 variants
2554
- label: "Depends check only"
2655
key: depends-on-variants
@@ -35,4 +64,6 @@ steps:
3564
tags: always
3665
key: final-validation
3766
command: echo "Trigger check"
38-
depends_on: depends-on-variants
67+
depends_on:
68+
- depends-on-variants
69+
- verify-cuda-images

.buildkite/array-test.wanda.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: "array-test-py$PYTHON_VERSION-cu$CUDA_VERSION"
2+
froms:
3+
- ubuntu:22.04
4+
dockerfile: .buildkite/array-test.Dockerfile
5+
build_args:
6+
- PYTHON_VERSION
7+
- CUDA_VERSION

raycicmd/array_config.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"strings"
88
)
99

10-
// arrayConfig represents a parsed array definition.
10+
// arrayConfig represents a parsed array definition. After expansion,
11+
// elements holds the final list (post-adjustments).
1112
type arrayConfig struct {
12-
dims map[string][]string // dimension name -> values
13+
dims map[string][]string // dimension name -> values
14+
elements []*arrayElement // populated during expansion
1315
}
1416

1517
// arrayElement is one combination from expand().

raycicmd/array_expand.go

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package raycicmd
22

33
import (
44
"fmt"
5+
"maps"
56
"slices"
67
)
78

@@ -113,6 +114,27 @@ func expandSingleArrayStep(
113114
)
114115
}
115116

117+
// Parse and apply adjustments if present.
118+
var adjustments []*arrayAdjustment
119+
if arrayMap, ok := arrayDef.(map[string]any); ok {
120+
if adjDef, ok := arrayMap["adjustments"]; ok {
121+
adjustments, err = parseArrayAdjustments(adjDef)
122+
if err != nil {
123+
return nil, fmt.Errorf(
124+
"step %q: %w", baseKey, err,
125+
)
126+
}
127+
}
128+
}
129+
130+
elements, err = applyAdjustments(elements, adjustments, cfg)
131+
if err != nil {
132+
return nil, fmt.Errorf(
133+
"step %q: %w", baseKey, err,
134+
)
135+
}
136+
137+
cfg.elements = elements
116138
configs[baseKey] = cfg
117139

118140
var result []map[string]any
@@ -137,6 +159,78 @@ func expandSingleArrayStep(
137159
return result, nil
138160
}
139161

162+
// applyAdjustments processes adjustments in two passes: first appends
163+
// additions, then filters out skipped elements.
164+
func applyAdjustments(
165+
elements []*arrayElement,
166+
adjustments []*arrayAdjustment,
167+
cfg *arrayConfig,
168+
) ([]*arrayElement, error) {
169+
// Pass 1: append additions.
170+
for _, adj := range adjustments {
171+
if adj.skip {
172+
continue
173+
}
174+
for dim := range cfg.dims {
175+
if _, ok := adj.with[dim]; !ok {
176+
return nil, fmt.Errorf(
177+
"addition adjustment must specify all "+
178+
"dimensions; missing %q", dim,
179+
)
180+
}
181+
}
182+
elements = append(elements, &arrayElement{
183+
values: maps.Clone(adj.with),
184+
})
185+
}
186+
187+
// Pass 2: collect skip adjustments, validate, then remove.
188+
var skipAdjs []*arrayAdjustment
189+
for _, adj := range adjustments {
190+
if adj.skip {
191+
skipAdjs = append(skipAdjs, adj)
192+
}
193+
}
194+
for _, adj := range skipAdjs {
195+
if !hasElement(elements, adj.with) {
196+
return nil, fmt.Errorf(
197+
"skip adjustment with=%v matches no element",
198+
adj.with,
199+
)
200+
}
201+
}
202+
elements = slices.DeleteFunc(elements, func(e *arrayElement) bool {
203+
for _, adj := range skipAdjs {
204+
if elemMatchesWith(e, adj.with) {
205+
return true
206+
}
207+
}
208+
return false
209+
})
210+
211+
return elements, nil
212+
}
213+
214+
func hasElement(
215+
elements []*arrayElement, with map[string]string,
216+
) bool {
217+
for _, elem := range elements {
218+
if elemMatchesWith(elem, with) {
219+
return true
220+
}
221+
}
222+
return false
223+
}
224+
225+
func elemMatchesWith(elem *arrayElement, with map[string]string) bool {
226+
for dim, val := range with {
227+
if elem.values[dim] != val {
228+
return false
229+
}
230+
}
231+
return true
232+
}
233+
140234
// arraySelector represents a dependency selector with optional array filter.
141235
type arraySelector struct {
142236
key string // step identifier: "key" for command steps, "name" for wanda steps
@@ -271,7 +365,8 @@ func resolveDependsOn(dependsOn any, configs map[string]*arrayConfig) ([]string,
271365
return result, nil
272366
}
273367

274-
// resolveArraySelector resolves a selector against an array config to concrete step keys.
368+
// resolveArraySelector resolves a selector against an array config
369+
// to concrete step keys, using the final element list (post-adjustments).
275370
func resolveArraySelector(sel *arraySelector, cfg *arrayConfig) ([]string, error) {
276371
for dim := range sel.filter {
277372
if _, ok := cfg.dims[dim]; !ok {
@@ -282,9 +377,8 @@ func resolveArraySelector(sel *arraySelector, cfg *arrayConfig) ([]string, error
282377
}
283378
}
284379

285-
elements := cfg.expand()
286380
var matches []string
287-
for _, elem := range elements {
381+
for _, elem := range cfg.elements {
288382
if elem.matchesSelector(sel) {
289383
matches = append(matches, elem.generateKey(sel.key))
290384
}

0 commit comments

Comments
 (0)