A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 9, 07:30 UTC
[Kernel] [Helion] [1/N] Add Helion ConfigManager (#32740)
Signed-off-by: Yanan Cao <gmagogsfm@gmail.com>
Yanan Cao committed
Jan 30, 2026 at 09:19 UTC
6c1f9e4c1877cb5cb6a702f5813e606eaba35600
4 files changed
+608
tests/kernels/helion/test_config_manager.py
new
+361
@@ -0,0 +1,361 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""
4
+Unit tests for Helion ConfigManager and ConfigSet.
5
+
6
+Tests the simplified configuration management system for Helion custom kernels.
7
+"""
8
+
9
+import json
10
+import tempfile
11
+from pathlib import Path
12
+
13
+import pytest
14
+
15
+from vllm.utils.import_utils import has_helion
16
+
17
+# Skip entire module if helion is not available
18
+if not has_helion():
19
+ pytest.skip(
20
+ "Helion is not installed. Install with: pip install vllm[helion]",
21
+ allow_module_level=True,
22
+ )
23
+
24
+import helion
25
+
26
+from vllm.kernels.helion.config_manager import (
27
+ ConfigManager,
28
+ ConfigSet,
29
+)
30
+
31
+
32
+@pytest.fixture(autouse=True)
33
+def reset_config_manager_singleton():
34
+ """Reset ConfigManager singleton before each test."""
35
+ ConfigManager.reset_instance()
36
+ yield
37
+ ConfigManager.reset_instance()
38
+
39
+
40
+class TestConfigSet:
41
+ """Test suite for ConfigSet class."""
42
+
43
+ def test_config_set_creation(self):
44
+ """Test creating an empty ConfigSet."""
45
+ config_set = ConfigSet("test_kernel")
46
+
47
+ assert config_set.kernel_name == "test_kernel"
48
+ assert config_set.get_platforms() == []
49
+
50
+ def test_config_set_from_dict(self):
51
+ """Test creating ConfigSet from dictionary data."""
52
+ # Use realistic config data that helion.Config can handle
53
+ config_data = {
54
+ "block_sizes": [32, 16],
55
+ "num_warps": 4,
56
+ "num_stages": 3,
57
+ "pid_type": "persistent_interleaved",
58
+ }
59
+ data = {"h100": {"batch_32_hidden_4096": config_data}}
60
+
61
+ config_set = ConfigSet.from_dict("test_kernel", data)
62
+
63
+ assert config_set.kernel_name == "test_kernel"
64
+ assert config_set.get_platforms() == ["h100"]
65
+
66
+ # Verify the config was created correctly
67
+ config = config_set.get_config("h100", "batch_32_hidden_4096")
68
+ assert isinstance(config, helion.Config)
69
+ assert config.block_sizes == [32, 16]
70
+ assert config.num_warps == 4
71
+ assert config.num_stages == 3
72
+ assert config.pid_type == "persistent_interleaved"
73
+
74
+ def test_config_set_get_config_keyerror(self):
75
+ """Test that accessing non-existent configs raises informative KeyErrors."""
76
+ config_set = ConfigSet("test_kernel")
77
+
78
+ with pytest.raises(KeyError, match="platform 'h100' not found"):
79
+ config_set.get_config("h100", "batch_32_hidden_4096")
80
+
81
+ # Use realistic config data
82
+ config_data = {"num_warps": 8, "num_stages": 4}
83
+ data = {"h100": {"batch_64_hidden_2048": config_data}}
84
+ config_set = ConfigSet.from_dict("test_kernel", data)
85
+
86
+ with pytest.raises(
87
+ KeyError, match="config_key 'batch_32_hidden_4096' not found"
88
+ ):
89
+ config_set.get_config("h100", "batch_32_hidden_4096")
90
+
91
+ def test_config_set_get_platforms(self):
92
+ """Test get_platforms method."""
93
+ # Use realistic config data
94
+ config1 = {"num_warps": 4, "num_stages": 3}
95
+ config2 = {"num_warps": 8, "num_stages": 5}
96
+
97
+ data = {
98
+ "h100": {"batch_32_hidden_4096": config1},
99
+ "a100": {"batch_16_hidden_2048": config2},
100
+ }
101
+ config_set = ConfigSet.from_dict("test_kernel", data)
102
+
103
+ platforms = config_set.get_platforms()
104
+ assert platforms == ["a100", "h100"] # Should be sorted
105
+
106
+ def test_config_set_get_config_keys(self):
107
+ """Test get_config_keys method."""
108
+ # Use realistic config data
109
+ config1 = {"num_warps": 4, "num_stages": 3}
110
+ config2 = {"num_warps": 8, "num_stages": 5}
111
+
112
+ data = {
113
+ "h100": {
114
+ "batch_32_hidden_4096": config1,
115
+ "batch_64_hidden_2048": config2,
116
+ }
117
+ }
118
+ config_set = ConfigSet.from_dict("test_kernel", data)
119
+
120
+ config_keys = config_set.get_config_keys("h100")
121
+ assert config_keys == ["batch_32_hidden_4096", "batch_64_hidden_2048"]
122
+
123
+ assert config_set.get_config_keys("v100") == []
124
+
125
+ def test_config_set_to_dict(self):
126
+ """Test converting ConfigSet to dictionary."""
127
+ # Use realistic config data
128
+ original_config = {
129
+ "block_sizes": [64, 32],
130
+ "num_warps": 16,
131
+ "num_stages": 4,
132
+ "pid_type": "persistent_blocked",
133
+ }
134
+ original_data = {"h100": {"batch_32_hidden_4096": original_config}}
135
+
136
+ config_set = ConfigSet.from_dict("test_kernel", original_data)
137
+ result_data = config_set.to_dict()
138
+
139
+ # The result should match the original (Config roundtrip should work)
140
+ assert result_data == original_data
141
+
142
+
143
+class TestConfigManager:
144
+ """Test suite for ConfigManager class."""
145
+
146
+ def test_config_manager_creation_default_base_dir(self):
147
+ """Test creating ConfigManager with default base directory."""
148
+ manager = ConfigManager()
149
+ assert manager._base_dir.name == "configs"
150
+
151
+ def test_config_manager_creation_custom_base_dir(self):
152
+ """Test creating ConfigManager with custom base directory."""
153
+ custom_dir = "/tmp/custom_configs"
154
+ manager = ConfigManager(base_dir=custom_dir)
155
+
156
+ # Paths are resolved, so compare with resolved path
157
+ assert manager._base_dir == Path(custom_dir).resolve()
158
+
159
+ def test_get_config_file_path(self):
160
+ """Test getting config file path for a kernel."""
161
+ manager = ConfigManager(base_dir="/tmp")
162
+
163
+ file_path = manager.get_config_file_path("silu_mul_fp8")
164
+
165
+ expected_path = Path("/tmp/silu_mul_fp8.json")
166
+ assert file_path == expected_path
167
+
168
+ def test_ensure_base_dir_exists(self):
169
+ """Test ensuring base directory exists."""
170
+ with tempfile.TemporaryDirectory() as temp_dir:
171
+ base_dir = Path(temp_dir) / "non_existent" / "configs"
172
+ manager = ConfigManager(base_dir=base_dir)
173
+ assert not base_dir.exists()
174
+
175
+ returned_path = manager.ensure_base_dir_exists()
176
+
177
+ assert base_dir.exists()
178
+ assert base_dir.is_dir()
179
+ assert returned_path == base_dir
180
+
181
+ def test_load_config_set_file_not_exists(self):
182
+ """Test loading config set when file doesn't exist."""
183
+ with tempfile.TemporaryDirectory() as temp_dir:
184
+ manager = ConfigManager(base_dir=temp_dir)
185
+ config_set = manager.load_config_set("non_existent_kernel")
186
+
187
+ assert isinstance(config_set, ConfigSet)
188
+ assert config_set.kernel_name == "non_existent_kernel"
189
+ assert config_set.get_platforms() == []
190
+
191
+ def test_load_config_set_valid_file(self):
192
+ """Test loading config set from valid file."""
193
+ with tempfile.TemporaryDirectory() as temp_dir:
194
+ # Use realistic config data
195
+ kernel_config = {
196
+ "block_sizes": [128, 64],
197
+ "num_warps": 8,
198
+ "num_stages": 6,
199
+ "pid_type": "persistent_interleaved",
200
+ }
201
+ config_data = {"h100": {"batch_32_hidden_4096": kernel_config}}
202
+ config_file = Path(temp_dir) / "test_kernel.json"
203
+ with open(config_file, "w") as f:
204
+ json.dump(config_data, f)
205
+
206
+ manager = ConfigManager(base_dir=temp_dir)
207
+ config_set = manager.load_config_set("test_kernel")
208
+
209
+ assert isinstance(config_set, ConfigSet)
210
+ assert config_set.kernel_name == "test_kernel"
211
+ assert config_set.get_platforms() == ["h100"]
212
+
213
+ # Verify the config was loaded correctly
214
+ config = config_set.get_config("h100", "batch_32_hidden_4096")
215
+ assert isinstance(config, helion.Config)
216
+ assert config.block_sizes == [128, 64]
217
+ assert config.num_warps == 8
218
+
219
+ def test_load_config_set_invalid_json(self):
220
+ """Test loading config set from file with invalid JSON."""
221
+ with tempfile.TemporaryDirectory() as temp_dir:
222
+ config_file = Path(temp_dir) / "test_kernel.json"
223
+ with open(config_file, "w") as f:
224
+ f.write("invalid json content {")
225
+
226
+ manager = ConfigManager(base_dir=temp_dir)
227
+ config_set = manager.load_config_set("test_kernel")
228
+
229
+ assert isinstance(config_set, ConfigSet)
230
+ assert config_set.kernel_name == "test_kernel"
231
+ assert config_set.get_platforms() == []
232
+
233
+ def test_save_config_set(self):
234
+ """Test saving ConfigSet to file."""
235
+ with tempfile.TemporaryDirectory() as temp_dir:
236
+ # Use realistic config data
237
+ kernel_config = {
238
+ "block_sizes": [256, 128],
239
+ "num_warps": 16,
240
+ "num_stages": 8,
241
+ "pid_type": "persistent_blocked",
242
+ }
243
+ data = {"h100": {"batch_32_hidden_4096": kernel_config}}
244
+ config_set = ConfigSet.from_dict("test_kernel", data)
245
+
246
+ manager = ConfigManager(base_dir=temp_dir)
247
+ saved_path = manager.save_config_set(config_set)
248
+
249
+ expected_path = Path(temp_dir) / "test_kernel.json"
250
+ assert saved_path == expected_path
251
+ assert saved_path.exists()
252
+
253
+ with open(saved_path) as f:
254
+ loaded_data = json.load(f)
255
+ assert loaded_data == data
256
+
257
+ def test_save_config_set_creates_directory(self):
258
+ """Test that save_config_set creates parent directories if needed."""
259
+ with tempfile.TemporaryDirectory() as temp_dir:
260
+ nested_dir = Path(temp_dir) / "nested" / "configs"
261
+ config_set = ConfigSet("test_kernel")
262
+
263
+ manager = ConfigManager(base_dir=nested_dir)
264
+ saved_path = manager.save_config_set(config_set)
265
+
266
+ assert nested_dir.exists()
267
+ assert nested_dir.is_dir()
268
+ assert saved_path.exists()
269
+
270
+ def test_get_platform_configs(self):
271
+ """Test getting all configs for a specific platform."""
272
+ with tempfile.TemporaryDirectory() as temp_dir:
273
+ # Use realistic config data
274
+ config_1 = {"num_warps": 4, "num_stages": 3, "block_sizes": [64, 32]}
275
+ config_2 = {"num_warps": 8, "num_stages": 5, "block_sizes": [128, 64]}
276
+ default_config = {
277
+ "num_warps": 16,
278
+ "num_stages": 7,
279
+ "block_sizes": [256, 128],
280
+ }
281
+ config_3 = {"num_warps": 2, "num_stages": 2, "block_sizes": [32, 16]}
282
+
283
+ config_data = {
284
+ "h100": {
285
+ "batch_32_hidden_4096": config_1,
286
+ "batch_64_hidden_2048": config_2,
287
+ "default": default_config,
288
+ },
289
+ "a100": {"batch_16_hidden_1024": config_3},
290
+ }
291
+ config_file = Path(temp_dir) / "test_kernel.json"
292
+ with open(config_file, "w") as f:
293
+ json.dump(config_data, f)
294
+
295
+ manager = ConfigManager(base_dir=temp_dir)
296
+
297
+ h100_configs = manager.get_platform_configs("test_kernel", "h100")
298
+ assert len(h100_configs) == 3
299
+ assert "batch_32_hidden_4096" in h100_configs
300
+ assert "batch_64_hidden_2048" in h100_configs
301
+ assert "default" in h100_configs
302
+ for config in h100_configs.values():
303
+ assert isinstance(config, helion.Config)
304
+
305
+ # Verify specific config details
306
+ assert h100_configs["batch_32_hidden_4096"].num_warps == 4
307
+ assert h100_configs["default"].num_stages == 7
308
+
309
+ a100_configs = manager.get_platform_configs("test_kernel", "a100")
310
+ assert len(a100_configs) == 1
311
+ assert "batch_16_hidden_1024" in a100_configs
312
+ assert isinstance(a100_configs["batch_16_hidden_1024"], helion.Config)
313
+ assert a100_configs["batch_16_hidden_1024"].num_warps == 2
314
+
315
+ nonexistent_configs = manager.get_platform_configs("test_kernel", "v100")
316
+ assert len(nonexistent_configs) == 0
317
+
318
+ def test_singleton_returns_same_instance(self):
319
+ """Test that ConfigManager returns the same instance on repeated calls."""
320
+ manager1 = ConfigManager(base_dir="/tmp/test_singleton")
321
+ manager2 = ConfigManager(base_dir="/tmp/test_singleton")
322
+
323
+ assert manager1 is manager2
324
+
325
+ def test_singleton_with_default_base_dir(self):
326
+ """Test singleton behavior with default base directory."""
327
+ manager1 = ConfigManager()
328
+ manager2 = ConfigManager()
329
+
330
+ assert manager1 is manager2
331
+ assert manager1._base_dir == manager2._base_dir
332
+
333
+ def test_singleton_error_on_different_base_dir(self):
334
+ """Test that ConfigManager raises error when created with different base_dir."""
335
+ ConfigManager(base_dir="/tmp/first_dir")
336
+
337
+ with pytest.raises(ValueError, match="singleton already exists"):
338
+ ConfigManager(base_dir="/tmp/different_dir")
339
+
340
+ def test_reset_instance_allows_new_base_dir(self):
341
+ """Test that reset_instance allows creating with a new base_dir."""
342
+ manager1 = ConfigManager(base_dir="/tmp/first_dir")
343
+ assert manager1._base_dir == Path("/tmp/first_dir").resolve()
344
+
345
+ ConfigManager.reset_instance()
346
+
347
+ manager2 = ConfigManager(base_dir="/tmp/second_dir")
348
+ assert manager2._base_dir == Path("/tmp/second_dir").resolve()
349
+ assert manager1 is not manager2
350
+
351
+ def test_get_instance_returns_existing(self):
352
+ """Test that get_instance returns the existing singleton."""
353
+ manager1 = ConfigManager(base_dir="/tmp/test_get_instance")
354
+ manager2 = ConfigManager.get_instance()
355
+
356
+ assert manager1 is manager2
357
+
358
+ def test_get_instance_raises_if_not_initialized(self):
359
+ """Test that get_instance raises RuntimeError if no instance exists."""
360
+ with pytest.raises(RuntimeError, match="has not been created"):
361
+ ConfigManager.get_instance()
vllm/kernels/__init__.py
new
+3
@@ -0,0 +1,3 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Kernel implementations for vLLM."""
vllm/kernels/helion/__init__.py
new
+13
@@ -0,0 +1,13 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Helion integration for vLLM."""
4
+
5
+from vllm.kernels.helion.config_manager import (
6
+ ConfigManager,
7
+ ConfigSet,
8
+)
9
+
10
+__all__ = [
11
+ "ConfigManager",
12
+ "ConfigSet",
13
+]
vllm/kernels/helion/config_manager.py
new
+231
@@ -0,0 +1,231 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""
4
+Configuration management for Helion kernels.
5
+
6
+This module provides centralized configuration file management for Helion custom
7
+operations, including naming conventions, directory resolution, and file I/O.
8
+
9
+Config File Structure
10
+---------------------
11
+Each kernel has a single JSON config file: {kernel_name}.json
12
+
13
+The file uses a simplified 2-layer hierarchical structure:
14
+{
15
+ "h100": { # GPU platform
16
+ "default": { ... }, # Fallback configuration
17
+ "batch_32_hidden_4096": { ... },
18
+ "batch_64_hidden_8192": { ... }
19
+ },
20
+ "a100": {
21
+ "default": { ... },
22
+ "batch_16_hidden_2048": { ... }
23
+ }
24
+}
25
+
26
+Example file: silu_mul_fp8.json
27
+
28
+Config keys should be structured strings that encode the relevant
29
+parameters (e.g., "batch_32_hidden_4096", "seq_512_heads_16", "fp8_batch_64", etc.).
30
+
31
+Classes
32
+-------
33
+- ConfigSet: In-memory collection of configs for a kernel with lookup/query APIs.
34
+- ConfigManager: File-level operations for config persistence.
35
+"""
36
+
37
+import json
38
+from pathlib import Path
39
+from typing import Any
40
+
41
+from vllm.logger import init_logger
42
+from vllm.utils.import_utils import has_helion
43
+
44
+if not has_helion():
45
+ raise ImportError(
46
+ "ConfigManager requires helion to be installed. "
47
+ "Install it with: pip install helion"
48
+ )
49
+
50
+import helion
51
+
52
+logger = init_logger(__name__)
53
+
54
+
55
+class ConfigSet:
56
+ """In-memory collection of Helion configs with lookup/query capabilities."""
57
+
58
+ # Type alias for nested config structure:
59
+ # platform -> config_key -> helion.Config
60
+ _ConfigDict = dict[str, dict[str, "helion.Config"]]
61
+
62
+ def __init__(self, kernel_name: str):
63
+ self._kernel_name = kernel_name
64
+ self._configs: ConfigSet._ConfigDict = {}
65
+
66
+ @property
67
+ def kernel_name(self) -> str:
68
+ return self._kernel_name
69
+
70
+ def get_config(self, platform: str, config_key: str) -> helion.Config:
71
+ platform_dict = self._configs.get(platform)
72
+ if platform_dict is None:
73
+ avail_platforms = self.get_platforms()
74
+ raise KeyError(
75
+ f"Config not found for kernel '{self._kernel_name}': "
76
+ f"platform '{platform}' not found. "
77
+ f"Available platforms: {avail_platforms or '(none)'}"
78
+ )
79
+
80
+ config = platform_dict.get(config_key)
81
+ if config is None:
82
+ avail_keys = self.get_config_keys(platform)
83
+ raise KeyError(
84
+ f"Config not found for kernel '{self._kernel_name}': "
85
+ f"config_key '{config_key}' not found for platform '{platform}'. "
86
+ f"Available config_keys: {avail_keys or '(none)'}"
87
+ )
88
+
89
+ return config
90
+
91
+ def get_platforms(self) -> list[str]:
92
+ return sorted(self._configs.keys())
93
+
94
+ def get_config_keys(self, platform: str) -> list[str]:
95
+ platform_dict = self._configs.get(platform.lower())
96
+ if platform_dict is None:
97
+ return []
98
+ return sorted(platform_dict.keys())
99
+
100
+ def to_dict(self) -> dict[str, Any]:
101
+ result: dict[str, Any] = {}
102
+
103
+ for platform, config_keys_dict in self._configs.items():
104
+ result[platform] = {}
105
+
106
+ for config_key, config in config_keys_dict.items():
107
+ # Convert helion.Config to dict using to_json() + json.loads()
108
+ import json
109
+
110
+ result[platform][config_key] = json.loads(config.to_json())
111
+
112
+ return result
113
+
114
+ @classmethod
115
+ def from_dict(cls, kernel_name: str, data: dict[str, Any]) -> "ConfigSet":
116
+ config_set = cls(kernel_name)
117
+ count = 0
118
+
119
+ for platform, platform_data in data.items():
120
+ if platform not in config_set._configs:
121
+ config_set._configs[platform] = {}
122
+
123
+ for config_key, config_data in platform_data.items():
124
+ config = helion.Config(**config_data)
125
+ config_set._configs[platform][config_key] = config
126
+ count += 1
127
+
128
+ if count > 0:
129
+ logger.debug(
130
+ "Loaded %d configs for kernel '%s'",
131
+ count,
132
+ kernel_name,
133
+ )
134
+
135
+ return config_set
136
+
137
+
138
+class ConfigManager:
139
+ """File-level configuration management for Helion kernels (global singleton)."""
140
+
141
+ _instance: "ConfigManager | None" = None
142
+ _instance_base_dir: Path | None = None
143
+
144
+ def __new__(cls, base_dir: str | Path | None = None) -> "ConfigManager":
145
+ resolved_base_dir = cls._resolve_base_dir(base_dir)
146
+
147
+ if cls._instance is not None:
148
+ # Instance already exists - check for base_dir mismatch
149
+ if cls._instance_base_dir != resolved_base_dir:
150
+ raise ValueError(
151
+ f"ConfigManager singleton already exists with base_dir "
152
+ f"'{cls._instance_base_dir}', cannot create with different "
153
+ f"base_dir '{resolved_base_dir}'"
154
+ )
155
+ return cls._instance
156
+
157
+ # Create new instance
158
+ instance = super().__new__(cls)
159
+ cls._instance = instance
160
+ cls._instance_base_dir = resolved_base_dir
161
+ return instance
162
+
163
+ def __init__(self, base_dir: str | Path | None = None):
164
+ # Only initialize if not already initialized
165
+ if hasattr(self, "_base_dir"):
166
+ return
167
+
168
+ self._base_dir = self._resolve_base_dir(base_dir)
169
+ logger.debug("ConfigManager initialized with base_dir: %s", self._base_dir)
170
+
171
+ @staticmethod
172
+ def _resolve_base_dir(base_dir: str | Path | None) -> Path:
173
+ if base_dir is not None:
174
+ return Path(base_dir).resolve()
175
+ return (Path(__file__).parent / "configs").resolve()
176
+
177
+ @classmethod
178
+ def get_instance(cls) -> "ConfigManager":
179
+ if cls._instance is None:
180
+ raise RuntimeError(
181
+ "ConfigManager instance has not been created. "
182
+ "Call ConfigManager(base_dir=...) first to initialize."
183
+ )
184
+ return cls._instance
185
+
186
+ @classmethod
187
+ def reset_instance(cls) -> None:
188
+ """For testing purposes only."""
189
+ cls._instance = None
190
+ cls._instance_base_dir = None
191
+
192
+ def get_config_file_path(self, kernel_name: str) -> Path:
193
+ return self._base_dir / f"{kernel_name}.json"
194
+
195
+ def ensure_base_dir_exists(self) -> Path:
196
+ self._base_dir.mkdir(parents=True, exist_ok=True)
197
+ return self._base_dir
198
+
199
+ def load_config_set(self, kernel_name: str) -> ConfigSet:
200
+ config_path = self.get_config_file_path(kernel_name)
201
+ if not config_path.exists():
202
+ return ConfigSet.from_dict(kernel_name, {})
203
+
204
+ try:
205
+ with open(config_path) as f:
206
+ data = json.load(f)
207
+ return ConfigSet.from_dict(kernel_name, data)
208
+ except (json.JSONDecodeError, OSError) as e:
209
+ logger.error("Failed to load config file %s: %s", config_path, e)
210
+ return ConfigSet.from_dict(kernel_name, {})
211
+
212
+ def get_platform_configs(
213
+ self, kernel_name: str, platform: str
214
+ ) -> dict[str, helion.Config]:
215
+ config_set = self.load_config_set(kernel_name)
216
+ config_keys = config_set.get_config_keys(platform)
217
+
218
+ return {
219
+ config_key: config_set.get_config(platform, config_key)
220
+ for config_key in config_keys
221
+ }
222
+
223
+ def save_config_set(self, config_set: ConfigSet) -> Path:
224
+ config_path = self.get_config_file_path(config_set.kernel_name)
225
+ config_path.parent.mkdir(parents=True, exist_ok=True)
226
+
227
+ with open(config_path, "w") as f:
228
+ json.dump(config_set.to_dict(), f, indent=2)
229
+
230
+ logger.info("Saved config to: %s", config_path)
231
+ return config_path