From 033c87d0b7a9551d9b97f83389bda6387bb8af98 Mon Sep 17 00:00:00 2001 From: "artem.tarasov" Date: Tue, 28 Jul 2026 00:48:46 +0300 Subject: [PATCH 1/2] Fix Windows probes in the dasllama bench harness --- .../dasLLAMA/performance/profile_common.das | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/modules/dasLLAMA/performance/profile_common.das b/modules/dasLLAMA/performance/profile_common.das index 37ff142198..a25b883ff6 100644 --- a/modules/dasLLAMA/performance/profile_common.das +++ b/modules/dasLLAMA/performance/profile_common.das @@ -321,7 +321,9 @@ def run_capture(cmd : string; first_line : bool) : string { // The running daslang's version, via the box's binary (DASLANG_BIN, default bin/daslang from the // repo-root cwd these tools run in). Takes the last token of the first --version line ("0.6.3"). def das_version() : string { - let bin = empty(get_env_variable("DASLANG_BIN")) ? "bin/daslang" : get_env_variable("DASLANG_BIN") + // cmd resolves `bin\daslang`, not `bin/daslang` (forward slash reads as an option there) + let dflt = is_windows() ? "bin\\daslang" : "bin/daslang" + let bin = empty(get_env_variable("DASLANG_BIN")) ? dflt : get_env_variable("DASLANG_BIN") let line = run_capture("{bin} --version", true) let toks <- split(line, " ") return empty(toks) ? "unknown" : toks[length(toks) - 1] @@ -436,7 +438,8 @@ def private platform_ram_gb() : int { } let gib = 1024.0 * 1024.0 * 1024.0 if (is_windows()) { - // wmic emits "TotalPhysicalMemory=68632934400" + // wmic emits "TotalPhysicalMemory=68632934400"; Windows 11 24H2 removed wmic, so + // fall through to the CIM probe when it yields nothing let raw = run_capture("wmic ComputerSystem get TotalPhysicalMemory /value", false) for (ln in split(raw, "\n")) { let i = find(ln, "=") @@ -444,7 +447,8 @@ def private platform_ram_gb() : int { return int(to_float(strip(slice(ln, i + 1))) / gib + 0.5) } } - return 0 + let ps = run_capture("powershell -NoProfile -Command \"(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory\"", true) + return empty(ps) ? 0 : int(to_float(ps) / gib + 0.5) } if (is_macos()) { return int(to_float(run_capture("sysctl -n hw.memsize", true)) / gib + 0.5) @@ -486,7 +490,22 @@ def private platform_gpu() : string { return strip(slice(ln, i + length("Name="))) } } - return "" + // wmic is gone on Windows 11 24H2+ — same probe via CIM; prefer a physical adapter + // (remote-desktop stacks enumerate their virtual display first) + let ps = run_capture("powershell -NoProfile -Command \"(Get-CimInstance Win32_VideoController).Name\"", false) + var first = "" + for (ln in split(ps, "\n")) { + let name = strip(ln) + if (!empty(name)) { + if (find(name, "Virtual") < 0) { + return name + } + if (empty(first)) { + first = name + } + } + } + return first } if (is_macos()) { let raw = run_capture("system_profiler SPDisplaysDataType", false) @@ -1068,9 +1087,13 @@ struct LlamaBenchRow { //! Run a llama-bench command line (must include `-o json`) and parse its rows. False on a run or //! parse failure. Stdout only — llama-bench keeps progress on stderr, so the pipe stays clean. def run_llama_bench(cmd : string; var rows : array) : bool { + // _popen hands the string to `cmd /c` verbatim; a command that starts with a quote and + // contains more quotes loses its first and last quote to cmd's strip rule, breaking the + // binary path — one extra outer pair is the documented workaround (safe either way) + let shell = is_windows() ? "\"{cmd}\"" : cmd var out = "" unsafe { - popen(cmd) $(f) { + popen(shell) $(f) { if (f != null) { // popen can hand a null handle on spawn failure (bad binary path) out = fread_to_eof(f) } From bab2c32f06077b8c3d401129e15061115043230d Mon Sep 17 00:00:00 2001 From: "artem.tarasov" Date: Tue, 28 Jul 2026 13:00:22 +0300 Subject: [PATCH 2/2] =?UTF-8?q?dasLLAMA=20bench:=20review=20round=20?= =?UTF-8?q?=E2=80=94=20shell-free=20das=5Fversion,=20GPU=20pick=20over=20b?= =?UTF-8?q?oth=20probes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The virtual-adapter preference sat in the CIM branch, which never runs on a box where wmic survived an in-place upgrade (the reviewer's 25H2 box recorded its Parsec adapter). Hoist the selection: collect every Name= line from wmic, fall through to CIM only when that yields nothing, and run one pick over whichever list we got. The virtual filter is now case-insensitive and knows the RDP / Hyper-V / VMware / Citrix / Basic Display spellings, not just Virtual; all-virtual and single-adapter lists still fall back to the first name. das_version goes through fio's run_and_capture instead of the cmd string — which surfaced that popen_argv is only half shell-free on Windows: CreateProcess resolves bin\daslang but not bin/daslang (probe-verified), so normalize slashes before spawning. That also fixes a forward-slashed DASLANG_BIN override, which the cmd path never handled. The caveat is now in run_and_capture's docstring. Smaller review items: 2>nul on both wmic probes (a wmic-less box printed the miss to the console mid-run), the null-handle guard run_llama_bench already had copied to run_capture, and the sacrificial-wrap comment names popen_argv so the next person doesn't re-derive the cmd /? quote rule. Verified: lint + format clean on both files; das_version returns 0.6.4 through the argv path with the default and a forward-slashed override; pick_gpu_name prefers the physical adapter under Parsec-first and RDP-first lists. Co-Authored-By: Claude Fable 5 --- daslib/fio.das | 2 + .../dasLLAMA/performance/profile_common.das | 87 +++++++++++++------ 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/daslib/fio.das b/daslib/fio.das index 6fd1c8afc9..55e162a5dc 100644 --- a/daslib/fio.das +++ b/daslib/fio.das @@ -647,6 +647,8 @@ def rmdir_rec_result(path : string) : fs_result_bool { def run_and_capture(args : array; var output : string&; timeout_sec : float = 0.0) : int { //! Run an external command and capture its stdout+stderr (merged into one pipe by the underlying ``popen_argv``). Returns the process exit code; + //! -1 means the spawn itself failed. No shell is involved, but on Windows the program path (``args[0]``) must use backslashes — + //! CreateProcess does not resolve ``bin/daslang``-style forward-slash relative paths (probe-verified); ``replace(exe, "/", "\\")`` first. var captured : string let exit_code = unsafe(popen_argv(args, timeout_sec, $(f) { if (f != null) { diff --git a/modules/dasLLAMA/performance/profile_common.das b/modules/dasLLAMA/performance/profile_common.das index a25b883ff6..3883904b29 100644 --- a/modules/dasLLAMA/performance/profile_common.das +++ b/modules/dasLLAMA/performance/profile_common.das @@ -305,7 +305,9 @@ def run_capture(cmd : string; first_line : bool) : string { var out = "" unsafe { popen(cmd) $(f) { - out = fread_to_eof(f) + if (f != null) { // popen can hand a null handle on spawn failure (bad binary path) + out = fread_to_eof(f) + } } } out = strip(out) @@ -321,10 +323,22 @@ def run_capture(cmd : string; first_line : bool) : string { // The running daslang's version, via the box's binary (DASLANG_BIN, default bin/daslang from the // repo-root cwd these tools run in). Takes the last token of the first --version line ("0.6.3"). def das_version() : string { - // cmd resolves `bin\daslang`, not `bin/daslang` (forward slash reads as an option there) - let dflt = is_windows() ? "bin\\daslang" : "bin/daslang" - let bin = empty(get_env_variable("DASLANG_BIN")) ? dflt : get_env_variable("DASLANG_BIN") - let line = run_capture("{bin} --version", true) + // shell-free spawn (fio's run_and_capture → popen_argv), so no cmd and no quote/option traps; + // CreateProcess still wants backslashes in the program path (probe-verified: `bin/daslang` + // fails to spawn, `bin\daslang` runs), hence the normalize + var bin = empty(get_env_variable("DASLANG_BIN")) ? "bin/daslang" : get_env_variable("DASLANG_BIN") + if (is_windows()) { + bin = replace(bin, "/", "\\") + } + var out = "" + if (run_and_capture([bin, "--version"], out) != 0) { + return "unknown" + } + var line = strip(out) + let nl = find(line, "\n") + if (nl >= 0) { + line = slice(line, 0, nl) + } let toks <- split(line, " ") return empty(toks) ? "unknown" : toks[length(toks) - 1] } @@ -438,9 +452,10 @@ def private platform_ram_gb() : int { } let gib = 1024.0 * 1024.0 * 1024.0 if (is_windows()) { - // wmic emits "TotalPhysicalMemory=68632934400"; Windows 11 24H2 removed wmic, so - // fall through to the CIM probe when it yields nothing - let raw = run_capture("wmic ComputerSystem get TotalPhysicalMemory /value", false) + // wmic emits "TotalPhysicalMemory=68632934400"; clean Windows 11 24H2+ installs ship + // without wmic (it survives in-place upgrades), so fall through to CIM when it yields + // nothing — and 2>nul keeps the miss off the console + let raw = run_capture("wmic ComputerSystem get TotalPhysicalMemory /value 2>nul", false) for (ln in split(raw, "\n")) { let i = find(ln, "=") if (i >= 0 && find(ln, "TotalPhysicalMemory") >= 0) { @@ -477,35 +492,55 @@ def private line_value(raw, key : string) : string { return "" } +// display-adapter names that belong to a virtual/remote stack (Parsec, RDP, Hyper-V, VMware, +// Citrix, the driverless fallback), not the physical GPU; matched case-insensitively +def private is_virtual_gpu(name : string) : bool { + let low = to_lower(name) + for (marker in fixed_array("virtual", "remote display", "basic display", "hyper-v", "vmware", "citrix")) { + if (find(low, marker) >= 0) { + return true + } + } + return false +} + +// first non-virtual adapter, else the first at all — remote-desktop stacks enumerate their +// virtual display ahead of the physical GPU +def private pick_gpu_name(names : array) : string { + for (name in names) { + if (!is_virtual_gpu(name)) { + return name + } + } + return empty(names) ? "" : names[0] +} + def private platform_gpu() : string { let ov = get_env_variable("DASLLAMA_GPU_NAME") if (!empty(ov)) { return ov } if (is_windows()) { - let raw = run_capture("wmic path win32_VideoController get name /value", false) + // wmic emits "Name=..." lines where present; clean Windows 11 24H2+ installs ship without + // it (survives in-place upgrades), so fall through to the same probe via CIM + var inscope names : array + let raw = run_capture("wmic path win32_VideoController get name /value 2>nul", false) for (ln in split(raw, "\n")) { let i = find(ln, "Name=") if (i >= 0) { - return strip(slice(ln, i + length("Name="))) + names |> push(strip(slice(ln, i + length("Name=")))) } } - // wmic is gone on Windows 11 24H2+ — same probe via CIM; prefer a physical adapter - // (remote-desktop stacks enumerate their virtual display first) - let ps = run_capture("powershell -NoProfile -Command \"(Get-CimInstance Win32_VideoController).Name\"", false) - var first = "" - for (ln in split(ps, "\n")) { - let name = strip(ln) - if (!empty(name)) { - if (find(name, "Virtual") < 0) { - return name - } - if (empty(first)) { - first = name + if (empty(names)) { + let ps = run_capture("powershell -NoProfile -Command \"(Get-CimInstance Win32_VideoController).Name\"", false) + for (ln in split(ps, "\n")) { + let name = strip(ln) + if (!empty(name)) { + names |> push(name) } } } - return first + return pick_gpu_name(names) } if (is_macos()) { let raw = run_capture("system_profiler SPDisplaysDataType", false) @@ -1088,8 +1123,10 @@ struct LlamaBenchRow { //! parse failure. Stdout only — llama-bench keeps progress on stderr, so the pipe stays clean. def run_llama_bench(cmd : string; var rows : array) : bool { // _popen hands the string to `cmd /c` verbatim; a command that starts with a quote and - // contains more quotes loses its first and last quote to cmd's strip rule, breaking the - // binary path — one extra outer pair is the documented workaround (safe either way) + // contains more quotes loses its first and last quote to cmd's strip rule (`cmd /?`), breaking + // the binary path — one extra outer pair is the documented workaround (safe either way). + // Shell-free popen_argv (fio's run_and_capture) would sidestep cmd, but callers hand us a + // full shell string here let shell = is_windows() ? "\"{cmd}\"" : cmd var out = "" unsafe {