mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-09 17:36:14 -10:00
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:
1 parent
ecb2fc4502
commit
b89bf5947d
6 files changed
+125
-2
No files matched your search
+7
-2
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user