diff --git a/c/coli b/c/coli index 8c89caf..9408281 100755 --- a/c/coli +++ b/c/coli @@ -161,6 +161,34 @@ def resource_request(a, env): def env_for(a): e = dict(os.environ, SNAP=a.model) + if sys.platform == "win32": + # COLI_NO_OMP_TUNE spegne SOLO il blocco OMP (stesso perimetro del + # self-exec di glm.c; presence-based come nel motore: impostarla a + # qualsiasi valore, anche 0, disattiva). I default I/O piu' sotto + # restano attivi: kill-switch dedicati = le var stesse (DIRECT=0 ecc.) + if not e.get("COLI_NO_OMP_TUNE"): + # parita' col tuning OMP self-exec di glm.c (solo Linux/FreeBSD, e + # comunque saltato sotto CUDA/Metal): libgomp legge queste variabili + # prima di main, quindi su Windows vanno nell'ambiente del figlio. + # niente OMP_PROC_BIND/OMP_PLACES: la libgomp di MinGW non supporta + # l'affinity su Windows ("Affinity not supported on this configuration") + from resource_plan import physical_cpu_count + for k, v in (("OMP_WAIT_POLICY", "active"), + ("GOMP_SPINCOUNT", "200000"), + ("OMP_DYNAMIC", "FALSE"), + ("OMP_NUM_THREADS", str(physical_cpu_count()))): + e.setdefault(k, v) + # Default Windows misurati sul box di riferimento (docs/tuning-9950x3d-5090.md), + # tutti lossless e tutti setdefault (un override esplicito vince sempre): + # - DIRECT=1: 10.7 GB/s O_DIRECT vs 9.0 buffered (iobench, anche a cache + # calda); nel motore 0.48 -> 1.02 tok/s. Upstream #162: 1.47x. + # - PIPE=1: overlap load/matmul, +8% sopra DIRECT (byte-identico, riordina + # solo l'I/O). PIPE_WORKERS resta al default 8 (sweep 4/8/16 piatto). + # - PILOT_REAL=1: prefetch cross-layer con load veri (unico prefetch + # funzionante su Windows: fadvise e' no-op), +11%, hit rate +19 punti. + e.setdefault("DIRECT", "1") + e.setdefault("PIPE", "1") + e.setdefault("PILOT_REAL", "1") e["COLI_POLICY"]=a.policy if a.ram: e["RAM_GB"]=str(a.ram) if a.ngen: e["NGEN"]=str(a.ngen) diff --git a/c/resource_plan.py b/c/resource_plan.py index 7258180..6b3f2fe 100644 --- a/c/resource_plan.py +++ b/c/resource_plan.py @@ -313,8 +313,11 @@ def environment_for_plan(plan, env=None, cuda_enabled=True): result = dict(env or {}) result.setdefault("COLI_POLICY", plan["policy"]["name"]) result.setdefault("OMP_NUM_THREADS", str(plan["cpu"]["physical_cores"])) - result.setdefault("OMP_PROC_BIND", "spread") - result.setdefault("OMP_PLACES", "cores") + if sys.platform != "win32": + # la libgomp di MinGW non supporta l'affinity su Windows + # ("Affinity not supported on this configuration"): non impostarle li'. + result.setdefault("OMP_PROC_BIND", "spread") + result.setdefault("OMP_PLACES", "cores") if plan["policy"]["name"] == "balanced": result.setdefault("REPIN", "64") ram = plan["tiers"]["ram"] diff --git a/c/tests/test_env_defaults.py b/c/tests/test_env_defaults.py new file mode 100644 index 0000000..12a3c99 --- /dev/null +++ b/c/tests/test_env_defaults.py @@ -0,0 +1,68 @@ +"""env_for: default Windows misurati (DIRECT/PIPE/PILOT_REAL + blocco OMP). + +Carica `coli` come modulo (ha la guardia __main__) e verifica il contratto: +- win32: i tre default I/O e il blocco OMP sono setdefault +- un override esplicito dell'utente vince sempre +- COLI_NO_OMP_TUNE spegne SOLO il blocco OMP, non i default I/O +- non-win32: env_for non tocca nulla di tutto questo +""" +import importlib.machinery +import importlib.util +import os +import sys +import types +import unittest +from pathlib import Path +from unittest import mock + +HERE = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(HERE)) + +_loader = importlib.machinery.SourceFileLoader("coli_cli", str(HERE / "coli")) +_spec = importlib.util.spec_from_loader("coli_cli", _loader) +coli = importlib.util.module_from_spec(_spec) +_loader.exec_module(coli) + + +def args(**over): + base = dict(model="X", policy="quality", ram=0, ngen=0, topp=0, topk=0, + temp=None, repin=0, ctx=0, auto_tier=False, gpu=None, vram=0) + base.update(over) + return types.SimpleNamespace(**base) + + +class EnvDefaultsTest(unittest.TestCase): + def env_for_with(self, environ, platform): + with mock.patch.dict(os.environ, environ, clear=True), \ + mock.patch.object(sys, "platform", platform): + return coli.env_for(args()) + + def test_win32_sets_measured_defaults(self): + e = self.env_for_with({}, "win32") + self.assertEqual(e["DIRECT"], "1") + self.assertEqual(e["PIPE"], "1") + self.assertEqual(e["PILOT_REAL"], "1") + self.assertEqual(e["OMP_WAIT_POLICY"], "active") + self.assertNotIn("OMP_PROC_BIND", e) # MinGW libgomp: niente affinity + + def test_explicit_override_wins(self): + e = self.env_for_with({"DIRECT": "0", "PIPE": "0"}, "win32") + self.assertEqual(e["DIRECT"], "0") + self.assertEqual(e["PIPE"], "0") + self.assertEqual(e["PILOT_REAL"], "1") # non overridden -> default + + def test_kill_switch_scope_is_omp_only(self): + e = self.env_for_with({"COLI_NO_OMP_TUNE": "1"}, "win32") + self.assertNotIn("OMP_WAIT_POLICY", e) + self.assertNotIn("OMP_NUM_THREADS", e) + self.assertEqual(e["DIRECT"], "1") # i default I/O restano attivi + self.assertEqual(e["PIPE"], "1") + + def test_non_windows_untouched(self): + e = self.env_for_with({}, "linux") + for k in ("DIRECT", "PIPE", "PILOT_REAL", "OMP_WAIT_POLICY"): + self.assertNotIn(k, e) + + +if __name__ == "__main__": + unittest.main() diff --git a/c/tests/test_resource_plan.py b/c/tests/test_resource_plan.py index 5a89773..023cb93 100644 --- a/c/tests/test_resource_plan.py +++ b/c/tests/test_resource_plan.py @@ -112,8 +112,13 @@ class ResourcePlanTest(unittest.TestCase): self.assertEqual(env["COLI_CUDA"], "1") self.assertEqual(env["COLI_GPUS"], "1") self.assertEqual(env["OMP_NUM_THREADS"], str(plan["cpu"]["physical_cores"])) - self.assertEqual(env["OMP_PROC_BIND"], "spread") - self.assertEqual(env["OMP_PLACES"], "cores") + if sys.platform == "win32": + # MinGW libgomp: niente affinity su Windows, le chiavi non vanno emesse + self.assertNotIn("OMP_PROC_BIND", env) + self.assertNotIn("OMP_PLACES", env) + else: + self.assertEqual(env["OMP_PROC_BIND"], "spread") + self.assertEqual(env["OMP_PLACES"], "cores") self.assertEqual(env["PIN_GB"], env["CUDA_EXPERT_GB"]) explicit_threads = environment_for_plan(plan, {"OMP_NUM_THREADS": "7",