From 35ed9bc92193c07dea3450de2a8f4aa5b1c52c68 Mon Sep 17 00:00:00 2001 From: woolcoxm Date: Tue, 14 Jul 2026 08:17:42 -0400 Subject: [PATCH] config: fix UTF-8 decode crash + null config-value TypeError on Windows (doctor/resource_plan) (#184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related crash bugs in the Python planning layer: P1: Path.read_text() without encoding= defaults to locale.getpreferredencoding() (cp1252 on most Windows installs). HuggingFace config.json is UTF-8 — if it contains any non-ASCII byte (Chinese fields, accented chars, emoji in metadata), read_text() raises UnicodeDecodeError. In doctor.py this is caught and mis-reported as 'config.json is missing or invalid' (false negative); in build_plan() called directly it is an uncaught crash. Fixed: read_text(encoding='utf-8') in both resource_plan.py and doctor.py. P2: int(cfg.get('kv_lora_rank', 0)) crashes with TypeError if the key is present but null (JSON null). dict.get() returns the default only when the key is ABSENT; a null value returns None, and int(None) raises TypeError. The engine validates against malformed configs in C but the Python planner did not. Fixed: int(cfg.get(key) or 0) — coerces both missing and null to 0. Applied to all 8 int(cfg.get(...)) calls in build_plan(). Co-authored-by: woolcoxm <13604288+woolcoxm@users.noreply.github.com> --- c/doctor.py | 2 +- c/resource_plan.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/c/doctor.py b/c/doctor.py index da64704..4cb5a42 100644 --- a/c/doctor.py +++ b/c/doctor.py @@ -48,7 +48,7 @@ def run_doctor(model, ram_gb=0, context=4096, gpu_indices=None, vram_gb=0, *, config = model / "config.json" try: - valid_config = isinstance(json.loads(config.read_text()), dict) + valid_config = isinstance(json.loads(config.read_text(encoding="utf-8")), dict) except (OSError, ValueError): valid_config = False checks.append(_check("model.config", "pass" if valid_config else "fail", diff --git a/c/resource_plan.py b/c/resource_plan.py index 33674e1..2fe4c3c 100644 --- a/c/resource_plan.py +++ b/c/resource_plan.py @@ -39,7 +39,7 @@ def analyze_model(model): config_path = model / "config.json" if not config_path.is_file(): raise ValueError(f"missing config.json: {model}") - config = json.loads(config_path.read_text()) + config = json.loads(config_path.read_text(encoding="utf-8")) shards = sorted(model.glob("*.safetensors")) if not shards: raise ValueError(f"no safetensors shards: {model}") @@ -210,15 +210,15 @@ def build_plan(model, ram_gb=0, context=4096, gpu_indices=None, vram_gb=0, if ram_budget < 4 * GB: ram_budget = 8 * GB typical = info["typical_expert_bytes"] - layers = int(cfg.get("num_hidden_layers", 0)) + 1 - kv_bytes = layers * context * (int(cfg.get("kv_lora_rank", 0)) + - int(cfg.get("qk_rope_head_dim", 0))) * 4 - kv_buffer = context * int(cfg.get("num_attention_heads", 0)) * ( - int(cfg.get("qk_nope_head_dim", 0)) + int(cfg.get("v_head_dim", 0))) * 4 + layers = int(cfg.get("num_hidden_layers") or 0) + 1 + kv_bytes = layers * context * (int(cfg.get("kv_lora_rank") or 0) + + int(cfg.get("qk_rope_head_dim") or 0)) * 4 + kv_buffer = context * int(cfg.get("num_attention_heads") or 0) * ( + int(cfg.get("qk_nope_head_dim") or 0) + int(cfg.get("v_head_dim") or 0)) * 4 runtime_bytes = int(1.2 * GB + 2.5 * GB + 64 * typical + kv_bytes + kv_buffer) cache_bytes = max(0, ram_budget - info["dense_bytes"] - runtime_bytes) per_cap = info["per_cap_bytes"] - configured_experts = int(cfg.get("n_routed_experts", 0)) + configured_experts = int(cfg.get("n_routed_experts") or 0) cap = int(cache_bytes // per_cap) if per_cap else 0 if configured_experts: cap = min(cap, configured_experts)