""" Benchmark Configuration - Configuration management with separated engine and eval configs """ from __future__ import annotations import base64 import json from dataclasses import dataclass, field from typing import Optional, Dict, Any import yaml from diffulex.config import Config as DiffulexConfig MODEL_ARG_COMPLEX_PREFIX = "b64json:" DEFAULT_DECODING_THRESHOLDS = { "add_block_threshold": 0.1, "semi_complete_threshold": 0.9, "accept_threshold": 0.9, "remask_threshold": 0.4, "token_stability_threshold": 0.0, } FLAT_THRESHOLD_KEYS = ( "add_block_threshold", "semi_complete_threshold", "accept_threshold", "remask_threshold", "token_stability_threshold", ) def diffulex_core_engine_fields() -> set[str]: """Diffulex Config fields that can be forwarded from benchmark config.""" return { name for name in DiffulexConfig.__dataclass_fields__.keys() if name not in {"model", "hf_config"} } CORE_ENGINE_FIELDS = diffulex_core_engine_fields() def normalize_engine_input_dict(config_dict: Dict[str, Any]) -> Dict[str, Any]: """Apply compatibility aliases for engine config input.""" d = dict(config_dict) if "block_size" not in d and "diffusion_block_size" in d: d["block_size"] = d.pop("diffusion_block_size") return d def encode_model_arg_value(value: Any) -> Any: """Encode complex values so lm-eval model_args can round-trip them safely.""" if value is None: return None if isinstance(value, (bool, int, float)): return value if isinstance(value, str): if "," not in value and not value.startswith(MODEL_ARG_COMPLEX_PREFIX): return value payload = json.dumps(value, ensure_ascii=False) else: payload = json.dumps(value, ensure_ascii=False, separators=(",", ":")) token = base64.urlsafe_b64encode(payload.encode("utf-8")).decode("ascii") return f"{MODEL_ARG_COMPLEX_PREFIX}{token}" def decode_model_arg_value(value: Any) -> Any: """Decode values produced by encode_model_arg_value().""" if not isinstance(value, str) or not value.startswith(MODEL_ARG_COMPLEX_PREFIX): return value payload = value[len(MODEL_ARG_COMPLEX_PREFIX) :] raw = base64.urlsafe_b64decode(payload.encode("ascii")).decode("utf-8") return json.loads(raw) def parse_engine_arg_override(value: str) -> Any: """Parse CLI --engine-arg values using YAML scalar/list/dict semantics.""" return yaml.safe_load(value) def extract_diffulex_engine_kwargs(source: Dict[str, Any]) -> Dict[str, Any]: """Keep only Diffulex Config kwargs and normalize defaults/aliases.""" normalized = normalize_engine_input_dict(source) kwargs = {k: v for k, v in normalized.items() if k in CORE_ENGINE_FIELDS and v is not None} strategy = kwargs.get("decoding_strategy") if strategy in ("multi_block_diffusion", "block_diffusion", "fast_dllm"): kwargs["decoding_strategy"] = "multi_bd" if not kwargs.get("use_lora", False): kwargs["lora_path"] = "" if kwargs.get("decoding_thresholds") is None and not any(kwargs.get(k) is not None for k in FLAT_THRESHOLD_KEYS): kwargs["decoding_thresholds"] = dict(DEFAULT_DECODING_THRESHOLDS) return kwargs @dataclass class EngineConfig: """ Engine configuration - Parameters for Diffulex engine initialization """ # Model and weights model_path: str tokenizer_path: Optional[str] = None model_name: str = "dream" # Options: dream, sdar, fast_dllm_v2, llada decoding_strategy: str = "d2f" # Options: d2f, multi_bd sampling_mode: str = "naive" # Options: naive, edit max_post_edit_steps: int = 16 # max refinement steps after all masks filled mask_token_id: int = 151666 # LoRA configuration use_lora: bool = False lora_path: str = "" pre_merge_lora: bool = True # Merge LoRA into base at load to avoid per-forward LoRA compute # Parallelism configuration tensor_parallel_size: int = 1 data_parallel_size: int = 1 expert_parallel_size: int = 1 # Memory and capacity configuration gpu_memory_utilization: float = 0.9 max_model_len: int = 2048 max_num_batched_tokens: int = 4096 max_num_reqs: int = 128 enable_prefill_cudagraph: bool = True enable_full_static_runner: bool = True prefill_cudagraph_max_len: int = 0 enable_torch_compile: bool = True enable_cudagraph_torch_compile: bool = False torch_compile_mode: str = "reduce-overhead" # Scheduler / truncation configuration auto_max_nfe_warmup_steps: int = 8 auto_max_nfe_tpf_floor: float = 1.0 # Engine behavior configuration enforce_eager: bool = False attn_impl: str = "triton" enable_prefix_caching: bool = True kv_cache_layout: str = "unified" # Options: unified, distinct page_size: int = 32 token_merge_mode: str = "dmax_topk" token_merge_top_k: int = 1 token_merge_renormalize: bool = True token_merge_weight: float = 1.0 # MoE configuration moe_dispatcher_backend: str = "standard" moe_gemm_impl: str = "triton" deepep_mode: str = "auto" deepep_num_max_dispatch_tokens_per_rank: int = 256 # D2F/MultiBD-specific configuration decoding_thresholds: Optional[Dict[str, float]] = ( None # {add_block_threshold, semi_complete_threshold, accept_threshold, remask_threshold, token_stability_threshold} ) block_size: int = 32 # Aligned with diffulex.config.Config.block_size buffer_size: int = 4 multi_block_prefix_full: bool = True extra_engine_kwargs: Dict[str, Any] = field(default_factory=dict) @classmethod def explicit_field_names(cls) -> set[str]: return { f.name for f in cls.__dataclass_fields__.values() if f.init and f.name != "extra_engine_kwargs" } @classmethod def accepted_input_fields(cls) -> set[str]: return cls.explicit_field_names() | CORE_ENGINE_FIELDS | {"diffusion_block_size"} @classmethod def from_dict(cls, config_dict: Dict[str, Any]) -> "EngineConfig": """Create engine configuration from dictionary while preserving extra core config fields.""" d = normalize_engine_input_dict(config_dict) valid = cls.explicit_field_names() filtered = {k: v for k, v in d.items() if k in valid} engine = cls(**filtered) engine.extra_engine_kwargs = { k: v for k, v in d.items() if k not in valid and k in CORE_ENGINE_FIELDS } return engine def to_dict(self) -> Dict[str, Any]: """Convert to dictionary""" data = { field.name: getattr(self, field.name) for field in self.__dataclass_fields__.values() if field.name != "extra_engine_kwargs" } data.update(self.extra_engine_kwargs) return data def apply_updates(self, updates: Dict[str, Any]) -> None: """Apply engine updates, preserving unknown-but-core fields for future configs.""" normalized = normalize_engine_input_dict(updates) valid = self.explicit_field_names() for key, value in normalized.items(): if key in valid: setattr(self, key, value) elif key in CORE_ENGINE_FIELDS: self.extra_engine_kwargs[key] = value def get_diffulex_kwargs(self) -> Dict[str, Any]: """Get arguments to pass to Diffulex engine (aligned with diffulex.config.Config).""" return extract_diffulex_engine_kwargs(self.to_dict()) @dataclass class EvalConfig: """ Evaluation configuration - Parameters for benchmark evaluation """ # Task/Dataset configuration (lm-eval task name; use bundled * _diffulex tasks for offline JSON) dataset_name: str = "gsm8k_diffulex" dataset_split: str = "test" dataset_limit: Optional[int] = None # Directory of custom task YAMLs for lm-eval (--include_path). None → diffulex_bench/tasks next to main. include_path: Optional[str] = None # Optional JSON data file override for tasks that declare `dataset_kwargs.data_files`. dataset_data_files: Optional[str] = None # Sampling configuration temperature: float = 0.0 max_tokens: int = 256 max_nfe: Optional[int] = None max_repetition_run: Optional[int] = None ignore_eos: bool = False add_bos_token: Optional[bool] = None # Base model: False; Instruct/chat: True # Output configuration output_dir: str = "benchmark_results" # If True, lm-eval outputs + diffulex stats/trajectory go under output_dir/run_