mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-07 06:15:58 -10:00
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:
1 parent
51a60af92a
commit
b8dbdfe12a
7 files changed
+286
-14
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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") +
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user