File size: 13,572 Bytes
31dc8dc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | """
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_<time>_<task>/
use_run_subdirectory: bool = True
save_results: bool = True
use_tqdm: bool = True
# lm-eval requires explicit confirmation for code tasks that execute generated code.
confirm_run_unsafe_code: bool = True
@classmethod
def from_dict(cls, config_dict: Dict[str, Any]) -> "EvalConfig":
"""Create evaluation configuration from dictionary"""
valid = {f.name for f in cls.__dataclass_fields__.values()}
filtered = {k: v for k, v in config_dict.items() if k in valid}
return cls(**filtered)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary"""
return {field.name: getattr(self, field.name) for field in self.__dataclass_fields__.values()}
def get_sampling_params(self):
"""Get sampling parameters"""
from diffulex import SamplingParams
return SamplingParams(
temperature=self.temperature,
max_tokens=self.max_tokens,
max_nfe=self.max_nfe,
max_repetition_run=self.max_repetition_run,
ignore_eos=self.ignore_eos,
)
@dataclass
class BenchmarkConfig:
"""
Benchmark configuration - Combines engine and evaluation configurations
"""
engine: EngineConfig
eval: EvalConfig
@classmethod
def from_dict(cls, config_dict: Dict[str, Any]) -> "BenchmarkConfig":
"""
Create benchmark configuration from dictionary
Supports both flat and nested dictionary structures for backward compatibility
"""
# Check if config_dict has nested structure
if "engine" in config_dict and "eval" in config_dict:
engine = EngineConfig.from_dict(config_dict["engine"])
eval_config = EvalConfig.from_dict(config_dict["eval"])
else:
# Flat structure - backward compatibility
# Split fields into engine and eval
engine_fields = EngineConfig.accepted_input_fields()
engine_dict = {k: v for k, v in config_dict.items() if k in engine_fields}
eval_dict = {k: v for k, v in config_dict.items() if k not in engine_fields}
engine = EngineConfig.from_dict(engine_dict)
eval_config = EvalConfig.from_dict(eval_dict)
return cls(engine=engine, eval=eval_config)
@classmethod
def from_json(cls, json_path: str) -> "BenchmarkConfig":
"""Load configuration from JSON file"""
with open(json_path, "r", encoding="utf-8") as f:
config_dict = json.load(f)
return cls.from_dict(config_dict)
@classmethod
def from_yaml(cls, yaml_path: str) -> "BenchmarkConfig":
"""Load configuration from YAML file"""
with open(yaml_path, "r", encoding="utf-8") as f:
config_dict = yaml.safe_load(f)
return cls.from_dict(config_dict)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary with nested structure"""
return {
"engine": self.engine.to_dict(),
"eval": self.eval.to_dict(),
}
def save_json(self, json_path: str):
"""Save to JSON file"""
with open(json_path, "w", encoding="utf-8") as f:
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
def save_yaml(self, yaml_path: str):
"""Save to YAML file"""
with open(yaml_path, "w", encoding="utf-8") as f:
yaml.dump(self.to_dict(), f, allow_unicode=True, default_flow_style=False)
def get_diffulex_kwargs(self) -> Dict[str, Any]:
"""Get arguments to pass to Diffulex engine"""
return self.engine.get_diffulex_kwargs()
def get_sampling_params(self):
"""Get sampling parameters"""
return self.eval.get_sampling_params()
# Convenience properties for backward compatibility
@property
def model_path(self) -> str:
return self.engine.model_path
@property
def tokenizer_path(self) -> Optional[str]:
return self.engine.tokenizer_path
@property
def model_name(self) -> str:
return self.engine.model_name
@property
def decoding_strategy(self) -> str:
return self.engine.decoding_strategy
@property
def dataset_name(self) -> str:
return self.eval.dataset_name
@property
def dataset_limit(self) -> Optional[int]:
return self.eval.dataset_limit
@property
def output_dir(self) -> str:
return self.eval.output_dir
@dataset_name.setter
def dataset_name(self, value: str):
self.eval.dataset_name = value
@dataset_limit.setter
def dataset_limit(self, value: Optional[int]):
self.eval.dataset_limit = value
@output_dir.setter
def output_dir(self, value: str):
self.eval.output_dir = value
@model_path.setter
def model_path(self, value: str):
self.engine.model_path = value
|