Windows: an engine started from a low-priority program no longer reads its model at Low disk priority; its disk priority goes back to Normal and one plain line says so (#4733)

A Windows process starts with the I/O priority of the program that started it. Run from a Task Scheduler task, a service, or a terminal that an IDE or coding agent keeps at low priority, the engine inherited Low I/O priority: the reporter measured PLE direct reads about 15x slower, cold prompt reads 3-5x slower and decode 14% slower on the same drive, and the engine blamed the drive. Both engines (strata and strata-dsv41) now read ProcessIoPriority at start and set it to Normal when it is lower (no privilege needed to go from Low to Normal). STRATA_KEEP_IO_PRIORITY=1 leaves it as inherited. The CPU priority class is not touched. New io_priority_test (Windows): started at Low it ends at Normal, started at Normal it is untouched, KEEP leaves Low alone. Tested here: that test passes on Windows; the HIP engine builds. Not tested: the reporter's slowdown itself (needs their drive and a low-priority launcher with an engine run).
This commit is contained in:
Niko1221 committed 2026-10-31 13:01:12 +01:00
1 parent ecb2fc4502
commit b89bf5947d
6 files changed
+125 -2

No files matched your search

+7 -2
View File
@@ -395,6 +395,11 @@ if(STRATA_BUILD_TESTS)
add_executable(host_limits_test tests/platform/host_limits_test.cpp)
target_include_directories(host_limits_test PRIVATE include)
add_test(NAME host_limits_test COMMAND host_limits_test)
# #4733: an engine started at Low I/O priority goes back to Normal (Windows; else skips)
add_executable(io_priority_test tests/platform/io_priority_test.cpp src/platform/io_priority.cpp)
target_include_directories(io_priority_test PRIVATE include)
add_test(NAME io_priority_test COMMAND io_priority_test)
set_tests_properties(io_priority_test PROPERTIES SKIP_RETURN_CODE 77)
endif()
# ---- strata_plan ----------------------------------------------------------
@@ -410,7 +415,7 @@ target_link_libraries(strata-plan PRIVATE strata_plan)
# ---- strata_core (GPU runtime) ---------------------------------------------
# Only built when a GPU backend is on, so the Phase 1 targets keep working without a toolkit.
if(STRATA_ENABLE_CUDA OR STRATA_ENABLE_HIP)
add_library(strata_core STATIC src/core/device.cu src/core/pinned.cu src/platform/memory.cpp src/core/graph.cpp src/core/weights.cpp
add_library(strata_core STATIC src/core/device.cu src/core/pinned.cu src/platform/memory.cpp src/platform/io_priority.cpp src/core/graph.cpp src/core/weights.cpp
src/core/layout.cpp)
target_include_directories(strata_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# the DFlash2 drafter (src/core/dflash2.cpp, src/kernels/cuda/dflash2.cu) exists in the CUDA and HIP builds; the Metal and SYCL builds compile without it
@@ -917,7 +922,7 @@ if(STRATA_ENABLE_CUDA OR STRATA_ENABLE_HIP)
target_link_libraries(strata_dsv41_kernels PUBLIC ${_strata_gpu_runtime_target})
add_executable(strata-dsv41 src/dsv41/ds4_main.cpp src/dsv41/ds4_model.cu src/dsv41/ds4_kernels.cu src/dsv41/ds4_vision.cu
src/dsv41/ds4_gguf.cu src/dsv41/ds4_gguf_cpu.cpp src/dsv41/ds4_iq.cu src/dsv41/ds4_iq_cpu.cpp
src/dsv41/ds4_tq.cu src/dsv41/ds4_tq_cpu.cpp src/dsv41/ds4_gg_cpu.cpp src/dsv41/ds4_packinfo.cpp)
src/dsv41/ds4_tq.cu src/dsv41/ds4_tq_cpu.cpp src/dsv41/ds4_gg_cpu.cpp src/dsv41/ds4_packinfo.cpp src/platform/io_priority.cpp)
# the commit the binary was built from, readable with --version (set -DSTRATA_BUILD_ID=... for a tree without .git)
if(NOT STRATA_BUILD_ID AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/BUILD_ID) # (a git archive has no .git: the packer writes the commit here)
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/BUILD_ID STRATA_BUILD_ID LIMIT_COUNT 1)
+21
View File
@@ -0,0 +1,21 @@
// strata/platform/io_priority.hpp - #4733. On Windows a process starts with the disk (I/O) priority of the program that
// started it. An engine started from a low-priority launcher (a Task Scheduler task, a service, a terminal that an IDE or a
// coding agent runs at low priority) therefore reads its model at Low I/O priority, even when its CPU priority is Normal:
// the reporter measured PLE direct reads about 15x slower, cold prompt reads 3-5x slower and decode 14% slower on the same
// drive, with the drive blamed ("--ple-io direct measures slow here"). restore_normal_io_priority() puts the process back
// to Normal when it started lower, and says so in one plain line. Raising from Low to Normal needs no privilege.
// STRATA_KEEP_IO_PRIORITY=1 leaves it as inherited (for a person who wants the engine in the background on purpose).
// Other systems: nothing to do.
#pragma once
#include <cstdio>
namespace strata::platform {
/// Windows: puts the process's I/O priority (ProcessIoPriority: 0 very low, 1 low, 2 normal) back to Normal when it started
/// lower, and prints one plain line to `log` (none when it is null). Returns the value the process had before (2 when nothing
/// was changed; -1 when it could not be read, STRATA_KEEP_IO_PRIORITY=1 is set, or on another system).
/// Defined in src/platform/io_priority.cpp (windows.h stays out of every file that includes this).
int restore_normal_io_priority(std::FILE* log = stderr);
} // namespace strata::platform
+2
View File
@@ -28,6 +28,7 @@
#include "ds4_model.hpp"
#include "ds4_experts_rule.hpp"
#include "ds4_os.hpp"
#include "strata/platform/io_priority.hpp"
#include "ds4_packinfo.hpp"
#include "ds4_slotmeta.hpp"
@@ -1283,6 +1284,7 @@ int spec_eval(strata::ds4::Model& m, const std::string& ids_path, int n, int win
#endif
int main(int argc, char** argv) {
strata::ds4::os::fine_timer();
strata::platform::restore_normal_io_priority(); // #4733
#if defined(STRATA_METAL)
if (argc > 1 && std::string(argv[1]) == "--sampler-test") return sampler_stat::run(argc - 1, argv + 1); // (the sampler's statistics harness)
#endif
+50
View File
@@ -0,0 +1,50 @@
// #4733: see include/strata/platform/io_priority.hpp
#include "strata/platform/io_priority.hpp"
#include <cstdlib>
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
namespace strata::platform {
int restore_normal_io_priority(std::FILE* log) {
#if defined(_WIN32)
const char* keep = std::getenv("STRATA_KEEP_IO_PRIORITY");
if (keep != nullptr && keep[0] == '1') return -1;
using Query = LONG(NTAPI*)(HANDLE, ULONG, PVOID, ULONG, PULONG);
using Set = LONG(NTAPI*)(HANDLE, ULONG, PVOID, ULONG);
HMODULE nt = GetModuleHandleW(L"ntdll.dll");
if (nt == nullptr) return -1;
auto query = reinterpret_cast<Query>(reinterpret_cast<void*>(GetProcAddress(nt, "NtQueryInformationProcess")));
auto set = reinterpret_cast<Set>(reinterpret_cast<void*>(GetProcAddress(nt, "NtSetInformationProcess")));
if (query == nullptr || set == nullptr) return -1;
constexpr ULONG kProcessIoPriority = 33;
ULONG now = 2, ret = 0;
if (query(GetCurrentProcess(), kProcessIoPriority, &now, sizeof now, &ret) != 0 || ret < sizeof now) return -1;
if (now >= 2) return (int) now;
ULONG want = 2;
if (set(GetCurrentProcess(), kProcessIoPriority, &want, sizeof want) != 0) {
if (log != nullptr)
std::fprintf(log, "strata: this program runs at a low disk priority (inherited from the program that started it) "
"and Windows did not let it go back to normal; reading the model can be several times slower (#4733)\n");
return (int) now;
}
if (log != nullptr)
std::fprintf(log, "strata: the program that started the engine runs at a low disk priority, which the engine inherits and "
"which makes model reads several times slower; the engine's disk priority is back to normal (#4733)\n");
return (int) now;
#else
(void) log;
return -1;
#endif
}
} // namespace strata::platform
+2
View File
@@ -16,6 +16,7 @@
// (`phase-2-correct-engine.md:5-9`). What it is FOR is the honest tok/s figure and the logit dump.
#include "strata/platform/memory.hpp"
#include "strata/platform/io_priority.hpp"
#include "strata/core/adapt_async_default.hpp"
#include "strata/core/arch_defaults.hpp"
#include "strata/core/nv_defaults.hpp"
@@ -2902,6 +2903,7 @@ int main(int argc, char** argv) {
// pipe or a file is block-buffered, so a program that dies loses every line it had already printed - which
// turns "it crashed at step 7" into "it crashed somewhere", and the difference is a debugging session.
std::setvbuf(stdout, nullptr, _IONBF, 0);
strata::platform::restore_normal_io_priority(); // #4733: not the low disk priority of the program that started this one
#if defined(STRATA_USE_HIP) && defined(_WIN32)
{ // 0.2.1.2: hipblas / libhipblaslt / rocblas are delay-loaded (CMakeLists.txt) and live in <exe folder>\rocm\bin, which only the
// launcher puts on PATH; make the folder part of this process's DLL search, so `strata.exe --version` run straight from the
+43
View File
@@ -0,0 +1,43 @@
// io_priority_test (#4733): on Windows a process that starts at Low I/O priority is put back to Normal; elsewhere the call does nothing.
#include "strata/platform/io_priority.hpp"
#include <cstdio>
#include <cstdlib>
#if defined(_WIN32)
#include <windows.h>
static unsigned long io_prio() {
using Query = LONG(NTAPI*)(HANDLE, ULONG, PVOID, ULONG, PULONG);
auto q = reinterpret_cast<Query>(reinterpret_cast<void*>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationProcess")));
ULONG v = 99, r = 0;
return q != nullptr && q(GetCurrentProcess(), 33, &v, sizeof v, &r) == 0 ? v : 99;
}
static bool set_io_prio(ULONG v) {
using Set = LONG(NTAPI*)(HANDLE, ULONG, PVOID, ULONG);
auto s = reinterpret_cast<Set>(reinterpret_cast<void*>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtSetInformationProcess")));
return s != nullptr && s(GetCurrentProcess(), 33, &v, sizeof v) == 0;
}
#endif
int main() {
#if defined(_WIN32)
int bad = 0;
// 1. started at Low: back to Normal, and the call reports what it found
if (!set_io_prio(1)) { std::printf("io_priority_test: SKIP (cannot lower the I/O priority here)\n"); return 77; }
int was = strata::platform::restore_normal_io_priority(stderr);
if (was != 1 || io_prio() != 2) { std::printf("FAIL lowered: was=%d now=%lu\n", was, io_prio()); ++bad; }
// 2. already Normal: untouched, reported as 2
was = strata::platform::restore_normal_io_priority(nullptr);
if (was != 2 || io_prio() != 2) { std::printf("FAIL normal: was=%d now=%lu\n", was, io_prio()); ++bad; }
// 3. STRATA_KEEP_IO_PRIORITY=1 leaves a low value alone
_putenv("STRATA_KEEP_IO_PRIORITY=1");
set_io_prio(1);
was = strata::platform::restore_normal_io_priority(nullptr);
if (was != -1 || io_prio() != 1) { std::printf("FAIL keep: was=%d now=%lu\n", was, io_prio()); ++bad; }
std::printf(bad == 0 ? "io_priority_test: OK\n" : "io_priority_test: %d FAILED\n", bad);
return bad == 0 ? 0 : 1;
#else
std::printf("io_priority_test: SKIP (Windows only)\n");
return 77;
#endif
}