| from __future__ import annotations |
|
|
| import os |
| from dataclasses import dataclass |
| from typing import Any, Dict, List, Optional |
|
|
| import yaml |
|
|
|
|
| @dataclass |
| class KnobSpec: |
| type: str |
| min: float |
| max: float |
|
|
|
|
| @dataclass |
| class RewardSpec: |
| weights: Dict[str, float] |
| wns_candidates: List[str] |
| area_candidates: List[str] |
| power_candidates: List[str] |
|
|
|
|
| @dataclass |
| class BudgetSpec: |
| total_actions: int |
| max_expensive: int |
|
|
|
|
| @dataclass |
| class ExperimentSpec: |
| name: str |
| seed: int |
| db_path: str |
| out_dir: str |
| orfs_flow_dir: Optional[str] |
|
|
|
|
| @dataclass |
| class DesignSpec: |
| platform: str |
| design: str |
| design_config: str |
|
|
|
|
| @dataclass |
| class FlowSpec: |
| fidelities: List[str] |
| targets: Dict[str, str] |
|
|
|
|
| @dataclass |
| class TuningSpec: |
| agent: str |
| budget: BudgetSpec |
| knobs: Dict[str, KnobSpec] |
|
|
|
|
| @dataclass |
| class Config: |
| experiment: ExperimentSpec |
| design: DesignSpec |
| flow: FlowSpec |
| tuning: TuningSpec |
| reward: RewardSpec |
|
|
|
|
| def load_config(path: str) -> Config: |
| with open(path, "r", encoding="utf-8") as f: |
| d = yaml.safe_load(f) |
|
|
| exp = d["experiment"] |
| design = d["design"] |
| flow = d["flow"] |
| tuning = d["tuning"] |
| reward = d["reward"] |
|
|
| knobs: Dict[str, KnobSpec] = {} |
| for k, ks in tuning["knobs"].items(): |
| knobs[k] = KnobSpec( |
| type=str(ks["type"]), |
| min=float(ks["min"]), |
| max=float(ks["max"]), |
| ) |
|
|
| cfg = Config( |
| experiment=ExperimentSpec( |
| name=str(exp["name"]), |
| seed=int(exp.get("seed", 0)), |
| db_path=str(exp.get("db_path", "runs/experiment.sqlite")), |
| out_dir=str(exp.get("out_dir", "runs")), |
| orfs_flow_dir=exp.get("orfs_flow_dir", None), |
| ), |
| design=DesignSpec( |
| platform=str(design["platform"]), |
| design=str(design["design"]), |
| design_config=str(design["design_config"]), |
| ), |
| flow=FlowSpec( |
| fidelities=list(flow["fidelities"]), |
| targets=dict(flow["targets"]), |
| ), |
| tuning=TuningSpec( |
| agent=str(tuning["agent"]), |
| budget=BudgetSpec( |
| total_actions=int(tuning["budget"]["total_actions"]), |
| max_expensive=int(tuning["budget"]["max_expensive"]), |
| ), |
| knobs=knobs, |
| ), |
| reward=RewardSpec( |
| weights=dict(reward["weights"]), |
| wns_candidates=list(reward["keys"]["wns_candidates"]), |
| area_candidates=list(reward["keys"]["area_candidates"]), |
| power_candidates=list(reward["keys"]["power_candidates"]), |
| ), |
| ) |
| |
| |
| _validate_config(cfg) |
| |
| return cfg |
|
|
|
|
| def _validate_config(cfg: Config) -> None: |
| """Validate configuration values.""" |
| |
| if cfg.tuning.budget.total_actions <= 0: |
| raise ValueError(f"total_actions must be > 0, got {cfg.tuning.budget.total_actions}") |
| if cfg.tuning.budget.max_expensive < 0: |
| raise ValueError(f"max_expensive must be >= 0, got {cfg.tuning.budget.max_expensive}") |
| if cfg.tuning.budget.max_expensive > cfg.tuning.budget.total_actions: |
| raise ValueError( |
| f"max_expensive ({cfg.tuning.budget.max_expensive}) cannot exceed " |
| f"total_actions ({cfg.tuning.budget.total_actions})" |
| ) |
| |
| |
| if not cfg.flow.fidelities: |
| raise ValueError("flow.fidelities cannot be empty") |
| |
| |
| if not cfg.tuning.knobs: |
| raise ValueError("tuning.knobs cannot be empty") |
| |
| for name, spec in cfg.tuning.knobs.items(): |
| if spec.min >= spec.max: |
| raise ValueError(f"Knob {name}: min ({spec.min}) must be < max ({spec.max})") |
| if spec.type not in ("int", "float"): |
| raise ValueError(f"Knob {name}: type must be 'int' or 'float', got '{spec.type}'") |
| |
| |
| if not cfg.reward.weights: |
| raise ValueError("reward.weights cannot be empty") |
| |
| |
| if not cfg.reward.wns_candidates and not cfg.reward.area_candidates and not cfg.reward.power_candidates: |
| raise ValueError("At least one reward candidate list must be non-empty") |
| |
| |
|
|