| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """ |
| Public config normalization / validation for the early-access public release. |
| |
| Responsibilities: |
| - Fail-fast if the user tries to set hidden/experimental fields (via Hydra CLI `+foo=...`) |
| - Merge in hidden defaults (sourced from model_1_d9 config) so training runs with a minimal public config |
| - Apply the selected public model architecture (model_id -> model.*) |
| - Clamp distance/n_rounds to the model receptive field: |
| D = min(distance, R) |
| N_R = min(n_rounds, R) |
| """ |
|
|
| from __future__ import annotations |
|
|
| from pathlib import Path |
| import os |
| from typing import Any, Dict, Iterable, Tuple |
|
|
| from omegaconf import DictConfig, OmegaConf |
|
|
| from model.registry import PublicModelSpec, get_model_spec |
|
|
| _PUBLIC_ROTATION_TO_INTERNAL = { |
| |
| "O1": "XV", |
| "O2": "XH", |
| "O3": "ZV", |
| "O4": "ZH", |
| } |
| _INTERNAL_ROTATION_TO_PUBLIC = {v: k for k, v in _PUBLIC_ROTATION_TO_INTERNAL.items()} |
|
|
| _PUBLIC_MODEL_ID_TO_LR = { |
| 1: 3e-4, |
| 2: 2e-4, |
| 3: 1e-4, |
| 4: 2e-4, |
| 5: 1e-4, |
| 6: 2e-4, |
| } |
|
|
|
|
| def _default_precomputed_frames_dir() -> str: |
| """ |
| Default location for precomputed frames shipped with (or generated inside) this repo. |
| |
| We compute this path relative to the codebase so it is stable regardless of the user's |
| current working directory. |
| """ |
| |
| repo_root = Path(__file__).resolve().parents[2] |
| return str((repo_root / "frames_data").resolve()) |
|
|
|
|
| def _get_env_bool(name: str, default: bool) -> bool: |
| raw = os.environ.get(name) |
| if raw is None: |
| return default |
| val = str(raw).strip().lower() |
| if val in ("0", "false", "no", "off", ""): |
| return False |
| return True |
|
|
|
|
| def _normalize_code_rotation(value: Any) -> str: |
| """ |
| Normalize code rotation values. |
| |
| Public config accepts O1..O4 for user convenience. Internally we keep using: |
| XV, XH, ZV, ZH (as expected by SurfaceCode / MemoryCircuit). |
| """ |
| if value is None: |
| return value |
| s = str(value).strip().upper() |
| if s in _PUBLIC_ROTATION_TO_INTERNAL: |
| return _PUBLIC_ROTATION_TO_INTERNAL[s] |
| if s in _INTERNAL_ROTATION_TO_PUBLIC: |
| return s |
| raise ValueError( |
| f"Invalid data.code_rotation={value!r}. " |
| f"Use one of {sorted(_PUBLIC_ROTATION_TO_INTERNAL.keys())} (public) " |
| f"or {sorted(_INTERNAL_ROTATION_TO_PUBLIC.keys())} (internal)." |
| ) |
|
|
|
|
| def _base_hidden_defaults_dict() -> Dict[str, Any]: |
| """ |
| Baseline config used as the source-of-truth for hidden defaults. |
| |
| IMPORTANT: We intentionally embed these defaults directly in code so the public |
| release does not ship internal/legacy config files. These values were copied |
| from the historical `config_pre_decoder_memory_surface_model_1_d9.yaml`. |
| """ |
| base_output_dir = os.environ.get("PREDECODER_BASE_OUTPUT_DIR", "outputs") |
| output_root = f"{base_output_dir}/${{exp_tag}}" |
| return { |
| "exp_tag": "pre-decoder", |
| "output": output_root, |
| "hydra": { |
| "run": { |
| "dir": "${output}" |
| }, |
| "output_subdir": "hydra" |
| }, |
| "resume_dir": f"{output_root}/models", |
| "enable_fp16": False, |
| "enable_bf16": False, |
| "enable_matmul_tf32": True, |
| "enable_cudnn_tf32": True, |
| "enable_cudnn_benchmark": True, |
| "torch_compile": _get_env_bool("PREDECODER_TORCH_COMPILE", True), |
| "torch_compile_mode": os.environ.get("PREDECODER_TORCH_COMPILE_MODE", "default"), |
| "load_checkpoint": False, |
| "code": "surface", |
| "distance": 9, |
| "n_rounds": 9, |
| "multiple_distances": [13, 13], |
| "multiple_rounds": [13, 13], |
| "use_multiple_patches": False, |
| "meas_basis": "both", |
| "workflow": { |
| "task": "train" |
| }, |
| "data": |
| { |
| "timelike_he": True, |
| "num_he_cycles": 1, |
| "use_weight2_timelike": False, |
| "max_passes_w1": 8, |
| "max_passes_w2": 4, |
| "decompose_y": True, |
| "p_error": None, |
| "p_min": 0.001, |
| "p_max": 0.006, |
| "error_mode": "circuit_level_surface_custom", |
| |
| "precomputed_frames_dir": _default_precomputed_frames_dir(), |
| "enable_correlated_pymatching": False, |
| "code_rotation": "XV", |
| "noise_model": None, |
| }, |
| "model": |
| { |
| "version": "predecoder_memory_v1", |
| "dropout_p": 0.05, |
| "activation": "gelu", |
| "num_filters": [128, 128, 128, 4], |
| "kernel_size": [3, 3, 3, 3], |
| "input_channels": 4, |
| "out_channels": 4, |
| }, |
| "datapipe": "memory", |
| "data_method": "train", |
| "train": |
| { |
| |
| |
| "num_samples": 67108864, |
| "accumulate_steps": 2, |
| "checkpoint_interval": 1, |
| "save_every_datasets": 5, |
| "epochs": 100, |
| }, |
| |
| "val": { |
| "num_samples": 65536, |
| "threshold": 0.5, |
| "trials": 1 |
| }, |
| "optimizer_type": "Lion", |
| "optimizer": { |
| "lr": 1e-4, |
| "weight_decay": 1e-7, |
| "beta2": 0.95 |
| }, |
| "lr_scheduler": |
| { |
| "type": "warmup_then_decay", |
| "warmup_steps": 100, |
| "milestones": [0.25, 0.5, 1.0], |
| "gamma": 0.7, |
| "min_lr": 1e-6, |
| }, |
| "batch_schedule": |
| { |
| "enabled": True, |
| "initial": 256, |
| "final": 1024, |
| "start_epoch": 1, |
| "end_epoch": 3, |
| }, |
| "validation_ler": True, |
| "early_stopping": { |
| "enabled": True, |
| "patience": 100 |
| }, |
| "time_based_early_stopping": { |
| "enabled": False, |
| "safety_margin_minutes": 5 |
| }, |
| "ema": { |
| "use_ema": True, |
| "decay": 0.0001 |
| }, |
| "test": |
| { |
| "num_samples": 262144, |
| "trials": 1, |
| "distance": 9, |
| "n_rounds": 9, |
| "noise_model": "train", |
| "p_error": 0.006, |
| "dataloader": |
| { |
| "batch_size": 64, |
| "num_workers": 0, |
| "persistent_workers": False, |
| }, |
| "latency_num_samples": 1000, |
| "sampler": { |
| "shuffle": False, |
| "drop_last": False |
| }, |
| "syn_red": "full", |
| "th_data": 0.0, |
| "th_syn": 0.0, |
| "sampling_mode": "threshold", |
| "temperature": 0.0, |
| "temperature_data": None, |
| "temperature_syn": None, |
| "per_round": False, |
| "meas_basis_test": "both", |
| "use_model_checkpoint": -1, |
| }, |
| "threshold": |
| { |
| "p_values": [0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008], |
| "distances": [5, 7, 9, 11, 13], |
| "n_rounds": None, |
| }, |
| } |
|
|
|
|
| def _select(cfg: DictConfig, key: str) -> Tuple[bool, Any]: |
| """ |
| Return (exists, value) for a dot-path in cfg. |
| Note: OmegaConf.select returns None both for missing keys and explicit nulls, |
| so we treat a key as existing iff it is present in the underlying container. |
| """ |
| |
| cur: Any = cfg |
| parts = key.split(".") |
| for p in parts: |
| if not isinstance(cur, DictConfig) or p not in cur: |
| return False, None |
| cur = cur[p] |
| return True, cur |
|
|
|
|
| def _assert_not_present(cfg: DictConfig, keys: Iterable[str], *, context: str) -> None: |
| for k in keys: |
| exists, _ = _select(cfg, k) |
| if exists: |
| raise ValueError( |
| f"Config field '{k}' is not supported in the public release ({context}). " |
| f"Remove it from the config/CLI overrides." |
| ) |
|
|
|
|
| def validate_public_config(cfg: DictConfig) -> PublicModelSpec: |
| """ |
| Validate the user-facing config BEFORE we merge in hidden defaults. |
| |
| Returns: |
| PublicModelSpec for cfg.model_id (validated). |
| """ |
| |
| if "model_id" not in cfg: |
| raise ValueError("Missing required field: 'model_id' (choose 1..5).") |
|
|
| model_spec = get_model_spec(cfg.model_id) |
|
|
| |
| if "distance" not in cfg or "n_rounds" not in cfg: |
| raise ValueError("Missing required fields: 'distance' and 'n_rounds'.") |
| try: |
| d = int(cfg.distance) |
| r = int(cfg.n_rounds) |
| except Exception as e: |
| raise ValueError( |
| f"Invalid distance/n_rounds: distance={cfg.distance!r}, n_rounds={cfg.n_rounds!r}" |
| ) from e |
| if d <= 0 or r <= 0: |
| raise ValueError( |
| f"Invalid distance/n_rounds: distance={d}, n_rounds={r} (must be positive integers)" |
| ) |
|
|
| if "train" in cfg: |
| raise ValueError("Config field 'train' is not supported in the public release.") |
| if "val" in cfg: |
| raise ValueError("Config field 'val' is not supported in the public release.") |
| if "test" in cfg: |
| raise ValueError("Config field 'test' is not supported in the public release.") |
|
|
| |
| _assert_not_present( |
| cfg, |
| keys=( |
| |
| "output", |
| "resume_dir", |
| |
| "enable_fp16", |
| "enable_bf16", |
| "enable_matmul_tf32", |
| "enable_cudnn_tf32", |
| |
| "meas_basis", |
| |
| "use_multiple_patches", |
| "multiple_distances", |
| "multiple_rounds", |
| |
| "optimizer", |
| "optimizer_type", |
| "lr_scheduler", |
| "batch_schedule", |
| |
| "train.save_every_datasets", |
| |
| "val.threshold", |
| "val.trials", |
| |
| "time_based_early_stopping", |
| "ema", |
| ), |
| context="hidden field override", |
| ) |
|
|
| |
| if "data" in cfg and isinstance(cfg.data, DictConfig): |
| |
| |
| if "precomputed_frames_dir" in cfg.data: |
| raise ValueError( |
| "Config field 'data.precomputed_frames_dir' is not supported in the public release. " |
| "Remove it from the config/CLI overrides." |
| ) |
| allowed_data_keys = {"code_rotation", "noise_model"} |
| for k in cfg.data.keys(): |
| if k not in allowed_data_keys: |
| raise ValueError( |
| f"Config field 'data.{k}' is not supported in the public release. " |
| f"Allowed data fields are: {sorted(allowed_data_keys)}" |
| ) |
| |
| if "code_rotation" in cfg.data: |
| _normalize_code_rotation(cfg.data.code_rotation) |
|
|
| |
| if "optimizer" in cfg and isinstance(cfg.optimizer, DictConfig): |
| for k in cfg.optimizer.keys(): |
| if k != "lr": |
| raise ValueError( |
| f"Config field 'optimizer.{k}' is not supported in the public release. " |
| f"Only 'optimizer.lr' is user-configurable." |
| ) |
|
|
| return model_spec |
|
|
|
|
| def clamp_to_receptive_field(cfg: DictConfig, R: int) -> None: |
| """In-place clamp of cfg.distance and cfg.n_rounds to receptive field R.""" |
| if not isinstance(R, int) or R <= 0: |
| raise ValueError(f"Invalid receptive field R={R!r}") |
| if "distance" not in cfg or "n_rounds" not in cfg: |
| raise ValueError("Both 'distance' and 'n_rounds' must be present in config.") |
| cfg.distance = int(min(int(cfg.distance), R)) |
| cfg.n_rounds = int(min(int(cfg.n_rounds), R)) |
|
|
|
|
| def apply_public_defaults_and_model(cfg: DictConfig, model_spec: PublicModelSpec) -> DictConfig: |
| """ |
| Merge hidden defaults and apply public model settings. |
| |
| Returns a new DictConfig (does not mutate input). |
| """ |
| base_cfg = OmegaConf.create(_base_hidden_defaults_dict()) |
|
|
| |
| merged = OmegaConf.merge(base_cfg, cfg) |
| OmegaConf.set_struct(merged, False) |
|
|
| |
| |
| |
| requested_distance = int(merged.distance) |
| requested_n_rounds = int(merged.n_rounds) |
|
|
| |
| merged.enable_fp16 = False |
| merged.enable_bf16 = False |
| merged.enable_matmul_tf32 = True |
| merged.enable_cudnn_tf32 = True |
|
|
| merged.meas_basis = "both" |
|
|
| |
| if "data" not in merged: |
| merged.data = {} |
| merged.data.use_multiple_patches = False |
| merged.multiple_distances = None |
| merged.multiple_rounds = None |
|
|
| |
| merged.data.precomputed_frames_dir = _default_precomputed_frames_dir() |
|
|
| |
| if "model" not in merged: |
| merged.model = {} |
| merged.model.version = model_spec.model_version |
| merged.model.num_filters = list(model_spec.num_filters) |
| merged.model.kernel_size = list(model_spec.kernel_size) |
|
|
| |
| |
| if "optimizer" not in merged: |
| merged.optimizer = {} |
| lr = _PUBLIC_MODEL_ID_TO_LR.get(int(model_spec.model_id)) |
| if lr is None: |
| raise ValueError(f"No public LR mapping for model_id={model_spec.model_id!r}") |
| merged.optimizer.lr = float(lr) |
|
|
| |
| |
| |
| if "batch_schedule" not in merged: |
| merged.batch_schedule = {} |
| merged.batch_schedule.enabled = True |
| if int(model_spec.model_id) == 3: |
| merged.batch_schedule.initial = 256 |
| merged.batch_schedule.final = 1024 |
| elif int(model_spec.model_id) == 6: |
| merged.batch_schedule.initial = 256 |
| merged.batch_schedule.final = 512 |
| else: |
| merged.batch_schedule.initial = 512 |
| merged.batch_schedule.final = 2048 |
| |
| merged.batch_schedule.start_epoch = 0 |
| merged.batch_schedule.end_epoch = 0 |
|
|
| |
| |
| if "train" not in merged: |
| merged.train = {} |
| if not ("train" in cfg and isinstance(cfg.train, DictConfig) and "epochs" in cfg.train): |
| merged.train.epochs = 100 |
|
|
| |
| |
| if "val" not in merged: |
| merged.val = {} |
| |
| if not ("val" in cfg and isinstance(cfg.val, DictConfig) and "num_samples" in cfg.val): |
| merged.val.num_samples = 65536 |
|
|
| |
| |
| |
| task = str(getattr(getattr(merged, "workflow", None), "task", "train")).strip().lower() |
| R = int(model_spec.receptive_field) |
| if R <= 0: |
| raise ValueError(f"Invalid receptive field R={R!r}") |
| if task == "train": |
| merged.distance = R |
| merged.n_rounds = R |
| else: |
| merged.distance = int(requested_distance) |
| merged.n_rounds = int(requested_n_rounds) |
|
|
| |
| if "data" in merged and "code_rotation" in merged.data: |
| merged.data.code_rotation = _normalize_code_rotation(merged.data.code_rotation) |
|
|
| |
| if "test" not in merged: |
| merged.test = {} |
| if not ("test" in cfg and isinstance(cfg.test, DictConfig) and "num_samples" in cfg.test): |
| merged.test.num_samples = 262144 |
| merged.test.distance = int(requested_distance) |
| merged.test.n_rounds = int(requested_n_rounds) |
| merged.test.noise_model = "train" |
| return merged |
|
|