|
1 | 1 | # Pipeline Generator |
2 | 2 |
|
3 | | -Python replacement for all three Jinja templates that generate Buildkite CI pipelines for vLLM: |
4 | | -- `test-template-ci.j2` (full CI pipeline) |
5 | | -- `test-template-fastcheck.j2` (fast pre-merge checks) |
6 | | -- `test-template-amd.j2` (AMD-only pipeline) |
| 3 | +Simple Python replacement for Jinja templates that generate Buildkite CI pipelines for vLLM. |
7 | 4 |
|
8 | 5 | ## Quick Start |
9 | 6 |
|
10 | 7 | ```bash |
11 | 8 | # CI mode (default) |
12 | | -python pipeline_generator.py --pipeline_mode ci |
| 9 | +python -m pipeline_generator --pipeline_mode ci |
13 | 10 |
|
14 | 11 | # Fastcheck mode |
15 | | -python pipeline_generator.py --pipeline_mode fastcheck |
| 12 | +python -m pipeline_generator --pipeline_mode fastcheck |
16 | 13 |
|
17 | 14 | # AMD mode |
18 | | -python pipeline_generator.py --pipeline_mode amd |
| 15 | +python -m pipeline_generator --pipeline_mode amd |
19 | 16 | ``` |
20 | 17 |
|
| 18 | +## Architecture |
21 | 19 |
|
22 | | -## Directory Structure |
| 20 | +Simple, readable code matching Jinja template complexity: |
23 | 21 |
|
24 | 22 | ``` |
25 | 23 | pipeline_generator/ |
26 | | -├── pipeline_generator.py # Main entry point |
27 | | -├── pipeline_config.py # Configuration |
28 | | -├── docker_build_configs.py # Build step configs |
29 | | -├── hardware_test_configs.py # Hardware test configs |
30 | | -├── pyproject.toml # Ruff & mypy config |
| 24 | +├── pipeline_generator.py # Main entry point |
| 25 | +├── config.py # All constants and configuration |
| 26 | +├── models.py # TestStep input model only |
31 | 27 | │ |
32 | | -├── core/ # Unified modules (mode-aware) |
33 | | -│ ├── docker_builds.py # Build step generation |
34 | | -│ ├── docker_plugins.py # Plugin construction |
35 | | -│ ├── test_step_converter.py # Convert tests to Buildkite steps |
36 | | -│ ├── test_filtering.py # Test selection logic |
37 | | -│ ├── manual_trigger_rules.py # Blocking logic |
38 | | -│ ├── amd_tests.py # AMD test group |
39 | | -│ └── hardware_tests.py # Hardware test generation |
| 28 | +├── modes/ # One file per mode (simple dict generation) |
| 29 | +│ ├── ci.py # CI pipeline (~630 lines) |
| 30 | +│ ├── fastcheck.py # Fastcheck pipeline (~520 lines) |
| 31 | +│ └── amd.py # AMD pipeline (~60 lines) |
40 | 32 | │ |
41 | | -├── ci/ # CI orchestration |
42 | | -│ ├── ci_pipeline.py # Main CI orchestration |
43 | | -│ └── torch_nightly_tests.py # Torch nightly group (CI only) |
44 | | -│ |
45 | | -├── fastcheck/ # Fastcheck orchestration |
46 | | -│ └── fastcheck_pipeline.py # Main fastcheck orchestration |
47 | | -│ |
48 | | -├── data_models/ # Pydantic data models |
49 | | -│ ├── test_step.py # Input from test-pipeline.yaml |
50 | | -│ ├── buildkite_step.py # Output for Buildkite |
51 | | -│ └── docker_config.py # Docker/K8s configs |
52 | | -│ |
53 | | -├── command_builders/ # Command transformations (CI only) |
54 | | -│ ├── normalizer.py # Flatten & normalize commands |
55 | | -│ ├── intelligent_test_selection.py # Intelligent test targeting |
56 | | -│ └── coverage_injection.py # Coverage injection |
57 | | -│ |
58 | | -├── utils/ # Shared utilities & constants |
59 | | -│ ├── constants.py # All constants (build keys, queues, labels, etc.) |
60 | | -│ ├── agent_queues.py # Agent queue selection |
61 | | -│ ├── command_utils.py # Command helpers |
62 | | -│ └── amd_command_builder.py # AMD command formatting |
63 | | -│ |
64 | | -└── tests/ # Test suite |
65 | | - ├── test_*.py # 125 unit tests |
66 | | - ├── test_integration_comprehensive.py # 56 CI scenarios |
67 | | - └── test_integration_fastcheck.py # 8 fastcheck scenarios |
| 33 | +└── helpers/ # Simple utilities |
| 34 | + ├── builds.py # Build step dicts |
| 35 | + ├── commands.py # Command normalization |
| 36 | + ├── coverage.py # Coverage injection (complex) |
| 37 | + └── test_selection.py # Intelligent test targeting (complex) |
68 | 38 | ``` |
69 | 39 |
|
70 | | -## Main Flow |
| 40 | +## Design Philosophy |
| 41 | + |
| 42 | +- **Simple over clever**: Each mode file reads top-to-bottom like its Jinja template |
| 43 | +- **Direct dict construction**: Use f-strings to build YAML dicts, no abstraction layers |
| 44 | +- **Helper functions only where complex**: Coverage and test selection logic is genuinely complex (exists in Jinja too) |
| 45 | +- **No Pydantic output models**: Only use Pydantic for input parsing (TestStep) |
71 | 46 |
|
72 | | -The `pipeline_generator.py` orchestrates everything: |
| 47 | +## Example Code |
73 | 48 |
|
74 | 49 | ```python |
75 | | -def generate(self, test_steps): |
76 | | - steps = [] |
77 | | - |
78 | | - # Build Docker images |
79 | | - steps.append(generate_main_build_step(self.config)) |
80 | | - steps.extend(generate_cu118_build_steps(self.config)) |
81 | | - steps.append(generate_cpu_build_step(self.config)) |
82 | | - |
83 | | - # Generate test steps |
84 | | - steps.extend(self.generate_test_steps(test_steps)) |
85 | | - |
86 | | - # Add special groups |
87 | | - steps.append(generate_torch_nightly_group(test_steps, self.config)) |
88 | | - steps.append(generate_amd_group(test_steps, self.config)) |
89 | | - |
90 | | - # Add external hardware tests |
91 | | - steps.extend(generate_all_hardware_tests(self.config.branch, self.config.nightly)) |
92 | | - |
93 | | - return steps |
| 50 | +def generate_test_step(test, config): |
| 51 | + """Generate a test step - simple dict construction.""" |
| 52 | + return { |
| 53 | + "label": test.label, |
| 54 | + "agents": {"queue": get_queue(test)}, |
| 55 | + "plugins": [{ |
| 56 | + "docker#v5.2.0": { |
| 57 | + "image": config.container_image, |
| 58 | + "command": ["bash", "-xc", build_command(test, config)], |
| 59 | + "environment": ["VLLM_USAGE_SOURCE=ci-test", "HF_TOKEN"], |
| 60 | + } |
| 61 | + }], |
| 62 | + "depends_on": "image-build", |
| 63 | + } |
94 | 64 | ``` |
95 | 65 |
|
96 | | -Most logic lives in unified modules in `core/` that use `config.pipeline_mode` to branch between CI and Fastcheck behavior. The `ci/` and `fastcheck/` directories only contain orchestration code. |
97 | | - |
98 | | -## Where to Find Things |
99 | | - |
100 | | -**Core logic (mode-aware):** |
101 | | -- Build steps: `core/docker_builds.py` |
102 | | -- Plugin construction: `core/docker_plugins.py` |
103 | | -- Test filtering: `core/test_filtering.py` and `core/manual_trigger_rules.py` |
104 | | -- Test conversion: `core/test_step_converter.py` |
105 | | -- AMD tests: `core/amd_tests.py` |
106 | | -- Hardware tests: `core/hardware_tests.py` |
107 | | - |
108 | | -**Pipeline orchestration:** |
109 | | -- CI: `ci/ci_pipeline.py` (uses core/) |
110 | | -- Fastcheck: `fastcheck/fastcheck_pipeline.py` (uses core/) |
111 | | -- Torch nightly: `ci/torch_nightly_tests.py` (CI only) |
112 | | - |
113 | | -**Shared code:** |
114 | | -- Constants: `utils/constants.py` |
115 | | -- Data models: `data_models/` |
116 | | -- Config files: `*_config.py` at root |
117 | | - |
118 | 66 | ## Testing |
119 | 67 |
|
120 | | -Run unit tests: |
121 | | -```bash |
122 | | -python -m pytest tests/ -k "not integration" -v |
123 | | -``` |
| 68 | +All integration tests verify byte-for-byte YAML compatibility with Jinja templates: |
124 | 69 |
|
125 | | -Run integration tests (verifies 100% compatibility with Jinja): |
126 | 70 | ```bash |
127 | | -# All integration tests via pytest |
| 71 | +# All integration tests (64 scenarios) |
128 | 72 | pytest tests/test_integration_comprehensive.py tests/test_integration_fastcheck.py |
129 | 73 |
|
130 | | -# Or run specific scenario |
131 | | -pytest tests/test_integration_comprehensive.py -k "coverage" |
| 74 | +# Unit tests |
| 75 | +pytest tests/ -k "not integration" |
132 | 76 | ``` |
133 | 77 |
|
134 | | -**Status**: CI and Fastcheck modes achieve 100% YAML compatibility with their respective Jinja templates. |
| 78 | +**Status**: ✅ 100% YAML compatibility verified (64/64 scenarios pass) |
135 | 79 |
|
136 | 80 | ## How It Works |
137 | 81 |
|
138 | | -### Input: test-pipeline.yaml |
139 | | - |
140 | | -```yaml |
141 | | -steps: |
142 | | - - label: "Basic Tests" |
143 | | - commands: |
144 | | - - pytest tests/basic/ |
145 | | - source_file_dependencies: |
146 | | - - vllm/engine/ |
147 | | -``` |
148 | | -
|
149 | | -### Processing |
150 | | -
|
151 | | -1. Parse YAML into `TestStep` objects (Pydantic models) |
152 | | -2. For each test, decide if it should run or be blocked |
153 | | -3. Convert to `BuildkiteStep` with appropriate Docker plugin |
154 | | -4. Apply command transformations |
155 | | -5. Add build steps, special groups, hardware tests |
156 | | -6. Write final pipeline YAML |
157 | | - |
158 | | -### Output: pipeline.yaml |
159 | | - |
160 | | -```yaml |
161 | | -steps: |
162 | | - - label: "Build vLLM Image" |
163 | | - key: "image-build" |
164 | | - # ... build configuration |
165 | | - |
166 | | - - label: "Basic Tests" |
167 | | - depends_on: "image-build" |
168 | | - agents: {queue: "gpu_1_queue"} |
169 | | - plugins: |
170 | | - - docker#v5.2.0: |
171 | | - image: "public.ecr.aws/..." |
172 | | - command: ["bash", "-xc", "cd /vllm-workspace/tests && pytest tests/basic/"] |
173 | | -``` |
174 | | - |
175 | | -## Backward Compatibility |
176 | | - |
177 | | -The Python generator produces identical YAML to the Jinja template. This is verified by `test_integration_comprehensive.py`, which runs 56 test scenarios covering: |
178 | | - |
179 | | -- Different branches (main vs PR) |
180 | | -- Run all vs selective testing |
181 | | -- Nightly mode |
182 | | -- File change detection |
183 | | -- Coverage injection |
184 | | -- Intelligent test filtering |
185 | | -- Multi-node/GPU configurations |
186 | | -- Optional tests |
187 | | -- And more... |
188 | | - |
189 | | -All 56 scenarios must produce byte-for-byte identical YAML. |
190 | | - |
191 | | -## Contributing |
192 | | - |
193 | | -When making changes: |
194 | | - |
195 | | -1. Write tests first (in `tests/`) |
196 | | -2. Make your changes |
197 | | -3. Run unit tests: `python -m pytest tests/ -k "not integration"` |
198 | | -4. Run integration tests: `python tests/test_integration_comprehensive.py` |
199 | | -5. Both must pass before merging |
| 82 | +1. Read `test-pipeline.yaml` → Parse into TestStep objects |
| 83 | +2. Generate mode-specific pipeline → Simple dicts with f-strings |
| 84 | +3. Write `pipeline.yaml` → Direct YAML dump |
200 | 85 |
|
201 | | -The integration test is non-negotiable - it ensures we don't break existing pipelines. |
| 86 | +No plugin builders, no converters, no abstraction - just straightforward code. |
0 commit comments