older CPUs (experimental): STRATA_ISA_FLOOR=avx|none, and setup compiles it instead of stopping (#394 #595 #623)

- CMake: STRATA_ISA_FLOOR (empty by default) builds ggml-cpu for AVX (Sandy/Ivy Bridge) or SSE4.2
  (Nehalem/Westmere) instead of the build host or AVX2; every other ggml ISA option off. Strata's own
  AVX2/AVX-512 kernels stay in the binary behind their CPU checks. Excludes STRATA_PORTABLE. The ggml
  block follows the Strata_Dirigo fork's (rwkeyes).
- STRATA_FORCE_ISA=avx2|avx|sse caps the engine's CPU probes, so the older-CPU paths run on any PC
  (how this was tested here: no old CPU). cpu_sse42_ok(), isa_floor_build().
- The engine: a normal build still refuses a CPU without AVX2, now pointing at the experimental build;
  a floor build checks its own floor (AVX, or SSE4.2 + POPCNT) and warns that it is experimental and
  slow (or, on an AVX2 CPU, that the normal build is faster).
- setup: a CPU without AVX2 warns ("experimental, slow") and compiles the engine on the PC with the
  floor it has, instead of failing (#595's idea); the prebuilt AVX2 engine is not used there. Recorded
  in BUILD.json; a build folder for another floor is configured afresh. STRATA_ISA_FLOOR=avx|none asks
  for it on any PC. Windows + AMD has no source build: a clear stop. The MCP server's recommendation
  notes it instead of refusing.
- tools/test_setup_oldcpu.py.

Default build: nothing changes (no new CMake definitions, the probes answer as before without the env).
This commit is contained in:
Niko1221 committed 2026-10-03 23:35:52 +02:00
1 parent 51a60af92a
commit b8dbdfe12a
7 files changed
+286 -14

No files matched your search

+33
View File
@@ -21,6 +21,18 @@ endif()
# instead of this machine's (Strata's own AVX-512 kernels are per-file and chosen at run time either way), and
# the MSVC runtime linked in, so no Visual C++ redistributable is needed.
option(STRATA_PORTABLE "build for other PCs: AVX2 baseline, static MSVC runtime" OFF)
# EXPERIMENTAL, opt-in: an engine for CPUs without AVX2 (#394, #595, #623), compiled on that PC. ggml-cpu, which then
# computes every CPU expert row, is built for this floor instead of AVX2 or the build host: "avx" (Sandy/Ivy Bridge:
# AVX, no FMA/F16C/AVX2) or "none" (SSE4.2 + POPCNT: Nehalem/Westmere). Strata's own AVX2/AVX-512 kernels stay in the
# binary behind their CPU checks; the router lookahead gets an AVX1 kernel. Empty (the default) changes nothing.
set(STRATA_ISA_FLOOR "" CACHE STRING "experimental older-CPU build: empty (default, AVX2 floor), avx, or none (SSE4.2)")
set_property(CACHE STRATA_ISA_FLOOR PROPERTY STRINGS "" avx none)
if(NOT STRATA_ISA_FLOOR STREQUAL "" AND NOT STRATA_ISA_FLOOR STREQUAL "avx" AND NOT STRATA_ISA_FLOOR STREQUAL "none")
message(FATAL_ERROR "STRATA_ISA_FLOOR must be empty, avx or none (got '${STRATA_ISA_FLOOR}')")
endif()
if(STRATA_ISA_FLOOR AND STRATA_PORTABLE)
message(FATAL_ERROR "STRATA_ISA_FLOOR (an older-CPU build) and STRATA_PORTABLE (the AVX2 release build) exclude each other")
endif()
if(STRATA_PORTABLE AND MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")
endif()
@@ -868,6 +880,22 @@ if(STRATA_NATIVE_EXPERTS)
foreach(_f GGML_AVX GGML_AVX2 GGML_FMA GGML_F16C GGML_BMI2)
set(${_f} ON CACHE BOOL "" FORCE)
endforeach()
elseif(STRATA_ISA_FLOOR)
# the experimental older-CPU build (STRATA_ISA_FLOOR above): one ggml-cpu for the floor, ggml's "sandybridge"
# feature set (SSE4.2 + AVX) or SSE4.2 alone - not the build host's, which may be newer than the PC it runs on.
# From the Strata_Dirigo fork (rwkeyes). ggml's defaults turn the rest on when GGML_NATIVE is off: all off here.
set(GGML_NATIVE OFF CACHE BOOL "" FORCE)
set(GGML_CPU_ALL_VARIANTS OFF CACHE BOOL "" FORCE)
set(GGML_SSE42 ON CACHE BOOL "" FORCE)
if(STRATA_ISA_FLOOR STREQUAL "avx")
set(GGML_AVX ON CACHE BOOL "" FORCE)
else()
set(GGML_AVX OFF CACHE BOOL "" FORCE)
endif()
foreach(_f GGML_AVX2 GGML_FMA GGML_F16C GGML_BMI2 GGML_AVX_VNNI GGML_AVX512 GGML_AVX512_VBMI GGML_AVX512_VNNI
GGML_AVX512_BF16 GGML_AMX_TILE GGML_AMX_INT8 GGML_AMX_BF16)
set(${_f} OFF CACHE BOOL "" FORCE)
endforeach()
else()
set(GGML_NATIVE ON CACHE BOOL "" FORCE)
endif()
@@ -904,6 +932,11 @@ if(STRATA_NATIVE_EXPERTS)
set_source_files_properties(src/kernels/cpu/kq_avx2.cpp PROPERTIES COMPILE_OPTIONS "-mavx2;-mfma;-mf16c")
set_source_files_properties(src/kernels/cpu/kq_avx1.cpp PROPERTIES COMPILE_OPTIONS "-mavx")
endif()
if(STRATA_ISA_FLOOR STREQUAL "avx")
target_compile_definitions(strata_kernels_cpu PRIVATE STRATA_ISA_FLOOR_AVX=1)
elseif(STRATA_ISA_FLOOR STREQUAL "none")
target_compile_definitions(strata_kernels_cpu PRIVATE STRATA_ISA_FLOOR_NONE=1)
endif()
target_link_libraries(strata_kernels_cpu PUBLIC ggml-cpu ggml-base)
target_compile_definitions(strata_kernels_cpu PUBLIC STRATA_NATIVE_EXPERTS=1)
set(_strata_mmq_types q2_0 iq2_xxs iq2_xs iq2_s iq3_xxs iq3_s iq4_nl iq4_xs q8_0)
+9 -1
View File
@@ -47,8 +47,16 @@ bool cpu_avx512_ok();
/// Whether this CPU (and its OS) runs the AVX2 kernels (AVX, AVX2, FMA, F16C): the floor of every expert kernel
/// (q2_avx2.cpp, iq_avx2.cpp, and ggml-cpu in the portable build). STRATA_FORCE_AVX2 does not change it.
bool cpu_avx2_ok();
/// Whether this CPU (and its OS) runs AVX (Sandy Bridge and newer): the AVX1 router dot (kq_avx1.cpp).
/// Whether this CPU (and its OS) runs AVX (Sandy Bridge and newer): the AVX1 router dot (kq_avx1.cpp) and ggml-cpu in
/// a STRATA_ISA_FLOOR=avx build. The other expert rows then run on ggml-cpu (experimental, slow).
bool cpu_avx1_ok();
/// SSE4.2 + POPCNT: ggml-cpu in a STRATA_ISA_FLOOR=none build.
bool cpu_sse42_ok();
/// STRATA_FORCE_ISA=avx2|avx|sse caps the probes above (3 = no cap, 2 = AVX2, 1 = AVX, 0 = SSE4.2), so the older-CPU
/// paths run on any PC: tests and the experimental builds' checks. ggml-cpu keeps what it was compiled for.
int cpu_isa_cap();
/// The experimental older-CPU build this engine is ("avx", "sse4.2"), or "" for the normal build (AVX2 floor).
const char* isa_floor_build();
/// Whether a native pack's layer of this ggml type runs on Strata's own Q2_0 kernels (AVX2 / AVX-512). On a
/// CPU without AVX2 (a native, non-portable build: ggml-cpu compiled for this CPU's SSE) a Q2_0 layer takes
/// ggml-cpu's own Q2_0 vec_dot like every other native type: slower, but it runs.
+76 -7
View File
@@ -407,6 +407,48 @@ def cpu_info():
return name, avx2, avx512
def _cpuid_floor() -> str:
"""Below AVX2 (Windows): "avx" when the CPU has AVX and the OS saves the YMM registers, "sse4.2" with SSE4.2 and
POPCNT, else ""."""
try:
regs = (ctypes.c_uint32 * 4)()
_run_stub(bytes([0x53, 0x49, 0x89, 0xC8, 0x89, 0xD0, 0x31, 0xC9, 0x0F, 0xA2, # push rbx; r8=rcx; eax=edx; ecx=0; cpuid
0x41, 0x89, 0x00, 0x41, 0x89, 0x58, 0x04, 0x41, 0x89, 0x48, 0x08, # [r8]=eax, [r8+4]=ebx, [r8+8]=ecx
0x41, 0x89, 0x50, 0x0C, 0x5B, 0xC3]), # [r8+12]=edx; pop rbx
ctypes.addressof(regs), 1)
ecx1 = regs[2]
if (ecx1 >> 27) & 1 and (ecx1 >> 28) & 1: # OSXSAVE, AVX
xcr0 = (ctypes.c_uint32 * 2)()
_run_stub(bytes([0x49, 0x89, 0xC8, 0x31, 0xC9, 0x0F, 0x01, 0xD0, # r8=rcx; ecx=0; xgetbv
0x41, 0x89, 0x00, 0x41, 0x89, 0x50, 0x04, 0xC3]), ctypes.addressof(xcr0))
if xcr0[0] & 6 == 6:
return "avx"
return "sse4.2" if (ecx1 >> 20) & 1 and (ecx1 >> 23) & 1 else ""
except Exception:
return ""
def cpu_floor(avx2: bool) -> str:
"""The experimental older-CPU build this PC needs (#394 #595 #623): "" with AVX2 (the normal engine), "avx" (Sandy /
Ivy Bridge, AMD Bulldozer), "none" (SSE4.2 + POPCNT: Nehalem, Westmere), or "unsupported". STRATA_ISA_FLOOR=avx|
none asks for that build on any PC (testing it on a newer one)."""
forced = os.environ.get("STRATA_ISA_FLOOR", "").strip().lower()
if forced in ("avx", "none"):
return forced
if avx2:
return ""
if WIN:
f = _cpuid_floor()
else:
try:
txt = open("/proc/cpuinfo").read()
flags = set(re.search(r"^flags\s*:\s*(.*)$", txt, re.M).group(1).split())
except (OSError, AttributeError):
flags = set()
f = "avx" if "avx" in flags else "sse4.2" if {"sse4_2", "popcnt"} <= flags else ""
return {"avx": "avx", "sse4.2": "none"}.get(f, "unsupported")
def _cpuid_avx512_full() -> bool:
"""Windows has no feature bit for VNNI / VBMI: ask the CPU (CPUID leaf 7) through a tiny machine-code stub."""
try:
@@ -1708,7 +1750,9 @@ def build_engine_hip(gpu, llama, vision="none") -> Path:
src, vsrc = source_hash(ENGINE_SOURCES), source_hash(VISION_SOURCES)
archs = sorted(set(gpu.get("archs") or [gpu["arch"]]))
has_archs = set(archs) <= set(meta.get("archs", []))
engine_ok = meta.get("backend") == "hip" and (eng / EXE).exists() and meta.get("src") == src and has_archs
floor = cpu_floor(cpu_info()[1]) # "" on an AVX2 CPU: the normal engine
engine_ok = meta.get("backend") == "hip" and (eng / EXE).exists() and meta.get("src") == src and has_archs and \
(meta.get("isa_floor") or "") == floor
vision_ok = vision == "none" or ((eng / VEXE).exists() and meta.get("vision_src") == vsrc)
if engine_ok and vision_ok:
ok("engine already built for this PC")
@@ -1736,10 +1780,10 @@ def build_engine_hip(gpu, llama, vision="none") -> Path:
f"-DCMAKE_HIP_COMPILER={root / 'llvm' / 'bin' / 'clang++'}", f"-DCMAKE_HIP_COMPILER_ROCM_ROOT={root}",
"-DCMAKE_PREFIX_PATH=" + ";".join([str(root), *libs]),
f"-DCMAKE_HIP_FLAGS=--rocm-path={root} --rocm-device-lib-path={bitcode}",
f"-DSTRATA_GGML_DIR={llama}"], None, "")
f"-DSTRATA_GGML_DIR={llama}", *isa_floor_defs(floor, ROOT / "build-hip", meta)], None, "")
shutil.copy2(ROOT / "build-hip" / EXE, eng / EXE)
meta = {"source": "local-hip", "backend": "hip", "version": source_version(), "archs": archs, "vision": "none",
"lib_dirs": dirs, "src": src}
"lib_dirs": dirs, "src": src, **({"isa_floor": floor} if floor else {})}
if vision != "none":
return build_vision_cpu(eng, stamp, meta, llama, vsrc)
stamp.write_text(json.dumps(meta, indent=1))
@@ -2064,6 +2108,14 @@ def source_hash(parts) -> str:
return h.hexdigest()[:16]
def isa_floor_defs(floor: str, bdir: Path, meta: dict) -> list:
"""The experimental older-CPU build's CMake definition (STRATA_ISA_FLOOR, CMakeLists.txt), none for the normal
build. A build folder configured for another floor is configured afresh: ggml's CPU options are cached there."""
if (meta.get("isa_floor") or "") != floor and (bdir / "CMakeCache.txt").exists():
(bdir / "CMakeCache.txt").unlink()
return [f"-DSTRATA_ISA_FLOOR={floor}"] if floor else []
def engine_defs(archs) -> list:
"""Extra CMake definitions for the engine: the experimental Pascal/Volta build (#295) for cards below sm_75."""
return ["-DSTRATA_EXPERIMENTAL_SM60=ON"] if min(int(x) for x in archs) < 75 else []
@@ -2098,7 +2150,9 @@ def build_engine(gpu, vision, yes, llama) -> Path:
# a card the engine has no code for (a GPU added with --gpus, #128) needs a compile even when the source is the
# same; the compile keeps the generations it was built for
new_arch = local and not set(archs) <= built
engine_ok = local and (eng / EXE).exists() and meta.get("src") == src and not new_arch
floor = cpu_floor(cpu_info()[1]) # "" on an AVX2 CPU: the normal engine
engine_ok = local and (eng / EXE).exists() and meta.get("src") == src and not new_arch and \
(meta.get("isa_floor") or "") == floor
vision_ok = not want_vision or ((eng / VEXE).exists() and (not local or meta.get("vision_src") == vsrc))
if engine_ok and vision_ok:
ok("engine already built for this PC")
@@ -2114,7 +2168,8 @@ def build_engine(gpu, vision, yes, llama) -> Path:
if local and (eng / EXE).exists() else " Compiling the Strata engine for your GPU (10-20 minutes, once) ...")
cmake_build(ROOT, ROOT / "build", "strata",
["-DSTRATA_ENABLE_CUDA=ON", "-DSTRATA_BUILD_TESTS=OFF", f"-DCMAKE_CUDA_ARCHITECTURES={cuda_archs}",
f"-DCMAKE_CUDA_COMPILER={nvcc}", f"-DSTRATA_GGML_DIR={llama}", *engine_defs(archs)],
f"-DCMAKE_CUDA_COMPILER={nvcc}", f"-DSTRATA_GGML_DIR={llama}", *engine_defs(archs),
*isa_floor_defs(floor, ROOT / "build", meta)],
vcvars, "build-strata.bat")
shutil.copy2(ROOT / "build" / EXE, eng / EXE)
if not vision_ok:
@@ -2128,7 +2183,8 @@ def build_engine(gpu, vision, yes, llama) -> Path:
dirs = [str(d) for d in (bindir, bindir / "x64", bindir.parent / "lib64") if d.is_dir()]
stamp.write_text(json.dumps({"source": "local", "version": source_version(), "archs": archs,
"vision": vision,
"cuda_dirs": dirs, "src": src, "vision_src": vsrc if want_vision else None}, indent=1))
"cuda_dirs": dirs, "src": src, "vision_src": vsrc if want_vision else None,
**({"isa_floor": floor} if floor else {})}, indent=1))
ok(f"engine compiled: {eng / EXE}")
return eng
@@ -3466,8 +3522,21 @@ def main() -> int:
"the model may not start or may use less VRAM. Set it to \"System managed\": System > About > "
"Advanced system settings > Performance > Advanced > Virtual memory")
ok(f"CPU: {cpu} ({'AVX-512' if avx512 else 'AVX2' if avx2 else 'no AVX2'})")
floor = cpu_floor(avx2)
if floor == "unsupported":
fail("this CPU has neither AVX2 nor SSE4.2; Strata needs at least SSE4.2 (Intel Nehalem, 2008, or newer)")
if not avx2:
fail("this CPU has no AVX2; Strata needs at least AVX2")
# #394 #595 #623: the ready-made engine is AVX2; an older CPU gets one compiled here, whose CPU experts run on
# ggml-cpu's kernels for this CPU. Experimental: measured only on newer CPUs with the older path forced, and by
# users on a few Xeons. A warning, not a stop.
warn(f"this CPU has no AVX2: Strata support for it is EXPERIMENTAL and slow. Setup compiles the engine on this "
f"PC for {'AVX' if floor == 'avx' else 'SSE4.2'} (STRATA_ISA_FLOOR={floor}; 10-20 minutes, once), and the "
"CPU's share of the experts runs on ggml-cpu's kernels, a few times slower than on an AVX2 CPU. "
"See \"Older CPUs\" in docs/INSTALL.md")
if hip and WIN:
fail("the older-CPU engine is compiled from source, and setup compiles the AMD engine on Linux only",
"use Linux for an AMD card on this CPU, or an NVIDIA card")
a.build = True
if a.check:
say()
for m, d in MODELS.items():
+44
View File
@@ -21,9 +21,26 @@ ExpertLayout g_layout;
const ExpertLayout& expert_layout() { return g_layout; }
int cpu_isa_cap() {
// STRATA_FORCE_ISA (tests, the experimental older-CPU builds): the engine's own dispatch acts as if this CPU
// stopped at that level. ggml-cpu is not affected: it runs what the build compiled it for.
static const int cap = [] {
const char* f = std::getenv("STRATA_FORCE_ISA");
if (f == nullptr || f[0] == '\0') return 3;
const std::string v(f);
if (v == "avx2") return 2;
if (v == "avx") return 1;
if (v == "sse" || v == "sse4.2" || v == "none") return 0;
std::fprintf(stderr, "strata: STRATA_FORCE_ISA=%s is not avx2, avx or sse; ignored\n", f);
return 3;
}();
return cap;
}
bool cpu_avx512_ok() {
static const bool ok = [] {
if (const char* f = std::getenv("STRATA_FORCE_AVX2"); f != nullptr && f[0] == '1') return false;
if (cpu_isa_cap() < 3) return false;
unsigned r[4] = {0, 0, 0, 0};
auto cpuid = [&](unsigned leaf, unsigned sub) {
#if defined(_MSC_VER)
@@ -55,6 +72,7 @@ bool cpu_avx512_ok() {
bool cpu_avx2_ok() {
static const bool ok = [] {
if (cpu_isa_cap() < 2) return false;
unsigned r[4] = {0, 0, 0, 0};
auto cpuid = [&](unsigned leaf, unsigned sub) {
#if defined(_MSC_VER)
@@ -89,6 +107,7 @@ bool cpu_avx1_ok() {
// AVX (Sandy Bridge, 2011): AVX + OSXSAVE with the OS saving the YMM state; FMA, F16C and AVX2 not needed.
// From the Strata_Dirigo fork (rwkeyes).
static const bool ok = [] {
if (cpu_isa_cap() < 1) return false;
unsigned r[4] = {0, 0, 0, 0};
#if defined(_MSC_VER)
int x[4];
@@ -110,6 +129,31 @@ bool cpu_avx1_ok() {
return ok;
}
bool cpu_sse42_ok() {
static const bool ok = [] {
unsigned r[4] = {0, 0, 0, 0};
#if defined(_MSC_VER)
int x[4];
__cpuidex(x, 1, 0);
for (int i = 0; i < 4; ++i) r[i] = (unsigned) x[i];
#else
__cpuid_count(1, 0, r[0], r[1], r[2], r[3]);
#endif
return ((r[2] >> 20) & 1u) && ((r[2] >> 23) & 1u); // SSE4.2, POPCNT
}();
return ok;
}
const char* isa_floor_build() {
#if defined(STRATA_ISA_FLOOR_AVX)
return "avx";
#elif defined(STRATA_ISA_FLOOR_NONE)
return "sse4.2";
#else
return "";
#endif
}
std::string cpu_name() {
unsigned r[12] = {};
#if defined(_MSC_VER)
+26 -4
View File
@@ -1923,12 +1923,33 @@ int main(int argc, char** argv) {
// ... and nothing runs on a CPU without AVX2: every CPU expert kernel is AVX2 at least (the AVX-512 ones are
// chosen above it), and so is ggml-cpu in the release build, which the native pack's layout load initializes
// next. Refused here, by name, rather than an illegal instruction in the first expert.
if (!strata::kernels::cpu::cpu_avx2_ok()) {
std::fprintf(stderr, "strata generate: this CPU (%s) does not support AVX2 with FMA and F16C, which every CPU "
"expert kernel needs; Strata runs on Intel Haswell (2013), AMD Zen (2017) or newer\n",
// The experimental older-CPU build (STRATA_ISA_FLOOR=avx|none, compiled on that PC; #394 #595 #623) has ggml-cpu
// for that floor, so a native pack's experts run there on ggml-cpu (every AVX2 kernel is behind cpu_avx2_ok).
const char* isa_floor = strata::kernels::cpu::isa_floor_build();
if (!strata::kernels::cpu::cpu_avx2_ok() && isa_floor[0] == '\0') {
std::fprintf(stderr, "strata generate: this CPU (%s) does not support AVX2 with FMA and F16C, which this engine's "
"CPU expert kernels need; Strata runs on Intel Haswell (2013), AMD Zen (2017) or newer. "
"Older CPUs are EXPERIMENTAL and slow: setup compiles an engine for them on this PC "
"(STRATA_ISA_FLOOR, see \"Older CPUs\" in docs/INSTALL.md)\n",
strata::kernels::cpu::cpu_name().c_str());
return 2;
}
if (isa_floor[0] != '\0') {
const bool avx_floor = std::strcmp(isa_floor, "avx") == 0;
if (avx_floor ? !strata::kernels::cpu::cpu_avx1_ok() : !strata::kernels::cpu::cpu_sse42_ok()) {
std::fprintf(stderr, "strata generate: this engine is the older-CPU build for %s, which this CPU (%s) does "
"not have%s\n", avx_floor ? "AVX" : "SSE4.2 with POPCNT",
strata::kernels::cpu::cpu_name().c_str(),
avx_floor ? "; compile it with STRATA_ISA_FLOOR=none" : "");
return 2;
}
if (!strata::kernels::cpu::cpu_avx2_ok())
std::fprintf(stderr, "strata generate: EXPERIMENTAL older-CPU build (ggml-cpu for %s): this CPU has no AVX2, "
"so the CPU's experts run on ggml-cpu's kernels - expect it to be slow\n", isa_floor);
else
std::fprintf(stderr, "strata generate: this is the older-CPU build (ggml-cpu for %s); this CPU has AVX2, "
"and the normal build is faster on it\n", isa_floor);
}
std::string err;
if (!o.native_head_gguf.empty() && !o.stream_token) {
@@ -2001,7 +2022,8 @@ int main(int argc, char** argv) {
else if (!strata::kernels::cpu::cpu_avx512_ok())
std::fprintf(stderr, "strata generate: this CPU has no AVX-512: the expert kernels run on %s "
"(multi-token for the i-quant gate/up rows)\n",
std::getenv("STRATA_NO_IQ256") == nullptr ? "AVX-2" : "ggml-cpu vec_dot (STRATA_NO_IQ256 set)");
!strata::kernels::cpu::cpu_avx2_ok() ? "ggml-cpu vec_dot (no AVX2: the older-CPU build)"
: std::getenv("STRATA_NO_IQ256") == nullptr ? "AVX-2" : "ggml-cpu vec_dot (STRATA_NO_IQ256 set)");
strata::core::ModelGeometry g; // canonical defaults; the model file overrides the MoE shape below
int64_t K = 10;
// THE ROPE CONFIG RESOLVES HERE, BEFORE ANY WEIGHT MOVES - the CLI and the model file have both spoken,
+3 -2
View File
@@ -680,8 +680,9 @@ class Strata:
ctx = 32768 if vram < 14 else 65536 if vram < 20 else 131072
if vram < 11:
notes.append("less than 12 GB of VRAM: it runs, but slowly (most experts stay on the CPU)")
if hw.get("cpu", {}).get("avx2") is False:
return {"family": None, "model": None, "why": "this CPU has no AVX2; Strata needs at least AVX2"}
if hw.get("cpu", {}).get("avx2") is False: # #623: a warning, not a stop (setup compiles for it)
notes.append("this CPU has no AVX2: EXPERIMENTAL and slow - setup compiles the engine on this PC for the "
"older CPU (10-20 minutes), and the CPU's share of the experts runs a few times slower")
if backend == "hip":
notes.append("AMD (experimental, Linux): the engine is compiled during setup; no images")
cmd = (("START-HERE.bat --setup" if WIN else "./setup.sh --setup") +
+95
View File
@@ -0,0 +1,95 @@
"""Tests for setup.py's experimental older-CPU build (#394 #595 #623): which floor a CPU without AVX2 gets, the CMake
definition, a build folder configured afresh when the floor changes, and the MCP server's note instead of a refusal.
No compiler, no GPU.
python -m unittest tools.test_setup_oldcpu
"""
from __future__ import annotations
import io
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "tools"))
import setup # noqa: E402
def cpuinfo(flags: str):
return mock.patch("builtins.open", lambda *a, **k: io.StringIO(f"model name\t: Old Xeon\nflags\t\t: {flags}\n"))
class FloorTest(unittest.TestCase):
def setUp(self):
self.env = mock.patch.dict(os.environ, {"STRATA_ISA_FLOOR": ""})
self.env.start()
def tearDown(self):
self.env.stop()
def test_avx2_cpu_gets_the_normal_engine(self):
self.assertEqual(setup.cpu_floor(True), "")
def test_linux_flags(self):
with mock.patch.object(setup, "WIN", False):
with cpuinfo("fpu sse4_1 sse4_2 popcnt avx xsave"):
self.assertEqual(setup.cpu_floor(False), "avx")
with cpuinfo("fpu sse4_1 sse4_2 popcnt"):
self.assertEqual(setup.cpu_floor(False), "none")
with cpuinfo("fpu sse3 ssse3"):
self.assertEqual(setup.cpu_floor(False), "unsupported")
def test_windows_probe(self):
with mock.patch.object(setup, "WIN", True):
for probe, want in (("avx", "avx"), ("sse4.2", "none"), ("", "unsupported")):
with mock.patch.object(setup, "_cpuid_floor", lambda p=probe: p):
self.assertEqual(setup.cpu_floor(False), want)
def test_env_asks_for_a_floor_build_on_any_cpu(self):
for v in ("avx", "none", "AVX"):
with mock.patch.dict(os.environ, {"STRATA_ISA_FLOOR": v}):
self.assertEqual(setup.cpu_floor(True), v.lower())
with mock.patch.dict(os.environ, {"STRATA_ISA_FLOOR": "bogus"}):
self.assertEqual(setup.cpu_floor(True), "")
@unittest.skipUnless(os.name == "nt", "the CPUID stub runs on Windows")
def test_this_pc_probe(self):
self.assertIn(setup._cpuid_floor(), ("avx", "sse4.2")) # any x86-64 PC that runs the tests
class DefsTest(unittest.TestCase):
def test_defs_and_fresh_configure(self):
with tempfile.TemporaryDirectory() as d:
b = Path(d)
cache = b / "CMakeCache.txt"
self.assertEqual(setup.isa_floor_defs("", b, {}), []) # the normal build: nothing added
cache.write_text("x")
self.assertEqual(setup.isa_floor_defs("avx", b, {"isa_floor": "avx"}), ["-DSTRATA_ISA_FLOOR=avx"])
self.assertTrue(cache.exists()) # same floor: the folder is kept
self.assertEqual(setup.isa_floor_defs("none", b, {"isa_floor": "avx"}), ["-DSTRATA_ISA_FLOOR=none"])
self.assertFalse(cache.exists()) # another floor: configured afresh
cache.write_text("x")
self.assertEqual(setup.isa_floor_defs("", b, {"isa_floor": "none"}), [])
self.assertFalse(cache.exists()) # back to the normal build
cache.write_text("x")
setup.isa_floor_defs("", b, {})
self.assertTrue(cache.exists()) # the normal build's folder untouched
class McpTest(unittest.TestCase):
def test_no_avx2_is_a_note_not_a_refusal(self):
import strata_mcp as M
s = M.Strata(ROOT)
gpu = [{"index": 0, "name": "x", "vram_gb": 24.0, "usable": True, "vendor": "nvidia"}]
r = s.recommend({"ram_gb": 64, "gpus": gpu, "cpu": {"avx2": False}})
self.assertIsNotNone(r["model"])
self.assertTrue(any("no AVX2" in n and "EXPERIMENTAL" in n for n in r["notes"]))
if __name__ == "__main__":
unittest.main()